学妹问我 JDK1.8 的新特性,我这样帮她总结

0 引子

学妹:师兄师兄!我去面试被问到 JDK1.8 的新特性这个问题,不知道怎么回答啊!哭了哭了,师兄能帮我总结一下 JDK1.8 的新特性吗?
我:小意思!学妹你先坐下,且让师兄为你慢慢讲解(嘻嘻)

1 前言

了解 JDK1.8 的新特性,无论是面试需要还是工作要求,对我们都是非常重要的,本文会介绍几种 JDK1.8 的新特性,希望能够对大家有所帮助。

2 JDK1.8 特性:Lambda 表达式

Lambda 表达式是一个匿名函数,Lambda 表达式没有声明的方法,也没有访问修饰符、返回值声明和名字,用于帮助我们写出更简洁、更灵活的代码。

Lambda 表达式建立在函数式接口之上,那什么是函数式接口呢?只包含一个抽象方法的接口就被称为函数式接口,我们可以通过 Lambda 表达式来创建函数式接口的对象。

下面我们举个栗子,相信大家经常使用排序功能吧,这里我们使用 Comparator 接口对 Lambda 表达式的使用做一个测试。

我们先使用匿名内部类来实现 Comparator 接口。

public class Test {

	public static void main(String[] args){
		String[] array = {"apple","string","ss"};
		
		//匿名内部类的使用
		Arrays.sort(array,new Comparator<String>() {

			@Override
			public int compare(String o1, String o2) {
				return o1.length() - o2.length();
			}
			
		});  
		
	}
	
}

然后再使用 Lambda 表达式实现 Comparator 接口。

public class Test {

	public static void main(String[] args){
		String[] array = {"apple","string","ss"};
		//使用 Lambda 表达式
		Arrays.sort(array, (String s1, String s2) -> (s1.length() - s2.length())); 	
	}
	
}

可以发现使用 Lambda 表达式更为简洁。

我们之前提到过 Lambda 表达式建立在函数式接口之上,为什么呢?其实是为了保证唯一,我们上面的 Comparator 接口只有一个抽象方法,使用 Lambda 表达式可以代表那个方法,如果接口有多个抽象方法,谁知道 Lambda 表达式代表的是哪一个抽象方法呢?

3 JDK1.8 特性:函数式接口

我们在介绍 Lambda 表达式时其实已经讲得很清楚了,函数式接口是只有一个抽象函数的接口,在 JDK1.8 中提供了@FunctionalInterface 注解来检查函数式接口的合法性。

下面我们介绍几种基础的函数式接口:

Consumer

消费者接口,有参无返回值,用于消费数据。

Consumer 接口中提供了 accept 抽象方法,accept 方法会接受一个变量,即使用该函数式接口时会提供数据,只需接受使用即可。

public class Test {
	
	//使用消费型接口
	public static void change(Consumer<String> con,String str) {
		con.accept(str);
	}

	public static void main(String[] args){
		change((str) -> System.out.println(str),"Hello World!");
	}
	
}

Function

函数式接口,有参有返回值,提供转换功能。

Function 接口中提供了 apply 抽象方法,apply 接受 T 类型数据并返回 R 类型数据。

public class Test {
	
	//使用函数式接口
	public static Integer change(Function<Integer, Integer> fun,Integer i) {
		return fun.apply(i);
	}

	public static void main(String[] args){
		Integer num = change((x) -> 2 * x,100);
		System.out.println(num);
	}
	
}

Predicate

断言型接口,有参有返回值,其中会返回一个布尔类型的变量,提供断言、判断功能。

Predicate 接口中提供了 test 抽象方法,对传入的数据进行判断,返回 boolean 类型。

public class Test {
	
	//使用断言型接口
	public static boolean change(Predicate<String> pre,String str) {
		return pre.test(str);
	}

	public static void main(String[] args){
		boolean res = change((str) -> str.equals("Hello"),"Hello");
		System.out.println(res);
	}
	
}

Supplier

供给型接口,无参有返回值,用于生产数据。

Supplier 接口中提供了 get 抽象方法,用于返回数据。

public class Test {
	
	//使用供给型接口
	public static String change(Supplier<String> sup) {
		return sup.get();
	}

	public static void main(String[] args){
		String str = change(() -> "Hello World!");
		System.out.println(str);
	}
	
}

函数式接口事实上是为了更加方便地使用 Lambda 表达式,有了 JDK1.8 提供的函数式接口,我们不需要手动创建函数式接口,直接使用即可。

4 JDK1.8 特性:方法引用

我们经常使用 Lambda 表达式来创建匿名方法,但有时我们只是调用一个已经存在的方法而已。在 JDK1.8 中,可以通过方法引用来简写 Lambda 表达式中已经存在的方法。

方法引用是一种更简洁易懂的 Lambda 表达式,其操作符为双冒号 :: 。方法引用是用来直接访问类或者实例的已经存在的方法,它提供了一种引用而不执行方法的方式。

如果 Lambda 表达式仅仅调用一个已存在的方法而不做任何其它事,通过一个方法名字来引用这个已存在的方法也许会更加清晰,Java 8 的方法引用允许我们这样操作。

下面我们举个栗子,对一个 Integer 的封装类进行排序。

Integer 的封装类代码如下:

class NewInteger{
	
	private Integer num;
	
	public NewInteger(Integer num) {
		this.num = num;
	}
	
	public int getNum() {
		return num;
	}
	
	public static int compare(NewInteger a,NewInteger b) {
		return a.getNum() - b.getNum();
	}
}

对其进行排序,我们可以使用匿名内部类写法:

public class Test {

	public static void main(String[] args){
		NewInteger[] array = {new NewInteger(1),new NewInteger(12),new NewInteger(-1),new NewInteger(34)};
		//匿名内部类写法
		Arrays.sort(array, new Comparator<NewInteger>() {
			@Override
			public int compare(NewInteger o1, NewInteger o2) {
				return o1.getNum() - o2.getNum();
			}
        });
	}
	
}

我们可以发现,Comparator 是一个函数式接口,故我们可以使用 Lambda 表达式,写法如下:

public class Test {

	public static void main(String[] args){
		NewInteger[] array = {new NewInteger(1),new NewInteger(12),new NewInteger(-1),new NewInteger(34)};
		//Lambda表达式写法
		Arrays.sort(array, (NewInteger a,NewInteger b) -> {
			return a.getNum() - b.getNum();
		});
	}
	
}

其实,我们之前在 Integer 的封装类中已经定义了一个比较方法,因此我们可以直接使用该比较方法:

public class Test {

	public static void main(String[] args){
		NewInteger[] array = {new NewInteger(1),new NewInteger(12),new NewInteger(-1),new NewInteger(34)};
		//Lambda表达式写法
		Arrays.sort(array, (a ,b) -> NewInteger.compare(a, b));
	}
	
}

由于 Lambda 表达式调用了一个已存在的方法,因此,我们可以使用方法引用来替代这个 Lambda 表达式。

public class Test {

	public static void main(String[] args){
		NewInteger[] array = {new NewInteger(1),new NewInteger(12),new NewInteger(-1),new NewInteger(34)};
		//方法引用写法
		Arrays.sort(array, NewInteger::compare);
	}
	
}

方法引用 NewInteger::compare 与 Lambda 表达式 (a ,b) -> NewInteger.compare(a, b) 是等价的。

方法引用的标准形式是 类名::方法名,一共有四种形式的方法引用,分别为引用静态方法,引用某个对象的实例方法,引用某个类型的任意对象的实例方法,引用构造方法。我们之前所举例子就是一个静态方法引用。

5 JDK1.8 特性:Stream API

由于文章篇幅关系,本文不准备详细介绍 Stream 的各类 API,只会介绍一下 Stream 的概念,并举一个使用 Stream 的小例子,关于 Stream 的各类 API,会在我的下一篇文章为大家一一介绍。

Stream 是一个处理集合的关键抽象概念,可以对集合进行各种操作,Stream API 为我们操作集合提供了强大的功能,同时操作简单,容易上手。

Stream 一般有如下三个操作步骤:

  1. 创建 Stream:从一个数据源(集合、数组)中获取流
  2. 中间操作:一个操作的中间链,对数据源的数据进行操作
  3. 终止操作:一个终止操作,执行中间操作链,并产生结果

注意,对流的操作完成后需要对其进行关闭。

如何理解 Stream?我们可以这么想,集合的要点在于数据,流的要点在于计算。Stream 既不会存储元素,也不会改变源对象,且会返回一个持有结果的新的 Stream,另外,Stream 的操作是延迟执行的,只有在需要结果时,才会执行。

我们现在实现一个小功能,计算集合中大于 10 的元素数量,在 JDK1.8 之前,我们一般这样实现:

public class Test {

	public static void main(String[] args){
		List<Integer> list = new ArrayList();
		list.add(34);
		list.add(7);
		list.add(2);
		list.add(66);
		list.add(-3);
		list.add(71);
		
		int count = 0;
		for (Integer integer : list) {
			if(integer > 10) count++;
		}
		System.out.println("集合中大于10的元素数量:" + count );
	}
	
}

如何使用 Stream API 的话,我们可以这样写代码:

public class Test {

	public static void main(String[] args){
		List<Integer> list = new ArrayList();
		list.add(34);
		list.add(7);
		list.add(2);
		list.add(66);
		list.add(-3);
		list.add(71);
		
		long count = list.stream().filter(i -> i > 10).count();
		System.out.println("集合中大于10的元素数量:" + count );
	}
	
}

使用 Stream API 一行代码就解决了,是不是很简单呢!

6 JDK1.8 特性:default 关键字

在 JDK1.8 之前,接口不能提供方法的实现,但在 JDK1.8 之后,我们可以为方法提供默认实现方法和静态方法,分别用关键字 default 和 static 修饰即可。

如果一个类既继承父类又实现接口时,两者方法名相同,类优先;如果实现两个同名方法的接口,则实现类必须手动声明默认实现哪个接口中的方法。

interface Car {
	
	void begin();
	
	//默认实现方法
	default void play() {
		System.out.println("正在开车");
	}
	
	//静态方法
	static void end() {
		System.out.println("结束开车");
	}
	
}

class Porsche implements Car {

	@Override
	public void begin() {
		System.out.println("保时捷启动");
	}
	
}

public class Test {

	public static void main(String[] args){
		Car porsche = new Porsche();
		porsche.begin();
		porsche.play();
		Car.end();
	}
	
}

7 总结

JDK1.8 的几个主要新特性我都简单介绍了一下,还有一些别的特性,例如 Optional,Date API 等,由于文章篇幅问题,只能一笔带过。面试能够讲到上面几点,通过应该是没问题的,但在工作中运用却是远远不够。冰冻三尺,非一日之寒,共勉!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章