Java——内部类

一、内部类是什么

1、内部类简介

Java 内部类(Inner Class)是定义在另一个类也就是外部类(Outer Class)内部的类。

内部类又称为嵌套类(Nested Class),外部类又称为封闭类(Enclosing Class)。

2、内部类的分类

Java 中有四种内部类:

  1. 成员内部类(Member Inner Class)
    定义在外部类的成员位置上,与成员变量和方法平级。
  2. 静态内部类(Static Nested Class)
    使用static关键字修饰。
  3. 局部内部类(Local Inner Class)
    定义在一个类的局部位置上,例如成员方法和代码块内。
  4. 匿名内部类(Anonymous Inner Class)
    没有名字的内部类,常用于简化代码。

二、成员内部类(Member Inner Class)

1、介绍

成员内部类是定义在一个外部类的成员位置的的类,没有 static 修饰。

示例:

class OuterClass {
	private String outerClassField = "outer class's field~";

	class MemberInnerClass { // 成员内部类
		private String memberInnerClassField = "member inner class's field";
	}
}

2、特点

1)在成员内部类中访问外部类的成员

在成员内部类中能访问外部类的所有成员,包括私有的。

i.方法一:直接访问

直接使用外部类的成员的名字访问:

class OuterClass {
	private String outerClassField = "outer class's field~";

	class MemberInnerClass { // 成员内部类
		public void foo() {
			System.out.println(outerClassField); // 直接使用成员名访问
		}
	}
}
ii.方法二:使用外部类的 this 引用访问
class OuterClass {
	private String outerClassField = "outer class's field~";

	class MemberInnerClass { // 成员内部类
		public void foo() {
			System.out.println(OuterClass.this.outerClassField);// 使用外部类的this引用访问
		}
	}
}

这个方法可以在外部类的成员与成员内部类的成员重名时使用。因为如果外部类成员与成员内部类成员重名,在成员内部类中直接使用这个成员的名字访问的成员是成员内部类的成员(遵循就近原则),如果想要访问外部类的这个成员,就要使用上面提到的方法。

格式:

外部类名.this.成员名

示例:

class OuterClass {
	private int a;

	class MemberInnerClass { // 成员内部类
		private int a;
		public void foo() {
			System.out.println("member inner class's field: " + a);
			System.out.println("outer class's field: " + OuterClass.this.a);
		}
	}
}

2)在外部类中访问成员内部类的成员

在外部类中可以访问成员内部类的所有成员,包括私有的。

class OuterClass {
	class MemberInnerClass { // 成员内部类
		private String privateField = "member inner class's private field~";
		
		private void privateMethod() {
			System.out.println("member inner class's private method~");
		}
	}
	
	public void fun() {
		MemberInnerClass mic = new MemberInnerClass();
		
		mic.privateMethod(); // 外部类访问成员内部类的私有方法

		System.out.println(mic.privateField); // 外部类访问成员内部类的私有属性
	}
}

在外部类中,通过成员内部类的实例,可以访问成员内部类的所有成员,包括私有的。

3)可以添加任何访问修饰符

成员内部类可以添加任何访问修饰符,因为它也是外部类的成员。

class OuterClass {
	public class MemberInnerClassA {
		
	}

	protected class MemberInnerClassB {

	}

	class MemberInnerClassC {

	}

	private class MemberInnerClassD {

	}
}

4)在其他类中创建成员内部类的实例

在其他类中创建成员内部类的实例的前提需要保证成员内部类的可见性是在这个其他类中可见的。而且由于成员内部类时外部类的实例成员,所以如果想要创建成员内部类的实例,首先需要创建外部类的实例,然后通过外部类的实例来创建成员内部类的实例。

方法一:

先创建一个外部类实例,然后使用这个外部类实例创建成员内部类的实例:

public class Example {
	public static void main(String[] args) {
		// 方法一
		OuterClass oc = new OuterClass();
		OuterClass.MemberInnerClass mic = oc.new MemberInnerClass();
	}
}

class OuterClass {
	class MemberInnerClass {
		
	}
}

或者,创建外部类的实例的同时创建成员内部类的实例:

public class Example {
	public static void main(String[] args) {
		// 方法二
		OuterClass.MemberInnerClass mic = new OuterClass().new MemberInnerClass();
	}
}

class OuterClass {
	class MemberInnerClass {

	}
}
方法二:

在外部类中定义一个方法,用来创建成员内部类的实例,然后返回这个实例:

public class Example {
	public static void main(String[] args) {
		// 方法三
		OuterClass.MemberInnerClass mic = new OuterClass().getMemberInnerClassInstance();
	}
}

class OuterClass {
	class MemberInnerClass {

	}
	
	public MemberInnerClass getMemberInnerClassInstance() {
		return new MemberInnerClass();
	}
}

5)编译器为成员内部类生成的类名

成员内部类在编译后会生成一个独立的类文件,类文件的命名通常是 外部类名$成员内部类名.class

我们可以通过 getClass() 方法来查看编译器为这个成员内部类生成的类名:

public class Example {
	public static void main(String[] args) {
		OuterClass oc = new OuterClass();
		oc.foo();
	}
}

class OuterClass {
	class MemberInnerClass {

	}

	public void foo() {
		MemberInnerClass mic = new MemberInnerClass();

		System.out.println(mic.getClass());
	}
}

运行结果:

类名格式一般是:外部类名$成员内部类名,这是编译器产生的,用于确保在字节码中能够唯一标识这个成员内部类。编译器生成的名称是该成员内部类的真正类名,而在代码中使用的名称则是一个标识符,用于在源代码中引用这个类。

3、补充

  1. 成员内部类的作用域为整个外部类类体,因为成员内部类的地位是外部类的实例成员。
  2. 由于成员内部类是外部类的实例成员,所以成员内部类的实例是需要基于外部类的实例来创建的,如果外部类没有创建实例,成员内部类是不能创建实例的。
    也就是说,如果你尝试在一个静态的块中创建一个成员内部类,就会导致报错。就如下面这样:
    class OuterClass {
    	class MemberInnerClass {
    		
    	}
    
    	public static void fun() {
    		MemberInnerClass mic = new MemberInnerClass(); 
    		// 这里会报错,因为静态函数是属于类的,
    		// 而不是对象的,所以没有外部类的对象这个函数也能调用,
    		// 又由于成员内部类要基于外部类的实例来创建,
    		// 所以这里无法创建内部类实例
    	}
    }
    这样就引出了下一个内部类,也就是静态内部类。

三、静态内部类(Static Nested Class)

1、介绍

静态内部类是定义在一个外部类的成员位置的的类,有 static 修饰。

示例:

class OuterClass {
	static class StaticNestedClass { // 静态内部类

	}
}

它的地位就相当于外部类的一个静态成员。

2、特点

1)在静态内部类中访问外部类的成员

在静态内部类中只能访问外部类的静态成员,包括私有的。

i.方法一:直接访问

直接使用外部类的静态成员名来访问这个静态成员:

class OuterClass {
	private static String outerClassField = "outer class's private field~";

	static class StaticNestedClass { // 静态内部类
		public void fun() {
			System.out.println(outerClassField); // 直接使用外部类的静态成员名访问这个成员
		}
	}
}
ii.方法二:使用外部类的类名访问

使用外部类的类名加这个静态成员名来访问这个静态成员:

class OuterClass {
	private static String outerClassField = "outer class's private field~";

	static class StaticNestedClass { // 静态内部类
		public void fun() {
			System.out.println(OuterClass.outerClassField);
			// 通过外部类名加静态成员名访问这个静态成员
		}
	}
}

这里因为是静态内部类,所以这个静态内部类是属于这个外部类整个类的,而不是属于某个外部类的实例的,所以这个静态内部类中没有外部类当前实例的 this 引用。同时,静态内部类也只能访问外部类的静态成员。

所以这里使用的是外部类名加静态成员名来访问。

格式:

外部类名.静态成员名

同时,这个方式可以在静态内部类中的成员与外部类的某个静态成员重名时使用,因为由于就近原则,在静态内部类中直接使用重名的成员的名字访问到的是静态内部类中的成员,如果想要访问外部类的这个静态成员,就需要使用上面的格式来指定。

示例:

class OuterClass {
	private static int a = 10;

	static class StaticNestedClass {
		private int a = 100;

		public void fun() {
			System.out.println(a); // 访问的是静态内部类中的成员
			System.out.println(OuterClass.a); // 访问的是外部类中的静态成员
		}
	}
}

2)在外部类中访问静态内部类中的成员

在外部类中可以访问静态内部类中的所有成员,包括私有的。

class OuterClass {
	static class StaticNestedClass {
		private String staticNestedClassField = "static nested class's field";
	}
	
	public void fun() {
		StaticNestedClass snc = new StaticNestedClass();

		System.out.println(snc.staticNestedClassField);
	}
}

在外部类中,通过静态内部类的实例可以访问静态内部类的所有成员,包括私有的。

3)可以添加任何访问修饰符

由于静态内部类的地位就是外部类的静态成员,所以可以添加访问修饰符,用来决定它的对外可见度。

class OuterClass {
	public static class StaticNestedClassA {
		
	}
	
	protected static class StaticNestedClassB {
		
	}
	
	static class StaticNestedClassC {
		
	}
	
	private static class StaticNestedClassD {
		
	}
}

4)在其他类中创建静态内部类的实例

在其他类中创建静态内部类的实例需要保证静态内部类的对外可见性在这个其他类中是可见的。

方法一:

可以直接使用外部类的类名来调用静态内部类的构造器来创建静态内部类的实例。

public class Example {
	public static void main(String[] args) {
		OuterClass.StaticNestedClass snc = new OuterClass.StaticNestedClass();

	}
}

class OuterClass {
	static class StaticNestedClass {
		
	}
}
方法二:

可以定义一个静态方法用来创建一个静态内部类的实例并返回。

public class Example {
	public static void main(String[] args) {
		OuterClass.StaticNestedClass snc = OuterClass.getStaticNestedClassInstance();
		
	}
}

class OuterClass {
	static class StaticNestedClass {

	}
	
	public static StaticNestedClass getStaticNestedClassInstance() {
		return new StaticNestedClass();
	}
}

5)编译器为静态内部类生成的类名

静态内部类在编译后会生成一个独立的类文件,类文件的命名通常是 外部类名$静态内部类名.class

我们可以通过 getClass() 方法来查看编译器为这个静态内部类生成的类名:

public class Example {
	public static void main(String[] args) {
		OuterClass.foo();
	}
}

class OuterClass {
	static class StaticNestedClass {

	}

	public static void foo() {
		StaticNestedClass snc = new StaticNestedClass();

		System.out.println(snc.getClass());
	}
}

运行结果:

类名一般为:外部类名$静态内部类名,这是编译器产生的,用于确保在字节码中能够唯一标识这个静态内部类。编译器生成的名称是该静态内部类的真正类名,而在代码中使用的名称则是一个标识符,用于在源代码中引用这个类。

3、补充

  1. 由于静态内部类相当于外部类的一个静态成员,所以它是属于外部类整个类的,而不是属于某个外部类实例的,所以它不需要基于外部类的实例来进行实例化,直接使用外部类的类名就可以调用它的构造器来进行实例化。
    同时,由于它不是实例成员,它的内部也不在内嵌有外部类当前实例的 this 引用。它也无法访问外部类的非静态成员。
  2. 静态内部类的作用域是外部类整个类体,因为它的地位是外部类的静态成员。

四、局部内部类(Local Inner Class)

1、介绍

局部内部类定义在方法、构造器或代码块内部,类似于方法的局部变量。它的作用域仅限于定义它的块中。

class OuterClass {
	public OuterClass() {
		class LocalInnerClassA { // 在构造器中定义局部内部类

		}
	}

	{
		class LocalInnerClassB { // 在实例代码块中定义局部内部类

		}
	}

	static {
		class LocalInnerClassC { // 在静态代码块中定义局部内部类

		}
	}

	public void fun() {
		int localVar;

		class LocalInnerClassD { // 在成员函数中定义局部内部类

		}
	}
}

同时,局部内部类本质也是类,所以大部分一般类的成员在局部内部类中都能有,甚至可以进行继承等操作。

2、特点

1)在局部内部类中访问外部类的成员

在局部内部类内可以直接访问到外部类的所有成员,包括私有的成员。

i.方法一:直接访问

直接通过成员名来访问具体的外部类的成员。

class OuterClass {
	private String outerField = "outer class private field~";

	public void fun() {
		class LocalInnerClass {
			public void foo() {
				System.out.println(outerField);// 直接访问
			}
		}

	}
}
ii.方法二:通过外部类的 this 引用访问

局部内部类也可以通过外部类的 this (当前实例的引用)引用来访问外部类的成员。

class OuterClass {
	private String outerField = "outer class private field~";

	public void fun() {
		class LocalInnerClass {
			public void foo() {
				System.out.println(OuterClass.this.outerField);// 通过外部类的this引用访问
			}
		}

	}
}

这个方法可以在外部类的成员与局部内部类的成员重名时使用。因为如果外部类成员与局部内部类成员重名,在局部内部类中直接使用这个成员的名字访问的成员是局部内部类的成员(遵循就近原则),如果想要访问外部类的这个成员,就要使用上面提到的方法。

格式为:

外部类名.this.成员名

示例:

class OuterClass {
	private int a = 100;
	
	public void fun() {
		class LocalInnerClass {
			private int a = 200;
			
			public void foo() {
				System.out.println("LocalInnerClass's field: " + a);
				System.out.println("outerClass's field: " + OuterClass.this.a);
			}
		}

	}
}
iii.注意

需要注意的是,如果局部内部类定义在一个静态的局部中(例如静态代码块和静态方法)中,则在局部内部类中就不能访问到外部类的所有非静态成员。

2)在外部类定义局部内部类的块中访问局部内部类的成员

在外部类定义局部内部类的块中也可以访问局部内部类的所有成员,包括私有的成员。

如何访问:

在外部类定义局部内部类的块中需要使用局部内部类的实例来访问局部内部类的成员。

需要注意的是,局部内部类是在方法内部定义的,因此它的实例化和使用必须在定义它的那个方法或其内部的代码块中进行,而且是需要再这个局部内部类的定义语句之后,因为它的作用域就从它的定义开始,直到这个方法的结束。

class OuterClass {
	public void fun() {
		class LocalInnerClass {
			private String innerfield = "Hello";
		}

		LocalInnerClass lic = new LocalInnerClass();
		System.out.println(lic.innerfield);// 通过局部内部类的实例来访问其成员

	}
}

3)局部内部类中访问方法的局部变量

在局部内部类中可以直接访问定义它的方法中的局部变量或者是该方法的参数,或者访问定义它的代码块的局部变量。但是访问这些局部变量的要求是,这些局部变量必须是显式 final 的(直接显式使用 final 修饰)或者是隐式 final 的(等价于 final 的,即在初始化后没有被修改,这个只有在 Java 8 及之后有效)。

访问方法中的局部变量:

class OuterClass {
	public void fun() {
		final int finalLocalVar = 0;
		int neverChangedVar = 0;
		// neverChangedVar = 1;
		// 这里如果有这一个语句的话,后面就会报错,因为这样这个局部变量就不是隐式final的了
		class LocalInnerClass {
			public void foo() {
				System.out.println(finalLocalVar);
				System.out.println(neverChangedVar);
			}
		}

	}
}

访问方法的参数:

class OuterClass {
	public void fun(final int finalParameter, int neverChangedParameter) {
		// neverChangedParameter = 0;
		// 这里如果有这一句的话,也会报错,因为这样这个参数就不是隐式final的了
		class LocalInnerClass {
			public void foo() {
				System.out.println(finalParameter);
				System.out.println(neverChangedParameter);
			}
		}

	}
}

访问代码块的局部变量:

class OuterClass {
	{
		final int finalLocalVar = 0;
		int neverChangedVar = 0;
		// neverChangedVar = 1;
		// 这里如果有这一个语句的话,后面就会报错,因为这样这个局部变量就不是隐式final的了
		class LocalInnerClass {
			public void foo() {
				System.out.println(finalLocalVar);
				System.out.println(neverChangedVar);
			}
		}

	}
}

4)编译器为局部内部类生成的类名

 局部内部类在编译后会生成一个独立的类文件,类文件的命名通常是 外部类名$1局部内部类名.class,其中1表示这是这个外部类中第一个局部内部类。

我们可以通过 getClass() 方法来查看编译器为这个局部内部类生成的类名:

public class Example {
	public static void main(String[] args) {
		new OuterClassA().foo();
	}
}

class OuterClassA {
	public void foo() {
		class LocalInnerClassA {
			
		}
		
		LocalInnerClassA lic = new LocalInnerClassA();
		System.out.println(lic.getClass());
	}
}

运行结果:

类名一般为:外部类名$1局部内部类名,其中1表示这是这个外部类中第一个局部内部类。这是编译器产生的,用于确保在字节码中能够唯一标识这个局部内部类。编译器生成的名称是该局部内部类的真正类名,而在代码中使用的名称则是一个标识符,用于在源代码中引用这个类。

3、补充

  1. 由于局部内部类的地位就相当于局部变量,所以它不能添加访问修饰符,但能用 final 修饰。
  2. 局部内部类的作用域只在定义它的块中。
  3. 在局部内部类中可以访问外部类的所有成员(包括私有的),同时在外部类定义了局部内部类的块中也可以访问局部内部类的所有成员(包括私有的)。
  4. 局部内部类不能包含静态成员(静态属性、静态方法、静态代码块和静态内部类),因为静态内部类的作用域仅限于方法内部,而静态成员通常是与类本身关联的。
    在 JDK 16 时,加了一个局部内部类中可以有的静态成员,就是静态属性,同时有一个限制,这个静态属性必须是 final 修饰的(这里有一个细节,static 和 final 同时使用,编译器有优化,这样这个属性就是编译时常量,不是在类加载时初始化,而是在编译时初始化)。其他的静态成员依然不能在局部内部类中出现。

五、匿名内部类(Anonymous Inner Class)

1、介绍

匿名内部类是一种特殊的内部类,它没有显式的类名。匿名内部类通常用于创建一个类的实例,而这个类只需要使用一次。匿名内部类可以继承一个类或实现一个接口,并且可以在定义类的内容的同时创建对象。

1)语法:

匿名内部类继承类语法:

new 父类构造器(参数列表) {
    // 类体
};

匿名内部类实现接口语法:

new 接口名() {
    // 类体
};

2)示例:

匿名内部类实现接口:

interface Interface {
	void cry();
}

class OuterClass {
	public void method() {
		Interface cat = new Interface() {
			@Override
			public void cry() {
				System.out.println("Meow~");
			}
		};// 这里就是一个匿名内部类,
		  // 这个匿名内部类实现了Interface接口,
		  // 定义了类的结构后就被实例化了

		cat.cry();
	}
}

 匿名内部类继承抽象类:

abstract class Animal {
	private String name;

	public Animal(String name) {
		this.name = name;
	}

	abstract void cry();
}

class OuterClass {
	public void method() {
		Animal cat = new Animal("cat") {
			@Override
			public void cry() {
				System.out.println("Meow~");
			}
		};// 这里就是一个匿名内部类,
		  // 这个匿名内部类继承了抽象类Animal,然后实现了抽象类的抽象方法,
		  // 定义了类的结构后就被实例化了

		cat.cry();
	}
}

 匿名内部类继承一般类:

class Animal {
	public void cry() {
		System.out.println("动物叫~");
	}
}

class OuterClass {
	public void fun() {
		Animal cat = new Animal() {
			@Override
			public void cry() {
				System.out.println("喵~");
			}
		};
		
		cat.cry();
	}
}

2、特点

1)匿名类的类名

 匿名内部类只是没有显式的类名,但实际上有隐式的名字,一般命名规则是 外部类名$1 ,这里的 1 是指在这个外部类内定义的第一个匿名内部类,我们可以通过 getClass() 方法获取类名:

public class Example {
	public static void main(String[] args) {
		new OuterClass().method();
	}
}

interface Interface {

}

class OuterClass {
	public void method() {
		Interface i = new Interface() {
			// 匿名内部类
		};

		System.out.println(i.getClass());
	}
}

运行结果:

如果你把这个类名占用了,它就会顺延:

public class Example {
	public static void main(String[] args) {
		new OuterClass().method();
	}
}

interface Interface {

}

class OuterClass$1 {

}

class OuterClass {
	public void method() {
		Interface i = new Interface() {
			// 匿名内部类
		};

		System.out.println(i.getClass());
	}
}

运行结果:

2)在匿名内部类中访问外部类的成员

在及匿名内部类内可以直接访问到外部类的所有成员,包括私有的。

i.方法一:直接访问
abstract class Animal {
    abstract void foo();
}

class OuterClass {
	private String outerField = "outer class private field~";

	public void fun() {
		Animal cat = new Animal() {
            @Override
			public void foo() {
				System.out.println(outerField);// 直接访问
			}
		};
		
	}
}
ii.方法二:通过外部类的 this 引用访问
abstract class Animal {
    abstract void foo();
}

class OuterClass {
	private String outerField = "outer class private field~";

	public void fun() {
		Animal cat = new Animal() {
            @Override
			public void foo() {
				System.out.println(OuterClass.this.outerField);// 通过外部类的this引用访问
			}
		};

	}
}

这个方法可以在外部类的成员与匿名内部类的成员重名时使用。因为如果外部类成员与匿名内部类成员重名,在匿名内部类中直接使用这个成员的名字访问到的 是匿名内部类的成员(遵循就近原则),如果想要访问外部类的这个成员,就要使用上面提到的方法。

格式:

外部类名.this.成员名

示例:

public class Example {
	public static void main(String[] args) {
		new OuterClass().fun();
	}
}

abstract class Animal {
	abstract void foo();
}

class OuterClass {
	private int a = 100;

	public void fun() {
		Animal cat = new Animal() {
			private int a = 200;

			@Override
			public void foo() {
				System.out.println("Anonymous inner class's field: " + a);
				System.out.println("Outer class's field: " + OuterClass.this.a);
			}
		};

		cat.foo();
	}
}

运行结果:

iii.注意

需要注意的是,如果匿名内部类定义在一个静态的局部中(例如静态代码块和静态方法)中,则在匿名内部类中就不能访问到外部类的所有非静态成员。

3)在外部类定义匿名内部类的块中访问匿名内部类的成员

由于匿名内部类的类名不是显式的,所以我们只能使用匿名内部类的父类或者其实现接口类型的引用来指向匿名内部类的对象,这就导致我们只能访问到匿名内部类的父类或者其实现接口中有的成员。对于匿名内部类中的所有特有成员,外界都是不能直接访问到的,因为我们没法将这个匿名内部类的引用向下转型,所以就没有办法访问子类(匿名内部类)的特有成员。

所以我们实际上不能直接访问到匿名内部类的成员,只有匿名内部类重写了父类或者接口的默认方法,或者匿名内部类实现了接口的抽象方法,这样就可以通过动态绑定实现调用匿名内部类的方法。但也仅限于调用方法,因为对于属性是静态绑定的,所以匿名内部类中的所有属性外界都是不能直接访问的(这与上面说的匿名内部类的所有特有成员外界都不能访问一致)。

interface Interface {
	int interfaceField = 100;
	void cry();
}

class OuterClass {
	public void fun() {
		Interface cat = new Interface() {
			@Override
			public void cry() {
				System.out.println("Meow~");
			}
			
			public String anonymousClassField = "anonymous class's specific field~";
			public void anonymousClassMethod() {
				System.out.println("anonymous class's specific method~");
			}
		};

		cat.cry(); // 可以直接访问匿名内部类重写的方法,通过动态绑定机制

		System.out.println(cat.interfaceField); // 可以访问接口中的字段,因为引用是接口类型的

		// cat.anonymousClassMethod(); // 没法访问匿名内部类的特有方法
		
		// System.out.println(cat.anonymousClassField); // 没法访问匿名内部类的特有属性
	}
}

4)匿名内部类的构造器

匿名内部类没有类名,因此不能定义显式的构造器。编译器会产生一个以外部类引用类型为参数的构造器。这里我们可以查看具体的编译器产生的语句。

public class Test {
	public static void main(String[] args) {
		
	}
}

class Animal {}

class OuterClass {
	public void fun() {
		Animal cat = new Animal() {
			
		};
	}
}

我们使用 javac 将这段代码编译,可以得到以下文件:

可以看到编译器会为匿名内部类命名,也会产生字节码文件 OuterClass$1.class,然后我们使用 javap 工具得到 OuterClass$1.class 的反编译代码:

class OuterClass$1 extends Animal {
  final OuterClass this$0;
  OuterClass$1(OuterClass);
}

可以看到第一个字段为外部类 OuterClass 的 this 引用,这是编译器添加的。然后就是匿名内部类的构造器了,也就是 OuterClass$1(OuterClass),这表示匿名内部类的构造器接收一个外部类的引用类型的参数,这个引用是用来初始化上面的字段的,也就是用来初始化外部类的 this 引用,所以我们可以在匿名内部类中直接使用外部类的所有成员方法和属性。

5)使用匿名内部类的实例访问其成员的语法

一般情况下,我们会这样使用匿名内部类的实例访问其成员:

abstract class Animal {
	abstract void cry();
}

class OuterClass {
	public void fun() {
		Animal cat = new Animal() {
			@Override
			void cry() {
				System.out.println("Meow~");
			}
		};

		cat.cry();// 一般调用方式
	}
}

其实我们也可以使用以下方式访问其成员:

abstract class Animal {
	abstract void cry();
}

class OuterClass {
	public void fun() {
		new Animal() {
			@Override
			void cry() {
				System.out.println("Meow~");
			}
		}.cry();// 创建对象之后立马调用,
				// 这种方式适用于创建实例后只使用一次这个实例的情形
				// 这中情况,前面不能使用Animal类型的引用来接受了,
				// 因为这样就是相当于调用了一个cry方法
				// 返回值是void
	}
}

也就是说这种方式只能访问一次匿名内部类的实例的成员,然后这个实例就不能访问到了,也就被垃圾回收机制回收了。

6)匿名内部类访问定义它的块中的局部变量

匿名内部类也可以访问定义它的方法中的局部变量和方法的参数,也可以访问代码块中的局部变量。与局部内部类的要求一样,需要这些局部变量是 final 修饰的,或者是隐式 final 的(也就是初始化之后没有被修改过)。

访问定义匿名内部类的方法中的局部变量:

interface Interface {
	void cry();
}

class OuterClass {
	public void fun() {
		final int finalLocalVar = 0;
		int neverChangedVar = 0;
		// neverChangedVar = 1;
		// 这里如果有这一个语句的话,后面就会报错,因为这样这个局部变量就不是隐式final的了
		Interface cat = new Interface() {
			@Override
			public void cry() {
				System.out.println(finalLocalVar);
				System.out.println(neverChangedVar);
			}
		};

	}
}

访问定义匿名内部类的方法的参数:

interface Interface {
	void cry();
}

class OuterClass {
	public void fun(final int finalParameter, int neverChangedParameter) {
		// neverChangedParameter = 0;
		// 这里如果有这一句的话,也会报错,因为这样这个参数就不是隐式final的了
		Interface cat = new Interface() {
			@Override
			public void cry() {
				System.out.println(finalParameter);
				System.out.println(neverChangedParameter);
			}
		};

	}
}

访问定义了匿名内部类的代码块中的局部变量:

interface Interface {
	void cry();
}

class OuterClass {
	{
		final int finalLocalVar = 0;
		int neverChangedVar = 0;
		// neverChangedVar = 1;
		// 这里如果有这一个语句的话,后面就会报错,因为这样这个局部变量就不是隐式final的了
		Interface cat = new Interface() {
			@Override
			public void cry() {
				System.out.println(finalLocalVar);
				System.out.println(neverChangedVar);
			}
		};

	}
}

3、补充

  1. 匿名内部类通常用于创建一个类的实例,而这个类只需要使用一次。
  2. 在匿名内部类中可以访问外部类的所有成员(包括私有的)。但是在外部类定义匿名内部类的块中不能直接访问到匿名内部类的成员。
  3. 匿名内部类可以继承一个类或实现一个接口。不能实现对多个接口实现。

4、匿名内部类的最佳实践

1)直接作为参数传递

直接将匿名内部类的实例作为参数传递,使代码更加简介明了。

public class Example {
	public static void animalCry(Cry c) {
		c.cry();
	}

	public static void main(String[] args) {
		animalCry(new Cry() { // 直接创建一个匿名内部类的实例,同时作为参数传给这个方法
			@Override
			public void cry() {
				System.out.println("Meow~");
			}
		});
	}
}

interface Cry {
	void cry();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值