@FunctionalInterface、Lambda表达式和方法引用

知识不回顾是会被遗忘的!

网上看了一些相关文章,这里记录一下,仅供参考

Java语言从JDK1.8开始引入了函数式编程。

        函数式编程的核心特点是,函数作为一段功能代码,可以像变量一样进行引用和传递,以便在有需要的时候进行调用。

1. @FunctionalInterface与“函数类型”

        Java对函数式编程的支持,本质是通过接口机制来实现的。首先定义一个仅声明一个方法的接口,然后对接口冠以@FunctionalInterface注解,那么这个接口就可以作为“函数类型”,可以接收一段以Lambda表达式,或者方法引用予以承载的逻辑代码。例如:

@FunctionalInterface
interface IntAdder {int add(int x, int y);
}IntAdder adder = (x, y) -> x + y;

IntAdder 就可以看成是一个“函数类型”。Lambda表达式和方法引用的介绍见后文。

概念如此,需要思考的有几点:

为什么必须是只声明一个方法的接口?
        显然这个方法就是用来代表“函数类型”所能执行的功能,一个函数一旦定义好,它能执行的功能是确定的,就是调用和不调用的区别。接口中声明的方法就是和函数体定义一一对应的。
        事实上,@FunctionalInterface下只能声明一个方法,多一个、少一个都不能编译通过 。覆写Object中toString/equals的方法不受此个数限制。

比如Comparator接口就声明了2个方法:

// Comparator.java
@FunctionalInterface
public interface Comparator<T> {int compare(T o1, T o2);boolean equals(Object obj);//...
}

        严格地说,@FunctionalInterface下只能声明一个未实现的方法,default方法和static方法因为带有实现体,所有不受此限制。

  @FunctionalInterfacepublic interface IAdd<T, R> {R add(T t1, T t2);default R test1(T t1, T t2) {//可以额外定义default方法return null;}static <T,R> R test2(T t1, T t2) {//可以额外定义static方法return null;}}

        关于interface中声明default/static方法有疑虑的话,可以查阅博主另一篇文章:java接口里面可以有成员变量么?

        @FunctionalInterface注解不是必须的,不加这个注解的接口(前提是只包含一个方法)一样可以作为函数类型。不过,显而易见的是,加了这个注解表意更明确、更直观,是更被推荐的做法。
        要定义清楚一个函数类型,除了函数名称,必须明确规定函数的参数个数和类型、返回值类型,这些信息都是包含于接口中声明的方法。


2. JDK提供的“函数类型”

java.util.function包下预定义了常用的函数类型,包括:

@FunctionalInterface
public interface Consumer<T> {void accept(T t); //接收一个类型为T(泛型)的参数,无返回值;所以叫消费者
}
@FunctionalInterface
public interface BiConsumer<T, U> {void accept(T t, U u);//接收2个参数,无返回值
}
@FunctionalInterface
public interface Supplier<T> {T get();//无参数,有返回值(所以叫提供者)
}
//注意没有BiSupplier,因为返回值只能有1个,不会有2个
@FunctionalInterface
public interface Function<T, R> {R apply(T t);//一个输入(参数),一个输出(返回值)
}
@FunctionalInterface
public interface BiFunction<T, U, R> {R apply(T t, U u);//两个输入T和U,一个输出R
}
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {static <T> UnaryOperator<T> identity() {//一元操作,输入原样返回给输出return t -> t;}
}
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {//二元操作,输入输出类型相同public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {Objects.requireNonNull(comparator);return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;//传入比较器,返回较小者}public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {Objects.requireNonNull(comparator);return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;//传入比较器,返回较大者}
}

        这些个定义,都是在参数个数(0,1,2)和有无返回值上做文章。另外还有一些将泛型类型具体化的衍生接口,比如Predicate、LongSupplier等等。

@FunctionalInterface
public interface Predicate<T> {boolean test(T t);//输入1个参数,返回boolean,就好比是预言家,预言你这个输入是真还是假
}
@FunctionalInterface
public interface LongSupplier {long getAsLong();//没有输入,输出long类型(long类型的提供者)
}

3. Lambda表达式

        上面弄清楚了函数类型@FunctionalInterface,那么函数类型能接收怎么样的函数实现体呢?怎么接收呢?该Lambda出场了。

        Lambda表达式能赋值给一个变量,也就能当作参数传给函数。这个Lambda形式的变量/参数的类型是它所实现的那个接口,所包含的方法体便是这个接口抽象方法的实现。以后看到调用方法的参数是一个SAM类型接口的时候就可以考虑使用Lambda表达式替换匿名内部类来写。

作用:

  • ①减少代码量,突出代码意图
  • ②对集合数据 Collection 操作更简便
  • ③使变量记住一段逻辑:

        任务逻辑传递(传递一段运算逻辑给执行者)

        回调逻辑传递(简化接口回调的时候 new匿名类后实现抽象方法的模版代码)

将一个方法写成Lambda表达式,只需要关注参数列表和方法体。        

语法组成:

(参数类型 参数名) -> {
        方法体;

        return 返回值;

}

  • ①形式参数:最前面的部分是一对括号,里面是参数,无参数就是一对空括号
  • ②箭头:中间的是 -> ,用来分割参数和body部分,读作“ goes to”
  • ③方法体:body部分可以是一个表达式或者一个代码块。

简写:

  • ①可选类型声明:不用声明参数类型,编译器可以自动识别。
  • ②可选参数括号:一个参数无需定义括号,多个参数需要定义。
  • ③可选大括号:如果body部分只包含一个语句或表达式,就不需要使用大括号括起来。
  • ④可选 return 关键字:如果body部分是一个表达式,表达式的值会被作为返回值返回;如果是代码块,需要用 return 指定返回值

        Java8 之前创建接口实现类总会有很多冗余的模版代码,接口中定义的抽象方法越多,每次实现的模版代码就越多,而很多时候这个接口实现类只需要用到一次。 

变量作用域 :

  • ①局部变量:引用的局部变量不可被修改值,即必须是final所修饰的,或者不被后面代码更改的(隐形final属性)。
  • ②成员变量:可以修改类成员变量(非 final 修饰的)和静态变量。
  • ③在Lambda表达式中,不允许声明一个与局部变量同名的参数或者局部变量。
  • ④在Lambda表达式中,使用this调用的是该Lambda表达式所属方法的所属实例的this。
  • ⑤在Lambda表达式中,无法访问到自身接口的默认方法(不存在实例)。

        Lambda表达式用来定义函数实现体。有很多种写法(都是为了简化书写),但核心是通过->连接参数和实现代码:

(入参)->{实现代码}

//无返回值的时候
(int x)->{System.out.println(x);}
(x)->{System.out.println(x);}//参数类型自动推断
x->{System.out.println(x);}//只有一个参数的时候,可以省略小括号
x->System.out.println(x);//实现体只有一个表达式可以省略大括号,System.out.println本身无返回值

//有返回值的情况
(int x)->{return x*x;}
(x)->{return x*x;}
//x->return x*x; //错误,不能这么写!!
x->x*x;

说了这么多,来实操一把:

IntConsumer ic = x->System.out.println(x);
IntFunction<Integer> ifi1 = x->{return x*x;};
IntFunction<Integer> ifi2 = x->x*x;
ic.accept(100);//100
System.out.println(ifi1.apply(5));//25
System.out.println(ifi2.apply(5));//25

好了,函数类型–>Lambda表达式说明白了,再来看看方法引用是怎么回事。

4. 四种方法引用

        文章开头说过了,函数类型可以接收一段Lambda表达式,或者对方法的引用。方法引用就是对一个类中已经存在的方法加以引用,分4中类型:(以Test类为例)

  1. 对类构造方法的引用,如Test::new。
  2. 对类静态方法的引用,如Test::staticMethodName
  3. 对对象实例方法的引用,如:new Test()::instanceMethod
  4. 是2和3的结合,如Test::instanceMethod2,但要求函数类型声明和函数调用的时候,其第一个参数必须是Test类的实例。

第4种比较难以说清楚,看看下面的例子吧:

public class Test {private String name = "";public Test() {System.out.println("构造方法:无参数");}public Test(String name) {this.name = name;System.out.println("构造方法:参数="+name);}public static void staticMethod(String str) {System.out.println("static method: input=" + str);}public void instanceMethod(String str) {System.out.println("instance method: input=" + str);}public static void main(String[] args) {Supplier<Test> s1 = Test::new;//对无参构造器的引用,无参构造器其实就是一个对象的Supplier(提供者)s1.get();//调用构造方法:无参数Function<String, Test> f1 = Test::new;//引用有一个String参数的构造器f1.apply("Test");//调用构造方法:参数=TestConsumer<String> c1 = Test::staticMethod;//对静态方法引用c1.accept("1");//static method: input=1Consumer<String> c2 = new Test()::instanceMethod;//对实例方法的引用c2.accept("2");//instance method: input=2//第4种BiConsumer<Test, String> bc1 = Test::instanceMethod;bc1.accept(new Test(), "3");//instance method: input=3}
} 

第4中方法引用,本质上是对实例方法的引用,只不过是在调用的时候才传入那个实例对象。

5. andThen链式表达

JDK中很多函数类型,都实现了default的andThen方法,可以将多个函数体(Lambda表达式、方法引用)串起来,方便进行链式调用。
调用链上的任何一个抛出异常,整个调用链会提前结束,异常由调用者处理。

/*** 通过andThen()进行链式操作*/
@Test
public void testLinkConsumer() {IntConsumer action = x-> System.out.print(x);action = action.andThen(x->System.out.print("--tail1")).andThen(x->System.out.print("--tail2"));//100--tail1--tail2action.accept(100);
}

6. 最后

小插曲:Callable和Runnable到底什么区别?

//java.util.concurrent.Callable
@FunctionalInterface
public interface Callable<V> {V call() throws Exception;
}
//java.lang.Runnable
@FunctionalInterface
public interface Runnable {public abstract void run();
}

这两者也都是JDK预定义的函数接口,两者都不接收参数,主要用于多线程编程。

Runnable无返回值,一般用于new一个新线程的时候,在新线程中执行代码。

Callable一样一般用于在新线程中执行,只不过执行成功后有返回值,如果执行失败还会抛异常。

最后,一起分析:

Callable<Integer> c1 = ()->1;
Callable<Integer> c2 = ()->c1.call();

c1引用了一个Lambda表达式;

c2引用了一个新的Lambda表达式,表示式的实现代码中调用了c1提供的call()方法,并将call()方法的返回值返回。

Callable<Integer> c1 = ()->1;
Callable<Integer> c2 = ()->c1.call();
Callable<Integer> c3 = ()->{System.out.println("c3 call c1");return c1.call();
};try {System.out.println(c1.call());//1System.out.println(c2.call());//1System.out.println(c3.call());//c3 call c1//1
} catch (Exception e) {e.printStackTrace();
}

注意,c3和c2本质上是一样的,只不过方法实现上,多加了一行打印代码。

7、Lambda 表达式的各种形态和使用场景

        Lambda 表达式是 Java 8 中添加的功能。引入 Lambda 表达式的主要目的是为了让 Java 支持函数式编程。 Lambda 表达式是一个可以在不属于任何类的情况下创建的函数,并且可以像对象一样被传递和执行。

Java lambda 表达式用于实现简单的单方法接口,与 Java Streams API 配合进行函数式编程

        在前几篇关于 List、Set 和 Map 的文章中,我们已经看到了这几个 Java 容器很多操作都是通过 Stream 完成的,比如过滤出对象 List 中符合条件的子集时,会使用类似下面的 Stream 操作。

List<A> list = aList.filter(a -> a.getId() > 10).collect(Colletors.toList);

        其中filter方法里用到的a -> a.getId() > 10就是一个 Lambda 表达式,前面对用到 Lambda 的地方知识简单的说了一下,如果你对各种 Stream 操作有疑问,可以先把本篇 Lambda 相关的内容学完,接下来再仔细梳理 Stream 时就会好理解很多了。

Lambda 表达式和函数式接口

        上面说了 lambda 表达式便于实现只拥有单一方法的接口,同样在 Java 里匿名类也用于快速实现接口,只不过 lambda 相较于匿名类更方便些,在书写的时候连创建类的步骤也免去了,更适合用在函数式编程。

        举个例子来说,函数式编程经常用在实现事件 Listener 的时候 。 在 Java 中的事件侦听器通常被定义为具有单个方法的 Java 接口。下面是一个 Listener 接口示例:

public interface StateChangeListener {public void onStateChange(State oldState, State newState);
}

        上面这个 Java 接口定义了一个只要被监听对象的状态发生变化,就会调用的 onStateChange 方法(这里不用管监听的是什么,举例而已)。 在 Java 8 版本以前,监听事件变更的程序必须实现此接口才能侦听状态更改。

比如说,有一个名为 StateOwner 的类,它可以注册状态的事件侦听器。

public class StateOwner {public void addStateListener(StateChangeListener listener) { ... }
}

我们可以使用匿名类实现 StateChangeListener 接口,然后为 StateOwner 实例添加侦听器。

StateOwner stateOwner = new StateOwner();stateOwner.addStateListener(new StateChangeListener() {public void onStateChange(State oldState, State newState) {// do something with the old and new state.System.out.println("State changed")}
});

        在 Java 8 引入Lambda 表达式后,我们可以用 Lambda 表达式实现 StateChangeListener 接口会更加方便。

        现在,把上面例子接口的匿名类实现改为 Lambda 实现,程序会变成这样:

StateOwner stateOwner = new StateOwner();stateOwner.addStateListener((oldState, newState) -> System.out.println("State changed")
);

在这里,我们使用的 Lambda 表达式是:

(oldState, newState) -> System.out.println("State changed")

        这个 lambda 表达式与 StateChangeListener 接口的 onStateChange() 方法的参数列表和返回值类型相匹配如果一个 lambda 表达式匹配单方法接口中方法的参数列表和返回值(比如本例中的 StateChangeListener 接口的 onStateChange 方法),则 lambda 表达式将转换为拥有相同方法签名的接口实现 这句话听着有点绕,下面详细解释一下 Lambda 表达式和接口匹配的详细规则。

匹配Lambda 与接口的规则

        上面例子里使用的 StateChangeListener 接口有一个特点,其只有一个未实现的抽象方法,在 Java 里这样的接口也叫做函数式接口 (Functional Interface)。将 Java lambda 表达式与接口匹配需要满足一下三个规则:

  • 接口是否只有一个抽象(未实现)方法,即是一个函数式接口
  • lambda 表达式的参数是否与抽象方法的参数匹配
  • lambda 表达式的返回类型是否与单个方法的返回类型匹配

如果能满足这三个条件,那么给定的 lambda 表达式就能与接口成功匹配类型。

函数式接口

        只有一个抽象方法的接口被称为函数是式接口,从 Java 8 开始,Java 接口中可以包含默认方法和静态方法。默认方法和静态方法都有直接在接口声明中定义的实现。这意味着,Java lambda 表达式可以实现拥有多个方法的接口——只要接口中只有一个未实现的抽象方法就行

        所以在文章一开头我说lambda 用于实现单方法接口,是为了让大家更好的理解,真实的情况是只要接口中只存在一个抽象方法,那么这个接口就能用 lambda 实现。

        换句话说,即使接口包含默认方法和静态方法,只要接口只包含一个未实现的抽象方法,它就是函数式接口。比如下面这个接口:

import java.io.IOException;
import java.io.OutputStream;public interface MyInterface {void printIt(String text);default public void printUtf8To(String text, OutputStream outputStream){try {outputStream.write(text.getBytes("UTF-8"));} catch (IOException e) {throw new RuntimeException("Error writing String as UTF-8 to OutputStream", e);}}static void printItToSystemOut(String text){System.out.println(text);}
}

即使这个接口包含 3 个方法,它也可以通过 lambda 表达式实现,因为接口中只有一个抽象方法 printIt没有被实现

MyInterface myInterface = (String text) -> {System.out.print(text);
};

Lambda VS 匿名类

        尽管 lambda 表达式和匿名类看起来差不多,但还是有一些值得注意的差异。 主要区别在于,匿名类可以有自己的内部状态--即成员变量,而 lambda 表达式则不能。

public interface MyEventConsumer {public void consume(Object event);
}

比如上面这个接口,通过匿名类实现

MyEventConsumer consumer = new MyEventConsumer() {public void consume(Object event){System.out.println(event.toString() + " consumed");}
};

MyEventConsumer 接口的匿名类可以有自己的内部状态。

MyEventConsumer myEventConsumer = new MyEventConsumer() {private int eventCount = 0;public void consume(Object event) {System.out.println(event.toString() + " consumed " + this.eventCount++ + " times.");}
};

        我们给匿名类,加了一个名为 eventCount 的整型成员变量,用来记录匿名类 consume 方法被执行的次数。Lambda 表达式则不能像匿名类一样添加成员变量,所以也成 Lambda 表达式是无状态的。

推断 Lamdba 的接口类型

        使用匿名类实现函数式接口的时候,必须在 new 关键字后指明实现的是哪个接口。比如上面使用过的匿名类例子

stateOwner.addStateListener(new StateChangeListener() {public void onStateChange(State oldState, State newState) {// do something with the old and new state.}
});

        但是 lambda 表达式,通常可以从上下文中推断出类型。例如,可以从 addStateListener() 方法声明中参数的类型 StateChangeListener 推断出来,Lambda 表达式要实现的是 StateChangeListener 接口。

stateOwner.addStateListener((oldState, newState) -> System.out.println("State changed")
);

        通常 lambda 表达式参数的类型也可以推断出来。在上面的示例中,编译器可以从StateChangeListener 接口的抽象方法 onStateChange() 的方法声明中推断出参数 oldState 和 newState 的类型。

Lambda 的参数形式

        由于 lambda 表达式实际上只是个方法,因此 lambda 表达式可以像方法一样接受参数。Lambda 表达式参数根据参数数量以及是否需要添加类型会有下面几个形式。

如果表达式的方法不带参数,那么可以像下面这样编写 Lambda 表达式:

() -> System.out.println("Zero parameter lambda");

如果表达式的方法接受一个参数,则可以像下面这样编写 Lambda 表达式:

(param) -> System.out.println("One parameter: " + param);

当 Lambda 表达式只接收单个参数时,参数列表外的小括号也可以省略掉。

param -> System.out.println("One parameter: " + param);

        当 Lambda 表达式接收多个参数时,参数列表的括号就没法省略了。

        如果编译器无法从 Lambda 匹配的函数式接口的方法声明推断出参数类型(出现这种情况时,编译器会提示),则有时可能需要为 Lambda 表达式的参数指定类型。

(Car car) -> System.out.println("The car is: " + car.getName());

Lambda 的方法体

lambda 表达式的方法的方法体,在 Lambda 声明中的 -> 右侧指定:

(oldState, newState) -> System.out.println("State changed")

如果 Lambda 表达式的方法体需要由多行组成,则需要把多行代码写在用{ }括起来的代码块内。

(oldState, newState) -> {System.out.println("Old state: " + oldState);System.out.println("New state: " + newState);
}

Lamdba 表达式的返回值

        可以从 Lambda 表达式返回值,就像从方法中返回值一样。只需在 Lambda 的方法体中添加一个 return 语句即可:

(param) -> {System.out.println("param: " + param);return "return value";
}

        如果 Lambda 表达式所做的只是计算返回值并返回它,我们甚至可以省略 return 语句。

(a1, a2) -> { return a1 > a2; }
// 上面的可以简写成,不需要return 语句的
(a1, a2) -> { a1 > a2; }

        Lambda 表达式本质上是一个对象,跟其他任何我们使用过的对象一样, 我们可以将 Lambda 表达式赋值给变量并进行传递和使用。

public interface MyComparator {public boolean compare(int a1, int a2);}---MyComparator myComparator = (a1, a2) -> a1 > a2;boolean result = myComparator.compare(2, 5);

        上面的这个例子展示 Lambda 表达式的定义,以及如何将 Lambda 表达式赋值给给变量,最后通过调用它实现的接口方法来调用 Lambda 表达式。

外部变量在 Lambda 内的可见性

        在某些情况下,Lambda 表达式能够访问在 Lambda 函数体之外声明的变量。 Lambda 可以访问以下类型的变量:

  • 局部变量
  • 实例变量
  • 静态变量

Lambda 内访问局部变量,Lambda 可以访问在 Lambda 方法体之外声明的局部变量的值

public interface MyFactory {public String create(char[] chars);
}String myString = "Test";MyFactory myFactory = (chars) -> {return myString + ":" + new String(chars);
};

Lambda 访问实例变量,Lambda 表达式还可以访问创建了 Lambda 的对象中的实例变量。

public class EventConsumerImpl {private String name = "MyConsumer";public void attach(MyEventProducer eventProducer){eventProducer.listen(e -> {System.out.println(this.name);});}
}

        这里实际上也是 Lambda 与匿名类的差别之一。匿名类因为可以有自己的实例变量,这些变量通过 this 引用来引用。但是,Lambda 不能有自己的实例变量,因此 this 始终指向外面包裹 Lambda 的对象。

Lambda 访问静态变量,Lambda 表达式也可以访问静态变量。这也不奇怪,因为静态变量可以从 Java 应用程序中的任何地方访问,只要静态变量是公共的。

public class EventConsumerImpl {private static String someStaticVar = "Some text";public void attach(MyEventProducer eventProducer){eventProducer.listen(e -> {System.out.println(someStaticVar);});}
}

把方法引用作为 Lambda

        如过编写的 lambda 表达式所做的只是使用传递给 Lambda 的参数调用另一个方法,那么 Java里为 Lambda 实现提供了一种更简短的形式来表达方法调用。比如说,下面是一个函数式数接口:

public interface MyPrinter{public void print(String s);
}

接下来我们用 Lambda 表达式实现这个 MyPrinter 接口

MyPrinter myPrinter = (s) -> { System.out.println(s); };

因为 Lambda 的参数只有一个,方法体也只包含一行,所以可以简写成

MyPrinter myPrinter = s ->  System.out.println(s);

        又因为 Lambda 方法体内所做的只是将字符串参数转发给 System.out.println() 方法,因此我们可以将上面的 Lambda 声明替换为方法引用。

MyPrinter myPrinter = System.out::println;

注意双冒号 :: 向 Java 的编译器指明这是一个方法的引用。引用的方法是双冒号之后的方法。而拥有引用方法的类或对象则位于双冒号之前。

我们可以引用以下类型的方法:

  • 静态方法
  • 参数对象的实例方法
  • 实例方法
  • 类的构造方法

引用类的静态方法

最容易引用的方法是静态方法,比如有这么一个函数式接口和类

public interface Finder {public int find(String s1, String s2);
}public class MyClass{public static int doFind(String s1, String s2){return s1.lastIndexOf(s2);}
}

如果我们创建 Lambda 去调用 MyClass 的静态方法 doFind

Finder finder = (s1, s2) -> MyClass.doFind(s1, s2);

所以我们可以使用 Lambda 直接引用 Myclass 的 doFind 方法。

Finder finder = MyClass::doFind;

引用参数的方法

接下来,如果我们在 Lambda 直接转发调用的方法是来自参数的方法

public interface Finder {public int find(String s1, String s2);
}Finder finder = (s1, s2) -> s1.indexOf(s2);

依然可以通过 Lambda 直接引用

Finder finder = String::indexOf;

        这个与上面完全形态的 Lambda 在功能上完全一样,不过要注意简版 Lambda 是如何引用单个方法的。 Java 编译器会尝试将引用的方法与第一个参数的类型匹配,使用第二个参数类型作为引用方法的参数。

引用实例方法

我们还也可以从 Lambda 定义中引用实例方法。首先,设想有如下接口

public interface Deserializer {public int deserialize(String v1);
}

该接口表示一个能够将字符串“反序列化”为 int 的组件。现在有一个 StringConvert 类

public class StringConverter {public int convertToInt(String v1){return Integer.valueOf(v1);}
}

        StringConvert 类 的 convertToInt() 方法与 Deserializer 接口的 deserialize() 方法具有相同的签名。因此,我们可以创建 StringConverter 的实例并从 Lambda 表达式中引用其 convertToInt() 方法,如下所示:

StringConverter stringConverter = new StringConverter();Deserializer des = stringConverter::convertToInt;
// 等同于 Deserializer des = (value) -> stringConverter.convertToInt(value)

        上面第二行代码创建的 Lambda 表达式引用了在第一行创建的 StringConverter 实例的 convertToInt 方法。

引用构造方法

        最后如果 Lambda 的作用是调用一个类的构造方法,那么可以通过 Lambda 直接引用类的构造方法。在 Lambda 引用类构造方法的形式如下:

ClassName::new

那么如何将构造方法用作 lambda 表达式呢,假设我们有这样一个函数式接口

public interface Factory {public String create(char[] val);
}

Factory 接口的 create() 方法与 String 类中的其中一个构造方法的签名相匹配(String 类有多个重载版本的构造方法)。因此,String类的该构造方法也可以用作 Lambda 表达式。

Factory factory = String::new;
// 等同于 Factory factory (chars) -> String.new(chars);

总结

        今天这篇文章把 Lambda 表达式的知识梳理的了一遍,相信看完了这里的内容,再看到 Lambda 表达式的各种形态就不觉得迷惑了,虽然今天的文章看起来有点枯燥,不过是接下来 咱们系统学习 Stream 操作的基础,以及后面介绍 Java 中提供的几个函数式编程 interface 也会用到 Lambda 里的知识,后面的内容可以继续期待一下。

参考:

Java 8 (2/6篇) - Lambda表达式 & 函数式接口(FunctionalInterface Lib)_java8 lambda函数式接口_Jomurphys的博客-CSDN博客

彻底弄懂@FunctionalInterface、Lambda表达式和方法引用_@interface 表达式-CSDN博客

Java Lambda 表达式的各种形态和使用场景,看这篇就够了 - 知乎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/210459.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

stm32 使用18B20 测试温度

用18b20 测试温度是非常常用的&#xff0c;不过18B20的调试不是这么容易的&#xff0c;有些内容网上很多的&#xff0c;不再重复说了&#xff0c;我先把波形说一下&#xff0c;再说程序部分&#xff1a; 整个都温度数据的顺序是&#xff1a; 1.700uS的低电平复位并测试18B20的…

mfc110u.dll丢失的解决方法,mfc110u.dll丢失原因是什么?

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“mfc110u.dll文件丢失”。那么&#xff0c;mfc110u.dll是什么&#xff1f;为什么会出现丢失的情况呢&#xff1f;本文将为您详细介绍mfc110u.dll文件的含义、丢失原因以及解决方法。 首先&…

MyBatis进阶之结果集映射注解版

文章目录 注解实现结果集映射注解实现关系映射常用功能注解汇总 注解实现结果集映射 注意 配置结果集映射&#xff0c;只用看 SQL 执行结果&#xff0c;不看 SQL 语句&#xff01; 注意 由于注解在映射结果集上没有实现 <resultMap> 的 100% 功能&#xff0c;因此&#x…

【车载开发系列】Visio工具使用小技巧

【车载开发系列】Visio工具使用小技巧 【车载开发系列】Visio工具使用小技巧 【车载开发系列】Visio工具使用小技巧一. Word中编辑Visio技巧二. Word中插入visio图形的问题三. 总结 一. Word中编辑Visio技巧 本节主要介绍了Microsoft Word中编辑Visio图形的具体方法。 在 Word…

单臂路由与三层交换机

单臂路由 划分VLAN后同一VLAN的计算机属于同一个广播域&#xff0c;同一VLAN的计算机之间的通信是不成问题的。然而&#xff0c;处于不同VLAN的计算机即使是在同一交换机上&#xff0c;它们之间的通信也必须使用路由器。 图&#xff08;a&#xff09;是一种实现VLAN间路由的方…

电脑中环境变量的设置方法

环境变量是在操作系统中一个具有特定名字的对象&#xff0c;它包含了一个或者多个应用程序所将使用到的信息。例如Windows和DOS操作系统中的path环境变量&#xff0c;当要求系统运行一个程序而没有告诉它程序所在的完整路径时&#xff0c;系统除了在当前目录下面寻找此程序外&a…

从赋码、防窜到私域营销,爱创科技助力西卡德高驶入发展快车道

在家居建材行业&#xff0c;西卡德高是一个绕不开的品牌。在瓷砖胶、美缝剂、防水等领域&#xff0c;西卡德高甚至一度成为这些细分产品的代名词。西卡德高的销售渠道以零售为主&#xff0c;其80%的产品是通过全国3000多个经销商、3000多家专卖店、4000多个装修师傅社群以及近2…

class_2:Java概念 java se ee me jdk jre jvm

一、什么是Java&#xff1f; Java是一门面向对象的编程语言&#xff0c;不仅吸收了C语言的各种优点&#xff0c;还摒弃了C里难以理解的多继承、指针等概念&#xff0c;因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表&#xff0c;极好地…

JDK 9 模块化系统 (Module System) 和 多版本兼容 Jar (Multi-Release Jar)

博文目录 文章目录 Module System原因JDK 模块化模块描述文件关键字 启用模块化测试结论 Multi-Release jar (MRJAR)原因原理结论用 IDEA 创建多版本兼容 Jar项目结构pom.xml测试 Module System 原因 Java 9引入了模块化系统的主要原因是为了解决Java平台面临的复杂性和可维…

C++中STL的容器vector

文章目录 什么是vectorvector与普通顺序表不同的点 vector的成员函数operatoroperator[]begin与end与iteratorsize()capacityresizeemptyreservepush_backpop_backinserteraseswapclear成员变量 总结 什么是vector vector&#xff1a;是数据结构里面的顺序表&#xff0c;开辟一…

【LeetCode刷题】数组篇2

&#x1f387;数组中等题Part &#x1f308; 开启LeetCode刷题之旅 &#x1f308; 文章目录 &#x1f387;数组中等题Part&#x1f370;229.多数元素II&#x1f451;思路分析1.哈希表法2.摩尔投票法(进阶) &#x1f370;15.三数之和&#x1f451;思路分析1.排序双指针 &#x…

音乐曲谱软件Guitar Pro 8.1.1 破解版下载和网盘补丁

Guitar Pro 8.1.1 for Mac 中文破解版是一款功能强大的音乐曲谱软件&#xff0c;非常适合学习如何玩&#xff0c;改进技巧&#xff0c;重现喜爱的歌曲或陪伴自己。可以帮助我们进行吉他的学习、绘谱与创作&#xff0c;它包含了几乎所有的吉他现有指法及音色&#xff0c;在做弹拨…

角谷定理 C语言xdoj32

角谷定理定义如下&#xff1a; 对于一个大于1的整数n&#xff0c;如果n是偶数&#xff0c;则n n / 2。如果n是奇数&#xff0c;则n 3 * n 1&#xff0c;反复操作后&#xff0c;n一定为1。 例如输入22的变化过程&#xff1a; 22 ->11 -> 34 -> 17 -> 52 -> 26 …

电源小白入门学习3——电源系统常见元件选型MOS管、二极管、电感篇

电源小白入门学习3——电源系统常见元件选型MOS管、二极管、电感篇 MOS管二极管电感 书接上文&#xff0c;上一期我们讲了电阻、电容选型中需要注意的事项&#xff0c;下面我们接着来介绍MOS管和二极管。 MOS管 关于MOS管的基本原理和内部的一些结构&#xff0c;PN结、半导体的…

FPGA 低延时 TCP UDP IP协议栈兼容1G 10G 25G MAC

在计算和数据中心、军事和航天、政府、仪器与测量、金融服务和广播和视频等行业&#xff0c;需要高可靠性的硬件和软件产品&#xff0c;帮助客户更快地开发部署新一代产品&#xff0c;减少技术和市场风险&#xff0c;我司研发的低延迟TCP/IP的IP核的传输速率高于传统网口&#…

uni-app 设置当前page界面进入直接变为横屏模式

首先 我们打开项目的 manifest.json 在左侧导航栏中找到 源码视图 然后找到 app-plus 配置 在下面加上 "orientation": [//竖屏正方向"portrait-primary",//竖屏反方向"portrait-secondary",//横屏正方向"landscape-primary",//横屏…

99、NeRF ray space

CG相机模型 在图形学中最常用的相机模型的原理和小孔成像是类似的。 不同之处在于&#xff0c;如上图&#xff0c;小孔成像得到的图像是倒立的&#xff0c;但是我们希望得到的图像是正向的&#xff0c;因此&#xff0c;我们选择小孔前成像。 从 3D 到 2D 的投影&#xff0c;…

Grad-CAM原理

这篇是我对哔哩哔哩up主 霹雳吧啦Wz 的视频的文字版学习笔记 感谢他对知识的分享 只要大家一提到深度学习 缺乏一定的解释性 比如说在我们之前讲的分类网络当中 网络它为什么要这么预测 它针对每个类别所关注的点在哪里呢 在great cam这篇论文当中呢 就完美的解决了在cam这篇论…

java多线程(常用方法、实现方式、线程安全问题、生命周期、线程池)

多线程相关的三组概念 程序和进程 程序&#xff08;program&#xff09;&#xff1a;一个固定的运行逻辑和数据的集合&#xff0c;是一个静态的状态&#xff0c;一般存储在硬盘中。简单来说就是我们编写的代码 进程&#xff08;process&#xff09;&#xff1a;一个正在运行的…

游戏玩家升级不伤手之选,光威龙武系列超强性能

得益于国产存储芯片的崛起&#xff0c;现在的内存条价格太香了。要放在前几年&#xff0c;购买内存条时都会优先考虑国际一线品牌。随着内存条行业发生巨变&#xff0c;国产品牌光威GLOWAY&#xff0c;是全球前三的内存模组厂商嘉合劲威旗下品牌&#xff0c;它推出的内存条产品…