lambda语法
i->{return "xxx";}
本质上是借用匿名类来实现,并没有超脱原来的java的解题思路。
这种类似于传一个函数指针的模式,底层是@FunctionalInterface
注解的接口,因此我们可以自己声明一个接口作为方法的行参:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| @FunctionalInterface public interface GetIdataFunction { int getI(int i);
default String getInfo() { return "找下标为i的数据"; }
}
public boolean searchMatrix(int[][] matrix, int target) { if (matrix == null || matrix.length == 0 || matrix[0].length == 0 || target < matrix[0][0]) { return false; }
int lowerBound = getLowerBound(target, 0, matrix.length - 1, i -> matrix[i][0]); if (lowerBound >= 0) { return true; } final int finalLowerBound = -lowerBound - 2; int x = getLowerBound(target, 0, matrix[0].length - 1, i -> matrix[finalLowerBound][i]); return x >= 0; }
private int getLowerBound(int target, int left, int right, GetIdataFunction helper) { int mid; while (left <= right) { mid = (left + right) >>> 1; int midVal = helper.getI(mid); if (target < midVal) { right = mid - 1; } else if (target > midVal) { left = mid + 1; } else { return mid; } } return -left - 1; }
|
现成的Function声明汇总
java8给了一些统一的描述术语(DSL)来分类这些函数接口:
接口 |
lambda |
含义 |
Predicate< T> |
T->boolean |
类似于filter,接收T类型返回boolean |
Consumer< T> |
T->void |
类似于foreach,接收T类型返回void |
Function< T,R> |
T->R |
类似于Map,接收T类型返回R类型 |
Supplier< T> |
()->T |
|
UnaryOperator< T> |
T->T |
|
BinaryOperator< T> |
(T,T)->T |
|
BiPredicate< L,R> |
(L,R)->boolean |
|
BiConsumer< T,U> |
(T,U)->void |
|
BiFunction< T,U,R> |
(T,U)->R |
|
常见的几种函数指针都可以用这些现成的声明,如果要抛异常,或者比较特殊的入参、出参,就自己用@FunctionalInterface
注解声明个接口和相应方法即可。
此外,上述接口的T,R泛型都需要类、引用,因此如果要节省装箱开销,可以用相应的原始类型声明,比如一个int->int
的变换,相应的接口就是IntUnaryOperator
。
因此上一节中的代码可以简化成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public boolean searchMatrix(int[][] matrix, int target) { if (matrix == null || matrix.length == 0 || matrix[0].length == 0 || target < matrix[0][0]) { return false; }
int lowerBound = getLowerBound(target, 0, matrix.length - 1, i -> matrix[i][0]); if (lowerBound >= 0) { return true; } final int finalLowerBound = -lowerBound - 2; int x = getLowerBound(target, 0, matrix[0].length - 1, i -> matrix[finalLowerBound][i]); return x >= 0; }
private int getLowerBound(int target, int left, int right, IntUnaryOperator helper) { int mid; while (left <= right) { mid = (left + right) >>> 1; int midVal = helper.applyAsInt(mid); if (target < midVal) { right = mid - 1; } else if (target > midVal) { left = mid + 1; } else { return mid; } } return -left - 1; }
|
其他的原始类型(免装箱)的现成有的声明如下:(要用的时候可以查表)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| IntPredicate,LongPredicate, DoublePredicate
IntConsumer,LongConsumer, DoubleConsumer
IntFunction<R>, IntToDoubleFunction, IntToLongFunction, LongFunction<R>, LongToDoubleFunction, LongToIntFunction, DoubleFunction<R>, ToIntFunction<T>, ToDoubleFunction<T>, ToLongFunction<T>
BooleanSupplier,IntSupplier,LongSupplier,DoubleSupplier
IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator
IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator
ObjIntConsumer<T>, ObjLongConsumer<T>, ObjDoubleConsumer<T>
ToIntBiFunction<T,U>, ToLongBiFunction<T,U>, ToDoubleBiFunction<T,U>
|
无状态
默认无状态的操作: map,filter,collect; // 假如不使用外部变量(闭包)
有状态的操作: reduce,sum,max,sorted,distinct,skip,limit; // 有内部状态、初始值
// flatmap?
原始类型流
原始类型流如: IntStream
1 2
| IntStream intStream = menu.stream().mapToInt(Dish::getCalories); Stream< Integer> stream = intStream.boxed();
|
原始类型的Optional:OptionalInt
1
| OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();
|
java中的蜂巢结构
d3中有个蜂巢结构,java8中也可以有:
1 2 3 4 5 6 7 8
| Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel = menu.stream().collect( groupingBy(Dish::getType, groupingBy(dish -> { if (dish.getCalories() <= 400) return CaloricLevel.DIET; else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL; else return CaloricLevel.FAT; }); ));
|
还可以分组计数:
1 2
| Map<Dish.Type, Long> typesCount = menu.stream().collect( groupingBy(Dish::getType, counting()));
|
collectors中的characteristics
返回枚举值的list:
1 2 3
| UNORDERED: 结果不受顺序影响; CONCURRENT: 可以并行运行; IDENTITY_FINISH: 累加器和最后的结果类型是一样的,不需要再转换。
|
一般是是返回后两者,因为一般都会希望维持顺序(如toList
):
1 2 3 4 5
| @Override public Set<Characteristics> characteristics() { return Collections.unmodifiableSet(EnumSet.of( IDENTITY_FINISH, CONCURRENT)); }
|
如果是生成质数这种,实现上依赖之前已经生成的质数,有状态,则不能并行。只返回一个IDENTITY_FINISH
.
Spliterator
Spliterator的特性:
1 2 3 4 5 6 7 8
| ORDERED 元素有既定的顺序(例如List),因此Spliterator在遍历和划分时也会遵循这一顺序 DISTINCT 对于任意一对遍历过的元素x和y,x.equals(y)返回false SORTED 遍历的元素按照一个预定义的顺序排序 SIZED 该Spliterator由一个已知大小的源建立(例如Set),因此estimatedSize()返回的是准确值 NONNULL 保证遍历的元素不会为null IMMUTABLE Spliterator的数据源不能修改。这意味着在遍历时不能添加、删除或修改任何元素 CONCURRENT 该Spliterator的数据源可以被其他线程同时修改而无需同步 SUBSIZED 该Spliterator和所有从它拆分出来的Spliterator都是SIZED
|
DISTINCT和SORTED比较难达到;
一般是:
1 2 3 4
| @Override public int characteristics() { return ORDERED + SIZED + SUBSIZED + NONNULL + IMMUTABLE; }
|
设计模式
策略模式: 策略发生变化,只修改用到的函数。(例如输入校验策略)
模版方法: 修改一大段代码中其中一个函数的调用实现。(传入函数指针)
观察者模式: 例如GUI场景下,注册一大堆监听事件。(传入函数指针)
责任链模式:
每个处理者可以增加后继处理节点,形成一个链表,每次处理的时候,调用下一个处理者。用lamda中的addThen的话:
1 2 3 4 5 6 7
| UnaryOperator<String> headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text; UnaryOperator<String> spellCheckerProcessing = (String text) -> text.replaceAll("labda", "lambda"); Function<String, String> pipeline = headerProcessing.andThen(spellCheckerProcessing); String result = pipeline.apply("Aren't labdas really sexy?!!")
|
工厂模式
可以把构造函数放map里:
1 2 3 4 5 6
| final static Map<String, Supplier<Product>> map = new HashMap<>(); static { map.put("loan", Loan::new); map.put("stock", Stock::new); map.put("bond", Bond::new); }
|
lamda方法的单元测试
- 存成成员变量(函数指针);
- 匿名方法粒度太小了,应该测试上层方法;
lambda的副作用
- 出错信息模糊: 错误栈缺失准确的信息;
调试技巧
使用peek函数。
1 2 3 4 5 6 7 8 9 10
| List<Integer> result = numbers.stream() .peek(x -> System.out.println("from stream: " + x)) .map(x -> x + 17) .peek(x -> System.out.println("after map: " + x)) .filter(x -> x % 2 == 0) .peek(x -> System.out.println("after filter: " + x)) .limit(3) .peek(x -> System.out.println("after limit: " + x)) .collect(toList());
|
3种兼容性
二进制级兼容: 现有二进制执行文件能无缝链接;
源码级兼容: 现有程序能够成功编译通过;
函数行为级兼容:程序接受同样的输入能得到同样的结果。
接口中加入新的方法:
二进制级兼容:兼容;(不会被调用)
源码级兼容:不兼容;(不符合语法)
函数行为级兼容: 兼容;(不影响原有函数)
抽象类和有默认方法的接口区别:抽象类可以有成员、不能多继承。
默认方法的接口多继承优先级问题:
- 类中的实现> 接口中的;
- 子接口中实现>父接口中的;
- 上述两种无法判断时,必须显式调用,声明调用的是哪个实现:
Option
每次新建一个类的时候,所有引用类成员都尽量搞成Option
.
如果可能为null,就搞成Option
;
如果不可能为null,就维持朴素类型。// 比如构造时就自动填充确定值的final
相当于领域中将是否可选这个属性也封装了进去,而不是靠注释来弱约束。
1 2 3 4
| Optional<Car> optCar = Optional.empty();
Optional<Car> optCar = Optional.ofNullable(car);
|
用了Optional以后,后续用Stream api处理(疯狂调用flatMap
),整个过程就变成函数式,再也不需要null关键字。
对于可选成员(Option<T>
): 调用flatMap
;
对于必选成员(final String
): 调用map
;
Option的缺点: 无法序列化。
如果需要可以序列化的类,只能隐藏Option变量:
1 2 3
| public Optional<Car> getCarAsOptional() { return Optional.ofNullable(car); }
|
Optional不能滥用(不能序列化):(java Lambda(JSR-335)专家组考虑并拒绝了它)
尽量避免将Optional用于类属性、方法参数及集合元素中,因为以上三种情况,完全可以使用null值来代替Optional,没有必要必须使用Optional,另外Optional本身为引用类型,大量使用Optional会出现类似(这样描述不完全准确)封箱、拆箱的操作,在一定程度上会影响JVM的堆内存及内存回收。
相关tip:
https://segmentfault.com/a/1190000018936877?utm_source=tag-newest
- 不要放进Map里,因为Optional不是作为值类型设计的,它的
equals、hashcode、==
方法都不能保证正确性(和唯一性有关的方法)。
最后: POJO还是保持纯粹,这样也有向前先后兼容性。
异步编程
无法从线程失败中恢复的版本:
1 2 3 4 5 6 7 8
| public Future<Double> getPriceAsync(String product) { CompletableFuture<Double> futurePrice = new CompletableFuture<>(); new Thread( () -> { double price = calculatePrice(product); futurePrice.complete(price); }).start(); return futurePrice; }
|
加上try catch:
1 2 3 4 5 6
| try { double price = calculatePrice(product); futurePrice.complete(price); } catch (Exception ex) { futurePrice.completeExceptionally(ex); }
|
精简后:
1 2 3
| public Future<Double> getPriceAsync(String product) { return CompletableFuture.supplyAsync(() -> calculatePrice(product)); }
|
并行查询(无状态)
无状态:
1 2
| shops.parallelStream().map(shop->String.format("%s price is %.2f" ,shop.getName(), shop.getPrice(product)))).collect(ToList());
|
定制Executor:
1 2 3 4 5 6 7 8 9
| private final Executor executor = Executors.newFixedThreadPool(Math.min(shops.size(), 100), new ThreadFactory() { public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setDaemon(true); return t; } });
|
计算密集,无IO: 使用Stream接口; // 不加线程数
IO密集,计算简单: 使用CompletableFuture
,自定义线程池; // 可以疯狂加线程
时间api
LocalDate和LocalTime
1 2 3 4 5 6
| LocalDate date = LocalDate.of(2014, 3, 18); LocalDate today = LocalDate.now(); LocalTime time = LocalTime.of(13, 45, 20);
LocalDate date = LocalDate.parse("2014-03-18"); LocalTime time = LocalTime.parse("13:45:20");
|
复合后: LocalDateTime
1
| LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20); LocalDateTime dt2 = LocalDateTime.of(date, time);
|
unix时间: java.time.Instant
1
| Instant.ofEpochSecond(3);
|
时间间隔: Duration
1 2 3 4
| Duration d1 = Duration.between(time1, time2); Duration d1 = Duration.between(dateTime1, dateTime2); Duration d2 = Duration.between(instant1, instant2); Duration threeMinutes = Duration.ofMinutes(3);
|
天间隔: Period
1 2 3
| Period tenDays = Period.between(LocalDate.of(2014, 3, 8), LocalDate.of(2014, 3, 18)); Period tenDays = Period.ofDays(10);
|
上述类都是不可变的。// 线程安全
修改直接返回新变量:
1 2
| LocalDate date2 = date1.withYear(2011); LocalDate date2 = date1.plusWeeks(1);
|
个性化操作: TemporalAdjuster
下个周日、下个工作日、本月最后一天:
1 2 3 4
| import static java.time.temporal.TemporalAdjusters.*; LocalDate date1 = LocalDate.of(2014, 3, 18); LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY)); LocalDate date3 = date2.with(lastDayOfMonth());
|
格式化、格式转换
localData<=>String
1 2 3 4 5 6 7 8
| LocalDate date = LocalDate.of(2014, 3, 18); String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE); String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
LocalDate date1 = LocalDate.parse("20140318", DateTimeFormatter.BASIC_ISO_DATE); LocalDate date2 = LocalDate.parse("2014-03-18", DateTimeFormatter.ISO_LOCAL_DATE);
|
定制格式:
1 2 3 4
| DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); LocalDate date1 = LocalDate.of(2014, 3, 18); String formattedDate = date1.format(formatter); LocalDate date2 = LocalDate.parse(formattedDate, formatter);
|
时区
java.time.ZoneId
和ZonedDateTime
:
从LocalData转换到ZonedDateTime:(其他几个也一样)
1 2 3 4
| ZoneId romeZone = ZoneId.of("Europe/Rome");
LocalDate date = LocalDate.of(2014, Month.MARCH, 18); 6 ZonedDateTime zdt1 = date.atStartOfDay(romeZone);
|
时区差: ZoneOffset
其他日历系统: ThaiBuddhistDate
,MinguoDate
,JapaneseDate
,HijrahDate
其他tips
ConcurrentHashMap类:
用mappingCount
方法代替size
方法;
mappingCount
: 返回long;
size
: 返回int; (实际大小超过int时无效)
重复遍历流: 可以使用StreamForker,缺点本质上是存了内存。
lambda底层实现
匿名类的实现
匿名内部类底层是生成一个class$1
的类。(外层类名+数字)
缺点: 每个类都需要加载、验证的操作,如果类太多,开销太大;
查看字节码和常量池
使用命令:
lambda的实现: InvokeDynamic
本质上是在所在类里新增了一个方法。将Lambda表达式的代码体填入到运行时动态创建的静态方法
,就完成了Lambda表达式的字节码转换。
动态链接方法,然后用InvokeDynamic
调用:
1 2 3 4 5 6
| public class Lambda { Function<Object, String> f = [dynamic invocation of lambda$1] static String lambda$1(Object obj) { return obj.toString(); } }
|
优点:
- lamda相关字节码的生成推迟到使用的时候; (懒加载)
- 如果不是闭包:有类似于static final变量的缓存效果;(闭包的话,因为可能捕获不同的局部变量,不可以缓存)
- 没有额外的开销;
如果lambda是个闭包,也就是捕获了其他作用域内的变量(当然编译时会要求是final或者实质上是final),也很简单,就是给这个函数加一个参数即可。