您的位置:首页 > 编程语言 > Java开发

Java JVM(五):JDK8 新特性

2015-08-29 19:05 639 查看
        Java8 是一个重大改变的版本。比如说增加了Lambda表达式,集合的流式操作,函数式接口,接口默认方法
等。

一. 函数式接口
        如果一个接口定义唯一一个抽象方法,那么这个接口就可以成为函数式接口,如 Runnable,Callable 接口。
        可以在函数式接口前加一个 @FunctionalInterface注解,非必须。在接口中添加了@FunctionalInterface,只允许有一个抽象方法,否则编译器也报错。Java8
中 Runnable 接口就是一个函数式接口,如下:

@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see     java.lang.Thread#run()
*/
public abstract void run();
}

        像Runnable,Callable等有且只有一个抽象方法的接口,一般称为单一抽象方法接口,我们平常使用它的时候,比较习惯于使用一个内部类,这至少也要几行的代码,如下:
public class AnonymousInnerClassTest {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("A thread created and running ...");
}
}).start();
}
}

        如果我们声明为函数式接口,再结合Lambda表达式,那么一行代码就可以搞定,比较方便。如下代码:
/*
* Implementing the interface by creating an
* anonymous inner class versus using
* lambda expression.
*/
public class SimpleFunInterfaceTest {
public static void main(String[] args) {
carryOutWork(new SimpleFuncInterface() {
@Override
public void doWork() {
System.out.println("Do work in SimpleFun impl...");
}
});

carryOutWork(() -> System.out.println("Do work in lambda exp impl..."));
}
public static void carryOutWork(SimpleFuncInterface sfi){
sfi.doWork();
}
}

输出为:
Do work in SimpleFun impl...
Do work in lambda exp impl...

        当然,一个接口也可以继承另外一个函数式接口,只要它不再声明抽象方法(可以使用默认方法),那么它也是一个函数式接口。

例如:
@FunctionalInterface
public interface ComplexFunctionalInterface extends SimpleFuncInterface {
default public void doSomeWork(){
System.out.println("Doing some work in interface impl...");
}
default public void doSomeOtherWork(){
System.out.println("Doing some other work in interface impl...");
}
}


总结:好处在哪里?
         结合Lambda表达式,比之前更加的简便。

二.lambda 表达式
        lambda 表达式是一个匿名方法,可由三部分组成: 参数列表,箭头( ->),表达式或者语句块。利用Lambda表达式,可以把一个几行的语句变成一个非常简单的语句。

        lambda表达式我们也可以把它当做一种类型,像基本类型,引用类型一样,我们可以把它当做是一种目标类型,这种目标类型就是函数式接口。一个lambda表达式必须对应至少一个目标类型,也就是必须对应一种函数接口。

        一个lambda表达式只有在转型成一个函数接口后才能被当做Object来进行使用,所以说我们要当Object来使用必须先转型。

Function:
在java.util.function包中 预定义了很多函数式接口以避免用户重复定义。最典型的就是Function:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
......
}

这个接口代表一个函数,接受一个 T 类型的参数,并且返回一个 R 类型的返回值。

Consumer:
另一个预定义函数接口叫做 Consumer,跟 Function 的唯一不同是它没有返回值。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
......
}

Predicate:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
......
}


用于判断某项条件是否满足。经常用来进行筛选操作。

综合来说:
        一个Lambda表达式就是定义了一个匿名方法,只不过这个方法必须符合至少一个函数接口。

用处:
        lambda表达式主要用于替换以前广泛使用的匿名内部类,各种回调,事件响应器等。
例如:
Thread oldSchool = new Thread( new Runnable () {
@Override
public void run() {
System.out.println("This is from an anonymous class.");
}
});

Thread gaoDuanDaQiShangDangCi = new Thread( () -> {
System.out.println("This is from an anonymous method (lambda exp).");
});

        在这里,比如说第二个线程中的lambda表达式,你并不需要显示地转换为一个Runnable,因为Java可以根据上下文自动推断出一个Thread 的构造函数接受一个Runnable
参数,而传入的 lambda表达式正好符合其 run() 函数,所以 Java编译器推断它为 Runnable。

        从形式上来看, lambda 表达式只是节省了几行代码,但是从目前来说,lambda表达式可以配合
"集合类批处理操作"的内部迭代和并行处理;另外,长期来看,Java 也好像是要向函数式编程语言这个方面来引导。

三.集合的批处理和流式操作:
3.1 批处理
        集合的批处理和流式操作再配合Lambda表达式应该算是Java8 中最重要的改进,主要是希望在对集合的操作中,可以充分利用现代多核 CPU 来进行并行计算。
        我们之前 Java8 之前的集合操作都是外部迭代的,也就是写一些 for 循环这种。那么现在的集合类都有一个forEach()方法,来对元素实现迭代,这个forEach方法就在Iterable接口中,Collection接口继承了它,同时,Map接口也有一个forEach方法,forEach()
方法可以接受一个函数式接口Consumer作为参数,所以说可以使用lambda表达式。

3.2 流式操作
        Java8为集合类引入了另外一个重要概念:流。一个流通常以一个集合类实例作为其数据源,然后在其上定义各种操作。流的 API 设计使用了管道。对一个流的操作会返回另一个流。如同 IO 的API 或者 StringBuffer的
append 方法那样,从而多个不同的操作可以在一个语句串起来。看下面的方法:
List<Shape> shapes = ...
shapes.stream()
.filter(s -> s.getColor() == BLUE)
.forEach(s -> s.setColor(RED));


        首先调用 stream() 方法,以集合类对象 shapes 里面的元素作为数据源,生成一个流。然后在这个流上调用 filter 方法,挑出蓝色的,返回另外一个流。最后调用forEach() 方法将这些蓝色的物体喷成红色。(forEach方法不再返回流,而是一个终端方法,类似于StringBuffer在调用若干append之后的那个toString)

        filter 方法的参数是 Predicate 类型。forEach方法的参数是Consumer 类型,它们都是函数式接口,所以可以使用lambda表达式。
        还有一个方法叫做parallelStream(),它和Stream()一样,只不过要指明并行处理,希望能充分利用现代CPU的多核特性。

例子: 给出一个String 类型的数组,找出其中所有不重复的素数
public void distinctPrimary(String... numbers) {
List<String> l = Arrays.asList(numbers);
List<Integer> r = l.stream()
.map(e -> new Integer(e))
.filter(e -> Primes.isPrime(e))
.distinct()
.collect(Collectors.toList());
System.out.println("distinctPrimary result is: " + r);
}

第一步:传入一系列String(假设都是合法的数字),转成一个List,然后调用stream()方法生成流。

第二步:调用流的map方法把每个元素由String转成Integer,得到一个新的流。map方法接受一个Function类型的参数,上面介绍了,Function是个函数接口,所以这里用λ表达式。

第三步:调用流的filter方法,过滤那些不是素数的数字,并得到一个新流。filter方法接受一个Predicate类型的参数,上面介绍了,Predicate是个函数接口,所以这里用λ表达式。

第四步:调用流的distinct方法,去掉重复,并得到一个新流。这本质上是另一个filter操作。

第五步:用collect方法将最终结果收集到一个List里面去。collect方法接受一个Collector类型的参数,这个参数指明如何收集最终结果。在这个例子中,结果简单地收集到一个List中。我们也可以用Collectors.toMap(e->e, e->e)把结果收集到一个Map中,它的意思是:把结果收到一个Map,用这些素数自身既作为键又作为值。toMap方法接受两个 Function类型的参数,分别用以生成键和值,Function是个函数接口,所以这里都用λ表达式。

你可能会觉得在这个例子里,List l被迭代了好多次,map,filter,distinct都分别是一次循环,效率会不好。实际并非如此。这些返回另一个Stream的方法都是“懒 (lazy)”的,而最后返回最终结果的collect方法则是“急(eager)”的。在遇到eager方法之前,lazy的方法不会执行。

当遇到eager方法时,前面的lazy方法才会被依次执行。而且是管道贯通式执行。这意味着每一个元素依次通过这些管道。例如有个元素“3”,首 先它被map成整数型3;然后通过filter,发现是素数,被保留下来;又通过distinct,如果已经有一个3了,那么就直接丢弃,如果还没有则保 留。这样,3个操作其实只经过了一次循环。

除collect外其它的eager操作还有forEach,toArray,reduce等。

下面来看一下也许是最常用的收集器方法,groupingBy:
//给出一个String类型的数组,找出其中各个素数,并统计其出现次数
public void primaryOccurrence(String... numbers) {
List<String> l = Arrays.asList(numbers);
Map<Integer, Integer> r = l.stream()
.map(e -> new Integer(e))
.filter(e -> Primes.isPrime(e))
.collect( Collectors.groupingBy(p->p, Collectors.summingInt(p->1)) );
System.out.println("primaryOccurrence result is: " + r);
}

注意这一行:
Collectors.groupingBy(p->p, Collectors.summingInt(p->1))


它的意思是:把结果收集到一个Map中,用统计到的各个素数自身作为键,其出现次数作为值。

下面是一个reduce的例子:
//给出一个String类型的数组,求其中所有不重复素数的和
public void distinctPrimarySum(String... numbers) {
List<String> l = Arrays.asList(numbers);
int sum = l.stream()
.map(e -> new Integer(e))
.filter(e -> Primes.isPrime(e))
.distinct()
.reduce(0, (x,y) -> x+y); // equivalent to .sum()
System.out.println("distinctPrimarySum result is: " + sum);
}


reduce方法用来产生单一的一个最终结果。

流有很多预定义的reduce操作,如sum(),max(),min()等。

再举个现实世界里的栗子比如:
// 统计年龄在25-35岁的男女人数、比例
public void boysAndGirls(List<Person> persons) {
Map<Integer, Integer> result = persons.parallelStream().filter(p -> p.getAge()>=25 && p.getAge()<=35).
collect(
Collectors.groupingBy(p->p.getSex(), Collectors.summingInt(p->1))
);
System.out.print("boysAndGirls result is " + result);
System.out.println(", ratio (male : female) is " + (float)result.get(Person.MALE)/result.get(Person.FEMALE));
}


Ps:
        给Java 集合类增加批量数据的支持,通常称这种批量数据操作为 "Java 中的 filter/map/reduce"。批量操作有并行和串行两种操作模式。我们期望可以利用底层平台的并行特性。
        JDK 中已经增加了一个新包:java.util.stream包,能够使java8 集合类库执行类似 filter/map/reduce 的操作。这个流式API 能够使我们在数据流之上编写串行或者并行的操作。比如说:
List persons = ..
//串行操作
Stream stream=persons.stream();
//并行操作
Stream parallelStream=persons.parallelStream();

四. 接口默认方法
        Java8 可以在 interface 中有方法的实现,也称为默认方法。那么 interface 中 有了方法的实现,也就是说,可以实现多继承,(当然之前利用一些方法也可以实现多继承,但是Java 一直不提倡多继承)。
        那么为什么现在加入了默认方法实现多继承呢?因为 interface 太过依赖他们的实现类了,要往 interface中加入一个方法就必须修改所有的实现类。那么Java8 中加入一些特性可能需要修改一些核心类,但是很多核心类不仅仅JDK中实现,很多第三方库中也会有实现,那么一改动可能就会出现兼容性问题。那么默认方法就可以很好的处理这个问题。
        代码示例:
interface ITest {
public default void sayHello(){
System.out.println("Hello");
}
}
public class Test implements ITest{
public static void main(String[] args) {
new Test().sayHello();
}
}


注意,需要加一个 default。

参考:
1.http://www.ibm.com/developerworks/cn/java/j-lo-jdk8newfeature/index.html
2.http://blog.sanaulla.info/2013/03/21/introduction-to-functional-interfaces-a-concept-recreated-in-java-8/
3.http://blog.csdn.net/ioriogami/article/details/12782141
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息