Java 8介绍
Java 8的主要新语法特性如下:
- Lambda表达式
Lambda表达式使Java程序员能够编写更加简洁、易读和易维护的代码。它是一种匿名函数,可以将其作为参数传递给其他方法或函数。
- 方法引用
方法引用是指通过名称来引用现有的方法,从而让代码变得更简洁、易读和易于维护。Java 8中提供了四种不同的方法引用方式:静态方法引用、实例方法引用、构造器引用和超类方法引用。
- 接口默认方法
Java 8允许在接口中定义具体实现的默认方法,这样实现类就可以继承该方法的实现,避免了因为新增方法而导致向所有实现类迁移代码的麻烦。
- 函数式接口
函数式接口仅包含一个抽象方法的接口,它是Java 8中Lambda表达式的基础。Java 8中提供了许多常用的函数式接口,例如:Predicate、Function、Consumer等。
- Stream API
Stream API是Java 8中一个强大的工具,它允许程序员轻松处理集合数据。使用流API可以实现筛选、排序、映射、归约等操作,让代码更加简洁、易读和易于维护。
- 时间API
Java 8中引入了新的时间API,它提供了一种更好的方式来处理日期和时间。这个API包含多个类,可以用来处理日历、时间、时区和持续时间等问题。
- Optional 类型
Optional类型是一个容器对象,可以包含null或非null值。它为程序员提供一种优雅的方式来处理null检查,从而避免NullPointerException异常。
这些新语法都带来了很大的变化,使得Java编程更加简洁、高效、可读性强和易于维护。
Lambda 表达式
Lambda是一个 匿名函数,我们可以把Lambda表达式理解为是 一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
基础语法
Java8引入了一个新的操作符: ->,该操作符称为箭头操作符或Lambda操作符,箭头操作符将Lambda表达式拆分为2部分:
- 左侧:Lambda表达式的 参数列表
- 右侧:Lambda表达式中 所执行的功能,即Lambda体
语法格式1:无参数,无返回值:
public class test { public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello World!"); } }; r.run(); System.out.println("--------------------------------------"); Runnable r1 = () -> System.out.println("Hello Lambda"); r1.run(); } }
语法格式2:有一个参数,并且无返回值,小括号可省略不写,:
public class test { public static void main(String[] args) { Consumer<String> con = x -> System.out.println(x); con.accept("SNOW"); } }
语法格式3:有2个以上的参数,有返回值,并且Lambda体中有多条语句 ,有返回值
public class test { public static void main(String[] args) { Comparator<Integer> com = (x,y) -> { System.out.println("函数式接口"); return Integer.compare(x,y); }; } }
语法格式4:若Lambda体中只有一条语句,return和大括号都可省略不写
public class test { public static void main(String[] args) { Comparator<Integer> com = (x,y) -> Integer.compare(x,y); } }
语法格式5:Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出数据类型,即“类型推断”
Lambda表达式需要 函数式接口 的支持!(函数式接口:接口中只有一个抽象方法的接口,称为函数式接口,可以使用 @FunctionalInterface 修饰。)
案例
@Data @AllArgsConstructor @NoArgsConstructor public class Employee { private String name; private int age; private Double salary; }
public class test { public static void main(String[] args) { List<Employee> list = Arrays.asList( new Employee("1号", 25, 5000.0), new Employee("2号", 35, 3000.0), new Employee("3号", 35, 2000.0), new Employee("4号", 35, 8000.0), new Employee("5号", 65, 1000.0) ); Collections.sort(list,(e1,e2)->{ if (e1.getAge() == e2.getAge()){ return e1.getName().compareTo(e2.getName()); }else { // 倒序 return -Integer.compare(e1.getAge(),e2.getAge()); } }); Iterator<Employee> iterator = list.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } } }
内置的四大核心函数式接口
- Consumer:消费型接口 void accept(T t);
- Supplier:供给型接口 T get();
- Function(T,R):函数型接口 R apply(T t);
- Predicate:断言型接口 boolean test(T t);
案例
- Consumer:消费型接口
@Test public void test(){ this.happy(1000,m -> { System.out.println("我消费了"+ m); }); } public void happy(double money, Consumer<Double> con){ con.accept(money); }
- Supplier:供给型接口
@Test public void test(){ List<Integer> numList = this.getNumList(5, () -> (int) (Math.random() * 100)); for (Integer integer : numList) { System.out.println(integer); } } //产生指定数量的整数 public List<Integer> getNumList(int num, Supplier<Integer> sup){ List<Integer> list = new ArrayList<>(); for (int i = 0; i < num; i++) { list.add(sup.get()); } return list; }
- Function(T,R):函数型接口
@Test public void test(){ System.out.println(this.handler("str", str -> (str + "123"))); } //处理字符串 public String handler(String str, Function<String,String> fun){ return fun.apply(str); }
- Predicate:断言型接口
@Test public void test(){ List<String> stringList = Arrays.asList("qwe", "123", "hello", "ranhaifeng", "asdasdsaewqewqe"); List<String> list = filterStr(stringList, s -> (s.length() > 3)); for (String s : list) { System.out.println(s); } } //将满足条件的字符串放入集合 public List<String> filterStr(List<String> list, Predicate<String> pre){ List<String> stringList = new ArrayList<>(); for (String s : list) { if (pre.test(s)){ stringList.add(s); } } return stringList; }
方法引用与构造器引用
方法引用
方法引用:若 Lambda 体中的内容有方法已经实现了,我们可以使用“方法引用”,可以理解为方法引用是Lambda表达式的另外一种表现形式。
主要有三种语法格式:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
//对象::实例方法 @Test public void test(){ Consumer<String> con = x -> { System.out.println(x); }; PrintStream out = System.out; Consumer<String> con1 = out::println; Consumer<String> con2 = System.out::println; con2.accept("qwe"); } //类::静态方法名 @Test public void test(){ int x;int y; Comparator<Integer> com = (x,y)-> Integer.compare(x,y); Comparator<Integer> com1 = Integer::compare; } // 类::实例方法名 @Test public void test(){ BiPredicate<String,String> bp = (x,y) -> x.equals(y); BiPredicate<String,String> bp2 = String::equals; }
注意:
- Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
- 若Lambda参数列表中的第一参数是实例方法的调用者,第二个参数是实例方法的参数时,可以使用ClassName::method
构造器引用
@Test public void test(){ Supplier<Object> sup = () -> new Object(); Supplier<Object> sup2 = Object::new; //如果有多个构造器,如何判断是调用的实体类的哪个构造器呢?看下面的注意,即Function(T,R)内部的函数 R apply(T t) 是1个参数,那么就会调用是1个参数的构造器。 Function<Integer,Employee> fun2 =Employee::new; }
注意:需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致!
并行流与串行流
- 并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API可以声明性地通过parallel() 与sequential()在并行流与顺序流之间进行切换。
- fork/join框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。
- Fork/Join框架与传统线程池的区别:采用工作窃取模式(work-stealing)
当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
相对于一般的线程池实现, fork/join框架的优势体现仕对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态.而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行,这种方式减少了线程的等待时间,提高了性能。
public class ForkJoinCalculate extends RecursiveTask<Long> { private static final long serialVersionUID = 12313435L; private long start; private long end; private static final long THRESHOLD = 10000; public ForkJoinCalculate(long start,long end){ this.start = start; this.end = end; } @Override protected Long compute() { long length = end - start; if (length <= THRESHOLD){ long sum = 0; for (long i = start; i <= end ; i++) { sum+= i; } return sum; }else { long middle = (start + end )/2; ForkJoinCalculate left = new ForkJoinCalculate(start,middle); left.fork(); //拆分子任务,同时压入线程队列 ForkJoinCalculate right = new ForkJoinCalculate(middle + 1,end); right.fork(); return left.join() + right.join(); } } }
@Test public void qwe(){ Instant start = Instant.now(); ForkJoinPool forkJoinPool = new ForkJoinPool(); ForkJoinCalculate forkJoinCalculate = new ForkJoinCalculate(0, 10000000000L); Long sum = forkJoinPool.invoke(forkJoinCalculate); System.out.println(sum); Instant end = Instant.now(); System.out.println("消耗时间:" + Duration.between(start,end).toMillis()); //5701 } //普通for循环计算 @Test public void test2(){ Instant start = Instant.now(); long sum = 0L; for (int i = 0; i < 10000000000L; i++) { sum += i; } System.out.println(sum); Instant end = Instant.now(); System.out.println("消耗时间:" + Duration.between(start,end).toMillis()); //没跑出来 } //java8并行流 @Test public void test3(){ Instant start = Instant.now(); LongStream.rangeClosed(0,10000000000L) .parallel() .reduce(0,Long::sum); Instant end = Instant.now(); System.out.println("消耗时间:" + Duration.between(start,end).toMillis()); //2145 }
Optional 容器
@Test public void test(){ // Option.of(T t):创建一个Optional实例 Optional<Person> op = Optional.of(new Person()); Person person = op.get(); System.out.println(person); // Person(name=null, age=null, sale=null, status=null) } @Test public void test2(){ // Option.empty():创建一个空Optional实例 Optional<Object> op = Optional.empty(); System.out.println(op.get()); // java.util.NoSuchElementException: No value present } @Test public void test3(){ // Option.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例 Optional<Object> op = Optional.ofNullable(new Person()); System.out.println(op.get()); // Person(name=null, age=null, sale=null, status=null) } @Test public void test4(){ Optional<Person> op = Optional.ofNullable(new Person()); // Option.isPresent():判断是否包含值 if (op.isPresent()){ System.out.println(op.get()); // Person(name=null, age=null, sale=null, status=null) } } @Test public void test5(){ Optional<Person> op = Optional.ofNullable(null); // Option.orElse(T t):如果调用对象包含值,返回该值,否则返回t Person emp = op.orElse(new Person("qwe", 18, 2000.2, Person.Status.BUSY)); System.out.println(emp); // Person(name=qwe, age=18, sale=2000.2, status=BUSY) } @Test public void test6(){ Optional<Person> op = Optional.ofNullable(null); //如果调用对象包含值,返回该值,否则返回s获取的值 Person person = op.orElseGet(() -> new Person()); System.out.println(person); // Person(name=null, age=null, sale=null, status=null) } @Test public void test7(){ Optional<Person> op = Optional.ofNullable(new Person("qwe", 18, 200.0, Person.Status.BUSY)); // map(Function f):如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty() Optional<String> str = op.map(e -> e.getName()); System.out.println(str.get()); // qwe // faltMap(Function mapper):map类似,要求返回值必须是Optional,方法中必须用Optional包装 Optional<String> str2 = op.flatMap(e -> Optional.of(e.getName())); System.out.println(str2.get()); }
接口中的默认方法与静态方法
接口默认方法的”类优先”原则:
若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
接口冲突。
如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突。
接口中的默认方法:
public interface MyFun { public static void show(){ System.out.println("接口中的静态方法"); } }
新时间日期 API
LocalDate、LocalTime、LocalDateTime
LocalDateTime
@Test public void test() { /* * LocalDateTime * */ LocalDateTime now = LocalDateTime.now(); System.out.println(now); // 2021-12-24T14:51:37.161 //LocalDataTime实例创建 LocalDateTime ldt = LocalDateTime.of(2021, 12, 23, 13, 23, 32); System.out.println(ldt); // 2021-12-23T13:23:32 //年份加 LocalDateTime ldt2 = ldt.plusYears(2); System.out.println(ldt2); // 2023-12-23T13:23:32 //月份减 LocalDateTime ldt3 = ldt.minusMonths(2); System.out.println(ldt3); // 2021-10-23T13:23:32 //获取时间的各个值 System.out.println(ldt.getYear()); // 2021 System.out.println(ldt.getMonthValue()); // 12 System.out.println(ldt.getDayOfMonth()); // 23 System.out.println(ldt.getHour()); // 13 System.out.println(ldt.getMinute()); // 23 System.out.println(ldt.getSecond()); // 32 }
Instant:时间戳(以Unix元年:1970年1月1日 00:00:00 到某个时间之间的毫秒值)
public class test { @Test public void test() { Instant ins = Instant.now(); // 默认获取UTC时区 System.out.println(ins); // 带偏移量,这里表示东八区 OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8)); System.out.println(odt); System.out.println(ins.toEpochMilli()); // 1640329758791 Instant instant = Instant.ofEpochSecond(60); // 1970-01-01T00:01:00Z System.out.println(instant); } }
计算时间间隔
- Duration:计算两个时间之间的间隔
- Period:计算两个日期之间的间隔
@Test public void test() { Instant ins1 = Instant.now(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Instant ins2 = Instant.now(); Duration duration = Duration.between(ins1, ins2); System.out.println(duration.toMillis()); // 1009 LocalDate ld1 = LocalDate.of(2021,12,12); LocalDate ld2 = LocalDate.now(); Period period = Period.between(ld1, ld2); System.out.println(period.getYears()); // 0 System.out.println(period.getMonths()); // 0 System.out.println(period.getDays()); // 12 System.out.println(); }
日期的操纵:
TemporalAdiuster :时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。
public class test { @Test public void test() { LocalDateTime ldt = LocalDateTime.now(); LocalDateTime ldt2 = ldt.withDayOfMonth(10); // 将日期设置为10 System.out.println(ldt); // 2021-12-24T16:23:16.451 System.out.println(ldt2); // 2021-12-10T16:22:40.417 LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)); // 下一个周日 System.out.println(ldt3); // 2021-12-26T16:22:40.417 //自定义:下一个工作日志 LocalDateTime ldt5 = ldt.with( l -> { LocalDateTime ldt4 = (LocalDateTime) l; DayOfWeek dow = ldt4.getDayOfWeek(); if (dow.equals(DayOfWeek.FRIDAY)) { return ldt4.plusDays(3); } else if (dow.equals(DayOfWeek.SATURDAY)) { return ldt4.plusDays(2); } else{ return ldt4.plusDays(1); } }); System.out.println(ldt5); // 2021-12-27T16:30:55.074 } }
DateTimeFormatter:格式化时间/日期
@Test public void test() { DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE; LocalDateTime ldt = LocalDateTime.now(); String strDate = ldt.format(dtf); System.out.println(strDate); // 2021-12-24 DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss"); String strDate2 = dtf2.format(ldt); System.out.println(strDate2); // 2021年12月24日 16:35:55 LocalDateTime newDate = LocalDateTime.parse(strDate2, dtf2); System.out.println(newDate); // 2021-12-24T16:38:58 }
时区的处理
@Test public void test() { LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Europe/Tallinn")); System.out.println(ldt); // 2021-12-24T10:43:37.135 LocalDateTime ldt2 = LocalDateTime.now(ZoneId.of("Europe/Tallinn")); ZonedDateTime zdt = ldt2.atZone(ZoneId.of("Europe/Tallinn")); System.out.println(zdt); // 2021-12-24T10:45:08.507+02:00[Europe/Tallinn] }