Java8(又称为JDK.8)是2014年3月发布的一个重要版本,新增了许多重要特性,如Lambda表达式,方法引用,函数式接口,Stream API,Date Time API,新工具等。下面将简要介绍。
Lambda表达式
在使用1.8以上级别的编译器时,如果可以使用Lambda表达式,IDE会有提示。举个栗子如下:
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello world!");
}
});
t1.start();
// Thread t2 = new Thread(() -> System.out.println("hello world!"));
// t2.start();
}
Thread构造方法里面的参数可以使用lambda表达式,即() -> System.out.println(“hello world!”)
。
不是所有的接口实例都能用lambda表达式来代替。当接口只有一个抽象方法时,该接口实现才能用lambda表达式代替,这样的接口也叫函数式接口。
lambda表达式的由来
假设我们按照最原始的写法,那就是定义一个实现了Runnable接口的类并复写run方法。然后实例化一个该类的对象,将其传给Thread类的构造方法,用来生成一个线程对象。
这一套写起来非常麻烦,而且这个Runnable接口的实现类,仅仅是为了用来构造线程对象,没有复用的需求。 因此,针对这种情况有了匿名类的出现,直接在使用的时候来实例化一个没有名字的类,减少了不少的工作量。
如果一个接口只有一个抽象方法,那么用匿名类的方式来实例化该接口,写起来还是会感觉有点多余,就比如上面的Runnable接口的实例化。于是lambda表达式就出现了。
lambda表达式的本质是一个接口的实例,而在表现形式上,lambda表达式一个没有方法签名,只有方法实现的匿名函数。为什么只有方法实现就能表示一个接口实例呢?因为既然这个lambda表达式出现在这里,其实现的接口和复写的方法就能确定了。比如上面的例子,() -> System.out.println(“hello world!”)
这个lambda表达式,显然代表一个Runnable接口实例,复写了run
方法。
lambda表达式的规则
Lambda表达式也可称为闭包,允许把函数作为一个方法的参数(函数作为参数传递进方法中),使得代码变得更加简洁、紧凑。
从表现形式上看,Lambda表达式就是一个匿名函数,(a,b,…)
表示参数列表,->
表示连接符,{}
内部是方法体。
Lambda表达式规则:
- 如果形参列表为空,只需保留
()
即可;如果形参只有一个,()
可以省略,只需要参数名称即可。 - 如果方法体只有一句且无返回值,
{}
可以省略。 - 形参列表的数据类型会自动推断。
- Lambda表达式不会生成单独的内部类文件。
- Lambda表达式若访问了局部变量,则局部变量必须是final的,若是没加final关键字,系统会自动添加,此后该局部变量不能修改,否则会报错。
方法引用
上文说了lambda表达式,下面看这样一个例子:
public static void main(String[] args) {
String[] strs = {"b","a","c"};
Arrays.sort(strs, (s1, s2) -> s1.compareToIgnoreCase(s2));
}
显然,sort方法的第二个参数使用了lambda表达式,等价于:
public static void main(String[] args) {
String[] strs = {"b","a","c"};
Arrays.sort(strs, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
}
});
}
Lambda表达式允许将函数作为形参传递到方法中,但这里还有一种更简单的方式,即方法引用:
public static void main(String[] args) {
String[] strs = {"b","a","c"};
Arrays.sort(strs, String::compareToIgnoreCase);
}
方法引用是用来直接访问类或者实例的已经存在的方法,仅仅是引用而不执行。
当lambda表达式中只是执行一个方法调用时,将其写成方法引用的形式可读性更高。方法引用仍然是lambda表达式,方法引用的操作符是双冒号"::"。简单地说,就是当lambda表达式仅仅调用一个已存在的方法而不做其他任何事时,可以通过方法名称引用这个方法。方法引用是一个更加紧凑,易读的lambda表达式。
方法引用类型
方法引用有四种类型,分别是:
- 引用静态方法。
- 引用实例方法。
- 引用某个类型的任意对象的实例方法。
- 引用构造方法。
如果将方法引用展开为基本的Java写法,方法是通过类调用的就是静态方法引用,通过具体对象实例调用的就是实例引用,通过任意对象调用的就是任意实例引用,通过构造方法调用的就是构造方法引用。
如上面的方法引用String::compareToIgnoreCase
,其展开后的调用是:s1.compareToIgnoreCase(s2)
。这里s1不是某个具体的对象实例,而是String类型的任意对象,故属于任意实例引用(写法跟静态方法引用一样,但不是静态方法引用)。
构造方法引用的写法有点特殊,不是nameSpace::methodName
的形式,而是namespace::new
。
Stream API
上文中介绍函数引用时举了一个栗子:
public static void main(String[] args) {
String[] strs = {"b","a","c"};
Arrays.sort(strs, String::compareToIgnoreCase);
}
遍历排序后的字符串数组,可以通过for循环的方式。而Java8中提供了一种新的方法:
Arrays.stream(strs).forEach(System.out::println);
Stream是Java函数式编程的重要API。Stream并不是某种数据结构,而是数据源的一种视图。这里的数据源可以是一个数组,Java容器或者I/O channel等。
常见的Stream接口继承关系图(引用自参考资料1):
IntStream,LongStream和DoubleStream对应三种基本(非包装类)类型的的视图,Stream对应所有剩余类型的视图。
stream特点
- 无存储。stream不是一种数据结构,它只是某种数据源的一个视图。
- 用于函数式编程。对stream的任何修改都不会修改实际的数据源,比如对stream执行过滤操作并不会删除被过滤的元素,而是产生一个过滤后的stream视图。
- 惰式执行。stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。
- 可消费性。Stream只能被消费一次,一旦遍历过就会失效,就像容器的迭代器一样,想要再次遍历必须重新生成。
详细介绍这里就不展开了,具体见参考资料1。
Date Time API
Java8中引入了新的时间日期类的API,可参考博主之前的文章-JAVA8日期API。
参考资料
1.http://www.cnblogs.com/CarpenterLee/p/6545321.html