1 Lambda 表达式
1-1 Lambda 表达式介绍
Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->
”,该操作符被称为 Lambda 操作符或剪头操作符。它将 Lambda 分为两个部分:
例:(o1,o2) -> Integer.compare(o1,o2);
左侧:指定了 Lambda 表达式需要的参数列表
(即接口中的抽象方法的形参列表)
右侧:指定了 Lambda 体(即重写的抽象方法的方法体
)
Lambda表达式的本质
:作为函数式接口的实例(函数式接口即接口中,只声明了一个抽象方法)
@Test
public void Test1(){
//匿名内部类
Runnable r1 = new Runnable() {
public void run(){
System.out.println("你好,世界");
}
};
r1.run();
//Lambda表达式
System.out.println("================");
Runnable r2 = () -> System.out.println("你好,中国");
r2.run();
}
1-2 Lambda 表达式语法
1-2-1 无参,无返回值,Lambda体只需一条语句
@Test
public void Test2(){
Runnable r1 = new Runnable() {
public void run(){
System.out.println("你好,世界");
}
};
r1.run();
System.out.println("===========");
Runnable r2 = () -> System.out.println("hello world");
r2.run();
}
1-2-2 Lambda需要一个参数
@Test
public void Test3(){
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("Go!");
System.out.println("=====================");
Consumer<String> con1 = (String args) -> System.out.println(args);
con1.accept("hello world");
}
1-2-3 类型推断
Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”
@Test
public void Test4(){
Consumer<String> con1 = (String args) -> System.out.println(args);
con1.accept("hello world");
System.out.println("=====================");
Consumer<String> con2 = (args) -> System.out.println(args);
con2.accept("hello cat");
}
1-2-4 Lambda只需要一个参数时,参数的小括号可以省略
@Test
public void Test5(){
Consumer<String> con1 = (args) -> System.out.println(args);
con1.accept("hello world");
System.out.println("=====================");
Consumer<String> con2 = args -> System.out.println(args);
con2.accept("hello boy");
}
1-2-5 Lambda需要两个及以上参数,并且,有多条语句,有返回值
@Test
public void Test6(){
Comparator<Integer> com = new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2) {
System.out.print("对比结果:");
return o1.compareTo(o2);
}
};
System.out.println(com.compare(21,12));
System.out.println("=====================");
Comparator<Integer> com1 = (o1,o2) -> {
System.out.print("对比结果:");
return o1.compareTo(o2);
};
System.out.println(com1.compare(20,22));
}
1-2-6 当 Lambda 体只有一条语句时,return 与大括号可以省略
@Test
public void Test7(){
Comparator<Integer> com1 = (o1,o2) -> {
return o1.compareTo(o2);
};
System.out.println(com1.compare(20,22));
System.out.println("=====================");
Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2);
System.out.println(com2.compare(20,20));
}
1-3 总结
左边: Lambda 表达式需要的参数列表的参数类型可以省略,如果形参列表只有一个参数,一对小括号()可省略
右边: Lambda体应使用一对{}包裹,如果Lambda体只有一条执行语句(或return语句),那就可以省略这一对{}和return关键字(要省都省,不能单省)
Lambda表达式使用:当需要对一个函数式接口进行实例化时,可以使用Lambda表达式
2 函数式接口
2-1 函数式接口介绍
- 只包含一个抽象方法的接口,称为函数式接口
- 你可以通过 Lambda 表达式来创建该接口的对象(若 Lambda表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)
- 我们可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,同时 javadoc也会包含一条声明,说明这个接口是一个函数式接口
例:
public class Function {
}
//自定义函数式接口
@FunctionalInterface
public interface MyNumber{
public int getValue();
}
//函数式接口使用泛型
@FunctionalInterface
public interface MyNumberes<T>{
public T getValue(T t);
}
2-2 Java 内置四大核心函数式接口
注:主要是第一张图
函数式接口的使用:当开发中需要定义时,先查看已有JDK提供的函数式接口是否提供满足需求的函数式接口,有就使用,没有就自定义。
3 方法引用与构造器引用
3-1 使用场景
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)
方法引用本质:lambda表达式深层次的表达,而lambda表达式作为函数式接口的实现,所以方法引用也是函数式接口的实例
。
格式:
类(或对象) :: 方法名
3-2 一般的三种情况
情况1 对象::实例方法(非静态方法)
//Consumer中的void accept(T t)
//PrintStream中的void println(T t)
@Test
public void test1(){
Consumer<String> con = str -> System.out.println(str);
con.accept("重庆1");
System.out.println("======================");
Consumer<String> con1 = System.out::println;
con.accept("重庆2");
//或如下:
PrintStream ps = System.out;
Consumer<String> con2 = ps::println;
con.accept("重庆3");
}
情况2:类::静态方法
//Comparator中的int compare(T t1,T t2)
//Integer中的int compare(T t1,T t2)
@Test
public void test2(){
Comparator<Integer> conn1 = (t1,t2) -> Integer.compare(t1,t2);
System.out.println(conn1.compare(12,23));
System.out.println("=====================");
Comparator<Integer> conn2 = Integer::compare;
System.out.println(conn2.compare(22,22));
}
情况3:类::实例方法(非静态方法){有点难度}
//Comparator中的int compare(T t1,T t2)
//String中的int t1.compareTo(t2)
@Test
public void test3(){
Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
System.out.println(com1.compare("acd","sdf"));
System.out.println("=================");
Comparator<String> com2 = String::compareTo;
System.out.println(com2.compare("acd","acd"));
}
//Bipredicate中的boolean test(T t1,T t2)
//String中的boolean t1.equals(t2)
@Test
public void test4(){
BiPredicate<String,String> pre1 = (s1,s2) -> s1.equals(s2);
System.out.println(pre1.test("abc","adx"));
System.out.println("=================");
BiPredicate<String,String> pre2 = String::equals;
System.out.println(pre2.test("abc","adx"));
}
要求:
- 接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同(针对情况1,情况2)
- 当需要引用方法的第一个参数是需要引用方法的调用对象,并且第二个参数是需要引用方法的第二个参数(或无参数)时:ClassName::methodName(针对情况3)
- 使用建议:在给函数式接口提供实例并满足使用场景时,能用就用,不熟悉就用Lambda表达式
3-3 构造器引用
要求:构造器参数列表要与函数式接口中抽象方法的参数列表一致
,抽象方法的返回值类型即为构造器所属的类的类型
格式:ClassName::new
//Supplier中的T get()
@Test
public void test5(){
Supplier<Boy> sup1 = () -> new Boy();
System.out.println(sup1.get());
System.out.println("==============");
Supplier<Boy> sup2 = Boy::new;
System.out.println(sup2.get());
}
3-4 数组引用
把数组看成一种特殊的类,则写法就与构造器引用一致
格式:type[] :: new
//Function中的R apply(T t)
@Test
public void test6(){
Function<Integer,String[]> fun1 = length -> new String[length];
String[] arr1 = fun1.apply(5);
System.out.println(Arrays.toString(arr1));
System.out.println("====================");
Function<Integer,String[]> fun2 = String[]::new;
String[] arr2 = fun1.apply(8);
System.out.println(Arrays.toString(arr2));
}
4 Stream API
4-1 介绍
Stream 是 Java8 中处理集合的关键抽象概念,对数据进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作,就类似于使用 SQL 执行的数据库查询。
简而言之,Stream API 提供了一种高效且易于使用的处理数据
的方式。
Stream关注对数据的运算
,与CPU打交道,集合关注对数据的存储
,与内存打交道
注意点:
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
4-2 使用流程
Stream的实例化 - -> 一系列中间操作(过滤,映射,归约等) - -> 终止操作
使用流程注意:
- 一个中间操作链,对数据源的数据进行处理
- 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
步骤一:Strean实例化
//方式一:通过集合
@Test
public void test1(){
List<Boy> boyes = new ArrayList<Boy>();//创建一个Boy的集合(可能这里有问题,是这个意思)
//default Stream<E> stream() : 返回一个顺序流
Stream<Boy> stream = boyes.stream();
// default Stream<E> parallelStream() : 返回一个并行流
Stream<Boy> stream1 = boyes.parallelStream();
}
//方式二:通过数组
/*
重载形式,能够处理对应基本类型的数组:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)
*/
@Test
public void test2(){
int[] arr = new int[]{1,2,3,4,5,6,7,8,9};
//调用Arrays类中的static IntStream stream(int[] array):返回一个流
IntStream stream = Arrays.stream(arr);
Boy b1 = new Boy(10, "小敏", 10000);
Boy b2 = new Boy(13, "小鹅", 4500);
Boy[] arr1 = new Boy[]{b1,b2};
Stream<Boy> stream1 = Arrays.stream(arr1);
}
//方式三:由值创建流
//通过Stream.of(),通过显示值创建一个流。它可以接收任意数量的参数。
@Test
public void test3(){
Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
}
//方式四:创建无限流
@Test
public void test4(){
//迭代
//public static <T> Stream<T> iterate(final T seed,final UnaryOperator<T> f )
//遍历前10个偶数
Stream.iterate(0,t -> t + 2).limit(10).forEach(System.out::println);
//生成
//public static <T> Stream<T> generate(Supplier<T> s )
//随机生成10个数
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
步骤二:中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为"惰性求值"
//以映射为例
@Test
public void test5(){
List<String> list = Arrays.asList("aa","vv","ddd","sss");
list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
//元素遍历(使用map)
Stream<Stream<Character>> stream = list.stream().map(StreamTest::StringToStream);
stream.forEach(s -> {
s.forEach(System.out::println);
});
//元素遍历(使用flatmap)
Stream<Character> characterStream = list.stream().flatMap(StreamTest::StringToStream);
characterStream.forEach(System.out::println);
}
//将字符串的多个字符构成的集合转换成对应的Stream的实例
public static Stream<Character> StringToStream(String str){
ArrayList<Character> list = new ArrayList<>();
for (Character c : str.toCharArray()){
list.add(c);
}
return list.stream();
}
步骤三:终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void
//以归约为例
@Test
public void test6(){java
//求1-10的和
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer reduce = list.stream().reduce(0, Integer::sum);
System.out.println(reduce);
}
5 Optional类
Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常
。
常用方法:
Optional.of(T t) : 创建一个 Optional 实例
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
isPresent() : 判断是否包含值
orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
flatMap(Function mapper):与 map 类似,要求返回值必须是Optional
例:
@Test
public void test7(){
String str = "word";
Optional<String> opl = Optional.of(str);//创建Optional实例,传入对象不能为空
String str1 = opl.get();//获取实例值,opl数据为空get()会报错
System.out.println(str1);
}
@Test
public void test8(){
String str = "word";
str = null;
Optional<String> str1 = Optional.ofNullable(str);
String str2 = str1.orElse("hello");
System.out.println(str2);
}