Java 8 lambda 方法/构造函数引用

Lambda

可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

  • 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多!
  • 函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
  • 传递——Lambda表达式可以作为参数传递给方法或存储在变中。
  • 简洁——无需像匿名类那样写很多模板代码。

方法引用

方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。在一些情况下,比起使用Lambda表达式,它们似乎更易读,感觉也更自然。

先前:
 List<Apple> lists= Arrays.asList(new Apple("red", 50),
                                  new Apple("green", 40),
                                  new Apple("blank", 90)
                                 );
        
lists.sort((Apple a1, Apple a2) 
              -> a1.getWeight().compareTo(a2.getWeight())); 
之后(使用方法引用和java.util.Comparator.comparing):
lists.sort(comparing(Apple::getWeight));

你为什么应该关心方法引用?方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。
它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。事实上,方法引用就是让你根据已有的方法实现来创建Lambda表达式。但是,显式地指明方法的名称,你的代码的可读性会更好。它是如何工作的呢?
当你需要使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面。例如,Apple::getWeight就是引用了Apple类中定义的方法getWeight。请记住,不需要括号,因为你没有实际调用这个方法。方法引用就是Lambda表达式(Apple a) -> a.getWeight()的快捷写法。

Lambda及其等效方法引用的例子

Lambda 等效的方法引用
(Apple a) -> a.getWeight() Apple::getWeight
() -> Thread.currentThread().dumpStack() Thread.currentThread()::dumpStack
(str, i) -> str.substring(i) String::substring
(String s) -> System.out.println(s) System.out::println

如何构建方法引用

方法引用主要有三类。

  • 指向静态方法的方法引用(例如Integer的parseInt方法,写作Integer::parseInt)。

  • 指 向 任意类型实例方法 的方法引用(例如 String 的 length 方法,写作String::length)。

  • 指向现有对象的实例方法的方法引用(假设你有一个局部变量expensiveTransaction用于存放Transaction类型的对象,它支持实例方法getValue,那么你就可以写expensiveTransaction::getValue)。

第二种和第三种方法引用可能乍看起来有点儿晕。类似于String::length的第二种方法引用的思想就是你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。例如,Lambda
表达式(String s) -> s.toUppeCase()可以写作String::toUpperCase。但第三种方法引用指的是,你在Lambda中调用一个已经存在的外部对象中的方法。例如,Lambda表达式()->expensiveTransaction.getValue()可以写作expensiveTransaction::getValue。

构造函数引用

对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用:
ClassName::new。它的功能与指向静态方法的引用类似。例如,假设有一个构造函数没有参数。
它适合Supplier的签名() -> Apple。你可以这样做:

Supplier<Apple> c1 = Apple::new; 
Apple a1 = c1.get();

这就等价于:

Supplier<Apple> c1 = () -> new Apple(); 
Apple a1 = c1.get();

如果你的构造函数的签名是Apple(Integer weight),那么它就适合Function接口的签
名,于是你可以这样写:

Function<Integer, Apple> c2 = Apple::new;
Apple a2 = c2.apply(110);

复合 Lambda 表达式的有用方法

Java 8的好几个函数式接口都有为方便而设计的方法。具体而言,许多函数式接口,比如用于传递Lambda表达式的Comparator、FunctionPredicate都提供了允许你进行复合的方法。
这是什么意思呢?在实践中,这意味着你可以把多个简单的Lambda复合成复杂的表达式。比如,你可以让两个谓词之间做一个or操作,组合成一个更大的谓词。而且,你还可以让一个函数的结果成为另一个函数的输入。你可能会想,函数式接口中怎么可能有更多的方法呢?(毕竟,这违背了函数式接口的定义啊!)窍门在于,我们即将介绍的方法都是默认方法,也就是说它们不是抽象方法。

比较器复合

我们前面看到,你可以使用静态方法Comparator.comparing,根据提取用于比较的键值
的Function来返回一个Comparator,如下所示:

Comparator<Apple> c = Comparator.comparing(Apple::getWeight);
  1. 逆序
    如果你想要对苹果按重量递减排序怎么办?用不着去建立另一个Comparator的实例。接口有一个默认方法reversed可以使给定的比较器逆序。因此仍然用开始的那个比较器,只要修改一下前一个例子就可以对苹果按重量递减排序:
inventory.sort(comparing(Apple::getWeight).reversed());
  1. 比较器链
    上面说得都很好,但如果发现有两个苹果一样重怎么办?哪个苹果应该排在前面呢?你可能需要再提供一个Comparator来进一步定义这个比较。比如,在按重量比较两个苹果之后,你可能想要按原产国排序。thenComparing方法就是做这个用的。它接受一个函数作为参数(就像comparing方法一样),如果两个对象用第一个Comparator比较之后是一样的,就提供第二个Comparator。你又可以优雅地解决这个问题了:
  inventory.sort(comparing(Apple::getWeight) 
		   .reversed() 
		   .thenComparing(Apple::getCountry));//两个苹果一样重时,进一步按国家排序

谓词复合

谓词接口包括三个方法:negate、and和or,让你可以重用已有的Predicate来创建更复杂的谓词。

比如,你可以使用negate方法来返回一个Predicate的非,比如苹果不是红的:

//产生现有Predicate对象redApple的非
Predicate<Apple> notRedApple = redApple.negate();

函数复合

最后,你还可以把Function接口所代表的Lambda表达式复合起来。Function接口为此配了andThen和compose两个默认方法,它们都会返回Function的一个实例。

andThen方法会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数。
比如,假设有一个函数f给数字加1 (x -> x + 1),另一个函数g给数字乘2,你可以将它们组
合成一个函数h,先给数字加1,再给结果乘2:

//数学上会写作g(f(x))或(g o f)(x)
Function<Integer, Integer> f = x -> x + 1; 
Function<Integer, Integer> g = x -> x * 2; 
Function<Integer, Integer> h = f.andThen(g); 
int result = h.apply(1);//结果为4

你也可以类似地使用compose方法,先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果。比如在上一个例子里用compose的话,它将意味着f(g(x)), 而andThen则意味着g(f(x)):

//数学上会写作f(g(x))或(f o g)(x)
Function<Integer, Integer> f = x -> x + 1; 
Function<Integer, Integer> g = x -> x * 2; 
Function<Integer, Integer> h = f.compose(g);
int result = h.apply(1);//结果为3

示例:

public class Letter{ 
 public static String addHeader(String text){ 
 return "From Raoul, Mario and Alan: " + text; 
 } 
 public static String addFooter(String text){ 
 return text + " Kind regards"; 
 } 
 public static String checkSpelling(String text){ 
 return text.replaceAll("labda", "lambda"); 
 } 
}

现在你可以通过复合这些工具方法来创建各种转型流水线了,比如创建一个流水线:先加上抬头,然后进行拼写检查,最后加上一个落款

Function<String, String> addHeader = Letter::addHeader; 
Function<String, String> transformationPipeline = addHeader.andThen(Letter::checkSpelling).andThen(Letter::addFooter);

你可能感兴趣的:(java8,java)