程序员你真的理解匿名内部类吗?

4,219 阅读8分钟

前言 嘿,java学习者,问你两个问题:如果提到线程你会不会立马想到接口和继承?如果提到接口和继承你会不会立马想到匿名内部类?

开篇甜点

为了加深各位对匿名内部类的印象、好奇心以及求知的渴望,咋们先来看一个程序

package AnonymousInner;

public class NiMingInnerClassThread {
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i<5;i++){
                    System.out.println("熊孩子:"+i);
                }
            }
        };
        new Thread(r).start();
        for (int i = 0; i < 5 ; i++){
            System.out.println("傻狍子:"+i);
        }
    }
}

看不懂没关系啦,看标题就知道了,这只是餐前甜点,这样你才会更有胃口去感受匿名内部类的魅力

唉唉唉...是不是感觉这个写法好像有点不正常啊?但事实上,java确实可以这样玩,这个写法正是匿名内部类方式实现线程的创建~不懂线程没关系,之后博主应该能抽个空出来写一篇线程文章~,这个程序就涉及到了这篇文章的主要核心,那就是匿名内部类。

何谓匿名内部类?

正所谓匿名内部类,正如它的字面意思一样,也就是没有名字的内部类。也是因为没有名字所以匿名内部类只能执行一次,想应该也能想到这一点吧。匿名内部类通常用简化代码编写,我们平时写代码都很少用简化代码,所以看着奇怪也不足为奇。

为什么要使用匿名内部类?

至于为什么要使用匿名内部类这个问题,我觉得先看下面程序之后,会更好的体会:

普通实现抽象方法(不使用匿名内部类)

package AnonymousInner;

abstract class Father{
    public abstract void speak();
}

class Son extends Father{

    @Override
    public void speak() {
        System.out.println("熊孩子:粑粑,我想哦粑粑");
    }
}

public class NIMingDemo {
    public static void main(String[] args) {
        Father f=new Son();
        f.speak();

    }
}
        
运行结果:
        熊孩子:粑粑,我想哦粑粑

可以看到,我们用Son继承了Father类,然后实现了Son的一个实例,将其向上转型为Father类的引用。 但是,如果此处的Son类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?这个时候就应该想到匿名内部类了,匿名内部类就此诞生了!所以我们就可以使用匿名内部类来实现抽象方法,具体操作在下面一节详细讲解。

注意:如果对多态的向上转型概念不够清晰的童鞋可以看看下面这篇文章,下面这篇文章里面详细的介绍了多态的向上向下转型以及多态的优点。【蓝色字体,点击进入】

【java基础之多态】理解多态的向上向下转型从“妈妈我想吃烤山药”讲起

匿名内部类的使用

开篇已经隐式地提醒过大家了,使用匿名内部类有个前提条件:必须继承一个父类或实现一个接口,但最多只能继承一个父类,或实现一个接口。

关于匿名内部类必须要知道的两点知识:

1、匿名内部类不能有构造器(构造方法),你想嘛,匿名内部类没有类名,咋定义构造器,构造方法的方法名是要与类名一致,但匿名内部类可以定义实例初始化块。

1、匿名内部类不可以是抽象类,刚说过了匿名内部类不能有构造器,而抽象类可以有构造方法,这是其一。java在创建匿名内部类的时候,会立即创建内部类的对象,而抽象类不能创建实例,这是其二。

到这里,你可能会问,都看不见名字,怎样判断一个匿名类的存不存在啊?其实很简单,我们讲匿名内部类的使用格式之前来看一个小程序,如下

package AnonymousInner;

abstract class Father{
    public abstract void speak();
}

public class NIMingDemo {
    public static void main(String[] args) {
        Father f=new Father();
    }
}

以上这个程序,小白童鞋会不以为然,而基础好一点的童鞋就会发现,这是错误的写法。你见过抽象类可以实例化嘛?显然不行,会编译失败如下:

在这里插入图片描述
这个时候,我们的匿名内部类就登场了~

package AnonymousInner;

abstract class Father{
    public abstract void speak();
}

public class NIMingDemo {
    public static void main(String[] args) {
        Father f=new Father() {
            @Override
            public void speak() {
                System.out.println("刘东强东墙东强");
            }
        };
    }
}

你可能有点蒙蒙的感觉,咋做到滴,先不说怎么做到代码实现的,我们先来看看java开发工具是怎么实现的,这里要为idea点个赞了!idea写上面的代码是这样的

在这里插入图片描述
eclipse或者Myeclipse工具则是这样的
在这里插入图片描述
通过这里这个小插件楼主只想告诉各位同学,idea的丧心病狂的提示绝壁会让你尖叫的,不仅仅是代码提示还是编译运行速度都强于eclipse,所以人生苦短,请直接上手idea,忘了eclipse吧,如果在学ssm框架的同学可以参考这篇IDEA优雅整合ssm框架(详细思路+附带源码)

OK,回到正题,我们把上面的那个程序的灵魂给抽出来,灵魂代码如下:

abstract class Father(){
....
}
public class NIMingDemo{
   Father f = new Father(){ .... };  //{}里就是个匿名内部类
}

一般来说,new 一个对象时()后应该是分号;,也就是new出对象该语句就结束了。但是匿名内部类就不一样,()后跟的是{}{}中是该new 出对象的具体的实现方法,最后还需要在{}后面加个;代表结束。因为楼主在前面也说过,一个抽象类是不能直接new 的,必须先有实现类了我们才能new出它的实现类。上面的灵魂代码就是表示new 的是Father的实现类,只不过这个实现类是个匿名内部类的形式而已。

到这里我们已经讲了继承这一方面的例子了,这个时候肯定会有小白同学说刚才不是一种再讲抽象和匿名内部类咩?好像没有讲到继承,呃呃呃,小白同学啊抽象类也是一个类,我已经是用Son子类继承了Father父类了,只是这个Father父类就是一个抽象类鸭!!!

至于接口方面也是一样的,啥?也要讲。。。行吧,那下面再来一个匿名内部类用于接口上的例子!

package AnonymousInner;

interface Father{
    public abstract void speak();
}

public class NIMingDemo {
    public static void main(String[] args) {

        Father f=new Father() {
            @Override
            public void speak() {
                System.out.println("粑粑:孩子,不你不想拉粑粑");
            }

            public void eatBaBa(){
                System.out.println("熊孩子:粑粑,我想拉粑粑");
            }
        };

        f.speak();
        f.eatBaBa(); //编译失败,不能调用eatBaBa()方法
    }
}

写这个程序的过程中, f.eatBaBa(); 会编译失败,提示不能调用eatBaBa()方法,Why?注意 Father f=new Father()创建的是Father的对象,而非匿名内部类的对象。其实匿名内部类连名字都没有,你咋实例对象去调用它的方法呢?但是f.speak()方法却可以执行,Why?因为匿名内部类实现了接口Fatherspeak()方法,因此可以借助Father的对象去调用。

肯定会有特别倔强的童鞋就是想调用匿名内部类的自定义的eatBaBa()方法,有没有办法呢?当然也是有办法滴,而且有两个方法:

方法一、 事实上匿名内部类中隐含一个匿名对象,通过该方法可以直接调用eatBaBa()speak()方法;具体代码如下:

package AnonymousInner;

interface Father{
    public abstract void speak();
}

public class NIMingDemo {
    public static void main(String[] args) {

        new Father() {
            @Override
            public void speak() {
                System.out.println("粑粑:孩子,不你不想拉粑粑");
            }

            public void eatBaBa(){
                System.out.println("熊孩子:粑粑,我想拉粑粑");
            }
        }.eatBaBa() ;

    }
}
                 
运行结果:
        熊孩子:粑粑,我想拉粑粑

只不过这个匿名对象只能调用一个方法也就是说只能使用一次,我试着调用两个方法好像行不通,当然你们也可以尝试尝试,博主还是个菜鸟并不能保证这句话的正确性,如有错误请一定要告诉我!感激不尽!!!

方法二、 把eatBaBa()方法更改为speak()方法一样的使用,也就是说在Father接口中声明eatBaBa()方法,然后在匿名内部类中覆写此方法即可。这个就不贴代码了吧!

匿名方法在多线程上的实现

不得不说匿名方法最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是继承Runnable接口,开篇的时候写的就是继承Runnable接口的代码,所以这里博主就讲讲Thread类的匿名内部类实现,代码如下:

package AnonymousInner;

public class ThreadDemo {

    public static void main(String[] args) {

        Thread t = new Thread() {
            public void run() {
                for (int i = 1; i <= 3; i++) {
                    System.out.print(i);
                }
            }
        };
        t.start();
    }
}
运行结果:  123

如果本文对你有所帮助,请给博主点一个爱心,支持一下hhhh!!!

结语:博主并不能保证每句话都正确,如有错误或者博主理解不够深刻的地方请一定要告诉我!!!

最后,欢迎各位关注我的公众号,一起探讨技术,向往技术,追求技术,说好了来了就是盆友喔...

在这里插入图片描述