1.行为参数化
行为参数化是将方法的具体实现抽象化,java8之前可以使用接口(策略模式,根据不同需求需要编写不同的实现类),匿名类(可读性差)对代码逻辑进行行为参数化,另外就是java8里的lambda表达式。
以Runnable接口为例,打印出hellow word:
//接口方式
Threadthread1 =new Thread(new SayHello());
//匿名类方式
Threadthread2 =new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello world");
}
});
//lambda表达式
Threadthread3 =new Thread(()-> System.out.println("Hello world"));
2.lambda表达式
() -> System.out.println("Hello world")
lambda表达式的结构为 参数列表 + 箭头 + lambda主体 ,如以上的表达式:()是参数列表,->箭头, System.out.println("Hello world")为lambda主体。
(1) () -> {} //无参数,返回值为void
(2) () -> "Raoul" //无参数,返回String作为表达式
(3) () -> {return "Mario";} //无参数,返回值为String。
(4) (Integer i) -> return "Alan" + i; //无效表达式,return是流程控制语句,需添加花括号
(5) (String s) -> {"IronMan";} //无效表达式,“ironMan”是表达式,不是语句,需要去除花括号,或者加入显示返回语句return。
那到底在哪里可以使用Lambda呢?你可以在函数式接口上使用Lambda表达式
3.函数式接口
函数式接口就是只定义一个抽象方法的接口。
如:Runnable r1 = () -> System.out.println("Hello World");
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作
函数描述符。例如,Runnable接口可以看作一个什么也不接受什么也不返回(void)的函数的
签名,因为它只有一个叫作run的抽象方法,这个方法什么也不接受,什么也不返回(void)
@FunctionalInterface注解
这个标注用于表示该接口会设计成一个函数式接口。如果你用@FunctionalInterface定义了一个接
口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误。
4.把Lambda 付诸实践:环绕执行模式
对一下代码进行改造
public static String processFile() throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader("data.txt"))) {
return br.readLine();
} //签名信息: (BuffereReader br) -> String
}
第1 步:行为参数化
String result = processFile((BufferedReader br) ->br.readLine() );
第2 步:使用函数式接口来传递行为
@FunctionalInterface
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
现在你就可以把这个接口作为新的processFile方法的参数了:
public static String processFile(BufferedReaderProcessor p) throws IOException {...}
第3 步:执行一个行为
public static String processFile(BufferedReaderProcessor p) throws IOException {
try (
BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return p.process(br);
}
}
第4 步:传递Lambda
现在你就可以通过传递不同的Lambda重用processFile方法,并以不同的方式处理文件了。
处理一行:
String oneLine = processFile((BufferedReader br) -> br.readLine());
处理两行:
String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());
4.使用函数式接口
Predicate: java.util.function.Predicate<T>接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。
Consumer: java.util.function.Consumer<T>定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void)。你如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口。
Function:java.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。
请注意,任何函数式接口都不允许抛出受检异常(checked exception)。如果你需要Lambda表达式来抛出异常,有两种办法:定义一个自己的函数式接口,并声明受检异常,或者把Lambda包在一个try/catch块中。5.原始类型特化
泛型(比如Consumer<T>中的T)只能绑定到引用类型。这是由泛型内部的实现方式造成的。①因此,在Java里有一个将原始类型转换为对应的引用类型的机制。这个机制叫作装箱(boxing)。相反的操作,也就是将引用类型转换为对应的原始类型,叫作拆箱(unboxing)。但这在性能方面是要付出代价的。
Java 8为我们前面所说的函数式接口带来了一个专门的版本,以便在输入和输出都是原始类型时避免自动装箱的操作。
如:public interface IntPredicate{
boolean test(int t);
}
一般来说,针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀,比如DoublePredicate、IntConsumer、LongBinaryOperator、IntFunction等。Function接口还有针对输出参数类型的变种:ToIntFunction<T>、IntToDoubleFunction等。
6.类型检查
Lambda的类型是从使用Lambda的上下文推断出来的。上下文(比如,接受它传递的方法的参数,或接受它的值的局部变量)中Lambda表达式需要的类型称为目标类型。
7.使用局部变量
Lambda可以没有限制地捕获(也就是在其主体中引用)实例变量和静态变量。但局部变量必须显式声明为final,或事实上是final。换句话说,Lambda表达式只能捕获指派给它们的局部变量一次。(注:捕获实例变量可以被看作捕获最终局部变量this。)
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
portNumber = 31337;
上述代码无法编译:Lambda表达式引用的局部变量必须是最终的(final)或事实上最终的
对局部变量的限制:第一,实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了这个限制。第二,这一限制不鼓励你使用改变外部变量的典型命令式编程模式,这种模式会阻碍很容易做到的并行处理。
8.方法引用
方法引用主要有三类。
(1) 指向静态方法的方法引用(例如Integer的parseInt(c)方法,写作Integer::parseInt)。
(2) 指向任意类型实例方法的方法引用( 例如String 的length() 方法, 写作String::length)。
(3) 指向现有对象的实例方法的方法引用(假设你有一个局部变量apple用于存放Apple类型的对象,它支持实例方法getColor,那么你就可以写成apple::getColor)
第二种和第三种方法引用可能乍看起来有点儿晕。类似于String::length的第二种方法引用的思想就是你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。例如,Lambda表达式(String s) -> s.toUppeCase()可以写作String::toUpperCase。但第三种方法引用指的是,你在Lambda中调用一个已经存在的外部对象中的方法。例如,Lambda表达式
()->apple.getColor()可以写作apple::getColor。
9.比较器复合
比较器Comparator接口有一个静态方法Comparator.comparing,根据提取用于比较的键值
的Function来返回一个Comparator
’Comparator<Apple> c = Comparator.comparing(Apple::getWeight);
逆序
inventory.sort(comparing(Apple::getWeight).reversed());
比较器链
inventory.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getCountry));
10.谓词复合
谓词接口(Predicate)包括三个方法:negate、and和or
Predicate<Apple> notRedApple = redApple.negate(); //非
Predicate<Apple> redAndHeavyApple =redApple.and(a -> a.getWeight() > 150); //与
Predicate<Apple> redAndHeavyAppleOrGreen =redApple.and(a -> a.getWeight() > 150)
.or(a -> "green".equals(a.getColor())); //或
11.函数复合
Function接口所代表的Lambda表达式复合起来。Function接口为此配了andThen和compose两个默认方法,它们都会返回Function的一个实例。
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g); //y = (x+1)*2可以使用如下表达
Function<Integer, Integer> h = f.compose(g); //y = (x*2)+1可以使用如下表达
可以通过复合这些工具方法来创建各种转型流水线
小结:
1.Lambda表达式可以理解为一种匿名函数:它没有名称,但有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常的列表。
2.Lambda表达式可以简洁地传递代码
3.函数式接口就是仅仅声明了一个抽象方法的接口。
4.只有在接受函数式接口的地方才可以使用Lambda表达式。
5.Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。
6.为了避免装箱操作,对Predicate<T>和Function<T, R>等通用函数式接口的原始类型特化:IntPredicate、IntToLongFunction等。
7.环绕执行模式(即在方法所必需的代码中间,你需要执行点儿什么操作,比如资源分配和清理)可以配合Lambda提高灵活性和可重用性。
8.Lambda表达式所需要代表的类型称为目标类型。
9.方法引用让你重复使用现有的方法实现并直接传递它们。
10.Comparator、Predicate和Function等函数式接口都有几个可以用来结合Lambda表达式的默认方法。
网友评论