Lambda
函数式接口
lambda 表达式的使用需要借助于 函数式接口, 也就是说只有函数式接口才可以将其用 lambda 表达式进行简化. 函数式接口定义为仅含有一个抽象方法的接口. 按照这个定义, 一个接口如果声明了两个或两个以上的方法就不叫函数式接口.
JDK1.8 为接口的定义引入了默认方法, 可以用 default 关键字在接口中直接定义方法的实现. 如果一个接口存在多个默认方法, 但是仅含有一个抽象方法, 这个接口也符合函数式接口的定义.
@FunctionalInterface 注解用于标记该接口是一个函数式接口, 这也是 JDK1.8 之后新增的. 添加了该注解之后, 编译器会限制接口只允许有一个抽象方法, 否则报错. 建议为函数式接口添加该注解. 在 JDK 中添加了这个注解的典型接口有 Function, Consumer, Predicate 等.
该注解只能标记在 "有且仅有一个抽象方法" 的接口上
JDK8 接口中的静态方法和默认方法, 都不算是抽象方法
接口默认继承 java.lang.Object, 所以如果接口显示声明覆盖了 Object 中方法, 那么也不算抽象方法
该注解不是必须的, 如果一个接口符合 "函数式接口" 定义, 那么加不加该注解都没有影响. 加上该注解能够更好地让编译器进行检查. 如果编写的不是函数式接口, 但是加上了 @FunctionInterface, 那么编译器会报错
在一个接口中定义两个自定义的方法, 就会产生 Invalid '@FunctionalInterface' annotation; FunctionalInterfaceTest is not a functional interface 错误.
Consumer Consumer<T> 接收 T 对象, 不返回值
Predicate Predicate<T> 接收 T 对象并返回 boolean
Function Function<T, R> 接收 T 对象, 返回 R 对象
Supplier Supplier<T> 提供 T 对象(例如工厂), 不接收值
UnaryOperator UnaryOperator 接收 T 类型参数, 并返回同一类型的结果. 例如
- public static void main(String[] args) {
- List<Integer> list = Arrays.asList(10,20,30,40,50);
- UnaryOperator<Integer> unaryOpt = i->i*i;
- unaryOperatorFun(unaryOpt, list).forEach(x->System.out.println(x));
- }
- private static List<Integer> unaryOperatorFun(UnaryOperator<Integer> unaryOpt, List<Integer> list){
- List<Integer> uniList = new ArrayList<>();
- list.forEach(i->uniList.add(unaryOpt.apply(i)));
- return uniList;
- }
BinaryOperator BinaryOperator 接收两个 T 类型的参数, 并返回同一类型的结果, 例如
- public static void main(String[] args) {
- Map<String,String> map = new HashMap<>();
- map.put("X", "A");
- map.put("Y", "B");
- map.put("Z", "C");
- BinaryOperator<String> binaryOpt = (s1,s2)-> s1+"-"+s2;
- binaryOperatorFun(binaryOpt, map).forEach(x->System.out.println(x));
- }
- private static List<String> binaryOperatorFun(BinaryOperator<String> binaryOpt, Map<String,String> map){
- List<String> biList = new ArrayList<>();
- map.forEach((s1,s2)->biList.add(binaryOpt.apply(s1,s2)));
- return biList;
- }
- Lambda
行为参数化 行为参数化简单的说就是将方法的逻辑以参数的形式传递到方法中, 方法主体仅包含模板类通用代码, 而一些会随着业务场景而变化的逻辑则以参数的形式传递到方法之中, 采用行为参数化可以让程序更加的通用, 以应对频繁变更的需求. 例如对于一个 Apple 对象
- public class Apple {
- private Color color;
- private Float weight;
- public Apple() {}
- public Apple(Color color, Float weight) {
- this.color = color;
- this.weight = weight;
- }
- ...
- }
最初是需要筛选颜色, 可以使用颜色作为参数
- public static List<Apple> filterApplesByCOlor(Lis<Apple> apples, Color color) {
- List<Apple> filtered = new ArrayList<>();
- for (final Apple apple : apples) {
- if (color.equals(apple.getColor())) {
- filtered.add(apple);
- }
- }
- return filtered;
- }
如果以重量为参数, 也可以仿照上门的格式再写一个方法 如果筛选的条件不止一种, 需要灵活组合, 那就有必要将 filter 作为一个参数, 将筛选行为抽象化
- public interface AppleFilter {
- boolean accept(Apple apple);
- }
- public static class List<Apple> filterApplesByFilter(List<Apple> apples, AppleFilter filter) {
- List<Apple> filtered = new ArrayList<Apple>();
- for (final Apple apple : apples) {
- if (filter.accept(apple)) {
- filtered.add(apple);
- }
- }
- return filtered;
- }
- public static void main(String[] args) {
- //...
- AppleFilter filter = new AppleFilter() {
- @Override
- public boolean accept(Apple apple) {
- //...
- }
- }
- //...
- }
上面的行为参数化方式采用匿名类实现, 可以在具体调用的地方用匿名类指定函数的具体执行逻辑, 但是还不够简洁, 在 JDK1.8 中可以通过 lambda 表达式进行简化
- List<Apple> filtered = filterApplesByFilter(apples,
- (apple) -> Color.RED.equals(apple.getColor()));
Lambda 表达式的定义与形式 Lambda 表达式
本质上是一个函数, 虽然它不属于某个特定的类, 但具备参数列表, 函数主体, 返回类型, 甚至能够抛出异常
其次它是匿名的, lambda 表达式没有具体的函数名称
Lambda 表达式可以像参数一样进行传递, 从而简化代码的编写, 其格式定义如下
参数列表 -> 表达式参数列表 -> {表达式集合}
注意
lambda 表达式隐含了 return 关键字, 所以在单个的表达式中, 我们无需显式的写 return 语句, 但是当表达式是一个语句集合的时候则需要显式添加 return 语句并用花括号{ } 将多个表达式包围起来.
lambda 中, this 不是指向 lambda 表达式产生的那个 SAM 对象, 而是声明它的外部对象
方法引用
- objectName::instanceMethod
- ClassName::staticMethod
- ClassName::instanceMethod
前两种方式类似, 等同于把 lambda 表达式的参数直接当成 instanceMethod|staticMethod 的参数来调用. 比如 System.out::println 等同于 x->System.out.println(x), Math::max 等同于(x, y)->Math.max(x,y), 例如 execStrs.forEach(System.out::println).
最后一种方式等同于把 lambda 表达式的第一个参数当成 instanceMethod 的目标对象, 其他剩余参数当成该方法的参数. 比如 String::toLowerCase 等同于 x->x.toLowerCase(). 可以这么理解, 前两种是将传入对象当参数执行方法, 后一种是调用传入对象的方法.
- List<BigDecimal> bdList = new ArrayList<>();
- BigDecimal result = bdList.stream()
- .reduce(BigDecimal.ZERO, BigDecimal::add);
上面的代码,
创建一个 BigDecimal 列表
转换为 Stream<BigDecimal>
调用 reduce 方法
提供了一个定义好的加数, 这里是 BigDecimal.ZERO
使用 BinaryOperator<BigDecimal>, 通过方法 BigDecimal::add 将两个 BigDecimal 的值相加
- List<Invoice> invoiceList = new ArrayList<>();
- //populate
- Function<Invoice, BigDecimal> totalMapper = invoice -> invoice.getUnit_price().multiply(invoice.getQuantity());
- BigDecimal result = invoiceList.stream()
- .map(totalMapper)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
上面的代码, 使用了一个 Function 做 stream 对象的 map.
构造器引用 构造器引用语法: ClassName::new 把 lambda 表达式的参数当成 ClassName 构造器的参数. 例如 BigDecimal::new 等同于 x->new BigDecimal(x).
Iterable
JDK1.8 中, Iterable 接口增加了两个带 default 实现的方法, 一个是
- default void forEach(Consumer<? super T> action) {
- Objects.requireNonNull(action);
- for (T t : this) {
- action.accept(t);
- }
- }
另一个是
- default Spliterator<T> spliterator() {
- return Spliterators.spliteratorUnknownSize(iterator(), 0);
- }
- Spliterator
Spliterator 这个类型是 JDK1.8 新增的接口方法
- boolean tryAdvance(Consumer<? super T> action);
- Spliterator<T> trySplit();
Spliterator 用于对一个数据源进行遍历和分区, 这个数据源可以是一个数组, 一个 Collection, 一个 IO 通道, 或者一个生成函数. Spliterator 可以单个或成批地处理元素, 也可以将部分元素划分为单独的 Spliterator, 不能分区的 Spliterator 将不能从并行处理中获益. Spliterator 用 characteristics()方法汇报结构特性. 调用 trySplit()的线程可以将返回的 Spliterator 传递给另一个线程, 这个线程可以遍历或进一步拆分这个 Spliterator.
Consumer
Consumer 是一个操作, 用于接受单个输入并且不返回结果. 在 stream 里主要是用于 forEach 内部迭代的时候, 对传入的参数做一系列的业务操作. Consumer 有相关的原始类型实现: IntConsumer,LongConsumer,DoubleConsumer, 是 Consumer 的特例
- void accept(T t);
- default Consumer<T> andThen(Consumer<? super T> after) {
- Objects.requireNonNull(after);
- return (T t) -> { accept(t); after.accept(t); };
- }
JDK8 中有双冒号的用法, 就是把方法当做参数传到 stream 内部, 使 stream 的每个元素都传入到该方法里面执行一下
- public class MyTest {
- public static void printValur(String str){
- System.out.println("print value :"+str);
- }
- public static void main(String[] args) {
- List<String> al = Arrays.asList("a", "b", "c", "d");
- al.forEach(AcceptMethod::printValur);
- // 下面的方法和上面等价的
- Consumer<String> methodParam = AcceptMethod::printValur;
- al.forEach(x -> methodParam.accept(x));// 方法执行 accept
- }
- }
- Collection
Collection 是集合类型对象的根接口. collection 代表了一组对象集合, 这些对象就是集合的元素. 一些集合允许重复的元素, 另一些则不允许. 一些集合是有序的, 另一些则是无序的. JDK 并没有提供这个接口的直接实现, 但是提供了其派生接口的实现, 例如 Set 和 List. 这个接口用于传递和操作集合类型的数据并保持最大的通用特性.
打包类型或多集合类型 (包含重复对象的无序集合) 应该直接实现这个接口.
所有通用的 Collection 实现类 (通常是实现某个派生接口) 都应该提高两个标准的构造方法: 一个 void (无参数) 构造方法和一个单参数构造方法, 前者创建一个空集合, 后者创建一个包含一个元素的集合. 实际上, 后者允许用户用任何集合创建包含相同元素的新类型集合. 虽然不能对这种便利进行强制 (因为接口不能包含构造方法) 但是 Java 平台上所有的通用 Collection 实现都遵守这种约定.
在操作集合时, 如果调用了此集合类型不支持的方法, 应当抛出 UnsupportedOperationException. 在特殊情况下, 例如调用对集合并不产生影响时, 可以抛出 UnsupportedOperationException, 但这不是强制的. 例如在一个不可修改的 collection 上调用 addAll(Collection)方法时, 如果参数的集合为空时, 不强制抛出异常.
有些集合实现会对元素有限制. 例如一些实现禁止 null 元素, 另一些则对元素的类型有要求. 添加不合法的元素时会抛出异常, 例如 NullPointerException 或 ClassCastException. 而查询一个不合法元素时可能会抛出异常, 也可能会返回 false; 这些异常在接口的规范中是可选的.
同步的策略由各个集合实现自己来决定. 由于不是强约束, 被其他线程转换的集合上调用任何接口方法都可能导致未定义的行为, 包含直接调用, 传参后调用, 或者用迭代器对集合进行操作.
Collections 框架中的许多方法是根据 equals 方法定义的. 例如, contains(Object o)方法的定义是: "仅当这个集合包含至少一个元素 e 满足 (o==null ? e==null : o.equals(e)) 时, 返回 true." 这个定义不应该被解释为说明使用非 null 参数调用 Collection.contains 时将导致 o.equals(e)对每一个 e 元素进行调用. Collection 的实现可以自行优化避免使用 equals, 例如首先比较两个元素的 hash code. (Object.hashCode() 的定义保证了 hash code 不相等的两个对象肯定不相等.) 更进一步, Collections 框架的不同实现可以自行利用对象方法的特定行为进行优化..
一些操作中, 对直接或间接包含自己的集合进行递归遍历可能会抛出异常, 例如 clone(), equals(), hashCode() 和 toString() 方法. 具体的实现类可能会对这种情况进行处理, 不过大多数现在的实现并不会这样做.
来源: http://www.bubuko.com/infodetail-3059132.html