Java8是Java最新的版本,它带给我们以"Project Lambda"为核心的一些新特性。在这篇文章中,就让我们一起领略一下Java8的魅力吧。

接口默认方法

Java8对接口做了一些改善,它支持在接口中定义默认方法。在过去,java类库的接口中添加方法基本上是不可能的,在接口中添加方法意味着破坏了实现了这个接口的代码。但是现在,我们只要能够提供一个正确默认方法的实现, 维护者就可以在接口中添加方法。

默认方法可以使用default关键字来定义。定义一个默认方法后,实现接口的所有类都可以直接使用这一方法。

public interface MathService {
    default Double abs(Double x) {
        return x > 0 ? x : -x;
    }

    public Double sqrt();
}

在MathService实现默认的方法abs。再在MathServiceImpl类中实现MathService接口。

public class MathServiceImpl implements MathService {

    @Override
    public Double pow(Double a, int times) {
        if (0 == a || 1 == a) {
            return a;
        }
        Double result = a;
        if (times > 0) {
            for (int i = 1; i < times; i++) {
                result *= result;
            }
            return result;
        }
        else if (0 == times) {
            return 1d;
        }
        else {
            return 1/pow(a, -times);
        }
    }
}

实例化MathServiceImpl类后即可调用接口的默认方法。

public static void main(String[] args) {
    MathService mathService = new MathServiceImpl();
    mathService.abs(-5d);
}

提示

需要注意的是:接口不能提供对Object类的任何方法的默认实现,包括equals,hashCode和toString方法。这是因为所有的类都是Object类的子孙类,而且如果一个类已经实现一个方法,它会优于默认方法使用。所以在实现这个接口前,该类就有了equals、hashCode和toString方法,所以接口实现这些默认方法也就无效了。

函数式接口

Java8的另一个概念是函数式接口。如果一个接口定义了唯一一个抽象方法的话,这个接口就成为了函数式接口。抽象方法可以使用abstract关键字来定义。如:

public abstract void doSomething();

与此同时,Java引入了一个新的标注:@FunctionalInterface。我们可以将标注写在接口前,表明这个接口是函数式接口。

Lambda表达式

Lambda表达式是Java8的核心,以至于它被叫做Lambda Project。一个函数式接口最有价值的属性就是能够用Lambda表达式来实例化。

什么是Lambda表达式

我们可以通过几个例子来学习Lambda表达式:

比如我们需要排列一组存放在List容器中的对象,过去我们需要这样实现:

List<SomeObject> words = new ArrayList<>();
        words.add(new SomeObject("test5"));
        words.add(new SomeObject("some0"));
        words.add(new SomeObject("another3"));

        Collections.sort(words, new Comparator<SomeObject>() {
            @Override
            public int compare(SomeObject o1, SomeObject o2) {
                return o1.getSomething().compareTo(o2.getSomething());
            }
        });

需要使用匿名的方式覆盖compare方法来进行排序。但是引入了Lambda表达式后,只需这样:

Collections.sort(words, (o1, o2) -> o1.getSomething().compareTo(o2.getSomething()));

是不是简洁了很多。其中(o1, o2) -> o1.getSomething().compareTo(o2.getSomething())是Lambda表达式,左侧的括号里的内容是输入列表,右边是返回值。每一个表达式都对应了一个类型,而通常来说会是接口类型。我们可以把Lambda表达式当做任何一个函数式接口类型。

使用Lambda表达式

Lambda表达式代表一个函数式接口,那么抽象方法不同的情况下该如何使用Lambda表达式:

  1. 抽象方法有多个参数,有返回值

(x1, x2) -> x1 + x2;
  1. 抽象方法只有一个参数,有返回值

x1 -> x1 + x1;
  1. 抽象方法无输入,有返回值

() -> 5;
  1. 抽象方法无返回值(x1, x2) -> { System.out.print(x1 + x2); }

方法与构造方法的引用

Java8允许使用::关键字来传递方法和构造函数的引用。它可以简化一些Lambda表达式的书写:

类的静态方法:x -> String.valueOf(x) 等价于 String::valueOf类的非静态方法:x -> x.toString() 等价于 Object::toString构造函数: () -> new HashMap() 等价于 HashMap::new

提示

在这里同样支持重载,如果一个类有多个构造函数如HashMap,那么HashMap::new就能够指向它的构造方法中任何一个。编译器根据在使用的函数式接口决定使用哪个方法。

将Lambda表达式赋值给函数式接口变量

因为Lambda表达式表示了一个函数式接口,我们也就可以将之赋值给一个变量。如:

Comparator<SomeObject> someObjectComparator = (o1, o2) -> o1.getSomething().compareTo(o2.getSomething());

作用域

在lambda表达式中访问外层作用域和匿名对象中的方式很相似。

final int plus = 1;
Comparator<SomeObject> someObjectComparator = (o1, o2) -> (o1.getSomething() + plus).compareTo(o2.getSomething());

它可以直接访问外部标记为final的变量,但是事实上不声明为fianl也可以进行访问,这一点是与匿名对象不同的:

int plus = 1;
Comparator<SomeObject> someObjectComparator = (o1, o2) -> (o1.getSomething() + plus).compareTo(o2.getSomething());

但是如果plus变量在后面的代码中被修改就会无法编译,所以实际上在Lambda表达式中使用的外部变量还是具有fianl语义的,这一点使我们需要注意的。

登录发表评论 注册

反馈意见