Java内部类相关知识

Java内部类基础

Java可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。内部类一般包括成员内部类,局部内部类,匿名内部类和静态内部类。下面就先来了解一下这四种内部类的用法。

成员内部类

成员内部类位于另一个类的内部,形如下面的形式:

class Circle{
	double radius = 0;
	public Circle(double radius){
		this.radius = radius;
	}
	class Draw{
		public void drawSahpe(){
			System.out.println("drawshape");
		}
	}
}

成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。

当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:

外部类.this.成员变量
外部类.this.成员方法

成员内部类访问外部类的方法:通过再内部类中创建一个对象来访问。

class Circle {
    private double radius = 0;
    public Circle(double radius) {
        this.radius = radius;
        getDrawInstance().drawSahpe();   //必须先创建成员内部类的对象,再进行访问
    }
    private Draw getDrawInstance() {
        return new Draw();
    }
    class Draw {     //内部类
        public void drawSahpe() {
            System.out.println(radius);  //外部类的private成员
        }
    }
}

再外界如果想要创建一个成员内部类对象,前提时必须存在一个外部类对象,创建内部类对象的一般方式如下:

public class Test {
    public static void main(String[] args)  {
        //第一种方式:
        Outter outter = new Outter();
        Outter.Inner inner = outter.new Inner();  //必须通过Outter对象来创建
        //第二种方式:
        Outter.Inner inner1 = outter.getInnerInstance();
    }
}
class Outter {
    private Inner inner = null;
    public Outter() { 
    }
    public Inner getInnerInstance() {
        if(inner == null)
            inner = new Inner();
        return inner;
    }
    class Inner {
        public Inner() {
        }
    }
}

内部类可以拥有private访问权限、pritected访问权限、public访问权限以及包访问权限。比如,如果成员内部类Inner用private修饰,则只能再外部类的内部访问,如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。

局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

class People{
    public People() {
    }
}
class Man{
    public Man(){
    }
    public People getWoman(){
        class Woman extends People{   //局部内部类
            int age =0;
        }
        return new Woman();
    }
}

匿名内部类

匿名内部类没有构造器,没有名字

静态内部类

静态内部类也是定义在另一个类里面的类,

public class Test {
    public static void main(String[] args)  {
        Outter.Inner inner = new Outter.Inner();
    }
}
class Outter {
    public Outter() {
    }
    static class Inner {
        public Inner() { 
        }
    }
}

理解内部类

1. 为什么成员内部类可以无条件访问外部类的成员?

我们通过反编译字节码文件看看是怎么回事?实际上,编译器在进行编译的时候,会将成员内部类单独编译成一个字节码文件,下面是Outter.java的代码:

public class Outter{
	private Inner inner = null;
	public Outter(){}
	public Inner getInnerInstance(){
		if(inner == null){
			inner = new Inner();
			return inner;
		}
	}
	protected class Inner{
		public Inner(){}
	}
}

反编译该类后,我们发现

final com.cxh.test2.Outter this$0;

这行是一个指向外部类对象的指针,也就是说编译器会默认为成员内部类添加一个指向外部类对象的引用,那么这个引用如何赋初值呢?下面接着看内部类的构造器:

public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);

从这里可以看出,虽然我们在定义的内部类的构造器是无参构造器,编译器还是会默认添加一个参数,该参数的类型为指向外部类对象的一个引用,所以成员内部类中的Outter this指针便指向了外部类对象,因此可以在成员内部类中随意访问外部类的成员,从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,则无法对Outter this&0引用进行初始化赋值,也就是无法成员内部类的对象了。

为什么局部内部类和匿名内部类只能访问局部final变量?

想必这个问题也曾经困扰阔很多人,在讨论这个问题之前,先看下面这段代码。

public class Test {
    public static void main(String[] args)  {
    }
    public void test(final int b) {
        final int a = 10;
        new Thread(){
            public void run() {
                System.out.println(a);
                System.out.println(b);
            };
        }.start();
    }
}

这段代码会被编译成两个class文件。默认情况下,编译器会为匿名内部类和局部内部类起名为Outterx.class。
在这里插入图片描述

根据上图可知,test方法中的匿名内部类的名字被起为Test$1。上段代码中,如果把变量a和b前面的任意一个final去掉,终端代码都编译不过,当test方法执行完毕之后,变量a的声明周期就结束了,而此时Thread对象的声明周期很可能还没有结束,那么在Thread的run方法中继续访问变量a就编程不可能了,但是又要实现这样的效果,怎么办呢,Java采用了复制的手段来解决这个问题。将这段代码的字节码反编译可以得到下面的内容:
在这里插入图片描述
我们看到run方法中有一条指令

bipush 10

这条指令表示将操作数10压栈,表示使用的是一个本地局部变量。这个过程是在编译期间有编译器默认进行,如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类的常量池中添加一个内容相等的字面量或直接将相应字节码嵌入到执行字节码中。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等,因此和方法中局部变量完全独立开。
对于通过形参传递进来的局部变量,我们可以得到下面一个例子

public class Test {
    public static void main(String[] args)  {
    }
    public void test(final int a) {
        new Thread(){
            public void run() {
                System.out.println(a);
            };
        }.start();
    }
}

反编译得到
在这里插入图片描述
我们看到匿名内部类Test$1的构造器罕有两个参数,一个是指向外部类对象的引用,一个是int变量,很显然,这里是将变量test方法中的形参a以参数的形式传进来对匿名内部类中的拷贝进行赋值初始化。
也就是说如果局部变量的值在编译期间就可以确定,则直接在匿名内部类里面创建一个拷贝。如果在局部变量的值无法在编译期间决定,则通过构造器传参的方法来对拷贝进行初始化赋值。
从上面可以看出,在run方法中访问的变量a根本不是test方法中的局部变量a。这样一来就解决了前面所说的声明周期不一致的问题。但是新的问题出现了,既然达不到原本的意图和要求。为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行修改,这样数据不一致的问题就得以解决了。

3. 静态内部类有特殊的地方吗?

静态内部类是不依赖于外部类的,也就是说可以在不创建外部类对象的情况下创建内部类的对象。另外,静态内部类是不持有指向外部类对象的引用的。

内部类的使用场景和好处

为什么在Java中需要内部类?

  • 每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个接口的实现,队医内部类都没有影响,内部类使得多继承的解决方案变得完整。
  • 方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
  • 方便编写事件驱动程序。
  • 方便编写线程代码。

补充

关于成员内部类的继承问题。一般来说,内部类是很少用来作为继承用的。但是当用来继承的话,要注意两点

  • 成员内部类的引用方式必须为Outter.Inner
  • 构造器中必须有指向外部类对象的引用,并它通过这个引用调用super()。
class WithInner{
	class Inner{
	}
}

class InheritInner

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值