Java 8 新特性(一)

151 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

1、Interface

interface 的设计初衷是面向抽象,提高扩展性。这也留有一点遗憾,Interface修改的时候,实现它的类也必须跟着修改。为了解决接口的修改与现有实现不兼容的问题。新 interface的方法可以用 default 或 static 修饰,这样就可以有方法体,实现类也不必重写此方法。

一个 interface 中可以有多个方法被它们修饰,这 2 个修饰符的区别主要也是普通方法和静态方法的区别。

  • default 修饰的方法,是普通实例方法,可以用this调用,可以被子类继承、重写。
  • static 修饰的方法,使用上和一般类静态方法一样。但它不能被子类继承,只能用Interface调用。
public interface InterfaceNew1 { 
    void f(); 
    static void sm(){ 
        System.out.println("interface 1 static 方法实现"); 
    } 
    default void def() { 
        System.out.println("interface 2 default 方法实现"); 
    } 
} 
public interface InterfaceNew2 { 
    default void def() { 
        System.out.println("interface 2 default 方法实现"); 
    } 
} 
public class InterfaceTest implements InterfaceNew1,InterfaceNew2 { 
    @Override public void f() { 
    
    } 
    @Override public void def() { 
        InterfaceNew1.super.def(); 
    } 
    public static void main(String[] args) { 
        InterfaceTest test = new InterfaceTest(); 
        test.def(); 
    } 
}

注意事项:

如果有一个类既实现了 InterfaceNew 接口又实现了 InterfaceNew1接口,它们都有def(),并且 InterfaceNew 接口和 InterfaceNew1接口没有继承关系的话,这时就必须重写def()。不然的话,编译的时候就会报错。

2、Functional Interface 函数式接口

函数式接口,也称 SAM 接口,即 Single Abstract Method interfaces (单个抽象方法接口),有且只有一个抽象方法,但可以有多个非抽象方法的接口。

在 java 8 中专门有一个包放函数式接口java.util.function,该包下的所有接口都有@FunctionalInterface 注解,提供函数式编程。 在其他包中也有函数式接口,其中一些没有@FunctionalInterface 注解,但是只要符合函数式接口的定义就是函数式接口,与是否有 @FunctionalInterface注解无关,注解只是在编译时起到强制规范定义的作用。其在 Lambda 表达式中有广泛的应用。

2.1 @FunctionalInterface 注解

如果一个类型被这个注解修饰,那么必须满足如下的条件才能编译通过:

  • 这个类型必须是一个 interface 接口;

  • 接口有且只有一个抽象方法,但可以用多个非抽象方法;

2.2 自定义函数接口

@FunctionalInterface 
public interface MyFunctionInterface { 
    void study();
} 
public class Main { 
    public static void main(String[] args) { 
        MyFunctionInterface myFunctionInterface = () -> System.out.println("Hello Function Interface!"); 
        myFunctionInterface.study(); 
    } 
}

2.3 内置四大函数接口

2.3.1 消费型接口

Consumer 执传入一个T类型的参数,执行一段处理函数,无返回结果。

@Test 
public void consumerTest() throws Exception { 
        Consumer<String> consumer = s -> System.out.println("Consumer:"+s);
        consumer.accept("consumer"); 
}

2.3.2 供给型接口

Suppiler 执行一段处理函数,无输入,返回一个T类型的结果。


/**
 * 供给型
 * @throws Exception
 */
@Test
public void supplierTest() throws Exception {
   //Supplier<String> supplier = () -> {return "Supplier";};
   Supplier<String> supplier = () -> "Supplier";
   System.out.println(supplier.get());
}

2.3.3 断定型接口

Predicate 传入一个T类型的参数,执行一段处理函数,返回一个boolean型的结果。

/**
 * 断定型
 * @throws Exception
 */
@Test
public void predicateTest() throws Exception {
   //Predicate<String> predicate = s -> "Predicate".equals(s);
   Predicate<String> predicate = "Predicate"::equals;
   System.out.println(predicate.test("A"));
}

2.3.4 函数型接口

Function 传入一个T类型的参数,执行一段处理函数,返回一个R类型的结果。

/**
 * 函数型
 * @throws Exception
 */
@Test
public void functionTest() throws Exception {
   Function<String,String> function = str -> "Function:"+str;
   System.out.println(function.apply("Hello"));
}

3、Lambda表达式

Lambda表达式是一个匿名的函数,Java 8允许把函数作为参数传递进方法中。使用Lambda表达式可以使代码变的更加简洁紧凑。让Java也能支持简单的函数式编程。

3.1 语法格式

(parameters) -> expression 或 (parameters) ->{ statements; }

3.2 Lambda 应用

3.2.1 替代匿名内部类

1)Runable接口

@Test 
public void runnableTest() throws Exception { 
    new Thread(new Runnable() { 
        @Override 
        public void run() { 
            System.out.println("The runnable now is using!"); 
        } 
    }).start(); 
    new Thread(() -> System.out.println("It's a lambda function!")).start(); 
}

2)Comparator接口

@Test 
public void comparatorTest() throws Exception { 
    List<Integer> integers = Arrays.asList(1, 2, 3); 
    Collections.sort(integers, new Comparator<Integer>() { 
        @Override 
        public int compare(Integer o1, Integer o2) { 
            return o1-o2; 
        } 
    }); 
    //lambda 
    Collections.sort(integers,((o1, o2) -> o1 - o2)); 
    // 分解 
    Comparator<Integer> comparator = ((o1, o2) -> o1- o2);
    Collections.sort(integers,comparator); 
}

3.2.2 集合迭代

@Test 
public void lambdaFor () throws Exception { 
    List<String> strings = Arrays.asList("1","2","3"); 
    //传统foreach 
    for (String s: strings) { 
        System.out.println(s); 
    } 
    //Lambda foreach 
    strings.forEach((s) -> System.out.println(s)); 
    strings.forEach(System.out::println); 
    Map<Integer,String> map = new HashMap<Integer,String>(); 
    map.put(1,"Hello"); 
    map.put(2,"Lambda"); 
    map.put(3,"Opm"); 
    map.forEach((k,v) -> System.out.println(v)); 
}

3.2.3 方法的应用

Java 8允许使用 :: 关键字来传递方法或者构造函数引用,无论如何,表达式返回的类型必须是 function-interface。

public class LambdaClassSuper { 
    LambdaInterface sf(){ 
        return null; 
    } 
} 
public class LambdaClass extends LambdaClassSuper { 
    public static LambdaInterface staticF() { 
        return null; 
    } 
    public LambdaInterface f() { 
        return null; 
    } 
    void show() { 
        //1.调用静态函数,返回类型必须是functional-interface 
        LambdaInterface t = LambdaClass::staticF; 
        //2.实例方法调用 
        LambdaClass lambdaClass = new LambdaClass(); 
        LambdaInterface lambdaInterface = lambdaClass::f; 
        //3.超类上的方法调用 
        LambdaInterface superf = super::sf; 
        //4. 构造方法调用 
        LambdaInterface tt = LambdaClassSuper::new; 
    } 
}

3.2.4 访问变量

lambda 表达式可以引用外边的变量,但是该变量默认拥有final属性,不能被修改,如果修改,编译时就报错。

int i = 0; Collections.sort(strings, (Integer o1, Integer o2) -> o1 - i);

4、Stream

4.1 概念

Java 新增了 java.util.stream 包,它和之前的流大同小异。之前接触最多的是资源流,比如java.io.FileInputStream,通过流把文件从一个地方输入到另一个地方,它只是内容搬运工,对文件内容不做任何CRUD。 Stream依然不存储数据,不同的是它可以检索(Retrieve)和逻辑处理集合数据、包括筛选、排序、统计、计数等。可以想象成是 Sql 语句。 它的源数据可以是 Collection、Array 等。由于它的方法参数都是函数式接口类型,所以一般和 Lambda 配合使用。

Stream流分为 stream 串行流和 parallelStream 并行流。并行流可多线程执行。

4.2 常用方法

java.util.stream.Stream

/** * 返回一个串行流 */ 
default Stream<E> stream() 
/** * 返回一个并行流 */ 
default Stream<E> parallelStream() 
/** * 返回T的流 */ 
public static<T> Stream<T> of(T t) 
/** * 返回其元素是指定值的顺序流。 */ 
public static<T> Stream<T> of(T... values) { 
    return Arrays.stream(values); 
} 
/** * 过滤,返回由与给定predicate匹配的该流的元素组成的流 */ 
Stream<T> filter(Predicate<? super T> predicate); 
/** * 此流的所有元素是否与提供的predicate匹配。 */ 
boolean allMatch(Predicate<? super T> predicate) 
/** * 此流任意元素是否有与提供的predicate匹配。 */ 
boolean anyMatch(Predicate<? super T> predicate); 
/** * 返回一个 Stream的构建器。 */ 
public static<T> Builder<T> builder(); 
/** * 使用 Collector对此流的元素进行归纳 */ 
<R, A> R collect(Collector<? super T, A, R> collector); 
/** * 返回此流中的元素数。 */ 
long count(); 
/** * 返回由该流的不同元素(根据 Object.equals(Object) )组成的流。 */ 
Stream<T> distinct(); 
/** * 遍历 */ 
void forEach(Consumer<? super T> action); 
/** * 用于获取指定数量的流,截短长度不能超过 maxSize 。 */ 
Stream<T> limit(long maxSize); 
/** * 用于映射每个元素到对应的结果 */ 
<R> Stream<R> map(Function<? super T, ? extends R> mapper); 
/** * 根据提供的 Comparator进行排序。 */ 
Stream<T> sorted(Comparator<? super T> comparator); 
/** * 在丢弃流的第一个 n元素后,返回由该流的 n元素组成的流。 */ 
Stream<T> skip(long n); 
/** * 返回一个包含此流的元素的数组。 */ 
Object[] toArray(); 
/** * 使用提供的 generator函数返回一个包含此流的元素的数组,以分配返回的数组,以及分区执行或调整大小可能需要的任何其他数组。 */ 
<A> A[] toArray(IntFunction<A[]> generator); 
/** * 合并流 */ 
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

4.3 使用

@Test
public void streamApi() throws Exception {
   List<String> strings = Arrays.asList("Hell", "Zero", "Open","");
   // 返回符合条件的Stream
   Stream<String> stringStream = strings.stream().filter(s -> !"".equals(s));
   System.out.println("满足条件的数量:"+stringStream.count());
   //遍历打印元素
   strings.stream().forEach(System.out::println);
   //limit 获取到一个元素的stream
   Stream<String> limit = strings.stream().limit(1);
   String[] array = limit.toArray(String[]::new);
   System.out.println(array[0]);

   //map 对每个元素操作,返回新的流
   Stream<String> map = strings.stream().map(s -> "[" + s + "]");
   //sorted 排序并打印
   map.sorted().forEach(s -> System.out.print(s));

   // collect
   List<String> collect = strings.stream().filter("Open"::equals).collect(Collectors.toList());
   System.out.println(collect);
   //把list转为string 各个元素用,号隔开
   String str = strings.stream().filter(s -> !s.isEmpty()).collect(Collectors.joining(","));
   System.out.println(str);

   // 数组的统计
   List<Integer> number = Arrays.asList(1,2,5,4);
   // int型 摘要统计
   IntSummaryStatistics intSummaryStatistics = number.stream().mapToInt(x -> x).summaryStatistics();
   System.out.println("列表中最大的数:"+intSummaryStatistics.getMax());
   System.out.println("列表中最小的数:"+intSummaryStatistics.getMin());
   System.out.println("平均数:"+intSummaryStatistics.getAverage());
   System.out.println("所有数之和:"+intSummaryStatistics.getSum());

   // concat 合并流
   List<String> strings2 = Arrays.asList("Tom","Jack");
   long count = Stream.concat(strings.stream(), strings2.stream()).count();
   System.out.println("流合并后元素的数量:"+count);

}

@Test
public void streamApiError() throws Exception {
   /**
    * TODO: 一个stream只能操作一次,不能断开,否则会报错
    * java.lang.IllegalStateException: stream has already been operated upon or closed
    */
   List<String> strings = Arrays.asList("Hell", "Zero", "Open");
   Stream<String> stream = strings.stream();
   // 第一次使用后流已关闭
   //stream.limit(2);
   // 第二次使用 报错
   //stream.forEach(System.out::println);

   stream.limit(2).forEach(System.out::println);
}

4.4 延迟执行

在执行返回 Stream 的方法时,并不立刻执行,而是等返回一个非 Stream 的方法后才执行。因为拿到 Stream 并不能直接用,而是需要处理成一个常规类型。这里的 Stream 可以想象成是二进制流。

@Test 
public void laziness() throws Exception { 
    List<String> strings = Arrays.asList("Hell", "Zero", "Open"); 
    Stream<String> stream = strings.stream().filter(new Predicate<String>() { 
        @Override 
        public boolean test(String s) { 
            System.out.println("Predicate Run"); 
            return true; 
        } 
    }); 
    System.out.println("count 执行!"); 
    stream.count(); 
} 
    
结果: 
    count 执行! 
    Predicate Run 
    Predicate Run 
    Predicate Run

Java 8 型特性(二)