lamda表达式
尽可能少的语法编写的函数定义,产生的是函数,而不是类
-> 可以认为是“产生”
只有一个参数,可以只写这个参数,不写括号
没有参数,必须用括号表示空的参数列表
如果表达式需要多行,需要将这些代码放入{}
方法引用
interface Callable{
void call(String s);
}
class Describe{
// show的签名(参数类型和返回类型)和Callable中call的签名一致
void show(String msg){
System.out.println(msg);
}
}
public class test1 {
// hello()的签名和call一致
static void hello(String name){
System.out.println("Hello "+name);
}
static class Description{
String about;
Description (String desc){
about = desc;
}
// help是静态内部类中的一个非静态方法
void help(String msg){
System.out.println(about + " " + msg);
}
}
static class Helper{
// assist是静态内部类中的一个静态方法
static void assist(String msg){
System.out.println(msg);
}
}
public static void main(String[] args){
Describe d = new Describe();
// 将Describe对象的一个方法引用赋值给了Callable, Callable中没有show()方法,只有一个Call方法.
// 然而,java对这种奇怪的赋值没有意见,因为这个方法引用的签名和Callable中的call方法一致
Callable c = d::show;
c.call("call()"); // java将call映射到了show上
// 静态方法引用
c = test::hello;
c.call("Hinton");
// 这是d::show的另一个版本,对某个活跃对象上的方法的引用,有事叫做"绑定方法引用"
c = new Description("valuable: " )::help;
c.call("细节");
// 获得静态内部类中的静态方法的方法引用
c = Helper::assist;
c.call("Help!");
}
}
Runnable
Runnable接口在java,lang中,遵循特殊的单方法接口格式,其Run方法没有参数,也没有返回值
package com.example.demo;
class Go{
static void go(){
System.out.println("Go::go()");
}
}
public class test1 {
public static void main(String[] args){
new Thread(new Runnable() {
public void run(){
System.out.println("Anonymous");
}
}).start();
new Thread(
()->System.out.println("lamda")
).start();
new Thread(Go::go).start();
}
}
Thread对象接受一个Runnable作为其构造器参数,它有一个start()方法会调用run()
未绑定方法引用
尚未关联到某个对象的普通(非静态)方法
未绑定引用必须先提供对象,然后才能使用
class X{
String f(){
return "X::f()";
}
}
interface MakeString{
String make();
}
interface TransformX{
String transform(X x);
}
public class test1 {
public static void main(String[] args){
// MakeString ms = x::f; //[1]
TransformX sp = X::f;
X x = new X();
System.out.println(sp.transform(x)); //[2]
System.out.println(x.f());
}
}
// 输出
// X::f()
// X::f()
到目前为止,我们看到的对方法的引用,与其关联接口的签名是相同的。在 [1]处, 我们尝试对X中的f()做同样的事情,将其赋值给MakeString。编译器会报错,提示“无效方法引用,即使make()的签名和f()相同。
问题在于,这里事实上还涉及另一个(隐藏的)参数:我们的老朋友this
如果没有一个可供附着的X 对象.就无法调用f()。因此,X::f 代表的是一个未绑定方法引用,因为它没有“绑定到” 某个对象。
为解决这个冋题,我们需要一个X对象,所以我们的接口事实上还需要一个额外的参数,如TransformX中所示。如果将X::f赋值给一个TransformX, Java会开心地接受。
我们必须再做一次心理调节:在未绑定引用的情况下,函数式方法(接口中的单一方法)的 签名与方法引用的签名不再完全匹配。这样做有一个很好的理由,那就是我们需要一个对象,让方法在其上调用。
在[2]处的结果有点儿像“脑筋急转弯”。我们接受了未绑定引用,然后以X为参数在 其上调用了 transformO,最终以某种方式调用了 x.f(),Java知道它必须接受第一个参数, 事实上就是this,并在它的上面调用该方法。
class This{
void two(int i, double d){}
void three(int i, double d, String s){}
void four(int i, double d, String s, char c){}
}
interface TwoArgs{
void call2(This athis, int i, double d);
}
interface ThreesArgs{
void call3(This athis, int i, double d, String s);
}
interface FourArgs{
void call4(This athis, int i, double d, String s,char c);
}
public class test1 {
public static void main(String[] args){
TwoArgs twoArgs = This::two;
ThreesArgs threesArgs = This::three;
FourArgs fourArgs = This::four;
This athis = new This();
twoArgs.call2(athis, 11, 3.14);
threesArgs.call3(athis, 11, 3.14, "hello");
fourArgs.call4(athis, 11, 3.14, "hello", 'H');
}
}
构造器方法引用
捕获对某个构造器的引用,之后通过该引用来调用哪个构造器
class Dog{
String name;
int age = -1;
Dog(){name = "旺财";}
Dog(String nm){name = nm; }
Dog(String nm, int a){name = nm; age = a;}
}
interface MakeNoArgs{
Dog make();
}
interface Make1Arg{
Dog make(String nm);
}
interface Make2Arg{
Dog make(String nm, int a);
}
public class test1 {
public static void main(String[] args){
MakeNoArgs mna = Dog::new;
Make1Arg m1a = Dog::new;
Make2Arg m2a = Dog::new;
Dog dn = mna.make();
Dog d1 = m1a.make("修斯");
Dog d2 = m2a.make("修斯", 2);
}
}
这三个构造器都只有一个名字 ::new
这时,构造器引用被赋值给了不同的接口,编译器可以从接口来推断使用哪个构造器
函数式接口
Java 8引入了包含一组接口的java.util.function,这些接口是 lambda表达式和方法引用的目标类型。每个接口都只包含一个抽象方法,叫作函数式方法
@FunctionalInterface
interface Functional{
String goodbye(String arg);
}
interface FunctionalNoAnn {
String goodbye(String arg);
}
/*
@FunctionalInterface
interface NotFunctional {
String goodbye(String arg);
String hello(String arg);
}
产生报错信息:
在接口 com.example.demo.NotFunctional 中找到多个非重写 abstract 方
*/
public class myTest {
public String goodbye(String arg) {
return "Goodbye, " + arg;
}
public static void main(String[] args) {
myTest fa = new myTest();
Functional f = fa::goodbye;
FunctionalNoAnn fna = fa::goodbye;
// Functional fac = fa; // 不兼容
Functional fl = a -> "Goodbye, " + a;
FunctionalNoAnn fnal = a -> "Goodbye, " + a;
}
}
如果我们将一个方法引用或lambda表达式赋值给某个函数式接口(而且类型可以匹配), 那么Java会调整这个赋值,使其匹配目标接口。而在底层,Java编译器会创建一个实现 了目标接口的类的实例,并将我们的方法弓I用或lambda表达式包果在其中。
高阶函数
只是一个能接受函数作为参数或能把函数当返回值的函数。
import java.util.function.*;
// 使用继承,可以轻松地为专门的接口创建一个别名
interface FuncSS extends Function<String, String> {}
public class myTest {
static FuncSS produce(){
return s -> s.toLowerCase(); // [2]
}
public static void main(String[] args) {
FuncSS f = produce();
System.out.println(f.apply("YELLING"));
}
}
/*输出:
* yelling
* */
class One {}
class Two {}
public class myTest {
static Two consume(Function<One, Two> oneTwo) {
return oneTwo.apply(new One());
}
public static void main(String[] args){
Two two = consume(one -> new Two());
}
}
class I {
@Override
public String toString(){
return "I";
}
}
class O {
@Override
public String toString(){
return "O";
}
}
public class myTest {
static Function<I, O> transform(Function<I, O> in){
/*
* andThen方法是专门为操作函数而设计的默认方法
* 会在in函数调用之后调用(还有一个compose方法, 它会在in函数之前应用新函数
* 要附加一个andThen函数, 只需要将该函数作为参数传递
* 从transform传出的是一个新函数,将in的动作和andThen参数的动作结合了起来
*/
return in.andThen(o -> {
System.out.println(o);
return o;
});
}
public static void main(String[] args){
Function <I,O> f2 = transform(i -> {
System.out.println(i);
return new O();
});
O o = f2.apply(new I());
}
}
/*输出
* I
* O
* */
闭包
如果有一个比之前更复杂的lambda表达式,它使用了其函数作用域之外的变量。当返回该函数时,会发生什么呢? 外部变量会变成什么呢?如果语言能自动处理这个问题,那就说它是支持闭包的
// functional/Closurel.java
public class Closure1 {
int i;
IntSupplier makeFun(int x){
return () -> x + i++;
}
}
在调用makeFun之后,这个对象仍然还存在
// functional/myTest.java
public class myTest {
public static void main(String[] args){
Closure1 c1 = new Closure1();
IntSupplier f1 = c1.makeFun(0);
System.out.println(f1.getAsInt());
IntSupplier f2 = c1.makeFun(0);
System.out.println(f2.getAsInt());
IntSupplier f3 = c1.makeFun(0);
System.out.println(f3.getAsInt());
}
}
/*输出:
* 0
* 1
* 2
* */
// functional/Closure2.java
public class Closure2 {
IntSupplier makeFun(int x){
int i = 0;
return () -> x + i;
}
}
/*输出:
* 0
* 0
* 0
* */
// functional/Closure3.java
public class Closure3 {
IntSupplier makeFun(int x){
int i = 0;
return () -> x++ + i++;
}
}
此时, 不论是x++或者i++都会报错 -> lambda 表达式中使用的变量应为 final 或有效 final
那为什么Closure2能编译成功呢?这里就引出了final的意义
虽然我们没有显式地声明为最终变量,但是仍然可以以最终变量的方式来对待它,只要不修改它即可
如果一个局部变量的从初始值没有改变,那它实际上就是一个最终变量
// functional/Closure4.java
public class Closure4 {
IntSupplier makeFun(int x){
int i = 0;
i++;
x++;
final int iFinal = i;
final int xFinal = x;
return () -> iFinal + xFinal;
}
}
// 这里的两个final是多余的
// functional/Closure5.java
public class Closure5 {
Supplier<List<Integer>> makeFun(){
final List<Integer> ai = new ArrayList<>(); // final可以删去
ai.add(1);
return () -> ai;
}
public static void main(String[] args){
Closure5 c = new Closure5();
List<Integer>
l1 = c.makeFun().get(),
l2 = c.makeFun().get();
System.out.println(l1);
System.out.println(l2);
l1.add(42);
l2.add(96);
System.out.println(l1);
System.out.println(l2);
}
}
/* 输出
[1]
[1]
[1, 42]
[1, 96]
*/
每次调用makeFun时,会创建一个全新的ArrayList
final关键字应用于对象引用,只是说这个对象引用不能被重新赋值,并不是说我们不能修改对象本身。
public class Closure6 {
IntSupplier makeFun(int x){
int i = 0;
// 同样的规则适用于i++, x++
return new IntSupplier() {
public int getAsInt(){
return x + i;
}
};
}
}
只要有内部类.就会有闭包
柯里化与部分求值
柯里化定义: 将一个接受多个参数的函数转变为一系列只接受一个参数的函数
public class CurryingAndPartials {
// 未柯里化
static String uncurried(String a, String b) {
return a + b;
}
public static void main(String[] args){
// 柯里化函数
Function<String, Function<String, String>> sum = a -> b -> a + b;
System.out.println(uncurried("嘿","哈"));
// 通过提供一个参数来创建一个新函数
Function<String, String> hi = sum.apply("你");
System.out.println(hi.apply("哈"));
// 部分应用
Function<String, String> sumHi = sum.apply("我");
System.out.println(sumHi.apply("哈"));
System.out.println(sumHi.apply("嘻"));
}
}
/* 输出:
嘿哈
你哈
我哈
我嘻
*/
public class Curry3Args {
public static void main(String[] args){
Function<String,
Function<String,
Function<String, String>>> sum = a -> b -> c -> a + b + c;
Function<String, Function<String, String>> hi = sum.apply("嘿: ");
Function<String, String> ho = hi.apply("哈: ");
System.out.println(ho.apply("我"));
}
}