Lambda表达式
概念
Lambda 表达式,也可称为闭包,其本质是一个Java语法糖,可以使匿名方法的代码变的更加简洁紧凑。同时,Lambda 允许把函数作为参数传递进方法中,推动了 Java 中的函数化编程。
语法
// 如果主体包含了一条语句,就不需要使用大括号;主体会将表达式的值作为返回值
() -> exp;
// 不需要声明参数类型
// 一个参数无需定义圆括号
p1 -> exp;
// 多个参数必须要定义圆括号
// 主体包含了多条语句,必须要使用大括号;大括号内需要显式指定返回值
(p1, p2) -> {
exp1;
exp2;
return "xxx";
}
// lambda 表达式只能引用不被修改的外层局部变量,否则会编译错误
int a = 0; // 编译器会自动将该变量视为常量,不可修改。
myFun( () -> a + 2 );
函数式接口
- 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法(静态方法或者默认方法)的接口。
- 函数式接口使用
@FunctionalInterface 注解。 - 函数式接口可以被隐式转换为 lambda 表达式,即可以作为参数直接传递给方法。
- 从 Java 8 开始,很多之前的接口,都被调整成函数式接口;也新增了很多新的函数式接口。
变成函数式接口的老接口
- Runnable
- Callable
- PrivilegedAction
- Comparator
- FileFilter
- PathMatcher
- InvocationHandler
- PropertyChangeListener
- ActionListener
- ChangeListener
Consumer接口
Consumer接口是消费性接口,对类型为T的对象应用操作,无返回值。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
Supplier接口
Supplier接口是供给型接口,无输入,返回类型为T的对象。
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Predicate接口
Predicate接口是断言型接口,确定类型为T的对象是否满足约束条件,返回值类型为boolean。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
Function接口
Function接口是函数型接口,对类型为T的对象应用操作,并返回R类型的返回结果。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
新增的其他函数式接口
- BiFunction(T, U, R):对类型为T,U的参数应用操作,返回R类型的结果。
- UnaryOperator(Function子接口):对类型为T的对象进行一 元运算, 并返回T类型的 结果。
- BinaryOperator (BiFunction 子接口):对类型为T的对象进行二 元运算, 并返回T类型的 结果。
- BiConsumer<T, U>:对类型为T, U 参数应用 操作。
- ToIntFunction、ToLongFunction、ToDoubleFunction:计算 int 等类型值的函数
- IntFunction、LongFunction、DoubleFunction:参数为 int 等类型的函数
方法引用
方法引用只是简化Lambda表达式的一种手段,是用来直接访问类或者实例的已经存在的方法或者构造方法。
方法引用提供了一种引用而不执行方法的方式,会创建函数式接口的一个实例。
语法:
// 等价于:
// Arrays.sort(stringsArray,(s1,s2)->s1.compareToIgnoreCase(s2));
// 其中,Lambda表达式 (s1,s2)->s1.compareToIgnoreCase(s2) 只是执行一个已有方法的调用,可以使用方法引用来简化表达
Arrays.sort(stringsArray, String::compareToIgnoreCase);
// 静态方法引用
ClassName::staticMethodName
// 特定实例对象的方法引用
instance::methodName
// 类型上的实例方法引用
TypeName::methodName
// 构造方法的引用
ClassName::new
TypeName[]::new
Stream API
概念
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作,也可以并行执行操作。 它是一个函数式语言+多核时代综合影响的产物。
使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询,本质上是实现一个filter-map-reduce过程,是一种简洁、高效的集合数据处理方式。
特性
- Stream 不是数据结构,自己不会存储元素。
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream 可以是无限的。集合有固定大小,Stream 则不必。limit(n)和findFirst()这类的short-circuiting操作可以对无限的 Stream 进行运算并很快完成。
- Stream 不支持索引访问。你可以请求第一个元素,但无法请求第二个,第三个,或最后一个。
- 所有Stream的操作必须以lambda表达式为参数。
- 当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行,例如:多个转换操作只会在Terminal操作的时候融合起来,一次循环完成。
操作步骤
Stream 的操作分为三个步骤
- 创建 Stream:一个数据源 (如 : 集合、数组), 获取一个流
- 中间操作(intermediate操作):多个个中间操作构成一个中间操作链,对数据源的数据进行处理
- 终止操作(Terminal操作):一个终止操作,执行中间操作链,并产生结果
并行流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。 Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。
创建 Stream
1)Java8 中的 Collection 接口被扩展,提供两个获取流的方法 :
default Stream<E> stream() : 返回一个顺序流default Stream<E> parallelStream() : 返回一个并行流
2)Java8 中的 Arrays 的静态方法 stream() 可以获取数组流 :
static <T> Stream<T> stream(T[] array) : 返回一个流
3)可以使用静态方法 Stream.of(), 通过显示值创建一个流,它可以接收任意数量的参数:
public static<T> Stream<T> of(T... values) : 返回一个流
4)可以提供函数参数,使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流:
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) public static<T> Stream<T> generate(Supplier<T> s)
示例:
// Collection 提供了两个方法 stream() 与 parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); // 获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); // 获取一个并行流
// 通过 Arrays 中的 stream() 获取一个数组流
Stream<Integer> stream1 = Arrays.stream ( new Integer[ 10 ]);
IntStream stream2 = Arrays.stream ( new int[ 10 ]);
// 通过 Stream 类中静态方法 of()
Stream<Integer> stream3 = Stream.of ( 1 , 2 , 3 , 4 , 5 , 6 );
// 创建无限流
// 迭代
Stream<Integer> stream3 = Stream.iterate ( 0 , (x) -> x + 2 ).limit( 10 );
// 生成
Stream<Double> stream4 = Stream.generate (Math:: random ).limit( 2 );
中间操作
惰性求值
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全部处理,称为“惰性求值”。
// 如下代码,只是定义stream,所有的中间操作不会做任何的处理
Stream<Employee> stream = emps.stream().filter((e) -> e.getAge() <= 35 );
// 如下代码,开始做终止操作时,所有的中间操作会一次性的全部执行,称为“惰性求值”
stream.forEach(System.out ::println);
筛选与切片
- filter(Predicate p):接收 Lambda , 从流中排除某些元素
- distinct():筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
- limit(long maxSize):截断流,使其元素不超过给定数量
- skip(long n):跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流
映射
- map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
- mapToDouble(ToDoubleFunction f)、mapToInt(ToIntFunction f)、mapToLong(ToLongFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream 等
- flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
示例:
// 使用map()来转换为大写
List<String> strList = Arrays.asList ( "aaa" , "bbb" , "ccc" , "ddd" , "eee" );
Stream<String> stream1 = strList.stream().map(String::toUpperCase);
stream1.forEach(System.out ::println);
// 使用flatMap()来把多个单词拆分成字母序列
public static Stream<Character> filterCharacter(String str) {
// 该函数将单个单词拆分成字母流
List<Character> list = new ArrayList<>();
for (Character ch : str.toCharArray()) {
list.add(ch);
}
return list.stream();
}
Stream<Character> stream3 = strList.stream().flatMap((str) -> filterCharacter(str));
stream3.forEach(System.out::println);
// 使用map()来转换为平方数
Integer[] nums = new Integer[]{ 1 , 2 , 3 , 4 , 5 };
Arrays.stream (nums).map((x) -> x * x).forEach(System.out ::println);
排序
- sorted():产生一个新流,其中按自然顺序排序
- sorted(Comparator comp):产生一个新流,其中按比较器顺序排序
示例:
List<Integer> list = new ArrayList<>();
list.stream().sorted((x, y) -> {
return x > y;
}).forEach(System.out ::println);
终止操作
终止操作后流不能再使用
终端操作会从流的流水线生成结果,其结果可以是任何不是流的值,例如 : List、 Integer,甚至是 void。故,流进行了终止操作后,不能再次使用。
// 定义stream
Stream<Employee> stream = emps.stream().filter((e) -> e.getAge() <= 35 );
// 执行终止操作
stream.forEach(System.out ::println);
// 流已经不可再使用,如下代码会报错!
// stream.limit(5).count();
Optional 类
Optional 仅仅是一个包装类:存放T类型的值或者null,它提供了一些有用的接口来避免显式的null检查。
- Optional.of(T t) : 创建一个 Optional 实例
- isPresent() : 判断是否包含值
- orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
- Optional.empty() : 创建一个空的 Optional 实例
- Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
- map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
详见:https://www.jianshu.com/p/d81a5f7c9c4e
查找与匹配
- allMatch(Predicate p):检查是否匹配所有元素
- anyMatch(Predicate p):检查是否至少匹配一个元素
- noneMatch(Predicate p):检查是否没有匹配所有元素
- findFirst():返回第一个元素
- findAny():返回当前流中的任意元素
- count():返回流中元素总数
- max(Comparator c):返回流中最大值
- min(Comparator c):返回流中最小值
- forEach(Consumer c):内部迭代(与之相对的:使用 Collection 接口需要用户去做迭代,称为外部迭代。)
归约
- reduce(T iden, BinaryOperator b):可以将流中元素反复结合起来,得到一个值,返回 T
- reduce(BinaryOperator b):可以将流中元素反复结合起来,得到一个值,返回 Optional<T>
示例:
// 将数组的所有值相加求和
List<Integer> list = Arrays.asList ( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 );
Integer sum = list.stream().reduce( 0 , (x, y) -> x + y);
收集
- collect(Collector c):将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
示例:
// Collector 接口中方法的实现决定如何对流执行收集操作(如收集到 List、 Set、 Map)
List<String> list = emps.stream().map(Employee::getName).collect(Collectors.toList ());
Set<String> set = emps.stream().map(Employee::getName).collect(Collectors.toSet ());
HashSet<String> hs = emps.stream().map(Employee::getName).collect(Collectors.toCollection (HashSet:: new ));
Optional<Double> max = emps.stream().map(Employee::getSalary).collect(Collectors.maxBy (Double:: compare ));
Optional<Employee> min = emps.stream().collect(Collectors.minBy ((e1, e2) -> Double.compare (e1.getSalary(), e2.getSalary())));
Double sum = emps.stream().collect(Collectors.summingDouble (Employee::getSalary));
Double avg = emps.stream().collect(Collectors.averagingDouble (Employee::getSalary));
Long count = emps.stream().collect(Collectors.counting ());
DoubleSummaryStatistics dss = emps.stream().collect(Collectors.summarizingDouble (Employee::getSalary)); // 收集统计值,DoubleSummaryStatistics中含有各种统计值,如总和、最小值、最大值、平均值等
Map<Status, List<Employee>> map = emps.stream().collect(Collectors.groupingBy (Employee::getStatus)); // 根据某属性值对流分组,得到以属性值为key的map
Map<Status, Map<String, List<Employee>>> map = emps.stream().collect(Collectors.groupingBy (Employee::getStatus, Collectors.groupingBy ((e) -> {
if (e.getAge() >= 60 ) {
return "老年" ;
} else if (e.getAge() >= 35 ) {
return "中年" ;
} else {
return "成年" ;
}
}))); // 多级分组
Map<Boolean,List<Emp>> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage)); // 根据true或false进行分区
String str = emps.stream().map(Employee::getName).collect(Collectors.joining ( "," , "----" , "----" )); // 连接流中每个字符串,其参数均为可选,依次为:拼接字符,前缀,后缀
Optional<Double> sum = emps.stream().map(Employee::getSalary).collect(Collectors.reducing (Double:: sum )); // 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值
int total = list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
int listSize = list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)); // 包裹另一个收集器,对其结果调用转换函数进行处理
其他语言新特性
接口支持默认方法和静态方法
所谓的接口默认方法,就是指接口中可以使用 default 关键字定义非抽象方法,来为该方法提供默认实现,从而实现类可以使用默认实现也可以覆盖该方法。
接口可以实现静态方法。静态方法是接口的一部分,可以通过接口名来调用,但是不能被实现类进行覆盖。
这个特性对重构很友好:修改接口后不需要大范围的修改以前老的实现类。
举例:
public interface MyInterface {
// 静态变量
static final String staticVar = "";
// 静态方法
static String myStaticFun2() {
return staticVar;
}
// 默认方法
default String introduce() {
return "";
}
}
多个同名默认方法
一个类实现了多个接口,且这些接口有同名的默认方法。可以使用 super 来调用指定接口的默认方法:InterfaceName.super.methodName();
支持重复注解,增加注解的使用场景
Java 8 之前,注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。Java 8打破了这个限制,引入了重复注解的概念,允许在同一个地方多次使用同一个注解:使用 @Repeatable 定义重复注解。
ElementType.TYPE_USER 和 ElementType.TYPE_PARAMETER 是 Java 8 新增的两个注解,用于描述注解的使用场景。 Java 8 新增了很多注解的使用场景,具体支持哪些使用场景,建议查看 ElementType 这个类。
其他官方类库新特性
时间API
LocalDate、 LocalTime、 LocalDateTime
LocalDate、 LocalTime、 LocalDateTime 类的实例是不可变的对象(每次都需要创建新的实例),分别表示使用 ISO-8601日历系统的日期、时间、日期和时间,也不包含与时区相关的信息。
// 获取当前系统时间
LocalDateTime localDateTime1 = LocalDateTime.now();
// 指定日期时间
LocalDateTime localDateTime2 = LocalDateTime.of(2019, 10, 27, 13, 45,10);
// 对日期进行加减
LocalDateTime localDateTime3 = localDateTime1.plusYears(3).minusMonths(3);
// 获取日期的年月日
localDateTime1.getYear();
localDateTime1.getMonthValue();
localDateTime1.getDayOfMonth();
localDateTime1.getHour();
localDateTime1.getMinute();
localDateTime1.getSecond();
Instant
用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的秒数进行运算。
// 默认获取UTC时区
Instant instant1 = Instant.now();
// 偏移量运算
OffsetDateTime offsetDateTime = instant1.atOffset(ZoneOffset.ofHours(8));
ZonedDateTime zonedDateTime = instant1.atZone(ZoneId.systemDefault());
// 获取时间戳
instant1.toEpochMilli();
instant1.getEpochSecond();
// 以Unix元年为起点,进行偏移量运算
Instant instant2 = Instant.ofEpochMilli(1584700633222L);
Instant instant3 = Instant.ofEpochSecond(60);
DateTimeFormatter
对日期进行解析与格式化。
// 预定义的标准格式,如下示例为yyyy-MM-dd
DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ISO_DATE;
LocalDateTime localDateTime = LocalDateTime.now();
String strDate1 = localDateTime.format(dateTimeFormatter1);
// 自定义的格式
DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String strDate2 = dateTimeFormatter2.format(localDateTime);
// 解析日期
LocalDateTime localDateTime1 = localDateTime.parse(strDate2, dateTimeFormatter2);
其他
- Duration:用于计算两个“时间”间隔。
- Period:用于计算两个“日期”间隔 。
- TemporalAdjuster:时间校正器。例如:将日期调整到“下个周日”等操作。
- ZonedDate、 ZonedTime、 ZonedDateTime:带时区的时间。
- ZoneId:该类中包含了所有的时区信息。
- Java 8之前的日期处理的“遗留类”,也新添加了方法来支持与新的时间API进行转换。例如:
Date.from(instant); date.toInstant(); Date.valueOf(loacalDate);
Base64
// Base64编码
Base64.getEncoder().encodeToString(bytes);
// Base64解码
Base64.getDecoder().decode(str);
// URL编解码
Base64.getUrlEncoder() / Base64.getUrlDecoder()
// MINE编解码
Base64.getMimeEncoder() / Base64.getMimeDecoder()
数组并发处理
Java8版本新增了很多新的方法,用于支持并行数组处理。
比如 Arrays.parallelSort( array ),可以显著加快多核机器上的大数组排序。 因为如果数组大小小于或等于 8192,或者处理器只有一个核心,它将使用顺序的 Dual-Pivot Quicksort 算法,故此时性能并没有优势。
其他并发支持
Java 8 在并发方面做了一些改进:
- 为 ConcurrentHashMap 类添加了新的方法来支持聚焦操作;
- 为 ConcurrentForkJoinPool 类添加了新的方法来支持通用线程池操作;
- 添加了新的 StampedLock 类,用于支持基于容量的锁,是 ReadWriteLock 的替代者;
- java.util.concurrent.atomic 包中也新增了不少工具类,如:DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder。
Nashorn JavaScript引擎
Java 8 提供了新的Nashorn JavaScript引擎,使得我们可以在JVM上开发和运行JS应用,允许Java和JavaScript交互使用。 注:Nashorn JavaScript Engine 在 Java 15 已经不可用了。
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");
String js = "10 + 2";
Integer result = (Integer) nashorn.eval( js );
其他命令行工具/编译器等新特性
Nashorn引擎:jjs
jjs是一个基于标准Nashorn引擎的命令行工具,可以接受js源码文件并执行:jjs func.js 。
类依赖分析器:jdeps
jdeps可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台。 例如:jdeps xxx.class ,jdeps xxx.jar
获取方法的参数名称:-parameters参数
为了在运行时获得Java程序中方法的参数名称,Java 8 在语言层面(使用反射API和Parameter.getName()方法)和字节码层面(使用新的javac编译器以及 -parameters 参数)提供支持。
在 Java 8 中这个特性是默认关闭的,可以在pom文件中通过 maven-compiler-plugin 来进行配置开启。
JVM的新特性
- 使用Metaspace(JEP 122)代替持久代(PermGen space);
- JVM参数,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原来的-XX:PermSize和-XX:MaxPermSize;
|