|
JDK1.8新特性 1 接口的默认方法 java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,如下: 代码如下:
interface Formula {
double calculate(int a); default double sqrt(int a) {
return Math.sqrt(a);
}
} Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用。 代码如下:
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
}; formula.calculate(100); formula.sqrt(16); 注意:在Java中只有单继承,如果要让一个类赋予新的特性,通常是使用接口来实现的。
2 Lambda 表达式
老版本的Java中是如何排列字符串的? 代码如下:
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
只需要给静态方法 Collections.sort 传入一个List对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。
在Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,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表达式还能作出什么更方便的东西来:
3 函数式接口
Lambda表达式是如何在java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。 我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。 示例如下: 代码如下:
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted); // 123
需要注意如果@FunctionalInterface如果没有指定,上面的代码也是对的。 译者注 将lambda表达式映射到一个单方法的接口上,这种做法在Java 8之前就有别的语言实现,比如Rhino JavaScript解释器,如果一个函数参数接收一个单方法的接口而你传递的是一个function,Rhino 解释器会自动做一个单接口的实例到function的适配器,典型的应用场景有 org.w3c.dom.events.EventTarget 的addEventListener 第二个参数 EventListener。
4 方法与构造函数引用
前一节中的代码还可以通过静态方法引用来表示: 代码如下: Converter<String,Interger> converter = Integer::valueOf; Integer converred = converter.convert(“123”); System.out.println(converted); Java 8 允许你使用 :: 关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法: 代码如下: Converter = something::startswith; String converted = converter.convert(“java”); System.out.println(converted);
接下来看看构造函数是如何使用::关键字来引用的,首先我们定义一个包含多个构造函数的简单类:
代码如下: Class Person{ String firstName; String lastName; Person(){} Person(String firtstName,String lastName){ this.fristName = firstName; This.lastName = lastName; } } 接下来我们指定一个用来创建Person对象的对象工厂接口: 代码如下: interface PersonFactory<P extends Person> {
P create(String firstName, String lastName);
} 我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂: 代码如下:
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");
我们只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。
5 Lambda 作用域
在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。
6 访问局部变量
我们可以直接在lambda表达式中访问外层的局部变量: 代码如下:
final int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确:
代码如下:
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
不过这里的num必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译:
代码如下:
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
num = 3;
在lambda表达式中试图修改num同样是不允许的。
7 访问对象字段与静态变量
和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的: 代码如下 Class Lambda{ Static int outerStaticNum; Int outeNum; Void testScoes(){ Converter<Integer,String> StringConverter1 =(from) ->{ outerNum = 18; Return String.valueOf(from); }; Converter<Integer,String> stringConverter2 = (form) ->{ outerStaticNum = 62; return String.valueOf(from); } } }
8 访问接口的默认方法
上面第一节中的formula例子,接口Formula定义了一个默认方法sqrt可以直接被formula的实例包括匿名对象访问到,但是在lambda表达式中这个是不行的。
Lambda表达式中是无法访问到默认方法的,以下代码将无法编译: 代码如下: Formula formula = (a) -> sqrt(a * 100); Built-in Functional Interfaces JDK 1.8 API包含了很多内建的函数式接口,在老Java中常用到的比如Comparator或者Runnable接口,这些接口都增加了@FunctionalInterface注解以便能用在lambda上。
Java 8 API同样还提供了很多全新的函数式接口来让工作更加方便,有一些接口是来自Google Guava库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。
Predicate接口 Predicate 接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非):
代码如下: Predicate<Satring> predicate = (s) -> s.length() > 0; Predicate.test(“foo”); Predicate.negate().test(“foo”); Predicate<Boolean> nonNull = Objects::nonNull; Predicate<Boolean>isNull = Objests::isNull; Predicate<String>isEmpty = String::isEmpty; Predicate<String>isNotEmpty.negate(); Function 接口 Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen):
代码如下:
Function<String,Integer>toInteger = Integer::valueOf; Function<String,Integer>backToString= toInteger.andThen(String::valueOf); backToString.apply(“123”); Supplier 接口
Supplier 接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数 代码如下: Supplier<Persion> personSupplier = Person:: new; personSupplier.get(); Comparator 接口
Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法: 代码如下: Comparrator<Person> comparator = (p1,p2) -> p1.firtName.compareTo(p2.firstName); Person p1 = new Person(“qqq”,”doe”); Person p1 = new Person(“yyy”,”wouderland”); Comparator.compate(p1,p2); Compatator.reversed().compare(p1,p2); Optional 接口 Optional 不是函数是接口,这是个用来防止NullPointerException异常的辅助类型,这是下一届中将要用到的重要概念,现在先简单的看看这个接口能干什么: Optional 被定义为一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java 8中,不推荐你返回null而是返回Optional。 代码如下: Optional<String> optinal = Optional.of(“bam”); optional.isPresent(); optional.get(); optional.orElse(“fallback”); Optional.iPresent((s) -> System.out.println(s.charAt(0)));
09 Annotation 注解
ava 8允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@Repeatable即可。 例 1: 使用包装类当容器来存多个注解(老方法)
代码如下:
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}
例 2:使用多重注解(新方法)
代码如下:
@Hint("hint1")
@Hint("hint2")
class Person {}
第二个例子里java编译器会隐性的帮你定义好@Hints注解,了解这一点有助于你用反射来获取这些信息:
代码如下: Hint hint = Person.class.getAnnotation(Hint.class); Sytem.out.println(hint); Hint hints1 = Person.class.getAnnotation(Hints.class); Sytem.out.println(hints1.value().length); Hint[] hintst2 = Person.class.getAnnottationsByType(Hint.calss); Sytem.out.println(hintst2 .length); 我们没有在Person类上定义@Hints注解,我们还是可以通过 getAnnotation(Hints.class) 来获取 @Hints注解,更加方便的方法是使用 getAnnotationsByType 可以直接获取到所有的@Hint注解。
另外Java 8的注解还增加到两种新的target上了:
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
代码如下:
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
|