标签:
三、使用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