JDK13于昨天正式GA,版本新特性可参考: https://www.oschina.net/news/109934/jdk-13-released

虽然JDK更新迅速,但开发者貌似并不买账,据统计,目前仍以JDK8使用最多,预计可能还会延续好长一段时间。虽然JDK版本已至13,但对Java8的新特性,掌握程度如何呢?
本文对Java8的主要特性进行了梳理。供温习参考。

1. 接口默认方法

以前的接口只允许有抽象方法(没有实现体),java8中提供了接口默认方法支持,即可以提供方法的默认实现,实现类可以直接继承,也可以覆盖。默认方法主要解决接口的修改导致现有实现类不兼容的问题。

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
@RunWith(SpringRunner.class)
@SpringBootTest
public class InterfaceDefaultFunctionTest {

public interface MyFunction<T> {
T func(T t);

//默认方法
default int func2(T t){
return t.hashCode();
}
//静态方法
static<T> void print(T t) {
System.out.println(t);
}
}

@Test
public void testInterface(){
MyFunction<String> myFunction = new MyFunction<String>(){
@Override
public String func(String s) {
return s.toUpperCase();
}
};
System.out.println(myFunction.func("abc"));
System.out.println(myFunction.func2("abc"));
LambdaTest.MyFunction.print("efg");
}
}

默认方法通过关键字 default 声明。同时也可以在接口中定义静态方法。

2. 函数式接口

函数式接口就是有且仅有一个抽象方法的接口(可以有其它非抽象方法),如1所示代码中 MyFunction 就是一个函数式接口,只有一个抽象方法 func, 其它非抽象方法如默认方法 func2, 静态方法 print 不影响其函数式接口的特性。

函数式接口可以使用注解 @FunctionalInterface 标注,该注解会去检查接口是否符合函数式接口的规范要求,不符合的话IDE会给出提示。

java中内置了一些函数式接口,

函数式接口 描述
Consumer 包含方法 void accept(T t), 对类型为T的对象t进行操作
Supplier 包含方法 T get(),返回类型为T的对象
Function<T,R> 包含方法 R apply(T t),对类型为T的对象进行操作,返回类型R的对象
Predicat 包含方法 boolean test(T t), 判断类型为T的对象是否满足条件

以及基于这些接口的其它变种或子接口,如BiConsumer<T,U>,BiFunction<T,U,R>等。还有如Runnable,Callable等接口,也属于函数式接口 —— 都只有一个抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);

default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
Objects.requireNonNull(after);

return (l, r) -> {
accept(l, r);
after.accept(l, r);
};
}
}

3. Lambda表达式

lambda表达式实质就是一个匿名函数,在python中很常见,java到了jdk8提供了支持。
lambda表达式的格式形如: (参数) -> {方法体语句},当参数只有一个时,左边小括号可以省略,当方法体语句只有一条时,右边大括号可以省略。

Java的lambda表达式基本上是对函数式接口实现的一种简化 —— 用lambda表达式直接代替一个函数式接口的具体实现(抽象方法的实现)。当我们使用jdk8在IDE中编写1中代码时,IDE会给出提示,
lambda-warn

匿名实现类可以用lambda表达式替换。上述代码使用lambda表达式替换可调整为,

1
2
3
4
5
6
@Test
public void testInterface(){
MyFunction<String> myFunction = s -> s.toUpperCase();
System.out.println(myFunction.func("abc"));
System.out.println(myFunction.func2("abc"));
}

lambda表达式甚至可作为方法参数传入(实质也是作为一个函数式接口的实现类实例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@FunctionalInterface
public interface MyFunction<T> {
T func(T t);
}

public void print(MyFunction<String> function, String s){
System.out.println(function.func(s));
}

@Test
public void testInterface(){
//将lambda表达式作为方法参数传入
print((String s) -> s.toUpperCase(), "abc");
}

局部变量在lambda表达式中是只读的,虽可不声明为final,但无法修改。如

1
2
3
4
5
6
@Test
public void testInterface(){
int i = 1;
//lambda表达式中无法修改局部变量i,将报编译错误
print((String s) -> {i = i+10; return s.toUpperCase();}, "abc");
}

4. 方法引用

当需要使用lambda表达式时,如果已经有了相同的实现方法,则可以使用方法引用来替代lambda表达式,几种场景示例如下。

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
@RunWith(SpringRunner.class)
@SpringBootTest
public class FunctionReferenceTest {

@Test
public void testFunctionReference() {
// 实例::实例方法
Consumer<String> consumer = s -> System.out.println(s); //lambda表达式
Consumer<String> consumer2 = System.out::println; //方法引用
consumer.accept("abc");
consumer2.accept("abc");

//类::静态方法
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y); //lambda表达式
Comparator<Integer> comparator2 = Integer::compare; //方法引用
System.out.println(comparator.compare(10, 8));
System.out.println(comparator2.compare(10, 8));

//类::实例方法, 当引用方法是形如 a.func(b)时,用类::实例方法的形式
BiPredicate<String, String> biPredicate = (a, b) -> a.equals(b); //lambda表达式
BiPredicate<String, String> biPredicate2 = String::equals; //方法引用
System.out.println(biPredicate.test("abc", "abb"));
System.out.println(biPredicate2.test("abc","abb"));

//type[]::new 数组引用
Function<Integer,Integer[]> fun= n-> new Integer[n]; //lambda表达式
Function<Integer,Integer[]> fun2=Integer[]::new; //方法引用
System.out.println(fun.apply(10));
System.out.println(fun2.apply(10));

//构造器引用
Function<String,String> func = n-> new String(n); //lambda表达式
Function<String,String> func2 = String::new; //方法引用
System.out.println(func.apply("aaa"));
System.out.println(func2.apply("aaa"));
}
}

5. Stream API

Stream与lambda应该是java8最重要的两大特性。Stream 对集合的处理进行了抽象,可以对集合进行非常复杂的查找、过滤和映射等操作。提供了一种高效的且易于使用的处理数据的方式。
Stream的三个特性:

  • Stream本身不会存储元素
  • Stream不会改变操作对象(即集合),会返回一个新的Stream
  • Stream的中间操作不会立刻执行,而是会等到需要结果的时候才执行

Java8 的Collection接口包含了两个方法 stream(), parallelStream(), 分别返回一个顺序流与一个并行流,所有Collection类型(如List, )的对象可以调用这两个方法生成流。
Java8 的Arrays类也提供了 stream(T[] array)等方法用以生成流。也可以使用静态方法 Stream.iterate() 和 Stream.generate() 来创建无限流。

Stream的中间操作包括

操作 描述
filter(Predicate p) 接收 Lambda , 从流中过滤出满足条件的元素
distinct() 通过hashCode() 和 equals() 去除重复元素
limit(long maxSize) 截断流,使元素的个数不超过给定数量
skip(long n) 跳过前面的n个元素,若流中元素不足n个,则返回一个空流
map(Function f) 将每个元素使用函数f执行,将其映射成一个新的元素
mapToDouble(ToDoubleFunction f) 将每个元素使用f执行,产生一个新的DoubleStream流
mapToInt(ToIntFunction f) 将每个元素使用f执行,产生一个新的IntStream流
mapToLong(ToLongFunction f) 将每个元素使用f执行,产生一个新的LongStream流
flatMap(Function f) 将流中的每个值都通过f转换成另一个流,然后把所有流连接成一个流
sorted() 按自然顺序排序,产生一个新流
sorted(Comparator comp) 根据比较器排序,产生一个新流
allMatch(Predicate p) 判断是否匹配所有元素
anyMatch(Predicate p) 判断是否匹配至少一个元素
noneMatch(Predicate p) 判断是否没有匹配任意元素
findFirst() 返回第一个元素
findAny() 返回任意一个元素
reduce(T iden, BinaryOperator b) 对流中的元素进行reduce操作,返回T类型对象
reduce(BinaryOperator b) 对流中的元素进行reduce操作,返回Optional对象

Stream的终止操作包括

操作 描述
count() 返回元素总数
max(Comparator c) 返回最大值
min(Comparator c) 返回最小值
forEach(Consumer c) 内部迭代调用Consumer操作
collect(Collector c) 将流转换为其他形式,一般通过Collectors来实现

Stream使用示例

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
@Test
public void testStream() {
List<User> list = new ArrayList<>();
//转换为List,这里没啥意义,仅做示范
List<User> users = list.stream().collect(Collectors.toList());
//转换为Set
Set<User> users1 = list.stream().collect(Collectors.toSet());
//转换为Collection
Collection<User> users2 = list.stream().collect(Collectors.toCollection(ArrayList::new));
//计数
long count = list.stream().collect(Collectors.counting());
//求和
int total = list.stream().collect(Collectors.summingInt(User::getAge));
//求平均值
double avg= list.stream().collect(Collectors.averagingInt(User::getAge));
//获取统计对象,通过该统计对象可获取最大值,最小值之类的数据
IntSummaryStatistics iss= list.stream().collect(Collectors.summarizingInt(User::getAge));
//将值通过","拼接
String str= list.stream().map(User::getName).collect(Collectors.joining(","));
//最大值
Optional<User> max= list.stream().collect(Collectors.maxBy(Comparator.comparingInt(User::getAge)));
//最小值
Optional<User> min = list.stream().collect(Collectors.minBy(Comparator.comparingInt(User::getAge)));
//从累加器开始,对指定的值,这里是年龄,进行sum的reduce操作
int t =list.stream().collect(Collectors.reducing(0, User::getAge, Integer::sum));
//对转换的结果再进行处理
int how = list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
//分组
Map<String, List<User>> map= list.stream().collect(Collectors.groupingBy(User::getName));
//根据条件进行分区
Map<Boolean,List<User>> vd= list.stream().collect(Collectors.partitioningBy(u -> u.getName().startsWith("W")));

}

6. Optional类

Optional是一个容器类,可以避免显式的null判断,基本使用示例如下

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
@RunWith(SpringRunner.class)
@SpringBootTest
public class OptionalTest {

@Test
public void testOptional(){
// of 不允许传入null值,否则抛出NPE
Optional<Integer> optional = Optional.of(new Integer(10));
System.out.println(optional.get());

// ofNullable 允许传入null,但是直接调用get会抛出NoSuchElementException异常,
// 可通过isPresent判断是否存在值
Optional<Integer> optional1 = Optional.ofNullable(null);
if(optional1.isPresent()) {
System.out.println(optional1.get());
}else{
System.out.println("optional1 is empty");
}
// orElse 判断是否存在值,存在则返回,不存在则返回参数里的值
Integer value = optional1.orElse(new Integer(0));

// map方法,如果optional有值,则对值进行处理返回新的Optional,
// 如果没有值则返回Optional.empty()
optional = optional.map(x -> x*x);
System.out.println(optional.get());

// 与map类似,只是要求返回值必须是Optional,进一步避免空指针
optional = optional.flatMap(x ->Optional.of(x*x));
System.out.println(optional.get());

}
}

7. Base64

在java8中,Base64成为了java类库的标准,可直接使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.Base64;

@RunWith(SpringRunner.class)
@SpringBootTest
public class Base64Test {

@Test
public void testBase64(){
//base64编码
String encode = Base64.getEncoder().encodeToString("abc".getBytes());
System.out.println(encode);
//base64解码
System.out.println(new String(Base64.getDecoder().decode(encode)));
}
}

8. 日期时间类

以前的Date类是非线程安全的,并且一些常用的日期时间运算需要自己编写util工具类。java8推出了java.time包,里面包含了如 LocalDate, LocalTime, LocalDateTime等类,可方便地进行日期时间的运算,如日期间隔、时间间隔,日期时间的加减,格式化等等。

—————————————————————————————
作者:空山新雨
欢迎关注我的微信公众号:jboost-ksxy
微信公众号