一、Lambda 表达式
1.1 基本概念
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
1.2 语法
(parameters) -> expression
或
(parameters) ->{ statements; }
1.3 重要特征
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
使用 Lambda 表达式需要注意以下两点:
- Lambda 表达式主要用来定义行内执行的方法类型接口。
- Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
1.4 变量作用域
lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)。
在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
二、方法引用
2.1 基本概念
方法引用通过方法的名字来指向一个方法。、
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用使用一对冒号 :: ;
2.2 引用方法
- 构造器引用
Class::new ,或者更一般的Class<T>::new - 静态方法引用
Class::static_method - 特定类的任意对象的方法引用
Class::method - 特定对象的方法引用
instance::method
三、函数式接口
3.1 基本概念
函数式接口就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的的接口。
函数式接口可以被隐式转换为lambda表达式。
例:
@FunctionalInterface
interface GreetingService {
void sayMesage(String message);
}
如果使用lambda表达式来创建一个函数式接口实例,那这个lambda表达式的入参和返回必须符合这个函数式接口中唯一的抽象方法的定义。
3.2 默认方法
简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。
我么只需在方法名前面加个 default 关键字即可实现默认方法。
为什么要有这个特性?
首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8 之前的集合框架没有 foreach 方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。
3.3 Supplier
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
3.4 Function
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
3.5 Predicate
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
3.6 Consumer
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
四、Optional
4.1 基本概念
为了解决NullPointerException问题,减少代码中的判空,实现函数式编程,给工程师们提供函数式的API。
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional 类的引入很好的解决空指针异常。
4.2 类方法
修饰符和类型 | 方法 | 描述 |
---|---|---|
static<T> Optional<T> | empty() | 返回空的Optional实例 |
boolean | equals(Object obj) | 判断其他对象是否等于Optional |
int | hashCode() | 返回存在值的哈希码,如果值不存在 返回 0 |
static <T> Optional<T> | of(T value) | 返回一个指定非null值的Optional |
static <T> Optional<T> | ofNullable(T value) | 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional |
T | orElse(T other) | 如果存在该值,返回值, 否则返回 other |
T | orElseGet(Supplier<? extends T> other) | 如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果 |
boolean | isPresent() | 如果值存在则方法会返回true,否则返回 false |
T | get() | 如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException |
String | toString() | 返回一个Optional的非空字符串,用来调试 |
Optional<T> | filter(Predicate<? super <T> predicate) | 如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional |
Optional<U> | flatMap(Function<? super T, Optional<U>> mapper) | 如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional |
void | ifPresent(Consumer<? super T> consumer) | 如果值存在则使用该值调用 consumer , 否则不做任何事情 |
Optional<U> | map(Function<? super T,? extends U> mapper) | 如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional |
<X extends Throwable> T | orElseThrow(Supplier<? extends X> exceptionSupplier) | 如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常 |
五、Stream
5.1 基本概念
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
流操作由3部分组成:
- 创建流
- 零个或多个中间操作
- 终止操作(到这一步才会执行整个stream pipeline计算)
5.2 什么是stream
Stream(流)是一个来自数据源的元素队列并支持聚合操作
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代:以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
5.3 创建流的方式:
-
通过Stream接口的of静态方法创建一个流
Stream<String> stream1 = Stream.of("a", "b", "c");
-
创建一个空的流
Stream<Object> empty = Stream.empty();
-
通过builder创建
Stream<Object> build = Stream.builder().add("a").add("b").add("c").build();
-
通过Arrays类的stream方法,实际上第一种of方法底层也是调用的Arrays.stream(values)
String[] array = new String[]{"hello","world","helloworld"}; Stream<String> stream3 = Arrays.stream(array);
-
通过集合的stream方法,该方法是Collection接口的默认方法,所有集合都继承了该方法
Stream<String> stream2 = Arrays.asList("hello","world","helloworld").stream();
-
合并多个Stream
Stream stream = Stream.concat(stream1,stream2);
-
generate()和iterate()
两个都是生成一个无限的流,通常跟
limit()
一起使用,限制流中元素的个数。不同的是前者可以根据任何计算方式来生成,后者只能根据给定的
seed
来生成。Stream<T> generate(Supplier<T> s): Stream.generate(UUID.randomUUID()::toString).limit(10).forEach(System.out::println); Stream<T> iterate(final T seed, final UnaryOperator<T> f): //从1开始,每个元素比前一个元素大2,最多生成10个元素 Stream.iterate(1,item -> item + 2).limit(10).forEach(System.out::println);
集合接口有两个方法来生成流:
- stream() − 为集合创建串行流。
- parallelStream() − 为集合创建并行流。
5.4 基本方法
5.4.1 中间处理
-
筛选
接收一个lambda表达式,过滤掉某些元素,仅留下符合要求的元素。
filter(d -> true)
Stream<Integer> stream = Stream.of(1, 2, -1, 0, 3, -2); stream.filter(value -> value > 0).forEach(System.out::print);
-
截断
仅保留流中的前n个元素。由于中间处理是惰性的,所以limit在某些情况下可以很大的提升处理速度。
limit(5)
Stream<Integer> stream = Stream.iterate(0, x -> x + 2); stream.limit(5).forEach(System.out::print);
-
舍弃
舍弃流中的前n个元素,仅保留第n+1个及其之后的元素。skip(5)
Stream<Integer> stream = Stream.iterate(1, 2, 3, 4, 5, 6, 7); stream.skip(3).forEach(System.out::print);
-
去重
去掉Stream中重复的元素,它使用hashCode和equals方法来判断元素是否相等。distinct()
Stream<String> stream = Stream.of("a", "b", "c", "b", "c"); stream.distinct().forEach(System.out::print);
-
排序
对Stream中的元素进行排序。sorted()
sorted(Comparator.comparingInt(v -> v))
、sorted((v1, v2) -> v2 - v1)
Stream<Integer> stream = Stream.of(2, 4, 1, 5, 3); stream.sorted().forEach(System.out::print); stream.sorted(Comparator.reverseOrder()).forEach(System.out::print);
-
映射
通过Lambda表达式,将每一个元素一一映射为一个新的元素。map(v -> v + 55)
Stream<Integer> stream = Stream.of(2, 4, 1, 5, 3); stream.map(v -> v + 55).forEach(System.out::println);
-
扁平化映射
通过Lambda表达式,将每一个元素一一映射为一个新的Stream后,将新的Stream全部连起来。
flatMap(theList -> theList.stream())
Stream<String> stream = Stream.of("abc", "def", "ghi"); stream.flatMap(str -> str.chars().boxed()).forEach(System.out::print);
5.4.2 结束处理
-
迭代
对Stream内的每一个元素进行循环处理。forEach(System.out::println)
Stream<String> stream = Stream.of("a", "b", "c"); stream.forEach(System.out::print);
匹配
判断Stream中的元素是否匹配某条件,返回boolean结果。allMatch:Stream中是否所有元素都匹配
allMatch(v -> true)
anyMatch:Stream中是否有任一元素匹配
noneMatch(v -> true)
noneMatch:Stream中是否所有元素都不匹配
anyMatch(v -> true)
Stream<String> stream = Stream.of(1, 2, 4, 0, -3, -5); stream.allMatch(value -> value > 0); // 返回false stream.anyMatch(value -> value > 0); // 返回true stream.noneMatch(value -> value > 0); // 返回false
-
查找
查找Stream中的一个元素,返回Optional类型。一般与filter等一起使用。
findFirst:查找第一个元素
findAny:查找任一个元素。在并行流(parallelStream)中性能提升比较明显。findFirst().orElse(0)
findAny().orElse(0)
Stream<String> stream = Stream.of(1, 2, 4, 0, -3, -5); System.out.print(stream.findFirst().orElse(0)); System.out.print(stream.findAny().orElse(0));
-
统计
count:统计Stream中元素的个数。min:获取Stream中的最小元素。
max:获取Stream中的最大元素。
count()
min(Comparator.comparing(v -> v))
、mapToInt(v -> v + 3).min().orElse(0)
max(Comparator.comparing(v -> v))
、mapToInt(v -> v + 3).max().orElse(0)
System.out.println(Stream.of(1, 2, 4, 0, -3, -5).count()); System.out.println(Stream.of(1, 2, 4, 0, -3, -5).min(Comparator.comparing(v -> v)).orElse(0)); System.out.println(Stream.of(1, 2, 4, 0, -3, -5).max((v1, v2) -> v1.compareTo(v2)).orElse(0));
-
规约
将Stream中的每一个元素进行指定的叠加处理,最终生成一个值。
reduce(BigDecimal.ZERO, BigDecimal::add).get()
Stream<String> stream = Stream.of("abc", "def", "ghi"); System.out.println(stream.reduce((s, s2) -> s + ", " + s2).get());
-
收集
将Stream收集成各种形式。主要利用Collectors中的静态方法来实现。
collect(Collectors.toList())
List<String>list = Stream.of("abc", "def", "ghi").collect(Collectors.toList()); Set<String>set = Stream.of("abc", "def", "ghi").collect(Collectors.toSet()); LinkedList<String>list = Stream.of("abc", "def", "ghi").collect(Collectors.toCollection(LinkedList::new));
collect(Collectors.toMap(v -> v, v -> v.toUpperCase())
Map<String, String> map = Stream.of("abc", "def", "ghi").collect(Collectors.toMap(v -> v, v -> v.toUpperCase()));
-
计数
collect(Collectors.counting())
long count = Stream.of(1, 2, 4, 0, -3, -5).collect(Collectors.counting());
-
平均
collect(Collectors.averagingDouble(v -> v))
collect(Collectors.averagingInt(v -> v))
collect(Collectors.averagingLong(v -> v))
double average = Stream.of(1, 2, 4, 0, -3, -5).collect(Collectors.averagingInt(v -> v));
-
最小值
collect(Collectors.minBy(Integer::compare)))
Optional<Integer> min = Stream.of(1, 2, 4, 0, -3, -5).collect(Collectors.minBy(Integer::compare));
-
最大值
collect(Collectors.maxBy(Integer::compare)))
Optional<Integer> max = Stream.of(1, 2, 4, 0, -3, -5).collect(Collectors.maxBy(Integer::compare));
合计
collect(Collectors.summingInt(v -> v))
-
分组
collect(Collectors.groupingBy(v -> v.equals("11")))
Stream<Goods> goodsStream = Stream.of(new Goods("A", 18), new Goods("A", 15), new Goods("B", 5), new Goods("B", 20)); Map<String, List<Goods>> groupedGoods = goodsStream.collect(Collectors.groupingBy(Goods::getGoodsName)); Map<String, Map<String, List<Goods>>> groupedGoods = goodsStream.collect(Collectors.groupingBy(Goods::getGoodsType, Collectors.groupingBy(Goods::getGoodsName)));
-
分组合计
collect(Collectors.groupingBy(v -> v, Collectors.summarizingInt(v -> v)))
Map<String, Double> groupedGoods = Stream.of(new Goods("A", 18), new Goods("A", 15), new Goods("B", 5), new Goods("B", 20)).collect(Collectors.groupingBy(Goods::getGoodsType, Collectors.summingDouble(Goods::getPrice)));
-
分区
collect(Collectors.partitioningBy(g -> g.getPrice() > 15))
Map<Boolean, List<Goods>> groupedGoods = Stream.of(new Goods("A", 18), new Goods("A", 15), new Goods("B", 5), new Goods("B", 20)).collect(Collectors.partitioningBy(g -> g.getPrice() > 15));
-
分区合计
collect(Collectors.partitioningBy(g -> g.getPrice() > 15, Collectors.summingDouble(d -> d.get("quantity")))
Map<Boolean, Double> groupedGoods = Stream.of(new Goods("A", 18), new Goods("A", 15), new Goods("B", 5), new Goods("B", 20)).collect(Collectors.partitioningBy(g -> g.getPrice() > 15, , Collectors.summingDouble(Goods::getPrice)));
六、日期时间API
6.1 基本概念
旧版Java中,日期时间API存在的问题:
- 非线程安全
java.util.Date是非线程安全的,所有的日期类都是可变的 - 设计很差
Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。
java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。
另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。 - 时区处理麻烦
Java8在java.time中提供的API:
- Local(本地)
简化了日期时间的处理,没有时区的问题 - Zoned(时区)
通过制定的时区处理日期时间
新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。
6.2 关键类
java.time
包里有许多可以代表时间和日期的类。
-
Instant
类,提供了一个机器视角的时间线。 -
LocalDate
,LocalTime
和LocalDateTime
类提供了人类视角的日期和时间,不涉及到时区。 -
ZoneId
,ZoneRules
和ZoneOffset
类描述了时区,时区偏移量和时区规则。 -
ZonedDateTime
类,代表了与时区关联的时间和日期。OffsetDateTime
和OffsetTime
分别代表了日期和时间和时间。这些类描述了时区偏移。 -
Duration
类在秒和毫秒尺度丈量一段时间。 -
Period
类在年、月和日尺度上丈量一段时间。
6.3 常用的API
-
获取当前日期时间
LocalDate date = LocalDate.now(); LocalTime time = LocalTime.now(); LocalDateTime dateTime = LocalDateTime.now().withNano(0);
-
获取指定日期时间
LocalDate specDatefromString = LocalDate.parse(“2014-12-12”); LocalDate specDate = LocalDate.of(2014, 2, 20); specDate = LocalDate.ofYearDay(2015, 100); specDate = LocalDate.ofEpochDay(200);//自1970年1月1日起200天后的日期
-
获取今天是今年的第几天
int dayOfYear = LocalDate.now().getDayOfYear();
-
当前月的最后一天
LocalDate date = LocalDate.now(); // 不用考虑是28、29、30还是31天 LocalDate lastDayOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());
-
2015年11月第一个周一
LocalDate firstMondayInOneMonth = LocalDate.parse("2015-11-11") .with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
-
日期加减与时间间隔
LocalTime time = LocalTime.now(); // 当前时间加20分钟 LocalTime timeAfterPlus = time.plusMinutes(20); // 当前时间减2小时 LocalTime timeAfterMinus = time.minusHours(2); // 两个时间间隔(单位:分钟),如第二个参数比第一个大,结果为负数 long duration = ChronoUnit.MINUTES.between(time, timeAfterPlus);
-
获取两个日期间的距离
LocalDate date = LocalDate.now(); Period period = Period.between(LocalDate.of(2014, 2, 10), date);
-
日期判断
LocalDate date = LocalDate.now(); LocalDate date1 = LocalDate.now(); // 判断是否相等 boolean isEqual = date.equals(date1); // 判断是否在另一个日期之前 boolean isBefore = date.isBefore(date1); // 判断是否为闰年 boolean isLeapYear = date.isLeapYear();
-
查看时区
// 获得所有时区 Set<String> allZone = ZoneId.getAvailableZoneIds(); ZoneId zone = ZoneId.systemDefault(); // 获得美国时间 ZoneId zoneInUSA = ZoneId.of("America/New_York"); LocalTime timeInUSA = LocalTime.now(zoneInUSA);
-
时间戳转换为日期
Instant second = Instant.ofEpochSecond(1234567890L); DateTimeFormatter formatter = DateTimeFormatter.ofPattern(“yyyy-MM-dd HH:mm:ss”); // 等同于 formatter.format(LocalDateTime.ofInstant(second, ZoneId.systemDefault())); String time = LocalDateTime.ofInstant(second, ZoneId.systemDefault()).format(formatter); // 另一种方法 LocalDateTime now = LocalDateTime.ofEpochSecond(12468312, 0, ZoneOffset.of("+8"));
-
日期转换为时间戳
// 第一种方式 Instant instant1 = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant(); // 第二种方式 OffsetDateTime offsetTime = OffsetDateTime.now(ZoneId.systemDefault()); ZoneOffset offset = offsetTime.getOffset(); Instant instant2 = LocalDateTime.now().toInstant(offset); // 获得绝对秒 long millisecond = instant1.getEpochSecond();