1.Lambda表达式
1.1 lambda的由来
public class Test01 {
public static void main(String[] args) {
//开启一个线程 该构造函数需要传递一个Runnable类型的接口参数
Thread thread = new Thread(new My());
thread.start();//开启线程
//匿名内部类
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("通过匿名内部类实现线程任务");
}
});
thread1.start();//开启线程
}
}
class My implements Runnable{
@Override
public void run() {
System.out.println("使用实现类来完成------->线程任务");
}
}
分析:
- Thread类需要一个Runnable接口作为参数,其中的抽象方法run方法是用来指定线程任务内容的核心
- 为了指定run方法体,不得不需要Runnable的实现类
- 为了省去定义一个Runnable 的实现类,不得不使用匿名内部类
- 必须覆盖重写抽象的run方法,所有的方法名称,方法参数,方法返回值不得不都重写一遍,而且不能出错,
- 而实际上,我们只在乎方法体中的代码.
我们可以使用lambda表达式来完成上面的功能。
1.2 初体验lambda表达式。
//lambda表达式.
Runnable runnable1=()->{
System.out.println(“这是lambda表达式完成线程任务”);
};
Thread thread2=new Thread(runnable1);
thread2.start();
1.3 lambda表达式的语法
Lambda省去了面向对象的条条框框,Lambda的标准格式由3个部分组成:
(参数列表)->{}
():参数列表
->:连接符 连接的是参数以及方法体。
{}: 方法体。
1.4 练习无参无返回值的Lambda
public class Test02 {
public static void main(String[] args) {
//主函数调用fun方法。第一种:创建UserService接口的实现类,并创建该实现类对象。
//第二种: 匿名内部类的方式
// UserService userService=new UserService() {
// @Override
// public void show() {
// System.out.println("这是匿名内部类的show方法的实现");
// }
// };
// fun(userService);
//第三种lambda表达式:--该接口必须为函数式接口
UserService userService=()->{
System.out.println("lambda表示的show方法");
};
fun(userService);
}
public static void fun(UserService userService){//UserService userService=
userService.show();
}
}
//函数式接口-->里面有且仅有一个抽象方法。--只有这种接口才能使用lambda表达式。
interface UserService{
public void show();
}
1.5 有参有返回值。
public class Test03 {
public static void main(String[] args) {
List<Person> personList=new ArrayList<>();
personList.add(new Person("张三",15));
personList.add(new Person("李四",5));
personList.add(new Person("王五",65));
personList.add(new Person("赵六",45));
personList.add(new Person("田七",35));
//对集合中的元素按照年龄排序。小到大
System.out.println(personList);
//Collections:集合工具类。--匿名内部类
// Comparator<Person> comparator=new Comparator<Person>() {
// @Override //如果是0表示相同 大于0表示o1大于02
// public int compare(Person o1, Person o2) {
// return o1.getAge()-o2.getAge();
// }
// };
// Collections.sort(personList,comparator);
// System.out.println(personList);
Comparator<Person> comparator=(o1,o2)->{
//就是对函数式接口中抽象方法的简写。
return o1.getAge()-o2.getAge();
};
Collections.sort(personList,comparator);
System.out.println(personList);
}
}
class Person{
private String name;
private Integer age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
1.6 Lambda表达式的省略写法
在lambda表达式的标准写法基础上,可以使用省略写法的规则为:
- 小括号内的参数类型可以省略[]
- 如果小括号内有且仅有一个参数,则小括号可以省略
- 如果大括号内有且仅有一个语句,可以同时省略大括号,return 关键字及语句分号。
public class Test05 {
public static void main(String[] args) {
USB u=(str)-> str.toUpperCase();//lambda表达式:
// USB u=new USB() {//匿名内部类对接口中抽象方法的个数没有任何要求.
// @Override
// public String toUpper(String str) {
// return null;
// }
//
// @Override
// public String toLower(String str) {
// return null;
// }
// };
fun(u);
}
public static void fun(USB usb){
String s = usb.toUpper("hello");
System.out.println(s);
}
}
interface USB{
public String toUpper(String str);
// public String toLower(String str);
}
1.7 Lambda表达式使用的前提
Lambda表达式的语法是非常简洁的,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意
- 方法的参数或局部变量类型必须为接口才能使用Lambda
- 接口中有且仅有一个抽象方法(@FunctionalInterface)
后面我们使用lambda表达式主要核心使用再Stream流中。
2.内置函数式接口
要想使用lambda表达式它的前提就是必须是函数式接口。
2.1 内置函数式接口的由来
public class Test {
public static void main(String[] args) {
Operation o=arr->{
int sum=0;
for(int s:arr){
sum+=s;
}
return sum;
};
fun(o);
}
public static void fun(Operation operation){
int[] arr={1,2,3,4};
int s = operation.getSum(arr);
System.out.println("数组的和:"+s);
}
}
@FunctionalInterface
interface Operation{
public int getSum(int[] arr);
}
分析:
我们知道使用Lambda表达式的前提是需要有函数式接口,而Lambda表达式使用时不关心接口名,抽象方法名。只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda表达式更加的方便,在JDK中提供了大量常用的函数式接口. 大多数无需自己再定义函数式接口,而可以直接使用jdk内置的函数式接口。分成四类。
2.2 消费型函数式接口Consumer
适合有参数,但是没有返回值的。
public class Test06 {
public static void main(String[] args) {
Consumer<Double> c=t->{
System.out.println("今天洗脚花费:"+t+"元");
};
fun(c,200.0);
}
public static void fun(Consumer<Double> consumer,Double money){
consumer.accept(money);
}
}
2.3 供给型函数式接口—Supplier
无参,需要返回值的接口类。
public class Test06 {
public static void main(String[] args) {
// Consumer<Double> c=t->{
// System.out.println("今天洗脚花费:"+t+"元");
// };
// fun(c,200.0);
Supplier<Integer> s=()-> new Random().nextInt(10);
fun2(s);
}
public static void fun2(Supplier<Integer> supplier){
Integer a = supplier.get();
System.out.println("结果:"+a);
}
public static void fun(Consumer<Double> consumer,Double money){
consumer.accept(money);
}
}
2.3 函数型函数式接口—Function<T,R>
T: 参数的泛型
R:返回值的泛型。
public class Test {
public static void main(String[] args) {
Function<int[],Integer> o=arr->{
int sum=0;
for(int s:arr){
sum+=s;
}
return sum;
};
fun(o);
}
public static void fun(Function<int[],Integer> fun){
int[] arr={1,2,3,4};
int s = fun.apply(arr);
System.out.println("数组的和:"+s);
}
}
2.4 断言型函数式接口–Predicate
T: 参数
boolean:返回值类型。
public class Test06 {
public static void main(String[] args) {
// Consumer<Double> c=t->{
// System.out.println("今天洗脚花费:"+t+"元");
// };
// fun(c,200.0);
// Supplier<Integer> s=()-> new Random().nextInt(10);
// fun2(s);
// Function<String,String> fun=t->t.toUpperCase();
// fun3(fun,"hello world");
Predicate<String> p=t->t.length()>3;
fun4(p,"欧阳锋"); //4
}
public static void fun4(Predicate<String> predicate,String name){
boolean test = predicate.test(name);
System.out.println("是否成年:"+test);
}
public static void fun3(Function<String,String> f,String str){
String apply = f.apply(str);
System.out.println("结果:"+apply);
}
public static void fun2(Supplier<Integer> supplier){
Integer a = supplier.get();
System.out.println("结果:"+a);
}
public static void fun(Consumer<Double> consumer,Double money){
consumer.accept(money);
}
}
3.方法引用
特殊的lambda表达式,它是对lambda表达式的一种简写方式。
3.1 方法引用的由来
public class Test07 {
public static void main(String[] args) {
Consumer<int[]> c=t->{
int sum=0;
for(int a:t){
sum+=a;
}
System.out.println("数组的和:"+sum);
};
fun(c);
}
public static void fun(Consumer<int[]> consumer){
int[] arr={1,2,3,4,5};
consumer.accept(arr);
}
//求和方法
public static void sum(int[] arr){
int sum=0;
for(int a:arr){
sum+=a;
}
System.out.println("数组的和:"+sum);
}
}
如果我们在Lambda中所指定的功能,已经有其他方法存在相同方案,那是否还有必要再写重复逻辑?可以直接“引 用”过去就好了:—方法引用。::
public class Test07 {
public static void main(String[] args) {
// Consumer<int[]> c=t->{
// int sum=0;
// for(int a:t){
// sum+=a;
// }
// System.out.println("数组的和:"+sum);
// };
// Consumer<int[]> c=(t)->Test07.sum(t); //
Consumer<int[]> c= Test07::sum;
fun(c);
}
public static void fun(Consumer<int[]> consumer){
int[] arr={1,2,3,4,5};
consumer.accept(arr);
}
//求和方法
public static void sum(int[] arr){
int sum=0;
for(int a:arr){
sum+=a;
}
System.out.println("数组的和:"+sum);
}
}
3.2 方法引用的类型
方法引用是lambda表达式的一种简写形式。如果lambda表达式中只调用一个特定的已经存在的方法,则可以使用方法引用。
3.3 静态方法引用
(args)->类名.静态方法(args). 当lambda表达式中方法体,只有一条语句,而这条语句是类名.静态方法。而静态方法的参数和lambda的参数一致时。
类名::静态方法;
3.4 实例方法引用
(args) -> inst.instMethod(args)
实例方法引用,顾名思义就是调用已经存在的实例的方法,与静态方法引用不同的是类要先实例化,静态方法引用类无需实例化,直接用类名去调用。
public class Test09 {
public static void main(String[] args) {
//创建一个类对象
Student s=new Student("赵志强",17);
//通过内置的函数接口,返回对象的名称。
// Supplier<String> supplier=()->s.getName();
Supplier<String> supplier=s::getName;
String s1 = supplier.get();
System.out.println(s1);
//观察: lambda表达式中有且仅有一条语句,方法调用调用语句。 ---实例方法引用特点: ()->对象.普通方法();
Function<Student,String> f=(t)->t.getName();
String apply = f.apply(s);
System.out.println(apply); //这里就无法使用方法引用。
}
}
class Student{
private String name;
private Integer age;//实体类中不能出现基本类型
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
3.5 对象方法引用
lambda: (inst,args)->inst.普通方法(args): -------->类名::普通方法
若Lambda参数列表中的第一个参数是实例方法的参数调用者,而第二个参数是实例方法的参数时,可以使用对象方法引用。
public class Test10 {
public static void main(String[] args) {
//判断两个字符串是否一致。
// BiFunction<String,String,Boolean> biFunction=(t1,t2)->t1.equals(t2);
// Boolean apply = biFunction.apply("hello", "hello");
// System.out.println(apply);
//观察:符合对象方法引用.----代码---进入公司---git拉取代码【看】---看懂
BiFunction<String,String,Boolean> biFunction=String::equals;
}
public static void show(BiFunction<String,String,Boolean> biFunction){
Boolean apply = biFunction.apply("hello", "hello");
System.out.println(apply);
}
}
3.6 构造方法引用
(args) -> new 类名(args)------构造方法引用: 类名::new
public class Test11 {
public static void main(String[] args) {
// Supplier<Student> supplier=()->new Student();
// Student student = supplier.get();
// System.out.println(student);
//观察:调用的构造函数.
// Supplier<Student> supplier=Student::new;
// Student student = supplier.get();
// System.out.println(student);
// BiFunction<String,Integer,Student> biFunction=(n,a)->new Student(n,a);
BiFunction<String,Integer,Student> biFunction=Student::new;
Student student = biFunction.apply("霍梦琪", 25);
System.out.println(student);
}
}
总结:
静态方法引用 类名::静态方法 lambda表达式: (参数)->类名.静态方法(参数)
实例方法引用:对象::实例方法 lambda表达式: (参数)->对象.实例方法(参数)
对象方法引用:类名::实例方法 lambda表达式: (参数1,参数2…)->参数1.实例方法(参数2…)
构造方法引用: 类名::new lambda表达式: (参数)->new 类名(参数);
4.Stream
Java8的两个重大改变,一个是Lambda表达式,另一个就是本节要讲的Stream API表达式。Stream 是Java8中处理集合的关键抽象概念,它可以对集合进行非常复杂的查找、过滤、筛选等操作.
4.1 为什么使用stream流
当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。我们来体验 集合操作数据的弊端,需求如下:
一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,成俊杰,张三丰
需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
public class Test {
public static void main(String[] args) {
// 一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,成俊杰,张三丰
// 需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
List<String> list=new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("程俊杰");
list.add("张三丰");
//1.拿到所有姓张的
List<String> newList01=new ArrayList<>();
for(String n:list){
if(n.startsWith("张")){
newList01.add(n);
}
}
//2.拿到名字长度为3个字的
List<String> newList02=new ArrayList<>();
for(String n:newList01){
if(n.length()==3){
newList02.add(n);
}
}
//3.打印这些数据
for(String s:newList02){
System.out.println(s);
}
}
}
分析:
循环遍历的弊端
这段代码中含有三个循环,每一个作用不同:
- 首先筛选所有姓张的人;
- 然后筛选名字有三个字的人;
- 最后进行对结果进行打印输出。
每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循环 是做事情的方式,而不是目的。每个需求都要循环一次,还要搞一个新集合来装数据,如果希望再次遍历,只能再使 用另一个循环从头开始。
那Stream能给我们带来怎样更加优雅的写法呢?
Stream初体验
List<String> list=new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张三");
list.add("程俊杰");
list.add("张三丰");
//
list.stream().filter(t->t.startsWith("张")).filter(t->t.length()==3).forEach(item-> System.out.println(item));
4.2 Stream流的原理
Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工 处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。
4.3 步骤
(1)获取Stream流对象
(2) 中间操作—返回类型还是Stream流对象。
(3)终止操作—不在是Stream流对象
4.4 获取Stream流对象的方式
(1) 通过集合对象调用stream()
(2)通过Arrays获取stream流对象
(3)通过Stream流里面of方法
public class Test11 {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
// Stream<String> stream = list.stream();
// stream.forEach(System.out::println);
// //第二种使用Arrays工具类
// String[] arr={};
// Stream<String> stream1 = Arrays.stream(arr);
//
// //第三种:Stream类
// Stream<Integer> stream2 = Stream.of(1, 2, 4, 7, 8);
//上面的流都是串行流。并行流
Stream<String> stringStream = list.parallelStream();
stringStream.forEach(System.out::println);
}
}
4.5 Stream流的api方法
举个简单的例子:
假设有一个Person类和一个Person列表,现在有两个需求:1)找到年龄大于18岁的人并输出;2)找出所有中国人的数量。
class Person {
private String name;
private Integer age;
private String country;
private char sex;
public Person(String name, Integer age, String country, char sex) {
this.name = name;
this.age = age;
this.country = country;
this.sex = sex;
}
}
public class Test {
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
personList.add(new Person("欧阳雪",18,"中国",'F'));
personList.add(new Person("Tom",24,"美国",'M'));
personList.add(new Person("Harley",22,"英国",'F'));
personList.add(new Person("向天笑",20,"中国",'M'));
personList.add(new Person("李康",22,"中国",'M'));
personList.add(new Person("小梅",20,"中国",'F'));
personList.add(new Person("何雪",21,"中国",'F'));
personList.add(new Person("李康",22,"中国",'M'));
//1. 年龄大于18 filter:过滤掉不满足条件的元素. forEach:输出元素. ---如果没有终止函数,那么中间函数的代码不会被执行。
personList.stream(). filter(item->{
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~");
return item.getAge()>18;
}).forEach(System.out::println);
//2. 找出中国人 并统计个数: count()
long count = personList.stream().filter(item -> item.getCountry().equals("中国")).count();
System.out.println("中国人:"+count);
}
}
2)找出年龄最大和最小
Person person = personList.stream().max(((o1, o2) -> o1.getAge() - o2.getAge())).get();
Person person2 = personList.stream().min(((o1, o2) -> o1.getAge() - o2.getAge())).get();
System.out.println(person);
System.out.println(person2);
(3)map–>
会把集合中的元素转化成另一种类型
personList.stream().filter(item->item.getCountry().equals("中国")).map(item->new P(item.getName(),item.getAge())).forEach(System.out::println);
public class Test03 {
public static void main(String[] args) {
// 整数数组每个元素+3
List<Integer> list = Arrays.asList(1, 17, 27, 7);
list.stream().map(item->item+3).forEach(System.out::println);
List<String> list2=Arrays.asList("hello","world","java","spring","springmvc");
//字符串大写
list2.stream().map(String::toUpperCase).forEach(System.out::println);
}
}
(4)收集 collect
把处理过的集合搜集成新的集合。
List<Person> personList = new ArrayList<>();
personList.add(new Person("小梅",24,"中国",'F'));
personList.add(new Person("欧阳雪",18,"中国",'F'));
personList.add(new Person("Tom",24,"美国",'M'));
personList.add(new Person("Harley",22,"英国",'F'));
personList.add(new Person("向天笑",20,"中国",'M'));
personList.add(new Person("李康",22,"中国",'M'));
personList.add(new Person("Tom",21,"中国",'F'));
personList.add(new Person("李康",22,"中国",'M'));
//把Person-年龄大于20人--里面名称----新的集合。
List<String> collect = personList.stream().filter(item -> item.getAge() > 20).map(item -> item.getName()).collect(Collectors.toList());
System.out.println(collect);
(5)sorted排序
List<Person> collect = personList.stream().sorted((o1, o2) -> o1.getAge() - o2.getAge()).collect(Collectors.toList());
System.out.println(collect);
(6) reduce规约
归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。
整型集合: -----请求。[1,2,3,4]=
List<Integer> list= Arrays.asList(1,2,3,5);Optional<Integer> reduce = list.stream().reduce((t1, t2) -> t1 * t2);//t1=1, t2=2 ===>2//t1=2 t2=3===>6//t1=6 t2=5====>30System.out.println(reduce.get());
(7)查询第一个findFirst
Optional<Person> first = personList.stream().filter(item->item.getAge()>=18&&item.getAge()<=20).findFirst();
System.out.println(first.get());
- 中间的操作: filter map sorted distinct() skip limit()
- 终止操作: forEach count() reduce() collect(Collectors.toList()) findFirst
max min