由于Java8作为工作中最常用的版本,今天重新梳理一下JAVA 8的所有新特性。

Interface

在 Java 8 之前,接口(interface)是非常“纯粹”的:它只能包含抽象方法(abstract methods),即只有声明,没有实现。而且在Interface修改的时候,实现它的类也必须修改。为了解决这个问题,Java 8 对接口做出了重大变革,最核心的新特性是引入了 默认方法(Default Methods)静态方法(Static Methods)。这样就可以又方法体,实现类也不必重写此方法。

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

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

下面通过一个例子展示下这两个新特性的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface InterfaceA {
static void doThis(){
System.out.println("A领导:做这个");
}

static void doThat(){
System.out.println("A领导:做那个");
}

default void goHome(){
System.out.println("A领导:回家");
}

default void goWork(){
System.out.println("A领导:上班");
}
// JAVA 8之前的方式
void f();
}

第二个接口

1
2
3
4
5
public interface InterfaceB {
default void goHome(){
System.out.println("B领导:回家");
}
}

实现类:

1
2
3
4
5
6
7
8
9
10
11
public class CoolBee implements InterfaceA,InterfaceB{
@Override
public void goHome() {
InterfaceB.super.goHome();
}

@Override
public void f() {
//这个必须实现,否则报错
}
}

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

这么一看,Interface越来越和abstract class像了,那他们的区别是什么呢?

特性 接口(Java 8+) 抽象类
多继承 一个类可以实现多个接口 一个类只能继承一个抽象类
状态存储 不能有成员变量(只能由常量 public static final) 可以有成员变量
构造函数 没有构造函数 可以有构造函数
设计目的 定义行为规范(能做什么) 定义身份和公用逻辑(is -a)

如果冲突怎么能?正如上面的例子,两个接口都有goHome(),并且都是默认方法,Java的规则如下:

  • 类优先:如果父类和接口方法重名,优先执行父类的方法。

  • 手动重写:如果两个接口冲突,编译器会强制要求你在实现类中显式重写该方法,你可以手动指定调用哪一个:InterfaceName.super.methodName();

注意,本次新增defaultstatic修饰的方法,是为了解决接口修改与实现不兼容的问题,而不是为了取代abstract class。在该用abstract class的场景还是要用abstract class的。要注意还是有差异性的。

functional interface函数式接口

函数式接口是指:有且仅有一个抽象方法(Abstract Method)的接口。

  • 核心准则:只要满足“只有一个抽象方法”的条件,它就是函数式接口。
  • 允许存在:默认方法(default)和静态方法(static),因为它们不属于抽象方法。
  • 允许重写Object 类中的公共方法(如 equals, toString),也不会增加抽象方法的计数。

@FunctionalInterface 注解

为了防止别人不小心往你的接口里加第二个抽象方法,Java 8 引入了这个注解。它不是强制的,但强烈建议加上。如果加了注解后接口不符合规范,编译器会直接报错。

1
2
3
4
5
6
7
8
9
@FunctionalInterface
public interface MyFunctionalInterface {
boolean validate(String s); // 唯一抽象方法

default void log(String s) {
//允许又默认方法
System.out.println("Logging: " + s);
}
}

解决的痛点就是在Java8之前,想要将一段逻辑代码传给一个方法,非常麻烦,例如需要通过匿名内部类,有了函数式接口,Lambda表达式才有了解析的目标,Java编译器会将Lambda表达式自动匹配到对应的函数时接口上。例如下面的这个例子:

1
2
3
4
@FunctionalInterface
public interface StringProcessor {
String process(String s); // 唯一的抽象方法
}

在任意类编写一个方法:

1
2
3
4
5
6
7
8
9
10
11
  public static void display(String text,StringProcessor processor){
String result = processor.process(text);
System.out.println("Processed String: " + result);
}

public static void main(String[] args) {
// StringProcessor toUpperCaseProcessor = s -> s.toUpperCase();
// StringProcessor addExclamationProcessor = s -> s + "!";
display("hello world", String::toUpperCase); // 输出: Processed String: HELLO WORLD
display("hello world", s-> s + "!"); // 输出: Processed String: hello world!
}

这里可以看到,display()不需要知道具体的逻辑,它只负责调用接口的process方法。

Java 8自带的四大核心函数式接口

为了让开发者不用每次都写新的接口,JDK 8 在 java.util.function 包下内置了 40 多个接口。最核心的是这四个:

接口名称 接收参数 返回值 核心方法 应用场景
Predicate T boolean test(T t) 用户过滤:list.removeIf(s -> s.isEmpty())
Consumer T void accept(T t) 用于消费:list.forEach(s -> System.out.println(s))
Function<T,R> T R apply(T t) 用于转换:将 String 转为 Integer
Supplier T get() 用于生产:工厂模式、懒加载获取对象

当你发现 Lambda 表达式只是简单地调用一个现有的方法时,可以使用更简洁的 方法引用。它是函数式接口的另一种表现形式。

  • Lambda: s -> System.out.println(s)

  • 方法引用: System.out::println

Lambda表达式

Lambda表达式Java8最重要的特性,是Java告别罗嗦代码的重要手段。它是简洁的标识可传递匿名函数的一种方式。

特性:

  1. 匿名:lambda表达式不像面向对象的方法一样,有确定的名称。
  2. 函数:虽然lambda不是对象的方法,属于某个特定的类,但lambda表达式一样有参数列表、函数主题、返回类型和异常声明。
  3. 可作为参数传递
  4. 简洁:写过匿名内部类的都会有这个感受,大量的模板代码使得Java看起来啰里啰唆。
  5. 可以为接口增加静态方法、可以为类增加默认方法。

先通过一个例子来感受下:

1
2
3
4
5
6
7
8
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello Inner Class!");
}
}).start();
//用lambda
new Thread(() -> System.out.println("Hello lambda")).start();

Lambda的语法格式:

1
2
3
(parameters) -> expression 

(parameters) ->{ statements; }
  • 当参数只有一个时,也可以去掉参数的括号。原因是java编译器的自动类型推断

  • 函数式接口不允许抛出受检异常

Java 8 允许使用 :: 关键字来传递方法或者构造函数引用,名曰:方法引用,无论如何,表达式返回的类型必须是 functional-interface。通常由三种场景:

  1. 对象::实例方法
  2. 类::静态方法
  3. 类::实例方法

第一种和第二种,方法的应用等于提供方法参数的lambda表达式。 下面通过一个例子解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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;
}
}

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

常见的函数式接口:

Predicate(断言型)接口,方法签名为:输入某个对象,返回boolean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* java.util.Predicate 是一个只有test方法,返回布尔值的一个函数式接口,
* 与其类似的还有用于比较,排序的Comparator接口,其只有一个返回整数的比较接口
* @param list
* @param p
* @param <T>
* @return
*/
public static <T> List<T> filter(List<T> list, Predicate<T> p){
List<T> result=new ArrayList<>();
for (T t : list) {
if (p.test(t))
result.add(t);
}
return result;
}

public static void main(String[] args) {
//Predicate函数式接口示例
List<Apple> appleList=new ArrayList<>();
List<Apple> resulAppleList=filter(appleList,(Apple a)->a.getColor().equals("red"));
}

Counsumer(消费型)接口,Accept()方法签名为:输入某个对象,返回void

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	/**
* 常用2:Consume
* consume接口定义了一个 名为accept的抽象方法,接收泛型 T 返回void
* 可用来访问T类型的对象,并且执行某些操作。
* 如下用其创建,一个foreach方法,可以实现对所有List的遍历。且对每个对象执行consume定义的操作。
* 该foreach方法,java8之后成了List接口的default方法。
* @param list
* @param <T>
*/
public static <T> void foreach(List<T> list, Consumer<T> consumer){
for (T t : list) {
consumer.accept(t);
}
}


//Consume函数式接口示例,遍历列表执行某项操作
foreach(appleList,(Apple a)->{if (a.getColor()==null);a.setColor("garly");});
appleList.forEach((Apple a)->{if (a.getColor()==null);a.setColor("garly");});

Function (功能性)接口,Apply()方法签名:输入某个对象,返回某个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Apply() 方法签名:输入某个对象、返回某个对象
//使用举例
public static void main(String[] args) {
List<Apple> appleList =new ArrayList<>();
appleList.add(new Apple("红色",11));
appleList.add(new Apple("蓝色",11));
//接下来,骚操作一行代码提取APple的颜色
List<String> allColor=map(appleList,(Apple a)->a.getColor());
}


/**
* 示范 函数式接口Function 的实例
* function.apply() 接收T,返回R 可将一种对象处理成另一种对象
* @param list
* @param function
* @param <T>
* @param <R>
* @return
*/
public static <T,R> List<R> map(List<T> list,Function<T,R> function){
List<R> result=new ArrayList<>();
list.forEach((T t)->{result.add(function.apply(t));});
return result;
}

Stream