java8——Coding with lambda

来源:互联网 发布:手机端淘宝怎么装修 编辑:程序博客网 时间:2024/06/03 22:40

本章关键点:

  • 使用lambda表达式的主要原因是,将代码的执行延迟到一个合适的时间点(?)
  • 要延迟组合转换,你需要保留一个所有未执行的转换列表,并在最后应用它们。
  • 如果你需要多次应用一个lambda 表达式,最好将工作分成多个子任务,以便可以并发执行。
  • 请考虑如何处理lambda 表达式中抛出异常的情况。
  • 当使用泛型函数式接口时,请使用? Super 通配符作为参数类型,使用? Entend 通配符作为返回类型。
  • 当使用可以被函数转换的泛型类型时,请考虑使用map 和flatMap。

3.1 延迟执行

简单的说,lambda表达式定义了一个方法调用点,具体调用那个方法要到运行时才能决定,这就是前面所说的:延迟执行。


3.2 lambda表达式的参数
(这一节想表达什么?)

public static void repeat(int n, IntConsumer action) {  for (int i = 0; i < n; i++) action.accept(i);  } 
repeat(10, i -> System.out.println("Countdown: " + (9 - i))); 

3.3 选择一个函数式接口
这里写图片描述
这里写图片描述

注意:大多数标准的函数式接口都拥有用来生成或组合函数的非抽象方法。例如,Predicate.isEqual(a)同a::equals 一样(假设a 不为null)。此外,它们还拥有用来组合predicate 的默认方法and、or、negate。例如Predicate.isEqual(a). or(Predicate.isEqual(b)) 同x -> a.equals(x) || b.equals(x)一样。

列举了34 个专门为原始类型int、long 和double 提供的函数式接口。使用这些函数式接口可以减少自动装箱(autoboxing)。
这里写图片描述
p、q 为int、long、double 类型,P、Q 为Integer、Long、Double 类型


3.4 返回函数

当需要向某个方法中传递更多参数时,可以考虑编写返回函数的方法。
jdk源码中,很多类(如Comparator类)就有可以生成或修改比较器的方法。


3.5 组合
我们首先将图片变亮,然后再将它变成黑白图片。

Image image = new Image("eiffel-tower.jpg");  Image image2 = transform(image, Color::brighter);  Image finalImage = transform(image2, Color::grayscale); 

但是这样做效率不高。我们需要创建一个中间图片。对于大图片来说,这可能需要一个不小的存储空间。如果我们可以将图片的操作组合起来,然后将组合后的操作应用到每个像素上,那就好多了。

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));  // 调用自定义的compose

3.6 延迟

如果你想要延迟处理,你的API 需要能够区分出哪些是累积已完成任务的中间操作,哪些是用来产生结果的终止操作。在图片处理示例中,我们将使得变换过程延迟处理,但是需要返回另一个不是Image.的对象。

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;  } ...} 
LatentImage latent = LatentImage.from(image)  .transform(Color::brighter).transform(Color::grayscale);  // 延迟操作

你只能将操作延迟到这里了。最终,工作还是需要完成的。我们可以提供一个toImage 方法来应用所有的操作并返回结果。

Image finalImage = LatentImage.from(image)  .transform(Color::brighter).transform(Color::grayscale)  .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 处理异常

first.run()抛出了一个异常,线程为终止,second 永远不会运行。但是doInOrderAsync 会立即返回并进行另一个线程中的工作,因此无法让方法重新抛出异常。在这种情况下,我们应该提供一个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();  } 

函数接口中的方法通常不允许检查期异常,这一点很不方便。当然,你的方法可以选择接受那些方法中允许检查期异常的函数式接口,例如Callable而不是Supplier 。Callable 对象有一个方法被声明为T call() throwsException。如果你希望一个与Consumer 或者Function 等价的对象,就只能自己创建它了。

有些时候,你会看到要求你使用泛型来“修复”该问题的提示,例如以下代码:

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;       }    };  } 

3.9 lambda表达式和泛型

假设Employee 是Person 的一个子类型。

如果一个方法只从列表中读取数据,那么它可以决定接受一个List< ? extends Person>对象,然后你可以传递List< Person >或者一个List< Employee >对象。如果一个方法只向列表中写数据,那么它可以接受一个List< ? super Employee>对象。这时你也可以传递一个List< Person >列表用来写入雇员信息。一般来说,读取是协变的(covariant,可以接受子类型),而写入是逆变的(contravariant,可以接受父类型)。Use-site variance 正好适用于可变的数据结构。它给了每个服务选择合适的可变型(如果有的话)的权利。

一般准则是父类作为参数类型,子类作为返回类型。这样,你可以将一个Consumer< Object >传递给一个Stream< String >的forEach 方法。如果它能够处理任何对象,那么也一定可以处理字符串。

要实现这样一个接受泛型lambda 表达式的方法,你只需要在所有不是返回类型的参数类型上加上? Super,并在所有不是参数类型的返回类型上加上? Extends


3.10 一元操作

当你使用泛型,以及返回这些类型的函数时,最好能够提供将这些函数组合起来的方法。
(待补充)