在本教程中主要讲解Java 8新的函数式编程功能,熟悉这些新的 API:streams, 函数接口, map扩展和新的日期API。
接口的缺省方法
Java 8让我们能够增加非抽象方法实现到一个接口中, 使用default,这个特点就是 Extension Methods.
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
接口 Formula 定义了一个默认方法sqrt. 该接口实现类只要完成接口中抽象方法calculate即可,而sqrt方法可以被外部使用。
ormula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
formula.calculate(100); // 100.0
formula.sqrt(16); // 4.0
代码中formula 实现类是一个匿名类,在Java 8中有很多好的方法实现这种单个方法的匿名类。
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和一个比较器comparator,后者是为了对集合中元素排序,一般我们都是创建一个匿名的比较器对象传递集合中。
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编译器会照顾你忽略了a和b的类型String。这称为语言的类型判断。
函数接口
Lambda表达式是一种函数语法,与Java的类型语言是两种不同性质的语法,如同南北两个不同方向,那么Java 8的Lambda表达式如何配合Java天生的类型系统呢?每个Lambda都对应一个给定的类型,主要是一个接口类型,也称为函数接口,只能包含一个抽象方法,每个类型的Lambda表达式与这个抽象方法匹配,因为默认default方法不是抽象方法,你可以在接口中自由增加自定义的default默认方法。
我们可以使用很多接口作为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
这段代码中,我们定义了接口Converter一个抽象方法,注意虽然使用了@FunctionalInterface ,但是不使用也是可以,那么这个接口中抽象方法就可以作为Lambda表达式使用,首先,我们定义了这个接口的实现:
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
(from) -> Integer.valueOf(from)实际是抽象方法 T convert(F from)的实现具体细节,这里是将字符串转换为整数型。
然后,我们就可以直接调用这个接口:converter.convert("123")
方法和构造器的引用
上述代码如果使用静态方法引用static method references将会更加简化:
Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted); // 123
Java 8 使用符号 ::让你传递方法或构造器的引用,因为 Integer.valueOf是一个静态方法,其引用方式是 Integer::valueOf,我们还能引用一个对象的普通方法:
class Something {
String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted); // "J"
这里引用的是类Something的方法startsWith,我们首先要创建这个类的实例,然后使用something::startsWith,这相当于实现了接口Converter的convert方法。不必像我们传统方式,通过Something implements Converter,然后在具体实现Converter的抽象方法convert。这样摆脱了子类对接口的依赖。
符号::也可以使用在构造器方法中:
class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = 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方法签名的构造器。
Lambda作用域
从Lambda表达式访问外部变量非常类似匿名对象访问外部一样,匿名对象只能访问外部访问有final定义的变量。
访问本地变量:
final int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
stringConverter.convert(2);
这里(from) -> String.valueOf(from + num)表达式 访问了外部的变量num,区别于匿名类,这个变量不是必须加上final定义。下面也可以:
int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
这就是Lambda表达式比匿名类的好处。但是这不代表你可以修改num值,这里是隐式的final,下面语法是不可以的:
int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
num = 3;
一旦被写入了Lambda表达式的变量不能再被修改了。
访问字段和静态变量
正好和本地变量相反,我们在Lambda表达式中可以对实例的字段和静态变量进行读和写。无论这个字段或静态变量是否在lambda表达式中使用过。
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,它可以被匿名对象访问 ,但是不能被Lambda表达式访问。
默认方法不能在Lambda表达式中访问,下面代码不会编译通过:
Formula formula = (a) -> sqrt( a * 100);
上节我们已经明白了Lambda表达式是通过函数接口实现的,JDK 1.8 API 包含了很多内建的函数接口,有一些是为了老版本如Comparator or Runnable. 这些已经存在的接口都做了拓展,通过 @FunctionalInterface 注解以便更好地支持Lambda。
但是在Java 8中还有一些新的函数接口让你开发更简单,一些新的接口来自于google的 Guava库。
内建函数接口
Predicates
Predicates是一个只有一个参数的返回boolean值的函数 这个接口包含了各种默认方法,用来组合predicates完成复杂的逻辑表达(and, or, negate)
//只有一个输入参数s,返回的是非真即否的boolean类型
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
Functions 接受一个输入参数,产生一个结果,默认方法能被多个函数一起作为链条一样使用 (compose, andThen).
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"
Suppliers
Suppliers 产生一个给定泛型类型的结果,不像Functions, Suppliers并不接受输入参数。
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person
Consumers
Consumers是对单个输入参数执行的一些操作。
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
Comparators
Comparators是老版本比较器在Java8应用,Java 8加了默认方法,可以作为Lambda表达式使用,不再需要以前老式专门做一个Comparator实现类了。
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小巧的工具。
Optional是一个值容器,这个值可以是空或不空。过去我们一个方法应该返回不空的值,但是可能返回空,那么现在我们可以使用Java 8,返回一个Optional即可
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
Streams
java.util.Stream代表一个序列元素,这些元素能够执行一个或多个操作, Stream操作既可以是intermediate 或者terminal. 而terminal操作返回某个类型的一个结果, intermediate 操作返回的是stream自身,这样你能将多个调用链接在一行中实现。Streams是被一个源 如一个java.util.Collection 类似lists 或 sets 创建(maps还没有支持). Stream 操作可以顺序也可以并行实现。
为了看看顺序操作是如何执行的,我们首先创建如下源:
List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");
这是一个字符串集合。在Java 8中中集合Collections已经被拓展,你能简单地调用 Collection.stream() 或 Collection.parallelStream()来创建Stream.下面解释 Stram的主要操作。
Filter
Filter接受一个predicate,然后根据其返回真或假对Stram中所有元素进行过滤,这个操作是intermediat,所以能让我们对结果再调用其他stream操作(如forEach)处理. ForEach能接受对 stream每个元素执行函数操作 (这个例子中是filtered). ForEach是一个terminal操作. 它返回 void, 这样我们不能基于其结果再调用其他Stram操作了。
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa2", "aaa1"
Sorted
Sorted是一个intermediate操作,能够返回Stream的排序结果,元素是按自然排序规则,除非你传入一个定制的Comparator.
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa1", "aaa2"
请记住已经排序意味着创建一个排过许的视图,并不会在后端真正产生一个的排序集合,stringCollection中的元素顺序其实没有变化:
System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
Map
Map操作也是intermediate的,按给定的函数对每个元素转换操作到一个结果。下面案例是使用函数转换字符串到大写将每个输入字符串转到到大写字符。
stringCollection
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);
// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
你也可以使用map转换一个对象到另外一个类型,Stream的结果的泛型类型取决于你传入函数的泛型类型。
Match
各种匹配操作能够用于检测某个predicate是否匹配stream. 所有这些操作都是terminal,不能再被用作Stream其他操作的输入了,并且只返回一个boolean结果
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
Count
Count是一个terminal操作,返回Stream中元素的个数,类型是初始类型long.
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
Reduce
这个terminal操作执行对Stream元素按给定函数执行约简的操作,结果是包含约简好的元素的一个Optional。
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
相关推荐
在Java8教程中,将重点介绍以下几个核心知识点。 首先,Java8中引入了lambda表达式,它是Java语言对函数式编程的一个重要支持。Lambda表达式可以理解为匿名函数,它没有名称也没有标识符,可以直接在需要的地方写...
这个名为"Java 8教程.zip"的开源项目,旨在帮助所有水平的开发者掌握Java 8的新功能,无论你是初学者还是经验丰富的专业人员。让我们深入探讨一下Java 8的主要特性及其重要性。 1. **Lambda表达式**: Lambda...
java8 简介,语法和函数等简单用法,具体可参考文档中的示例,体会版本更新带来的好处。
这个简明教程旨在帮助开发者快速掌握Java 8的主要特性,通过实践和示例加深理解,提升编程效率。如果你想要深入学习Java 8,建议查阅文档中提到的各个专题教程,结合实际项目进行练习。同时,保持对最新技术的关注,...
根据提供的文件信息,我们可以从标题、描述以及部分给出的内容中提炼出关于“Java 8编程入门官方教程”的关键知识点。 ### Java 8编程入门官方教程 #### 一、概述 《Java 8编程入门官方教程》是一本由美国作者...
这份"Java8官方教程"是学习这个版本的基础资源,特别适合编程新手入门。以下将详细解析Java8的关键知识点。 1. **函数式编程**:Java8引入了函数式编程的概念,其中最显著的是Lambda表达式。Lambda表达式可以看作是...
Java8简明教程、数据流教程、Nashorn教程、线程和执行器、同步和锁、原子变量和ConcurrentMap、字符串、数值、算术和文件、避免null检查、使用 Intellij IDEA 解决 Java 8 的数据流问题、在 Nashron 中使用 Backbone...
### Java 8 简明教程 #### Introduction Java 8 是 Java 语言的一个重要版本,带来了许多新特性,极大地提升了开发效率并简化了代码。本教程旨在介绍 Java 8 的核心新特性及其应用场景。 #### Java 8 新特性概述 ...
这份"java基础教程ppt"提供了一个全面的学习资源,帮助初学者掌握Java编程的基本概念和语法。 在Java的基础教程中,通常会包含以下几个核心知识点: 1. **Java简介**:介绍Java的历史、特点以及其在软件开发中的...
但是,我可以根据“JAVA经典教程”这个标题,提供一份关于Java编程语言的经典知识点总结,这可以帮助有意向学习Java编程的读者。 1. Java简介:Java是一种高级、面向对象的编程语言,由Sun Microsystems公司于1995...
java8的官方文档,全书1200多页,完整介绍了java语言的特性,并引入的java8新的语言特性,例如stream,lambda表达式等,适合作为工具书查阅,java8的官方文档,全书1200多页,完整介绍了java语言的特性,并引入的...
Java 8 官方教程是Java开发者不可或缺的学习资源,它涵盖了这一版本的重要更新和特性。在Java 8中,Oracle引入了一系列创新,旨在提高代码效率、可读性和可维护性。以下是对Java 8核心知识点的详细阐述: 1. **...
本文档《Java8简明教程》是一份详尽的指南,旨在帮助开发者快速掌握Java8的新特性。本教程覆盖了从基础到高级的应用场景,包括流(Stream)操作、Lambda表达式、函数式接口以及新的并发模型等。 #### 二、Java8的新...
在《Java 8编程官方参考教程(第9版)》中,你可以深入学习这些核心改变,包括函数式编程、Lambda表达式、Stream API、日期与时间API、并发改进以及新的接口和类库等。 首先,Lambda表达式是Java 8的一大亮点,它...
总的来说,CoreJava8教程全面介绍了Java 8的核心特性和改进,对于想要掌握Java 8的开发者来说,是一份宝贵的参考资料。通过深入学习和实践,开发者可以更好地利用这些新特性来提升代码质量和开发效率。
* OnJava8:Java 8教程,涵盖了Java 8的基础知识、Java 8的新特性等。 * e!ective-java-3rd-chinese:Effective Java第三版中文版,涵盖了Java的最佳实践、Java编程指南等。 SpringBoot * springboot-guide:...
Java8 教程 引言 这个 java8 教程列表中将分享那些 java8 中最重要的那些功能和链接。原文地址 如果觉得这些内容可以帮助到你,也可以在 上点一个:thumbs_up:支持一下~ 阅读地址 参与翻译 感谢以下译者 @...
原文:java8-tutorial Modern Java - A Guide to Java 8 http://winterbe.com ---------------------------------------------------- 本 ePub 基于开源文档,目录书签齐全。 版权归原作者,翻译版权归译者。 ------...
Java 8 简明教程 Java 8 Tutorial中文版 “Java并没有没落,人们很快就会发现这一点” 欢迎阅读我编写的Java 8介绍。本教程将带领你一步一步地认识这门语言的新特 性。通过简单明了的代码示例,你将会学习到如何使用...
文件内容还包含了多个Java版本的参考,如“JAVA8”,“JAVA5”,“JAVA6”,“JAVA9”等,显示了Java语言的版本迭代和演进。例如,“JAVA6”中的“RxgZy4hO,h+~JAVA5Ym/Z+HJ%e;;”可能是在解释Java 5到Java 6的变化...