深入理解Java之内部类

1. 为什么要使用内部类

内部类就是定义在一个类内部的类,那么为什么要使用内部类呢?主要原因有以下几点:第一,内部类中定义的方法能访问到它所在外部类的私有属性及方法;第二,外部类无法实现对同一包中的其他类隐藏,而内部类可以做到这一点;第三,匿名内部类在我们只需使用该类的实例依次时可以有效减少我们的代码量。关于以上三点,我们在下文中会举出具体例子进行进一步的说明。

2. 如何使用内部类

(1)使用内部类访问外围类私有变量

在内部类中,我们能够访问到它所在外部类中的私有实例变量及方法,请看以下代码:

 1 public class Outer {
 2     private int own = 1;
 3     public void outerMethod() {
 4         System.out.println("In Outer class");
 5         Inner inner = new Inner();
 6         inner.innerMethod();
 7     }
 8     public static void main(String[] args) {
 9         Outer outer = new Outer();
10         outer.outerMethod();
11     }
12
13     private class Inner {
14         public void innerMethod() {
15             System.out.println("The var own in Outer is " + own);
16         }
17     }
18 }

这段代码的输出如下所示:

我们可以看到,在内部类中确实访问到了外部类Outer的private变量own。那么,这是如何做到的呢?实际上,内部类对象隐式地持有一个外部类对象的引用,我们假设这个引用名为outer,那么实际上内部类的innerMethod方法是这样子的:

1 public void innerMethod() {
2     System.out.println("The var own in Outer is " + <strong>outer</strong>.own);
3 }

编译器会修改Inner类的构造器,添加一个外部类Outer的引用作为参数,大概是这个样子:

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

所以我们在Outer类的outerMethod方法中调用Inner构造器那条语句实际上会被编译器“改成“这个样子:

1 Inner inner = new Inner(this);

我们来通过javap看下生成的字节码,来直观地感受下:

我们重点看一下这一行:

我们可以看到,调用Inner类的构造方法时,确实传入了类型为Outer的参数(即外围类的引用)。

我们还可以看到,编译器为这个类生成了一个名为access$100的静态方法,在这个方法中,加载并获取了own变量。实际上,内部类就会调用这个方法来获取外部类的私有实例变量own。

我们再来看下编译器为内部类生成的字节码:

我们来看一下标号16和19的行,确实是现获取外围类引用,然后调用了access$100方法,并传入了外围类引用作为参数,从而在内部类中能够访问外围类中的private变量。

(2)内部类的特殊语法规则

实际上,使用外围类引用的正规语法规则如下所示:

1 OuterClass.this

例如,以上Inner类的innerMethod方法我们使用正规语法应该这么写:

public void innerMethod() {
    System.out.println("The var own in Outer is " + Outer.this.own);
}  

另一方面,我么也可以采用以下语法更加明确地初始化内部类:

Inner inner = this.new Inner();

我们还可以显示的将内部类持有的外围类引用指向其它的外围类对象,假设outerObject是一个Outer类实例,我们可以这样写:

Outer.Inner inner = outerObject.new Inner();

这样一来,inner所持有的外围类对象引用即为outerObject。

在外围类的作用域之外,我们还可以像下面这样引用它的内部类:

OuterClass.InnerClass

(3)局部内部类

具备内部类即定义在一个方法内部的类,如以下代码所示:

 1 public class Outer {
 2     private int own = 1;
 3     public void outerMethod() {
 4         class Inner {
 5             public void innerMethod() {
 6                 System.out.println("The var own in Outer is " + own);
 7             }
 8         }
 9         System.out.println("In Outer class");
10         Inner inner = new Inner();
11         inner.innerMethod();
12     }
13     public static void main(String[] args) {
14         Outer outer = new Outer();
15         outer.outerMethod();
16     }
17 }

局部类的作用域就被限制在定义它的方法的方法体中,因此它不能用public或private访问修饰符来修饰。

与常规内部类比较,局部类具有一个优势:可以访问局部变量。但是这有一个限制,就是它访问的局部变量必须被声明为final。简单地说,这是出于一致性的考虑。因为局部变量的生命周期随着方法的运行结束也随之结束了,而局部类的生命周期却不会随着方法的结束而结束。在方法运行完后,局部类为了能够继续访问局部变量,需要对局部变量进行备份。

实际上,在创建局部类的对象时,编译器会隐式修改具备类的构造器,并将局部类要访问的“外部变量”作为参数传递给它,这样具备类可以在其内部创建一个拷贝并存储在自己的实例域中。设想若这个变量不是final的,即我们可以在具备类对它进行修改,这无疑会破坏数据的一致性(局部变量与其在局部类内部的拷贝版本不一样)。所以想让局部类访问的变量必须加上final修饰符。

(4)匿名内部类

对于只需要实例化一次的类,我们可以不给它命名,而是通过匿名内部类的形式来使用。匿名内部类的语法形式如下:

new SuperType(construction parameters) {
    inner class methods and data
}

匿名类不能有构造器,因此将构造器参数传给超类的构造器(SuperType)。匿名类内部可以定义一些方法与属性。

还有一种形式的匿名内部类是实现了某种接口,它的语法格式如下:

new Interface() {
    methods and data
}

注意,以上代码的含义并不是实例化一个接口,而是实例化实现了某种接口的匿名内部类。

我们上面提到的传递给Time的构造器的参数之一是一个实现了ActionListener接口的类对象,显然那个类只需要实例化一次,因此我们可以用匿名内部类来实例化:

...
ActionListener listener = new ActionListener() {
    public void actionPerformed(ActionEvent event) {
        ...
    }
};

(5)静态内部类

有时候,我们不想让一个内部类持有外围类对象的引用,这是我们可以选择使用静态内部类。静态内部类不会持有外围类的引用,而非静态的内部类都会持有外围类对象的引用(隐式持有),而这也是导致内存泄露(Memory Leak)的一个常见原因之一。

请看以下代码:

1 public class Outer {
2     private int own = 1;
3     public void outerMethod() { }
4     public static void main(String[] args) { }
5
6     private class Inner {
7         public void innerMethod() { }
8     }
9 }

现在内部类Inner是非静态的,我们用javap查看下编译器生成的相应class文件:

可以看到,Inner类内部持有一个Outer类的引用。

现在我们给Inner类加上static修饰符,让它变为一个静态内部类,再来看一下:

可以看到,现在内部类不再持有外围类Outer的引用了。

3. 参考资料

JAVA核心技术(卷1) (豆瓣)

时间: 2024-10-20 07:58:57

深入理解Java之内部类的相关文章

java之内部类

1 public class RedCowForm { 2 static String formName ; 3 RedCow cow ; //内部类声明对象 4 RedCowForm(){} 5 RedCowForm(String s) 6 { 7 cow =new RedCow(150,112,5000); 8 formName= s; 9 } 10 public void showCowMess() 11 { 12 cow.speak(); 13 } 14 class RedCow //内

java之内部类(InnerClass)----非静态内部类、静态内部类、局部内部类、匿名内部类

提起java内裤类(innerClass)很多人不太熟悉,实际上类似的概念在c++里面也有,那就是嵌套类(Nested Class),关于这俩者的区别,在下文中会有对比.内部类从表面上看,就是在类中定义了一个类(下文可以看到,内部类可以在很多地方定义),而实际上并没有那么简单,乍看上去内部类似乎有些多余,他的用处可能对于初学者来说并不是那么显著,但是随着对他的深入了解,你会发现java的设计者在内裤类上面的确是用心良苦.学会使用内部类,是掌握java高级编程的一部分,他可以让你更优雅的设计你的程

java之内部类总结

内部类:如果A类需要直接访问B类中的成员,而B类又需要建立A类的对象.这时,为了方便设计和访问,直接将A类定义在B类中.就可以了.A类就称为内部类.内部类可以直接访问外部类中的成员.而外部类想要访问内部类,必须要建立内部类的对象. ----------------------------------------------------- class Outer{ int num = 4; class Inner { void show(){ System.out.println("inner s

Java之内部类、包及代码块

个人通俗理解: 1.内部类:有点类似于写在父类中的子类,根据位置不一样为不同的名字,和相应的访问方式不同:不过要访问外部类的话,需要充分运用好this(本类)的这个关键字:要是需要快速的创建子类对象的话,可以用到匿名内部类. 2.包:其实就是个放类的文件夹,当需要用的别的包里的类的时候,则需要导包.不过存在四种不同的修饰符,则对应四种访问权限(default只是理论上的,实际上是不需要写的,直接默认了) 3.代码块:按优先级的话,静态代码块的肯定在第一次使用的时候就执行这唯一的一次了,一般都是用

Effective Java之内部类

Effective Java中对类的权限,接口等的要求,总结起来就是够用就行,不要赋予过多的访问权限,类的定义也是,如果某个类只会在类的内部使用,那就将该类定义为内部类吧. 内部类分为四种: 1.静态内部类:静态内部类就是在class前面多了static关键词的内部类,这种类和类的静态方法和静态变量一样,针对类本省进行操作,在静态内部类中可以随意访问其所在类的静态方法和静态变量. 2.非静态内部类:和静态内部类相对于,其实在类内部定义的所有东西只是受到访问修饰符的限制,所以非静态内部类和类的非静

java之内部类详解

序言 有位小同学要我写一篇这个的总结,我说那好吧,那就动手写总结一下这个内部类的知识,感觉这个在面试中也会经常遇到,内部类.反射.集合.IO流.异常.多线程.泛型这些重要的基础知识大家都比较容易记不住.大概是自己平常用的比较少,所以经常性的会忘记,现在通过博文的方式记录下来,以后忘记可以回过头来自己看. --WH 一.什么是内部类 顾名思义,内部类就是在一个类的内部在定义一个类,比如,A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了. 在一个类中有很多属性,比

Java之内部类的初级应用详解(附源码)

示例源码 在本节中我们将讲述内部类应用中的一个更典型的情况:外部类将有一个方法,该方法返回一个指向内部类的引用,就像在to()和contents()方法中看到的那样. package com.mufeng.thetenthchapter; public class Parcell2 { class Contents { private int i = 11; public int value() { return i; } } class Destination { private String

Java之内部类语法详解(附源码)

前言   可以将一个类的定义放在另一个类的定义内部,这就是内部类. 内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性.然而必须要了解,内部类与组合是完全不同的概念,这一点很重要. 在最初,内部类可能看起来比较奇怪,而且要花些时间才能在设计中轻松地使用它们.对内部类的需求并非总是很明显的,但是在描述完内部类的基本语法与语义之后,在接下来的几个章节中就会发现内部类的益处. 示例源码 package com.mufeng.thetenthchapter;

Java之内部类可以被覆盖吗详解(附源码)

前言 如果创建了一个内部类,然后继承其外围类并重新定义此内部类时,会发生什么呢?也就是说,内部类可以被覆盖吗?这看起来似乎是个很有用的思想,但是"覆盖"内部类就好像它是外围类的一个方法,其实并不起什么作用: 示例源码1 package com.mufeng.thetenthchapter; class Egg { private Yolk y; public Egg() { // TODO Auto-generated constructor stub System.out.print