Java8新特性
由于Java8作为工作中最常用的版本,今天重新梳理一下JAVA 8的所有新特性。
Interface
在 Java 8 之前,接口(interface)是非常“纯粹”的:它只能包含抽象方法(abstract methods),即只有声明,没有实现。而且在Interface修改的时候,实现它的类也必须修改。为了解决这个问题,Java 8 对接口做出了重大变革,最核心的新特性是引入了 默认方法(Default Methods) 和 静态方法(Static Methods)。这样就可以又方法体,实现类也不必重写此方法。
一个 interface 中可以有多个方法被它们修饰,这 2 个修饰符的区别主要也是普通方法和静态方法的区别。
default修饰的方法,是普通实例方法,可以用this调用,可以被子类继承、重写。static修饰的方法,使用上和一般类静态方法一样。但它不能被子类继承,只能用Interface调用。
下面通过一个例子展示下这两个新特性的用法:
1 | public interface InterfaceA { |
第二个接口
1 | public interface InterfaceB { |
实现类:
1 | public class CoolBee implements InterfaceA,InterfaceB{ |
如果有一个类既实现了 InterfaceA 接口又实现了 InterfaceB接口,它们都有goHome(),并且 InterfaceA 接口和 InterfaceB接口没有继承关系的话,这时就必须重写goHome()。不然的话,编译的时候就会报错。
这么一看,Interface越来越和abstract class像了,那他们的区别是什么呢?
| 特性 | 接口(Java 8+) | 抽象类 |
|---|---|---|
| 多继承 | 一个类可以实现多个接口 | 一个类只能继承一个抽象类 |
| 状态存储 | 不能有成员变量(只能由常量 public static final) | 可以有成员变量 |
| 构造函数 | 没有构造函数 | 可以有构造函数 |
| 设计目的 | 定义行为规范(能做什么) | 定义身份和公用逻辑(is -a) |
如果冲突怎么能?正如上面的例子,两个接口都有goHome(),并且都是默认方法,Java的规则如下:
类优先:如果父类和接口方法重名,优先执行父类的方法。
手动重写:如果两个接口冲突,编译器会强制要求你在实现类中显式重写该方法,你可以手动指定调用哪一个:
InterfaceName.super.methodName();
注意,本次新增default和static修饰的方法,是为了解决接口修改与实现不兼容的问题,而不是为了取代abstract class。在该用abstract class的场景还是要用abstract class的。要注意还是有差异性的。
functional interface函数式接口
函数式接口是指:有且仅有一个抽象方法(Abstract Method)的接口。
- 核心准则:只要满足“只有一个抽象方法”的条件,它就是函数式接口。
- 允许存在:默认方法(default)和静态方法(static),因为它们不属于抽象方法。
- 允许重写:
Object类中的公共方法(如equals,toString),也不会增加抽象方法的计数。
@FunctionalInterface 注解
为了防止别人不小心往你的接口里加第二个抽象方法,Java 8 引入了这个注解。它不是强制的,但强烈建议加上。如果加了注解后接口不符合规范,编译器会直接报错。
1 |
|
解决的痛点就是在Java8之前,想要将一段逻辑代码传给一个方法,非常麻烦,例如需要通过匿名内部类,有了函数式接口,Lambda表达式才有了解析的目标,Java编译器会将Lambda表达式自动匹配到对应的函数时接口上。例如下面的这个例子:
1 |
|
在任意类编写一个方法:
1 | public static void display(String text,StringProcessor processor){ |
这里可以看到,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告别罗嗦代码的重要手段。它是简洁的标识可传递匿名函数的一种方式。
特性:
- 匿名:lambda表达式不像面向对象的方法一样,有确定的名称。
- 函数:虽然lambda不是对象的方法,属于某个特定的类,但lambda表达式一样有参数列表、函数主题、返回类型和异常声明。
- 可作为参数传递
- 简洁:写过匿名内部类的都会有这个感受,大量的模板代码使得Java看起来啰里啰唆。
- 可以为接口增加静态方法、可以为类增加默认方法。
先通过一个例子来感受下:
1 | new Thread(new Runnable() { |
Lambda的语法格式:
1 | (parameters) -> expression |
当参数只有一个时,也可以去掉参数的括号。原因是java编译器的自动类型推断
函数式接口不允许抛出受检异常
Java 8 允许使用 :: 关键字来传递方法或者构造函数引用,名曰:方法引用,无论如何,表达式返回的类型必须是 functional-interface。通常由三种场景:
- 对象::实例方法
- 类::静态方法
- 类::实例方法
第一种和第二种,方法的应用等于提供方法参数的lambda表达式。 下面通过一个例子解释:
1 | public class LambdaClassSuper { |
lambda 表达式可以引用外边变量,但是该变量默认拥有 final 属性,不能被修改,如果修改,编译时就报错。
常见的函数式接口:
Predicate(断言型)接口,方法签名为:输入某个对象,返回boolean
1 | /** |
Counsumer(消费型)接口,Accept()方法签名为:输入某个对象,返回void
1 | /** |
Function (功能性)接口,Apply()方法签名:输入某个对象,返回某个对象
1 | // Apply() 方法签名:输入某个对象、返回某个对象 |










