【问题标题】:Java 8 lambda expression with an abstract class having only one method [duplicate]Java 8 lambda表达式,抽象类只有一个方法[重复]
【发布时间】:2017-12-19 04:02:04
【问题描述】:

我正在学习 Java 8 中的 lambda 表达式。有人可以向我解释如何将 lambda 表达式与只有一种方法的抽象类一起使用(如果可能的话)?

例如,这是抽象类:

public abstract class ClassA {

    public abstract void action();

}

我还有另一个类,它在其构造函数中包含ClassA 的实例:

public ClassB {
   public ClassB(String text, ClassA a){
      //Do stuff
    }
}

所以我想知道如何写这样的东西:

ClassB b = new ClassB("Example", new ClassA(() -> System.out.println("Hello")));

显然该语句不起作用,但有没有办法在这里使用 lambda 表达式?如果有,我做错了什么?

【问题讨论】:

标签: java lambda java-8


【解决方案1】:

不,您无法实现这一点,因为ClassA 必须是一个函数式接口。功能接口是一个只包含一个抽象方法的接口。除了抽象方法之外,它可能包含零个或多个默认方法和/或静态方法。因为函数式接口只包含一个抽象方法,所以在使用 lambda 表达式实现它时可以省略该方法的名称。例如,考虑以下接口:

interface Predicate<T> {
    boolean test(T t);
}

这个接口的目的是提供一个对T类的对象进行操作并返回一个boolean的方法。你可以有一个方法,它接受一个实现这个接口的类的实例,定义如下:

public void printImportantData(ArrayList<Data> dataList, Predicate<Data> p) {
    for (Data d : dataList) {
        if (p.test(d)) {
            System.out.println(d);
        }
    }
}

Data 类可以很简单:

public class Data {
    public int value;
}

现在,您可以按如下方式调用上述方法:

 printImportantData(al, (Data d) -> { return d.value > 1; }); 

请注意,该方法在此处没有名称。这是可能的,因为接口只有一个抽象方法,因此编译器可以找出名称。这可以缩短为:

printImportantData(al, (Data d) -> d.value > 1);

请注意,这里没有大括号,也没有返回关键字。这是可能的,因为该方法返回一个布尔值,而表达式d.value &gt; 1 也返回一个布尔值。因此,编译器能够确定该表达式的值将从方法中返回。这可以进一步缩短为:

printImportantData(al, d -> d.value > 1);

注意没有d的类型声明!编译器可以计算出它需要的所有信息,因为接口只有一个抽象方法,而该方法只有一个参数。因此,您无需在代码中编写所有这些内容。
将上述方法与使用有效执行相同操作的内部类的旧 Java 7 样式进行比较:

printImportantData(al, new Predicate<Data>() {
    public boolean test(Data d) {
        return d.value > 1;
    }
});

【讨论】:

  • 感谢您的回答。因此,如果 ClassA 是一个只有一个参数的接口,我将使用一个接口,我可以使用 lambda。 ;)
  • 不客气!是的,作为functional interface。干杯!
  • 阅读问题的答案我将我的答案标记为“重复”:stackoverflow.com/questions/34424410/… 我现在有点困惑,显然你可以使用 Lambda,即使是普通类,而不仅仅是函数界面,是真的吗?
  • @navy1978 在那个答案中有一个关于Runnable 的例子。正如我们所看到的,这也是一个functional interface,而不是一个 bastract 类。它有一个确切的抽象方法,run()。在我看来,这不是一个正确的例子。
【解决方案2】:

您只能使用 lambda 表达式实例化功能接口。

例如:

如果您将ClassA 更改为接口(您可能想要重命名它):

public interface ClassA {
    public abstract void action();
}

并保持ClassB 原样:

public class ClassB {
   public ClassB(String text, ClassA a){
      //Do stuff
    }
}

您可以将(接口)ClassA 的实例传递给ClassB 构造函数:

ClassB b = new ClassB("Example", () -> System.out.println("Hello"));    

另一方面,你的

ClassB b = new ClassB("Example", new ClassA(() -> System.out.println("Hello")));

由于多种原因尝试失败:

  1. 您不能实例化抽象类(您只能实例化该类的子类)。
  2. 即使您可以实例化类ClassA(如果它不是抽象的),您的ClassA 也没有将某些功能接口作为参数的构造函数,因此您不能将() -&gt; System.out.println("Hello") 作为参数传递给ClassA 的构造函数。

【讨论】:

  • 第一点是错误的:“你不能实例化一个抽象类”,因为你甚至不能实例化一个接口,但是使用一个内部类你可以做到这两点。无论如何,我都给了你 +1,因为你对其余部分的回答是正确的。
  • 声明"You cannot instantiate an abstract class"有什么问题? “但是使用内部类,你可以两者都做” - “内部类”是指匿名类实例吗?那将是抽象类的 子类 的一个实例。 OP 在失败的尝试中没有创建匿名类实例。他们试图创建抽象类本身的实例,这是不可能的。 @navy1978
  • 是的,我指的是匿名类,对不起,刚刚醒来......我不清楚你为什么指出在我的情况下使用抽象类与使用接口不同,我只是认为它是只是实现 Lambda 的问题,因为它也可以用于管理抽象类。从我的角度来看,使用匿名类可以实例化接口或抽象类。我不明白你的意思,对不起...
  • 我想补充一点,如果 lambda 也使用抽象类以及你写的内容:“ClassB b = new ClassB("Example", () -> System.out.println("Hello" ));"在这两种情况下使用它都是正确的方法......
  • @navy1978 该问题的答案创建了一个具体类的实例——它被称为AbstractLambda,但它不是抽象的。并且 lambda 表达式作为参数传递给构造函数,作为功能接口的实现 - Supplier。
【解决方案3】:

嗯,你可以改变你的类,以便能够按照这些思路写一些东西:

public static void main(String[] args) {
    ClassB b = new ClassB("Example", new ClassA(() -> System.out.println("Hello")) {

        @Override
        public void action() {

        }
    });
}

但是为此,您需要更改ClassA 的构造函数以接受功能接口的某些实现,例如java.lang.Runnable:

public abstract class ClassA {

    public ClassA(Runnable runnable) {
        runnable.run();
    }

    public abstract void action();

}

class ClassB {
    public ClassB(String text, ClassA a) {
        //Do stuff
    }
}

当然你还需要实现ClassA中的抽象方法,见上例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-14
    • 1970-01-01
    • 2017-11-16
    • 1970-01-01
    • 2014-03-17
    相关资源
    最近更新 更多