以下内容为笔者在学习过程中对相关知识点的理解和实践的记录,如有谬误,还请指出。
一、匿名内部类:我看到很多人有提到“匿名内部类是没有名字的类”,个人觉得用陈国君主编的《Java程序设计基础(第五版)》中的 “所谓匿名内部类,是指可以利用内部类创建没有名称的对象,它一步完成了声明内部类和创建该类的一个对象,并利用该对象访问到类里面的成员”来描述,或许要更好理解一些。
下面,我们来看一段代码:
package lambdatest; public class InnerclassTest { //外部类 String name="黄林"; public static void main(String []args){ ( new InnerclassTest(){ //匿名内部类开始 void setName(String n){ this.name = n; System.out.println("内部类---姓名:"+name); } } ).setName("王佳"); //匿名内部类结束 } }
这是它的运行结果:
这里的内部类是直接用外部类InnerclassTest的名字new一个对象,并紧接着定义类体的,这个内部类没有自己的名字,甚至将创建对象的工作直接交给了其父类InnerclassTest(在这里InnerclassTest既是一个外部类,也是匿名内部类的父类),在匿名内部类中重新给其父类成员name赋值,所以最后的结果是匿名内部类中所给的值将其父类成员变量的初始值给覆盖了。
匿名内部类不需要class关键字,自然也不需要public,private等修饰,它没有构造方法,一般用来补充内部类中没有定义的内容,或者实现外部接口或其抽象父类中的抽象方法,这样使代码更加简短。比如上面的代码就为其外部类中的成员name属性添加了setName()方法,并利用匿名内部类对象调用该方法并为name赋值。
编译之后所产生的字节码文件如下:
package lambdatest; public class InnerclassTest { String name="黄岚岚"; void test(){ System.out.println("这是外部类中的方法~"); } public static void main(String []args){ ( new InnerclassTest(){ void setName(String n){ this.name = n; System.out.println("内部类---姓名:"+name); } void test(){ System.out.println("这是内部类中Test方法的输出结果,看看我会不会覆盖外部类中的方法~"+name); } } // ).setName("王佳"); ).test(); } }
这是它的运行结果:
从运行结果来看,匿名内部类中的test()方法对其父类(外部类进行了覆盖),而name仍然是初值,没有改变。
我们再看下面这段代码:
package lambdatest; public class InnerclassTest { //这是一个包含内部类的外部类 public static void main(String []args){ ( new Inner(){ //匿名内部类开始 void setName(String n){ this.name = n; System.out.println("内部类---姓名:"+name); } } ).setName("王佳"); //匿名内部类结束 } static class Inner{ //这是一个普通的内部类,,,mian方法是static的,这里不加static会出错,报错信息见下图 String name="黄岚岚"; } }
在上面的代码中,包含了一个内部类Inner和用Inner来创建的匿名内部类,其中,Inner是InnerclassTest的一个成员内部类。
因为从文件管理的角度看,内部类在编译完成之后,所产生的文件名为:“外部类名$内部类名.class”;
匿名内部类在编译完成之后,所产生的文件名为:“外部类名$编号.class”;
下图为编译完成之后所产生的三个字节码文件:
内部类:一个类定义在另一个类的内部。其中,内部类又可以分为成员内部类(定义在外部类中,与其它外部类中的属性、方法同级)和局部内部类(定义在外部类的方法里面)
package lambdatest; public class InnerclassTest { public static void main(String []args) { Inner i = new Inner() { // static int aa; //匿名内部类中不允许出现静态属性 void test () { System.out.println("这是匿名内部类,运行看会发生什么"); } void haha(){ //如果父类中没有定义该方法,就不能通过父类对象来访问子类特有的方法 System.out.println("hahahahahahahhahahahahah~"); } // static void hi(){ //匿名内部类中不允许出现静态方法 // // } }; i.test(); System.out.println(i.name); // ((Inner) i).haha(); } static class Inner{ //mian方法是static的,这里不加static会出错 String name="黄岚岚"; void test(){ System.out.println("这是内部类"); } // void haha(){ // System.out.println("hahahahahahahhahahahahah~"); // } } }
这里的写法跟上面的不太一样,但仔细看一下,其实是用成员内部类Inner实例化了一个对象 i ,在这里,Inner就变成了那个利用Inner来创建的匿名内部类的父类。
要通过父类对象访问子类的成员,只限于“覆盖”的情况发生时。即子类中的成员test()对父类中同名且参数个数相同(都是无参)的test()进行了覆盖;如果释放 ((Inner) i).haha(); 则会出现错误,因为其父类Inner中并没有定义 haha()方法,所以不能通过其父类对象i去访问子类中特有的方法。
在这里,因为i是父类Inner的实例化对象,所以用i访问不到匿名内部类中的haha()方法。再次强调:i时Inner的对象,不是匿名内部类的,访问不了匿名内部类的特有方法,只能覆盖。
接着我们看看用匿名内部类实现接口的情况:
package lambdatest; public class InnerclassTest { //外部类 public static void main(String []args){ ( new Inner(){ //匿名内部类开始 public void test(){ //不用public修饰不通过,修饰范围>=接口才能覆盖 System.out.println("这是外部类接口中Test方法的实现类~"+name); } } ).test(); //匿名内部类结束 } interface Inner{ //定义一个接口 String name="黄岚岚"; // 默认public static final ,值只能赋一次,且必须赋初值 void test(); //默认用 public abstract修饰 } }
在上面的代码中,在InnerclassTest中定义了一个特殊的内部类------Inner接口(接口是一个特殊的抽象类),里面有一个属性name和一个抽象方法test();同样,用Inner接口new了一个匿名内部类,并实现了它里面的抽象方法test();
package lambdatest; public class InnerclassTest { //外部类 String aa="ccccc"; public void Aa(){ System.out.println("aaaaaa"); } public static void main(String []args){ ( new InnerclassTest(){ //匿名内部类开始,继承InnerclassTest public void Aa(){ System.out.println("bbbbbbbbbb"+aa); } } ).Aa(); //匿名内部类结束 ( new Inner(){ //匿名内部类开始,实现Inner接口 public void test(){ System.out.println("匿名-------------~"+name); } } ).test(); //匿名内部类结束 } interface Inner{ //定义一个接口 String name="黄岚岚"; void test(); } }
上面的代码则定义了一个外部类,并在里面定义了一个接口Inner以及分别用他们创建了一个匿名内部类。
读到这里,相信你们也都看出来了,内部类继承一个类不需要extends修饰,实现一个接口也不需要implements,因为匿名内部类本身没有自己的名字,它的定义与创建该类的实例是同时进行的,也就是说它的前面用new运算符,而不是class关键字,用其父类名或父接口名加上一个"()"就完成了匿名对象的创建。
在这里,InnerclassTset是它们(两个匿名内部类和一个匿名内部类)的外部类,而InnerclassTset对应的匿名内部类继承了InnerclassTset,Inner对应的匿名内部类则实现了Inner接口,也就是说,用谁来创建匿名内部类,就继承(或实现)谁。
创建匿名内部类并访问其成员的语法格式为:
( new 类名或接口名() // ()中不能有参数 { 方法名(参数1,参数2,……) { //方法体 } } ).方法名(参数1,参数2,……);
为什么在类(接口)名后的"()"中不能带有参数呢?
首先我们简单了解什么是构造方法:
构造方法:(下面这句话是从https://baike.so.com/doc/1017212-1075815.html抄来的)
构造方法是一种特殊的方法,它是一个与类同名且返回值类型为同名类类型的方法。对象的创建就是通过构造方法来完成,其功能主要是完成对象的初始化。当类实例化一个对象时会自动调用构造方法。构造方法和其他方法一样也可以重载。因为匿名内部类没有名字,故,它本身没有构造方法,所以也就无法定义带参数的构造函数,因而,在new 一个匿名内部类的时候不可以带上参数。
匿名内部类虽然没有自己的构造方法,但它可以访问其父类的构造方法:
package lambdatest; public class InnerclassTest { //外部类 public void InnerclassTest(){ System.out.println("ffffffffffffff"); } String aa="ccccc"; public void Aa(){ System.out.println("aaaaaa"); } public static void main(String []args) { InnerclassTest in=new InnerclassTest(); in.InnerclassTest(); InnerclassTest inct = ( new InnerclassTest() { public void Aa() { System.out.println("bbbbbbbbbb" + aa); } } ); inct.Aa(); inct.aa = "jjjj"; inct.InnerclassTest(); System.out.println("bbbbbbbbbb" + inct.aa); } }
从产生的字节码文件来看,这里定义了一个外部类以及他的匿名内部类,在匿名内部类中对其父类的Aa()方法进行了覆盖,并利用内部类对象为aa重新赋值,但是笔者在这里还有个疑问(先记录一下。。):
等我弄懂了再来补上好了。
上面说了那么多,我无非是想弄清楚什么是匿名内部类以及证明匿名内部类与创建匿名内部类的类(或接口)之间是一种继承(或实现)关系,虽然书上一开始就提出了这一点,但我一直没弄明白(可能我理解能力比较差,爱钻牛角尖吧,QAQ),上面的内容纯属自己的理解,看到这边文章并且发现有什么不对的或者遗漏的,还请指出来。
现在让我们来总结一下,匿名内部类必须实现一个接口或者继承一个类,不用class修饰,也不需要public,private,protected,static 等修饰;没有类名,也没有构造方法,在匿名内部类里面不能定义静态的属性和方法,只能实例化一次(因为匿名内部类的创建和实例化是同时进行的);匿名内部类其实可以看做是其外部类的一个成员,所以在匿名内部类中可以获取声明为private static 的属性的值,如下面的代码中,同样可以获取到aa的值的(这里跟单纯地继承一个类又有点不同了,因为如果只是继承,是无法获取父类中私有成员的值,也无法访问到父类私有方法的)
package lambdatest; public class InnerclassTest { //外部类 private static String aa = "ccccc"; public void Aa() { System.out.println("aaaaaa"); } public static void main(String[] args) { InnerclassTest ict=new InnerclassTest(); ( new InnerclassTest() { //匿名内部类开始,继承InnerclassTest public void Aa() { System.out.println("bbbbbbbbbb" + aa); } } ).Aa(); //匿名内部类结束 } }
应用场景:
①只需要实例化一次,并且想使代码看起来精简时;
②在Java的窗口程序设计中,用来编写“事件”的程序代码