本文主要介绍了JDK1.8版本中的6个新特性,仅供参考。
jdk1.8新特性知识点:

  1. Lambda表达式
  2. 函数式接口
  3. 方法引用和构造器调用
  4. Stream API
  5. 接口中的默认方法和静态方法
  6. 新时间日期API

1. Lambda表达式

lambda表达式本质上是一段匿名内部类,也可以是一段可以传递的代码

先来体验一下lambda最直观的优点:简洁代码

  1. //匿名内部类
  2. Comparator<Integer> cpt = new Comparator<Integer>() {
  3. @Override
  4. public int compare(Integer o1, Integer o2) {
  5. return Integer.compare(o1,o2);
  6. }
  7. };
  8. 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) 方法引用
三种表现形式:

  1. 对象::实例方法名
  2. 类::静态方法名
  3. 类::实例方法名 (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 biFun = (x, y) -> Integer.compare(x, y); BiFunction biFun2 = Integer::compare; Integer result = biFun2.apply(100, 200);

// 方法引用-类名::实例方法名 BiFunction fun1 = (str1, str2) -> str1.equals(str2); BiFunction fun2 = String::equals; Boolean result2 = fun2.apply(“hello”, “world”); System.out.println(result2); }

(b)构造器引用 <br />格式:ClassName::new

public void test2() { // 构造方法引用 类名::new Supplier sup = () -> new Employee(); System.out.println(sup.get());

Supplier sup2 = Employee::new; System.out.println(sup2.get());

// 构造方法引用 类名::new (带一个参数) Function fun = (x) -> new Employee(x); Function fun2 = Employee::new; System.out.println(fun2.apply(100)); }

(c)数组引用<br />格式:Type[]::new

public void test(){ // 数组引用 Function fun = (x) -> new String[x]; Function fun2 = String[]::new; String[] strArray = fun2.apply(10); Arrays.stream(strArray).forEach(System.out::println); }

<a name="stream-api"></a>
## **Stream API**
Stream操作的三个步骤

- 创建stream
- 中间操作(过滤、map)
- 终止操作

stream的创建:

// 1,校验通过Collection 系列集合提供的stream()或者paralleStream() List list = new ArrayList<>(); Strean stream1 = list.stream();

// 2.通过Arrays的静态方法stream()获取数组流 String[] str = new String[10]; Stream stream2 = Arrays.stream(str);

// 3.通过Stream类中的静态方法of Stream stream3 = Stream.of(“aa”,”bb”,”cc”);

// 4.创建无限流 // 迭代 Stream stream4 = Stream.iterate(0,(x) -> x+2);

//生成 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 opt = emps.stream().findFirst(); System.out.println(opt.get());

// 并行流 Optional opt2 = emps.parallelStream().findAny(); System.out.println(opt2.get());

long count = emps.stream().count(); System.out.println(count);

Optional max = emps.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())); System.out.println(max.get());

Optional min = emps.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())); System.out.println(min.get());

还有功能比较强大的两个终止操作 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 sum = emps.stream().map(Employee::getSalary).reduce(Double::sum); System.out.println(sum);

collect操作:Collect-将流转换为其他形式,接收一个Collection接口的实现,用于给Stream中元素做汇总的方法

/**

  • collect:收集操作 */ List ageList = emps.stream().map(Employee::getAge).collect(Collectors.toList()); ageList.stream().forEach(System.out::println);

List existUnboundList = existStuAcctList.stream().filter(t -> StringUtil.isBlank(t.getDeptNo())).collect(Collectors.toList());

Map map = existUnboundList.stream().collect(Collectors.toMap(RbacStudentAccount::getAccount, item -> item));

Set existStudentNoList = existStuAcctList.stream().map(RbacStudentAccount::getAccount).collect(Collectors.toSet());//已去重

<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); }
    <a name="7dBbB"></a>
    ## **Optional容器**
    
    使用Optional容器可以快速的定位NPE,并且在一定程度上可以减少对参数非空检验的代码量。 ```
  • 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 op = Optional.of(new Employee(“zhansan”, 11, 12.32, Employee.Status.BUSY)); System.out.println(op.get());

// NPE Optional op2 = Optional.of(null); System.out.println(op2);

@Test public void test2(){ Optional op = Optional.empty(); System.out.println(op); // No value present System.out.println(op.get()); } @Test public void test3(){

Optional op = Optional.ofNullable(new Employee(“lisi”, 33, 131.42, Employee.Status.FREE)); System.out.println(op.get());

Optional op2 = Optional.ofNullable(null); System.out.println(op2); // System.out.println(op2.get()); }

@Test public void test5(){ Optional op1 = Optional.ofNullable(new Employee(“张三”, 11, 11.33, Employee.Status.VOCATION)); System.out.println(op1.orElse(new Employee())); System.out.println(op1.orElse(null)); } @Test public void test6(){ Optional op1 = Optional.of(new Employee(“田七”, 11, 12.31, Employee.Status.BUSY)); op1 = Optional.empty(); Employee employee = op1.orElseGet(() -> new Employee()); System.out.println(employee); } @Test public void test7(){ Optional op1 = Optional.of(new Employee(“田七”, 11, 12.31, Employee.Status.BUSY)); System.out.println(op1.map( (e) -> e.getSalary()).get()); }

<a name="GBU33"></a>
### **接口中可以定义默认实现方法和静态方法**
在接口中可以使用default和static关键字来修饰接口中定义的普通方法

public interface Interface { default String getName(){ return “zhangsan”; } static String getName2(){ return “zhangsan”; } }

在JDK1.8中很多接口会新增方法,为了保证1.8向下兼容,1.7版本中的接口实现类不用每个都重新实现新添加的接口方法,引入了default默认实现,static的用法是直接用接口名去调方法即可。当一个类继承父类又实现接口时,若后两者方法名相同,则优先继承父类中的同名方法,即“类优先”,如果实现两个同名方法的接口,则要求实现类必须手动声明默认实现哪个接口中的方法。

<a name="rQ5oD"></a>
## 新的日期API 
LocalDate | LocalTime | LocalDateTime<br />新的日期API都是不可变的,更使用于多线程的使用环境中

@Test public void test(){

// 从默认时区的系统时钟获取当前的日期时间。不用考虑时区差 LocalDateTime date = LocalDateTime.now(); //2018-07-15T14:22:39.759

System.out.println(date); System.out.println(date.getYear()); System.out.println(date.getMonthValue()); System.out.println(date.getDayOfMonth()); System.out.println(date.getHour()); System.out.println(date.getMinute()); System.out.println(date.getSecond()); System.out.println(date.getNano());

// 手动创建一个LocalDateTime实例 LocalDateTime date2 = LocalDateTime.of(2017, 12, 17, 9, 31, 31, 31); System.out.println(date2);

// 进行加操作,得到新的日期实例 LocalDateTime date3 = date2.plusDays(12); System.out.println(date3);

// 进行减操作,得到新的日期实例 LocalDateTime date4 = date3.minusYears(2); System.out.println(date4); }

```
@Test
public void test2(){
// 时间戳  1970年1月1日00:00:00 到某一个时间点的毫秒值
// 默认获取UTC时区
Instant ins = Instant.now();
System.out.println(ins);
System.out.println(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli());
System.out.println(System.currentTimeMillis());
System.out.println(Instant.now().toEpochMilli());
System.out.println(Instant.now().atOffset(ZoneOffset.ofHours(8)).toInstant().toEpochMilli());
}
@Test
public void test3(){
  // Duration:计算两个时间之间的间隔
  // Period:计算两个日期之间的间隔
  Instant ins1 = Instant.now();
  try {
          Thread.sleep(1000);
  } catch (InterruptedException e) {
      e.printStackTrace();
  }
  Instant ins2 = Instant.now();
  Duration dura = Duration.between(ins1, ins2);
  System.out.println(dura);
  System.out.println(dura.toMillis());
  System.out.println("======================");
  LocalTime localTime = LocalTime.now();
  try {
      Thread.sleep(1000);
  } catch (InterruptedException e) {
      e.printStackTrace();
  }

  LocalTime localTime2 = LocalTime.now();
  Duration du2 = Duration.between(localTime, localTime2);
  System.out.println(du2);
  System.out.println(du2.toMillis());
}
@Test
public void test4(){
  LocalDate localDate =LocalDate.now();
  try {
      Thread.sleep(1000);
  } catch (InterruptedException e) {
      e.printStackTrace();
  }
  LocalDate localDate2 = LocalDate.of(2016,12,12);
  Period pe = Period.between(localDate, localDate2);
  System.out.println(pe);
}
@Test
public void test5(){
  // temperalAdjust 时间校验器
  // 例如获取下周日  下一个工作日
  LocalDateTime ldt1 = LocalDateTime.now();
  System.out.println(ldt1);
  // 获取一年中的第一天
  LocalDateTime ldt2 = ldt1.withDayOfYear(1);
  System.out.println(ldt2);
  // 获取一个月中的第一天
  LocalDateTime ldt3 = ldt1.withDayOfMonth(1);
  System.out.println(ldt3);
  LocalDateTime ldt4 = ldt1.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
  System.out.println(ldt4);
  // 获取下一个工作日
  LocalDateTime ldt5 = ldt1.with((t) -> {
      LocalDateTime ldt6 = (LocalDateTime)t;
      DayOfWeek dayOfWeek = ldt6.getDayOfWeek();
    if (DayOfWeek.FRIDAY.equals(dayOfWeek)){
    return ldt6.plusDays(3);
    }
    else if (DayOfWeek.SATURDAY.equals(dayOfWeek)){
    return ldt6.plusDays(2);
    }
    else {
    return ldt6.plusDays(1);
    }
  });
  System.out.println(ldt5);
}
@Test
public void test6(){
// DateTimeFormatter: 格式化时间/日期
// 自定义格式
LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
String strDate1 = ldt.format(formatter);
String strDate = formatter.format(ldt);
System.out.println(strDate);
System.out.println(strDate1);
// 使用api提供的格式
DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE;
LocalDateTime ldt2 = LocalDateTime.now();
String strDate3 = dtf.format(ldt2);
System.out.println(strDate3);
// 解析字符串to时间
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime time = LocalDateTime.now();
String localTime = df.format(time);
LocalDateTime ldt4 = LocalDateTime.parse("2017-09-28 17:07:05",df);
System.out.println("LocalDateTime转成String类型的时间:"+localTime);
System.out.println("String类型的时间转成LocalDateTime:"+ldt4);
}
// ZoneTime  ZoneDate       ZoneDateTime
@Test
public void test7(){
LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println(now);
LocalDateTime now2 = LocalDateTime.now();
ZonedDateTime zdt = now2.atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(zdt);
Set<String> set = ZoneId.getAvailableZoneIds();
set.stream().forEach(System.out::println);
}

补充:

表示日期的LocalDate
表示时间的LocalTime
表示日期时间的LocalDateTime

新的日期API的几个优点:

  • 之前使用的java.util.Date月份从0开始,我们一般会+1使用,很不方便,java.time.LocalDate月份和星期都改成了enum
  • java.util.Date和SimpleDateFormat都不是线程安全的,而LocalDate和LocalTime和最基本的String一样,是不变类型,不但线程安全,而且不能修改。
  • java.util.Date是一个“万能接口”,它包含日期、时间,还有毫秒数,更加明确需求取舍
  • 新接口更好用的原因是考虑到了日期时间的操作,经常发生往前推或往后推几天的情况。用java.util.Date配合Calendar要写好多代码,而且一般的开发人员还不一定能写对。

  • LocalDate

    public static void localDateTest() {
    //获取当前日期,只含年月日 固定格式 yyyy-MM-dd    2018-05-04
    LocalDate today = LocalDate.now();
    // 根据年月日取日期,5月就是5,
    LocalDate oldDate = LocalDate.of(2018, 5, 1);
    // 根据字符串取:默认格式yyyy-MM-dd,02不能写成2
    LocalDate yesteday = LocalDate.parse("2018-05-03");
    // 如果不是闰年 传入29号也会报错
    LocalDate.parse("2018-02-29");
    }
    
  • LocalDate常用转化 ```java /**

  • 日期转换常用,第一天或者最后一天… */ public static void localDateTransferTest(){ //2018-05-04 LocalDate today = LocalDate.now(); // 取本月第1天: 2018-05-01 LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth()); // 取本月第2天:2018-05-02 LocalDate secondDayOfThisMonth = today.withDayOfMonth(2); // 取本月最后一天,再也不用计算是28,29,30还是31: 2018-05-31 LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth()); // 取下一天:2018-06-01 LocalDate firstDayOf2015 = lastDayOfThisMonth.plusDays(1); // 取2018年10月第一个周三 so easy?: 2018-10-03 LocalDate thirdMondayOf2018 = LocalDate.parse(“2018-10-01”).with(TemporalAdjusters.firstInMonth(DayOfWeek.WEDNESDAY)); } ```
  • LocalTime

    public static void localTimeTest(){
    //16:25:46.448(纳秒值)
    LocalTime todayTimeWithMillisTime = LocalTime.now();
    //16:28:48 不带纳秒值
    LocalTime todayTimeWithNoMillisTime = LocalTime.now().withNano(0);
    LocalTime time1 = LocalTime.parse("23:59:59");
    }
    
  • LocalDateTime

    public static void localDateTimeTest(){
    //转化为时间戳  毫秒值
    long time1 = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
    long time2 = System.currentTimeMillis();
    //时间戳转化为localdatetime
    DateTimeFormatter df= DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss.SSS");
    System.out.println(df.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(time1),ZoneId.of("Asia/Shanghai"))));
    }
    
  • 巧用Stream优化老代码

    如果有一个需求,需要对数据库查询到的菜肴进行一个处理:

  • 筛选出卡路里小于400的菜肴

  • 对筛选出的菜肴进行一个排序
  • 获取排序后菜肴的名字

菜肴:Dish.java

public class Dish {
    private String name;
    private boolean vegetarian;
    private int calories;
    private Type type;

    // getter and setter
}

Java8以前的实现方式

private List<String> beforeJava7(List<Dish> dishList) {
        List<Dish> lowCaloricDishes = new ArrayList<>();

        //1.筛选出卡路里小于400的菜肴
        for (Dish dish : dishList) {
            if (dish.getCalories() < 400) {
                lowCaloricDishes.add(dish);
            }
        }

        //2.对筛选出的菜肴进行排序
        Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
            @Override
            public int compare(Dish o1, Dish o2) {
                return Integer.compare(o1.getCalories(), o2.getCalories());
            }
        });

        //3.获取排序后菜肴的名字
        List<String> lowCaloricDishesName = new ArrayList<>();
        for (Dish d : lowCaloricDishes) {
            lowCaloricDishesName.add(d.getName());
        }

        return lowCaloricDishesName;
    }

Java8之后的实现方式

 private List<String> afterJava8(List<Dish> dishList) {
        return dishList.stream()
                .filter(d -> d.getCalories() < 400)  //筛选出卡路里小于400的菜肴
                .sorted(comparing(Dish::getCalories))  //根据卡路里进行排序
                .map(Dish::getName)  //提取菜肴名称
                .collect(Collectors.toList()); //转换为List
    }

不拖泥带水,一气呵成,原来需要写24代码实现的功能现在只需5行就可以完成了

JDK1.8 新特性(全) - 图1
高高兴兴写完需求这时候又有新需求了,新需求如下:
对数据库查询到的菜肴根据菜肴种类进行分类,返回一个Map>的结果
这要是放在jdk8之前肯定会头皮发麻。
Java8以前的实现方式

private static Map<Type, List<Dish>> beforeJdk8(List<Dish> dishList) {
    Map<Type, List<Dish>> result = new HashMap<>();

    for (Dish dish : dishList) {
        //不存在则初始化
        if (result.get(dish.getType())==null) {
            List<Dish> dishes = new ArrayList<>();
            dishes.add(dish);
            result.put(dish.getType(), dishes);
        } else {
            //存在则追加
            result.get(dish.getType()).add(dish);
        }
    }

    return result;
}

还好jdk8有Stream,再也不用担心复杂集合处理需求。

Java8以后的实现方式

private static Map<Type, List<Dish>> afterJdk8(List<Dish> dishList) {
    return dishList.stream().collect(groupingBy(Dish::getType));
}

又是一行代码解决了需求,忍不住大喊Stream API牛批 看到流的强大功能了吧,接下来将详细介绍流。

image.png
什么是流

流是从支持数据处理操作的源生成的元素序列,源可以是数组、文件、集合、函数。流不是集合元素,它不是数据结构并不保存数据,它的主要目的在于计算。

如何生成流

生成流的方式主要有五种。

1.通过集合生成,应用中最常用的一种

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream();

通过集合的stream方法生成流。

2.通过数组生成

int[] intArr = new int[]{1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(intArr);

通过Arrays.stream方法生成流,并且该方法生成的流是数值流【即IntStream】而不是Stream。补充一点使用数值流可以避免计算过程中拆箱装箱,提高性能。

Stream API提供了mapToInt、mapToDouble、mapToLong三种方式将对象流【即Stream】转换成对应的数值流,同时提供了boxed方法将数值流转换为对象流。
3.通过值生成

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

通过Stream的of方法生成流,通过Stream的empty方法可以生成一个空流

4.通过文件生成

Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())

通过Files.line方法得到一个流,并且得到的每个流是给定文件中的一行

5.通过函数生成 提供了iterate和generate两个静态方法从函数中生成流iterator

Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(5);

iterate方法接受两个参数,第一个为初始化值,第二个为进行的函数操作,因为iterator生成的流为无限流,通过limit方法对流进行了截断,只生成5个偶数
generator

Stream<Double> stream = Stream.generate(Math::random).limit(5);

generate方法接受一个参数,方法参数类型为Supplier,由它为流提供值。generate生成的流也是无限流,因此通过limit对流进行了截断。
流的操作类型

流的操作类型主要分为两种。

1.中间操作
一个流可以后面跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的,仅仅调用到这类方法,并没有真正开始流的遍历,真正的遍历需等到终端操作时,常见的中间操作有下面即将介绍的filter、map等。
2.终端操作
一个流有且只能有一个终端操作,当这个操作执行后,流就被关闭了,无法再被操作,因此一个流只能被遍历一次,若想在遍历需要通过源数据在生成流。终端操作的执行,才会真正开始流的遍历。如下面即将介绍的count、collect等。
流的使用

流的使用将分为终端操作和中间操作进行介绍。

中间操作

filter筛选

 List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
 Stream<Integer> stream = integerList.stream().filter(i -> i > 3);

通过使用filter方法进行条件筛选,filter的方法参数为一个条件

distinct去除重复元素

List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream().distinct();

通过distinct方法快速去除重复的元素

limit返回指定流个数

List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream().limit(3);

通过limit方法指定返回流的个数,limit的参数值必须>=0,否则将会抛出异常

skip跳过流中的元素

 List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream().skip(2);

通过skip方法跳过流中的元素,上述例子跳过前两个元素,所以打印结果为2,3,4,5,skip的参数值必须>=0,否则将会抛出异常

map流映射
所谓流映射就是将接受的元素映射成另外一个元素。

List<String> stringList = Arrays.asList("Java 8", "Lambdas",  "In", "Action");
Stream<Integer> stream = stringList.stream().map(String::length);

通过map方法可以完成映射,该例子完成中String -> Integer的映射,之前上面的例子通过map方法完成了Dish->String的映射。
flatMap流转换
将一个流中的每个值都转换为另一个流。

List<String> wordList = Arrays.asList("Hello", "World");
List<String> strList = wordList.stream()
        .map(w -> w.split(" "))
        .flatMap(Arrays::stream)
        .distinct()
        .collect(Collectors.toList());

map(w -> w.split(“ “))的返回值为Stream,我们想获取Stream,可以通过flatMap方法完成Stream ->Stream的转换
元素匹配

提供了三种匹配方式。
1.allMatch匹配所有

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
if (integerList.stream().allMatch(i -> i > 3)) {
    System.out.println("值都大于3");
}

通过allMatch方法实现。
2.anyMatch匹配其中一个

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
if (integerList.stream().anyMatch(i -> i > 3)) {
    System.out.println("存在大于3的值");
}

等同于:

for (Integer i : integerList) {
    if (i > 3) {
        System.out.println("存在大于3的值"); 
        break;
    }
}

存在大于3的值则打印,java8中通过anyMatch方法实现这个功能。
3.noneMatch全部不匹配

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
if (integerList.stream().noneMatch(i -> i > 3)) {
    System.out.println("值都小于3");
}

通过noneMatch方法实现。

终端操作

统计流中元素个数
1.通过count

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Long result = integerList.stream().count();

通过使用count方法统计出流中元素个数。
2.通过counting

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Long result = integerList.stream().collect(counting());

最后一种统计元素个数的方法在与collect联合使用的时候特别有用。
查找
提供了两种查找方式。
1.findFirst查找第一个

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> result = integerList.stream().filter(i -> i > 3).findFirst();

通过findFirst方法查找到第一个大于三的元素并打印。
2.findAny随机查找一个

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> result = integerList.stream().filter(i -> i > 3).findAny();

通过findAny方法查找到其中一个大于三的元素并打印,因为内部进行优化的原因,当找到第一个满足大于三的元素时就结束,该方法结果和findFirst方法结果一样。提供findAny方法是为了更好的利用并行流,findFirst方法在并行上限制更多【本篇文章将不介绍并行流】。
reduce将流中的元素组合起来
假设我们对一个集合中的值进行求和
jdk8之前

int sum = 0;
for (int i : integerList) {
    sum += i;
}

jdk8之后通过reduce进行处理

int sum = integerList.stream().reduce(0, (a, b) -> (a + b));

一行就可以完成,还可以使用方法引用简写成:

int sum = integerList.stream().reduce(0, Integer::sum);

reduce接受两个参数,一个初始值这里是0,一个BinaryOperator accumulator来将两个元素结合起来产生一个新值,
另外reduce方法还有一个没有初始化值的重载方法。
获取流中最小最大值
通过min/max获取最小最大值

Optional<Integer> min = menu.stream()
    .map(Dish::getCalories)
    .min(Integer::compareTo);

Optional<Integer> max = menu.stream()
    .map(Dish::getCalories)
    .max(Integer::compareTo);

也可以写成:

OptionalInt min = menu.stream().mapToInt(Dish::getCalories).min();
OptionalInt max = menu.stream().mapToInt(Dish::getCalories).max();

min获取流中最小值,max获取流中最大值,方法参数为Comparator<? super T> comparator。
通过minBy/maxBy获取最小最大值

Optional<Integer> min = menu.stream().map(Dish::getCalories).collect(minBy(Integer::compareTo));
Optional<Integer> max = menu.stream().map(Dish::getCalories).collect(maxBy(Integer::compareTo));

minBy获取流中最小值,maxBy获取流中最大值,方法参数为Comparator<? super T> comparator。
通过reduce获取最小最大值

Optional<Integer> min = menu.stream().map(Dish::getCalories).reduce(Integer::min);
Optional<Integer> max = menu.stream().map(Dish::getCalories).reduce(Integer::max);

求和
通过summingInt

int sum = menu.stream().collect(summingInt(Dish::getCalories));

如果数据类型为double、long,则通过summingDouble、summingLong方法进行求和。
通过reduce

int sum = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);

通过sum

int sum = menu.stream().mapToInt(Dish::getCalories).sum();

在上面求和、求最大值、最小值的时候,对于相同操作有不同的方法可以选择执行。可以选择collect、reduce、min/max/sum方法,推荐使用min、max、sum方法。因为它最简洁易读,同时通过mapToInt将对象流转换为数值流,避免了装箱和拆箱操作。

通过averagingInt求平均值

double average = menu.stream().collect(averagingInt(Dish::getCalories));

如果数据类型为double、long,则通过averagingDouble、averagingLong方法进行求平均。
通过summarizingInt同时求总和、平均值、最大值、最小值

IntSummaryStatistics intSummaryStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));
double average = intSummaryStatistics.getAverage();  //获取平均值
int min = intSummaryStatistics.getMin();  //获取最小值
int max = intSummaryStatistics.getMax();  //获取最大值
long sum = intSummaryStatistics.getSum();  //获取总和

如果数据类型为double、long,则通过summarizingDouble、summarizingLong方法。
通过foreach进行元素遍历

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);integerList.stream().forEach(System.out::println);

而在jdk8之前实现遍历:

for (int i : integerList) {
    System.out.println(i);
}

jdk8之后遍历元素来的更为方便,原来的for-each直接通过foreach方法就能实现了

返回集合

List<String> strings = menu.stream().map(Dish::getName).collect(toList());
Set<String> sets = menu.stream().map(Dish::getName).collect(toSet());

只举例了一部分,还有很多其他方法 jdk8之前

 List<String> stringList = new ArrayList<>();
Set<String> stringSet = new HashSet<>(); 
for (Dish dish : menu) {
     stringList.add(dish.getName());
    stringSet.add(dish.getName());
}

通过遍历和返回集合的使用发现流只是把原来的外部迭代放到了内部进行,这也是流的主要特点之一。内部迭代可以减少好多代码量
通过joining拼接流中的元素

String result = menu.stream().map(Dish::getName).collect(Collectors.joining(", "));

默认如果不通过map方法进行映射处理拼接的toString方法返回的字符串,joining的方法参数为元素的分界符,如果不指定生成的字符串将是一串的,可读性不强。
进阶通过groupingBy进行分组

Map<Type, List<Dish>> result = dishList.stream().collect(groupingBy(Dish::getType));

在collect方法中传入groupingBy进行分组,其中groupingBy的方法参数为分类函数。还可以通过嵌套使用groupingBy进行多级分类。

Map<Type, List<Dish>> result = menu.stream().collect(groupingBy(Dish::getType,
        groupingBy(dish -> {
            if (dish.getCalories() <= 400) return CaloricLevel.DIET;
                else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
                else return CaloricLevel.FAT;
        })));

进阶通过partitioningBy进行分区
分区是特殊的分组,它分类依据是true和false,所以返回的结果最多可以分为两组

Map<Boolean, List<Dish>> result = menu.stream().collect(partitioningBy(Dish :: isVegetarian))

等同于

Map<Boolean, List<Dish>> result = menu.stream().collect(groupingBy(Dish :: isVegetarian))

这个例子可能并不能看出分区和分类的区别,甚至觉得分区根本没有必要,换个明显一点的例子:

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Map<Boolean, List<Integer>> result = integerList.stream().collect(partitioningBy(i -> i < 3));

返回值的键仍然是布尔类型,但是它的分类是根据范围进行分类的,分区比较适合处理根据范围进行分类。
总 结

通过使用Stream API可以简化代码,同时提高了代码可读性,赶紧在项目里用起来。讲道理在没学Stream API之前,谁要是给我在应用里写很多Lambda,Stream API,飞起就想给他一脚。