Java是一门不断更新迭代的语言。以下是一些重要更新点:
- JDK1.5:增强for循环、可变参数列表、静态导入、枚举类型、包装类的自动装箱和拆箱、泛型
- JDK1.7:二进制字面量、数字常量下划线、Switch运算中String类型和枚举类型的引入、try-with-resource资源自动释放
JDK1.8:接口的默认方法和静态方法、Lambda表达式、方法引用、Optional、Stream,大部分新增的内容都是围绕函数式编程而生的
接口的默认方法和静态方法
接口中的方法一般都是抽象方法。而有些方法十分常用,且方法体固定,如果每次实现类都要重写这个方法,就十分麻烦。Java1.8为接口提供了默认方法和静态方法,使得接口中也可以进行方法的具体实现。
默认方法由default修饰符修饰,可以为接口添加默认方法,并实现方法体。实现接口的实现类既不必重写默认方法,也能够“继承”默认方法的实现。默认方法可以让接口更优雅地升级,不必随着接口方法增加,每次都需要修改实现类的代码,也可以让实现类中省略很多不必要方法的空实现。
接口的静态方法与类的静态方法完全相同,不能被继承,也不能被重写。因为接口没有具体的对象,故也只能通过接口名调用。interface A {
static void show() {
System.out.println("SSS");
}
default void defaultMethod() {
System.out.println("DDD");
}
}
class B implements A {
/** A没有合适的方法必须重写 **/
public static void main(String[] args) {
A.show(); // 打印SSS
new B().defaultMethod(); // 打印DDD
}
}
如果实现类重写了接口的默认方法,则调用重写以后的。如果一个类实现了多个接口,且接口之间没有继承关系,但是默认方法名相同,则只能重写该默认方法后才能调用。
Lambda表达式
Lambda表达式就是对匿名内部类的一种简化。但在此之前,仍然需要对实现的接口/抽象类,以及抽象方法进行约定。因为进行了约定,故方法名称、参数列表和返回值的类型都是确定的,在Lambda表达式使用过程中可以被省略。
Lambda表达式的格式为接口的引用 = () -> {}
。其中:()内为要实现方法的参数列表。参数列表的参数类型可以省略。当参数只有一个时,括号也可以省略。没有参数时,需要保留括号。当接口的引用作为一个重载方法的参数时,类型需要强转以符合其中一个方法。
- {}内为方法体和返回值。当方法体只有一行时,大括号可以省略。当只有一句返回语句时,return也可以省略。
Lambda表达式依旧是一个匿名内部类,其中的局部变量依旧需要是一个final常量或未被改变值的变量。FunInt f = () -> {
System.out.println();
System.out.println("ABCBoy");
}
FunInt1 f1 = () -> System.out.println("ABCBoy"); // 只有一句可以省略{}
FunInt2 f2 = () -> System.out::println; // 这里是方法的引用
FunInt3 f3 = (int a, int b) -> { return a+b;};
FunInt4 f4 = (a, b) -> a+b; // 可以省略参数类型和return
int sum = f4.sum(2, 7); // 假设该方法名为sum
函数式编程
函数式编程,和面向对象编程、面向过程编程一样,都是编程的一种方式。函数式编程是面向数学的抽象,将计算过程描述为一种表达式求值的过程,且具有明确的返回值。
行为参数化是函数式编程的一种形式。将核心代码(方法/函数)通过传参的形式传入到方法中,从而实现不同的功能。在此过程中,核心代码的方法名并不是需要关心的。函数式接口
因为Lambda表达式省略了方法名,故要实现的接口中有且只能有一个抽象方法,当然包括可能继承和实现的方法,但静态方法和默认方法不在此列。这样的接口被称为函数式接口。
当然,实现了方法并不意味着调用了方法。依旧需要通过对象对方法使用方法名进行调用。@FunctionalInterface // 可以为接口添加该注解,用编译器检查该接口是否是函数式接口
interface FunInt {
void show();
}
当一个函数式接口的引用作为一个方法的参数时,可以在传参的同时,实现该接口,即接口的参数化。例如之前的Runnable和Comparator接口: ```java ExecutorService pool = Executors.newCachedThreadPool(); pool.execute(() -> { / 要执行的代码 / }); // 这里的参数是Runnable实现类对象
Set
<a name="DAX83"></a>
### JDK提供的接口
JDK1.8中,定义了一些常用的函数式接口,这些接口都定义在java.lang.function包中。
![](https://cdn.nlark.com/yuque/0/2021/jpeg/2437684/1621220612338-a0e3ea59-fc20-4ac6-a5f2-7aba3b445230.jpeg)
- Predicate(判断条件):传入一个对象,返回一个布尔值,函数名称为test。通常用于对传入的对象进行判断。常用在if表达式中,以影响方法流程的执行。拥有and()、or()、negate()三个默认方法,用于连接Predicate条件,形成复合条件。
```java
public static List<Integer> filter(
List<Integer> list, Predicate<Integer> p) {
// 该方法传入一个整形列表,根据Predicate表达式的条件
// 返回由特定数字组成的列表
List<Integer> data = new ArrayList<>();
for (Integer integer : list) { // 遍历
if(p.test(integer)) { // 判断当前数字对象,判断条件见实现类
data.add(integer); // 满足条件则放入列表中
}
}
return data; // 返回列表
}
public static void main(String[] args) {
Predicate<Integer> p = t -> { // 实现类,实现其中的test方法
if(t > 4) { // 判断条件是大于4的整数
return true;
}
return false;
};
Predicate<Integer> p2 = t -> {
if(t < 8) { // 判断条件是小于8的整数
return true;
}
return false;
};
Predicate<Integer> p3 = p.and(p2);
List<Integer> list = filter(alist, p3);
// 筛选条件是p1&&p2的p3
// 传入不同的筛选条件获得不同的列表
}
Consumer(处理器):传入一个对象,没有返回值,函数名称为accept。通常用于对传入的对象进行一些操作,如给属性赋值、遍历等。拥有andThen()默认方法,用于连接两个Consumer操作。
Consumer<String> s = t -> System.out.println(t);
// 传入一个字符串,对字符串进行打印
List<String> list = new ArrayList<>(); // 定义一个集合
list.add("A");
list.add("B"); // 添加两个元素
list.forEach(s);
// Collection接口提供的forEach默认方法,传入一个Consumer的实现类对象
// 对集合进行遍历,对集合的每个对象进行Consumer的操作,这里是打印
Function(函数):传入一个对象,返回一个对象(这两个对象类型可以不同),函数名称为apply。通常用于对传入的对象进行一些操作,并获取一个返回值,如获取对象属性等。拥有compose()和andThen()默认方法,用于连接两个Function操作。拥有identity()静态方法,用于直接返回传入的Function的参数,不做任何操作。 ```java public static int jisuan(String info, Function
function) { // 传入一个字符串和一个Function操作,返回一个Integer return function.apply(info); // 对传入的字符串执行Function操作 }
public static void main(String[] args) { String info = “蒙娜丽莎,15 小李,16 小红,18”;
int result = jisuan(info, t -> { // 传入上面的字符串,并对字符串进行操作
String[] split = t.split(" ");
int sum = 0;
for (String string : split) {
String[] split2 = string.split(",");
sum += Integer.parseInt(split2[1]);
}
return sum / 3; // 这一串操作是对字符串中的数字进行加和并求平均值
});
System.out.println(result);
}
- Supplier(提供器):不传入任何参数,返回一个对象,函数名称为get。通常执行一段代码,并返回一个值。没有任何默认方法。
- UnaryOperator
- BinaryOperator
因为上面的一些方法的返回的都是对象,对于基本数据类型需要频繁的进行拆箱和装箱。为了提供运行速度,也对基本数据类型提供了特化的接口:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/2437684/1599374546191-82857499-92fa-4467-a5a2-2574b3fb9c01.png#height=242&id=eQVG8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=323&originWidth=913&originalType=binary&ratio=1&size=43587&status=done&style=none&width=685)
<a name="KcnOr"></a>
### 集合中的默认方法
Collection和Map接口中,在JDK1.8新增了一些默认方法。这些方法都需要传入一个上面的函数式接口的实现类对象,对集合或集合的元素进行一些操作。<br />Collection列表:
- removeIf(Predicate<? super T> filter):对元素进行遍历,对满足filter条件的元素进行移除
- forEach(Consumer<? super T> action):对元素进行遍历,执行action操作
Map集合:
- computeIfAbsent(K key, Function<K, V> mappingFunction):若key不存在,则执行mappingFunction的操作获取一个value,并添加到Map中
<a name="aPaML"></a>
## 方法引用
方法引用是对Lambda表达式的再次简化。对于Lambda表达式中只使用了仅一个函数(方法)的情况,将参数列表和传入参数这部分代码进行省略。<br />使用方式:
Clazz::new // 构造器引用 Clazz::StaticMethodName // 静态方法引用 Clazz::InstenceMethodName // 当前对象的方法引用 object::InstenceMethodName // 特定对象的方法引用
例如:
```java
// 一接口(Stream)中某方法的定义为
Stream<T> filter(Predicate<? super T> predicate);
// 使用时,既可以实现Predicate(判断条件),传入一个匿名内部类或Lambda表达式;
// 也可以使用其他类中的具体方法作为方法的引用,作为该Predicate的实现:
stream.filter(Objects::nonNull);
// Objects::nonNull的定义为:
public static boolean nonNull(Object obj) {
return obj != null;
}
使用方法引用,能够将其他类中的方法作为函数式接口中方法的实现,能够多次复用代码。缺点是太简略了,可读性不佳,除了能装逼,一点都不好用。
流对象
这里的流对象不是输入输出流,也不是其中的对象流,而是一种集合的抽象。它能对集合进行过滤(Filter,移除或获取指定的元素)、排序(Sort,按特定顺序排序)、映射(Map,提取元素的某些属性或处理每个元素)、归约(reduce,对集合进行求和等操作)等加工。
以下是一个遍历课程列表(Course courses)获取学时(Long hours)并求学时总和的例子:
courses.stream() // 获取流对象
.map(Course::getHours) // 获取集合每个元素的hours属性,得到一个新的流对象
.filter(Objects::nonNull) // 去除流对象中的空对象
.reduce(Long::sum) // 将所有对象加和,得到一个Optional对象
.orElse(0L); // 若Optional对象属性为null,则返回0L
Optional对象
Optional对象是对Object的封装,包含一个value属性,主要是避免空指针异常。
Optional.of(value); // 赋值,value不能为null
Optional.ofNullable(value); // 赋值,value可以为null
Optional.empty(); // 获取一个value为null的optional
optional.get(); // 取值,value为null会抛异常
optional.isPresent(); // 返回boolean,value为null为false
optional.ifPresent(value -> value.doSomething()); // value不为空就执行某方法
optional.orElse(0); // value为空返回0否则返回value
optional.orElseGet(() -> xxx.get()); // value为空返回Lambda方法返回值否则返回value
optional.orElseThrow(() -> new Exception()); // value为null就抛异常