java源码分析-内部类

java源码分析-内部类

​ 在我们学习源码的时候经常会看见内部类,本片我们就来详细了解一下内部类的底层原理。

什么是内部类

​ 什么叫内部类?通俗的说,内部类就是定义在一个类的内部的类。内部类本身其实也是一个类的属性,定义方式也是非常类似。举个例子:

public class OuterClass1 {

    class InnerClass{

        public void name(){
            System.out.println("InnerClass.");
        }
    }

    public void name(){
        System.out.println("OuterClass");
    }

    public static void main(String[] args) {
        OuterClass1 outerClass1 = new OuterClass1();
        outerClass1.name();

        OuterClass1.InnerClass innerClass = outerClass1.new InnerClass();
        innerClass.name();
    }
}

以上就是一个内部类,而且是成员内部类。

分类与创建

​ 内部类按功能和定义位置不同可以分为成员内部类、局部内部类、匿名内部类和静态内部类。

成员内部类

​ 成员内部类就是定义在类的内部,成员属性的位置上,同时是非静态的。如下:

public class Outer1 {
    private static String name = "name";

    private int count =2;

    class Inner1 {

        public void num(){
            System.out.println("InnerClass, name:" + name + " count:" +count);
        }
    }

    public void num(){
        System.out.println("OuterClass, name:" + name + " count:" +count);
    }

    public static void main(String[] args) {
        Outer1 outer1 = new Outer1();
        outer1.num();

        Outer1.Inner1 inner1 = outer1.new Inner1();
        inner1.num();
    }
}

特点:

成员内部类可以访问外部类的任意属性和方法,包括静态和非静态、公共和私有的。

成员内部类其实就好比于外部类的一个成员属性,它的创建以来外部类实例。

//创建外部类实例
Outer1 outer1 = new Outer1();
//根据外部类实例通过.new创建内部类实例
Outer1.Inner1 inner1 = outer1.new Inner1();

局部内部类

​ 局部内部类就是定义在方法内的类。方法可以是静态的也可以是非静态的。如下

public class Outer2 {

    private static String name = "name";

    private int num = 12;

    public void method(){
        class Inner21{
            public void fun1(){
                System.out.println(name + num);
            }
        }
    }

    public static void method2(){
        class Inner22{
            public void fun1(){
                //System.out.println(name+num);局部内部类在静态方法中时只能访问外部类的静态成员
                System.out.println(name);
            }
        }
    }
}

特点:

  • 局部内部类如果定义在实例方法(非静态方法)中时,该内部类可以访问外部类的所有变量和方法,包括静态和非静态,公共和非公共的。

  • 局部内部类如果定义在类方法(静态方法)中时,该内部类只可以访问外部类的静态变量和方法。

局部内部类的作用域和局部变量一致只能在方法中使用,所以局部内部类只能在方法中创建:

public static void method2(){
        class Inner22{
            public void fun1(){
                //System.out.println(name+num);局部内部类在静态方法中时只能访问外部类的静态成员
                System.out.println(name);
            }
        }

        Inner22 inner22 = new Inner22();//在内部类所在方法内直接创建
        inner22.fun1();
    }

匿名内部类

​ 匿名内部类就是没有名字的内部类,它是一种特殊的局部内部类,定义在方法中,通过直接使用new关键字的方式定义。

public class Outer3 {

    private static String name = "outer";

    private int num = 10;

    public Inner3 method(final String name){
        return new Inner3(){

            private int num = 10;

            public String name(){
                return name;
            }
        };
    }

    public static void main(String[] args) {
        Outer3 outer3 = new Outer3();
        outer3.method("张三");

    }
}

//匿名内部类必须继承或者实现一个已存在的基类或者接口
class Inner3{

}

特点:

  • 匿名内部类必须继承或者实现一个已存在的基类或者接口;
  • 匿名内部类中不能定义任何静态成员和静态方法;
  • 匿名内部类所在方法的形参如要在内部类中访问,需要被final修饰;
  • 匿名内部类不能是抽象的,它必须要继承类或者实现接口的所有抽象方法;

匿名内部类的创建在上面我们也看到了,就是通过new关键字创建对象的方式:

new/接口{ 
  //匿名内部类实现部分
}

静态内部类

​ 静态内部类其实就是通过static修饰的内部类:

public class Outer4 {
    private static String name = "outer";

    private int num = 8;

    static class Inner4 {
        public void method(){
            System.out.println(name);//匿名内部类不可以访问外部类的成员变量
            Outer4.method();
        }
    }

    public static void method() {
        System.out.println("out method");
    }
}

特点:

静态内部类可以访问外部类所有的静态属性和方法,但是无法访问非静态属性和方法。

静态内谷类的创建方式:

Outer4.Inner4 inner4 = new Outer4.Inner4();
inner4.method();

内部类优点

内部类经常被使用,在源码中也经常能够看到,主要是因为它有以下优点:

1)内部类对想可以访问创建它的外部类对象的所有内容,包括私有属性和方法;

2)被private内部类不能被同一个包下的其它类访问到,具有很好的封装性;

​ 当一个内部类被private修饰时,这个内部类所在包下的其它外部类将无法访问。

public class Outer5 {

    private class Inner5{
        private int a = 10;

        public void num(){
            System.out.println(a);
        }
    }
}

class Other5{

    public void test(){
        Outer5 outer5 = new Outer5();
        //Outer5.Inner5 inner5 = outer5.new Inner5();//这里编译报错
    }
}

当被private修饰的内部类实现某个接口,并且进行向上转型,对外部类来说,接口的实现已经隐藏起来了,很好体现了封装性。

public class Outer6 {

    private class Inner6 implements InterFace6{

        @Override
        public String getName() {
            System.out.println("inner .");
            return "innner";
        }
    }

    //对外提供内部类对象的方法
    public InterFace6 getInterFace6(){
        return new Inner6();
    }

    public static void main(String[] args) {
        Outer6 outer6 = new Outer6();
        InterFace6 interFace6 = outer6.getInterFace6();
        interFace6.getName();
    }
}

interface InterFace6{
    String getName();
}

可以看到,通过将内部类用private修饰,这样就屏蔽了内部类的内部细节,然后通过Outer6外部类提供getInterFace6方法获取接口(实际是内部类的实现),这样就很好的实现类封装的特点。

3)内部类有效的实现了“多继承”,优化了java单继承的缺陷;

​ 在java中,一个类只能有一个直接的父类,也就是说只能显式extends继承一个类。而内部类却可以通过继承某个类或者抽象类的方式来使得外部类看起来就好像继承了多个类。

4)匿名内部类可以很方便的定义回调。

首先我们需要清楚什么是回调?我们看下面这张图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qf9B3OER-1615984371518)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\image-20210316201405152.png)]

在ClassA中调用ClassB的一个方法methodB1(),而ClassB在执行该方法时又调用了ClassA的方法methodA2(),则methodA2()就称为回调函数

当然回调函数也可以是methodA1()方法,这样就成了同步回调函数。

底层实现

字节码标识

​ 内部类也是一个具体的类,只是定义在一个类的内部,那么它生成字节码文件.class吗?肯定的,只要是类,必能会被编译期编译,然后加载到内存中生成Class对象。但是内部类被编译器编译后生成的.class文件又与正常的类生成的class文件有所不同。

​ 我们简单的写一个内部类:

public class Outer7 {

    class Inner7{

    }

}

编译后查看字节码文件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uUPBUE2U-1615984371521)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\image-20210317185554978.png)]

可以发现,编译后生成了两个文件:一个是外部类Outer7.class,而另一个就是内部类的字节码文件了。只是命名有些特殊:

内部类的字节码文件命名规则是:外部类$内部类.class

为什么内部类可以访问外部类的属性和方法

前面我们总结过,对于成员内部类,可以在内部类中访问其外部类的任何成员属性和方法,包括私有的。那为什么它能有这样的特性呢?

我们简单的给上面的内部类添加点东西,如下:

public class Outer7 {

    private int a = 10;

    class Inner7{
        public void show(){
            System.out.println(a);//内部类访问外部类私有成员属性
        }
    }

}

然后编译,并将编译后的Outer7$Inner7.class文件进行反编译:

package test.java.lang;

class Outer7$Inner7 {
    Outer7$Inner7(Outer7 this$0) {
        this.this$0 = this$0;
    }

    public void show() {
        System.out.println(Outer7.access$000(this.this$0));
    }
}

经过反编译后,编译期自动的帮我们生成了一个含有Outer7类型参数的构造器,而这个Outer7 this$0就是外部类的一个引用,看到这里我们及基本明白了,时的,内部类通过类似于构造器注入的方式,将外部类引用注入到内部类的this.this$0,这样,在需要访问外部类属性或者方法的时候就可以通过这个引用来进行访问了。

JDK相关源码

1.作为内部缓存

public final class Integer extends Number implements Comparable<Integer> {
	....

	private static class IntegerCache {     //Integer对象的缓存类,会缓存-127~128之间的int整数对应的Integer对象
        static final int low = -128;
        static final int high;
        static final Integer cache[];   //将数据放入Integer对象数组中

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");    //从jvm系统环境中获取最大值
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);    //转为int数值
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];  //创建(high - low) + 1的大小的数组
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);    //创建对应的Integer对象并存入数组

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }
 }

我们在学习基本类型包装类时,提到一个问题就是包装类有着一定的缓存机制。不同的包装类缓存机制略有不同,这里是Integer的包装类缓存机制的实现。就是通过静态内部类IntegerCache来实现缓存值在指定大小的Integer对象的。通过private进行了内部封装,对于外部来说是透明的。

2.迭代器

​ 在ArrayList等集合中,当我们进行遍历是有一个推荐的方式就是使用迭代器,这个迭代器就是每一个集合类中定义的内部类:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

    ...
        
    public Iterator<E> iterator() {
            return new Itr();
        }

        /**
         * An optimized version of AbstractList.Itr
         */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        ...
    }
        
        ...
 }

​ 内部类Itr实现了Iterator接口,从而实现了相关方法,将内部类Itr定义为private修饰,然后外部类通过提供iterator()方法,来攻外部获取Itr对象,这样就很好的封装了内部实现。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值