java内部类笔记(超级详细!) | java

发布于 2021-07-10  22 次阅读


前言

因为java内部类东西有点多(多的就离谱 ) ,所以记录一下,下面开始讲解内部类

内部类

1.定义

定义在类1中的类2被称为内部类,类2称为外部类

2.作用

需要内部类主要有以下几点原因

(1)内部类可以实现多重继承,解决java单继承的缺陷(经常配合接口使用)
(2)内部类方法可以访问该类定义所在作用域的数据,包括被private修饰的私有数据
(3)内部类可以很好的实现隐藏(隐藏主要针对的是对同一包中的其他类而言的)
(4)当我们想要定义一个回调函数却不想写大量代码的时候我们可以选择匿名内部类来实现(这个我感觉可有可无)

后面会解释为什么是这几个原因,如果暂时不理解也没问题,要讲原因之前,得先介绍有哪些内部类

3.内部类类别

内部类可以分为静态内部类和非静态内部类。非静态内部类可以分为成员内部类局部内部类匿名内部类

静态内部类

使用static关键字修饰的内部类称为静态内部类。
1、静态内部类访问外部类时——只能访问静态成员
2、外部类访问静态内部类时——首先得创建一个内部类对象,通过对象来调用静态内部类成员
3、外部类外访问静态内部类时——因为静态内部类时外部类的静态成员,静态成员是不需要外部类对象而存在的,所以在外部类外,对静态内部类进行访问时是不需要创建外部类对象的

静态内部类与非静态内部类的对比:

静态内部类非静态内部类
是否可以有静态成员变量
是否可以访问外部类的非静态变量
是否可以访问外部类的静态变量
创建是否依赖于外部类
public class innerStaticClass{
    public static void main(String args[]){
        outClass.inner w1n = new outClass.inner();        //外部类外创建静态内部类对象
        outClass2.inner w2n = new outClass2().new inner();    //外部类外创建非静态内部类对象
        w1n.print();
        System.out.println();
        w2n.print();
    }
}

class outClass{
    static int j = 11;
    static class inner{    //创建静态内部类
        int i=1;
        void print(){
            System.out.println("这是静态内部类");
            System.out.println("j的值为:"+j);
        }
    }
    public void myVoid(){
        inner n=new inner();    //外部类中创建静态内部类对象
        int ii=n.i;
        System.out.println("静态内部类的变量i值为:"+ii);
    }
}

class outClass2{
    class inner{                 //创建非静态内部类
        void print(){
            System.out.println("这是非静态内部类");
        }
    }
}
------------------
Output:
这是静态内部类
j的值为:11

这是非静态内部类

成员内部类

当一个类作为另一个类的非静态成员,则这个类就是一个成员内部类。
1、 类似于成员变量和方法,同样可以在成员内部类前面加可以修饰成员的修饰符
2、成员内部类访问外部类时——可访问任意成员,包括private成员,如果内部类成员与外部类成员同名时可以采用外部类名.this.成员名访问外部类成员,或者是创建一个外部类对象,然后使用该对象调用外部类成员变量。
3、外部类访问内部类时——创建内部类对象,通过对象调用内部类成员。
4、外部类外访问内部类时——首先创建一个外部类对象,然后通过该外部类对象调用创建一个内部类对象。

class outClass{
    private int i=8;            //外部类成员变量i
    class innerClass{            //创建非静态内部类
        int i=9;                //内部类成员变量i
        public void myVoid(){
            System.out.println("内部类中的成员变量值为:" + i);

            outClass w = new outClass();
            System.out.println("外部类中的成员变量值为:"+ w.i);

            System.out.println("外部类中的成员变量值为:"+ outClass.this.i);
        }
    }
}

public class NeiBuLei2{
    public static void main(String args[]){
        outClass w=new outClass();                        //创建外部类对象
        outClass.innerClass wn2=w.new innerClass();        //创建内部类对象
        wn2.myVoid();                                    //调用内部类中成员
    }
}
------------------
Output:
内部类中的成员变量值为:9
外部类中的成员变量值为:8
外部类中的成员变量值为:8

局部内部类

如果一个内部类只在一个方法中使用到了,那么我们可以将这个类定义在方法内部,这种内部类被称为局部内部类。
1、局部内部类的作用范围——同局部变量的作用范围,只在包含该局部内部类的方法内起作用(也就是局部内部类对外完全隐藏,除了创建这个类的方法可以访问它,其余地方是不允许访问的)。
2、局部内部类不允许使用访问修饰符public private protected
3、局部内部类中访问外部类成员变量——可以直接调用
4、局部类中访问外部类的局部变量——在java8以前也就是jdk1.8可以直接访问,没有限制
5、静态方法里的局部内部类只能访问外部类静态成员

class outClass{
    int i = 8;                 //定义一个外部类的成员变量
    public void myVoid(){
        int j=9;            //定义一个外部类的局部变量
        class innerClass{            //定义一个局部内部类
            public void myNeiVoid(){
                i++;//外部类局部变量可以修改并不是final
            System.out.println("外部类的成员变量i值为:"+i);
            System.out.println("外部类的局部变量j值为:"+j);
        }
    }
    innerClass n=new innerClass();        //创建内部类对象
    n.myNeiVoid();      //调用内部类中的成员方法
    }
}

public class Test{
    public static void main(String args[]){
        outClass w=new outClass();        //创建外部类对象
        w.myVoid();
    }
}
------------------
Output:
外部类的成员变量i值为:9
外部类的局部变量j值为:9

如果尝试把局部内部类外的方法改为静态方法就不能访问外部类静态成员

class outClass{
    int i=3;
    public static void myVoid(){
        class innerClass{                //定义一个局部内部类
            public void myNeiVoid(){
                System.out.println("外部类的局部变量值为:"+i);
            }
        }
        innerClass n=new innerClass();            //创建内部类对象
        n.myNeiVoid();            //调用内部类中的成员方法
    }
}

public class Test{
    public static void main(String args[]){
        outClass w=new outClass();        //创建外部类对象
        w.myVoid();
    }
}

报错
在这里插入图片描述

匿名内部类

匿名内部类语法格式:
new 父类构造器(参数列表)| 实现接口() { //匿名内部类的类体部分 }
注意要点:
1、在这里我们看到使用匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐式的。
2、匿名内部类是没有名字的
3、不能显式地为它添加构造方法,参数列表是直接传给父类的,匿名内部类没有构造方法
4、匿名内部类可以直接调用或者重写父类中方法,而且也可以有父类或接口中没有的方法
5、匿名内部类不能访问未加final的局部变量,(注意:JDK1.8即使没有用final修饰也可以访问,但是一旦调用就成了final类型)
6、匿名内部类可以访问外部内的所有成员;
7、属性屏蔽,匿名内部类定义的类型(如变量)会屏蔽其作用域范围内的其他同名类型(变量),而且匿名内部类里变量可以改变值!这点跟外部类变量不同
8、匿名内部内中可以定义内部类;(真套娃,恶心到我了

//网上的经典代码
public abstract class Bird {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public abstract int fly();
}

public class Test {

    public void test(Bird bird){
        System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米");
    }

    public static void main(String[] args) {
        Test test = new Test();
        //下面使用了匿名内部类用来作为参数传递
        test.test(new Bird() {

            public int fly() {
                return 10000;
            }

            public String getName() {
                return "大雁";
            }
        });
    }
}
------------------
Output:
大雁能够飞 10000米

关于上述提到的第五点 ,如果Test类换一种写法就会有关的报错 比如下面这种因为匿名内部类想要使用外部类中变量i但是又想改变i的值这时候就会报错

public class Test {

    public void test(Bird bird){
        System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米");
    }

    public static void main(String[] args) {
        Test test = new Test();
        //下面使用了匿名内部类用来作为参数传递
        int i=10000;
        test.test(new Bird() {

            public int fly() {
                return ++i;
            }

            public String getName() {
                return "大雁";
            }
        });
    }
}

必须要final类型或者是实际上的final类型(意思就是你调用任意外部类变量可以,但是一旦你调用,系统就规定这个变量为final,而且不管是内部类还是外部类里都不准修改,因为final类型就是表示该变量为常量)

在这里插入图片描述

但是如果改成下面这样是可以的,这是因为上述提到的第七点

public class Test {

    public void test(Bird bird){
        System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米");
    }

    public static void main(String[] args) {
        Test test = new Test();
        //下面使用了匿名内部类用来作为参数传递
        int i=10000;
        test.test(new Bird() {
            int i=10000;
            public int fly() {
                return ++i;
            }

            public String getName() {
                return "大雁";
            }
        });
    }
}
------------------
Output:
大雁能够飞 10001米

解释为什么匿名内部类使用的局部变量必须是final类型

(参考了java内部类的四大作用JDK8之前,匿名内部类访问的局部变量为什么必须要用final修饰
为什么局部变量需要final修饰呢
用final修饰实际上就是为了保护数据的一致性
因为局部变量和匿名内部类的生命周期不同。

匿名内部类是创建后是存储在堆中的,而方法中的局部变量是存储在Java栈中,当方法执行完毕后,就进行退栈,同时局部变量也会消失。

那么此时匿名内部类还有可能在堆中存储着,那么匿名内部类要到哪里去找这个局部变量呢?

为了解决这个问题编译器为自动地帮我们在匿名内部类中创建了一个局部变量的备份,也就是说即使方法执结束,匿名内部类中还有一个备份,自然就不怕找不到了。

但是问题又来了。

如果局部变量中的a不停的在变化。
那么岂不是也要让备份的a变量无时无刻的变化。
为了保持局部变量与匿名内部类中备份域保持一致。
编译器不得不规定死这些局部域必须是常量,一旦赋值不能再发生变化了。

所以为什么匿名内部类应用外部方法的域必须是常量域的原因所在了。

特别注意
在Java8中已经去掉要对final的修饰限制,但其实只要在匿名内部类使用了,该变量还是会自动变为final类型(只能使用,不能赋值)。

但是我其实还有一个问题现在还没解决仍有疑惑到现在我也没懂,不知道为什么,就是下面的例子
静态成员变量被匿名内部类调用后却并没有变成final?????是因为不是局部变量吗?等我搞懂了再来回答,暂时保留这个

public class Test {

    public void test(Bird bird){
        System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米");
    }
    static int i=10000;
    public static void main(String[] args) {
        Test test = new Test();
        //下面使用了匿名内部类用来作为参数传递


        test.test(new Bird() {

            public int fly() {
                return ++i;
            }

            public String getName() {
                return "大雁";
            }
        });

    }
}
------------------
Output:
大雁能够飞 10001米

4、作用的解释

前面我们提到了内部类有这几大作用
分别是
(1)内部类可以实现多重继承,解决java单继承的缺陷(经常配合接口使用)
(2)内部类方法可以访问该类定义所在作用域的数据,包括被private修饰的私有数据
(3)内部类可以很好的实现隐藏(隐藏主要针对的是对同一包中的其他类而言的)
(4)当我们想要定义一个回调函数却不想写大量代码的时候我们可以选择匿名内部类来实现(这个我感觉可有可无)

现在举例解释这几大作用:
(参考了java内部类的四大作用 讲的真好!)

(1)内部类可以实现多重继承,解决java单继承的缺陷(经常配合接口使用)
下面例子里用在Wai类里内部类Nei1和内部类Nei2分别继承了Father1和Father2,这样Wai就有了Father1和Father2里的方法和属性,相当于间接让Wai实现了多继承

在这里插入图片描述

(2)内部类方法可以访问该类定义所在作用域的数据,包括被private修饰的私有数据
为什么可以访问:

内部类虽然和外部类写在同一个文件中, 但是编译完成后, 还是生成各自的class文件,内部类通过this访问外部类的成员。

1 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象(this)的引用;

2 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为内部类中添加的成员变量赋值;

3 在调用内部类的构造函数初始化内部类对象时,会默认传入外部类的引用。

编译指令 javac classpath(.java文件的路径)
反编译指令 javap -v(详细信息) classpath(.class文件的路径)

(3)内部类可以很好的实现隐藏(隐藏主要针对的是对同一包中的其他类而言的)
关于内部类的第二个好处其实很显而易见,我们都知道外部类即普通的类不能使用 private protected 访问权限符来修饰的,而内部类则可以使用 private 和 protected 来修饰。当我们使用 private 来修饰内部类的时候这个类就对外隐藏了。这看起来没什么作用,但是当内部类实现某个接口的时候,在进行向上转型,对外部来说,就完全隐藏了接口的实现了

接口

public interface InnerInterface {

    void innerMethod();

}

具体类

/**
 * 实现信息隐藏
 */
public class OuterClass {

    /**
     * private修饰内部类,实现信息隐藏
     */
    private class InnerClass implements InnerInterface {

        @Override
        public void innerMethod() {
            System.out.println("实现内部类隐藏");
        }

    }

    public InnerInterface getInner() {

        return new InnerClass();

    }

}

测试Test

public class Test {

    public static void main(String[] args) {

        OuterClass outerClass = new OuterClass();

        InnerInterface inner = outerClass.getInner();

        inner.innerMethod();

    }

}
------------------
Output:
实现内部类隐藏

从这段代码里面只知道OuterClass的getInner()方法能返回一个InnerInterface接口实例但我并不知道这个实例是这么实现的。

而且由于InnerClass是private的,所以我们如果不看代码的话根本看不到这个具体类的名字,所以说它可以很好的实现隐藏。

(4)当我们想要定义一个回调函数却不想写大量代码的时候我们可以选择匿名内部类来实现
关于匿名内部类相信大家都不陌生,我们常见的点击事件的写法就是这样的:
不用再去实现OnClickListener对象了。

    view.setOnClickListener(new View.OnClickListener(){
        @Override
        public void onClick(){
            // ... do XXX...
        }
    })

后话

这只是我学习java的笔记,如有不全面或者讲错的地方,欢迎大家指正,我会及时查看以及修改,谢谢大家