JAVA学习笔记(内部类)

内部类属性

内部类的方法可以访问该类定义所在作用域中的所有实例域,下面通过一个简单的例子来说明其工作原理:

public class Outer {
	// 定义Outer类的私有变量
	private String outerMessage;
	
	public Inner_demo(String outerMessage) {
		this.outerMessage = outerMessage;
	}
	
	// 定义调用内部类的方法
	public void test() {
		Inner inner = new Inner();
		inner.call();
	}
	
	// 定义Outer类的内部类
	public class Inner
	{
		// 内部类的方法可以调用Outer类的实例域
		public void call() {
			System.out.println("Inner_class can call " + outerMessage);
		}
	}
}

可以看到,Inner类中并没有定义outerMessage,但程序仍可执行并打印outerMessage的信息。
原因是内部类维护了一个隐式引用的对象,方便起见我们将其称为outer,call()方法等价于以下形式:

public void call() {
	System.out.println("Inner_class can call " + outer.outerMessage);
}

而outer又是怎么跟外部类关联起来的?我们注意到Inner类并没有定义构造器,所以编译器为它生成了一个默认的构造器,其代码如下:

public Inner(Outer outer) {
	this.outer = outer;
}

这样一来就可以解释内部类访问外围实例域啦,至于为什么可以用outer.outerMessage访问到私有属性?这就关乎内部类具有特殊的权限,具体就不一一解释了。

语法规则

上面讲到内部类有一个外围类的引用,事实上,使用外围类引用的正规语法还要复杂一些:

OuterClass.this

表示外围类的引用。即可以像下面这样写Inner内部类的call方法:

public void call() {
	System.out.println("Inner_class can call " + Outer.this.outerMessage);
}

同样地,可以采用下列语法格式更加明确地构造内部对象:

outerObject.new InnerClass(construction parameters)

注意到定义的Inner类是公有的,因此,对于任意的Outer对象都可以构造一个Inner对象:

Outer outer = new Outer("outerMessage");
Outer.Inner inner = outer.new Inner();
// inner同样可以接收到"outerMessage"字符串

局部内部类

举例用的Inner类只在外围类的test方法中使用了一次,因此可以将其转化为这个方法中的局部类:

public void test() {
	class Inner
	{
		// 内部类的方法可以调用Outer类的实例域
		public void call() {
			System.out.println("Inner_class can call " + outerMessage);
		}
	}
	
	Inner inner = new Inner();
	inner.call();
}

需要注意的是,局部类不能用public或private进行声明,它的作用域被严格限定在声明这个局部类的块中,它对外部环境是完全隐藏的,即使是Outer类中的其它方法也不能调用。
同样地,Inner类可以使用传入test方法或方法中定义的局部变量,但这些局部变量必须为final(可以不声明),规则就同lamba表达式差不多。

匿名内部类

上面的局部内部类只创建了一个对象,如果还想要偷一偷懒,可以写成这样子:

public void test() {
	Inner inner = new Inner()
	{
		public void call() {
			System.out.println("Inner_class can call " + outerMessage);
		}
	};
	inner.call();
}

准确来说,这样定义的Inner类没有类名,因此也没有构造器,那这个Inner到底是什么呢?答案是它是一个已经存在的类(或者接口),也就是说,java会将匿名内部类的构造器参数传递给超类,由它来产生一个对象。为了方便起见,我定义了一个Inner接口来接收这个匿名内部类的参数(当然java库中有这个接口的时候就可以直接用而不用创建啦):

public interface Inner {
	public void call();
}

所以说,所谓的匿名内部类只是在当前的java文件中匿名,实际上还是有个爸爸罩着它的。
奇技淫巧:运用匿名内部类的小知识,我们可以用江湖人称“双括号初始化”的方法构造一个对象,如ArrayList:

new ArrayList<String>() {{ add("What"); add("the"); add("hell?") }}

外层大括号建立了ArrayList的一个匿名子类,内层括号则是创建匿名子类对象时执行的语句(对象构造块)。这样创建的对象没有名字,需要将一个对象传入一个方法,但别的地方不再用到这个对象时,可以留意一个这个技巧。

静态内部类

回到最开始定义的Outer类,如果我们不需要引用外围类的实例域,则可以将内部类声明为static,这样可以禁止内部类引用外围对象。

public static class Inner
	{
		public void call() {
			System.out.println("Inner_class can call " + outerMessage);
		}
	}

除了无法引用外围对象以外,静态内部类还可以拥有静态域和静态方法,这是和常规内部类不同的地方。
特别地,如果Outer类的方法test声明为static,且里面创建了Inner类对象,则Inner类必须声明为static,否则编译器会给出错误报告:No enclosing instance of type Outer is accessible。
还有一点有趣的特性是:声明在接口中的内部类自动成为static和public类。

Ending & Reference

感谢阅读,本文内容整理自:
[1] Cay S. Horstmann. Java核心技术·卷 I[M]. 第10版. 北京: 机械工业出版社, 2016: 243-257.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值