引言
在java中,声明并使用一个内部类是相当简单的,就像使用一个普通的类一样,我们在工作中或多或少的能从内部类中获益,它能为我们隐藏很多代码逻辑, 能让代码变得简单优雅。
但是用法简单并不代表语义上也简单,不管是从实现上还是功能是,内部类是有很多值得我们研究的东西的。
这篇文章,我们先看普通的内部类即成员内部类和静态内部类(嵌套类)。
内部类的划分
从概念上来说,一个定义在另一个类或者方法里面的类,就可以叫做内部类。
如果将内部类进行划分,可以分为成员内部类、静态内部类、局部内部类和匿名内部类四种。下面看这四种内部类的用法:
成员内部类
成员内部类是最普通的内部类,它的定义位于另外一个类的内部。看下面这个例子:
public class MemberOuter {
class MemberInner{
}
}
注意内部类MemberInner没有static,这是很重要的一点。
声明很简单,但是作为最普通的内部类,成员内部类是有很多特性的,我们逐一来分析:
访问外部类的成员和方法
成员内部类可以没有限制的访问外部类的变量和方法,不论是私有的还是公有的:
public class MemberOuter {
public String aPublicString;
protected String aProtectedString;
String aDefaultString;
private String aPrivateString;
private static String aPrivateStaticString;
private void aPrivateMethod(){
System.out.println("aPrivateMethod");
}
class MemberInner{
public void accessOuter(){
System.out.println(aPublicString);
System.out.println(aProtectedString);
System.out.println(aDefaultString);
System.out.println(aPrivateString);
System.out.println(aPrivateStaticString);
aPrivateMethod();
}
}
}
我们在外部类中声明了各种访问标志的变量,在内部类中的accessOuter方法中,这些变量都能被访问,就向访问内部类自己的变量一样。
为什么能够访问
能访问静态变量我们可以理解,因为静态变量是所有外部类对象共享的,但是访问成员变量是怎样做到的?难道每个成员内部类对象都对应一个外部类的对象了?确实是这样的。
成员内部类是依附外部类而存在的,创建成员内部类的对象的前提是存在一个外部类的对象。我们可以看一下反编译后的文件,看看编译器是怎样将外部类和内部类对象关联在一起的:
public class MemberOuter
{
public String aPublicString;
protected String aProtectedString;
String aDefaultString;
private String aPrivateString;
private static String aPrivateStaticString;
private void aPrivateMethod()
{
System.out.println("aPrivateMethod");
}
class MemberInner
{
public void accessOuter()
{
System.out.println(aPublicString);
System.out.println(aProtectedString);
System.out.println(aDefaultString);
System.out.println(aPrivateString);
System.out.println(MemberOuter.aPrivateStaticString);
aPrivateMethod();
}
final MemberOuter this$0;
MemberInner()
{
this.this$0 = MemberOuter.this;
super();
}
}
}
注意内部类MemberInner的代码,编译器为我们增加了一个类型为MemberOuter的变量this$0,并且在构造方法中进行了赋值,这个就是创建内部类对象时的用到的外部类对象。有了这个变量,我们当然就可以任意访问外部类对象的方法和变量了。
通过外部类来创建内部类
那么问题又来了,我们要怎样编码来创建内部类对象呢?我们可以使用.new关键字,看下面的例子:
public class MemberOuter {
public static void main(String[] args) {
MemberOuter mo = new MemberOuter();
MemberInner memberInner = mo.new MemberInner();
memberInner.accessOuter();
}
class MemberInner{
public void accessOuter(){
}
}
}
在main方法中,我们先创建了一个外部类的对象,然后通过.new innerClass();这种方式创建了一个内部类对象。这就是使用外部类对象创建内部类对象的一种方式。这样,我们反编译后的代码中内部类的this$0变量就指向我们创建的MemberOuter对象了。
当然,我们也可以在外部类中定义额外的方法来创建内部类对象,看下面的例子:
public class MemberOuter {
public MemberInner inner(){
return new MemberInner();
}
public static void main(String[] args) {
MemberOuter memberOuter = new MemberOuter();
MemberInner inner = memberOuter.inner();
}
class MemberInner{
public void accessOuter(){
}
}
}
在外部类中,我们声明了一个inner方法,直接返回一个内部类的对象,然后再main方法中直接调用。这样,内部类的this$0也会是我们创建的MemberOuter的对象。注意,在外部类中,我们只能通过非静态方法来创建内部类的对象,因为只有非静态方法才能拿到到外部类对象的引用,所以类似下面的方法是不能编译通过的:
错误提示也很明显,在static方法中不能访问到this。
在成员内部类中使用外部类对象的引用
知道了怎样通过外部类来创建成员内部类对象,我们来看一下怎样在内部类中访问与之关联的外部类对象的引用。
很简单,因为编译器已经为我们添加了this&0变量来指向与之关联的外部类的对象,我们肯定是能在内部类中拿到这个引用的,通过 .this关键字,我们就可以达到目的:
public class MemberOuter {
public MemberInner inner(){
return new MemberInner();
}
public static void main(String[] args) {
MemberOuter memberOuter = new MemberOuter();
MemberInner inner = memberOuter.inner();
inner.accessOuter();
}
class MemberInner{
public void accessOuter(){
System.out.println(MemberOuter.this);
}
}
}
内部类的accessOuter方法中,我们使用MemberOuter.this就能访问与之关联的外部类了。为什么不是直接用this$0呢?可能是因为这个变量名是编译器自动为我们声明的或者Outer.this更加好理解吧。
所以我们知道了,对于成员内部类来说,能够随意访问外部类的变量和方法,是因为编译器为我们构造内部类时加入了外部类对象的引用,然后对创建方式进行了限制,只能通过外部类的对象创建,例如.new关键字和外部类的非静态方法,这样实现了成员内部类的特性,成员内部类就像外部类的一个成员一样。
禁用static声明
在成员内部类中,是不能有静态字段和静态方法声明的:
要想在内部类中声明静态字段和静态方法,可以使用静态内部类。
对应关系
一个内部类对象肯定有一个外部类与之对应,但是一个外部类对象不一定有对应的内部类对象,不创建就行了,对吧。一个外部类对象也可能对应多个内部类对象,这样这些内部类对象的this$0就是同一个:
public class MemberOuter {
public MemberInner inner(){
return new MemberInner();
}
public static void main(String[] args) {
MemberOuter memberOuter = new MemberOuter();
MemberInner inner1 = memberOuter.inner();
MemberInner inner2 = memberOuter.inner();
MemberInner inner3 = memberOuter.inner();
inner1.accessOuter();;
inner2.accessOuter();
inner3.accessOuter();
}
class MemberInner{
public void accessOuter(){
System.out.println(MemberOuter.this);
}
}
}
inner1、inner2、inner3三个内部类的外部类对象是一个,所以三次调用accessOuter方法输出的是一样的。
成员内部类的继承
成员内部类的继承语法有点特殊。上面我们已经看到,编译器在成员内部类中默认加上了到外部类对象的引用,我们继承成员内部类也得需要保证这一点。《java编程思想》中给出的示例如下:
public class WithInner {
public class Inner{
}
}
public class InheritInner extends WithInner.Inner {
public InheritInner(WithInner withInner) {
withInner.super();
}
}
在InheritInner构造方法中,有一个到外部类WithInner的对象,并且必须调用使用Outer.super()这种特殊的语法才能保证编译成功。
成员内部类的继承并不常见,我们只需要记住这种特殊的语法即可。
静态内部类(嵌套类)
静态内部类也被称为嵌套类。 它与上面说的成员内部类有很大不同,但是理解了成员内部类的特性,再学习静态内部类就简单很多了。
定义嵌套类
静态内部类在定义时需要加上static关键字。下面就是一个简单的静态内部类:
public class MemberOuter {
static class MemberInner{
public void accessOuter(){
}
}
}
我们对比成员内部类来说明静态内部类的特点,静态内部类与成员内部类的区别主要在以下三个方面:
(1)要创建静态内部类的对象,并不需要外部类的对象。
(2)不能从静态内部类的对象中访问非静态的外部类对象。
(3)成员内部类不能声明静态字段和静态方法,也不能包含静态内部类,而静态内部类可以包含所有这些。
下面我们具体来看一下这三点:
嵌套类对象的创建
对成员内部类,编译器自动生成了this$0这个到外部类的引用来建立和外部类对象的依赖关系,保证了成员内部类对象的创建必须通过外部类对象这一机制,使得成员内部类能够访问外部类对象的任何字段和方法。但是编译器不会对嵌套类做这样的处理,我们不需要外部类的对象就能创建嵌套类的对象。
看下面的例子:
public class MemberOuter {
public static MemberInner inner(){
return new MemberInner();
}
public static void main(String[] args) {
new MemberInner();
}
static class MemberInner{
public void accessOuter(){
}
}
}
反编译后的文件:
public class MemberOuter
{
static class MemberInner
{
public void accessOuter()
{
}
MemberInner()
{
}
}
public MemberOuter()
{
}
public static MemberInner inner()
{
return new MemberInner();
}
public static void main(String args[])
{
new MemberInner();
}
}
编译器没有自动加上到外部对象的引用。
我们在静态的inner方法和main方法中都直接使用new关键字创建了嵌套类的对象,并且没有创建外部类的对象。这与成员内部类不同,成员内部类只能通过外部类的对象创建,所以成员内部类的.new关键字对嵌套类也是无法编译成功的:
实际上,嵌套类的创建就跟普通的java类创建一样,只要使用new关键字就行。
嵌套类访问外部类的成员
正是由于嵌套类的创建不依赖与外部类对象,所以嵌套类就不能访问外部类的非静态字段和方法了,这个很好理解。
嵌套类可以声明静态字段和方法
成员内部类不能声明静态字段和方法,但是嵌套类可以。
public class MemberOuter {
public static void main(String[] args) {
}
static class MemberInner{
private String name;
private static String aStaticString;
private static String getTheStaticString(){
return aStaticString;
}
public void accessOuter(){
System.out.println(aStaticString);
}
}
}
小结
成员内部类作为最普通的内部类,更像是外部类的一个成员,它能够自由地访问外部类的任意字段和方法,每一个成员内部类的对象都有一个对应的外部类对象,为了实现这个目标,编译器为成员内部类的代码自动加上了到外部类对象的引用,我们在成员内部类中可以通过这个引用访问到对应的外部类,同时编译器对成员内部类的创建方式做了限制,只能通过外部类对象来创建,另外,成员内部类不能拥有静态字段和静态方法。
静态内部类(嵌套类)更像是一个普通的类,不需要与外部类对象一一对应,可以随意创建,也可以拥有自己的静态字段和静态方法,但是当静态内部类访问外部类时,就不能那么随意了,它只能访问外部类的静态字段和方法。
成员内部类和静态内部类都是定义在外部类里面的类,下一篇文章,我们来学习另外两种内部类—局部内部类和匿名内部类,这两种是在方法或者作用域内定义的类。