一文搞定Java内部类

一、前言

内部类多而繁杂,互访情况下,不管是内访外,还是外访内,静态(类,方法,成员变量)与非静态(类,方法,成员变量)之间的访问也错综复杂。还有各种令人头疼的编译问题,匿名内部类使用的形参为何必须为final修饰等都是面试喜欢问的点。如果您对内部类还有疑惑,读完本文,说不定能让面试官膜拜您。
本文将从内部类的种类,命名规则,匿名内部类编译,内外互访等角度来阐述。

二、内部类的种类

内部类分为4种:

  • 成员内部类
  • 局部内部类
  • 静态内部类
  • 匿名内部类

成员内部类
与成员变量类似,写在类里。如果内部类持有外部类的引用,可能导致内部类没有执行完,外部类也无法释放,有内存泄漏风险。想要避免这种现象,需要在外部类对象生命周期结束时手动将内部类对象生命周期结束。除非延迟回收会成为系统的运维瓶颈,一般不需要特别关注,GC机制通常足以应付。

public class Outter {
    String s;
    class Inner{
    }
}

局部内部类
局部内部类是定义在一个方法或者一个作用域里的类。Inner2和Inner都是Outter的内部类。
 

public class Outter {
    public Inner getInner2(){
        class Inner2 extends Inner{
            String s = "Inner2";
        }
        return new Inner2();
    }
}
class Inner{
    String s;
}

 静态内部类
好吧,就是成员内部类前面加了static。声明为static的类不会持有外部类的引用,可以通过软引用的方式保存外部类的了引用,只有静态内部类不可能造成内存泄漏。

public class Outter {
    String s;
    static class Inner{
    }
}

匿名内部类
匿名内部类应该是我们用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。

public class Outter {
    void test() {
        new Thread(new Runnable(){
            String S;
            @Override
            public void run(){}
        }).start();
    }
}

三、内部类编译后的命名规则

非匿名内部类类名规则为 OutClass$InnerClass(外部类类名与内部类类名用$连接) 匿名内部类类名则为OutClass$数字(OutClass$1,OutClass$2,OutClass$3)

public class A {
	C c = new C(){
		String s="i am c";
		@Override
		public void demo() {
		}
	};
	C c2 = new C(){
		String s="i am c2";
		@Override
		public void demo() {
		}
	};
}
interface C{
	public void demo();
}

编译之后:显然,符合上述命名规则
一文搞定Java内部类_第1张图片

四、关于匿名内部类的编译问题

先看如下代码:

public class Test {
   public void test(final int b) { 
    final int a = 10;
 
    new Thread(){  
    public void run() {
         System.out.println(a);
    }
    }.start();
  }
}
  • 当方法执行完成后,局部变量a声明周期结束,此时Thread对象的声明周期可能没有结束,在run方法中继续访问a变量变成不可能,怎么解决?
    java采用的是拷贝的套路。如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造方法传参的方式来对拷贝进行初始化赋值(可以反编译查看,发现Test$1的构造方法有两参数,一个是指向外部类对象的引用,一个是int型变量。很显然,int型变量就是Test成员变量a的拷贝。)
  • 在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?
    会造成数据不一致性。为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),数据不一致的问题就解决了。
    这就是必须使用final修饰的原因。

五、内部类,外部类互访

  • 内部类访问外部类

里面的可以自由访问外面的,访问非静态成员变量时必须先创建对象。
以下从内部类的状态开始阐述。

1.非静态内部类的非静态方法
直接访问

public class Outter {
    int i = 5;
    static String word = "Hello";

    class Inner {
        void Test() {
            System.out.println(i);
            System.out.println(word);
        }
    }
}

2.非静态内部类的静态方法
非静态内部类不能有静态方法,编译报错。

3.静态内部类的非静态方法
访问非静态成员变量必须先new外部类。

public class Outter {
    int i = 5;
    static String word = "Hello";

    static class Inner {
        void Test() {
            System.out.println(new Outter().i);
            System.out.println(word);
        }
    }
}

4.静态内部类的静态方法
和第三种情况类似,静态方法访问外部类非静态成员变量必须先new外部类。

public class Outter {
    int i = 5;
    static String word = "Hello";

    static class Inner {
        static void Test() {
            System.out.println(new Outter().i);
            System.out.println(word);
        }
    }
}
  • 外部类访问内部类

    可以将内部类理解为外部类的一个普通成员,所以外面的访问里面的需先new一个对象。
    Outter.Inner inner = new Outter.Inner();

    1.非静态方法访问非静态内部类成员
    先new内部类

public class Outter {
    void Test() {
        System.out.println(new Inner().i);
    }
    class Inner {
        int i = 5;
//      static String word = "hello";  编译报错!
    }
}

2.非静态方法访问静态内部类的成员
先new内部类,再访问非静态的成员变量
静态成员变量可外部类.内部类.变量名直接访问

public class Outter {
    void Test() {
        Outter.Inner inner = new Outter.Inner();
        System.out.println(inner.i);
        System.out.println(inner.string);
        System.out.println(Outter.Inner.string);
    }
    static class Inner {
        int i = 5;
        static String string = "Hello";
    }
}

3.静态方法访问非静态内部类的成员

public class Outter {
    static void Test() {
        System.out.println(new Outter().new Inner().i);
    }
    class Inner {
        int i = 5;
//	static String word = "Hello";  编译报错!
    }
}

4.静态方法访问静态内部类的成员
访问非静态成员需先new 内部类
new Inner().i
与第2种情况相似

public class Outter {
    static void Test() {
        Outter.Inner inner = new Outter.Inner();
        System.out.println(new Inner().i);
        System.out.println(inner.i);
        System.out.println(Outter.Inner.word);
    }
    static class Inner {
        int i = 5;
        static String word = "Hello";
    }
}

5.匿名内部类
匿名内部类访问外部成员变量时,成员变量前应加final关键字。

至此,内部类完结,感觉身体被掏空~
博主常年在线,如有疑问或错误,欢迎评论指出
如果喜欢我的文章,欢迎关注知乎专栏Java修仙道路~
参考博文【Java】内部类与外部类的互访使用小结,匿名内部类 类名规则 定位$1

 

你可能感兴趣的:(Java基础)