什么叫匿名类

Java 的匿名类(也叫匿名内部类,因为肯定在某个类的内部)是没有名称的类。因为没有名称就无法在别的地方调用,只能在定义的时候调用了。

匿名类定义:

new SuperClass(Parameter Lists) {
    // 匿名类实现部分
};
// 或者
new SuperInterface() {
    // 匿名类实现部分
};

匿名类是与new关联的,在创建对象的时候定义类,new后面是父类或者父接口;然后是圆括号(),里面可以是传递给父类构造函数的参数,如果父类是一个接口时,没有构造函数则是一对空括号;最后是大括号{},里面是类的定义;因为匿名类定义是一个表达式,所以它是语句的一部分,因此最后有个分号,表示语句结束。

为什么有匿名类

有时候,为了实现一个功能或者实例化一个对象,需要实现一个接口,但是接口应用的地方很多,每个地方的实现都不尽相同,而且需要实例化的地方就只有那么一两处,这个时候,如果为了这些地方,每次都声明一个类来实现接口的话,就会浪费很多空间,还得费时编译,匿名类可以在需要的地方使用接口,可以在使用的同时实现定义,这样不但节省了空间,还可以使代码更加明了。

示例

public class HelloTest {

    private String text = " world";

    private void hello() {

        IHello ihello = new IHello() {
            @Override
            public void sayHello() {
                System.out.print("Hello " + text);
            }
        };

        ihello.sayHello();
    }

    public static void main(String[] args) {
        new HelloTest().hello();
    }
}

public interface IHello {
    void sayHello();
}

以上代码定义了一个IHello接口,只包含一个抽象方法void sayHello(),这是使用匿名类的写法,编译器会提示你替换为 Lambda 表达式:

public class HelloTest {

    private String text = " world";

    private void hello() {

        IHello ihello = () -> System.out.print("Hello " + text);

        ihello.sayHello();
    }

    public static void main(String[] args) {
        new HelloTest().hello();
    }
}

但如果IHello接口包含了两个或两个以上抽象方法,则编译器不会提示你替换为 Lambda 表达式,为什么?

因为,Java 中规定如果一个接口有且仅有一个抽象方法,那么接口就被称为函数接口/功能接口( Functional interface),例如Comparable , Runnable , EventListener , Comparator 等。在 Java8 及之后的版本中,函数接口可以使用 Lambda 表达式代替匿名类表达式,也就是说函数接口可以使用匿名函数代替匿名类实现,这就是 Lambda 表达式的特性之一。所以,了解匿名类有助于我们从本质上去了解匿名函数。

但是,为什么我们将这种接口称为函数接口呢?为啥不叫单方法接口(Single Method Interface)?

这是一个很好的问题,如果大家对函数式编程有所了解,就知道它可以传递代码,即函数,就像将数据或对象传递给方法一样。这些接口只有一种抽象方法被用于传递代码,就像函数式编程语言传递函数一样, 这就是为什么它们被称为函数接口。

一个简单的例子:

Runnable runnable = new Runnable(){
    @Override
    public void run(){
        System.out.println("Running without Lambda");
    }
};
new Thread(runnable).start();

// Running with Lambda
new Thread(() -> System.out.println("Running without Lambda")).start();

仔细观察,我们正在使用这些接口将代码传递给 Thread 的构造函数 ,对于 Thread 类来说,重要的是 run 方法里的代码。而且,我们很容易替换 run 方法的实现,这些接口实际上是策略接口,因为这是策略模式的实现,其中,构成策略的代码被注入到在运行时运行该策略的代码中。

有且仅有一个抽象方法的接口当做函数一样使用,所以叫做函数接口,没毛病吧。那如果接口中声明多个抽象方法呢?还能这么用吗?答案是不能。因为你要实现所有的抽象方法,这时候 Lambda 表达式就会报错了。

函数接口是 Java 8 最重要的概念之一,为 Lambda 表达式提供了动力,但是许多开发人员没有首先了解函数接口在 Java 8 中的作用就花了很多精力来理解它,并花时间学习 Lambda 表达式和 Stream API . 除非您知道什么是功能接口以及Lambda与它之间的关系,否则您将无法使用 Java 8 的强大功能,例如 Lambda 表达式和流 API.

没有函数接口的知识,可能就无法理解在代码中可以使用 Lambda 的位置,并且很难编写所期望的 Lambda 表达式,因此,在 Java 8 中对函数接口有一个很好的了解是非常重要的。可以看到 Java 8 函数接口和 Lambda 表达式通过删除许多样板代码来帮助我们编写更小巧,更简洁的代码。

private void hello() {
    //匿名内部类
    IHello hello = new IHello() {
            @Override
            public void sayHello() {
                System.out.print("Hello " + text);
            }
            
        };
    //匿名函数(Lambda表达式)
    IHello hello = () -> System.out.print("Hello " + text);
}

匿名类和匿名函数的区别

匿名类匿名函数( Lambda 表达式)
没有名称的内部类没有名称的函数
它可以实现包含任何抽象方法的接口它可以实现一个包含单个抽象方法的接口(函数接口)
它可以扩展抽象或具体的类它不能扩展抽象或具体的类
匿名类可以具有实例变量和方法局部变量匿名函数只能具有局部变量
在匿名内部类内部,this始终是指当前匿名类对象,而不是外部对象在匿名函数内部,this始终引用当前的外部类对象
如果我们要处理多种方法,这是最佳选择如果我们要处理接口,这是最佳选择
在编译时,将生成一个单独的.class文件在编译时,不会生成单独的.class文件。只是将其转换为外部类的私有方法
每当我们创建对象时,内存分配都是按需的它驻留在JVM的永久内存中

参考资料:
深入理解Java Lambda表达式,匿名函数,闭包

标签: none

评论已关闭