JDK 1.8 新特性
函数式接口
定义:所谓的函数式接口,实际上就是接口里面只能有一个抽象方法的接口。Comparator接口、Runanle接口都是典型的函数式接口。
特点:
接口有且仅有一个抽象方法,如抽象方法compare允许定义静态非抽象方法。允许定义默认defalut非抽象方法(default方法也是java8才有的)允许java.lang.Object中的public方法,如方法equals。FunctionInterface注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错
default关键字:
1.8之前接口是不能存在方法的实现的(只能有抽象方法),在1.8后出现default关键字,它可以存在方法体。接口实现类可以不去实现default方法,并且可以使用default方法
‘ :: ’ 关键字的用法
引用静态方法引用特定对象的实例方法引用特定类型的任意对象的实例方法引用构造函数
public class User {
private Integer id
;
private Integer age
;
private String name
;
public static int comparator(User user1
, User user2
){
return user1
.getAge() - user2
.getAge();
}
}
User user1
= new User(1,22,"小王");
User user2
= new User(2,20,"小李");
User user3
= new User(3,18,"小黄");
List
<User> list
= Arrays
.asList(user1
, user2
, user3
);
list
.sort(User
::comparator
);
list
.forEach(System
.out
::println
);
list
.sort(user
::compareByName
);
String
[] stringArray
= { "Barbara", "James", "Mary", "John",
"Patricia", "Robert", "Michael", "Linda" };
Arrays
.sort(stringArray
, String
::compareToIgnoreCase
);
public static <T
, SOURCE
extends Collection<T>, DEST
extends Collection<T>>
DEST
transferElements(
SOURCE sourceCollection
,
Supplier
<DEST> collectionFactory
) {
DEST result
= collectionFactory
.get();
for (T t
: sourceCollection
) {
result
.add(t
);
}
return result
;
}
Set
<Person> rosterSetLambda
=
transferElements(roster
, () -> { return new HashSet<>(); });
Set
<Person> rosterSet
= transferElements(roster
, HashSet
::new);
Set
<Person> rosterSet
= transferElements(roster
, HashSet
<Person>::new);
@FunctionalInterface
public interface TestInterface2 {
User
creat();
}
TestInterface2 testInterface2
= User
::new;
Lambda表达式
lambda表达式表达的是接口函数,箭头左侧是函数参数,箭头右侧是函数体。函数的参数类型和返回值类型都可以省略,程序会根据接口定义的上下文自动确定数据类型。Lambda表达式是不能操作外部对象的,因为Lambda 实质上是接口的子对象,只能访问静态资源和本身的内部变量。
定义:Lambda表达式是Java 8最流行最常用的功能特性。它将函数式编程概念引入Java,函数式编程的好处在于可以帮助我们节省大量的代码,方便易用,能够大幅度的提高我们的编码效率。
(param1
, param2
, param3
, param4…)
->{ doing……
};
() -> System
.out
.println("Hello Lambda");
number1
-> int a
= number1
* 2;
(number1
, number2
) -> int a
= number1
+ number2
;
(number1
, number2
) -> {
int a
= number1
+ number2
;
System
.out
.println(a
);
}
Stream API
Stream 管道流
通过前面章节的学习,我们应该明白了Stream管道流的基本操作。
源操作:可以将数组、集合类、行文本文件转换成管道流Stream进行数据处理中间操作:对Stream流中的数据进行处理,比如:过滤、数据转换等等终端操作:作用就是将Stream管道流转换为其他的数据类型。
Stream 的获取
例子一:Stream API 代表 for 循环
List
<String> list
= nameStrs
.stream()
.filter(s
-> s
.startsWith("L"))
.map(String
::toUpperCase
)
.sorted()
.collect(toList());
例子二:将数组转换为管道流
使用Stream.of()方法,将数组转换为管道流。
String
[] array
= {"Monkey", "Lion", "Giraffe", "Lemur"};
Stream
<String> nameStrs2
= Stream
.of(array
);
Stream
<String> nameStrs3
= Stream
.of("Monkey", "Lion", "Giraffe", "Lemur");
例子三:将集合类对象转换为管道流
List
<String> list
= Arrays
.asList("Monkey", "Lion", "Giraffe", "Lemur");
Stream
<String> streamFromList
= list
.stream();
Set
<String> set
= new HashSet<>(list
);
Stream
<String> streamFromSet
= set
.stream();
例子四:将文本文件转换为管道流
Stream
<String> lines
= Files
.lines(Paths
.get("file.txt"));
中间操作
Filter 与 谓语逻辑
谓语逻辑:Predicate(也是一个函数式接口)
通常情况下,filter函数中lambda表达式为一次性使用的谓词逻辑。如果我们的谓词逻辑需要被多处、多场景、多代码中使用,通常将它抽取出来单独定义到它所限定的主语实体中。
谓词逻辑的复用
and语法(并)or语法(交集)negate(取反)
public static Predicate
<Employee> ageGreaterThan70
= x
-> x
.getAge() >70;
public static Predicate
<Employee> genderM
= x
-> x
.getGender().equals("M");
List
<Employee> filtered
= employees
.stream()
.filter(Employee
.ageGreaterThan70
.and(Employee
.genderM
))
.collect(Collectors
.toList());
map 的基础用法
map的基础用法
List
<String> alpha
= Arrays
.asList("Monkey", "Lion", "Giraffe", "Lemur");
List
<Integer> lengths
= alpha
.stream()
.map(String
::length
)
.collect(Collectors
.toList());
peek用法:由于map的参数e就是返回值,所以可以用peek函数。peek函数是一种特殊的map函数,当函数没有返回值或者参数就是返回值的时候可以使用peek函数。
List
<Employee> maped
= employees
.stream()
.peek(e
-> {
e
.setAge(e
.getAge() + 1);
e
.setGender(e
.getGender().equals("M")?"male":"female");
}).collect(Collectors
.toList());
flatMap:map只能针对一维数组进行操作,数组里面还有数组,管道里面还有管道,它是处理不了每一个元素的。flatMap可以理解为将若干个子管道中的数据全都,平面展开到父管道中进行处理。
words
.stream()
.flatMap(w
-> Arrays
.stream(w
.split("")))
.forEach(System
.out
::println
);
Limit与Skip管道数据截取
List
<String> limitN
= Stream
.of("Monkey", "Lion", "Giraffe", "Lemur")
.limit(2)
.collect(Collectors
.toList());
List
<String> skipN
= Stream
.of("Monkey", "Lion", "Giraffe", "Lemur")
.skip(2)
.collect(Collectors
.toList());
limt方法传入一个整数n,用于截取管道中的前n个元素。经过管道处理之后的数据是:[Monkey, Lion]。skip方法与limit方法的使用相反,用于跳过前n个元素,截取从n到末尾的元素。经过管道处理之后的数据是: [Giraffe, Lemur]
Distinct元素去重
我们还可以使用distinct方法对管道中的元素去重,涉及到去重就一定涉及到元素之间的比较,distinct方法时调用Object的equals方法进行对象的比较的,如果你有自己的比较规则,可以重写equals方法。
List
<String> uniqueAnimals
= Stream
.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
.distinct()
.collect(Collectors
.toList());
Sorted排序
默认的情况下,sorted是按照字母的自然顺序进行排序。
List
<String> alphabeticOrder
= Stream
.of("Monkey", "Lion", "Giraffe", "Lemur")
.sorted()
.collect(Collectors
.toList());
串行、并行与顺序
串行的好处是可以保证顺序,但是通常情况下处理速度慢一些
并行的好处是对于元素的处理速度快一些(通常情况下),但是顺序无法保证。这可能会导致进行一些有状态操作的时候,最后得到的不是你想要的结果。
parallel()函数表示对管道中的元素进行并行处理,而不是串行处理。但是这样就有可能导致管道流中后面的元素先处理,前面的元素后处理,也就是元素的顺序无法保证。
当然,并不是并行的性能就比串行的性能好
说明并行操作的适用场景:
数据源易拆分:从处理性能的角度,parallel()更适合处理ArrayList,而不是LinkedList。因为ArrayList从数据结构上讲是基于数组的,可以根据索引很容易的拆分为多个。
适用于无状态操作:每个元素的计算都不得依赖或影响任何其他元素的计算,的运算场景。
基础数据源无变化:从文本文件里面边读边处理的场景,不适合parallel()并行处理。parallel()一开始就容量固定的集合,这样能够平均的拆分、同步处理。
比较器(Comparator)
字符串List排序:
String.CASE_INSENSITIVE_ORDER:当使用sort方法,按照String.CASE_INSENSITIVE_ORDER(字母大小写不敏感)的规则排序
cities
.sort(String
.CASE_INSENSITIVE_ORDER
);
整数类型List排序
Comparator.naturalOrder():字母自然顺序排序Comparator.reverseOrder():倒叙排序
List
<Integer> numbers
= Arrays
.asList(6, 2, 1, 4, 9);
System
.out
.println(numbers
);
numbers
.sort(Comparator
.naturalOrder());
System
.out
.println(numbers
);
numbers
.sort(Comparator
.reverseOrder());
System
.out
.println(numbers
);
按对象字段对List<Object>排序
Employee e1
= new Employee(1,23,"M","Rick","Beethovan");
Employee e2
= new Employee(2,13,"F","Martina","Hengis");
Employee e3
= new Employee(3,43,"M","Ricky","Martin");
Employee e4
= new Employee(4,26,"M","Jon","Lowman");
Employee e5
= new Employee(5,19,"F","Cristine","Maria");
Employee e6
= new Employee(6,15,"M","David","Feezor");
Employee e7
= new Employee(7,68,"F","Melissa","Roy");
Employee e8
= new Employee(8,79,"M","Alex","Gussin");
Employee e9
= new Employee(9,15,"F","Neetu","Singh");
Employee e10
= new Employee(10,45,"M","Naveen","Jain");
List
<Employee> employees
= Arrays
.asList(e1
, e2
, e3
, e4
, e5
, e6
, e7
, e8
, e9
, e10
);
employees
.sort(Comparator
.comparing(Employee
::getAge
));
Comparator链对List<Object>排序
employees
.sort(
Comparator
.comparing(Employee
::getGender
)
.thenComparing(Employee
::getAge
)
.reversed()
);
employees
.forEach(System
.out
::println
);
判断是否存在
anyMatch:判断是否有
boolean isExistAgeThan70
= employees
.stream().anyMatch(Employee
.ageGreaterThan70
);
boolean isExistAgeThan72
= employees
.stream().anyMatch(e
-> e
.getAge() > 72);
allMatch 和 noneMatch:都,前者表示全部都,后者表示全部都不
boolean isExistAgeThan10
= employees
.stream().allMatch(e
-> e
.getAge() > 10);
boolean isExistAgeLess18
= employees
.stream().noneMatch(e
-> e
.getAge() < 18);
元素查找与Optional
Optional类代表一个值存在或者不存在。在java8中引入,这样就不用返回null了。
isPresent() 将在 Optional 包含值的时候返回 true , 否则返回 false 。ifPresent(Consumer block) 会在值存在的时候执行给定的代码块。 Consumer 函数式接口;它让你传递一个接收 T 类型参数,并返回 void 的Lambda表达式。T get() 会在值存在时返回值,否则?出一个 NoSuchElement 异常。T orElse(T other) 会在值存在时返回值,否则返回一个默认值。findFirst用于查找第一个符合“匹配规则”的元素,返回值为OptionalfindAny用于查找任意一个符合“匹配规则”的元素,返回值为Optional
Optional
<Employee> employeeOptional
= employees
.stream().filter(e
-> e
.getAge() > 40).findFirst();
System
.out
.println(employeeOptional
.get());
Stream集合元素归约
Stream API为我们提供了Stream.reduce用来实现集合元素的归约。reduce函数有三个参数:
Identity标识:一个元素,它是归约操作的初始值,如果流为空,则为默认结果。Accumulator累加器:具有两个参数的函数:归约运算的部分结果和流的下一个元素。Combiner合并器(可选):当归约并行化时,或当累加器参数的类型与累加器实现的类型不匹配时,用于合并归约操作的部分结果的函数。
Integer类型归约
List
<Integer> numbers
= Arrays
.asList(1, 2, 3, 4, 5, 6);
int result
= numbers
.stream()
.reduce(0, (subtotal
, element
) -> subtotal
+ element
);
System
.out
.println(result
);
int result
= numbers
.stream()
.reduce(0, Integer
::sum
);
System
.out
.println(result
);
String类型归约
List
<String> letters
= Arrays
.asList("a", "b", "c", "d", "e");
String result
= letters
.stream()
.reduce("", (partialString
, element
) -> partialString
+ element
);
System
.out
.println(result
);
String result
= letters
.stream()
.reduce("", String
::concat
);
System
.out
.println(result
);
复杂对象的归约
Employee e1
= new Employee(1,23,"M","Rick","Beethovan");
Employee e2
= new Employee(2,13,"F","Martina","Hengis");
Employee e3
= new Employee(3,43,"M","Ricky","Martin");
Employee e4
= new Employee(4,26,"M","Jon","Lowman");
Employee e5
= new Employee(5,19,"F","Cristine","Maria");
Employee e6
= new Employee(6,15,"M","David","Feezor");
Employee e7
= new Employee(7,68,"F","Melissa","Roy");
Employee e8
= new Employee(8,79,"M","Alex","Gussin");
Employee e9
= new Employee(9,15,"F","Neetu","Singh");
Employee e10
= new Employee(10,45,"M","Naveen","Jain");
List
<Employee> employees
= Arrays
.asList(e1
, e2
, e3
, e4
, e5
, e6
, e7
, e8
, e9
, e10
);
Integer total
= employees
.stream().map(Employee
::getAge
).reduce(0,Integer
::sum
);
System
.out
.println(total
);
先用map将Stream流中的元素由Employee类型处理为Integer类型(age)。然后对Stream流中的Integer类型进行归约
Combiner合并器的使用
除了使用map函数实现类型转换后的集合归约,我们还可以用Combiner合并器来实现,这里第一次使用到了Combiner合并器。因为Stream流中的元素是Employee,累加器的返回值是Integer,所以二者的类型不匹配。这种情况下可以使用Combiner合并器对累加器的结果进行二次归约,相当于做了类型转换。
Integer total3
= employees
.stream()
.reduce(0,(totalAge
,emp
) -> totalAge
+ emp
.getAge(),Integer
::sum
);
System
.out
.println(total
);
Integer total2
= employees
.parallelStream()
.map(Employee
::getAge
)
.reduce(0,Integer
::sum
,Integer
::sum
);
System
.out
.println(total
);
终端操作
ForEach和ForEachOrdered
parallel()函数表示对管道中的元素进行并行处理,而不是串行处理,这样处理速度更快。但是这样就有可能导致管道流中后面的元素先处理,前面的元素后处理,也就是元素的顺序无法保证forEachOrdered从名字上看就可以理解,虽然在数据处理顺序上可能无法保障,但是forEachOrdered方法可以在元素输出的顺序上保证与元素进入管道流的顺序一致。也就是下面的样子(forEach方法则无法保证这个顺序):
Stream
.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
.parallel()
.forEach(System
.out
::println
);
Stream
.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
.parallel()
.forEachOrdered(System
.out
::println
);
元素的收集Collect
通用的收集方式:自定义容器
LinkedList
<String> collectToCollection
= Stream
.of(
"Monkey", "Lion", "Giraffe", "Lemur", "Lion"
).collect(Collectors
.toCollection(LinkedList
::new));
收集为Set
Set
<String> collectToSet
= Stream
.of(
"Monkey", "Lion", "Giraffe", "Lemur", "Lion"
)
.collect(Collectors
.toSet());
收集为List
List
<String> collectToList
= Stream
.of(
"Monkey", "Lion", "Giraffe", "Lemur", "Lion"
).collect(Collectors
.toList());
收集为Array(数组)
String
[] toArray
= Stream
.of(
"Monkey", "Lion", "Giraffe", "Lemur", "Lion"
) .toArray(String
[]::new);
收集倒Map
使用Collectors.toMap()方法将数据元素收集到Map里面,但是出现一个问题:那就是管道中的元素是作为key,还是作为value。我们用到了一个Function.identity()方法,该方法很简单就是返回一个“ t -> t ”(输入就是输出的lambda表达式)。另外使用管道流处理函数distinct()来确保Map键值的唯一性。
Map
<String, Integer> toMap
= Stream
.of(
"Monkey", "Lion", "Giraffe", "Lemur", "Lion"
)
.distinct()
.collect(Collectors
.toMap(
Function
.identity(),
s
-> (int) s
.chars().distinct().count()
));
分组收集groupingBy
Map
<Character
, List
<String>> groupingByList
= Stream
.of(
"Monkey", "Lion", "Giraffe", "Lemur", "Lion"
)
.collect(Collectors
.groupingBy(
s
-> s
.charAt(0) ,
));
其他方法
boolean containsTwo
= IntStream
.of(1, 2, 3).anyMatch(i
-> i
== 2);
long nrOfAnimals
= Stream
.of(
"Monkey", "Lion", "Giraffe", "Lemur"
).count();
int sum
= IntStream
.of(1, 2, 3).sum();
OptionalDouble average
= IntStream
.of(1, 2, 3).average();
int max
= IntStream
.of(1, 2, 3).max().orElse(0);
IntSummaryStatistics statistics
= IntStream
.of(1, 2, 3).summaryStatistics();
Map处理
HashMap的merge()函数
看下面一段代码,我们首先创建了一个HashMap,并往里面放入了一个键值为k:1的元素。当我们调用merge函数,往map里面放入k:2键值对的时候,k键发生重复,就执行后面的lambda表达式。表达式的含义是:返回旧值oldVal加上新值newVal(1+2),现在map里面只有一项元素那就是k:3。
String k
= "key";
HashMap
<String, Integer> map
= new HashMap<String, Integer>() {{
put(k
, 1);
}};
map
.merge(k
, 2, (oldVal
, newVal
) -> oldVal
+ newVal
);
按Map的键排序
Map
<String, Integer> codes
= new HashMap<>();
codes
.put("United States", 1);
codes
.put("Germany", 49);
codes
.put("France", 33);
codes
.put("China", 86);
codes
.put("Pakistan", 92);
Map
<String, Integer> sortedMap
= codes
.entrySet().stream()
.sorted(Map
.Entry
.comparingByKey())
.collect(
Collectors
.toMap(
Map
.Entry
::getKey
,
Map
.Entry
::getValue
,
(oldVal
, newVal
) -> oldVal
,
LinkedHashMap
::new
)
);
sortedMap
.entrySet().forEach(System
.out
::println
);
首先使用entrySet().stream() 将Map类型转换为Stream流类型。然后使用sorted方法排序,排序的依据是Map.Entry.comparingByKey(),也就是按照Map的键排序最后用collect方法将Stream流转成LinkedHashMap。 其他参数都好说,重点看第三个参数,就是一个merge规则的lambda表达式,与merge方法的第三个参数的用法一致。由于本例中没有重复的key,所以新值旧值随便返回一个即可。
按Map的值排序
Map
<String, Integer> sortedMap2
= codes
.entrySet().stream()
.sorted(Map
.Entry
.comparingByValue())
.collect(Collectors
.toMap(
Map
.Entry
::getKey
,
Map
.Entry
::getValue
,
(oldVal
, newVal
) -> oldVal
,
LinkedHashMap
::new));
sortedMap2
.entrySet().forEach(System
.out
::println
);
使用TreeMap按键排序
Map
<String, Integer> sorted
= new TreeMap<>(codes
);
Java 8读取文件过滤
Path filePath
= Paths
.get("c:/temp", "data.txt");
try (Stream
<String> lines
= Files
.lines(filePath
)){
List
<String> filteredLines
= lines
.filter(s
-> s
.contains("password"))
.collect(Collectors
.toList());
filteredLines
.forEach(System
.out
::println
);
} catch (IOException e
) {
e
.printStackTrace();
}