码迷,mamicode.com
首页 > 编程语言 > 详细

JAVA SE 8 学习笔记(三)使用lambda编程

时间:2016-05-13 02:32:11      阅读:329      评论:0      收藏:0      [点我收藏+]

标签:

三、使用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 选择一个函数式接口


Table 3-1 Common Functional Interfaces

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>

TU

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>

TU

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>

TT

T

apply

A binary operator on the type T

andThen

Predicate<T>

T

boolean

test

A Boolean-valued function

andor,negate,isEqual

BiPredicate<T, U>

TU

boolean

test

A Boolean-valued function with two arguments

andor,negate

函数式接口的使用可以参照以下文章,写的较为详细:

http://qiita.com/opengl-8080/items/22c4405a38127ed86a31


另外java8也专门为原始类型提供了对应的函数式接口,可以减少自动装箱(autoboxing)

Table 3-2 Functional Interfaces for Primitive Types: pq is intlongdoublePQ isIntLongDouble

Functional Interface

Parameter Types

Return Type

Abstract Method Name

BooleanSupplier

none

boolean

getAsBoolean

PSupplier

none

p

getAsP

PConsumer

p

void

accept

ObjPConsumer<T>

Tp

void

accept

PFunction<T>

p

T

apply

PToQFunction

p

q

applyAsQ

ToPFunction<T>

T

p

applyAsP

ToPBiFunction<T, U>

TU

p

applyAsP

PUnaryOperator

p

p

applyAsP

PBinaryOperator

pp

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


3.6 延迟

对于上一节合并操作,还有另外一个做法。就是将操作积累起来,一块执行。

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方法







JAVA SE 8 学习笔记(三)使用lambda编程

标签:

原文地址:http://blog.csdn.net/flycct/article/details/51316661

(1)
(1)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!