1.内部类的描述:
- 定义在类内部的类叫做内部类,内部类跟其他成员一样,是类的组成部分
- 同一文件内的两个并列的类不是内部类,内部类是以类内外进行区分的,不是以文件内外进行区分的
- 内部类会被编译成这样的class文件,OutClass.class、OutClass$A.class、OutClass$B.class
2.内部类的本质:
内部类其实就是进行了一定访问约束的类(普通类、接口、枚举、抽象类),访问约束原则与变量(静态变量、实例变量、局部变量)访问约束原则相同,具备类的特征也具备变量的特征!
2.内部类的作用域:
由于内部类即是类也是变量,所以内部类的作用域比较宽,包含了类和变量两者范围的作用域!
外部类修饰符 | 内部类修饰符 | 内部类作用域 |
public | public | 任何位置 |
public/省略控制符 | 省略控制符 | 同一个包内 |
public/省略控制符 | protect | 父子类内或者同一个包内 |
public/省略控制符 | private | 同一个类内 |
public/省略控制符 | 无(定义在方法内部) | 方法内部 |
3.内部类的分类:
内部类共分四种:非静态内部类、静态内部类、局部内部类、匿名内部类
PS:由于内部类具备类和变量的双重特性,所以把内部类分为静态内部类和非静态内部类(普通类不区分静态非静态),通过static修饰符修饰;并且非静态内部类中不可以定义静态成员!
3.1 内部类的访问原则:
- 内部类的访问分两种:外部类内访问、外部类外访问;不管是那种访问都需要通过外部类来进行访问内部类!
- 内部类可以访问外部类的所有成员,但是外部类只能访问内部类的public属性成员!
- 内部类的作用域,内部类的静态与非静态修饰问题,都可以把内部类当做一个成员变量来看待,与成员变量的处理方式完全相同
- 外部类的静态成员不可访问,非静态内部类,因为非静态内部类中存在访问外部类实例成员的可能!
- 内部类是可以被继承的,子类访问内部类的方式与父类完全相同!
- 内部类变量访问顺序,局部变量-----内部类成员-----外部类成员
3.2 非静态内部类使用示例:
public class Main {
public static void main(String args[]) {
/*外部类内,访问内部类*/
OutClass out = new OutClass();
out.testFunOut();
/*外部类外,访问内部类*/
OutClass.InClass in = new OutClass().new InClass();
in.testFunIn();
}
}
class OutClass {
private String testLog = "OutClass";
public static void testfun() {
/*外部类静态成员(静态属性和方法),不可以访问非静态内部类,因为非静态内部类中存在访问外部类实例成员的可能*/
//InClass in = new InClass();
}
public void testFunOut() {
InClass in = new InClass();
System.out.println(testLog);
/*外部类内,访问内部类成员*/
System.out.println(in.testLog);
in.testFunIn();
}
class InClass {
/*非静态内部不可以存在静态成员(静态属性和方法)*/
//private static String test = "test";
private String testLog = "InClass";
public void testFunIn() {
/*内部类访问外部类成员变量*/
/*访问内部类成员testLog*/
System.out.println(testLog);
System.out.println(this.testLog);
}
}
}
示例说明:
- 当调用一个非静态内部类的实例方法的时候,必须通过一个非静态外部类实例,非静态内部类实例必须寄生在外部类实例里面
- 非静态内部类中不可以定义静态成员!因为类的静态成员都是在类初始的时候生成的,所以非静态内部类与非静态内部类中的静态成员的生成时间会存在矛盾!!!
3.3 静态内部类使用示例:
public class Main {
public static void main(String args[]) {
/*外部类内,访问内部类*/
OutClass out = new OutClass();
/*访问实例成员*/
out.testFunOut();
/*访问静态成员*/
OutClass.testfun();
/*外部类外,访问内部类*/
/*这个就是静态内部类,即使是实例方法也不能访问外部类的实例成员的原因,因为内部类实例方法被调用的时候,可能不存在外部类实例*/
OutClass.InClass in = new OutClass.InClass();
/*访问实例成员*/
in.testFunIn();
/*访问静态成员*/
System.out.println(OutClass.InClass.test);
}
}
class OutClass {
private String testLog = "OutClass";
public static void testfun() {
/*外部类静态成员(静态属性和方法),可以访问静态内部类实例成员*/
InClass in = new InClass();
in.testFunIn();
/*外部类静态成员(静态属性和方法),可以访问静态内部类静态成员*/
System.out.println(InClass.test);
}
public void testFunOut() {
/*外部类内,访问静态内部类实例成员*/
InClass in = new InClass();
in.testFunIn();
/*外部类内,访问静态内部类静态成员*/
System.out.println(InClass.test);
}
static class InClass {
/*静态内部类中,可以定义静态成员也可以定义非静态成员*/
static public String test = "test";
public void testFunIn() {
/*静态内部类中,即使是实例方法也不能使用外部类的实例成员*/
//System.out.println(testLog);
System.out.println("Inclass");
}
}
}
示例说明:
- 静态内部类中即可以存在静态成员,也可以存在非静态成员
- 静态内部类中不可以访问外部类的实例成员,即使是内部类的实例方法中也不可以,因为内部类实例方法被调用的时候,可能不存在外部类实例
3.4 局部内部类使用示例:
public class Main {
public static void main(String args[]) {
OutClass out = new OutClass();
out.testFunOut();
}
}
class OutClass {
public void testFunOut() {
/*局部内部类定义*/
class InClass {
String test = "InClass";
void fun() {
System.out.println(test);
}
}
/*局部内部类使用*/
InClass in = new InClass();
in.fun();
}
}
示例说明:
局部内部类,就是将内部类的作用域限定为方法内部,类似于方法内部的局部变量!
局部内部类,因为是局部变量,所以不需要任何修饰符修饰!
3.5 匿名内部类使用示例:
匿名内部类定义格式:
new 实现接口 | 父类构造器(参数列表)
{
//匿名内部类结构
}
public class Main {
public static void main(String args[]) {
/*创建一个继承NoNameClass的匿名内部类实例,并访问父类NoNameClass的fun方法*/
new NoNameClass()
{
}.func();
/*通过调用不同的父类构造函数,来创建不同的父类实例*/
new NoNameClass("parent")
{
}.func();
/*创建一个继承NoNameClass的匿名内部类实例,并访问本实例的test方法*/
new NoNameClass()
{
public void test()
{
System.out.println("test");
}
}.test();
}
}
class NoNameClass{
private String className;
public NoNameClass()
{
this.className = "";
}
public NoNameClass(String name)
{
this.className = name;
}
public void func()
{
System.out.println("NoNameClass" + this.className);
}
}
public interface FunInterface {
void run();
}
示例说明:
- 匿名内部类必须继承自一个类,可以是普通类、抽象类和接口;但是接口和抽象类中的抽象方法必须在匿名内部类中完成重写!
- 匿名内部类中不可以存在抽象方法,即匿名内部类不可以是抽象类;因为匿名内部类在定义的时候就完成了实例的创建,如果存在抽象方法,实例创建会失败!
- 匿名内部类中不可以定义构造器,因为匿名内部类没有名字;但是可以定义初始化块,在初始化块中完成初始化操作!
- 匿名内部类如果是继承自父类,可以通过父类构造器参数差异,来选择使用不同的父类构造器创建实例!
3.6 effectively final规则:
定义:Java 8以前的版本规定,凡是被局部内部类和匿名内部类使用到的局部变量(实例变量和类变量没有关系)必须使用final修饰;Java 8以后的版本虽然不存在这个规定,但是约束必须按照这个规则来使用局部变量,即其中使用的局部变量,不可以再被进行赋值,否则会编译报错!
原因:因为局部内部类和匿名内部类中使用的局部变量,在定义类的时候局部变量就被拷贝到类内了,如果这个时候局部变量发生数据变化,内部类是没法感知的,导致内部类中使用局部变量时的值可能会与真实值不符!
4. 内部类与内存泄漏:
非静态内部类、非静态方法中的局部内部类、非静态方法中的匿名内部类,使用不当很容易造成内存泄漏;原因在于这些内部类的实例化后,实例中会默认创建一个外部类实例的强引用,这个引用会导致外部类实例的释放依赖与这个内部类实例的释放,如果忽略了这个隐式的引用关系,很容易造成外部类实例的内存泄漏!但是需要注意的是,外部类实例显示引用内部类实例,内部类实例隐示引用外部类实例,仅仅存在这样的循环引用的时候,内部类实例和外部类实例都是可以被GC回收的!
- 这也解释了为什么非静态内部的创建一定要寄生在一个外部类实例的原因;
- 同时也可以解释为什么在非静态内部类中可以直接调用外部类中的私有实例成员!
示例代码:
public class InClassTest {
public static void main(String args[])
{
OutClass out = new OutClass();
out.test();
}
}
class OutClass
{
public void test()
{
/*1.查看非静态内部类中的成员*/
InClass inClass = new InClass();
inClass.inFun();
/*2.查看非静态方法中的匿名内部类的成员*/
FunInterface noNameInClass = new FunInterface() {
public void run()
{
/*非静态方法中的匿名内部类测试场景*/
System.out.println("No static No name InClass Test Begin");
for (Field field : this.getClass().getDeclaredFields())
{
System.out.println(field.getName());
}
System.out.println("No static No name InClass Test End");
}
};
noNameInClass.run();
/*3.查看非静态方法中的局部内部类的成员*/
class localVarInClass
{
public void in()
{
/*非静态方法中的局部内部类测试场景*/
System.out.println("No static Local Var InClass Test Begin");
for (Field field : this.getClass().getDeclaredFields())
{
System.out.println(field.getName());
}
System.out.println("No static Local Var InClass Test End");
}
}
localVarInClass localVarInClass = new localVarInClass();
localVarInClass.in();
/*4.函数式接口*/
FunInterface funInterface = () -> {
/*非静态方法中的匿名内部类测试场景*/
System.out.println("FunInterface Test Begin");
for (Field field : this.getClass().getDeclaredFields())
{
System.out.println(field.getName());
}
System.out.println("FunInterface Test End");
};
funInterface.run();
}
class InClass
{
public void inFun()
{
/*非静态内部类测试场景*/
System.out.println("No static InClass Test Begin");
for (Field field : this.getClass().getDeclaredFields())
{
System.out.println(field.getName());
}
System.out.println("No static InClass Test End");
}
}
}
输出结果:
Ps:
- 上述三种场景分别为静态内部类、非静态方法中的局部内部类、非静态方法中的匿名内部类;都会创建一个this$0指向外部实例的强引用!
- 通过Lamda表达式访问函数式接口虽然与匿名内部类原理类似,但是Lamda表达式不会创建一个指向外部实例的强引用,所以不存在内存泄漏的问题!
不会创建指向外部类实例强引用的内部类:静态内部类、静态方法中的局部内部类、静态方法中的匿名内部类、静态成员指向的匿名内部类;因为这些内部类构造的时候,都不一定会存在外部类实例,所以他们不依赖于外部类实例,所以不需要指向外部类实例的强引用!