image.png

什么是内部类

内部类(inner class),是指在一个外部类的内部再定义一个类。内部类作为外部类的一个成员,并且依附于外部类而存在的。
内部类可为静态,可用protected和private修饰(而外部类只能使用public和缺省的包访问权限)。
内部类主要有以下几类:成员内部类、局部内部类、静态内部类、匿名内部类

为什么要使用内部类

主要原因有以下三点:

  • 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据
  • 内部类可以对同一个包中的其他类隐藏起来
  • 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷

此外,每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。如果没有内部类提供的可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。

成员内部类

作为外部类的一个成员存在,与外部类的属性、方法并列。

  1. public class OutterClass {
  2. private String name;
  3. private int age;
  4. public void getInnerClass(){
  5. InnerClass innerClass = new InnerClass();
  6. System.out.println(innerClass);
  7. }
  8. //一个内部类
  9. class InnerClass{
  10. private String nickName;
  11. private String name;
  12. void hello(){
  13. System.out.println("Hello" + OutterClass.this.name);
  14. }
  15. }
  16. }

对于成员内部类来说,有以下需要注意的规则:

  • 成员内部类中,不能定义静态成员
  • 成员内部类中,可以访问外部类的所有成员
  1. class InnerClass{
  2. private String nickName;
  3. private String name;
  4. void hello(){
  5. //在内部类中,访问自己的变量时直接使用变量名,也可以使用this.变量名
  6. System.out.println(nickName);
  7. System.out.println(this.nickName);
  8. //在内部类中访问外部类中与内部类同名的实例变量用外部类名.this.变量名
  9. System.out.println(OutterClass.this.name);
  10. //如果内部类中没有与外部类同名的变量,则可以直接用变量名访问外部类变量
  11. System.out.println(name);
  12. }
  13. }
  • 创建内部类的对象时,必须由此外围类的一个对象来创建其内部类的一个对象
  1. public class OutterClass {
  2. private String name;
  3. private int age;
  4. //外部类的非静态方法访问成员内部类
  5. public void getInnerClass(){
  6. InnerClass innerClass = new InnerClass();
  7. System.out.println(innerClass);
  8. }
  9. //外部类的静态方法访问成员内部类,和外部类的外部访问内部类的方法一样
  10. public static void get(){
  11. OutterClass outterClass = new OutterClass();
  12. InnerClass innerClass = outterClass.new InnerClass();
  13. }
  14. //一个内部类
  15. class InnerClass{
  16. private String nickName;
  17. private String name;
  18. }
  19. }

由此可见,在该外部类的外部,已经该外部类的静态方法里,想要访问到内部类,都要通过该外部类的对象去创建其内部类的对象,语法如下:

OutterClass outterClass = new OutterClass(); InnerClass innerClass = outterClass.new InnerClass();

原因是在内部类的对象被创建的时候,内部类的对象会悄悄地链接到创建它地外部类地对象,从而获得它地一个隐式引用。因此,除非你已经有了外部类的一个对象,否则不可能生成内部类的对象。当然,如果是静态内部类则不需要对其外部类对象的引用。

  • 只有内部类可以是私有类,而常规类只可以是protected或public
  • 内部类中声明地所有静态域都必须是final
  • 内部类不允许有static方法

注意:内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。 对于一个名为outer的外部类和其内部定义的名为inner的内部类。编译完成后出现outer.class和outer$inner.class两类。

局部内部类

在方法中定义的内部类称为局部内部类。与局部变量类似,局部内部类不能有访问说明符,因为它不是外部类的一部分,但是它可以访问当前代码块内的常量,和此外部类所有的成员。

  1. public class OutterClass {
  2. private String name;
  3. private int age;
  4. //方法里定义了一个局部内部类
  5. public void getInnerClass(){
  6. int i = 10;
  7. class InnerClass{
  8. int j = 100;
  9. void get(){
  10. i++; //Error 不可以修改外部变量的值
  11. System.out.println(i);
  12. }
  13. }
  14. InnerClass innerClass = new InnerClass();
  15. System.out.println(innerClass);
  16. }
  17. }

局部类不能public或private访问说明符进行声明,它的作用域被限定在声明这个局部类的块(方法)中。
局部类有一个优势,即是对外部世界可以完全地隐藏起来。即是是OutterClass类中的其他代码也不能访问它。

此外,与其他内部类相比较,局部类还有一个优点。它们不仅能够访问包含它们的外部类,还可以访问局部变量。但是,那些局部变量必须事实上为final。

匿名内部类

匿名内部类(anonymous inner class),简单地说:匿名内部类就是没有名字的内部类。什么情况下需要使用匿名内部类?如果满足下面的一些条件,使用匿名内部类是比较合适的:

  • 只用到类的一个实例。
  • 类在定义后马上用到。
  • 类非常小(SUN推荐是在4行代码以下)
  • 给类命名并不会导致你的代码更容易被理解。

在使用匿名内部类时,要记住以下几个原则

  • 匿名内部类不能有构造方法。
  • 匿名内部类不能定义任何静态成员、方法和类。
  • 匿名内部类不能是public,protected,private,static。
  • 只能创建匿名内部类的一个实例。
  • 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
  • 因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
  1. public void start(){
  2. //这就是一个匿名类,ActionListener是一个函数式接口,创建了一个匿名内部类实现了该接口
  3. ActionListener actionListener = new ActionListener(){
  4. public void actionPerformed(ActionEvent event){
  5. //
  6. }
  7. }
  8. }

因为匿名内部类不能有构造器,但是我们需要一个有参构造器的时候应该怎么办?

这时我们只需简单地传递合适的参数给基类的构造器即可,这里是将x 传进new Wrapping(x)。

  1. public class Parcel7 {
  2. public static void main(String[] args) {
  3. Parcel7 p = new Parcel7();
  4. Wrapping w = p.wrap(10);
  5. }
  6. public Wrapping wrap(int x) {
  7. // Base constructor call:
  8. // Pass constructor argument.
  9. return new Wrapping(x) {
  10. public int value() {
  11. return super.value() * 47;
  12. }
  13. }; // Semicolon required
  14. }
  15. }

如果你有一个匿名内部类,它要使用一个在它的外部定义的对象,编译器会要求其参数引用是final 型的。

静态内部类

如果你不需要内部类对象与其外围类对象之间有联系,那你可以将内部类声明为static。这通常称为嵌套类(nested class)。想要理解static应用于内部类时的含义,你就必须记住,普通的内部类对象隐含地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样了。嵌套类意味着:

  • 要创建嵌套类的对象,并不需要其外围类的对象。
  • 不能从嵌套类的对象中访问非静态的外围类对象。

单例模式:由于静态内部类的加载机制,决定了他可以使用来处理单例模式,而且性能客观

  1. public class Outer {
  2. private static int i = 1;
  3. private int j = 10;
  4. public static void outerF1() {
  5. }
  6. public void outerF3() {
  7. // 外部类访问内部类的静态成员:内部类.静态成员
  8. System.out.println(Inner.inner_i);
  9. Inner.innerF1();
  10. // 外部类访问内部类的非静态成员:实例化内部类即可
  11. Inner inner = new Inner();
  12. inner.innerF2();
  13. }
  14. /**
  15. * 静态内部类可以用public,protected,private修饰
  16. * 静态内部类中可以定义静态或者非静态的成员
  17. */
  18. static class Inner {
  19. static int inner_i = 100;
  20. int innerJ = 200;
  21. static void innerF1() {
  22. // 静态内部类只能访问外部类的静态成员(包括静态变量和静态方法)
  23. System.out.println("Outer.i" + i);
  24. outerF1();
  25. }
  26. void innerF2() {
  27. // 静态内部类不能访问外部类的非静态成员(包括非静态变量和非静态方法)
  28. // System.out.println("Outer.i"+j);
  29. // outerF2();
  30. }
  31. }
  32. }

正常情况下,你不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分,因为它是static 的。只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。另外,声明在接口中的内部类自动成为static和public类

与常规内部类不同,静态内部类可以有静态域和方法。此外,静态内部类只能访问外部类的静态成员。