一、在接口中提供默认的方法实现(有点像抽象类)
在jdk1.8里面,不仅可以定义接口,还可以在接口中提供默认的实现。这一个小小的改变却让整个抽象设计都随着改变了!
二、Lambda表达式
Lambda 表达式(是一种匿名函数)。它是推动 Java 8 发布的最重要新特性。是继泛型和注解以来最大的变化。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。让 java 也能支持简单的函数式编程。Lambda允许把函数作为一个方法的参数(传递进方法中)
从一段熟悉的排序例子入手:
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
Collections 工具类提供了静态方法 sort 方法,入参是一个 List 集合,和一个 Comparator 比较器,以便对给定的 List 集合进行排序。上面的示例代码创建了一个匿名内部类作为入参,这种类似的操作在我们日常的工作中随处可见。
Java 8 推荐使用 Lambda 表达:
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
更加简单优秀的写法:
Collections.sort(names, (String a, String b) -> b.compareTo(a));
再短点(如果你的实现不是一行代码,那么不能这么干):
names.sort((a, b) -> b.compareTo(a));
三、函数式接口(Functional Interface)
并不是每个接口都可以缩写成Lambda表达式的开发方式。其实是只有那些函数式接口才能缩写成 Lambda 表示式。(函数式接口可以被隐式转换为lambda表达式)
所谓函数式接口就是只包含一个抽象方法的声明(但可以有多个非抽象方法的接口)。针对该接口类型的所有 Lambda 表达式都会与这个抽象方法匹配。(另外,只是在接口上添加default并不算抽象方法)
总结:为了保证一个接口明确的被定义为一个函数式接口,我们需要为该接口添加注解:@FunctionalInterface。这样,一旦你添加了第二个抽象方法,编译器会立刻抛出错误提示。(不填写,但是只写一个default也可以)
//定义含有注解@FunctionalInterface的接口
@FunctionalInterface
public interface IConverter<F, T> {
T convert(F from);
}
1、先来一个传统方式:
IConverter<String, Integer> converter01 = new IConverter<String, Integer>() {
@Override
public Integer convert(String from) {
return Integer.valueOf(from);
}
2、稍微简化下,只有一个参数括号可以不要:
IConverter<String, Integer> converter02 = (from) -> {
return Integer.valueOf(from);
};
3、继续简化,因为它的实现只有一行代码,可以更简短:
IConverter<String, Integer> converter03 = from -> Integer.valueOf(from);
4、还能短点,其实这个另类属于下一段的内容了,先放这有个印象
IConverter<Integer, String> converter04 = String::valueOf;
四、方法和构造函数的便捷应用
五、Lambda作用范围
Lambda表达式访问外部的变量(局部变量,成员变量,静态变量,接口的默认方法),它与匿名内部类访问外部变量非常相似。
六、内置的函数式接口
JDK 1.8 API 包含了很多内置的函数式接口。其中就包括我们在老版本中经常见到的 Comparator 和 Runnable,Java 8 为他们都添加了 @FunctionalInterface 注解,以用来支持 Lambda 表达式。
例如我们旧版本的Jdk中常用的 Comparator 和 Runnable 外,还有一些新的函数式接口,可以通过函数注解实现Lamdba支持,它们很多都借鉴于知名的 Google Guava库。
七、Optionals
Optional不是一个函数式接口,设计它的目的是为了防止空指针异常(NullPointerException)。
当你定义了一个方法,这个方法返回的对象可能是空,也有可能非空的时候,你就可以考虑用 Optional 来包装它,这也是在 Java 8 被推荐使用的做法。
@Test
public void test16(){
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
Optional<Person> optionalPerson = Optional.of(new Person());
optionalPerson.ifPresent(s -> System.out.println(s.firstName));
}
八、Stream流
我们可以使用 java.util.Stream 对一个包含一个或多个元素的集合做各种操作(只能对实现了 java.util.Collection 接口的类做流的操作)。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
Stream 流支持同步执行,也支持并发执行(注意:Map不支持Stream流,但是它的key和value是支持的!)。在 Java 8 中,集合接口有两个方法来生成流:
- stream() − 为集合创建串行流。
- parallelStream() − 为集合创建并行流。
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
1、forEach 迭代
Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
2、Filter 过滤
filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();
3、Sorted 排序
Sorted 用于对流进行排序。是一个中间操作,它的返参是一个 Stream 流。另外,我们可以传入一个 Comparator 用来自定义排序,如果不传,则使用默认的排序规则。
@Test
public void test18() {
stringCollection.stream().sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
}
注意:这个sorted 只是做了一个排序的视图进行输出,实际没有将List内的数据进行排序
System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
4、Map 转换
map 方法用于映射每个元素到对应的结果。如下,通过 map 我们将每一个 string 转成大写:
@Test
public void test19(){
stringCollection
.stream()
.map(String::toUpperCase)
.sorted(Comparator.reverseOrder()) //等同于(a, b) -> b.compareTo(a)
.forEach(System.out::println);
}
这个可以用做DTO数据对象转换,领域驱动设计开发中将DTO转为DO向后台传输。
5、Match 匹配
match 用来做匹配操作,它的返回值是一个 boolean 类型。通过 match, 我们可以方便的验证一个 list 中是否存在某个类型的元素。
@Test
public void test20(){
// anyMatch:验证 list 中 string 是否有以 a 开头的, 匹配到第一个,即返回 true
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
// allMatch:验证 list 中 string 是否都是以 a 开头的
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
// noneMatch:验证 list 中 string 是否都不是以 z 开头的
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
}
6、limit 限制
limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
7、Count 计数
count 是一个终端操作,它能够统计 stream 流中的元素总数,返回值是 long 类型。
@Test
public void test21() {
// count:先对 list 中字符串开头为 b 进行过滤,让后统计数量
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
}
8、Reduce
Reduce 中文翻译为:减少、缩小。通过入参的 Function,我们能够将 list 归约成一个值。它的返回类型是 Optional 类型。
@Test
public void test22() {
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
// aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2
}
九、Parallel-Streams 并行流
流可以是顺序的,也可以是并行的。顺序流上的操作在单个线程上执行,而并行流上的操作在多个线程上并发执行。
下面的示例演示了使用并行流来提高性能是多么的容易。亲测提升了1倍性能!
首先,我们创建一个较大的List:
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
1、Sequential Sort 顺序流排序
@Test
public void test23() {
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
// 纳秒
long t0 = System.nanoTime();
long count = values.stream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
// 纳秒转微秒
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("顺序流排序耗时: %d ms", millis));
//顺序流排序耗时: 712 ms
}
2、Parallel Sort 并行流排序
@Test
public void test24(){
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
long t0 = System.nanoTime();
long count = values.parallelStream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));
//parallel sort took: 385 ms
}
这两个代码片段几乎相同,但并行排序大约快50%。你只需将stream()更改为parallelStream()。
十、Map 集合
Map是不支持 Stream 流的,因为 Map 接口并没有像 Collection 接口那样,定义了 stream() 方法。但是,我们可以对其 key,values,entry 使用流操作,如 map.keySet().stream(),map.values().stream() 和 map.entrySet().stream().
另外,JDK 8 中对 map 提供了一些其他新特性:
@Test
public void test25() {
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
// 与老版不同的是,putIfAbent() 方法在 put 之前, 不用在写if null continue了
// 会判断 key 是否已经存在,存在则直接返回 value, 否则 put, 再返回 value
map.putIfAbsent(i, "val" + i);
}
// forEach 可以很方便地对 map 进行遍历操作
map.forEach((key, value) -> System.out.println(value));
}
之后我们做一个Map对象的转换输出;(定义两个类BeanA、BeanB)
@Test
public void test26() {
Map<Integer, BeanA> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
// 与老版不同的是,putIfAbent() 方法在 put 之前,不用在写if null continue了
// 会判断 key 是否已经存在,存在则直接返回 value, 否则 put, 再返回 value
map.putIfAbsent(i, new BeanA(i, "明明" + i, i + 20, "89021839021830912809" + i));
}
Stream<BeanB> beanBStream00 = map.values().stream().map(new Function<BeanA, BeanB>() {
@Override
public BeanB apply(BeanA beanA) {
return new BeanB(beanA.getName(), beanA.getAge());
}
});
Stream<BeanB> beanBStream01 = map.values().stream().map(beanA -> new BeanB(beanA.getName(), beanA.getAge()));
beanBStream01.forEach(System.out::println);
}
除了上面的 putIfAbsent() 和 forEach() 外,我们还可以很方便地对某个 key 的值做相关操作:
@Test
public void test27() {
// 如下:对 key 为 3 的值,内部会先判断值是否存在,存在,则做 value + key 的拼接操作
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3); // val33
// 先判断 key 为 9 的元素是否存在,存在,则做删除操作
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9); // false
// computeIfAbsent(), 当 key 不存在时,才会做相关处理
// 如下:先判断 key 为 23 的元素是否存在,不存在,则添加
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true
// 先判断 key 为 3 的元素是否存在,存在,则不做任何处理
map.computeIfAbsent(3, num -> "bam");
map.get(3); // val33
}
关于删除操作,JDK 8 中提供了能够新的 remove() API:
@Test
public void test28() {
map.remove(3, "val3");
map.get(3); // val33
map.remove(3, "val33");
map.get(3); // null
}
如上代码,只有当给定的 key 和 value 完全匹配时,才会执行删除操作。
关于添加方法,JDK 8 中提供了带有默认值的 getOrDefault() 方法:
@Test
public void test29() {
// 若 key 42 不存在,则返回 not found
map.getOrDefault(42, "not found"); // not found
}
对于 value 的合并操作也变得更加简单:
@Test
public void test30() {
// merge 方法,会先判断进行合并的 key 是否存在,不存在,则会添加元素
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9); // val9
// 若 key 的元素存在,则对 value 执行拼接操作
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9); // val9concat
}
十一、日期 Date API
- Clock
- Timezones时区
- LocalTime
- LocalDate
- LocalDateTime
十二、Annotations 注解
Java8中的注释是可重复的。让我们直接深入到一个例子中来解决这个问题。{在SpringBoot的启动类中就可以看到这中类型的注解}
首先,我们定义一个包装器注释,它包含一个实际注释数组:
@Repeatable(Hints.class)
public @interface Hint {
String value();
}
public @interface Hints {
Hint[] value();
}
Java 8通过声明注释@Repeatable,使我们能够使用同一类型的多个注释。
第一种形态:使用注解容器(老方法)
@Test
public void test40() {
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {
}
}
第二种形态:使用可重复注解(新方法)
@Test
public void test41() {
@Hint("hint1")
@Hint("hint2")
class Person {
}
}
java编译器使用变量2隐式地在引擎盖下设置@Hints注释。这对于通过反射读取注释信息很重要。
@Test
public void test41() {
@Hint("hint1")
@Hint("hint2")
class Person {
}
Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint); // null
Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length); // 2
Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class
System.out.println(hints2.length); // 2
}
尽管我们绝对不会在 Person 类上声明 @Hints 注解,但是它的信息仍然是可以通过 getAnnotation(Hints.class) 来读取的。 并且,getAnnotationsByType 方法会更方便,因为它赋予了所有 @Hints 注解标注的方法直接的访问权限。
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
参考【有修改】:有点干货 | Jdk1.8新特性实战篇