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

三、使用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方法

时间: 2024-08-27 09:55:12

JAVA SE 8 学习笔记(三)使用lambda编程的相关文章

java之jvm学习笔记三(Class文件检验器)

前面的学习我们知道了class文件被类装载器所装载,但是在装载class文件之前或之后,class文件实际上还需要被校验,这就是今天的学习主题,class文件校验器. class文件 校验器,保证class文件内容有正确的内部结构,java虚拟机的class文件检验器在字节码执行之前对文件进行校验,而不是在执行中进行校验 class文件校验器要进行四趟独立的扫描来完成校验工作 class文件校验器分成四趟独立的扫描来完成校验. 第一趟 在装载字节序列的时候进行,这个是校验class文件的结构的合

Java多线程技术学习笔记(二)

目录: 线程间的通信示例 等待唤醒机制 等待唤醒机制的优化 线程间通信经典问题:多生产者多消费者问题 多生产多消费问题的解决 JDK1.5之后的新加锁方式 多生产多消费问题的新解决办法 sleep和wait的区别 停止线程的方式 守护线程 线程的其他知识点 一.线程间的通信示例 返目录回 多个线程在处理同一资源,任务却不同. 假设有一堆货物,有一辆车把这批货物往仓库里面运,另外一辆车把前一辆车运进仓库的货物往外面运.这里货物就是同一资源,但是两辆车的任务却不同,一个是往里运,一个是往外运. 下面

java/android 设计模式学习笔记(14)---外观模式

这篇博客来介绍外观模式(Facade Pattern),外观模式也称为门面模式,它在开发过程中运用频率非常高,尤其是第三方 SDK 基本很大概率都会使用外观模式.通过一个外观类使得整个子系统只有一个统一的高层的接口,这样能够降低用户的使用成本,也对用户屏蔽了很多实现细节.当然,在我们的开发过程中,外观模式也是我们封装 API 的常用手段,例如网络模块.ImageLoader 模块等.其实我们在开发过程中可能已经使用过很多次外观模式,只是没有从理论层面去了解它. 转载请注明出处:http://bl

lucene学习笔记(三)

好几天没更新了.更新一下,方便自己和大家学习. 这是最基本的代码 package index; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document;

java/android 设计模式学习笔记(12)---组合模式

这篇我们来介绍一下组合模式(Composite Pattern),它也称为部分整体模式(Part-Whole Pattern),结构型模式之一.组合模式比较简单,它将一组相似的对象看作一个对象处理,并根据一个树状结构来组合对象,然后提供一个统一的方法去访问相应的对象,以此忽略掉对象与对象集合之间的差别.这个最典型的例子就是数据结构中的树了,如果一个节点有子节点,那么它就是枝干节点,如果没有子节点,那么它就是叶子节点,那么怎么把枝干节点和叶子节点统一当作一种对象处理呢?这就需要用到组合模式了. 转

java/android 设计模式学习笔记(9)---代理模式

这篇博客我们来介绍一下代理模式(Proxy Pattern),代理模式也成为委托模式,是一个非常重要的设计模式,不少设计模式也都会有代理模式的影子.代理在我们日常生活中也很常见,比如上网时连接的代理服务器地址,更比如我们平时租房子,将找房子的过程代理给中介等等,都是代理模式在日常生活中的使用例子. 代理模式中的代理对象能够连接任何事物:一个网络连接,一个占用很多内存的大对象,一个文件,或者是一些复制起来代价很高甚至根本不可能复制的一些资源.总之,代理是一个由客户端调用去访问幕后真正服务的包装对象

java/android 设计模式学习笔记(6)---适配器模式

这篇来介绍一下适配器模式(Adapter Pattern),适配器模式在开发中使用的频率也是很高的,像 ListView 和 RecyclerView 的 Adapter 等都是使用的适配器模式.在我们的实际生活中也有很多类似于适配器的例子,比如香港的插座和大陆的插座就是两种格式的,为了能够成功适配,一般会在中间加上一个电源适配器,形如: 这样就能够将原来不符合的现有系统和目标系统通过适配器成功连接. 说到底,适配器模式是将原来不兼容的两个类融合在一起,它有点类似于粘合剂,将不同的东西通过一种转

java之jvm学习笔记十三(jvm基本结构)

java之jvm学习笔记十三(jvm基本结构) 这一节,主要来学习jvm的基本结构,也就是概述.说是概述,内容很多,而且概念量也很大,不过关于概念方面,你不用担心,我完全有信心,让概念在你的脑子里变成图形,所以只要你有耐心,仔细,认真,并发挥你的想象力,这一章之后你会充满自信.当然,不是说看完本章,就对jvm了解了,jvm要学习的知识实在是非常的多.在你看完本节之后,后续我们还会来学jvm的细节,但是如果你在学习完本节的前提下去学习,再学习其他jvm的细节会事半功倍. 为了让你每一个知识点都有迹

java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)

java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessController的checkPerssiom方法,访问控制器AccessController的栈检查机制又遍历整个 PerssiomCollection来判断具体拥有什么权限一旦发现栈中一个权限不允许的时候抛出异常否则简单的返回,这个过程实际上比我的描述要复杂 得多,这里我只是简单的一句带过,因为这