内部类与静态内部类详解
前言
如果你是一个急性子,没什么耐性的人,可以只看下句,自己去品味理解:
内部类:就是我是你的一部分,我了解你,我知道你的全部,没有你就没有我。(所以内部类对象是以外部类对象存在为前提的)
静态内部类:就是我跟你没关系,自己可以完全独立存在,但是我就借你的壳用一下,来隐藏自己。
如果还不知道静态和普通成员的区别,就先学static吧。
静态成员:属于这个类,数据存放在class文件中,程序运行之前就已经存进去数据了。
普通成员:属于这个类的对象,程序运行后生成堆栈中。
先来看一下官方说法,根据Oracle官方的说法:
Terminology: Nested classes are divided into two categories: static and non-static. Nested classes that are declared static
are called static nested classes. Non-static nested classes are called inner classes.
一个称为静态嵌套类(静态内部类),一个称为内部类。那么两者到底有什么区别呢?很多JDK源码用到了静态内部类。HashMap、ThreadLocal、AQS的sync等。那么接下来学习一下内部类吧!
内部类
内部类是定义在另外一个类中的类,主要原因有:
- 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据
- 内部类可以对同一个包的其他类隐藏
静态内部类和非静态内部类最大的区别是:非静态内部类编译后隐式保存着外部类的引用(就算外部类对象没用了也GC不掉),但是静态内部类没有。
1.1 非静态内部类
1.1.1 定义
内部类定义语法格式如下:
class OuterClass { ... class NestedClass { ... } }
我们直接先看来一个例子吧,在Human类里定义了一个HumanLeg非静态内部类,并且在HumanLeg类的实例方法中直接访问外部类的private访问权限的实例变量和类变量。
/** * 人类 - 外部类 * * @author 47Gamer */ public class Human { private static final int eyes = 2; private static void count() { System.out.println("I can count number"); } private int teeth = 10; private void say() { System.out.println("Hello world"); } /** * 人腿 - 非静态内部类 */ public class HumanLeg { private Double length; public HumanLeg(Double length) { this.length = length; } public void test() { say(); count(); System.out.println("I have " + eyes + " eyes"); System.out.println("I have " + teeth + " teeth"); System.out.println("My leg has " + length.toString() + "cm long"); } } public static void main(String[] args) { Human human = new Human(); HumanLeg humanLeg = human.new HumanLeg(100D); humanLeg.test(); } }
运行结果:
Hello world I can count number I have 2 eyes I have 10 teeth My leg has 100.0cm long
由此看出,非静态内部类可以直接访问外部类的实例变量、类变量、实例方法、类方法。这是因为在非静态内部类对象里,保存了一个它所寄生的外部类对象的引用(非静态内部类实例必须寄生在外部类实例里)。也就是说,非静态内部类对象总有一个隐式引用,指向了创建它的外部类对象。我们来画一张示意图来理解一下:
另外,还有一些要注意的点
- 非静态内部类的成员只是在非静态内部类范围是可知的,并不能被外部类直接使用,如果要访问非静态内部类的成员必须显示创建非静态内部类对象来调用访问!
- 根据静态成员不能访问非静态成员的规则,外部类的静态方法不能访问非静态内部类。
- 非静态内部类不允许定义静态成员。如下面例子所示:
/** * @author 47Gamer */ public class Test { class Inner{ static { } } // 静态成员无法访问非静态成员 public static void main(String[] args) { new Inner(); } }
1.1.2 内部类的特殊语法规则
如果非静态内部类方法访问某个变量,其顺序为
- 该方法是否有该名字的成员变量 - 直接用该变量名
- 内部类中是否有该名字的成员变量 - 使用this.变量名
- 外部类中是否有该名字的成员变量 - 使用外部类的类名.this.变量名
接下来看一个例子:
/** * @author 47Gamer */ public class Outer { private int i = 1; public class Inner { private int i = 2; public void print() { int i = 3; System.out.println(i); System.out.println(this.i); System.out.println(Outer.this.i); } } public static void main(String[] args) { Outer outer = new Outer(); Inner inner = outer.new Inner(); inner.print(); } }
运行结果:
3 2 1
1.2 静态内部类
如果用static来修饰一个内部类,那么就是静态内部类。这个内部类属于外部类本身,但是不属于外部类的任何对象。因此使用static修饰的内部类称为静态内部类。静态内部类有如下规则:
- 静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。
- 外部类可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象访问其实例成员。
/** * 静态内部类测试外部类。 * * @author 47Gamer */ public class StaticInnerTest { private int x = 1; private static int y = 2; public void test(){ System.out.println(new InnerClass().a); System.out.println(InnerClass.b); } static class InnerClass { private int a = 3; private static int b = 4; public void test(){ //无法访问 // System.out.println(x); System.out.println(y); } } public static void main(String[] args) { StaticInnerTest staticInnerTest = new StaticInnerTest(); staticInnerTest.test(); InnerClass innerClass = new InnerClass(); innerClass.test(); } }