原创

java8新特性学习

https://img.luoyuanxiangvip.com/blogImg/1561448436232.jpg

接口的默认方法(Default Methods for Interfaces)

Java 8使我们能够通过使用 default 关键字向接口添加非抽象方法实现。 此功能也称为 虚拟扩展方法 。 第一个例子:

        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
interface Formula{ double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } }

Formula 接口中除了抽象方法计算接口公式还定义了默认方法 sqrt。 实现该接口的类只需要实现抽象方法 calculate。 默认方法sqrt 可以直接使用。当然你也可以直接通过接口创建对象,然后实现接口中的默认方法就可以了,我们通过代码演示一下这种方式。

        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
public class Main { public static void main(String[] args) { // TODO 通过匿名内部类方式访问接口 Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100); } }; System.out.println(formula.calculate(100)); // 100.0 System.out.println(formula.sqrt(16)); // 4.0 } }

formula 是作为匿名对象实现的。该代码非常容易理解,6行代码实现了计算 sqrt(a * 100)。在下一节中,我们将会看到在 Java 8 中实现单个方法对象有一种更好更方便的方法。

译者注: 不管是抽象类还是接口,都可以通过匿名内部类的方式访问。不能通过抽象类或者接口直接创建对象。对于上面通过匿名内部类方式访问接口,我们可以这样理解:一个内部类实现了接口里的抽象方法并且返回一个内部类对象,之后我们让接口的引用来指向这个对象。

Lambda表达式(Lambda expressions)

首先看看在老版本的Java中是如何排列字符串的:

        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
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表达式:

        
  • 1
  • 2
  • 3
Collections.sort(names, (String a, String b) -> { return b.compareTo(a); });

可以看出,代码变得更段且更具有可读性,但是实际上还可以写得更短:

        
  • 1
Collections.sort(names, (String a, String b) -> b.compareTo(a));

对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字,但是你还可以写得更短点:

        
  • 1
names.sort((a, b) -> b.compareTo(a));

List 类本身就有一个 sort 方法。并且Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。接下来我们看看lambda表达式还有什么其他用法。

函数式接口(Functional Interfaces)

Java 语言设计者们投入了大量精力来思考如何使现有的函数友好地支持Lambda。最终采取的方法是:增加函数式接口的概念。“函数式接口”是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是上面提到的默认方法)的接口。 像这样的接口,可以被隐式转换为lambda表达式。java.lang.Runnablejava.util.concurrent.Callable 是函数式接口最典型的两个例子。Java 8增加了一种特殊的注解@FunctionalInterface,但是这个注解通常不是必须的(某些情况建议使用),只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口。一般建议在接口上使用@FunctionalInterface 注解进行声明,这样的话,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的

        
  • 1
  • 2
  • 3
  • 4
@FunctionalInterface public interface Converter<F, T> { T convert(F from); }
        
  • 1
  • 2
  • 3
  • 4
// TODO 将数字字符串转换为整数类型 Converter<String, Integer> converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted.getClass()); //class java.lang.Integer

译者注: 大部分函数式接口都不用我们自己写,Java8都给我们实现好了,这些接口都在java.util.function包里。

方法和构造函数引用(Method and Constructor References)

前一节中的代码还可以通过静态方法引用来表示:

        
  • 1
  • 2
  • 3
Converter<String, Integer> converter = Integer::valueOf; Integer converted = converter.convert("123"); System.out.println(converted.getClass()); //class java.lang.Integer

Java 8允许您通过::关键字传递方法或构造函数的引用。 上面的示例显示了如何引用静态方法。 但我们也可以引用对象方法:

        
  • 1
  • 2
  • 3
  • 4
  • 5
class Something { String startsWith(String s) { return String.valueOf(s.charAt(0)); } }
        
  • 1
  • 2
  • 3
  • 4
Something something = new Something(); Converter<String, String> converter = something::startsWith; String converted = converter.convert("Java"); System.out.println(converted); // "J"

接下来看看构造函数是如何使用::关键字来引用的,首先我们定义一个包含多个构造函数的简单类:

        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
class Person { String firstName; String lastName; Person() {} Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }

接下来我们指定一个用来创建Person对象的对象工厂接口:

        
  • 1
  • 2
  • 3
interface PersonFactory<P extends Person> { P create(String firstName, String lastName); }

这里我们使用构造函数引用来将他们关联起来,而不是手动实现一个完整的工厂:

        
  • 1
  • 2
PersonFactory<Person> personFactory = Person::new; Person person = personFactory.create("Peter", "Parker");

我们只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的参数类型来选择合适的构造函数。

Lamda 表达式作用域(Lambda Scopes)

访问局部变量

我们可以直接在 lambda 表达式中访问外部的局部变量:

        
  • 1
  • 2
  • 3
  • 4
final int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3

但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确:

        
  • 1
  • 2
  • 3
  • 4
int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3

不过这里的 num 必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译:

        
  • 1
  • 2
  • 3
  • 4
int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); num = 3;//在lambda表达式中试图修改num同样是不允许的。

访问字段和静态变量

与局部变量相比,我们对lambda表达式中的实例字段和静态变量都有读写访问权限。 该行为和匿名对象是一致的。

        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
class Lambda4 { static int outerStaticNum; int outerNum; void testScopes() { Converter<Integer, String> stringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); }; Converter<Integer, String> stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; } }

访问默认接口方法

还记得第一节中的 formula 示例吗? Formula 接口定义了一个默认方法sqrt,可以从包含匿名对象的每个 formula 实例访问该方法。 这不适用于lambda表达式。

无法从 lambda 表达式中访问默认方法,故以下代码无法编译:

        
  • 1
Formula formula = (a) -> sqrt(a * 100);

内置函数式接口(Built-in Functional Interfaces)

JDK 1.8 API包含许多内置函数式接口。 其中一些借口在老版本的 Java 中是比较常见的比如: ComparatorRunnable,这些接口都增加了@FunctionalInterface注解以便能用在 lambda 表达式上。

但是 Java 8 API 同样还提供了很多全新的函数式接口来让你的编程工作更加方便,有一些接口是来自 Google Guava 库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。

Predicates

Predicate 接口是只有一个参数的返回布尔类型值的 断言型 接口。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非):

译者注: Predicate 接口源码如下

        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
package java.util.function; import java.util.Objects; @FunctionalInterface public interface Predicate<T> { // 该方法是接受一个传入类型,返回一个布尔值.此方法应用于判断. boolean test(T t); //and方法与关系型运算符"&&"相似,两边都成立才返回true default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } // 与关系运算符"!"相似,对判断进行取反 default Predicate<T> negate() { return (t) -> !test(t); } //or方法与关系型运算符"||"相似,两边只要有一个成立就返回true default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } // 该方法接收一个Object对象,返回一个Predicate类型.此方法用于判断第一个test的方法与第二个test方法相同(equal). static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); }

示例:

        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
Predicate<String> predicate = (s) -> s.length() > 0; predicate.test("foo"); // true predicate.negate().test("foo"); // false Predicate<Boolean> nonNull = Objects::nonNull; Predicate<Boolean> isNull = Objects::isNull; Predicate<String> isEmpty = String::isEmpty; Predicate<String> isNotEmpty = isEmpty.negate();

Functions

Function 接口接受一个参数并生成结果。默认方法可用于将多个函数链接在一起(compose, andThen):

译者注: Function 接口源码如下

        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
package java.util.function; import java.util.Objects; @FunctionalInterface public interface Function<T, R> { //将Function对象应用到输入的参数上,然后返回计算结果。 R apply(T t); //将两个Function整合,并返回一个能够执行两个Function对象功能的Function对象。 default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } // default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } static <T> Function<T, T> identity() { return t -> t; } }
        
  • 1
  • 2
  • 3
Function<String, Integer> toInteger = Integer::valueOf; Function<String, String> backToString = toInteger.andThen(String::valueOf); backToString.apply("123"); // "123"

Suppliers

Supplier 接口产生给定泛型类型的结果。 与 Function 接口不同,Supplier 接口不接受参数。

        
  • 1
  • 2
Supplier<Person> personSupplier = Person::new; personSupplier.get(); // new Person

Consumers

Consumer 接口表示要对单个输入参数执行的操作。

        
  • 1
  • 2
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker"));

Comparators

Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法:

        
  • 1
  • 2
  • 3
  • 4
  • 5
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Person p1 = new Person("John", "Doe"); Person p2 = new Person("Alice", "Wonderland"); comparator.compare(p1, p2); // > 0 comparator.reversed().compare(p1, p2); // < 0

Optionals

Optionals不是函数式接口,而是用于防止 NullPointerException 的漂亮工具。这是下一节的一个重要概念,让我们快速了解一下Optionals的工作原理。

Optional 是一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是有时却什么也没有返回,而在Java 8中,你应该返回 Optional 而不是 null。

译者注:示例中每个方法的作用已经添加。

        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
//of():为非null的值创建一个Optional Optional<String> optional = Optional.of("bam"); // isPresent(): 如果值存在返回true,否则返回false optional.isPresent(); // true //get():如果Optional有值则将其返回,否则抛出NoSuchElementException optional.get(); // "bam" //orElse():如果有值则将其返回,否则返回指定的其它值 optional.orElse("fallback"); // "bam" //ifPresent():如果Optional实例有值则为其调用consumer,否则不做处理 optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"

推荐阅读: [Java8]如何正确使用Optional

Streams(流)

java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如java.util.Collection 的子类,List 或者 Set, Map 不支持。Stream 的操作可以串行执行或者并行执行。

首先看看Stream是怎么用,首先创建实例代码的用到的数据List:

        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
List<String> stringList = new ArrayList<>(); stringList.add("ddd2"); stringList.add("aaa2"); stringList.add("bbb1"); stringList.add("aaa1"); stringList.add("bbb3"); stringList.add("ccc"); stringList.add("bbb2"); stringList.add("ddd1");

Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。下面几节将详细解释常用的Stream操作:

Filter(过滤)

过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。

        
  • 1
  • 2
  • 3
  • 4
// 测试 Filter(过滤) stringList.stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println);//aaa2 aaa1

forEach 是为 Lambda 而设计的,保持了最紧凑的风格。而且 Lambda 表达式本身是可以重用的,非常方便。

Sorted(排序)

排序是一个 中间操作,返回的是排序好后的 Stream。如果你不指定一个自定义的 Comparator 则会使用默认排序。

        
  • 1
  • 2
  • 3
  • 4
  • 5
// 测试 Sort (排序) stringList.stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println);// aaa1 aaa2

需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的:

        
  • 1
System.out.println(stringList);// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1

Map(映射)

中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。

下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。

        
  • 1
  • 2
  • 3
  • 4
  • 5
// 测试 Map 操作 stringList.stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println);// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"

本文于   2019/7/5 上午  发布在  宁静寺  分类下,当前已被围观  176  次

相关标签: 学习 Web开发

永久地址: https://luoyuanxiangvip.com/article/11

版权声明: 自由转载-署名-非商业性使用   |   Creative Commons BY-NC 3.0 CN