玩转Java 8

251 阅读7分钟

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

概述

  • Lambda表达式 - 允许把函数作为方法的参数。
  • 方法引用 - lambda基础上进一步简化。
  • 默认方法 - 接口里面有实现的方法。
  • Stream API - 将元素集合看作流的一种Java表达的高阶抽象
  • Date Time API - 加强对日期和时间的处理。
  • Optional 类 - 用来解决空指针异常。
  • Nashorn,JavaScript 引擎 - 它允许我们在JVM上运行特定的javascript应用。
  • 新工具 - 新的编译工具。

1. Lambda 表达式

Lambda 表达式,也称闭包,Java 8重要特性。 Lambda 允许把函数作为方法的参数。 Lambda 表达式使代码更加简洁。

1.1 Lambda 转化要求

替换参数必须是函数式接口

PS:

  • 函数式接口中只有一个抽象方法。
  • 默认方法有实现,不是抽象方法。
  • 重写超类Object类中public方法不算。
  • @FunctionalInterface 注解表示该类满足函数式接口的要求。

1.2 Lambda 简单例子

public class Demo{  
    public static void main(String[] args){
        
        List<String> names1 = new ArrayList<String>();
        names1.add("Google "); 
        names1.add("Runoob ");
        names1.add("Taobao "); 
        names1.add("Baidu ");
        names1.add("Sina "); 

        List<String> names2 = new ArrayList<String>();
        names2.add("Google ");
        names2.add("Runoob ");
        names2.add("Taobao "); 
        names2.add("Baidu "); 
        names2.add("Sina ");
        
        // 普通写法
        Collections.sort(names1, new Comparator<String>(){
            @Override
            public int compare(String s1, String s2){
                return s1.compareTo(s2);
            }
        });
        System.out.println("普通写法: ");
        System.out.println(names1);
        
        // Lambda写法
        Collections.sort(name2, (s1, s2) - > s1.compareTo(s2));
        System.out.println("Lambda写法: ");
        System.out.println(names2);
    }
}

结果

普通写法: [Baidu , Google , Runoob , Sina , Taobao ] Lambda写法: [Baidu , Google , Runoob , Sina , Taobao ]

2. 方法引用

方法引用 通过方法的名字来指定一个方法。 方法引用 可以简化代码。 方法引用 使用一对冒号::

2.1 方法引用转换要求

  • 方法引用及构造器引用可以理解为lambda的另一种表现形式。
  • 方法引用被调用的方法的参数列表和返回值要与函数式接口中抽象方法参数列表和返回值对应一致。(构造器引用一样)
  • 还有一种情况是,类::实例方法,此情况第一个参数是实例的调用者,第二个参数是实例方法的第二参数。

2.2 方法引用实例

  1. 对象::实例方法名
@Test
void test1(){
    // lambda写法
    Consumer<String> con2 = x -> System.out.println(x);
   
    // 方法引用写法
    Consumer<String> con2 = System.out::println
}

Consumer接口

@FunctionalInterface 
public interface Consumer<T> { 
    void accept(T t); 
}
  1. 类::静态方法
@Test
void test2(){
    // lambda写法
    Comparator<Integer>  com1 = (x, y) -> Integer.compare(x, y);
    
    // 方法引用写法
    Comparator<Integer> com2 = Integer::compare;
}

Comparator接口

@FunctionalInterface 
public interface Consumer<T> { 
    int compare(T o1, T o2);
}

Integer的compare方法

public final class Integer extends Number implements Comparable<Integer> { 
    public static int compare(int x, int y) { 
        return (x < y) ? -1 : ((x == y) ? 0 : 1); 
    } 
}

被调用的方法参数列表和返回值类型要与函数式接口的抽象方法参数、返回值保持一致。

  1. 类::实例方法名
@Test
void test3(){ 
    // lambda写法
    BiPredicate<String,String> bp = (x,y) -> x.equals(y);
    
    // 方法引用写法
    BiPredicate<String,String> bp = String::equals;
    // 第一个参数是实例方法的调用者
    // 第二个参数的实例方法的第二参数的情况可以这样用
}

BiPredicate接口

@FunctionalInterface 
public interface BiPredicate<T, U> { 
    boolean test(T t, U u); 
}
  1. 构造器引用 类::new
@Test
void test4(){
    // lambda写法
    Supplier<Person> supplier1 = ()->new Person();
    
    // 构造器引用写法
    Supplier<Person> supplier = Person::new;
}

Supplier接口

@FunctionalInterface 
public interface Supplier<T> { 
    T get(); 
}

Person类

@Data 
public class Person implements Serializable { 
    private static final long serialVersionUID = -7008474395345458049L; 
    private String name; 
    private int age; 
    
    // 调用哪个构造器是函数式接口决定,即Supplier中的get是无参的,所有调无参构造器。
    public Person() { } 
    
    public Person(String name, int age) { 
        this.name = name; 
        this.age = age; 
    }
}
  1. 数组引用 Type::new
@Test
void test5(){
    // lambda写法
    Function<Integer,String[]> fun = x -> new String[x];
    
    // 数组引用写法
    Function<Integer, String[]> fun = String[]::new;
}

Function接口部分内容

@FunctionalInterface 
public interface Function<T, R> { 
    R apply(T t); 
}

3. 默认方法

默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。 方法名前加default就可以在接口实现默认方法。

为什么要有这个特性? 为了解决接口的修改与现有的实现不兼容的问题。

3.1 语法例子

public interface Vehicle{
    default void print(){
        System.out.println("我是一辆车!");
    }
}

3.2 多个默认方法

说明情况:一个类实现了多个接口,且这些接口有相同的默认方法,此情况下解决办法有两个:

public interface Vehicle { 
    default void print(){ 
    System.out.println("我是一辆车!"); 
    } 
} 
public interface FourWheeler { 
    default void print(){ 
    System.out.println("我是一辆四轮车!"); 
    } 
}
  1. 自己创建默认方法进行覆盖
public class Car implements Vehicle,FourWheeler{
    default void print(){
        System.out.println("我是一辆四轮车");
    }
}
  1. 使用super来调用指定接口的默认方法
public class Car implements Vehicle,FourWheeler{
    public void println(){
        Vehicle.super.print();
    }
}

3.3 静态默认方法

Java 8 的另一个特性是接口可以声明并实现静态方法。

public interface Vehicle{
    static void blowHorn(){
        System.out.println("按喇叭!");
    }
}
public class Car implements Vehicle{
    public void print(){
        Vehicle.blowHorn();
    }
}

调用

public class Test{
    public static void main(String[] args){
        Vehicle v = new Car();
        v.print();
    }
}

结果

按喇叭!

4. Steam API

流Steam,让你可以以一种声明的方式处理数据。 使代码简洁。

4.1 Stream 介绍

Steam 是一个来自数据源的元素队列,并支持聚合操作

数据源:集合,数组, I/O channel,产生器gengrator等。 聚合操作:类似SQL语句的操作,如 filter,map,reduce,find等

中间操作都会返回流对象本身,有点像建造者模式中的一种。对集合遍历Steam提供了内部迭代,不同于Iterator或for-each的显示外部迭代。

4.2 转换生成流

集合接口有两种方法生成流

  • steam() - 为集合创建串行流
  • parallelSteam() - 为集合创建并行流
4.3 Steam提供的方法

forEach : 迭代流中的每个数据。

Random  random = new Random();
random.ints().limit(10).forEach(System.out::println);

map : 映射每个元素到对应的结果

List<Integer> numbers = Arrays.asList(3, 2, 3, 7, 3, 5);
// 获取对应平方数
List<Integer> squaresList = numbers.stream().map.(i -> i*i).distinct().collect(Collectors.toList);

distinct 过滤重复元素

filter :设置条件,过滤元素

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); 
// 获取空字符串的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();

limit : 获取指定数量

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

sorted : 对流开始排序

Random random = new Random();
random.ints().limit(10.sorted().forEach(System.out::println));

parallelSteam 并行流 : 并行流,多线程处理,非线程安全。

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); 
// 获取空字符串的数量 
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();

串行流: 适合存在线程安全问题、阻塞任务、重量借任务,需要使用同一事务的逻辑。 并行流:适合没有线程安全问题,较单纯的数据处理任务。

Collectors :实现了很多规约操作,如流转集合,聚合元素。可用于返回列表或字符串


List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); 
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); 

System.out.println("筛选列表: " + filtered); String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", ")); 
System.out.println("合并字符串: " + mergedString);

统计 :产生统计结果的收集器,主要用于int,double,long等基本类型上。


List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); 

IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics(); 

System.out.println("列表中最大的数 : " + stats.getMax()); 
System.out.println("列表中最小的数 : " + stats.getMin()); 
System.out.println("所有数之和 : " + stats.getSum()); 
System.out.println("平均数 : " + stats.getAverage());

5. Date Time API

5.1 本地化日期时间 API


import java.time.LocalDate; 
import java.time.LocalTime; 
import java.time.LocalDateTime;
import java.time.Month; 
public class Java8Tester { 
    public static void main(String args[]){ 
    Java8Tester java8tester = new Java8Tester();
    java8tester.testLocalDateTime(); 
} 
public void testLocalDateTime(){ 
// 获取当前的日期时间 LocalDateTime 
currentTime = LocalDateTime.now(); 
System.out.println("当前时间: " + currentTime); 
LocalDate date1 = currentTime.toLocalDate(); System.out.println("date1: " + date1); 
Month month = currentTime.getMonth(); 
int day = currentTime.getDayOfMonth(); 
int seconds = currentTime.getSecond(); 
System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds); 
LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012); 
System.out.println("date2: " + date2); 
// 12 december 2014 
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12); System.out.println("date3: " + date3); 
// 22 小时 15 分钟 
LocalTime date4 = LocalTime.of(22, 15); System.out.println("date4: " + date4); 
// 解析字符串 
LocalTime date5 = LocalTime.parse("20:15:30"); System.out.println("date5: " + date5); } }

5.2 使用时区的日期时间API


import java.time.ZonedDateTime; 
import java.time.ZoneId; 

public class Java8Tester { 
    public static void main(String args[]){ 
    Java8Tester java8tester = new Java8Tester(); 
    java8tester.testZonedDateTime(); 
} 
public void testZonedDateTime(){ 
    // 获取当前时间日期 
    ZonedDateTime date1 = ZonedDateTime.parse("2021-11-03T10:15:30+05:30[Asia/Shanghai]"); 
    System.out.println("date1: " + date1); 
    ZoneId id = ZoneId.of("Europe/Paris"); 
    System.out.println("ZoneId: " + id); 
    ZoneId currentZone = ZoneId.systemDefault(); 
    System.out.println("当期时区: " + currentZone); 
    } 
}

date1: 2021-11-03T10:15:30+08:00[Asia/Shanghai] ZoneId: Europe/Paris 当期时区: Asia/Shanghai

6. Optional 类

可以为null的容器对象。若值存在isPresent()方法,调get()则返回该对象。 用于解决空指针异常。