本文主要介绍了JDK1.8版本中的6个新特性,仅供参考。
jdk1.8新特性知识点:
- Lambda表达式
- 函数式接口
- 方法引用和构造器调用
- Stream API
- 接口中的默认方法和静态方法
- 新时间日期API
1. Lambda表达式
lambda表达式本质上是一段匿名内部类,也可以是一段可以传递的代码
先来体验一下lambda最直观的优点:简洁代码
//匿名内部类
Comparator<Integer> cpt = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
};
TreeSet<Integer> set = new TreeSet<>(cpt);
//使用lambda表达式
//使用lambda表达式
Comparator<Integer> cpt = (x,y) -> Integer.compare(x,y);
TreeSet<Integer> set = new TreeSet<>(cpt);
只需要一行代码,极大减少代码量!!
这样一个场景,在商城浏览商品信息时,经常会有条件的进行筛选,例如要选颜色为红色的、价格小于8000千的….
// 筛选颜色为红色
public List<Product> filterProductByColor(List<Product> list){
List<Product> prods = new ArrayList<>();
for (Product product : list){
if ("红色".equals(product.getColor())){
prods.add(product);
}
}
return prods;
}
// 筛选价格小于8千的
public List<Product> filterProductByPrice(List<Product> list){
List<Product> prods = new ArrayList<>();
for (Product product : list){
if (product.getPrice() < 8000){
prods.add(product);
}
}
return prods;
}
实际上这些过滤方法的核心就只有if语句中的条件判断,其他均为模版代码,每次变更一下需求,都需要新增一个方法,假设这个过滤方法有几百行,那么这样的做法难免笨拙了一点。如何进行优化呢?
优化一:使用设计模式
定义一个MyPredicate接口
public interface MyPredicate <T> {
boolean test(T t);
}
如果想要筛选颜色为红色的商品,定义一个颜色过滤类
public class ColorPredicate implements MyPredicate<Product> {
private static final String RED = "红色";
@Override
public boolean test(Product product) {
return RED.equals(product.getColor());
}
}
例如,如果想要筛选价格小于8000的商品,那么新建一个价格过滤类既可
public class PricePredicate implements MyPredicate<Product> {
@Override
public boolean test(Product product) {
return product.getPrice() < 8000;
}
}
定义过滤方法,将过滤接口当做参数传入,这样这个过滤方法就不用修改,在实际调用的时候将具体的实现类传入即可。
public List<Product> filterProductByPredicate(List<Product> list,MyPredicate<Product> mp){
List<Product> prods = new ArrayList<>();
for (Product prod : list){
if (mp.test(prod)){
prods.add(prod);
}
}
return prods;
}
这样实现的话可能有人会说,每次变更需求都需要新建一个实现类,感觉还是有点繁琐呀,那么再来优化一下
优化二:使用匿名内部类
定义过滤方法:
public List<Product> filterProductByPredicate(List<Product> list,MyPredicate<Product> mp){
List<Product> prods = new ArrayList<>();
for (Product prod : list){
if (mp.test(prod)){
prods.add(prod);
}
}
return prods;
}
调用过滤方法的时候:
// 按价格过滤
public void test2(){
filterProductByPredicate(proList, new MyPredicate<Product>() {
@Override
public boolean test(Product product) {
return product.getPrice() < 8000;
}
});
}
// 按颜色过滤
public void test3(){
filterProductByPredicate(proList, new MyPredicate<Product>() {
@Override
public boolean test(Product product) {
return "红色".equals(product.getColor());
}
});
}
使用匿名内部类,就不需要每次都新建一个实现类,直接在方法内部实现。看到匿名内部类,不禁想起了Lambda表达式。
优化三:使用lambda表达式
定义过滤方法:
public List<Product> filterProductByPredicate(List<Product> list,MyPredicate<Product> mp){
List<Product> prods = new ArrayList<>();
for (Product prod : list){
if (mp.test(prod)){
prods.add(prod);
}
}
return prods;
}
使用lambda表达式进行过滤
@Test
public void test4(){
List<Product> products = filterProductByPredicate(proList, (p) -> p.getPrice() < 8000);
for (Product pro : products){
System.out.println(pro);
}
}
在jdk1.8中还有更加简便的操作 Stream API
优化四:使用Stream API
甚至不用定义过滤方法,直接在集合上进行操作
// 使用jdk1.8中的Stream API进行集合的操作
@Test
public void test(){
// 根据价格过滤
proList.stream().fliter((p) -> p.getPrice() <8000).limit(2).forEach(System.out::println);
// 根据颜色过滤
proList.stream().fliter((p) -> "红色".equals(p.getColor())).forEach(System.out::println);
// 遍历输出商品名称
proList.stream().map(Product::getName).forEach(System.out::println);
}
Lmabda表达式的语法总结: () -> ();
前置 | 语法 |
---|---|
无参数无返回值 | () -> System.out.println(“Hello WOrld”) |
有一个参数无返回值 | (x) -> System.out.println(x) |
有且只有一个参数无返回值 | x -> System.out.println(x) |
有多个参数,有返回值,有多条lambda体语句 | (x,y) -> {System.out.println(“xxx”);return xxxx;}; |
有多个参数,有返回值,只有一条lambda体语句 | (x,y) -> xxxx |
口诀:左右遇一省括号,左侧推断类型省
注:当一个接口中存在多个抽象方法时,如果使用lambda表达式,并不能智能匹配对应的抽象方法,因此引入了函数式接口的概念
函数式接口
函数式接口的提出是为了给Lambda表达式的使用提供更好的支持。
什么是函数式接口?
简单来说就是只定义了一个抽象方法的接口(Object类的public方法除外),就是函数式接口,并且还提供了注解:@FunctionalInterface
常见的四大函数式接口
- Consumer 《T》:消费型接口,有参无返回值 ```
@Test public void test(){ changeStr(“hello”,(str) -> System.out.println(str)); }
/**
- Consumer
消费型接口 */ public void changeStr(String str, Consumer con){ con.accept(str); }
- Supplier 《T》:供给型接口,无参有返回值
@Test public void test2(){ String value = getValue(() -> “hello”); System.out.println(value); }
/**
- Supplier
供给型接口 */ public String getValue(Supplier sup){ return sup.get(); } ```
- Function 《T,R》::函数式接口,有参有返回值 ``` @Test public void test3(){ Long result = changeNum(100L, (x) -> x + 200L); System.out.println(result); }
/**
- Function
函数式接口 */ public Long changeNum(Long num, Function fun){ return fun.apply(num); } ```
- Predicate《T》: 断言型接口,有参有返回值,返回值是boolean类型 ``` public void test4(){ boolean result = changeBoolean(“hello”, (str) -> str.length() > 5); System.out.println(result); }
/**
- Predicate
断言型接口 */ public boolean changeBoolean(String str, Predicate pre){ return pre.test(str); } ``` 在四大核心函数式接口基础上,还提供了诸如BiFunction、BinaryOperation、toIntFunction等扩展的函数式接口,都是在这四种函数式接口上扩展而来的,不做赘述。
总结:函数式接口的提出是为了让我们更加方便的使用lambda表达式,不需要自己再手动创建一个函数式接口,直接拿来用就好了
方法引用
若lambda体中的内容有方法已经实现了,那么可以使用“方法引用”
也可以理解为方法引用是lambda表达式的另外一种表现形式并且其语法比lambda表达式更加简单
(a) 方法引用
三种表现形式:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名 (lambda参数列表中第一个参数是实例方法的调用者,第二个参数是实例方法的参数时可用) ``` public void test() { /* 注意:
- 1.lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
- 2.若lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method
/
Consumer
con = (x) -> System.out.println(x); con.accept(100); // 方法引用-对象::实例方法 Consumer con2 = System.out::println; con2.accept(200);
// 方法引用-类名::静态方法名
BiFunction
// 方法引用-类名::实例方法名
BiFunction
(b)构造器引用 <br />格式:ClassName::new
public void test2() {
// 构造方法引用 类名::new
Supplier
Supplier
// 构造方法引用 类名::new (带一个参数)
Function
(c)数组引用<br />格式:Type[]::new
public void test(){
// 数组引用
Function
<a name="stream-api"></a>
## **Stream API**
Stream操作的三个步骤
- 创建stream
- 中间操作(过滤、map)
- 终止操作
stream的创建:
// 1,校验通过Collection 系列集合提供的stream()或者paralleStream()
List
// 2.通过Arrays的静态方法stream()获取数组流
String[] str = new String[10];
Stream
// 3.通过Stream类中的静态方法of
Stream
// 4.创建无限流
// 迭代
Stream
//生成 Stream.generate(() ->Math.random());
Stream的中间操作:
/**
- 筛选 过滤 去重(需要流中的元素重写hashCode和equals方法) */ emps.stream().filter(e -> e.getAge() > 10).distinct().forEach(System.out::println);
/**
- 生成新的流 通过map映射 */ emps.stream().map((e) -> e.getAge()).forEach(System.out::println);
/**
- 自然排序 定制排序
*/
emps.stream().sorted((e1 ,e2) -> {
if (e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
} else{
return e1.getAge().compareTo(e2.getAge());
}
})
.forEach(System.out::println);
/**Stream的终止操作:
- 查找和匹配
- allMatch-检查是否匹配所有元素
- anyMatch-检查是否至少匹配一个元素
- noneMatch-检查是否没有匹配所有元素
- findFirst-返回第一个元素
- findAny-返回当前流中的任意元素
- count-返回流中元素的总个数
- max-返回流中最大值
- min-返回流中最小值 / /*
- 检查是否匹配元素 */ boolean b1 = emps.stream().allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY)); System.out.println(b1);
boolean b2 = emps.stream().anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY)); System.out.println(b2);
boolean b3 = emps.stream().noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY)); System.out.println(b3);
Optional
// 并行流
Optional
long count = emps.stream().count(); System.out.println(count);
Optional
Optional
还有功能比较强大的两个终止操作 reduce和collect <br />reduce操作: reduce:(T identity,BinaryOperator)/reduce(BinaryOperator)-可以将流中元素反复结合起来,得到一个值
/**
- reduce :规约操作
*/
List
list = Arrays.asList(1,2,3,4,5,6,7,8,9,10); Integer count2 = list.stream().reduce(0, (x, y) -> x + y); System.out.println(count2);
Optional
collect操作:Collect-将流转换为其他形式,接收一个Collection接口的实现,用于给Stream中元素做汇总的方法
/**
- collect:收集操作
*/
List
ageList = emps.stream().map(Employee::getAge).collect(Collectors.toList()); ageList.stream().forEach(System.out::println);
List
Map
Set
<a name="uHT5z"></a>
## **并行流和串行流**
在jdk1.8新的stream包中针对集合的操作也提供了并行操作流和串行操作流。并行流就是把内容切割成多个数据块,并且使用多个线程分别处理每个数据块的内容。Stream api中声明可以通过parallel()与sequential()方法在并行流和串行流之间进行切换。 <br />jdk1.8并行流使用的是fork/join框架进行并行操作
<a name="4ocOA"></a>
### **ForkJoin框架**
Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。 <br />关键字:递归分合、分而治之。 <br />采用 “工作窃取”模式(work-stealing): <br />当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态.而在fork/join框架实现中,如果 某个子问题由于等待另外一个子问题的完成而无法继续运行.那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能.。
/**
- 要想使用Fark—Join,类必须继承
- RecursiveAction(无返回值)
- Or
- RecursiveTask(有返回值)
/
public class ForkJoin extends RecursiveTask
{ /** - 要想使用Fark—Join,类必须继承RecursiveAction(无返回值) 或者
- RecursiveTask(有返回值) *
- @author Wuyouxin / private static final long serialVersionUID = 23423422L; private long start; private long end; public ForkJoin() { } public ForkJoin(long start, long end) { this.start = start; this.end = end; } // 定义阙值 private static final long THRESHOLD = 10000L; @Override protected Long compute() { if (end - start <= THRESHOLD) { long sum = 0; for (long i = start; i < end; i++) { sum += i; } return sum; } else { long middle = (end - start) / 2; ForkJoin left = new ForkJoin(start, middle); //拆分子任务,压入线程队列 left.fork(); ForkJoin right = new ForkJoin(middle + 1, end); right.fork(); //合并并返回 return left.join() + right.join(); } } /*
- 实现数的累加
/
@Test
public void test1() {
//开始时间
Instant start = Instant.now();
//这里需要一个线程池的支持
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask
task = new ForkJoin(0L, 10000000000L); // 没有返回值 pool.execute(); // 有返回值 long sum = pool.invoke(task); //结束时间 Instant end = Instant.now(); System.out.println(Duration.between(start, end).getSeconds()); } /* - java8 并行流 parallel()
*/
@Test
public void test2() {
//开始时间
Instant start = Instant.now();
// 并行流计算 累加求和
LongStream.rangeClosed(0, 10000000000L).parallel()
.reduce(0, Long :: sum);
//结束时间
Instant end = Instant.now();
System.out.println(Duration.between(start, end).getSeconds());
}
@Test
public void test3(){
List
list = Arrays.asList(1, 2, 3, 4, 5); list.stream().forEach(System.out::print); list.parallelStream() .forEach(System.out::print); }
@Test public void test(){ // 并行流 多个线程执行 List展示多线程的效果:
numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); numbers.parallelStream() .forEach(System.out::print); // System.out.println(“=========================”); numbers.stream() .sequential() .forEach(System.out::print); }
使用Optional容器可以快速的定位NPE,并且在一定程度上可以减少对参数非空检验的代码量。 ```<a name="7dBbB"></a> ## **Optional容器**
- 1 ``` /**
- Optional.of(T t); // 创建一个Optional实例
- Optional.empty(); // 创建一个空的Optional实例
- Optional.ofNullable(T t); // 若T不为null,创建一个Optional实例,否则创建一个空实例
- isPresent(); // 判断是够包含值
- orElse(T t); //如果调用对象包含值,返回该值,否则返回T
- orElseGet(Supplier s); // 如果调用对象包含值,返回该值,否则返回s中获取的值
- map(Function f): // 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty();
- flatMap(Function mapper);// 与map类似。返回值是Optional *
- 总结:Optional.of(null) 会直接报NPE */
Optional
// NPE
Optional
@Test public void test2(){ Optional