通过一些小代码段,看看Java8新的特性吧
接口的默认方法 Default Methods for Interfaces:
Java8允许我们使用default 关键字来实现对非抽象方法的实现,例:
interface Formula {double calculate(int a);default double sqrt(int a) { return Math.sqrt(a);}}
看,Formula定义了一个抽象方法calculate,然后又定义了一个默认方法sqrt,具体的类只要实现抽象方法calculate即可,默认方法可以直接使用。
Formula formula = new Formula() {@Overridepublic double calculate(int a) { return sqrt(a * 100);}};
formula.calculate(100); // 100.0 formula.sqrt(16); // 4.0
上面的这段代码中,formula是通过匿名对象实现的。当然,这段代码看起来过于繁琐,用6行代码实现一个小小的计算sqrt(a * 100),不过在下面我们会看到Java8用更加帅气的方式实现单个方法对象的做法,那就是
Lambda 表达式
让我们先来一个小例子:看看在之前的Java版本中如何实现对String字符串进行排序
Listnames = Arrays.asList("peter", "anna", "mike", "xenia");Collections.sort(names, new Comparator () {@Overridepublic int compare(String a, String b) { return b.compareTo(a);} });
Collections.sort这个方法的参数是一个列表和一个比较器,它可以对你给出的list进行排序。这样你就发现经常需要创造一些匿名的comparators来进行排序方法。
为了取代一直以来的创造匿名对象的方式,Java8推出了更加简洁的语法:lambda表达式:
Collections.sort(names, (String a, String b) -> {return b.compareTo(a); });
就像你看见的这段代码,它,更加的简洁,更加的清晰易读,但是,它还可以更加的简洁:
Collections.sort(names, (String a, String b) -> b.compareTo(a));
这行代码,你可以省略掉{}和关键字return,但是还可以更简洁:
Collections.sort(names, (a, b) -> b.compareTo(a));
Java编译器可以自动判断参数类型,因此,类型也可以省略
感受到了简洁的语法后,我们需要深入了解一下,看看Lambda表达式怎样在实际工作中使用:
函数式接口
Lambda表达式是怎样匹配Java类型系统呢?通过接口指定,每一个lambda对应一个给定的类型。一个被称为函数式接口必须要包含一个准确的抽象方法的声明。这样每个lambda和它的对应类型将会匹配到个抽象方法。因为default method不是抽象的,所以可以自由的在函数式接口中添加default方法 只要这个接口包含唯一的一个抽象方法,我们就可以使用任意的接口。为了确定你的接口满足要求,需要添加一个@FunctionalInterface注解,这样,当你试图给这个接口添加第二个抽象方法声明的时候,编译器就会自动感知,会抛出一个编译期错误。
栗子: <!-- lang:java --> @FunctionalInterface interface Converter<F, T> { T convert(F from); } <!-- lang:java --> Converter<String, Integer> converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123
记住:如果代码中没有写注解 @FunctionalInterface,那么也是错误的!
方法和构造函数的引用
上面实例的代码通过使用静态函数引用可以进一步的简化
Converterconverter = Integer::valueOf; Integer converted = converter.convert("123"); System.out.println(converted); // 123
Java 8 允许你使用关键字 :: 来引用方法和构造器。上面的代码显示的是怎样引用一一个静态方法。但是我们同样,也可以引用对象方法。 <!-- lang:java --> class Something { String startsWith(String s) { return String.valueOf(s.charAt(0)); } }
Something something = new Something(); Converterconverter = something::startsWith; String converted = converter.convert("Java"); System.out.println(converted); // "J"
让我们看看 :: 关键字对于构造方法是如何工作的。首先我们需要构造一个带有不同构造器的bean实例: <!-- lang:java --> class Person { String firstName; String lastName;
Person() {} Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
然后我们来指定一个person 工厂接口,为创建person对象时使用: <!-- lang:java --> interface PersonFactory<P extends Person> { P create(String firstName, String lastName); } 我们可以通过构造器引用来连接所有的东西,来取代手动的创建工厂 <!-- lang:java --> PersonFactory<Person> personFactory = Person::new; Person person = personFactory.create("Peter", "Parker"); 我们通过Person::new 创建了一个Person构造器的引用,Java编译器会通过PersonFactory.create的方法签名,匹配到正确的构造器方法。
Lambda作用域
从Lambda表达式中获取外部作用域变量的方式与匿名对象的方式非常相似,你可以获取final变量从局部外部变量中,跟实例域和静态变量一样。 获取局部变量 我们可以读取final局部变量从lambda表达式的外部作用域中 <!-- lang:java --> final int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
但是不同于匿名对象,变量num不需要必须设置为final,下面的代码同样有效: <!-- lang:java --> int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
然而,num是一个隐式的final局部变量,编译器才能通过。下面这段代码就不会编译通过: <!-- lang:java --> int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); num = 3; 在Lambda表达式内部修改num的值同样是不被允许的。你只要把这个变量看做final修饰的即可,只是final没有写出来罢了。
获取实体域和静态变量 不同于局部变量,我们在具有Lambda表达式的代码中,可以对实体域和静态变量进行读和写的权利。这种方式同样在匿名对象中为我们所熟知。 <!-- lang:java --> class Lambda4 { static int outerStaticNum; int outerNum;
void testScopes() { ConverterstringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); }; Converter stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; } }
获取默认接口方法: 还记得开头讲述的formula 那个栗子吗?接口fomula定义个一个默认方法:sqrt,可以被包含匿名对象的每一个formula实例使用,这种机制跟lambda表达式无关
默认方法不能被lambda表达式使用,下面的代码不能被编译: <!-- lang:java --> Formula formula = (a) -> sqrt( a * 100);