你好,我是java中的内部类

内部类日常用的仍是挺多的,所以研究和总结内部类就显得很是重要。内部类,顾名思义就是将一个类的定义放在另外一个类的内部进行,这就是内部类。内部类较难理解,可是功能强大,理解和运用内部类对提升编码水平有很是大的帮助。html

初次邂逅---内部类存在的原因

举一个常常看到的例子:java

/**外部类**/
public class Outer {
    /**内部类(内部是相对于外部而言)**/
    class Inner{
        //doSomething
    }
}

如今问题来了,为什么将一个类定义在某个类的内部,难道这不违反程序设计的单一职责原则吗?一个类定义在一个java源文件中不香吗?所以在使用内部类以前,了解使用内部类的理由显得尤其重要,我的以为学知识带着目的来学,印象可能更深一些,毕竟有实际的例子来辅助记忆。安全

曾经读过一本书《Think in java》(像java同样思考),记得里面有一句关于内部类的话:"使用内部类最吸引人的缘由是:每一个内部类都能独立地继承一个(接口的)实现,因此不管外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响"。固然这句话如今几乎成了劝人学内部类的定理。其实这句话透露出一些信息:一、内部类也能够继承某个类或实现某个接口;二、能够突破Java中类的单继承“限制”。微信

看一个例子,来加深对上述论据的理解。定义两个类ClassA和ClassB,很显然ClassC只能实现其中的任意一个类(此处继承ClassA),可是当你在ClassC的内部定义一个内部类InnerClassC,让它继承ClassB时,你会发如今内部类InnerClassC中是能够同时访问到ClassA和ClassB类中的方法:ide

public class ClassA {
    public void classAmethod(){
        System.out.println("classAmethod");
    }
}

public class ClassB {
    public void classBmethod(){
        System.out.println("classBmethod");
    }
}

public class ClassC extends ClassA {
    class InnerClassC extends ClassB{
        public InnerClassC(){
            classAmethod();
            classBmethod();
        }
    }
}

因此使用内部类最大的优势就在于,它能解决多重继承的问题。若是开发者不须要解决多重继承这一问题,那么可使用其余方式,杀鸡焉用牛刀?函数

固然内部类的优势不只仅只有上述一点,《Think in java》这本书中还列举了5点做为补充:一、内部类能够有多个实例,每一个实例都有本身的状态信息,且与其余外围对象的信息相互独立。二、在单个外围类中,可让多个内部类以不一样的方式继承同一个类或者实现同一个接口。三、内部类对象的建立并不必定依赖于外围类对象。四、内部类并无使人迷惑的“is-a”关系(继承关系),它就是一个独立的实体。五、内部类提供了更好的封装,除了该外围类,其余类都不能访问。测试

相识---内部类基础

接下来介绍内部类的基础知识,同时对前面的例子进行更深层次的分析与研究。来看一段代码:ui

//ClassA.class
public class ClassA {
    private String Aname;
    //getter和setter方法
    public void classAmethod(){
        System.out.println("classAmethod");
    }
}

//ClassB.class
public class ClassB {
    private String Bname;
    //getter和setter方法
    public void classBmethod(){
        System.out.println("classBmethod");
    }
}

//ClassC.class
public class ClassC extends ClassA {
    private String Cname;
    //getter和setter方法
    class InnerClassC extends ClassB{
         public InnerClassC(){
            Cname = "I am innerClassC";
            System.out.println(getAname());
            System.out.println(getBname());
            System.out.println(getCname());
        }

        public void show(){
             System.out.println("Cname:"+getCname());
        }
    }

    public static void main(String[] args){
        ClassC c = new ClassC();
        ClassC.InnerClassC ic = c.new InnerClassC();
        ic.show();
    }
}

运行结果为:this

null
null
I am innerClassC
Cname:I am innerClassC

分析一下上述代码的含义:定义了两个类ClassA和ClassB,且均在内部设置了Xname属性及对应方法和一个classXmethod方法。接着再定义了一个ClassC类,它做为外部类继承ClassA类,同时内部也设置了Xname属性及对应方法。ClassC内部类InnerClassC继承了ClassB类,并定义了一个无参的构造方法。在这个无参的构造方法中,能够访问外部ClassC类的属性,哪怕是private修饰的。编码

为什么内部类能够访问外部类的全部属性(包括私有)?缘由在于建立某个外部类的内部类对象时,此时内部类对象一定会捕获一个指向该外部类对象的引用,只要内部类在访问外部类的成员时,就会使用这个引用来选择外部类的成员。说白了,就是这里内部类的建立须要依赖外部类对象(注意是这里,不是全部后续会说明)。

继续回到代码中,在ClassC外部类中定义了一个main方法。在这个main方法中首先实例化一个外部类对象,接着使用ClassC.InnerClassC ic = c.new InnerClassC()来实例化内部类对象。注意看这个内部类对象的引用为ClassC.InnerClassC,对象类型都得依赖外部类,且使用了外部类对象的new方法来建立内部类对象。

仔细查看内部类InnerClassC无参构造方法可知,竟然能够直接使用getAname()getCname方法,这都是外部类的。按照常理开发者都习惯使用this来代指本类,super代指父类。尝试将InnerClassC内部类无参构造方法中的代码修改成以下:

public InnerClassC(){
            Cname = "I am innerClassC";
            System.out.println(this.getAname());
            System.out.println(this.getBname());
            System.out.println(this.getCname());
        }

但是IDEA出现错误提示:

再次验证了前面的论述:这里的getAname()getCname方法都是外部类的,而它又能够直接使用,不须要显式调用。说明内部类中存在对外部类对象的一个隐式引用,其实就是ClassC.this

所以当开发者须要在内部类中生成一个外部类的引用时,使用ClassC.this便可。

内部类初学者都有一个疑问,这个包含内部类的java源文件在编译成class文件后,其中的内部类还存在吗?这个问题今天有必要验证一下。进入到ClassC类所在的文件夹,使用javac *.java命令进行编译(注意不能只单纯编译ClassC文件,其中包含了其余类的引用,须要同时编译):

发现问题了,这个ClassC类竟然编译出两个文件。查看一下这个ClassC$InnerClassC字节码文件:

再来查看ClassC字节码文件:

能够看到这两个class文件已经再也不是一个类了,是具备联系的两个对象。

间接说明内部类是个编译时的概念,一旦编译成功后,就与外部类属于两个彻底不一样但具备必定联系的类。

相知---内部类分类

内部类一共分为4种,分别是:成员内部类、静态内部类、方法内部类和匿名内部类。

成员内部类

成员内部类,顾名思义,内部类做为一个成员而存在于外部类中。它是最普通的内部类,正如前面所见,它能够访问外部类的全部成员属性和方法,哪怕是private修饰的。可是外部类想要访问内部类的成员属性和方法,则须要经过内部类的实例才能进行访问。

如今有一个问题,在成员内部类中是否能使用static关键词?这个static关键词很是魔性,后续会专门出一篇文章来聊聊它。

答案是不能,IDEA提示成员内部类中不能包含任何静态声明。其实很好理解,缘由在于成员属性或者方法都是依赖于对象而存在,而静态属性或者方法并非,它与类有关。

举一个比较典型的成员内部类实例来加深理解:

public class Outer{
   private int outerVar = 1;
   private int commonVar = 2;
   private static int outStaticVar = 3;
   //getter和setter方法
   /**成员方法**/
   public void outerMethod(){
       System.out.println("outerMethod");
   }
    /**静态方法**/
    public static void outerStaticMethod(){
        System.out.println("outerStaticMethod");
    }

    /**内部类**/
    public class Inner{
        /**成员属性**/
        private int commonVar = 102;

        /**无参构造方法**/
        public Inner(){};
        /**成员方法,用来访问外部类的属性和方法**/
        public void show(){
            //当内部类和外部类属性同名时,默认调用的是内部类的属性
            System.out.println("内部类的commonVar属性值:"+commonVar);
            //当内部类和外部类属性同名时,获取同名外部类属性(外部类名.this.同名属性)
            System.out.println("外部类的commonVar属性值:"+Outer.this.commonVar);
            //内部类访问外部类的成员属性
            System.out.println("外部类的outerVar属性值:"+outerVar);
            //内部类访问外部类的静态属性
            System.out.println("外部类的outStaticVar属性值:"+outStaticVar);
            //内部类访问外部类的成员方法
            outerMethod();
            //内部类访问外部类的静态方法
            outerStaticMethod();
        }
    }

    public static void main(String[] args){
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.show();
    }
}

运行结果:

内部类的commonVar属性值:102
外部类的commonVar属性值:2
外部类的outerVar属性值:1
外部类的outStaticVar属性值:3
outerMethod
outerStaticMethod

既然是做为外部类的成员而存在,那么其余的类是否也能访问呢?定义一个Other类,里面的代码为:

public class Other {
    public static void main(String[] args){
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.show();
    }
}

确定是没有问题,可是是否以为代码重复太明显了,所以合适的作法是在Outer类中定义一个get方法用于获取其成员内部类,特别是在内部类构造方法无参的状况下:

//用于获得一个内部类对象
    public Inner getInner(){
        return new Inner();
    }

固然有参构造方法也不麻烦,做为参数传进去便可。

成员内部类小结。经过前面的介绍,能够知道成员内部类具备如下5个特色:

  • 成员内部类做为外部类的成员而存在,能够被任意修饰符修饰;
  • 成员内部类能够直接访问外部类的全部属性和方法,哪怕是private/static修饰的;
  • 成员内部类依赖于外部类,所以内部类对象的建立必须依赖于外部类对象;
  • 成员内部类中不能包含任何静态声明;
  • 成员内部类能够与外部类属性、方法重名,但默认调用的是内部类属性/方法。如想访问外部类属性/方法,可使用外部类名.this.同名属性来访问。

静态内部类

顾名思义,静态内部类就是被static修饰的内部类,既然被static修饰,说明它比较特殊。顺便提一下static能够修饰成员变量、方法、代码块及内部类,这一点后续会有文章进行介绍。前面也说过被static修饰的东西都不依赖对象而存在,而是依赖于类。

在前面介绍成员内部类时,说了这么一句话:成员内部类对象的建立须要依赖于外部类对象。可是如今要谈的静态内部类对象的建立就不依赖外部类对象,也就意味着静态内部类中不能使用外部类中任何非static修饰的变量/方法。

一样举一个比较典型的静态内部类实例来加深理解:

public class Outer {
    private int outerVar = 1;
    private static int commonVar = 2;
    private static int outStaticVar = 3;
    //getter和setter方法
    /**成员方法**/
    public void outerMethod(){
        System.out.println("outerMethod");
    }
    /**静态方法**/
    public static void outerStaticMethod(){
        System.out.println("outerStaticMethod");
    }
    /**静态代码块**/
    static {
        System.out.println("out static block");
    }

    /**静态内部类**/
    public static class StaticInner{
        /**成员属性**/
        private int innerVar = 101;
        /**静态属性**/
        private static int commonVar = 102;
        private static int innerStaticVar = 103;
        /**静态代码块**/
        static {
            System.out.println("inner static block");
        }
        /**成员方法**/
        public void show(){
            //当静态内部类和外部类属性同名时(只能是static属性),默认调用内部类的静态属性
            System.out.println("内部类的commonVar属性值:"+commonVar);
            //当静态内部类和外部类属性同名时(只能是static属性),获取同名外部类属性(外部类名.同名属性)
            System.out.println("外部类的commonVar属性值:"+OuterA.commonVar);
            //静态内部类访问自身成员属性
            System.out.println("内部类的innerStaticVar属性值:"+innerStaticVar);
            //静态内部类访问外部类的静态属性
            System.out.println("外部类的outStaticVar属性值:"+outStaticVar);
            //静态内部类访问外部类的静态方法
            outerStaticMethod();
        }
        /**静态方法**/
        public static void innerStaticMethod(){
            System.out.println("innerStaticMethod");
        }
    }
    public static void main(String[] args){
        System.out.println(StaticInner.innerStaticVar);
        StaticInner.innerStaticMethod();
        new StaticInner().show();
    }
}

猜猜看运行结果:(仔细体会一些里面各个组件的执行顺序)

out static block
inner static block
103
innerStaticMethod
内部类的commonVar属性值:102
外部类的commonVar属性值:2
内部类的innerStaticVar属性值:103
外部类的outStaticVar属性值:3
outerStaticMethod

这里须要注意的一点就是在静态内部类,准确来讲是被static修饰的java组件(属性、方法、代码块,内部类)而言,其内部都不能使用this和super,由于它缺乏一个隐式的this和super,被static修饰的组件与对象无关,仅与类有关。

一样试试其余的类是否能够访问某外部类中的静态内部类,定义一个Other类,里面的代码为:

public class Other {
    public static void main(String[] args){
        //访问某外部类中静态内部类的静态方法时,静态内部类被加载,请注意此时外部类未被加载
        Outer.StaticInner.innerStaticMethod();
        //访问某外部类中静态内部类的成员方法
         new Outer.StaticInner().show();
    }
}

运行结果为:

inner static block  >>>>>>>看到没有外部类的静态代码块没有被初始化
innerStaticMethod
内部类的commonVar属性值:102
out static block
外部类的commonVar属性值:2
内部类的innerStaticVar属性值:103
外部类的outStaticVar属性值:3
outerStaticMethod

请注意,当你直接访问静态内部类中的静态方法时,因为它不依赖与外部类,所以外部类是不会被调用的,可从外部类的静态代码块没有被初始化这一点获得证实。

静态内部类小结。经过前面的介绍,能够知道静态内部类具备如下5个特色:

  • 静态内部类内部能够包含任意信息;
  • 静态内部类只能访问外部类的static属性/方法;
  • 静态内部类能够独立存在,不依赖于其外部类;
  • 可经过外部类名.内部类名.static属性/方法来直接访问内部类的static属性/方法;
  • 静态内部类能够与外部类被static修饰的属性、方法重名,但默认调用的是内部类的属性/方法。如想访问外部类被static修饰的属性/方法,可使用外部类名.同名属性来访问。

局部内部类

顾名思义,局部内部类就是定义在方法和做用域中,通常用来解决较为复杂的问题。既然是局部内部类,那么它就不能被任何访问修饰符修饰,且只能在该定义的方法或做用域中使用。注意局部内部类中不能使用static关键字。

一样举一个比较典型的局部内部类实例来加深理解:

public class OuterB {
    private int outerVar = 1;
    private int commonVar = 2;
    private static int outStaticVar = 3;
    //getter和setter方法
    /**成员方法**/
    public void outerMethod(){
        System.out.println("outerMethod");
    }
    /**静态方法**/
    public static void outerStaticMethod(){
        System.out.println("outerStaticMethod");
    }

    /**外部类的main方法**/
    public static void main(String[] args){
        OuterB outerB = new OuterB();
        outerB.outerCreateMethod("1234");
    }

    /**成员方法,内部定义局部内部类**/
    public void outerCreateMethod(String password){
        /**方法内定义的变量**/
        Boolean flag = true;

        /**局部内部类,注意前面不能使用访问修饰符**/
        class Inner{
            /**局部内部类成员属性**/
            private int innerVar = 101;
            private int commonVar = 102;

            /**局部内部类成员方法**/
            public void show(){
                //当局部内部类和外部类属性同名时,默认调用局部内部类的同名属性
                System.out.println("局部内部类的commonVar属性值:"+commonVar);
                //当局部内部类和外部类属性同名时,获取同名外部类属性(外部类名.this.同名属性)
                System.out.println("外部类的commonVar属性值:"+OuterB.this.commonVar);
                //局部内部类访问自身成员属性
                System.out.println("局部内部类的innerVar属性值:"+innerVar);
                //局部内部类访问外部类的成员属性
                System.out.println("外部类的outerVar属性值:"+outerVar);
                //局部内部类访问外部类的静态属性
                System.out.println("外部类的outStaticVar属性值:"+outStaticVar);

                //局部内部类访问定义它的方法内的参数
                System.out.println("outerCreateMethod方法中传入的参数password:"+password);
                //局部内部类访问定义它的方法内的局部变量
                System.out.println("outerCreateMethod方法中定义的flag:"+flag);

                //局部内部类访问外部类的静态方法
                outerStaticMethod();
                //局部内部类访问外部类的成员方法
                outerMethod();
            }
        }

        /**只能在局部内部类定义的方法内才能访问到它**/
        Inner inner = new Inner();
        System.out.println("局部内部类的commonVar属性值:"+inner.commonVar);
        System.out.println("局部内部类的innerVar属性值:"+inner.innerVar);
        inner.show();
    }
}

猜一猜运行结果:

局部内部类的commonVar属性值:102
局部内部类的innerVar属性值:101
局部内部类的commonVar属性值:102
外部类的commonVar属性值:2
局部内部类的innerVar属性值:101
外部类的outerVar属性值:1
外部类的outStaticVar属性值:3
outerCreateMethod方法中传入的参数password:1234
outerCreateMethod方法中定义的flag:true
outerStaticMethod
outerMethod

经过上面的例子,你们都知道了,局部内部类只能在定义它的方法或者做用域内使用,同时局部内部类的前面不能有任何访问修饰符。除此以外,其他的用法和成员内部类很是类似,所以彻底能够认为局部内部类只是把做用范围缩小了,缩到只能在定义它的方法或者做用域内生效的一个“特殊”的成员内部类。

不知你是否注意到局部内部类中这几行代码,它用于输出局部内部类定义所在方法的参数和局部变量(没法修改参数和变量的值),看到下面的写法,甚至以为局部内部类能够直接访问方法内的局部变量和参数???没错,可是有条件。

//局部内部类访问定义它的方法内的参数
System.out.println("outerCreateMethod方法中传入的参数password:"+password);
//局部内部类访问定义它的方法内的局部变量
System.out.println("outerCreateMethod方法中定义的flag:"+flag);

什么条件呢?前面设置flag为true,可是后面你修改了,将其设置为false:

/**方法内定义的变量**/
        Boolean flag = true;
        flag = false;

这时候局部内部类抛出异常:

IDEA的提示是说"flag变量从内部类中访问,须要final或有效的final"。言外之意:若是局部内部类想访问定义它的方法内的局部变量,那么这个变量要么前面使用final修饰,要么第一次赋值后再也不修改(引用类型是指向不能改变)。特别注意JDK1.8以前(不含JDK1.8)只能访问被final修饰的变量,也就是只有前一种方式,后面这种方式是后来添加的。对于这种特殊的变量限制,在局部内部类访问定义它的方法的参数时,也一样适用。

局部内部类小结。经过前面的介绍,能够知道局部内部类具备如下6个特色:

  • 局部内部类只能定义在方法或者做用域内;
  • 局部内部类前不能有任何访问修饰符;
  • 局部内部类中不能包含任何静态声明;
  • 当被定义的方法/做用域中的参数、变量前有final修饰或者参数、变量的值在赋值后再也不修改时,局部内部类能够直接访问被定义的方法/做用域中的参数、变量,可是没法修改;
  • 局部内部类能够直接访问外部类的全部属性和方法,哪怕是private/static修饰的;
  • 局部内部类能够与外部类的属性、方法重名,但默认调用的是局部内部类的属性/方法。如想访问外部类的属性/方法,可使用外部类名.this.同名属性来访问。

匿名内部类

顾名思义,匿名内部类就是没有名字的内部类,既然没有名字,那么就没法使用访问修饰符,也没有构造方法。匿名内部类用的比较多,所以对于它的研究就显得尤其重要。其实你彻底能够认为匿名内部类是一个没有名字的局部内部类。

匿名内部类的建立格式为:

new 父类构造器(参数列表)|| 实现接口()  
    {  
     //匿名内部类的类体部分  
    }

新手可能第一眼还没明白怎么回事,就建立了一个匿名内部类。从这个建立格式能够看出匿名内部类必须继承一个类或者实现一个接口。匿名内部类的声明不能使用class关键字,而是直接使用new来生成一个对象的隐式引用。

匿名内部类的用法较为特殊,举一个比较典型的实例来加深理解:

package com.envy.inner;

public class OuterC {

    public interface Fruit{
        void eat();
    };

    public static Fruit getFruit(String description){
        Boolean flag = true;
        return new Fruit() {
            @Override
            public void eat() {
                System.out.println("获取被定义类的参数信息:"+description);
                System.out.println("获取被定义类的局部变量:"+flag);
            }
            //注意后面一行的分号不能少!
        };

    }

    public static void main(String[] args){
        /**外部类调用匿名内部类中的方法**/
        OuterC.getFruit("真香").eat();
    }
}

运行结果:

获取被定义类的参数信息:真香
获取被定义类的局部变量:true

从上面的代码中能够知道几点:一、匿名内部类必须继承一个类或者实现一个接口(只能二选一);二、匿名内部类没有类名,因此也没有构造方法;三、匿名内部类没有访问修饰符;四、使用匿名内部类时,new后面的类/接口必须存在,其次可能要重写new以后类/接口的某个或某些方法;五、匿名内部类访问方法参数/变量时,一样也存在和局部内部类同样的访问限制;六、匿名内部类不能是抽象类,所以必须实现继承类或者实现接口的全部抽象方法。

相思---访问限制思考

如今来思考一下为什么在局部内部类(包含匿名内部类)中访问定义它的方法的参数或者局部变量时,会有一个访问限制?即要求这个局部变量(方法参数要)要么前面使用final修饰,要么第一次赋值后再也不修改(引用类型是指向不能改变)。便于说明这里统一为必须使用final关键词修饰。

若是开发者不按照要求进行设置,那么强行访问IDEA就会提示图片所示异常:

便于研究,这里提供一个测试了,里面的代码为:

public class OuterD {
    public interface Fruit{
        void eat();
    }

    public Fruit getFruit(String name){
        boolean flag = true;
        return new Fruit() {
            @Override
            public void eat() {
                System.out.println("parameter:"+name);

                System.out.println("local variable:"+flag);
            }
        };
    }
}

先尝试使用javac *.java命令编译一下这个测试类,因为其中包含一个接口,所以能够猜猜最后会编译出三个class字节码文件:

OuterD.class文件是外部类编译后生成的文件,查看一下其中的内容:

package com.envy.test;

public class OuterD {
    public OuterD() {
    }

    public OuterD.Fruit getFruit(final String var1) {
        final boolean var2 = true;
        return new OuterD.Fruit() {
            public void eat() {
                System.out.println("parameter:" + var1);
                System.out.println("local variable:" + var2);
            }
        };
    }

    public interface Fruit {
        void eat();
    }
}

能够发现编译后外部类内部自动添加了其无参的构造方法,其次对于定义内部类的方法的参数和局部变量前面也都自动添加了final关键词,笔者此处使用的是java1.8,因此也就知道了,为什么1.8及之后能够容许局部变量/方法参数第一次赋值后再也不修改(引用类型是指向不能改变)也是可能的,其实编译后仍是会添加final关键词,只是再也不强制要求开发者添加了。且能够发现它经过使用外部类.接口名这种方式对外提供了内部类的访问方式。

再来查看一下OuterD$Fruit.class文件,能够发现其实它就是Fruit接口编译后的文件:

package com.envy.test;

public interface OuterD$Fruit {
    void eat();
}

能够发现编译后在外部类文件中访问内部类信息都是经过.形式,可是在外部类文件外则是使用$,言外之意为只有某个类中包含内部类,编译后才会有$存在。

再来看这个OuterD$1.class文件,显然这个就是匿名内部类编译后的文件,由于它没有名字只能使用1来标识:

package com.envy.test;

import com.envy.test.OuterD.Fruit;

class OuterD$1 implements Fruit {
    OuterD$1(OuterD var1, String var2, boolean var3) {
        this.this$0 = var1;
        this.val$name = var2;
        this.val$flag = var3;
    }

    public void eat() {
        System.out.println("parameter:" + this.val$name);
        System.out.println("local variable:" + this.val$flag);
    }
}

看到问题的关键了,这里竟然存在一个有参的构造方法,第一个参数是var1(外部类对象),第二个参数是被定义方法传入的参数,第三个参数是被定义方法内定义的局部变量的值。

因此匿名内部类并非直接调用被定义的方法内传递的参数或者局部变量,而是使用本身的构造函数对传入的参数进行了备份,匿名内部类内的方法调用的其实是本身的属性,并非外部传进来的参数或者局部变量。(eat方法就说明了这一点)

不过这个彷佛好像和必须使用final关键词联系不大,其实否则,前面说了内部类中方法其实调用的是本身的属性,那么就能够对内部类的属性进行修改,而不会影响到外部被定义方法中的方法参数或者局部变量。按照常理来讲的确是这样的,可是当笔者尝试在匿名内部类的方法中修改“方法参数或者局部变量”时,IDEA报错了:

其实从开发者角度来讲,既然将形参var2赋值给了val$name属性,那么val$name其实就是var2,二者是同一个对象:

OuterD$1(OuterD var1, String var2, boolean var3) {
        this.this$0 = var1;
        this.val$name = var2;
        this.val$flag = var3;
    }

若是方法内的val$name属性发生了变化,相应的形参var2的值也应该跟着变化,可是每每形参var2的值并不会变,所以这就形成了严重的安全问题,因此才规定使用final关键词来规避这一问题的产生。

看到这里也就明白了其实就是一个拷贝问题,Java不存在指针,那么为避免拷贝的值发生变化(如某局部变量被修改了,而内部类拷贝的值没有跟着变化,这样就形成了内外值不一致的状况),因而引入final关键词来保证该值永远不会变化。

有人可能有这么一个疑问,明明在被定义方法内修改的局部变量是做为一个形参传入到匿名内部类之中,形参变了,它赋值给内部类中的属性,这个属性怎么不会跟着变化呢?

为了解决这个疑问,下面采用Debug来一步步进行调试:

package com.envy.inner;

public class OuterD {
    public interface Fruit{
        void eat();
    }

    public Fruit getFruit(String name){
        boolean flag = true;
        return new Fruit() {
            @Override
            public void eat() {
                System.out.println("parameter:"+name);
                System.out.println("local variable:"+flag);
            }
        };
    }

    public static void main(String[] args){
        OuterD outerD = new OuterD();
        Fruit fruit= outerD.getFruit("envy");
        fruit.eat();
    }
}

执行顺序以下:

仔细看执行顺序,说明getFruit方法是在eat方法前执行的,也就是eat方法中是直接保存了getFruit方法中对象的值。请注意这个匿名内部类中的eat方法是能够被Fruit对象所调用,不过晚于getFruit方法的执行罢了,getFruit方法都执行完了,保存在栈中的局部变量的生命周期也结束了。

须要注意的是,只有匿名内部类访问的方法参数或者局部变量前才须要添加final或者首次赋值后再也不进行修改,对于没有访问的就没有这个限制了:

相恋---没有构造方法也能初始化

在前面屡次提到,因为匿名内部类没有名字,所以没有构造方法,那么它是否也能初始化呢?答案是确定的,可使用构造代码块!举一个比较典型的例子:

package com.envy.inner;

public class OuterE {

    public interface Fruit{
        double getFruitPrice();
        String getFruitName();
    };

    public Fruit getFruit(final String name, final double price){
        return new Fruit() {
            String fruitName;
            double fruitPrice;
            {
                fruitName = name;
                fruitPrice = price;
            }
            public double getFruitPrice() {
                return fruitPrice;
            }
            public String getFruitName(){
                return fruitName;
            }
        };
    }

    public static void main(String[] args){
        OuterE outerE = new OuterE();
        Fruit fruit = outerE.getFruit("苹果",2.8);
        System.out.println(fruit.getFruitPrice());
        System.out.println(fruit.getFruitName());
    }
}

运行结果:

2.8
苹果

请注意这里的Fruit接口中必须定义getFruitPricegetFruitName方法,不然你没法直接经过fruit.getFruitPrice()等形式来调用匿名内部类中属性的getter方法,由于匿名内部类没有名字,因此没法来调用它的方法,只能借助于它实现类的同名方法的实现来完成方法调用。

那么关于内部类就先介绍到这里,里面涉及到的知识点仍是挺多的,须要好好复习和消化。

参考文章:详解内部类浅谈Java内部类,感谢大佬们的指点。

获取更多技术文章和信息,请关注个人我的微信公众号:余思博客,欢迎你们前来围观和玩耍:

image

相关文章
相关标签/搜索