第三章 使用lambda编程

一、延迟执行
所有的lambda表达式都是延迟执行的,使用lambda表达式的主要原因是将代码的执行延迟到一个合适的时间点,如果希望立即执行一段代码,那就没有必要使用lambda表达式了
以下用两个例子进行对比:
--------------------------------------------------------------------------------------------------------------------
logger.info("x:"+x+",y:"+y);
--------------------------------------------------------------------------------------------------------------------
在本例子中,如果日志级别设置为忽略INFO消息时,该字符串会被计算并传递给info方法,然后再确定是否真的要执行
--------------------------------------------------------------------------------------------------------------------
public static void info(Logger logger,Supplier<String> message){
if(logger.isLoggable(Level.INFO)){
logger.info(message.get());
}
}
--------------------------------------------------------------------------------------------------------------------
本例中的方法具有延迟记录日志的能力,其中Supplier<String>是一个函数式接口,
我们使用Looger类的isLoggable方法来决定什么时候应该记录INFO消息,当需要记录时,在通过其抽象方法调用lambda表达式

二、lambda表达式的参数
当执行一个lambda表达式时,请确认提供了所有必须的数据作为输入
--------------------------------------------------------------------------------------------------------------------
public static void repeat(int n,IntConsumer action){
for(int i=0;i<n;i++){
action.accept(i);
}
}
--------------------------------------------------------------------------------------------------------------------
例子中使用了IntConsumer而不是Runnable正是因为需要向函数式接口传递一个参数,而Runnable的run()方法是不需要传递参数的

三、选择一个函数式接口
1.在大多数函数式编程语言中,函数类型都是结构化的。为了指定将两个字符串映射到一个整数的函数,在java中,需要使用Comparator<String>这样的函数式接口来声明函数的目的,在编程语言的理论中,这被称为名义类型
2.在选择函数式接口时,应该尽量使用已经存在的函数式接口
接口
参数类型
返回类型
抽象方法名
描述
Runnable
void
run
执行一个没有参数和返回值的操作
Supplier<T>
T
get
提供一个T类型的值
Consumer<T>
T
void
accept
处理一个T类型的值
BiConsumer<T,U>
T,U
void
accept
处理T类型和U类型的值
Function<T,R>
T
R
apply
一个参数类型为T的函数
BiFunction<T,U,R>
T,U
R
apply
一个参数类型为T和U的函数
UnaryOperator<T>
T
T
apply
对类型T进行的一元操作
BinaryOperator<T>
T,T
T
apply
对类型T进行的二元操作
Predicate<T>
T
boolean
test
一个计算Boolean值的函数
BiPredicate<T,U>
T,T
boolean
test
一个含有两个参数,计算Boolean值的
函数
3.大多数标准的函数式接口都拥有用来生成或组合函数的非抽象方法,例如
Predicate . isEqual (a)同a::equals一样,此外还拥有用来组合 predicate 的默认方法 and or negate 。例如 Predicate . isEqual (a). or ( Predicate . isEqual (b))同x->a.equals(x)||
b.equals(x)一样
4.Java8中专门为int、long和double提供的函数式接口。使用这些函数式接口可以减少自动装箱
接口
参数类型
返回类型
抽象方法名
BooleanSupplier
none
boolean
getAsBoolean
P Supplier
none
p
getAsP
P Consumer
p
void
accept
Obj P Consumer<T>
T,p
void
accept
P Function<T>
p
T
apply
P To Q Function
p
q
applyAsQ
To P Function<T>
T
p
applyAsP
To P BiFunction<T,U>
T,U
P
applyAsP
P UnaryOperator
p
p
applyAsP
P BinaryOperator
p,p
p
applyAsP
P Predicate
p
boolean
test
其中p、q为int、long、double类型P、Q为Int、Long、Double类型

四、返回函数
在一门函数式编程语言中,函数是一级公民,如同你可以将数字传递给方法,也可以让方法产生数字一样,函数不仅可以作为参数,也可以作为返回值。Java并不是一个完全的函数式编程语言,因为它使用了函数式接口,但是原则式一样的。下面通过图片变化的例子来了解一下返回类型式一个函数式接口的方法
--------------------------------------------------------------------------------------------------------------------
public static UnaryOperator<Color> brighten(double factor){
return c->c.deriveColor(0,1,factor,1);
}
Image brightenedImage=transform(image,brighten(1,2));
--------------------------------------------------------------------------------------------------------------------

五、组合
只有一个参数的函数可以将一个值转换为另一个值,如果有两个这样的值,那么在进行一个转换之后再进行的第二个转换,也是一种转换。例如,将图片变量后再变黑白,可以调用两次相同的方法传进两次不同的参数即可,但是这样的效率并不高,可以将操作组合起来以提高效率
--------------------------------------------------------------------------------------------------------------------
public static <T> UnaryOperator<T> compose(UnaryOperator<T> op1,
UnaryOperator<T> op2){
return t->op2.apply(op1.apply(t));
}
Image finalImage=transform(image,compose(Color::brighter,
Color::hrayscale));
--------------------------------------------------------------------------------------------------------------------

六、异常处理
1.当你编写一个接受lambda表达式作为参数的方法时,你需要考虑如何处理和报告lambda表达式执行过程中可能会发生的异常
当lambda表达式抛出一个异常时,它会被传递给调用者
--------------------------------------------------------------------------------------------------------------------
public static void doInOrder(Runnable first,Runnable second){
first.run();
second.run();
}
--------------------------------------------------------------------------------------------------------------------
此时如果first.run()方法抛出一个异常,然后doInOrder方法被终止,second不会执行,并且调用者需要处理异常
现在假设任务式异步执行的
--------------------------------------------------------------------------------------------------------------------
public static void doInOrderAsync(Runnable first,Runnable second){
Thread t=new Thread(){
public void run(){
first.run();
second.run();
}
};
t.start();
}
--------------------------------------------------------------------------------------------------------------------
此时如果first.run()抛出一异常,线程终止,second永远不会运行,但是doInOrderAsync会立即返回并进行另一个线程中的工作,因此无法让方法重新抛出异常,在这种情况下,我们应该改提供一个handler
--------------------------------------------------------------------------------------------------------------------
public static void doInOrderAsync(Runnable first,Runnable second,
Consumer<Throwable> handle){
Thread t=new Thread(){
public void run(){
try{
first.run();
second.run();
}catch(Throwable t){
handle.accept(t);
}
}
};
t.start();
}
--------------------------------------------------------------------------------------------------------------------
2.函数式接口中的方法通常不允许检查期异常,当然,我们自己的方法可以选择接受那些方法中允许检查期异常的函数式接口

七、lambda表达式和泛型
1.一般来说,lambda表达式可以与泛型很好地一起工作,但一些问题需要我们时刻谨记。其中一个就式,类型擦除会导致你无法在运行时创建一个泛型数组,例如,Collection<T>和Stream<T>类的toArray()方法不能调用T[] result=new T[n],在过去,解决这个问题的方法式提供给另一个接受数组的方法,然后用反射的方式来填充该数组,或者通过它来创建一个新的数组。例如,Collection<T>类有一个方法toArray(T[] a),通过使用lambda表达式,你有了一种新的选择,即传递构造函数:
--------------------------------------------------------------------------------------------------------------------
String[] result=words.toArray(String[]::new);
--------------------------------------------------------------------------------------------------------------------
当实现这样的方法时,构造函数式表达式是一个IntFunction<T[]>对象,这是因为数组的长度被传递给了构造函数,在代码中需要调用T[] result=constr.apply(n)
2.假设Employee是Person的一个子类型,来看一个例子
--------------------------------------------------------------------------------------------------------------------
List<Employee> staff=……;
List<Person> tenants=staff; //假设合法
tenants.add(new Person("John Q.Public"));
--------------------------------------------------------------------------------------------------------------------
显然,将Person加入到Employee列表中是错误的,要避免这种类型的错误,必须禁止从List<Employee>转换到List<Person>,但是在Java中,更倾向于另一种不同的概念:use-site variance 或者 通配符
在Java中,如果有一个方法只从列表中读取数据,那么它可以决定接受一个
List<? extends Person>对象,然后你可以传递List<Person>或者一个List<Employee>对象。如果一个方法只想列表中写数据,那么它可以接受一个List<? super Employee>对象。这时,你可以传递一个List<Person>列表用来写入雇员信息,一般来说,读取是协变的(covariant,可以接受子类型),写入是逆变的(contravariant,可以接受父类型)。
3.并不总是用通配符,例如
--------------------------------------------------------------------------------------------------------------------
T reduce(T identity,BinaryOperator<T> accumulator)
--------------------------------------------------------------------------------------------------------------------
由于T是参数并且返回值是BinaryOperator类型,所以类型并没有改变,事实上,是逆变和协变相互抵消了
要实现这样一个接受泛型lambda表达式的方法,只需要在所有不是返回类型的参数类型上加上? super T,并在所有不是参数类型的返回类型上加上? extends T
例如上节中的doInOrderAsync方法
--------------------------------------------------------------------------------------------------------------------
public static <T> void doInOrderAsync(Supplier<? extends T> first,
Consumer<? super T> second,Consumer<? super Throwable> handler)
--------------------------------------------------------------------------------------------------------------------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值