内部类
一、概述
1. 理解内部类
如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类
例如:身体和心脏的关系、汽车和发动机的关系
2. 内部类的分类
- 成员内部类
- 局部内部类(包含匿名内部类)
二、成员内部类
定义在方法体外部,属于外部类中的一个成员
1. 编写格式
修饰符 class 外部类名称 {
修饰符 class 内部类名称 {
// 内部类定义...
}
// 外部了定义...
}
内外成员类之间的访问/调用规则:内用外,随意访问;外用内,需要借助内部类对象
2. 使用成员内部类
-
间接方式:在外部类方法中,使用内部类, main 方法只是调用外部类的方法
-
直接方式:[外部类名称].[内部类名称] [对象名] = new [外部类名称]().new [内部类名称]();
public static void main(String[] args) { // 成员内部类实例化 Outer.Inner inner = new Outer().new Inner(); inner.methodInner(); // 内部类对象调用自身方法 }
3. 成员访问
使用 [外部类名] 和 this指针 解决重名歧义
public class Outer {
int num = 10; // 外部类成员变量
public class Inner {
int num = 20; // 内部类成员变量
public void methodInner() {
int num = 30; // 内部类方法的局部变量
System.out.println(num); // 30 访问局部变量
System.out.println(this.num); // 20 内部类成员变量
System.out.println(Outer.this.num); // 10 外部类成员变量
}
}
}
三、局部内部类
定义在方法体内部
局部:只有所属的方法才能使用,外部无法使用
1. 编写格式
修饰符 class 外部类名称 {
修饰符 返回值类型 外部类方法名(形参列表) {
// 方法体
class 局部内部类名称 {
// 局部内部类定义...
}
}
}
2. 变量访问
在方法体中,如果希望局部内部类访问所在方法中的局部变量,那么这个变量必须是有效 final 的。
从JDK1.8开始,若局部变量事实不变,那么 final 关键字可以省略
原因:外部类方法和局部内部类的实例化对象在内存中的生命周期不同
1. 局部内部对象是 “new” 出来的,在堆内存中
2. 局部变量是跟着外部类方法走的,在栈内存中
3. 外部类方法运行结束后会弹栈,局部变量也随之消失
4. 但是内部类对象会在堆内存中持续存在,直至GC回收
public class Outer {
public void methodOuter() {
class Inner { // 局部内部类
final int num = 10; // 若局部变量事实上未更新,final可省略
// num = 20; // 不可以!!!
public void methodInner() {
System.out.println(num); // 10
}
}
new Inner().methodInner(); // OK
System.out.println(new Inner().num); // OK
}
}
四、匿名内部类
如果 接口的实现类 或者 抽象类的继承类 只需使用一次,这种情况可以省略编写专门的类定义代码,而改为使用匿名内部类实现。
1. 编写格式
- 实现接口的内部类:
[接口名称] [对象名] = new 接口名称() { // 重写接口中所有的抽象方法 }
- 继承抽象类的内部类:
[抽象类名] [对象名] = new 抽象类名() { // 重写抽象类中所有的抽象方法 }
对格式 new [接口名称/抽象类名]() { . . . } 进行解析
- new表示创建对象的动作
- 接口名称/抽象类名 是内部类需要 实现哪个接口/继承哪个抽象类
- { . . . }是内部类的内容
2. 匿名类替换继承类
-
抽象类定义
public abstract class MyAbstractCls { // 抽象类 abstract void method(); // 抽象方法 }
-
继承类调用
class MyInheritCls extends MyAbstractCls { // 有时没有必要为抽象类单独写一个继承类 @Override void method() { System.out.println("继承类重写抽象类方法"); } } public class DemoAnonInnerCls { public static void main(String[] args) { MyAbstractCls obj4 = new MyInheritCls(); // 继承类实例化 obj4.method(); // 继承类调用 } }
-
匿名内部类调用,省去了继承类的定义
public class DemoAnonInnerCls { public static void main(String[] args) { MyAbstractCls obj3 = new MyAbstractCls() { @Override void method() { System.out.println("匿名内部类重写抽象类方法"); } }; // 匿名内部类实例化 obj3.method(); // 调用抽象类方法 } }
3. 匿名类替换实现类
- 接口定义
public interface MyInterface { // 接口 void method(); }
- 通过实现类调用
class MyImplementCls implements MyInterface { // 有时没有必要为接口单独写一个实现类 @Override public void method() { System.out.println("实现类重写接口方法"); } // 实现类 } public class DemoAnonInnerCls { // 通过实现类调用 public static void main(String[] args) { MyInterface obj2 = new MyImplementCls(); obj2.method(); obj2.method2(); } }
- 通过匿名内部类调用,省去了实现类的定义
public class DemoAnonInnerCls { public static void main(String[] args) { MyInterface obj1 = new MyInterface() { @Override public void method() { System.out.println("匿名内部类重写接口方法"); } }; // 匿名内部类实例化 obj1.method(); // 调用接口方法 } }
其它注意事项
- 匿名内部类在创建对象时是能使用唯一一次,如果希望多次实例化同一个类的对象时不能使用匿名类
- 匿名对象在调用方法时只能调用唯一一次,如果希望同一个对象多次调用方法时不能使用匿名对象
- 匿名内部类省略的是实现类名/继承类名,匿名对象省略的是对象名称