Java 8新特性
1、概述
Java 8发布于2014-03-18,发布至今已经8年了,是目前企业中使用最广泛的一个版本。Java 8是一次重大的版本升 级,带来了很多的新特性。
JDK 8新特性
- Lambda表达式
- 集合之Stream流式操作
- 接口的增强
- 并行数组排序
- Optional中避免Null检查
- 新的时间和日期 API
- 可重复注解
1.1为什么学?
- 能够看懂公司里的代码
- 大数量下处理集合的效率高
- 代码可读性高
- 消灭嵌套地狱
1.2函数式编程思想
关注对数据进行操作
优点:
- 代码简洁,开发快速
- 接近自然语言,易于理解
- 易于并发编程
2、Lambda表达式
Lambda是JDK8中的一个语法糖,可以对某些匿名内部类的写法进行简化,它是函数式编程思想的一个重要体现,让我们不用关注是什么对象,而是关注我们对数据进行了什么操作。
核心原则
可推导可省略
2.1 基本格式
(参数列表)—>{
代码
}
2.2 示例
匿名内部类创建线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类创建线程");
}
}
).start();
Lambda表达式写法:
new Thread(()->{
System.out.println("Lambda表达式创建线程");
}).start();
2.2.1 无参返回值的Lambda
public interface Swimmable {
public abstract void run();
}
public class DemoLambdaUse {
public static void main(String[] args) {
//匿名内部类写法
goRun(new Swimmable() {
@Override
public void run() {
System.out.println("匿名内部类写法");
}
});
//Lambda表达式写法
goRun(()->{
System.out.println("Lambda表达式写法");
});
}
public static void goRun(Swimmable swimmable){
swimmable.run();
}
}
2.2.2 有参数返回的Lambda
下面举例演示 java.util.Comparator 接口的使用场景代码,其中的抽象方法定义为:
public abstract int compare(T o1, T o2);
当需要对一个对象集合进行排序时, Collections.sort 方法需要一个 Comparator 接口实例来指定排序的规则。
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(9);
list.add(5);
//匿名内部类写法
Collections.sort(list, new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
//Lambda表达式写法
Collections.sort(list, (Integer o1, Integer o2) ->{
return o1 - o2;
});
for (Integer integer : list) {
System.out.println(integer);
}
List<Integer> asList = Arrays.asList(11, 33, 22, 44);
//匿名内部类写法
asList.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
//Lambda表达式写法
asList.forEach((s) -> {
System.out.println(s);
});
//Lambda表达式写法 变形
asList.forEach(s->System.out.println(s));
asList.forEach(s->{
if (s > 20){
System.out.println(s);
}
});
2.3 Lambda的实现原理
匿名内部类实现原理
说实现原理前先说说匿名内部类实现原理
public class DemoLambdaUse {
public static void main(String[] args) {
//匿名内部类写法
goRun(new Swimmable() {
@Override
public void run() {
System.out.println("匿名内部类写法");
}
});
}
public static void goRun(Swimmable swimmable){
swimmable.run();
}
}
匿名内部类会在编译后产生一个类: DemoLambdaImpl$1.class
反编译后的内容
package com.sl.java8;
final class DemoLambdaUse$1 implements Swimmable {
DemoLambdaUse$1() {
}
public void run() {
System.out.println("匿名内部类写法");
}
}
Lambda表达式实现原理
public class DemoLambdaUse {
public static void main(String[] args) {
//Lambda表达式写法
goRun(()->{
System.out.println("Lambda表达式写法");
});
}
public static void goRun(Swimmable swimmable){
swimmable.run();
}
}
运行程序,控制台可以得到预期的结果,但是并没有出现一个新的类,也就是说Lambda并没有在编译的时候产生一 个新的类。
我们使用 JDK自带的一个工具: javap ,对字节码进行反汇编,查看字节码指令。 在DOS命令行输入:
javap -c -p 文件名.class
-c:表示对代码进行反汇编
-p:显示所有类和成员
F:\workspace\java_projcet\study\java8\target\classes\com\sl\java8>javap -c -p DemoLambdaUse.class
Compiled from "DemoLambdaUse.java"
public class com.sl.java8.DemoLambdaUse {
public com.sl.java8.DemoLambdaUse();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokedynamic #2, 0 // InvokeDynamic #0:run:()Lcom/sl/java8/Swimmable;
5: invokestatic #3 // Method goRun:(Lcom/sl/java8/Swimmable;)V
8: return
public static void goRun(com.sl.java8.Swimmable);
Code:
0: aload_0
1: invokeinterface #4, 1 // InterfaceMethod com/sl/java8/Swimmable.run:()V
6: return
private static void lambda$main$0();
Code:
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String Lambda表达式写法
5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
多了一个私有的静态方法
private static void lambda$main$0();
其实Lambda在运行的时候会生成一个内部类,为了验证是否生成内部类,可以在运行时加 上 -Djdk.internal.lambda.dumpProxyClasses ,加上这个参数后,运行时会将生成的内部类class码输出到一个文 件中。使用java命令如下:
F:\workspace\java_projcet\study\java8\target\classes>java -Djdk.internal.lambda.dumpProxyClasses com.sl.java8.DemoLambdaUse
Lambda表达式写法
反编译DemoLambdaUse$$Lambda$1.class
package com.sl.java8;
import java.lang.invoke.LambdaForm.Hidden;
// $FF: synthetic class
final class DemoLambdaUse$$Lambda$1 implements Swimmable {
private DemoLambdaUse$$Lambda$1() {
}
@Hidden
public void run() {
DemoLambdaUse.lambda$main$0();
}
}
可以看到这个匿名内部类实现了 Swimmable 接口,并且重写了 swimming 方法, swimming 方法调用 Demo04LambdaImpl.lambda$main$0() ,也就是调用Lambda中的内容。最后可以将Lambda理解为:
public class Demo04LambdaImpl {
public static void main(String[] args) {
goSwimming(new Swimmable() {
public void swimming() {
Demo04LambdaImpl.lambda$main$0();
}
});
}
private static void lambda$main$0() {
System.out.println("Lambda表达式游泳");
}
public static void goSwimming(Swimmable swimmable) {
swimmable.swimming();
}
}
总结:
Lambda在程序运行的时候形成一个类
- 在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码
- 还会形成一个匿名内部类,实现接口,重写抽象方法
- 在接口的重写方法中会调用新生成的方法.
2.4 Lambda省略格式
在Lambda标准格式的基础上,使用省略写法的规则为:
- 小括号内参数的类型可以省略
- 如果小括号内有且仅有一个参数,则小括号可以省略
- 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号
(int a) -> {
return new Person();
}
省略后
a -> new Person()
2.5 Lambda的前提条件
Lambda表达式不是随便使用的,使用时有几个条件要特别注意:
- 方法的参数或局部变量类型必须为接口才能使用Lambda
- 接口中有且仅有一个抽象方法
Lambda表达式的前提条件:
- 方法的参数或变量的类型是接口
- 这个接口中只能有一个抽象方法
2.6 函数式接口
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
ava 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注 解可用于一个接口的定义上:
//检测这个接口是不是只有一个抽象方法
@FunctionalInterface
public interface Swimmable {
public abstract void run();
}
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即 使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
2.7 常用内置函数式接口
Supplier接口
它们主要在 java.util.function 包中。下面是最常用的几个接口。
Supplier接口
java.util.function.Supplier 接口,它意味着"供给" , 对应的Lambda表达式需要“对外提供”一个符合泛型类 型的对象数据。
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
示例
public class TestSupplier {
public static void printMax(Supplier<Integer> supplier){
System.out.println(supplier.get());
}
public static void main(String[] args) {
printMax(()->{
int[] arry = {11, 33, 22};
Arrays.sort(arry);
return arry[arry.length -1];
});
}
}
Consumer接口
java.util.function.Consumer 接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
默认方法:andThen
如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合。
java.util.Objects 的 requireNonNull 静态方法将会在参数为null时主动抛出 NullPointerException 异常。
这省去了重复编写if语句和抛出空指针异常的麻烦。
示例:
public class TestConsumer {
public static void test(Consumer<String> c1, Consumer<String> c2){
String s = "HelloWorld";
c1.accept(s);
}
public static void main(String[] args) {
test(s -> {
System.out.println(s.toLowerCase());
});
}
}
示例2:
public class TestConsumer {
public static void test(Consumer<String> c1, Consumer<String> c2){
String s = "HelloWorld";
c1.andThen(c2).accept(s);
}
public static void main(String[] args) {
test(s -> {
System.out.println(s.toLowerCase());
},s -> {
System.out.println(s.toUpperCase());
});
}
}
Function接口
java.util.function.Function 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件, 后者称为后置条件。有参数有返回值。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
示例:
public class TestFunction {
public static void test(Function<String, Integer> function){
Integer apply = function.apply("10");
System.out.println("in:" + (apply+ 5));
}
public static void main(String[] args) {
test(s->{
return Integer.parseInt(s);
});
}
}
默认方法:andThen
Function 接口中有一个默认的 andThen 方法,用来进行组合操作。
该方法同样用于“先做什么,再做什么”的场景,和 Consumer 中的 andThen 差不多:
public class Demo09FunctionAndThen {
public static void main(String[] args) {
// Lambda表达式
test((String s) -> {
return Integer.parseInt(s);
}, (Integer i) -> {
return i * 10;
});
}
public static void test(Function<String, Integer> f1, Function<Integer, Integer> f2) {
// Integer in = f1.apply("66"); // 将字符串解析成为int数字
// Integer in2 = f2.apply(in);// 将上一步的int数字乘以10
Integer in3 = f1.andThen(f2).apply("66");
System.out.println("in3: " + in3); // 660
}
}
请注意,Function的前置条件泛型和后置条件泛型可以相同。
Predicate接口
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用 java.util.function.Predicate 接口。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
示例:
public class TestPredicate {
private static void test(Predicate<String> predicate){
boolean test = predicate.test("1");
System.out.println(test);
}
public static void main(String[] args) {
test(s->{
return s.equals("1");
});
}
}
2.8 Lambda表达式和匿名内部类
匿名内部类 | Lambda表达式 | |
---|---|---|
所需类型 | 可以是类,抽象类,接口 | 必须是接口 |
抽象方法数量 | 随意 | 只能有一个抽象方法 |
实现原理 | 在编译后会形成class | 在程序运行时动态生成class |
总结:
当接口中只有一个抽象方法时,建议使用Lambda表达式,其他其他情况还是需要使用匿名内部类
3、JDK8接口新增的两个方法
接口增强介绍
jdk8之前:
public interface Swimmable {
静态常量;
抽象方法;
}
增强后:
可以有默认方法和静态方法
interface 接口名 {
静态常量;
抽象方法;
默认方法;
静态方法;
}
3.1默认方法
格式
interface 接口名 {
修饰符 default 返回值类型 方法名() {
代码;
}
}
public interface Swimmable {
//默认方法;
default void func(){
System.out.println("接口中的抽象方法");
}
}
class SwimmableImpl implements Swimmable{
public static void main(String[] args) {
new SwimmableImpl().func();
}
}
使用
- 实现接口&#x