目录
1. 匿名内部类的语法格式
匿名内部类的定义:匿名内部类我们从字面上的意思理解就可以了, "匿名" 就是没有名字,"内部类" 就是在其它类内部的一个类,合在一起的意思就是 "在其它类中没有名字的内部类"。
匿名内部类的语法格式也比较简单,如下图所示;
对于匿名内部类而言,它里面整体包含了三个部分,继承/实现,方法重写,创建对象。各位童鞋先把匿名内部类的格式混个眼熟,下面我们一点一点来推演匿名内部类为什么是这样的格式
2. 常规类举例
因为直接讲解可能会有很多同学懵,不知道所以然,所以我们先从 Java 中最为常规的类举例说起,一点点带你解析匿名内部类的由来。
如下所示,我先创建一个 Swim 游泳接口
// 创建一个 Swim 游泳接口
public interface Swim {
// 定义一个抽象方法,用于让子类重写实现具体行为
public abstract void swim();
}
现在我来提问,如果我现在要调用 swim 方法,该怎么做?
答案也呼之欲出了吧!使用我们最为常规的方法,可以定义一个类然后实现 Swim 游泳接口,然后重写接口中的方法,再然后创建定义出来的那个类对象,利用类对象调用重写的 swim 游泳方法就可以了。
好的,既然这么说了,下面我们就来试着做一下,代码已经写好了如下所示
// 定义一个学生类实现 Swim 游泳接口
public class Student implements Swim{
// 重写 Swim 接口中的游泳方法
@Override
public void swim() {
// 为了演示简单打印一句话,不做逻辑复杂的语句
System.out.println("重写的游泳方法");
}
// 定义 main 方法
public static void main(String[] args) {
// 创建 Student 类的对象 s1
Student s1 = new Student();
// 让对象 s1 调用实现的方法
s1.swim();
}
}
经过上面的代码书写,我们已经完成了所有的步骤,现在来运行上述方法,我们就可以达到我们想要的结果了,运行结果如下图所示
3. 匿名内部类的推演过程
上面的常规类代码相比对于各位同学来说应该是小意思,那么现在我们来对上面的代码做一个分析。
Student 就是类的名字,实现了 Swim 接口,大括号体内部的所有东西就是当前类的所有内容。
(1)推理第一步:现在我这样做,我把大括号前面的内容全部删掉,只留大括号内部的内容,如下所示,仔细分析可以看出,此时这个类是一个没有名字的类,而且重写了 swim 方法,内部类三要素之一的方法重写已经出现了。
(2)推理第二步:这个没有名字的类重写的方法正是 Swim 接口中的方法,所以说这个没有名字的类实现了 Swim 接口,匿名内部类三要素之一的继承/实现也出现了。
既然这个没有名字的类实现了 Swim 接口,我们姑且把 Swim 接口放在这个没有名字的类的前面,如下图所示
(3)推理第三步:创建对象,我们先来回顾普通创建对象时的创建方法,基本都是使用 new Object() 的方式,而匿名内部类也是一样的道理,既然我们要创建对象,当然也是采用 new ... 的方式,接着刚才第二部的推理,我们将 new 关键字放在 Swim 的前面,将 "()" 放在 Swim 的后面,就得到了如下的样子,匿名内部类三要素之一的创建对象也完成了
此时我们来对比刚才匿名内部类的语法格式,可以发现已经完全你全匹配上了,我们也可以简单的这样理解匿名的推演过程
4. 匿名内部深层分析
经过了上面的推演过程,想必各位同学对于匿名内部类的格式已经有了更好的理解。
精炼成一句话来说就是,"匿名内部类是指大括号内部的那部分代码才是真正的没有名字的类,这个没有名字的类继承或者实现了方法体之前的那个类或接口,最前面的 new 关键字是创建对象用的,new 出来的对象其实就是后面大括号内部没有名字的那个类的对象"。
5. 匿名内部类有什么用?
5.1 问题引入
OK,上面我们大篇幅地讲解了匿名内部类的推演和底层逻辑,估计现在很多同学都会很疑惑,这玩意有啥用啊,我看不出来它有什么好处和优点?
我给大家就一个场景提问一个问题,你就知道它有什么用了。
如下代码,我定义了一个 Animal 接口,
public interface Animal {
void eat();
}
在另一个类中,我定义了一个静态方法,并且方法的参数需要传递一个 Animal 类型的对象,
public class Test6 {
public static void method(Animal a){
a.eat();
}
}
现在,我要在测试类中调用 method 方法,该怎么办?
5.2 解决思路
各位同学要知道,现在 Animal 是个接口,我们不能通过接口去创建对象,所以按照正常的逻辑来说,我们必须创建一个类去实现 Animal 接口,并重写 Animal 接口中的方法,然后再创建 Animal 接口的实现类对象,将对象传递给 method 方法,然后才能调用 Animal 接口中的方法。
如下代码展示
// 创建 Dog 类 实现 Animal 接口
public class Dog implements Animal{
// 重写Animal 中的方法
@Override
public void eat() {
System.out.println("狗吃东西");
}
}
public class Test6 {
public static void method(Animal a){
a.eat();
}
public static void main(String[] args) {
// 创建一个狗对象
Dog dog = new Dog();
// 将 dog 对象作为参数传递给 method
method(dog);
}
}
此时运行 main 方法,就可以得到我们的结果,在控制台得到 "狗吃东西" 的运行结果
5.3 巧用匿名内部类解决代码冗余
通过刚才举的例子和我们的代码解决方案,不难看出,传统的解决思路虽然可以达到目的,却是极为麻烦,带来了大量的代码冗余。如果这个实现类我只使用一次,会不会很浪费。而且这才只是一个接口,如果还有别的静态方法和这个一样,其实会对开发造成一定的影响。
但如果现在我们使用匿名内部类的话,就可以极大地简化开发,提高效率。
代码如下所示,我们完全不需要创建一个类去实现 Animal 接口,直接使用匿名内部类的方式创建 Animal 接口的方法,并在匿名内部类方法内部直接重写 Animal 接口的 eat 方法。这样非常非常简化将代码,极大的提高了开发效率,也避免了代码的冗余
public class Test6 {
public static void method(Animal a){
a.eat();
}
public static void main(String[] args) {
method(new Animal() {
@Override
public void eat() {
System.out.println("狗吃东西");
}
});
}
}
运行结果如下所示,同样可以达到相同的效果
5.4 牢记匿名内部类创建的也是一个对象哦!
各位同学不要忘记了,匿名内部类语法本身创建出来的就是一个对象,既然是一个对象,那么对象 Object 类中的方法它都有,它也可以作为对象调用它自己的方法,下面我就来给同学们展示一些匿名内部类花里胡哨的玩法。
如下代码,我们都知道接口不能创建对象,但现在我利用匿名内部类的方式就能创建 Animal 接口的对象,还能重写 eat 方法,
public class Test6 {
public static void main(String[] args) {
// 采用多态+匿名内部类的方式创建 Animal 接口的对象
Animal animal = new Animal() {
@Override
public void eat() {
System.out.println("猫吃小鱼");
}
};
// 调用 animal 对象中的方法
animal.eat();
}
}
不仅如此,创建出来的 animal 对象还可以调用它自己重写的 eat 方法,是不是很巧妙呀!
运行如下,完全没有问题