Java8新特性
1、Lambda表达式
2、函数式接口
3、接口的默认方法与静态方法
4、方法引用
5、Optional
一、Java 8 Lambda表达式
Lambda表达式,也称为闭包,它是推动Java 8发布的最重要新特性。
Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法)。
1.1 语法
使用表达式可以使代码变得更加简洁。
(parameters) -> expression 或者 (premeters) -> {statements}
可选类型声明: 不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号: 一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号: 如果主体包含了一个语句,就不需要使用大括号
可选的返回关键字: 如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回一个数值。
1.2 Lambda表达式实例
public class LambdaTest { interface IMath{ int operation(int a, int b); } interface IPrint{ void printMessage(String message); } private int operate(int a , int b, IMath math){ return math.operation(a, b); } //类型声明 IMath add = (int a, int b) -> a + b; //不用类型声明 IMath sub = (a,b) -> a -b; //大括号中返回语句 IMath multi = (int a, int b) -> { return a * b; }; IMath div = (int a, int b) -> a/b; void testLambda(LambdaTest lambdaTest){ System.out.println(" 2 + 1 = " + lambdaTest.operate(2,1, add)); System.out.println(" 2 - 1 = " + lambdaTest.operate(2,1, sub)); System.out.println(" 2 * 1 = " + lambdaTest.operate(2,1, multi)); System.out.println(" 2 / 1 = " + lambdaTest.operate(2,1, div)); //不用括号 IPrint iPrint1 = message -> System.out.println("Hi " + message); iPrint1.printMessage("Nick"); //使用括号 IPrint iPrint2 = (message) -> System.out.println("Hi " + message); iPrint2.printMessage("Tome"); } public static void main(String[] args) { LambdaTest test = new LambdaTest(); test.testLambda(test); } }
打印结果:
Lambda的原理可参考: Java 8 Lambda表达式实现原理解析
二、函数式接口
用@FunctionalInterface修饰的接口叫做函数式接口,
函数式接口就是一个只具有一个抽象方法的普通接口, @FunctionalInterface可以起到校验的作用。
比较常用的接口
Supplier: 无参数,返回一个结果
Function:接受一个输入参数,返回一个结果
Consumer: 接受一个输入参数,无返回结果
Predicate: 接受一个输入参数,返回一个布尔值结果。
1、创建实例
interface IMoneyFormat { /** * 将int的钱转为string */ String format2(int i); } class MyMoney{ private final int money; public MyMoney(int money){ this.money = money; } public void printMoney(IMoneyFormat moneyFormat) { System.out.println("我的存款:" + moneyFormat.format2(this.money)); } } public class MoneyDemo { public static void main(String[] args) { MyMoney me = new MyMoney(1999950); me.printMoney(i -> new DecimalFormat("#,###").format(i)); } }
输出如下: 我的存款:1,999,950
2、使用函数接口。修改方法printMoney,删除自己定义的接口IMoneyFormat
class MyMoney{ private final int money; public MyMoney(int money){ this.money = money; } /** * Function<Integer, String> 输入格式为Integer,输出为String */ public void printMoney(Function<Integer, String> moneyFormat) { System.out.println("我的存款:" + moneyFormat.apply(this.money)); } } public class MoneyDemo { public static void main(String[] args) { MyMoney me = new MyMoney(1999950); me.printMoney(i -> new DecimalFormat("#,###").format(i)); } }
输出结果是一样的: 我的存款:1,999,950
可以得出结论: 使用函数接口好处是自己不用定义那么多的接口。另外一个好处是支持链式操作。
链式操作重构如下:
class MyMoney{ private final int money; public MyMoney(int money){ this.money = money; } /** * Function<Integer, String> 输入格式为Integer,输出为String */ public void printMoney(Function<Integer, String> moneyFormat) { System.out.println("我的存款:" + moneyFormat.apply(this.money)); } } public class MoneyDemo { public static void main(String[] args) { MyMoney me = new MyMoney(1999950); Function<Integer,String> moneyForamt = i -> new DecimalFormat("#,###").format(i); //函数接口链式操作 me.printMoney(moneyForamt.andThen(s -> "人民币" + s)); } }
3、自带的函数接口
实例:
public class FunctionDemo { public static void main(String[] args) { // 断言函数接口 IntPredicate predicate = i -> i > 0; System.out.println(predicate.test(-9)); //打印: false //消费函数接口 Consumer<String> consumer = s -> System.out.println(s); consumer.accept("输入的数据"); // 打印: 输入的数据 } }
三、接口的默认方法与静态方法
我们在Java8之前,如果我们需要在为一个增加一个方法,那么它的所有实现类都要实现这个方法
如:动物接口
public interface AnimalInterface { void getName(); }
有两个实现类,分别为
public class Cat implements AnimalInterface{ public void getName() { System.out.println("I'am cat"); } }
和
public class Dog implements AnimalInterface { public void getName() { System.out.println("I'm dog"); } }
如果要在AnimalInterface中增加方法,如 int getAge(); 那么在Dog和Cat类都要实现这个方法。
解决方法:
增加一个抽象类,实现了AnimalInterface接口
public abstract class AbstractAnimal implements AnimalInterface{ public int getAge() { return 0; } }
Cat类和Dog类分别继承抽象类AbstractAnimal
public class Cat extends AbstractAnimal{ public void getName() { System.out.println("I'am cat"); } }
此时,在AnimialInterface中增加了方法
public interface AnimalInterface { void getName(); int getAge(); }
这时候,主需要修改抽象AbstractAnimal 就可以了。AbstractAnimal 增加getAge方法,Dog类和Cat类就不需要动。
那么在Java8中已经实现了直接在接口中添加方法,一种是Default 方法(默认方法),一种Static方法(静态方法)
1、接口中的默认方法
在接口中用default修饰的方法称为默认方法
public interface AnimalInterface { void getName(); default int getAge(){ return 0; }; }
接口中的默认方法一定要有默认实现(方法体),接口实现者可以继承它,也可以覆盖它。
2、静态方法
因为有了默认方法和静态方法,就不用修改它的实现了,可以进行直接调用。
四、方法引用
1、什么是方法引用
有个函数式接口Consumer,
@FunctionalInterface 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); }; } }
里面有个方法accept能够接受一个参数但是没有返回值。要实现accept方法,让它能打印接收到那个参数,可以使用Lambda表达式
Consumer<String> consumer = s -> System.out.println(s); consumer.accept("Hi, Nick");
如果想要实现PrintStream类,那就更简单一点
Consumer<String> consumer2 = System.out::println; consumer2.accept("Hi, Nick2");
这就是方法引用,方法引用的参数列表必须与函数式接口的抽象方法的参数类别保持一致,返回值不作要求。
2、什么是方法引用? (另外一种简洁的表达方式)
/** * @description: 方法引用 */ public class MethodRefrenceDemo { public static void main(String[] args) { Consumer<String> consumer = s -> System.out.println(s); consumer.accept("#consumer1#接收的数据"); //对比consumer,consumer2等号右边的方式就是方法引用 Consumer<String> consumer2 = System.out::println; consumer2.accept("#consumer2#接收的数据"); } }
3、方法引用的各种形式
class Dog { private String name = "旺仔"; /** * 默认10斤狗粮 */ private int food = 10; public String getName(){ return this.name; } public Dog(){ } public Dog(String name){ this.name = name; } /** * 这是一个静态方法 */ public static void bark (Dog dog){ System.out.println(dog.name + "叫了"); } /** * 吃了多少斤狗粮 */ public int eat( int num){ System.out.println("吃了" + num + "斤狗粮" ); this.food -= num; return this.food; } } /** * @description: 方法引用 */ public class MethodRefrenceDemo { public static void main(String[] args) { Consumer<String> consumer = s -> System.out.println(s); consumer.accept("#consumer1#接收的数据"); //对比consumer,consumer2等号右边的方式就是方法引用 Consumer<String> consumer2 = System.out::println; consumer2.accept("#consumer2#接收的数据"); //1、静态方法的方法引用 Consumer<Dog> consumer3 = Dog::bark; Dog dog = new Dog(); consumer3.accept(dog); // 打印: 毛毛叫了 // 2、非静态方法,使用对象实例的方法引用 //Function<Integer, Integer> function = dog::eat; UnaryOperator<Integer> function = dog::eat; //输入和输出都是Integer,Function<Integer, Integer>可以改成UnaryOperator<Integer> System.out.println("还剩下" + function.apply(3) + "斤"); // 打印:吃了3斤狗粮 \n 还剩下7斤 //可以改成IntUnaryOperator, 并使用applyAsInt方法 IntUnaryOperator function2 = dog::eat; System.out.println("还剩下" + function2.applyAsInt(3) + "斤"); //使用类名来方法引用 BiFunction<Dog, Integer, Integer> eatFunction = Dog::eat; System.out.println("还剩下" + eatFunction.apply(dog,3) + "斤"); //3、构造函数(没有参数)的方法引用 Supplier<Dog> supplier = Dog::new; System.out.println("创建了新对象:" + supplier.get().getName()); //构造函数(有参数)的方法引用 Function<String,Dog> function3 = Dog::new; System.out.println("创建了新对象:" + function3.apply("阿奇").getName()); } }
五、Optional
空指针异常时导致Java应用程序失败的最常见原因之一,为了接口空指针异常,Google在Guava项目中引入Optional类,Java 8 中也引入了Optional。
Optional实际上是个容器: 它可以保存类型T的值,或者仅仅保存null。Optional提供了很多方法,这样就不需要显示进行空值检测。
创建Optional对象的几个方法:
1、Optional.of(T value),返回一个Optional对象,value不能为空,否则会出空指针异常
2、Optional.ofNullable(T value), 返回一个Optiona对象,value可以为空
3、Optional.empty() 代表空
其它API:
1、optional.isPresent(), 是否存在值(不为空)
2、optional.ifPresent(Consumer<? super T> consumer), 如果存在值则执行consumer
3、optional.get(),获取value
4、optional.orElse(T other), 如果没有值则返回other
5、optional.orElseGet(Suppliser<? extends T> other),如果没有值则执行other并返回
6、optional.orElseThrow(Supplier<? extentds X> exceptionSupplier), 如果没值则执行execeptionSupplier,并抛出异常