一、函数式接口
函数式接口(functional interface 也叫功能性接口,其实是同一个东西)。简单来说,函数式接口是只包含一个方法的接口。比如Java标准库中的java.lang.Runnable和 java.util.Comparator都是典型的函数式接口。
java 8提供 @FunctionalInterface作为注解,这个注解是非必须的,只要接口符合函数式接口的标准(即只包含一个方法的接口),虚拟机会自动判断, 但 最好在接口上使用注解@FunctionalInterface进行声明,以免团队的其他人员错误地往接口中添加新的方法。
Java中的lambda无法单独出现,它需要一个函数式接口来盛放,lambda表达式方法体其实就是函数接口的实现.
下面的接口就是一个函数式接口
@FunctionalInterface //添加此注解后,接口中只能有一个抽象方法。
public interface A {
void call();
}
二、lambda语法
包含三部分:
1、一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数
2、一个箭头符号:->
3、方法体,可以是表达式和代码块。
(parameters) -> expression 或者 (parameters) -> { statements; }
通过下面的代码可以看到lambda表达式设计的代码更简洁,而且可读性更好。
public class Demo1 {
public static void main(String[] args) {
runThreadByLambda();
runThreadByInnerClass();
}
public static void runThreadByLambda() {
/*
Runnable就是一个函数式接口:他只有一个方法run()方法。
1、因为run()方法没有参数,所以 ->前面的()中不需要声明形参
2、run返回的是void,所以不需要return。
3、->后面写的代码其实就是定义在run方法内的代码。因为此处代码只有一行,所以{}也可以省略。如果此处多与一行,则无法省略。
*/
Runnable runnable = () -> System.out.println("这个是用拉姆达实现的线程");
new Thread(runnable).start();
}
public static void runThreadByInnerClass() {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("这个是用内部类实现的线程");
}
};
new Thread(runnable).start();
}
}
三、方法引用
其实是lambda表达式的一种简化写法。所引用的方法其实是lambda表达式的方法体实现,语法也很简单,左边是容器(可以是类名,实例名),中间是"::",右边是相应的方法名。如下所示:
ObjectReference::methodName
一般方法的引用格式:
如果是静态方法,则是ClassName::methodName。如 Object ::equals
如果是实例方法,则是Instance::methodName。如Object obj=new Object();obj::equals;
构造函数.则是ClassName::new
四、默认方法—接口改进
简单说,就是接口可以有实现方法,而且不需要实现类去实现其方法。只需在方法名前面加个default关键字即可。
@FunctionalInterface
public interface A {
void call();
default void fun() {
System.out.println("我是接口的默认方法1中的代码");
}
default void fun2() {
System.out.println("我是接口的默认方法2中的代码");
}
}
为什么要有这个特性?首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口 添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了使接口没有引入与现有的实现不兼容发展。
java8中接口和抽象类的区别
形同点:
1.都是抽象类型;
2.都可以有实现方法(以前接口不行);
3.都可以不需要实现类或者继承者去实现所有方法,(以前不行,现在接口中默认方法不需要实现者实现)
不同点
1.抽象类不可以多重继承,接口可以(无论是多重类型继承还是多重行为继承);
2.抽象类和接口所反映出的设计理念不同。其实抽象类表示的是"is-a"关系,接口表示的是"like-a"关系;
3.接口中定义的变量默认是public static final 型,且必须给其初值,所以实现类中不能重新定义,也不能改变其值;抽象类中的变量默认是 default 型,其值可以在子类中重新定义,也可以重新赋值。
五、使用lambda改进的集合框架
5.1 集合中内部迭代
import java.util.ArrayList;
import java.util.List;
public class Demo3 {
public static void main(String[] args) {
List<User> users = new ArrayList<User>();
users.add(new User(20, "张三"));
users.add(new User(22, "李四"));
users.add(new User(10, "王五"));
users.forEach((User user) -> System.out.println(user.getAge()));
}
}
5.2 Stream API
流(Stream)仅仅代表着数据流,并没有数据结构,所以他遍历完一次之后便再也无法遍历(这点在编程时候需要注意,不像Collection,遍历多少次里面都还有数据),它的来源可以是Collection、array、io等等。
流作用是提供了一种操作大数据接口,让数据操作更容易和更快。它具有过滤、映射以及减少遍历数等方法,这些方法分两种:中间方法和终端方法,“流”抽象天生就该是持续的,中间方法永远返回的是Stream,因此如果我们要获取最终结果的话,必须使用终点操作才能收集流产生的最终结果。区分这两个方法是看他的返回值,如果是Stream则是中间方法,否则是终点方法。
filter
在数据流中实现过滤功能是首先我们可以想到的最自然的操作了。Stream接口暴露了一个filter方法,它可以接受表示操作的Predicate实现来使用定义了过滤条件的lambda表达式。
import java.util.stream.Stream;
public class StreamDemo {
public static void main(String[] args) {
List<User> users = new ArrayList<User>();
users.add(new User(20, "张三"));
users.add(new User(22, "李四"));
users.add(new User(10, "王五"));
Stream<User> stream = users.stream();
stream.filter(p -> p.getAge() > 20); //过滤年龄大于20的
}
}
map
假使我们现在过滤了一些数据,比如转换对象的时候。Map操作允许我们执行一个Function的实现(Function<T,R>的泛型T,R分别表示执行输入和执行结果),它接受入参并返回。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamDemo {
public static void main(String[] args) {
List<User> users = new ArrayList<User>();
users.add(new User(20, "张三"));
users.add(new User(22, "李四"));
users.add(new User(10, "王五"));
Stream<User> stream = users.stream();
//所有的年龄大于20岁的User对象,转换为字符串50对象。现在流中只有字符串对象了。
stream.filter((User user) -> user.getAge() > 20).map((User user) -> {return "50";});
}
}
count
count方法是一个流的终点方法,可使流的结果最终统计,返回long
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collector;
import java.util.stream.Stream;
public class StreamDemo {
public static void main(String[] args) {
List<User> users = new ArrayList<User>();
users.add(new User(20, "张三"));
users.add(new User(22, "李四"));
users.add(new User(10, "王五"));
Stream<User> stream = users.stream();
long count = stream.filter((User user) -> user.getAge() >= 20).map((User user) -> {return "50";})
.count(); //返回流中元素的个数。
System.out.println(count);
}
}
Collect
它是一个能够把stream管道中的结果集装进一个List集合的终极操作。 collect是一个把stream规约成一个value的规约操作,这里的value可以是一个Collection、Map或者一个value对象。在下面这几种情况下,可以使用collect操作。
1.把stream规约到一个单独的值 stream的执行结果可以规约成一个单独的值,这个单独的值可以是Collection或者数值型的值如int、double等,还可以是一个自定义的值对象。
2.在stream中对元素进行分组 对stream中的所有task按照TaskType分组。这会生成一个一个Map<TaskType,List,其中的每个entry都包含一个TaskType和与它相关联的Task。也可以使用其它任何的Collection来替代List。如果不需要把所有的task对应到一个TaskType,也可以生成一个Map<TaskType,Task>。
3.分离stream中的元素 可以把一个stream分离到两个组中–正在进行中的和已经完成的task。
Collector in Action
下面我们通过这个根据type来对task进行分组的例子,来体验Collector的作用。在java8中,我们可以像下面这样来实现根据TaskType分组。
private static Map<TaskType, List<Task>> groupTasksByType(List<Task> tasks) {
return tasks.stream().collect(Collectors.groupingBy(task -> task.getType()));
}
}
上面的代码使用了Collectors工具类中定义的groupingBy Collector方法。它创建一个map,其中key为TaskType、value为所有具有相同TaskType的task组成的一个list列表。在java7中要实现相同的功能,需要写如下的代码。
public static void main(String[] args) {
List<Task> tasks = getTasks();
Map<TaskType, List<Task>> allTasksByType = new HashMap<>();
for (Task task : tasks) {
List<Task> existingTasksByType = allTasksByType.get(task.getType());
if (existingTasksByType == null) {
List<Task> tasksByType = new ArrayList<>();
tasksByType.add(task);
allTasksByType.put(task.getType(), tasksByType);
} else {
existingTasksByType.add(task);
}
}
for (Map.Entry<TaskType, List<Task>> entry : allTasksByType.entrySet()) {
System.out.println(String.format("%s =>> %s", entry.getKey(), entry.getValue()));
}
}
收集器(Collectors):常用规约操作
Collectors 工具类提供了许多静态工具方法来为大多数常用的用户用例创建收集器,比如将元素装进一个集合中、将元素分组、根据不同标准对元素进行汇总等。本文中将覆盖大多数常见的收集器(Collector)。
规约到一个单独的值
如上面所说,收集器(collector)可以用来把stream收集到一个collection中或者产生一个单独的值。
把数据装进一个list中
下面我们给出第一个测试用例–将给定的任务列表的所有标题收集到一个List列表中。
public class Example2_ReduceValue {
public List<String> allTitles(List<Task> tasks) {
return tasks.stream().map(Task::getTitle).collect(toList());
}
}
toList收集器通过使用List的add方法将元素添加到一个结果List列表中,toList收集器使用ArrayList作为List的实现。
将数据收集到一个Set中
如果要保证所收集的title不重复并且我们对数据的排序没有要求的话,可以采用toSet收集器。
public Set<String> uniqueTitles(List<Task> tasks) {
return tasks.stream().map(Task::getTitle).collect(toSet());
}
toSet 方法采用HashSet作为Set的实现来储存结果集。
把数据收集到一个Map中
可以使用toMap收集器将一个stream转换成一个Map。toMap收集器需要两个集合函数来提取map中的key和value。下面的代码中,Task::getTitle需要一个task并产生一个仅有一个标题的key。task -> task是一个用来返回自己的lambda表达式,上例中返回一个task。
private static Map<String, Task> taskMap(List<Task> tasks) {
return tasks.stream().collect(toMap(Task::getTitle, task -> task));
}
可以使用Function接口中的默认方法identity来让上面的代码代码变得更简洁明了、传递开发者意图时更加直接,下面是采用identity函数的代码。
import static java.util.function.Function.identity;
private static Map<String, Task> taskMap(List<Task> tasks) {
return tasks.stream().collect(toMap(Task::getTitle, identity()));
}
代码创建了一个Map,当出现相同的key时就会抛出如下的异常。
Exception in thread "main" java.lang.IllegalStateException: Duplicate key Task{title='Read Version Control with Git book', type=READING}
at java.util.stream.Collectors.lambda$throwingMerger$105(Collectors.java:133)
toMap还有一个可以指定合并函数的变体,我们可以采用它来处理重复的副本。合并函数允许开发者指定一个解决同一个key冲突的规则。在下面的代码中,我们简单地使用最后一个value,当然你也可以写更加智能的算法来处理冲突。
private static Map<String, Task> taskMap_duplicates(List<Task> tasks) {
return tasks.stream().collect(toMap(Task::getTitle, identity(), (t1, t2) -> t2));
}
我们还可以使用toMap的第三种变体方法来使用任何其它的Map实现,这需要指定Map 和Supplier来存放结果。
public Map<String, Task> collectToMap(List<Task> tasks) {
return tasks.stream().collect(toMap(Task::getTitle, identity(), (t1, t2) -> t2, LinkedHashMap::new));
}
与toMap收集器类似,toConcurrentMap收集器可以产生ConcurrntMap来替代HashMap。
Using other collections 使用其它的集合
toList和toSet等特定的收集器不支持指定潜在的list或set的实现,当你想要像下面这样这样把结果聚合到其它类型的集合时可以采用toCollection收集器。
private static LinkedHashSet<Task> collectToLinkedHaskSet(List<Task> tasks) {
return tasks.stream().collect(toCollection(LinkedHashSet::new));
}
找出标题最长的task
public Task taskWithLongestTitle(List<Task> tasks) {
return tasks.stream().collect(collectingAndThen(maxBy((t1, t2) -> t1.getTitle().length() - t2.getTitle().length()), Optional::get));
}
统计tags的总数
public int totalTagCount(List<Task> tasks) {
return tasks.stream().collect(summingInt(task -> task.getTags().size()));
}
生成task标题的汇总
public String titleSummary(List<Task> tasks) {
return tasks.stream().map(Task::getTitle).collect(joining(";"));
}
将元素分组
Collector收集器一个最常见的用户用例就是对元素进行分组,下面我们通过几个示例来理解我们可以如何来分组。
Example 1: 根据type对tasks分组
下面这个例子,我们根据TaskType对task进行分组。通过使用Collectors工具类的groupingBy收集器,我们可以非常简单的完成这个功能。可以使用方法引用和静态引入来让代码变得更加简洁。
import static java.util.stream.Collectors.groupingBy;
private static Map<TaskType, List<Task>> groupTasksByType(List<Task> tasks) {
return tasks.stream().collect(groupingBy(Task::getType));
}
会产生如下的输出:
{CODING=[Task{title='Write a mobile application to store my tasks', type=CODING, createdOn=2015-07-03}], WRITING=[Task{title='Write a blog on Java 8 Streams', type=WRITING, createdOn=2015-07-04}], READING=[Task{title='Read Version Control with Git book', type=READING, createdOn=2015-07-01}, Task{title='Read Java 8 Lambdas book', type=READING, createdOn=2015-07-02}, Task{title='Read Domain Driven Design book', type=READING, createdOn=2015-07-05}]}
Example 2: 根据tags分组
private static Map<String, List<Task>> groupingByTag(List<Task> tasks) {
return tasks.stream().
flatMap(task -> task.getTags().stream().map(tag -> new TaskTag(tag, task))).
collect(groupingBy(TaskTag::getTag, mapping(TaskTag::getTask,toList())));
}
private static class TaskTag {
final String tag;
final Task task;
public TaskTag(String tag, Task task) {
this.tag = tag;
this.task = task;
}
public String getTag() {
return tag;
}
public Task getTask() {
return task;
}
}
Example 3: 根据tag和tag的个数分组
private static Map<String, Long> tagsAndCount(List<Task> tasks) {
return tasks.stream().
flatMap(task -> task.getTags().stream().map(tag -> new TaskTag(tag, task))).
collect(groupingBy(TaskTag::getTag, counting()));
}
Example 4: 根据TaskType和createdOn分组
private static Map<TaskType, Map<LocalDate, List<Task>>> groupTasksByTypeAndCreationDate(List<Task> tasks) {
return tasks.stream().collect(groupingBy(Task::getType, groupingBy(Task::getCreatedOn)));
}
分割
有时候,你需要根据一定的规则将一个数据集分成两个数据集。比如,我们可以定义一个分割函数,根据规则进行时间早于今天和进行时间晚于今天将task分成两组。
private static Map<Boolean, List<Task>> partitionOldAndFutureTasks(List<Task> tasks) {
return tasks.stream().collect(partitioningBy(task -> task.getDueOn().isAfter(LocalDate.now())));
}
生成统计
另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。
IntSummaryStatistics summaryStatistics = tasks.stream().map(Task::getTitle).collect(summarizingInt(String::length));
System.out.println(summaryStatistics.getAverage()); //32.4
System.out.println(summaryStatistics.getCount()); //5
System.out.println(summaryStatistics.getMax()); //44
System.out.println(summaryStatistics.getMin()); //24
System.out.println(summaryStatistics.getSum()); //162
还有一些其它的基本类型的变体,比如LongSummaryStatistics 和 DoubleSummaryStatistics
还可以用combine操作来把两个IntSummaryStatistics结合到一起。
firstSummaryStatistics.combine(secondSummaryStatistics);
System.out.println(firstSummaryStatistics)
把所有的titles连在一起
private static String allTitles(List<Task> tasks) {
return tasks.stream().map(Task::getTitle).collect(joining(", "));
}
编写一个自定义的收集器
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
public class MultisetCollector<T> implements Collector<T, Multiset<T>, Multiset<T>> {
@Override
public Supplier<Multiset<T>> supplier() {
return HashMultiset::create;
}
@Override
public BiConsumer<Multiset<T>, T> accumulator() {
return (set, e) -> set.add(e, 1);
}
@Override
public BinaryOperator<Multiset<T>> combiner() {
return (set1, set2) -> {
set1.addAll(set2);
return set1;
};
}
@Override
public Function<Multiset<T>, Multiset<T>> finisher() {
return Function.identity();
}
@Override
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH));
}
}
import com.google.common.collect.Multiset;
import java.util.Arrays;
import java.util.List;
public class MultisetCollectorExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("shekhar", "rahul", "shekhar");
Multiset<String> set = names.stream().collect(new MultisetCollector<>());
set.forEach(str -> System.out.println(str + ":" + set.count(str)));
}
}
Word Count in Java 8 Java8中的单词统计
下面,我们通过java8中的Streams和Collectors编写一个非常著名的单词统计示例来结束本文。
public static void wordCount(Path path) throws IOException {
Map<String, Long> wordCount = Files.lines(path)
.parallel()
.flatMap(line -> Arrays.stream(line.trim().split("\\s")))
.map(word -> word.replaceAll("[^a-zA-Z]", "").toLowerCase().trim())
.filter(word -> word.length() > 0)
.map(word -> new SimpleEntry<>(word, 1))
.collect(groupingBy(SimpleEntry::getKey, counting()));
wordCount.forEach((k, v) -> System.out.println(String.format("%s ==>> %d", k, v)));
}