> 文章列表 > Java8 新特性

Java8 新特性

Java8 新特性

Interface

nterface 的设计初衷是面向抽象,提高扩展性。这也留有一点遗憾,Interface 修改的时候,实现它的类也必须跟着改。

为了解决接口的修改与现有的实现不兼容的问题。新 interface 的方法可以用default 或 static修饰,这样就可以有方法体,实现类也不必重写此方法。

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

  1. default修饰的方法,是普通实例方法,可以用this调用,可以被子类继承、重写。
  2. static修饰的方法,使用上和一般类静态方法一样。但它不能被子类继承,只能用Interface调用。

我们来看一个实际的例子。

public interface Interface1 {static void fun1() {System.out.println("Interface1提供的方式实现");}static void fun2() {System.out.println("Interface1提供的方式实现");}default void def1() {System.out.println("Interface1 default1方法");}default void def2() {System.out.println("Interface1 default2方法");}//必须要实现类重写void func();}public interface Interface2 {default void def1() {System.out.println("Interface2 default1方法");}
}

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

public class Test implements Interface1, Interface2{@Overridepublic void def1() {Interface1.super.def1();}@Overridepublic void func() {}
}

functional interface 函数式接口

定义:也称 SAM 接口,即 Single Abstract Method interfaces,有且只有一个抽象方法,但可以有多个非抽象方法的接口。

在 java 8 中专门有一个包放函数式接口java.util.function,该包下的所有接口都有 @FunctionalInterface 注解,提供函数式编程。

在其他包中也有函数式接口,其中一些没有@FunctionalInterface 注解,但是只要符合函数式接口的定义就是函数式接口,与是否有@FunctionalInterface注解无关,注解只是在编译时起到强制规范定义的作用。其在 Lambda 表达式中有广泛的应用

Lambda 表达式

Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。让 java 也能支持简单的函数式编程。

Lambda 表达式是一个匿名函数,java 8 允许把函数作为参数传递进方法中。

Java8 的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。当开发者在编写Lambda表达式时,也会随之被编译成一个函数式接口。下面这个例子就是使用Lambda语法来代替匿名的内部类,代码不仅简洁,而且还可读。

没有使用Lambda的老方法:

button.addActionListener(new ActionListener(){public void actionPerformed(ActionEvent actionEvent){System.out.println("Action detected");}
});

使用Lambda:

button.addActionListener( actionEvent -> { System.out.println("Action detected");
});

让我们来看一个更明显的例子。

不采用Lambda的老方法:

Runnable runnable1=new Runnable(){
@Override
public void run(){System.out.println("Running without Lambda");
}
};

使用Lambda:

Runnable runnable2=()->System.out.println("Running from Lambda");

正如你所看到的,使用Lambda表达式不仅让代码变的简单、而且可读、最重要的是代码量也随之减少很多。然而,在某种程度上,这些功能在Scala等这些JVM语言里已经被广泛使用。

并不奇怪,Scala社区是难以置信的,因为许多Java 8里的内容看起来就像是从Scala里搬过来的。在某种程度上,Java 8的语法要比Scala的更详细但不是很清晰,但这并不能说明什么,如果可以,它可能会像Scala那样构建Lambda表达式。

一方面,如果Java继续围绕Lambda来发展和实现Scala都已经实现的功能,那么可能就不需要Scala了。另一方面,如果它只提供一些核心的功能,例如帮助匿名内部类,那么Scala和其他语言将会继续茁壮成长,并且有可能会凌驾于Java之上。其实这才是最好的结果,有竞争才有进步,其它语言继续发展和成长,并且无需担心是否会过时。

语法格式

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

Lambda 实战

替代匿名内部类

过去给方法传动态参数的唯一方法是使用内部类。比如

1.Runnable 接口

new Thread(new Runnable() {@Overridepublic void run() {System.out.println("The runable now is using!");}
}).start();
//用lambda
new Thread(() -> System.out.println("It's a lambda function!")).start();

2.Comparator 接口

List<Integer> strings = Arrays.asList(1, 2, 3);Collections.sort(strings, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {return o1 - o2;}
});//Lambda
Collections.sort(strings, (Integer o1, Integer o2) -> o1 - o2);
//分解开
Comparator<Integer> comparator = (Integer o1, Integer o2) -> o1 - o2;
Collections.sort(strings, comparator);

3.Listener 接口

JButton button = new JButton();
button.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {e.getItem();
}
});
//lambda
button.addItemListener(e -> e.getItem());

4.自定义接口

上面的 3 个例子是我们在开发过程中最常见的,从中也能体会到 Lambda 带来的便捷与清爽。它只保留实际用到的代码,把无用代码全部省略。那它对接口有没有要求呢?我们发现这些匿名内部类只重写了接口的一个方法,当然也只有一个方法须要重写。这就是我们上文提到的函数式接口,也就是说只要方法的参数是函数式接口都可以用 Lambda 表达式。

@FunctionalInterface
public interface Comparator<T>{}@FunctionalInterface
public interface Runnable{}

我们自定义一个函数式接口

@FunctionalInterface
public interface LambdaInterface {void f();
}
//使用
public class LambdaClass {public static void forEg() {lambdaInterfaceDemo(()-> System.out.println("自定义函数式接口"));}//函数式接口参数static void lambdaInterfaceDemo(LambdaInterface i){i.f();}
}

集合迭代

public static void main(String[] args) {List<String> strings = Arrays.asList("a", "b",  "c", "d", "e", "f", "g");//传统foreachfor (String s : strings) {System.out.println(s);}//Lambda foreachstrings.forEach((s) -> System.out.println(s));strings.forEach(System.out::println);//mapMap<String, String> map = new HashMap<>();map.put("1", "1");map.put("2", "2");map.put("3", "3");map.forEach((k,v) -> System.out.println(v));}

方法引用

Java 8 允许使用 :: 关键字来传递方法或者构造函数引用,无论如何,表达式返回的类型必须是 functional-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-interfaceLambdaInterface t = LambdaClass::staticF;//2.实例方法调用LambdaClass lambdaClass = new LambdaClass();LambdaInterface lambdaInterface = lambdaClass::f;//3.超类上的方法调用LambdaInterface superf = super::sf;//4. 构造方法调用LambdaInterface tt = LambdaClassSuper::new;}
}

访问变量

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

Stream

java 新增了 java.util.stream 包,它和之前的流大同小异。之前接触最多的是资源流,比如java.io.FileInputStream,通过流把文件从一个地方输入到另一个地方,它只是内容搬运工,对文件内容不做任何CRUD。

Stream依然不存储数据,不同的是它可以检索(Retrieve)和逻辑处理集合数据、包括筛选、排序、统计、计数等。可以想象成是 Sql 语句。

它的源数据可以是 Collection、Array 等。由于它的方法参数都是函数式接口类型,所以一般和 Lambda 配合使用。

流类型

  1. stream 串行流

  2. parallelStream 并行流,可多线程执行

常用方法

接下来我们看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)

实战

public static void main(String[] args) {List<String> strings = Arrays.asList("abc", "def", "hig", "klm", "abc", "def", "abc", "def");//返回符合条件的streamStream<String> stringStream1 = strings.stream().filter(s -> "abc".equals(s));Stream<java.lang.String> stringStream2 = strings.stream().filter("abc"::equals);//计算流符合条件的流的数量long count = stringStream1.count();//forEach遍历->打印元素strings.stream().forEach(System.out::println);//limit 获取到1个元素的streamStream<String> limit = strings.stream().limit(1);//toArray 比如我们想看这个limitStream里面是什么,比如转换成String[],比如循环String[] array = limit.toArray(String[]::new);//map 对每个元素进行操作返回新流Stream<String> stringStream3 = strings.stream().map(s -> s + "?");//sorted 排序并打印strings.stream().sorted().forEach(System.out::println);//Collectors collect 把abc放入容器中List<String> collect1 = strings.stream().filter(string -> "abc".equals(string)).collect(Collectors.toList());List<String> collect2 = strings.stream().filter("abc"::equals).collect(Collectors.toList());//把list转为string,各元素用,号隔开String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(","));//对数组的统计,比如用List<Integer> number = Arrays.asList(1, 2, 5, 4);IntSummaryStatistics statistics = number.stream().mapToInt((x) -> x).summaryStatistics();System.out.println("列表中最大的数 : "+statistics.getMax());System.out.println("列表中最小的数 : "+statistics.getMin());System.out.println("平均数 : "+statistics.getAverage());System.out.println("所有数之和 : "+statistics.getSum());//concat 合并流List<String> strings2 = Arrays.asList("nop", "qrs");Stream.concat(strings2.stream(),strings.stream()).count();//注意 一个Stream只能操作一次,不能断开,否则会报错。Stream stream = strings.stream();//第一次使用stream.limit(2);//第二次使用stream.forEach(System.out::println);//报错 java.lang.IllegalStateException: stream has already been operated upon or closed//但是可以这样, 连续使用stream.limit(2).forEach(System.out::println);}

延迟执行

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

我们下面分解一下 filter 方法。

 public static void main(String[] args) {List<String> strings = Arrays.asList("abc", "def", "ghi", "jkl");Stream<Integer> stream = strings.stream().filter(new Predicate() {@Overridepublic boolean test(Object o) {System.out.println("Predicate 执行");return true;}});System.out.println("count 执行");stream.count();}//执行结果
count 执行
Predicate 执行
Predicate 执行
Predicate 执行
Predicate 执行

按执行顺序应该是先打印 4 次「Predicate 执行」,再打印「count 执行」。实际结果恰恰相反。说明 filter 中的方法并没有立刻执行,而是等调用count()方法后才执行。

上面都是串行 Stream 的实例。并行 parallelStream 在使用方法上和串行一样。主要区别是 parallelStream 可多线程执行,是基于 ForkJoin 框架实现的,有时间大家可以了解一下 ForkJoin 框架和 ForkJoinPool。这里可以简单的理解它是通过线程池来实现的,这样就会涉及到线程安全,线程消耗等问题。下面我们通过代码来体验一下并行流的多线程执行。

@Test
public void parallelStreamTest(){List<Integer> numbers = Arrays.asList(1, 2, 5, 4);numbers.parallelStream() .forEach(num->System.out.println(Thread.currentThread().getName()+">>"+num));
}
//执行结果
main>>5
ForkJoinPool.commonPool-worker-2>>4
ForkJoinPool.commonPool-worker-11>>1
ForkJoinPool.commonPool-worker-9>>2

从结果中我们看到,for-each 用到的是多线程。

小结

从源码和实例中我们可以总结出一些 stream 的特点

  1. 通过简单的链式编程,使得它可以方便地对遍历处理后的数据进行再处理。

  2. 方法参数都是函数式接口类型

  3. 一个 Stream 只能操作一次,操作完就关闭了,继续使用这个 stream 会报错。

  4. Stream 不保存数据,不改变数据源