Lambda 表达式的基础语法:Java8中引入了一个新的操作符 “->” 该操作符称为箭头操作符或 Lambda 操作符
箭头操作符将 Lambda 表达式拆分成两部分
左侧:Lambda 表达式的**参数列表**(其实就是接口中的抽象方法的形参列表)
右侧:Lambda 表达式中所需执行的功能, 即 **Lambda 体**(其实就是重写的抽象方法的方法体)
化简依据:函数式接口的方法只有固定一个,可以化简。参数类型由泛型固定,可以化简
**本质:作为==函数式接口的实例==**
语法格式一:无参数,无返回值
() -> System.out.println("Hello Lambda!");
语法格式二:有一个参数,并且无返回值
(x) -> System.out.println(x)
语法格式三:若只有一个参数,小括号可以省略不写
x -> System.out.println(x)
语法格式四:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
Comparator<Integer> com = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
语法格式五:若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写(但是需要同时省略,不能只省略一个)
Comparator
语法格式六:Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
(Integer x, Integer y) -> Integer.compare(x, y);
上联:左右遇一括号省
下联:左侧推断类型省
横批:能省则省
Lambda 表达式需要“函数式接口”的支持
函数式接口:接口中**只有一个抽象方法**的接口,称为函数式接口。 可以使用注解 @FunctionalInterface 修饰
可以检查是否是函数式接口
public class TestLambda2 {
@Test
public void test1(){
int num = 0;//jdk 1.7 前,必须是 final
//原形式
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello World!" + num);
}
};
r.run();
System.out.println("-------------------------------");
//语法格式一
Runnable r1 = () -> System.out.println("Hello Lambda!");
r1.run();
}
//语法格式二、三
@Test
public void test2(){
Consumer<String> con = x -> System.out.println(x);
con.accept("我大尚硅谷威武!");
}
//语法格式四
@Test
public void test3(){
Comparator<Integer> com = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
}
//语法格式五
@Test
public void test4(){
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
}
//语法格式六
@Test
public void test5(){
// String[] strs;
// strs = {"aaa", "bbb", "ccc"};
List<String> list = new ArrayList<>();
show(new HashMap<>());
}
public void show(Map<String, Integer> map){
}
//需求:对一个数进行运算
@Test
public void test6(){
Integer num = operation(100, (x) -> x * x);
System.out.println(num);
System.out.println(operation(200, (y) -> y + 200));
}
public Integer operation(Integer num, MyFun mf){
return mf.getValue(num);
}
}
//代替匿名内部类
@Test
public void test01(){
//匿名内部类写法
happyTime(500, new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println("消费:"+aDouble);
}
});
System.out.println("------------");
//Lambda表达式写法
happyTime(400,x -> System.out.println("消费2:"+x));
}
public void happyTime(double money, Consumer<Double> con){
con.accept(money);
}
注:上面的代码中,
new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println("消费:"+aDouble);
}
}
为函数式接口Consumer
的一个匿名实例,而在下面的Lambda表达式x -> System.out.println("消费2:"+x)
代替了它,所以说==Lambda表达式的本质是函数式接口的实例==
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer 消费型接口 |
T | void | 对类型为T的对象应用操作, 包含方法: void accept(T t) |
Supplier 供给型接口 |
无 | T | 返回类型为T的对象, 包含方法: T get(); |
Function 函数型接口 |
T | R | 对类型为T的对象应用操作, 并返回结果。 结果是R类型的对象。 包含方法: R apply(T t); |
Predicate 断定型接口 |
T | boolean | 确定类型为T的对象是否满足某约束, 并返回boolean 值。 包含方法boolean test(T t); |
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
BiFunction |
T, U | R | 对类型为 T, U 参数应用操作, 返回 R 类型的结 果。包含方法为R apply(T t, U u); |
UnaryOperator (Function子接口) |
T | T | 对类型为T的对象进行一元运算, 并返回T类型的结果。 包含方法为T apply(T t); |
BinaryOperator (BiFunction 子接口) |
T, T | T | 对类型为T的对象进行二元运算, 并返回T类型的结果。 包含方法为T apply(T t1, T t2); |
BiConsumer |
T, U | void | 对类型为T, U 参数应用操作。 包含方法为void accept(T t, U u) |
ToIntFunction ToLongFunction ToDoubleFunction |
T | int long double | 分别计算 int , long 、double、 值的函数 |
IntFunction LongFunction |
int long double | R | 参数分别为int、 long、double 类型的函数 |
/*
* Java8 内置的四大核心函数式接口
*
* Consumer : 消费型接口
* void accept(T t);
*
* Supplier : 供给型接口
* T get();
*
* Function : 函数型接口
* R apply(T t);
*
* Predicate : 断言型接口
* boolean test(T t);
*
*/
public class TestLambda3 {
//Predicate 断言型接口:
@Test
public void test4(){
List<String> list = Arrays.asList("Hello", "atguigu", "Lambda", "www", "ok");
List<String> strList = filterStr(list, (s) -> s.length() > 3);
for (String str : strList) {
System.out.println(str);
}
}
//需求:将满足条件的字符串,放入集合中
public List<String> filterStr(List<String> list, Predicate<String> pre){
List<String> strList = new ArrayList<>();
for (String str : list) {
if(pre.test(str)){
strList.add(str);
}
}
return strList;
}
//Function 函数型接口:
@Test
public void test3(){
String newStr = strHandler("\t\t\t 我大尚硅谷威武 ", (str) -> str.trim());
System.out.println(newStr);
String subStr = strHandler("我大尚硅谷威武", (str) -> str.substring(2, 5));
System.out.println(subStr);
}
//需求:用于处理字符串
public String strHandler(String str, Function<String, String> fun){
return fun.apply(str);
}
//Supplier 供给型接口 :
@Test
public void test2(){
List<Integer> numList = getNumList(10, () -> (int)(Math.random() * 100));
for (Integer num : numList) {
System.out.println(num);
}
}
//需求:产生指定个数的整数,并放入集合中
public List<Integer> getNumList(int num, Supplier<Integer> sup){
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer n = sup.get();
list.add(n);
}
return list;
}
//Consumer 消费型接口 :
@Test
public void test1(){
happy(10000, (m) -> System.out.println("你们刚哥喜欢大宝剑,每次消费:" + m + "元"));
}
public void happy(double money, Consumer<Double> con){
con.accept(money);
}
}
使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以方法引用,也是函数式接口的实例
使用格式:类(或对象)::方法名
具体分为如下的三种情况(注意没有对象::静态方法
,因为没有意义)
对象::非静态方法
(非静态方法也叫实例方法)类::静态方法
类::非静态方法
(难点)方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同(只针对前两种情况)
理解:本质上是将已经有的方法替换对函数式接口里的方法的重写
相比Lambda表达式:不用传入参数和标明返回值类型,因为形参列表和返回值类型相同
// 情况一: 对象::非静态方法
//consumer中的void accept(T t)
//Printstream中的void println(T t)
//将已经有的Printstream类中的println方法替换对Consumer接口里的accept方法的重写
@Test
public void test1(){
//Lambda表达式写法
Consumer<String>conl=str -> System.out.println(str);
con1.accept("北京");
//方法引用写法
Printstream ps = System.out;
Consumer<String>con2 = ps::println; //ps::println为对象ps的非静态方法println
con2.accept("beijing");
// 匿名内部类写法(传统)
Consumer<String> con3 = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
// 调用 accept 方法
con3.accept("beijing");
// 情况一: 对象::非静态方法
//supplier中的T get()
//Employee中的string getName()
@Test
public void test2(){
Employee emp = new Employee( id: 1001,name:"Tom",age: 23, salary: 5680);//另外有Employee类,这里省略
//Lambda表达式写法,将Supplier接口里的get方法重写,内容是执行emp对象的getName方法
Supplier<String>sup1= () -> emp.getName();
System.out.println(sup1.get());
//方法引用写法,直接将重写内容改为了执行emp对象的getName方法
Supplier<string>sup2 = emp::getName;
System.out.println(sup2.get());
// 匿名内部类写法
Supplier<String> sup3 = new Supplier<String>() {
@Override
public String get() {
return emp.getName();
}
};
System.out.println(sup3.get());
// 情况二:类 ::静态方法
//Comparator中的int compare(T t1, T t2)
//Integer中的int compare(T t1,T t2)
@Test
public void test3(){
//Lambda表达式写法
Comparator<Integer>com1 = (t1,t2) -> Integer.compare(t1,t2);
System.out.println(com1.compare(12,21));
//方法引用写法
Comparator<Integer>com2 =Integer::compare;
System.out.println(com2.compare(12,3));
//Function中ER apply(T t)
//Math中的Long round(Double d)
@Test
public void test4(){
// 匿名内部类写法
Function<Double,Long>func = new Function<Double, Long>(){
@Override
public Long apply(Double d){
return Math.round(d);
};
//Lambda表达式写法
Function<Double,Long>func1=d-> Math.round(d);
System.out.println(func1.apply( t: 12.3));
//方法引用写法
Function<Double,Long>func2 = Math::round;
System.out.println(func1.apply(t:12.6));
理解:传入的参数作为一个对象,用这个对象调用方法,此时调用的为非静态方法。但是写函数式接口的代码时还不知道传入的参数(对象)是什么,故用类名放在前面表示
类名或对象名:
String::
表示正在引用String
类中的方法。方法名:
compareTo
是String
类中的一个方法。如何工作
当使用
String::compareTo
作为方法引用时,它意味着想要调用String
类中的compareTo
方法。这里的关键点是,compareTo
方法是一个实例方法,它需要一个String
对象来调用它。传入的参数
在
Comparator
接口中,compare
方法有两个参数t1
和t2
。当使用String::compareTo
作为方法引用时,这两个参数会被自动映射到String
类中的compareTo
方法的调用上。具体来说:
- 第一个参数
t1
将作为compareTo
方法的调用者。- 第二个参数
t2
将作为compareTo
方法的参数。
//情况三:类::非静态方法
//Comparator中的int comapre(T t1,T t2)
//String中的int t1.compareTo(t2)
@Test
public void test5(){
//Lambda表达式写法
Comparator<string>com1=(s1,s2)->s1.compareTo(s2);
System.out.println(com1.compare("abc","abd"));
//方法引用写法
Comparator<String>com2 = String ::compareTo;
System.out.println(com2.compare("abd","abm"));
//情况三:类::非静态方法
//BiPredicate中的boolean test(T t1,T t2);
//string中的boolean t1.equals(t2)
@Test
public void tesi6(){
//Lambda表达式写法
BiPredicate<string,string>pre1=(s1,s2)->s1.equals(s2);
System.out.println(pre1.test(t:"abc",u:"abc"));
//方法引用写法
BiPredicate<string,string>pre2=string ::equals;
System.out.println(pre1.test( t:"abc",u: "abd"));
//情况三:类::非静态方法
// Function中的R apply(T t)
// Employee中String getName();
@Test
public void test7(){
Employee employee = new Employee( id: 1001, name: "Jerry", age: 23,salary:6000);
//Lambda表达式写法
Function<Employee,string>func1=e->e.getName(
System.out.println(func1.apply(employee));
//方法引用写法
Function<Employee,string>func2 = Employee::getName;
System.out.println(func2.apply(employee));
和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致,抽象方法的返回值类型即为构造器所属的类的类型
//构造器引用
//Supplier中的T get()
//Employee的空参构造器:Employee()
@Test
public void test1(){
// 匿名内部类写法
Supplier<Employee>sup = new supplier<Employee>(){
@Override
public Employee get(){
return new Employee();
};
//Lambda表达式写法
Supplier<Employee>sup1=() -> new Employee();
System.out.println(sup1.get());
//构造器引用写法
Supplier<Employee>sup2 =Employee :: new;
system.out.println(sup2.get());
//Function中的R apply(T t)
@Test
public void test2(){
//Lambda表达式写法
Function<Integer,Employee>func1 =id -> new Employee(id);
Employee employee = func1.apply( t: 1001);
System.out.println(employee);
//构造器引用写法
Function<Integer,Employee>func2 = Employee :: new;
Employee employee1=func2.apply( t:1002);
System.out.println(employee1);
//BiFunction中的R appLy(T t,U u)
@Test
public void test3(){
BiFunction<Integer,string,Employee>func1 =(id,name) -> new Employee(id,name)
System.out.println(func1.apply(t:1001,u:"Tom"));
BiFunction<Integer,string,Employee>func2 = Employee :: new;
System.out.println(func2.apply( t: 1682,u: "Tom"));
把数组看做是一个特殊的类,则写法与构造器引用一致
//数组引用
//Function中的R apply(T t)
@Test
public void test4(){
Function<Integer,string[]> func1 = length -> new string[length];
string[] arr1 = func1.apply(t:5);
System.out.println(Arrays.tostring(arr1));
Function<Integer,string[]>func2 = string[] :: new;
String[] arr2 =func2.apply( t: 10);
System.out.println(Arrays.tostring(arr2));
Stream关注的是对数据的运算,与CPU打交道
集合关注的是数据的存储,与内存打交道
注意:
Stream自己不会存储元素(类似迭代器,只处理集合数据,不存储集合数据)
不会改变源对象。相反,他们会返回一个持有结果的新Stream
Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
Stream 执行流程
stream()
和 parallelstream()
//创建 stream方式-:通过集合
@Test
public void test1(){
List<Employee>employees =EmployeeData.getEmployees()
//default streamstream():返回一个顺序流
Stream<Employee>stream = employees.stream();
//default streamparallelstream():返回一个并行流
Stream<Employee> parallelstream = employees.parallelstream();}
stream(T[] array)
//创建 stream 方式二:通过数组
@Test
public void test2(){
int[] arr = new int[]{1,2,3,4,5,6};
//调用Arrays类的staticstreamstream(T[] array):返回一个流
IntStream stream =Arrays.stream(arr);
Employee e1 = new Employee( id:1001,name:"Tom");
Employee e2 = new Employee( id: 1002,name: "Jerry");
Employee[l arr1 = new Employee[]{e1,e2};
Stream<Employee>stream1 = Arrays.stream(arr1);}
stream.of()
//创建 stream方式三:通过stream的of()
@Test
public void test3(){
Stream<Integer>stream = stream.of(1,2,3,4,5,6)
}
//创建 stream方式四:创建无限流
@Test
public void test4(){
//迭代
//public static stream iterate(final T seed, final Unaryoperator f)
//遍历前10个偶教
Stream.iterate(seed:0,t->t+ 2).limit(10).forEach(System.out::println);
//生成
//public staticstream generate(supplier s)
Stream.generate(Math::random).limit(10).forEach(System.out::println)}
多个中间操作可以连接起来形成一个流水线, 除非流水线上触发终止操作, 否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理, 称为“惰性求值”
方 法 | 描 述 |
---|---|
filter(Predicate p) | 接收 Lambda , 从流中排除某些元素。 |
distinct() | 筛选, 通过流所生成元素的 hashCode() 和 equals() 去除重复元素 |
limit(long maxSize) | 截断流, 使其元素不超过给定数量(截断后面) |
skip(long n) | 跳过元素, 返回一个扔掉了前 n 个元素的流。 若流中元素不足 n 个, 则返回一个空流。 与 limit(n) 互补(截断前面) |
public void test1(){
List<Employee>list = EmployeeData.getEmployees();
//filter(Predicate p)-接收Lambda, 从流中排除某些元素。
Stream<Employee>stream=list.stream();
//查询员工表中薪资大于7000的员工信息
//foreach方法:遍历,可以看作for循环,这里直接用方法引用(这里foreach算是一个终止操作)
stream.filter(e->e.getsalary() > 7000).forEach(System.out::println).
System.out.println();
//limit(n)-截断流,使其元素不超过给定数量。
stream,limit(3).forEach(system.out::println);
System.out.println();
//skip(n)- 跳过元素,返回一个扔掉了前 n 个元素的流。
list.stream().skip(3).forEach(system.out::println);
//distinct()-筛选,通过流所生成元素的 hashcode()和 equals()去除重复元素
list.add(new Employee( id: 1010, name:"刘强东",age: 40,salary:8000));
list.add(new Employee( id: 1010, name:"刘强东",age:8000));
list.stream().distinct().forEach(system.out::println);
方 法 | 描 述 |
---|---|
map(Function f) | 接收一个函数作为参数, 该函数会被应用到每个元素上,并将其映射成一个新的元素 |
flatMap(Function f) | 接收一个函数作为参数, 将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
public void test2(){
List<string>list =Arrays.asList("aa","bb","cc","dd");
//例子1:将集合中的字母全部变成大写
list.stream().map(str -> str.toUpperCase()).forEach(system.out::println);
//例子2:获取员工姓名长度大于3的员工的姓名
List<Emplloyee>employees = EmployeeData.getEmployees();
//得到的是处理过后的新的流
Stream<String> stringStream = employees.stream().map(Employee::getName);
//再将这个流过滤
namesStream.filter(name ->name.length()>3).forEach(System.out::println);
区别:map()
方法接收一个函数作为参数,并将该函数应用于流中的每个元素,然后返回一个新的流,其中包含应用函数后的结果。换句话说,map()
会将流中的每个元素转换为另一个类型的单个元素。
示例代码:
假设我们有一个整数流,我们想要将每个整数转换为它的平方值:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList());
// 输出: [1, 4, 9, 16, 25]
在这个例子中,map(n -> n * n)
将每个整数 n
映射到 n * n
的结果,生成一个新的流,最终收集为一个列表。
flatMap()
方法接收一个函数作为参数,该函数返回一个流。flatMap()
方法会将原始流中的每个元素转换为一个流,然后将这些流扁平化为一个单一的流。这意味着 flatMap()
可以将嵌套的流转换为一个单一的流。
示例代码:
假设我们有一个整数列表的列表,我们想要将其扁平化为一个单一的整数列表:
List<List<Integer>> listOfLists = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6),
Arrays.asList(7, 8, 9)
);
List<Integer> flattened = listOfLists.stream()
.flatMap(list -> list.stream())
.collect(Collectors.toList());
// 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
//注:如果用map的操作,会得到类似[[1,2],3,4,5,[6,7,8,9]]的输出
在这个例子中,flatMap(list -> list.stream())
将每个内部列表转换为其自身的流,然后将所有这些流合并成一个单一的流。
在实际应用中,当你需要处理嵌套的数据结构时,例如列表的列表、集合的集合等,flatMap()
通常是非常有用的工具。而当你只需要对每个元素进行简单的转换时,map()
就足够了。
方 法 | 描 述 |
---|---|
sorted() | 产生一个新流, 其中按自然顺序排序 |
sorted(Comparator comp) | 产生一个新流, 其中按比较器顺序排序 |
public void test4(){
//sorted()一自然排序
List<Integer>list=Arrays.aslist(12,43,65,34,87,0,-98,7);
list.stream().sorted().forEach(system.out::println);
//抛异常,原因:Employee没有实现Comparable接口
//Listemployees =EmployeeData.getEmployees();
//employees.stream().sorted().forEach(System.out::println).sorted(Comparator com)——定制排序
List<Employee>employees = EmployeeData.getEmployees();
employees.stream().sorted((e1,e2)->{
int ageValue = Integer.compare(e1.getAge(),e2.getAge());
if(ageValue != 0){
return ageValue;
}else{
return pouble.compare(e1.getSalary(),e2.getSalary());
}).forEach(system.out::println);
}
终端操作会从流的流水线生成结果。 其结果可以是任何不是流的值, 例如: List、 Integer, 甚至是 void
方 法 | 描 述 |
---|---|
allMatch(Predicate p) |
检查是否匹配所有元素 |
anyMatch(Predicate p) |
检查是否至少匹配一个元素 |
noneMatch(Predicate p) |
检查是否没有匹配所有元素 |
findFirst() |
返回第一个元素 |
findAny() |
返回当前流中的任意元素 |
count() |
返回流中元素总数 |
max(Comparator c) |
返回流中最大值 |
min(Comparator c) |
返回流中最小值 |
forEach(Consumer c) |
内部迭代(使用 Collection 接口需要用户去做迭代, 称为外部迭代。 相反, Stream API 使用内部迭代——它帮你把迭代做了) |
//以第一个为例
public void test1(){
List<Employee>employees =EmployeeData.getEmployees();
//allMatch(Predicate p)-检查是否匹配所有元素。
//练习:是否所有的员工的年龄都大于18
boolean allMatch = employees.stream().allMatch(e ->e.getAge()> 18);
System.out.println(allMatch);
}
reduce(T identity, BinaryOperator b) |
可以将流中元素反复结合起来, 得到一个值(identity)。返回 T |
---|---|
reduce(BinaryOperator b) |
可以将流中元素反复结合起来, 得到一个值。返回 Optional |
注: map 和 reduce 的连接通常称为map-reduce 模式, 因 Google 用它来进行网络搜索而出名
//练习1:计算1-10的自然数的和
List<Integer> list = Arrays.aslist(1,2,3,4,5,6,7,8,9,10);
Integer sum= list.stream().reduce(identity:0, Integer::sum);
System.out.println(sum);
//练习2:计算公司所有员工工资的总和
List<Employee>employees= EmployeeData.getEmployees();
Stream<Double>salarystream = employees.stream().map(Employee::getSalary).
//直接用方法引用的话:OptionalsumMoney = salarystream.reduce(Double::sum);
Optional<Double>sumMoney= salaryStream.reduce((d1,d2)-> d1 + d2);
System.out.println(sumMoney);
方 法 | 描 述 |
---|---|
collect(Collector c) |
将流转换为其他形式。 接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法 |
Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、 Set、 Map)。
但是 Collectors 实用类提供了很多静态方法, 可以方便地创建常见收集器实例, 具体方法与实例如下表 :
方法 | 返回类型 | 作用 |
---|---|---|
toList | List | 把流中元素收集到List |
List |
||
toSet | Set | 把流中元素收集到Set |
Set |
||
toCollection | Collection | 把流中元素收集到创建的集合 |
Collection |
||
counting | Long | 计算流中元素的个数 |
long count = list.stream().collect(Collectors.counting()); |
||
summingInt | Integer | 对流中元素的整数属性求和 |
int total=list.stream().collect(Collectors.summingInt(Employee::getSalary)); |
||
averagingInt | Double | 计算流中元素Integer属性的平均值 |
double avg= list.stream().collect(Collectors.averagingInt(Employee::getSalary)); |
||
summarizingInt | IntSummaryStatistics | 收集流中Integer属性的统计值。如: 平均值 |
IntSummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary)); |
joining | String | 连接流中每个字符串 |
---|---|---|
String str= list.stream().map(Employee::getName).collect(Collectors.joining()); |
||
maxBy | Optional | 根据比较器选择最大值 |
Optional |
||
minBy | Optional | 根据比较器选择最小值 |
Optional |
||
reducing | 归约产生的类型 | 从一个作为累加器的初始值开始, 利用BinaryOperator与流中元素逐个结合, 从而归约成单个值 |
int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum)); |
||
collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器, 对其结果转换函数 |
int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)); |
||
groupingBy | Map |
根据某属性值对流分组, 属性为K, 结果为V |
Map |
||
partitioningBy | Map |
根据true或false进行分区 |
Map |
Optional
可以用来封装可能为 null
的值,从而避免在使用这些值时产生 NullPointerException
。Optional
可以明确地表示某个变量可能没有值,从而让调用者能够更容易地处理这种情况。null
值:
Optional
提供了一系列方法来安全地处理可能为 null
的值,如 ifPresent
, orElse
, orElseGet
, orElseThrow
等。null
的情况下进行操作,使得代码更加简洁和易于理解。Optional
之前,通常需要显式地检查一个变量是否为 null
。Optional
可以减少这些条件判断,使得代码更加简洁。Optional
的使用使得代码更加清晰,意图更加明显。Optional
的方法,可以减少不必要的嵌套条件语句,使代码结构更加清晰。Optional.of(T t) | 创建一个 Optional 实侧,t必须非空 |
---|---|
Optional.empty() | 创建一个空的 Optional 实例 |
Optional.ofNullable(T t) | t 可以为 null |
boolean isPresent( ) | 判断是否包含对象 |
---|---|
void ifPresent(Consumer superT> consumer) |
如果有值,就执行`Consumer接口的实现代码,并且该值会作为参数传给它。 |
T get() | 如果调用对象包含值,返回该值,否则抛异常 |
---|---|
T orElse(T other) | 如果有值则将其返回,否则返回指定的other对象 |
T orElseGet(Supplier extends T> other) | 如果有值则将其返回,否则返回由Supplier接口实现提供的对象 |
T orElseThrow(Supplier< ? extends X >exceptionSupplier) |
如果有值则将其返回,否则抛出由Supplier接口实现提供的异常 |
获取到boy对象里的girl对象里的属性girlName
//1.直接写法:如果boy、girl对象不存在或为空,会报异常
public void test3(){
Boy boy = new Boy();
boy = null;
String girlName= getGirlName(boy);
System.out.println(girlName);
}
//2.优化以后的getGirlName():
public string getGirlNamel(Boy boy){
if(boy != null){
Girl girl = boy.getGir1();
if(girl != null){
return girl.getName();
}
}
return null;
}
//3.使用optional类的getGirlName(),这样能保证没有空指针异常,保底会输出默认值
public string getGirlName2(Boy boy){
Optional<Boy> boyoptional = optional.ofNullable(boy);
//此时的boy1一定非空
Boy boy1 = boyoptional.orElse(new Boy(new Girl( name:"迪丽热巴")));
Girl girl = boy1.getGir1();
Optional<Girl> girloptional = optional.ofNullable(gir1);
//girl1一定非空
Girl girl1 = girloptional.orElse(new Girl( name:"古力娜扎"));
return girl1.getName();
}
接口中的“默认方法”(default method)是一种特殊类型的方法,它允许在接口中定义一个具有实现体的方法。这为接口提供了一种向后兼容的方式,可以在不破坏现有实现类的前提下添加新的方法
可以在接口中声明一个方法,并使用default
关键字来指定其为默认方法。这样,任何实现了该接口的类都可以选择性地覆盖这个方法,如果不覆盖,则会继承接口中的默认实现。
默认方法语法格式如下:
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
}
一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,以下实例说明了这种情况的解决方法:
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
}
public interface FourWheeler {
default void print(){
System.out.println("我是一辆四轮车!");
}
}
第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法:
public class Car implements Vehicle, FourWheeler {
default void print(){
System.out.println("我是一辆四轮汽车!");
}
}
第二种解决方案可以使用 super 来调用指定接口的默认方法:
public class Car implements Vehicle, FourWheeler {
public void print(){
Vehicle.super.print();
}
}
Java 8 的另一个特性是接口可以声明(并且可以提供实现)静态方法。例如:
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
// 静态方法
static void blowHorn(){
System.out.println("按喇叭!!!");
}
}
Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。
LocalDate/LocalTime 和 LocalDateTime 类可以在处理时区不是必须的情况。代码如下:
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.Month;
public class Java8Tester {
public static void main(String args[]){
Java8Tester java8tester = new Java8Tester();
java8tester.testLocalDateTime();
}
public void testLocalDateTime(){
// 获取当前的日期时间
LocalDateTime currentTime = LocalDateTime.now();
System.out.println("当前时间: " + currentTime);
LocalDate date1 = currentTime.toLocalDate();
System.out.println("date1: " + date1);
Month month = currentTime.getMonth();
int day = currentTime.getDayOfMonth();
int seconds = currentTime.getSecond();
System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);
LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
System.out.println("date2: " + date2);
// 12 december 2014
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println("date3: " + date3);
// 22 小时 15 分钟
LocalTime date4 = LocalTime.of(22, 15);
System.out.println("date4: " + date4);
// 解析字符串
LocalTime date5 = LocalTime.parse("20:15:30");
System.out.println("date5: " + date5);
}
}
执行结果:
$ javac Java8Tester.java
$ java Java8Tester
当前时间: 2016-04-15T16:55:48.668
date1: 2016-04-15
月: APRIL, 日: 15, 秒: 48
date2: 2012-04-10T16:55:48.668
date3: 2014-12-12
date4: 22:15
date5: 20:15:30
如果我们需要考虑到时区,就可以使用时区的日期时间API:
import java.time.ZonedDateTime;
import java.time.ZoneId;
public class Java8Tester {
public static void main(String args[]){
Java8Tester java8tester = new Java8Tester();
java8tester.testZonedDateTime();
}
public void testZonedDateTime(){
// 获取当前时间日期
ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
System.out.println("date1: " + date1);
ZoneId id = ZoneId.of("Europe/Paris");
System.out.println("ZoneId: " + id);
ZoneId currentZone = ZoneId.systemDefault();
System.out.println("当期时区: " + currentZone);
}
}
执行结果:
$ javac Java8Tester.java
$ java Java8Tester
date1: 2015-12-03T10:15:30+08:00[Asia/Shanghai]
ZoneId: Europe/Paris
当期时区: Asia/Shanghai