Lambda表达式
1. 需求分析
创建一个新的线程,指定线程要执行的任务
代码分析:
- Thread类需要一个Runnable接口作为参数,其中的抽象方法run方法是用来指定线程任务内容的核心
- 为了指定run方法体,不得不需要Runnable的实现类
- 为了省去一个Runnable的实现类,不得不使用匿名内部类
- 必须覆盖重写抽象的run方法,所有的方法名称、方法参数、方法返回值、不得不都重写一遍,而且不能出错
- 而实际上,我们只在乎方法体中的代码
2.Lambda初体验
上述代码用Lambda表达式
- Lambda表达式是一个匿名函数,可以理解为一段可以传递的代码;
- Lambda表达式的优点:简化了匿名内部类的使用,语法更加简单、
- 匿名内部类语法冗余,体验了Lambda表达式后,发现Lambda表达是简化匿名内部类的一种方式
3.Lambda表达式的语法规则
- Lambda省去了面向对象的条条框框,Lambda的标准格式由3个部分组成:
(参数类型 参数名称 ) -> {
代码体;
} - 练习1
// 定义一个接口
public interface UserService {
void show();
}
// 然后在主方法使用
public class Demo02Lambda {
public static void main(String[] args) {
goShow(new UserService() {
@Override
public void show() {
System.out.println("show 方法执行了...");
}
});
System.out.println("--------------------------------");
goShow(()->{
System.out.println("lambda方式执行了");
});
}
public static void goShow(UserService userService) {
userService.show();
}
}
输出
show 方法执行了...
--------------------------------
lambda方式执行了
- 练习2
完成一个有参且有返回值的Lambda表达式案例
// 创建一个Person类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Integer age;
private Integer height;
}
4.Lambda表达式省略写法
在Lambda表达式的标准写法基础上,可以使用省略写法规则
- 小括号内的参数类型可以省略
- 如果小括号内有且仅有一个参数,则小括号可以省略
- 如果大括号内仅有一行代码,可以同时省略大括号,return关键字及语句分号
5.Lambda表达式的使用前提
Lambda表达式的语法是非常简洁的,但是不是随便使用的,使用时有几个条件需要特别注意
- 方法的参数或局部变量类型必须为接口才能使用Lambda
- 接口中有且仅有一个抽象方法(@FunctionallInterface被这个注解修饰的接口只能有一个抽象方法)
6.Lambda和匿名内部类的对比
Lambda和匿名内部类的对比
- 所需类型不一样
->匿名内部类类型可以是类,抽象类,接口
->Lambda表达式需要的类型必须是接口 - 抽象方法的数量不一样
->匿名内部类所需的接口中的抽象方法的数量是随意的
->Lambda表达式所需的接口中只能有一个抽象方法 - 实现原理不一样
->匿名内部类是在编译后形成一个Class
->Lambda表达式是在程序运行的时候动态生成Class
接口中新增的方法
JDK8中接口的新增
在JDK8之前接口中只有静态常量和抽象方法
在JDK8之后接口做了增加,接口中可以有默认方法和静态方法
默认方法:
为什么要增加默认方法?
- 在JDK8以前接口中只能有抽象方法和静态变量,会存在以下问题
- 如果接口中新增抽象方法,那么实现类都必须要重写这个抽象方法,非常不利与接口扩展
- 默认方法实现类可以选择性的重写,解决了这个问题
格式:
interface 接口名{
public default String test(){
System.out.println("接口中的默认方法执行了");
return "hello";
}
}
静态方法
格式:
interface 接口名{
修饰符 static String test2(){
System.out.println("接口中的静态方法执行了");
return "hello";
}
}
静态方法的使用:
接口中的静态方法在实现类中是不能别重写的,调用的话只能通过接口类型来实现,接口名.静态方法名();
两者的区别
- 默认方法通过实例调用,静态方法通过接口名调用
2.默认方法可以别继承,实现类可以直接调用接口默认方法,也可以重写接口默认方法
3.静态方法不能被继承,实现类不能重写接口的静态方法,只能用接口名调用
函数式接口
介绍:
在JDK中帮我们提供的有函数式接口,主要在java.util.function包中
Supplier(用来生产数据的)
无参有返回值的接口,对应的Lambda表达式需要提供一个返回数据的类型
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
使用:
public class SupplierTest {
public static void main(String[] args) {
fun1(()->{
int arr[] = {22,33,44,55,99,88};
// 计算出数组中最大值
Arrays.sort(arr);
return arr[arr.length - 1];
});
}
private static void fun1(Supplier<Integer> supplier) {
// get() 是一个无参的有返回值的 抽象方法
Integer max = supplier.get();
System.out.println("max = "+max);
}
}
Consumer(用来消费数据的)
有参无返回值的接口,使用的时候需要指定一个泛型来定义参数类型
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
使用:
将输入的数据统一转换为小写输出
public class ConsumerTest {
public static void main(String[] args) {
test(str -> {
System.out.println(str + "转换为小写:"+str.toLowerCase());
});
}
public static void test(Consumer<String> consumer) {
consumer.accept("Hello World");
}
}
默认方法:andThen
如果一个方法的参数和返回值全部是Consumer类型,那么就可以实现效果,消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合,而这个方法就是Consumer接口中的default方法
public static void main(String[] args) {
test(str -> {
System.out.println(str + "转换为小写:"+str.toLowerCase());
},str ->{
System.out.println(str + "转换为大写:"+str.toUpperCase());
});
}
public static void test(Consumer<String> c1,Consumer<String> c2) {
String str = "Hello World";
c1.andThen(c2).accept(str); // 先转小写再转大写
}
Function
有参有返回值,Function接口是根据一个类型的数据得到另一个类型的数据,前者称之为前置条件,后者称之为后置条件,有参数有返回值
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
使用:
传入一个字符串返回一个数字
public class FunctionTest {
public static void main(String[] args) {
test(msg -> {
return Integer.parseInt(msg);
});
}
public static void test(Function<String,Integer> function) {
Integer apply = function.apply("666");
System.out.println("apply = "+apply);
}
}
Predicate
有参且返回值为布尔值,
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
使用:
public class PredicateTest {
public static void main(String[] args) {
// 如果msg长度大于三返回true,否则返回false
test(msg -> {
return msg.length() > 3;
},"HelloWorld");
}
private static void test(Predicate<String> predicate,String msg) {
boolean b = predicate.test(msg);
System.out.println("b:"+b);
}
}
Stream API
1. 集合处理数据的弊端
当我们需要对集合中的元素进行操作的时候,除了必须的添加,删除,获取外,最典型的操作就是集合遍历
public class StreamTest1 {
public static void main(String[] args) {
// 定义一个list集合
List<String> list = Arrays.asList("张三", "张三丰", "成龙", "周星驰");
List<String> list1 = new ArrayList<>();
// 获取所有姓张的信息
for (String s: list) {
if (s.startsWith("张")) {
list1.add(s);
}
}
// 获取名称长度为3的用户
List<String> list2 = new ArrayList<>();
for (String s : list1) {
if (s.length() ==3) {
list2.add(s);
}
}
// 打印到控制台
for (String s : list2) {
System.out.println(s);
}
}
}
上面的代码针对不同的需求总是一次次遍历集合,这时我们希望有更加高效的处理方式,
这时我们就可以通过Stream API来解决这个问题了
Stream解决方案:
public class StreamTest2 {
public static void main(String[] args) {
// 定义一个list集合
List<String> list = Arrays.asList("张三", "张三丰", "成龙", "周星驰");
// 获取所有姓张的信息
// 获取名称长度为3的用户
// 打印到控制台
list.stream()
.filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(System.out::println);
}
}
上面的StreamAPI代码的含义:通过流,过滤张,过滤长度,逐一打印出来
Stream流式思想概述
注意:Stream和IO流没有任何关系,Stream流式思想类似于工厂车间的生产流水线,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理,Stream可以看做流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品
Stream流的获取方式
根据Collection获取
- 首先,java.util.Collection接口中加入了default方法Stream,也就是说Collection接口下的所有的实现都可以通过Stream方法来获取Stream流
public class StreamTest3 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.stream();
Set<String> set = new HashSet<>();
set.stream();
Vector vector = new Vector();
vector.stream();
}
}
- 但是Map接口没有实现Collection接口,这时我们可以根据Map获取对应的key value的集合。
public class StreamTest4 {
public static void main(String[] args) {
Map<String,Object> map = new HashMap<>();
Stream<String> stream = map.keySet().stream(); // key
Stream<Object> stream1 = map.values().stream(); // value
Stream<Map.Entry<String, Object>> stream2 = map.entrySet().stream(); // entry
}
}
通过Stream的of方法
3. 我们在实际开发中不可避免还是会操作数组中的数据,由于数组对象不能添加默认方法,所有Stream接口中提供了静态方法of
Stream常用方法介绍
Stream流模型的操作很丰富,这里介绍一些常用的API,这些方法可以被分为两种
终结方法:返回值类型不再是Stream类型的方法,不在支持链式调用。
非终结方法:返回值类型仍然是Stream类型的方法,支持链式调用。除了终结方法外,其余都是非终结方法
Stream注意事项(重要)
- Stream只能操作一次
- Stream方法返回的是新的流
- Stream不调用终结方法,中间的操作不会执行
forEach
forEach用来遍历流中的数据的
void forEachOrdered(Consumer<? super T> action);
该方法接受一个Consumer接口,会将每一个流元素交给函数处理
public class StreamTest6 {
public static void main(String[] args) {
Stream.of("a1", "a2", "a3")
.forEach(System.out::println);
}
}
count
Stream流中的count方法用来统计其中的元素个数的
long count();
该方法返回一个long值,代表元素的个数
public class StreamTest6 {
public static void main(String[] args) {
long count = Stream.of("a1", "a2", "a3").count();
System.out.println(count);
}
}
filter
filter方法的作用是用来过滤数据的,返回符合条件的数据
Stream<T> filter(Predicate<? super T> predicate);
该接口接收一个Predicate函数式接口参数作为筛选条件
public class StreamTest7 {
public static void main(String[] args) {
Stream.of("a1", "a2", "a3").filter((s)->s.contains("a"))
.forEach(System.out::println);// 输出包含a满足条件的数据
}
}
limit
Limit方法可以对流进行截取处理,只取前n个数据
Stream<T> limit(long maxSize);
参数是一个long类型的数值,如果集合当前长度大于参数,就进行截取
// 只取前两个元素
public class StreamTest8 {
public static void main(String[] args) {
Stream.of("a1", "a2", "a3").limit(2)
.forEach(System.out::println);
}
}
skip
如果希望跳过前面几个元素,可以使用skip方法获取一个截取之后的新流
Stream<T> skip(long n);
应用:
public class StreamTest9 {
public static void main(String[] args) {
Stream.of("a1", "a2", "a3").skip(1)
.forEach(System.out::println);
}
}
map
如果我们需要将流中的元素映射到另一个流中,可以使用map方法,
该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转成另一种R类型数据
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
应用:
public class StreamTest10 {
public static void main(String[] args) {
/* 把字符串类型的数据转换成整形 */
Stream.of("1", "2", "3", "4", "5", "6", "7")
// .map(msg -> Integer.parseInt(msg))
.map(Integer::parseInt) //这里可以使用双冒号写法,更加简洁
.forEach(System.out::println);
}
}
sorted
如果需要将数据排序,可以使用sorted方法:
Stream<T> sorted();
使用:
public class StreamTest11 {
public static void main(String[] args) {
Stream.of("1", "2", "7", "4","0","5", "6", "8","3")
.map(Integer::parseInt) //先把字符串转换为整形
.sorted() // 根据数据自然顺序排序
.forEach(System.out::println);
}
}
// Comparator
public class StreamTest11 {
public static void main(String[] args) {
Stream.of("1", "2", "7", "4", "0", "5", "6", "8", "3")
.map(Integer::parseInt) //先把字符串转换为整形
// .sorted() // 根据数据自然顺序排序
.sorted((o1, o2) -> o2 - o1) // 降序
.forEach(System.out::println);
}
}
distinct
去除重复的数据
Stream<T> distinct();
使用:
public class StreamTest12 {
public static void main(String[] args) {
Stream.of("1", "1", "7", "3", "5", "8", "3")
.map(Integer::parseInt) //先把字符串转换为整形
// .sorted() // 根据数据自然顺序排序
.sorted((o1, o2) -> o2 - o1) // 降序
.distinct() //去重
.forEach(System.out::println);
}
}
reduce
将所有数据归纳得到一个数据
T reduce(T identity, BinaryOperator<T> accumulator);
使用:
public class StreamTest13 {
public static void main(String[] args) {
Integer sum = Stream.of(4, 5, 3, 9)
// identity 默认值
//第一次的时候会将默认值赋值给x
//之后每次会将上一次的操作结果赋值给x y就是每次从数据中获取的元素
.reduce(0, (x, y) -> {
System.out.println("x=" + x + ",y=" + y);
return x + y;
});
System.out.println(sum);
// 取最大值
Integer max = Stream.of(4, 5, 3, 9)
.reduce(0, (x, y) -> {
return x > y ? x : y;
});
System.out.println("最大值:"+max);
}
}
map和reduce的组合
在实际开发中我们经常会将map和reduce结合使用
使用:
public class StreamTest14 {
public static void main(String[] args) {
// 需求:求出所有年龄的总和
Integer sumAge = Stream
.of(
new Person("张三", 18),
new Person("李四", 19),
new Person("王五", 21),
new Person("赵六", 28)
)
.map(p -> p.getAge())
.reduce(0, (x, y) -> x + y);
System.out.println("年龄总和为:"+sumAge);
}
}
mapToInt
concat