JDK8发布已经近5年了,已经不再是新鲜事物了。其他人我不清楚,但就我而言,虽然一直环境是JDK8,但确基本上没用JDK8的什么特性,是时候系统学一下开始使用了。
JDK8最大的特性应该非lambda莫属,该特性让你可以将功能视为方法参数,或者代码视为数据。使用lambda表达式,你可以更简洁地表示单方法接口(功能接口)实例。
如示例:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("from JDK7 Thread(run)");
}
}).start();
大家应该很熟悉上面这种匿名类的写法。
new Thread(() -> System.out.println("from JDK8 Thread(run)");).start();
lambda表达式对单方法接口实现更简洁的方式实现实例,如上述代码
Lambda表达式一共有三部分组成:
左边可以填入功能接口参数,右边表示方法函数体,可以具备返回值。
//无参数,无返回
() -> System.out.println("test");
//有参数,无返回
e -> System.out.println(e);
//有参数,有返回
e -> true
e -> {return e == null ? false : true;}
能够接收Lambda表达式的参数类型,是一个只包含一个方法的接口。
能够接收Lambda表达式的参数类型,是一个只包含一个方法的接口。
能够接收Lambda表达式的参数类型,是一个只包含一个方法的接口。
重要的事情说三遍!
函数接口,即上述说到的包含一个方法的接口,当然这里所谓的一个方法不包含默认方法和静态方法。
java8中提供一个特殊的注解显示声明接口为函数接口,当然也可以不用这个接口。
@FunctionalInterface
方法引用是只需要使用方法的名字,而具体调用交给函数式接口,需要和Lambda表达式配合使用。
举个栗子,有个Persion类,我们有个Persion数组,现在我们需要根据年龄排序好Persion:
public class Person {
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static int compareByAge(Person a, Person b) {
return a.age - b.age;
}
}
原始写法,匿名类:
private static void anonymousClassSort(Person[] persons){
Arrays.sort(persons, new Comparator() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});
}
可以将匿名类换成lamda表达式的方式:
private static void lambdaSort(Person[] persons){
Arrays.sort(persons, (o1, o2) -> o1.getAge() - o2.getAge());
}
还有就是使用方法引用的方式,可以注意到我们Person类中有方法compareByAge:
private static void methodReferenceSort(Person[] persons){
Arrays.sort(persons, Person::compareByAge);
}
方法引用分为4种:
1、静态方法引用
组成语法格式:Class::static_method
注意:
静态方法引用比较容易理解,和静态方法调用相比,只是把 . 换为 ::
在目标类型兼容的任何地方,都可以使用静态方法引用。
例子:
2、实例对象的方法引用
与静态方法引用类似,只是使用对象实例,而非类。具体用法有多种:
1.实例方法引用
class ComparisonProvider{
public int compareByName(Person a, Person b){
return a.getName().compareTo(b.getName());
}
public int compareByAge(Person a, Person b){
return a.getBirthday().compareTo(b.getBirthday());
}
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);
接口方法实现引用:
/**
* 函数式接口
*/
public interface StringFunc {
String func(String n);
}
public class MyStringOps {
//普通方法: 反转字符串
public String strReverse1(String str) {
String result = "";
for (int i = str.length() - 1; i >= 0; i--) {
result += str.charAt(i);
}
return result;
}
}
public class MethodRefDemo2 {
public static String stringOp(StringFunc sf, String s) {
return sf.func(s);
}
public static void main(String[] args) {
String inStr = "lambda add power to Java";
MyStringOps strOps = new MyStringOps();//实例对象
//strOps::strReverse1 相当于实现了接口方法func()
String outStr = stringOp(strOps::strReverse1, inStr);
System.out.println("Original string: " + inStr);
System.out.println("String reserved: " + outStr);
}
}
这个很关键,可以实现我们自定义这种方法引用的模式,让别人也可以使用我们的方法引用。
2.this or super引用
组成语法格式:super::methodName
方法的名称由methodName指定,通过使用super,可以引用方法的超类版本。
例子:
还可以捕获this 指针,this :: equals 等价于lambda表达式 x -> this.equals(x);
3、类的普通方法引用
这种有强制要求,方法没有参数
如:
String::toString 等价于 x -> x.toString()
4、构造方法引用
1.对象构造方法引用
A::new 等价于() -> new A()
2.数组构造引用
需要传递一个参数,表示数组长度
A[]:new 等价于 a -> new A[a];
方法引用可以看出来还是很易使用的,但跟过去的编码模式还是有较大的变化,所以需要多使用才能熟练。
这个最为简单,可以简单的理解现在的接口中方法可以定默认实现。
这样做到了像以前一样的抽象方法实现接口的默认实现,也方便了我们不在需要像以前一样做抽象的模板模式。
interface A{
defalut void method1(){
method2(); //默认实现
}
void method2();
}
java8接口中除了default method,还提供定义(并实现)静态方法。
interface B{
static String method(){
return "xxx";
}
}
java8之前不支持重复注解,当然这种使用场景也是极少的,在java8中支持,使用@Repeatable定义重复注解。
经典案例:
public class RepeatingAnnotations {
@Target( ElementType.TYPE )
@Retention( RetentionPolicy.RUNTIME )
public @interface Filters {
Filter[] value();
}
@Target( ElementType.TYPE )
@Retention( RetentionPolicy.RUNTIME )
@Repeatable( Filters.class )
public @interface Filter {
String value();
};
@Filter( "filter1" )
@Filter( "filter2" )
public interface Filterable {
}
public static void main(String[] args) {
for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
System.out.println( filter.value() );
}
}
}
对开发者忽略实现细节,实际上Filters就是保存多Filter注解的容器。
java8对注解进行了加强扩展,现在,注解几乎可以使用在任何元素上:局部变量、接口类型、超类和接口实现类,甚至可以用在函数的异常定义上。
枚举常量
ANNOTATION_TYPE 注解类型声明
CONSTRUCTOR 构造方法声明
FIELD 字段声明(包括枚举常量)
LOCAL_VARIABLE 局部变量声明
METHOD 方法声明
PACKAGE 包声明
PARAMETER 参数声明
TYPE 类、接口(包括注解类型)或枚举声明
TYPE_PARAMETER @since 1.8
TYPE_USE @since 1.8
public class Annotations {
@Retention( RetentionPolicy.RUNTIME )
@Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
public @interface NonEmpty {
}
public static class Holder< @NonEmpty T > extends @NonEmpty Object {
public void method() throws @NonEmpty Exception {
}
}
@SuppressWarnings( "unused" )
public static void main(String[] args) {
final Holder< String > holder = new @NonEmpty Holder< String >();
@NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();
}
}
过去我们或多或少遇到过泛型的使用,有时候我们会觉得有时候泛型不会根据我们所需要的类型而自动判断,总需要我们显示编码指定。而现在java8就对这一块进行了优化,使得具备更好的类型推断能力。
如下案例,在java7将编译报错:
public interface Value {
public static T defaultValue(){
return null;
}
}
public class TypeValueDemo {
public static void main(String[] args) {
getOrDefault(1, Value.defaultValue());
getOrDefault("1", Value.defaultValue());
}
public static Integer getOrDefault(Integer value, Integer defaultValue){
return value == null ? defaultValue : value;
}
public static String getOrDefault(String value, String defalutValue){
return value == null ? defalutValue : value;
}
}
编译器根据所需参数类型,判断泛型具体的类型。
使用反射我们知道,一个方法的参数类型能获取,但无法获取参数的名称,在java8开始我们终于能获取到方法名称了。但这个特性默认是关闭的,需要编译器打开其开关,保留编译后的参数名,-parameters,即:打包编译期间需要加上参数,使class文件保留方法的参数名。
public class ParameterNames {
public static void main(String[] demoArgs) throws NoSuchMethodException {
Method method = ParameterNames.class.getMethod("main", String[].class);
for(Parameter parameter : method.getParameters()){
System.out.println(parameter.getName());
}
}
}
java7以及之前或java8+没开开关则输出arg0,java8+打开开关输出demoArgs
stream可以说是java8三大特性之一(lambda、method reference、stream),可以发现stream是在java.util.stream包下,所以它的定位是工具。stream api极大的简化了集合操作。
使用工具方法特别多,这里可能无法全列,列出部分常用的。使用stream后集合操作大大简化,据说可以实现数据库类似的各种操作,统计、求和、分组等等。
public class StreamDemo {
static class User{
private String name;
private int age;
private List asset = new ArrayList<>();
private final Random random = new Random();
public User(String name, int age) {
this.name = name;
this.age = age;
for(int i=20; i < age; i++){
//模拟收入
asset.add(random.nextInt(i));
}
}
}
public static void main(String[] args) {
List userList = new ArrayList<>();
Random random = new Random();
IntStream.range(0, 10).forEach(i -> userList.add(new User(String.valueOf(i), random.nextInt(100))));
//输出总收入大于500万的姓名和年级
userList.stream().filter(u -> u.asset.stream().mapToInt(i -> i).sum() > 500).map(user -> user.name+"["+user.age+"]").forEach(System.out::println);
long start = 0L;
//获取所收入总和
start = System.nanoTime();
System.out.println(userList.stream().mapToInt(u -> u.asset.stream().mapToInt(i -> i).sum()).sum());
System.out.println(System.nanoTime()-start);
//并行计算
start = System.nanoTime();
System.out.println(userList.stream().parallel().mapToInt(u -> u.asset.stream().mapToInt(i -> i).sum()).reduce(0, Integer::sum));
System.out.println(System.nanoTime()-start);
//同龄分组
Map> map = userList.stream().collect(Collectors.groupingBy(u -> u.age));
System.out.println(map);
}
}
当然stream能力远比这介绍的强大,还需要多多摸索。毕竟集合可以算是java编程中使用最多的工具了。
JDK1.8对HashMap的数据结构做了一定的调整,将数组和链表的模式,改成了数组和链表(转红黑树)的模式,具体的性能提升简单说是从数据结构层面调整使得性能得到提升。具体建议大家可以看下源码,毕竟JDK的源码还是很值得看的。(后续单独写一篇笔记)
紧凑profile简单理解为了减少java平台的应用内存占用,将jdk的API包分为三个层次,使用profile控制加载所需的API。这个应该只在特殊场景才会使用,毕竟一般也不会省这么一点内存而使用。
确实可以看到,大部分时候我们应该都只需要compact1而已。
从官方文档来看,安全改动似乎还挺多的,但可能对于我们开发而言很少会感知这些,这里也不做篇幅介绍。
因为没有使用过javafx,所以没有去了解和关注
jdeps命令工具,用于分析包、类层级的依赖关系。
jjs是一个基于标准Nashorn引擎的命令行工具,可以接受js源码并执行。没太用过,所以不发表意见。据说是一个较大的改动,可能是对js引擎的替换吧。
javac和javadoc优化,其中就包括javac的-parameters和javadoc DocTree
java8对Date和Time工具API改动比较大,受joda-time和开发人员吐槽的意见终于调整了一波。
Clock类可以替代System.currentTimeMillis()和TimeZone.getDefault()。
LocalDate、LocalTime、LocalDateTime即日期、时间和时间日期工具类,和ZonedDateTime差不多,只是一个带有时区信息,一个不带有时区信息。
Duration用来计算两个时间日期的不同,可以当做工具类使用。
Clock clock = Clock.systemUTC();
System.out.println(clock.instant());
System.out.println(clock.millis());
LocalDate date = LocalDate.now();
LocalDate dateFromClock = LocalDate.now(clock);
System.out.println( date );
System.out.println( dateFromClock );
LocalTime time = LocalTime.now();
LocalTime timeFromClock = LocalTime.now( clock );
System.out.println( time );
System.out.println( timeFromClock );
LocalDateTime datetime = LocalDateTime.now();
LocalDateTime datetimeFromClock = LocalDateTime.now( clock );
//不带有时区信息
System.out.println( datetime );
System.out.println( datetimeFromClock );
ZonedDateTime zonedDatetime = ZonedDateTime.now();
ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );
System.out.println( zonedDatetime );
System.out.println( zonedDatetimeFromClock );
System.out.println( zonedDatetimeFromZone );
final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );
final Duration duration = Duration.between( from, to );
System.out.println( "Duration in days: " + duration.toDays() );
System.out.println( "Duration in hours: " + duration.toHours() );
Arrays加了一些对数组的处理,其中就包括并行操作,如:parallelSort 并行排序。
Random random = new Random();
long[] arrayOfLong = new long [ 20000000 ];
Arrays.parallelSetAll(arrayOfLong, i -> random.nextLong());
long[] copy = Arrays.copyOf(arrayOfLong, arrayOfLong.length);
Arrays.stream(arrayOfLong).limit(10).forEach(System.out::println);
System.out.println("----------------------");
long start = System.nanoTime();
Arrays.sort(copy);
Arrays.stream(arrayOfLong).limit(10).forEach(System.out::println);
System.out.println("----------------------");
System.out.println("take time :" + (System.nanoTime() - start));
start = System.nanoTime();
Arrays.parallelSort(arrayOfLong);
Arrays.stream(arrayOfLong).limit(10).forEach(System.out::println);
System.out.println("----------------------");
System.out.println("take time :" + (System.nanoTime() - start));
会发现确实需要很大的数据才能看到性能优势,当然可能是因为我本就cpu核数和服务器不能比。
使用Metaspace(JEP 122)代替持久代(PermGen space)。在JVM参数方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原来的-XX:PermSize和-XX:MaxPermSize。
国际化增强
部署增强
java8支持标准的base64编解码,不再需要引入第三方包了。
优化IO、NIO,提升string.getbytes性能
增加并发工具类
java8确实算是改动比较大的一个版本,对我们开发人员较相关,需要关注的个人感觉主要是三大特性:lambda、stream、方法引用,其他的作为了解应该就可以了。