Java 8新特性
Lambda表达式
所谓Lambda表达式,也就是Dart中的闭包,作为传统Java开发者,不一定能很快理解此概念,但作为Dart开发者,应当是相当熟悉了。它允许我们将一个函数当作方法的参数进行传递,或者说把代码当作数据传递。
基本语法结构
(参数列表)-> 表达式;
// 或者带有花括号
(参数列表)-> {表达式;}
实例
// 声明lambda 表达式
ToLongBiFunction<Integer,Long> func = (Integer x, Long y) -> x + y;
// 创建匿名内部类对象
Runnable run1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello world !");
}
};
// 使用lambda表达式简化匿名内部类
Runnable run2 = () -> System.out.println("Hello world !");
// 一行代码也可以使用花括号包裹
// (String name) -> {System.out.println(name);}
总的来说,Java中的Lambda表达式与Dart中的闭包相比,最多只能算半成品,最大的意义就是在某种情况下可以少写几行代码。使用Java的Lambda表达式有许多要注意的地方
Lambda表达式中可以访问局部变量,但不能修改
Runnable get(String name){ String hello = "hello"; return () -> System.out.println(hello + "," + name); }用于方法参数的传递,不能直接执行Lambda表达式
不能完全和匿名内部类等同,只在特定情况下可以替换匿名内部类
主要场景用于为函数式接口的抽象方法提供实现
什么是函数式接口?
有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数接口可以隐式地转换成lambda表达式。
// 函数式接口
public interface MyInterface {
void work(String data);
}
使用Lambda表达式为函数式接口提供实现时,必须保证接口中的抽象方法的签名与Lambda表达式签名一致。
函数式接口存在一定缺陷,只要有人在接口里多添加一个方法,那么这个接口就不是函数接口了,就会导致编译失败。Java 8提供了一个特殊的注解@FunctionalInterface来克服此脆弱性,并且显示地表明函数接口的目的。
@FunctionalInterface
public interface MyInterface {
void work(String data);
}
Java 8的java.util.function 包已经为我们提供了一些函数式接口
| 接口 | 描述 |
|---|---|
BiConsumer<T,U> |
代表了一个接受两个输入参数的操作,并且不返回任何结果 |
BiFunction<T,U,R> |
代表了一个接受两个输入参数的方法,并且返回一个结果 |
BinaryOperator<T> |
代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果 |
BiPredicate<T,U> |
代表了一个两个参数的boolean值方法 |
BooleanSupplier |
代表了boolean值结果的提供方 |
Consumer<T> |
代表了接受一个输入参数并且无返回的操作 |
DoubleBinaryOperator |
代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。 |
DoubleConsumer |
代表一个接受double值参数的操作,并且不返回结果。 |
DoubleFunction<R> |
代表接受一个double值参数的方法,并且返回结果 |
DoublePredicate |
代表一个拥有double值参数的boolean值方法 |
DoubleSupplier |
代表一个double值结构的提供方 |
DoubleToIntFunction |
接受一个double类型输入,返回一个int类型结果。 |
DoubleToLongFunction |
接受一个double类型输入,返回一个long类型结果 |
DoubleUnaryOperator |
接受一个参数同为类型double,返回值类型也为double 。 |
Function<T,R> |
接受一个输入参数,返回一个结果。 |
IntBinaryOperator |
接受两个参数同为类型int,返回值类型也为int 。 |
IntConsumer |
接受一个int类型的输入参数,无返回值 。 |
IntFunction<R> |
接受一个int类型输入参数,返回一个结果 。 |
IntPredicate |
接受一个int输入参数,返回一个布尔值的结果。 |
IntSupplier |
无参数,返回一个int类型结果。 |
IntToDoubleFunction |
接受一个int类型输入,返回一个double类型结果 。 |
IntToLongFunction |
接受一个int类型输入,返回一个long类型结果。 |
IntUnaryOperator |
接受一个参数同为类型int,返回值类型也为int 。 |
LongBinaryOperator |
接受两个参数同为类型long,返回值类型也为long。 |
LongConsumer |
接受一个long类型的输入参数,无返回值。 |
LongFunction<R> |
接受一个long类型输入参数,返回一个结果。 |
LongPredicate |
接受一个long输入参数,返回一个布尔值类型结果。 |
LongSupplier |
无参数,返回一个结果long类型的值。 |
LongToDoubleFunction |
接受一个long类型输入,返回一个double类型结果。 |
LongToIntFunction |
接受一个long类型输入,返回一个int类型结果。 |
LongUnaryOperator |
接受一个参数同为类型long,返回值类型也为long。 |
ObjDoubleConsumer<T> |
接受一个object类型和一个double类型的输入参数,无返回值。 |
bjIntConsumer<T> |
接受一个object类型和一个int类型的输入参数,无返回值。 |
ObjLongConsumer<T> |
接受一个object类型和一个long类型的输入参数,无返回值。 |
Predicate<T> |
接受一个输入参数,返回一个布尔值结果。 |
Supplier<T> |
无参数,返回一个结果。 |
ToDoubleBiFunction<T,U> |
接受两个输入参数,返回一个double类型结果 |
ToDoubleFunction<T> |
接受一个输入参数,返回一个double类型结果 |
ToIntBiFunction<T,U> |
接受两个输入参数,返回一个int类型结果。 |
ToIntFunction<T> |
接受一个输入参数,返回一个int类型结果。 |
ToLongBiFunction<T,U> |
接受两个输入参数,返回一个long类型结果。 |
ToLongFunction<T> |
接受一个输入参数,返回一个long类型结果。 |
UnaryOperator<T> |
接受一个参数为类型T,返回值类型也为T。 |
默认方法
Java 8允许在接口内声明静态方法或者声明默认方法。也就是说现在Java接口也能提供方法的具体实现。并且实现接口的类如果不覆写该方法,就会自动继承默认的实现。
Java中,通常工具类定义了与接口实例协作的很多静态方法,现在静态方法可以存在于接口内部,那么代码中的那些辅助类就没有了存在的必要,就可以把这些静态方法转移到接口内部。
interface MyInterface {
void work(String data);
// 声明默认方法
default void print(Object data){
System.out.println(data);
}
}
默认方法的意义
传统上,Java程序的接口是将相关方法按照约定组合到一起的方式。实现接口的类必须为接口中定义的每个方法提供一个实现,或者从父类中继承它的实现。但是,一旦类库的设计者需要更新接口,向其中加入新的方法,这种方式就会出现问题,那些接口实现类为了适配新接口约定都需要进行修改,现在有了默认方法,接口实现类会自动继承它,这样就可以使你平滑地进行接口的优化和演进。
方法引用
所谓方法引用,类似Dart中的将方法作为参数传递。即把Java中已经存在的方法作为参数传递。Java 8用Lambda表达式来实现匿名方法,用方法引用来传递已经存在的方法,注意两者的区别。
主要有四种方法引用,参见官方文档-方法引用
| 类型 | 语法 | 对应的Lambda表达式 |
|---|---|---|
| 静态方法引用 | 类名::staticMethod |
(args) -> 类名.staticMethod(args) |
| 特定对象的实例方法引用 | expr::instMethod |
(args) -> expr.instMethod(args) |
| 特定类型的任意对象的实例方法引用 | 类型名::instMethod |
(inst,args) -> inst.instMethod(args) |
| 构造方法引用 | 类名::new |
(args) -> new 类名(args) |
声明一个类,定义一些方法用于测试
public class Car {
private String name;
public Car(){}
public Car(String name){
this.name = name;
}
// 静态方法
public static void print() {
System.out.println("this is a static method");
}
// (实例方法)成员方法
public void drive() {
System.out.println("drive "+this.name);
}
public void repair() {
System.out.println("Repaired " + this.toString());
}
}
声明一个测试类
public class Test{
// Car 类构造方法做参数
public Car testCreate(final Function<String,Car> func){
// 调用了Car有参数的那个构造方法
return func.apply("特斯拉");
}
// 特定对象的方法做参数
public void testDrive(final Runnable runnable){
runnable.run();
}
// 特定类型任意对象方法做参数
public void testRepair(final Consumer<Car> action){
action.accept(new Car());
}
// 静态方法做参数
public void testPrint(final Runnable runnable){
runnable.run();
}
}
验证
Test test = new Test();
// 构造方法引用
Car car = test.testCreate(Car::new);
// 特定对象方法引用
test.testDrive(car::drive);
// 特定类型任意对象方法引用
test.testRepair(Car::repair);
// 静态方法引用
test.testPrint(Car::print);
Optional
Optional 是一个容器,它可以保存一些类型的值或者null,它可以用来处理Java中棘手的空指针异常,但它更重大的意义是应用于函数式编程中。
举例:
String name = user.getAddress().getCountry().getName().toUpperCase();
如果其中有一环返回null值,将引发NullPointerException空指针异常。因此应当使用防御式编程:
String name = null;
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
String name = country.getName();
if (name != null) {
name = name.toUpperCase();
}
}
}
}
由此代码变得冗长臃肿,可读性差,难以维护。Dart中,在语法层面为我们提供了?.操作符避免此类问题,而Java中一直没有好的方式处理,直到Java 8增加了Optional。
使用Optional改写上例:
String name = Optional.ofNullable(user)
.map(u -> u.getAddress())
.map(addr -> addr.getCountry())
.map(country -> country.getName())
.map(n -> n.toUpperCase())
.orElse(null);
System.out.println(name);
Optional 三种构造方式:
声明一个空的Optional
Optional<User> optUser = Optional.empty();通过一个非空值创建Optional(如果是空值,则会报
NullPointException)Optional<User> user = Optional.of(new User());可以接受
null值的OptionalOptional<User> optUser = Optional.ofNullable(user);
其中,Optional的isPresent方法可以用于判断保存的值是否为空,该方法返回一个布尔值;get方法可用于取出保存的值,但如果值为空,则会报NoSuchElementException异常。于是有人将isPresent方法和get方法结合起来使用,但这是不推荐的,如果这样组合,就与上述的加if语句判空差不多了。
推荐的使用方式:
ifPrensent:检查是否有值,值存在则调用传入的 Lambda 表达式Optional.ofNullable(person).ifPresent(p -> p.setAge(21));orElse:如果有值则返回该值,否则返回传递给它的参数值name = Optional.ofNullable(name).orElse("");orElseGet:与orElse类似,但传入的参数是一个Lambda 表达式name = Optional.ofNullable(name).orElseGet(()->"");map:如果值存在,则将提供的映射函数应用于该值。其作用就是从Optional对象中提取和转换值。注意,该方法的返回值是将传入的 Lambda 表达式的返回值包装到一个新的Optional中返回,因此map方法可以无限级联。
Optional还有一个orElseThrow()方法,在有值时直接返回, 无值时抛出想要的异常。
Optional 的使用需要注意:
- 尽量不要使用
isPresent、get方法 - 尽量不要作为类或实例属性使用
- 尽量不要作为方法参数使用
公众号“编程之路从0到1”