一、概念
1.1、定义
- 外部类:这是一个相对内部类的概念,如果一个类中嵌套了另外一个类,我们就把这个类叫做外部类。
- 内部类:顾名思义,就是定义在里边的那个类。 内部类可以作用在方法里以及外部类里,作用在方法里称为局部内部类,作用在外部类里分为实例内部类和静态内部类。
二、内部类与外部类的互访
2.1、外部类和内部类的类访问修饰符
- 外部类只有两种访问控制符,即public和default(包访问控制级别)。原因:外部类的上一级程序单元是包,所以它只有两种作用域:同一个包内和任何位置,这样只需要用public和default即可,用public修饰的类可以被任何位置的其他类访问,而不添加访问控制符的类的访问控制权限为包访问级别,即该类只能被同一个包的其他类访问。
- 内部类有四种访问控制符(public,private,protected),因为内部类实际上就是外部类的一个成员,所以内部类的上一级程序单元为类,因此它有四种作用域:同一个类、同一个包、父子类和任何位置,因此可以使用四种访问控制权限。
另外补充知识点 private,public,protected,default在访问权限上有什么不同。
1、private:访问权限最低的访问控制符,被它修饰的变量只能访问本类的对象。
2、public:访问权限最高,不仅是本类,子类,本包,其他的包,都可以去访问 它修饰的对象。
3、default:访问权限限制在本类以及本包内。
4、protected:访问的范围是本类,本包内,子类,但别的包不可以访问。
2.2、访问规则
- (1)内部类可以直接访问外部类的成员,包括私有
- (2)外部类要想访问内部类成员,必须创建对象
2.2.1、内部类的分类
- 成员内部类
- 局部内部类
- 静态内部类
- 匿名内部类
2.2.1、 内部类访问外部类
里面的可以自由访问外面的,规则和static一样。(访问非静态时必须先创建对象)
具体如下:
(1)非静态内部类的非静态方法:直接访问
public class Outter {
int i = 5;
static String string = "Hello";
class Inner1{
void Test1 () {
System.out.println(i);
System.out.println(string);
}
}
}
(2)静态内部类的非静态方法:因为静态方法访问非静态外部成员需先创建实例,所以访问i时必须先new外部类
public class Outter {
int i = 5;
static String string = "Hello";
static class Inner2{
void Test1 () {
System.out.println(new Outter().i);
System.out.println(string);
}
}
}
(3)静态内部类的静态方法:规则如上
public class Outter {
int i = 5;
static String string = "Hello";
static class Inner2{
static void Test2 () {
System.out.println(new Outter().i);
System.out.println(string);
}
}
}
2.2.2、外部类访问内部类
(1)成员内部类:
成员内部类——就是位于外部类成员位置的类
特点:可以使用外部类中所有的成员变量和成员方法(包括private的)
a.例子:
public class People {
private String name;
private int age=20;
private int ID ;
public static void main(String[] args){
People p = new People();
People.Student s = p.new Student();
s.study();
}
class Student {
private String name;
private int age=18;
private int studentNumber;
public void study(){
System.out.println("输出age"+age);
System.out.println("内部类变量"+this.age);
System.out.println("外部类变量"+People.this.age);
}
}
}
输出结果:输出age18、内部类变量18、外部类变量20;
我们可以发现内部类对象不能直接获得,只能由外部类的对象获取。并且我们通过输出结果,可以发现当外部类和内部类有相同属性时,在内部类中会优先输出内部类的属性,只有通过外部类的对象来调用age属性,才会在本例中输出20。在内部类中,使用this,可以获取内部类的属性。
b.创建对象时:
在外部类的main方法中创建了外部类的对象,并通过它创建了内部类的对象,并调用了内部类的方法;
//成员内部类不是静态的:
外部类名.内部类名 对象名 = new 外部类名.new 内部类名();
//成员内部类是静态的:
外部类名.内部类名 对象名 = new 外部类名.内部类名();
c.成员内部类常见修饰符:
-
private:如果我们的内部类不想轻易被任何人访问,可以选择使用private修饰内部类,这样我们就无法通过创建对象的方法来访问,想要访问只需要在外部类中定义一个public修饰的方法,间接调用。这样做的好处就是,我们可以在这个public方法中增加一些判断语句,起到数据安全的作用。
class Outer { private class Inner { public void show() { System.out.println(“密码备份文件”); } } //使用getXxx()获取成员内部类,可以增加校验语句(文中省略) public void getInner() { return new Inner(); } public static void main(String[] args) { Outer outer = new Outer(); Outer.Inner inner = outer.getInner(); inner.show(); }
-
static:这种被 static 所修饰的内部类,按位置分,属于成员内部类,但也可以称作静态内部类,也常叫做嵌套内部类。具体内容我们在下面详细讲解。
d.成员内部类经典题(填空):
请在三个println 后括号中填空使得输出25,20,18
class Outer {
public int age = 18;
class Inner {
public int age = 20;
public viod showAge() {
int age = 25;
System.out.println(age);//空1
System.out.println(this.age);//空2
System.out.println(Outer.this.age);//空3
}
}
}
(2)局部内部类:
局部内部类——就是定义在一个方法或者一个作用域里面的类
特点:主要是作用域发生了变化,只能在自身所在方法和属性中被使用
a.例子:
public class People {
private String name;
private int age=20;
private int ID ;
public static void main(String[] args){
People p = new People();
class Student {
private String name;
private int age=18;
private int studentNumber;
public void study(){
System.out.println("输出age"+age);
System.out.println("内部类变量"+this.age);
System.out.println("外部类变量"+p.age);
}
}
new Student().study();
}
}
输出结构和之前是一样的,我们只是把类在方法里定义了,我们可以联想到局部变量的定义,只能在本方法中使用,那么,方法内的类自然也只是属于这个方法,访问权限只限于方法内。
b.访问时:
//在局部位置,可以创建内部类对象,通过对象调用和内部类方法
class Outer {
private int age = 20;
public void method() {
final int age2 = 30;
class Inner {
public void show() {
System.out.println(age);
//从内部类中访问方法内变量age2,需要将变量声明为最终类型。
System.out.println(age2);
}
}
Inner i = new Inner();
i.show();
}
}
c.为什么局部内部类访问局部变量必须加final修饰呢?
因为局部变量是随着方法的调用而调用,使用完毕就消失,而堆内存的数据并不会立即消失。
所以,堆内存还是用该变量,而该变量已经没有了。为了让该值还存在,就加final修饰。
原因是,当我们使用final修饰变量后,堆内存直接存储的是值,而不是变量名。
(即上例 age2 的位置存储着常量30 而不是 age2 这个变量名)
(3)静态内部类:
我们所知道static是不能用来修饰类的,但是成员内部类可以看做外部类中的一个成员,所以可以用static修饰,这种用static修饰的内部类我们称作静态内部类,也称作嵌套内部类.
特点:不能使用外部类的非static成员变量和成员方法
解释:非静态内部类编译后会默认的保存一个指向外部类的引用,而静态类却没有。
a.例子:
public class People {
private String name;
private int age=20;
private int ID ;
public static void main(String[] args){
People p = new People();
new People.Student().study();
}
static class Student {
private String name;
private int age=18;
private int studentNumber;
public void study(){
System.out.println("输出age"+age);
System.out.println("内部类变量"+this.age);
}
}
}
我们把内部类变成静态类,我们可以理解为,这个内部类现在属于外部类了,外部类可以通过类名来调用这个类并创建对象,结果是输出18。
b.简单理解:
即使没有外部类对象,也可以创建静态内部类对象,而外部类的非static成员必须依赖于对象的调用,静态成员则可以直接使用类调用,不必依赖于外部类的对象,所以静态内部类只能访问静态的外部属性和方法。
class Outter {
int age = 10;
static age2 = 20;
public Outter() {
}
static class Inner {
public method() {
System.out.println(age);//错误
System.out.println(age2);//正确
}
}
}
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
inner.method();
}
}
(4)匿名内部类:
一个没有名字的类,是内部类的简化写法
本质:其实是继承该类或者实现接口的子类匿名对象
a.例子1:
匿名内部类的实现涉及到了抽象类的实现,那么什么是抽象类呢?抽象类拥有一系列抽象的属性或者方法,它本身不能new对象,只有在它被继承并被重写所有抽象方法之后才能得到实现。我们这次要通过创建一个匿名内部类的对象来使得一个抽象的类得到实体化并执行一系列方法。首先我们需要创建一个抽象类,然后再写一个类创建匿名内部类的对象实现抽象类的方法(此处我们没有用到继承)。
abstract class People {
private String name;
private int age=20;
private int ID ;
public abstract void work();
}
public class Test{
public static void main(String[] args){
Test t = new Test();
t.test(new People(){
public void work(){
System.out.println("我在工作");
}
});
}
public void test(People people){
people.work();
}
}
似乎我们new了一个抽象类People的对象,但其实我们通过了匿名内部类来实现People类的实例化,它实现了work()抽象方法,然后调用此方法,输出“我在工作”。
b.例子2:
直接使用 new Inner() {}.show(); == 子类对象.show();
interface Inter {
public abstract void show();
}
class Outer {
public void method(){
new Inner() {
public void show() {
System.out.println("HelloWorld");
}
}.show();
}
}
class Test {
public static void main(String[] args) {
Outer o = new Outer();
o.method();
}
}
如果匿名内部类中有多个方法又该如何调用呢?
Inter i = new Inner() { //多态,因为new Inner(){}代表的是接口的子类对象
public void show() {
System.out.println("HelloWorld");
}
};
c.匿名内部类在开发中的使用:
在开发的时候,会看到抽象类,或者接口作为参数。
而这个时候,实际需要的是一个子类对象。
如果该方法仅仅调用一次,我们就可以使用匿名内部类的格式简化。