标签:
三、使用lambda编程
3.1 延迟执行
所有lambda表达式都是延迟执行的,如果希望立即执行一段代码,则没必要使用lambda表达式
延迟执行代码原因可能有:
·在另一个线程中运行代码
·多次运行代码
·在某个算法的正确时间点上运行代码
·在某些情况发生时运行代码(如按钮点击、数据到达)
·只有在需要时运行代码
例如:
public static void info(Logger logger, Supplier<String> message) {
if (logger.isLoggable(Level.Info))
logger.info(message.get());
}
logger.info(() -> 一些运算);
只有当条件符合时,运算才会被执行。
3.2 选择lambda表达式的参数
一般来说,在设计算法时,希望将所需信息作为参数传递进逻辑。
如果需要在lambda表达式执行时捕获传入的参数,可以选择IntConsume等接口
例如:
public static void repeat(int n, IntConsumer action) {
for(int i=0; i<n; i++) action.accept(i);
}
如果不需要参数,可以考虑以下写法
public static void repeat(int n, Runnable action) {
for ( int i=0; i<n; i++) action.run();
}
repeat(10, () -> System.out.println("Hello, World!"));
3.3 选择一个函数式接口
Functional Interface |
Parameter Types |
Return Type |
Abstract Method Name |
Description |
Other Methods |
Runnable |
none |
void |
run |
Runs an action without arguments or return value |
|
Supplier<T> |
none |
T |
get |
Supplies a value of type T |
|
Consumer<T> |
T |
void |
accept |
Consumes a value of typeT |
chain |
BiConsumer<T, U> |
T, U |
void |
accept |
Consumes values of types T and U |
chain |
Function<T, R> |
T |
R |
apply |
A function with argument of type T |
compose,andThen,identity |
BiFunction<T, U, R> |
T, U |
R |
apply |
A function with arguments of types T and U |
andThen |
UnaryOperator<T> |
T |
T |
apply |
A unary operator on the type T |
compose,andThen,identity |
BinaryOperator<T> |
T, T |
T |
apply |
A binary operator on the type T |
andThen |
Predicate<T> |
T |
boolean |
test |
A Boolean-valued function |
and, or,negate,isEqual |
BiPredicate<T, U> |
T, U |
boolean |
test |
A Boolean-valued function with two arguments |
and, or,negate |
函数式接口的使用可以参照以下文章,写的较为详细:
http://qiita.com/opengl-8080/items/22c4405a38127ed86a31
另外java8也专门为原始类型提供了对应的函数式接口,可以减少自动装箱(autoboxing)
Functional Interface |
Parameter Types |
Return Type |
Abstract Method Name |
BooleanSupplier |
none |
boolean |
getAsBoolean |
PSupplier |
none |
p |
getAsP |
PConsumer |
p |
void |
accept |
ObjPConsumer<T> |
T, p |
void |
accept |
PFunction<T> |
p |
T |
apply |
PToQFunction |
p |
q |
applyAsQ |
ToPFunction<T> |
T |
p |
applyAsP |
ToPBiFunction<T, U> |
T, U |
p |
applyAsP |
PUnaryOperator |
p |
p |
applyAsP |
PBinaryOperator |
p, p |
p |
applyAsP |
PPredicate |
p |
boolean |
test |
@FunctionalInterface public interface ColorTransformer { Color apply(int x, int y, Color colorAtXY); }
3.4 返回函数
java中的返回函数也是建立在函数式接口的基础上
例:
public static UnaryOperator<Color> brighten(double factor) { return c -> c.deriveColor(0, 1, factor, 1); }
3.5 组合
当在构建一组动作时,如果能将动作组合即可以更优雅的调用,某些时候也可以省去一些大对象的创建
由于UnaryOperator<T>中compose方法是继承自Function<T, R> 接口,返回值并不是UnaryOperator<T>而是Function<T, T>,很难进一步组合,所以自定义compose方法是个很好的想法
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::grayscale));
附上Function接口中compose源码:
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); }
对于上一节合并操作,还有另外一个做法。就是将操作积累起来,一块执行。
public class LatentImage { private Image in; private List<UnaryOperator<Color>> pendingOperations; ... }
LatentImage transform(UnaryOperator<Color> f) { pendingOperations.add(f); return this; }
public Image toImage() { int width = (int) in.getWidth(); int height = (int) in.getHeight(); WritableImage out = new WritableImage(width, height); for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) { Color c = in.getPixelReader().getColor(x, y); for (UnaryOperator<Color> f : pendingOperations) c = f.apply(c); out.getPixelWriter().setColor(x, y, c); } return out; }调用:
Image finalImage = LatentImage.from(image) .transform(Color::brighter).transform(Color::grayscale) .toImage();在toImage方法调用之前,只是积累操作,最终一次性执行
3.7 并行操作
当一个函数式接口的对象需要多次调用时,就可以考虑能否应用并发来进行处理
如下例,是一个并行的图片处理,将图片分隔为多条,每个线程处理一条:
public static Color[][] parallelTransform(Color[][] in, UnaryOperator<Color> f) { int n = Runtime.getRuntime().availableProcessors(); int height = in.length; int width = in[0].length; Color[][] out = new Color[height][width]; try { ExecutorService pool = Executors.newCachedThreadPool(); for (int i = 0; i < n; i++) { int fromY = i * height / n; int toY = (i + 1) * height / n; pool.submit(() -> { for (int x = 0; x < width; x++) for (int y = fromY; y < toY; y++) out[y][x] = f.apply(in[y][x]); }); } pool.shutdown(); pool.awaitTermination(1, TimeUnit.HOURS); } catch (InterruptedException ex) { ex.printStackTrace(); } return out; }3.8 异常处理
当编写一个接收Lambda表达式作为参数的方法时,需要考虑如何处理和报告可能出现的异常。
尤其当异步执行时。
一个办法是提供一个处理异常的handler。
例如:
public static void doInOrderAsync(Runnable first, Runnable second, Consumer<Throwable> handler) { Thread t = new Thread() { public void run() { try { first.run(); second.run(); } catch (Throwable t) { handler.accept(t); } } }; t.start(); }一般的函数式接口不允许检查checkedException,这点很不方便。
如果有需要只能自己创建接口,传递异常信息:
public static <T> Supplier<T> unchecked(Callable<T> f) { return () -> { try { return f.call(); } catch (Exception e) { throw new RuntimeException(e); } catch (Throwable t) { throw t; } }; }使用时可以将
unchecked(() -> new String(Files.readAllBytes( Paths.get("/etc/passwd")), StandardCharsets.UTF_8))传递给Supplier<String>,即使readAllBytes申明了IOException异常
3.9 lambda表达式和泛型
lambda表达式和泛型可以很好的工作,例如上节的unchecked方法。但是有些问题要注意。
例如由于泛型类型擦除导致无法在运行时创建泛型数组
例如List<String> list = ...;
list.toArray的结果是object[]
利用lambda表达式可以通过传递构造函数来克服这个问题
String[] result = words.toArray(String[] :: new);
但是在另一个情况下,又会遇到类型可变的局限
public class Executor<T, R> { public static void main(String[] args) { Executor<son, father> exe = new Executor<>(); exe.operate((father value) -> {return;}); } public static abstract class father { } public static abstract class son extends father { } public Stream<R> operate(FunInterfaceParam<T> param) { return null; } }
exe.operate((father value) -> {return;});会编译错误,但是实际上应该允许将泛型father类型的FunInterfaceParam传递给operate方法,因为它能处理son类型,肯定也能处理father类型。
因此需要专门定制operate方法的T泛型
public Stream<R> operate(FunInterfaceParam<? super T> param) {
类似的,由于不能指定函数是接口的函数参数总是协变或逆变的,因此需要每次重复定制
一般准则是,不是返回类型的参数加上 ? super,不是参数类型的返回类型加上? extends
例如,上一节中的doInOrderAsync方法:
public static <T> void doInOrderAsync(Supplier<? extends T> first, Consumer<? super T> second, Consumer<? super Throwable> handler)3.10 一元操作
1. 当使用泛型,或者返回这些类型的函数时,最好能将这些函数组合起来,一个接一个的调用
2. 当设计一个类型G<T>和一个函数G->U时,需要考虑是否需要一个返回G<U>的map方法。如果合适的话,也可以为函数T -> G(U)提供一个flatMap方法
标签:
原文地址:http://blog.csdn.net/flycct/article/details/51316661