即日起在codingBlog上分享您的技术经验即可获得积分,积分可兑换现金哦。

java8之Coding with lambda

编程语言 qqqqq1993qqqqq 14℃ 0评论

本章关键点:

  • 使用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  UnaryOperator compose(UnaryOperator op1,  
UnaryOperator 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> pendingOperations;  

LatentImage transform(UnaryOperator 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 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 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 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  Supplier unchecked(Callable 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 一元操作

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


(待补充)

转载请注明:CodingBlog » java8之Coding with lambda

喜欢 (0)or分享 (0)
发表我的评论
取消评论

*

表情