1. lambda表达式
1. lambda表达式:特殊的匿名内部类,语法更加简洁。
2. lambda表达式允许把函数作为一个方法的参数(函数作为方法参数传递),将代码像数据一样传递。
3. 基本语法
1.1 如何使用lambda表达式
public class Test01 {
public static void main(String[] args) {
//该构造方法需要传递一个线程任务对象。Runnable类型
My task=new My();
Thread t1=new Thread(task);
t1.start();
//匿名内部类
Runnable task02=new Runnable() {
@Override
public void run() {
System.out.println("这时匿名内部类方式的任务对象");
}
};
Thread t2=new Thread(task02);
t2.start();
//lambda表达式
Runnable task03 = ()-> {
System.out.println("这是使用Lambda表达式完成的");
};
Thread t3=new Thread(task03);
t3.start();
}
}
class My implements Runnable{
@Override
public void run() {
System.out.println("自定义任务接口类");
}
}
Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是 用来指定线程任务内容的核心为了指定 run 的方法体,不得不需要 Runnable 接口的实现类 为了省去定义一个 Runnable 实现类的麻烦,不得不使用匿名内部 类 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回 值不得不再写一遍,且不能写错 而实际上,似乎只有方法体才是关键所在。
前提:必须是函数式接口。 简化匿名内部类的使用,语法更加简单。
1.2 练习lambda表达式
1.2.1 无参无返回值
public class Test02 {
public static void main(String[] args) {
//匿名内部类
Swimmable swimmable=new Swimmable() {
@Override
public void swimming(){
System.out.println("这时使用匿名内部类的方式");
}
};
fun(swimmable);
//lambda表达式
// Swimmable swimmable1=()->{
// System.out.println("使用lambda表达式");
// };
fun(()->{System.out.println("使用lambda表达式");});
}
public static void fun(Swimmable w){
w.swimming();
}
}
//函数式接口
interface Swimmable{
public void swimming();
}
1.2.2 练习有参数有返回值的Lambda
下面举例演示 java.util.Comparator 接口的使用场景代码,其中的抽象方 法定义为: public abstract int compare(T o1, T o2); 当需要对一个对象集合进行排序时, Collections.sort 方法需要一个 Comparator 接口实例来指定排序的规则。
public class Test01 {
public static void main(String[] args) {
List<Person> personList=new ArrayList<>();
personList.add(new Person("张三",11,177));
personList.add(new Person("李四",12,174));
personList.add(new Person("王伟",13,175));
personList.add(new Person("王五",14,179));
//对集合中的元素进行排序 按照年龄大小排序
// 传统做法 Comparator:排序规则接口
// int:0表示新加的元素和集合中的比对相同
// 1:o2比o1大
// -1:o2比o1小
// 传统做法 按照年龄排序
// Comparator<Person> comparator =new Comparator<Person>() {
// @Override
// public int compare(Person o1, Person o2) {
// return o2.getAge()- o1.getAge();
// }
// };
// Collections.sort(personList,comparator);
// for (Person p:personList) {
// System.out.println(p);
// }
// lambda表达式 按照身高排序 有参有返回值
Comparator<Person> comparator=(Person o1,Person o2)->{
return o1.getTall()-o2.getTall();
};
Collections.sort(personList,comparator);
for (Person p:personList) {
System.out.println(p);
}
}
}
class Person{
private String name;
private int age;
private int tall;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", tall=" + tall +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getTall() {
return tall;
}
public void setTall(int tall) {
this.tall = tall;
}
public Person(String name, int age, int tall) {
this.name = name;
this.age = age;
this.tall = tall;
}
}
1.2.3 详细介绍lambda表达式
2. 函数式接口
如果一个接口只有一个抽象方法,则该接口称之为函数式接口,函数式接口可以使用lambda表达式,lambda表达式会被匹配到这个抽象方法上。
@FunctionalInterface 注解检测接口是否符合函数式接口
public class Test03 {
public static void main(String[] args) {
Operater o=arr -> {
int sum=0;
for(int n:arr){
sum+=n;
}
System.out.println("数组的和为:"+sum);
};
fun(o);
}
public static void fun(Operater operater){
int[] arr={2,3,4,5,6,7,11};
operater.getSum(arr);
}
}
@FunctionalInterface
interface Operater{
//求数组的和
public abstract void getSum(int[] arr);
}
我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使 用时不关心接口名,抽象方法名,只关心抽 象方法的参数列表和返回值类 型。因此为了让我们使用Lambda方便,JDK提供了大量常用的函数式接口。
2.1 常见得函数式接口
1.Supplier接口,仅包含一个无参的方法:T get()用来获取一个泛型参数指定类型的数据。
2.Consumer接口,包含抽象方法,void accept(T t) 正好与Supplier相反,它不是生产一个数据,而是 消费一个数据,其数据类型由泛型决定。
3.Predicate接口,包含一个抽象方法:boolean test(T t)用于条件场景判断。
4.Function接口,用来 根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
2.1.1 Consumer 有参数,无返回值。
public class Test {
public static void main(String[] args) {
Consumer<Double> c= t->{
System.out.println("洗脚花费了:"+t);
};
fun01(c,200);
}
//调用某个方法时,该方法需要的参数为接口类型,这时就应该
能想到使用lambda
public static void fun01(Consumer<Double>
consumer,double money){
consumer.accept(money);
}
}
2.2.2 Supplier供给型函数式接口
T:表示返回结果的泛型 无参,想有返回结果的函数式接口时 T get();
public class Test02 {
public static void main(String[] args) {
//Supplier<Integer> supplier=()->new
Random().nextInt(10);
fun(()->new Random().nextInt(10));
}
public static void fun(Supplier<Integer> supplier){
Integer result = supplier.get();
System.out.println("内容为:"+result);
}
}
2.2.3 Function 函数型函数式接口
T: 参数类型的泛型 R: 函数返回结果的泛型
有参,有返回 值时。 例子: 传入一个字符串把小写转换为大写。
public class Test03 {
public static void main(String[] args) {
fun((t)->{
return t.toUpperCase();
},"hello world");
}
//传入一个字符串,返回字符串的长度。
public static void fun(Function<String,String>
function,String msg){
String s = function.apply(msg);
System.out.println("结果为:"+s);
}
}
2.2.4 Predicate接口
有参返回值为boolean,Predicate<T>
包含一个抽象方法:boolean test(T t)用于条件场景判断。
public class Test05 {
// 断言型接口
public static void main(String[] args) {
fun(n->{
return n.length()>3?true:false;
},"诸葛孔明");
}
public static void fun(Predicate<String> predicate,String name){
boolean b=predicate.test(name);
System.out.println("长名字"+b);
}
}
2.2 方法引用
我们用Lambda表达式来实现==匿名方法==。但有些情况下,我们用Lambda表达式仅仅是调用==一些已经存在的方法,除了调用方法外,没有其他任何多余的动作==,在这种情况下,我们倾向于通过方法名来调用它,而Lambda表达式可以帮助我们实现这一要求,它使得Lambda在调用那些已经拥有方法名的方法的代码更简洁、更容易理解。方法引用可以理解为Lambda表达式的另外一种表现形式。
类型 | 语法 | 对应的Lambda表达式 |
---|---|---|
静态方法引用 | 类名::staticMethod | (args) -> 类名.staticMethod(args) |
实例方法引用 | inst::instMethod | (args) -> inst.instMethod(args) |
对象方法引用 | 类名::instMethod | (inst,args) -> inst.instMethod(args) |
构建方法引用 | 类名::new | (args) -> new 类名(args) |
2.2.1 静态方法引用
有一个Person类,如下所示:
@Data
public class Person {
private String name;
private Integer age;
//比较两个Person对象的年龄是否一致。
public static int compareByAge(Person a, Person b) {
return a.age.compareTo(b.age);
}
}
现假设,一个部门有10人,把他们存放在一个数组中,并按年龄排序,通常我们可以自己写一个比较器,代码如下:
Person[] rosterAsArray = new Person[10];
// 添加数组元素省略
class PersonAgeComparator implements Comparator<Person> {
public int compare(Person a, Person b) {
return a.getAge().compareTo(b.getAge());
}
}
Arrays.sort(rosterAsArray, new PersonAgeComparator());
Arrays.sort的声明为:public static <T> void sort(T[] a, Comparator<? super T> c),比较器参数Comparator为一个函数式接口,利用上一节Lambda表达式所学知识,可以改写为以下代码:
Person[] rosterAsArray = new Person[10];
// 添加数组元素省略
Arrays.sort(rosterAsArray, (a,b) -> a.getAge().compareTo(b.getAge()));
然而,你会发现,Person类中已经有了一个静态方法的比较器:compareByAge,因此,我们改用Person类已经提供的比较器:
Person[] rosterAsArray = new Person[10];
// 添加数组元素省略
Arrays.sort(rosterAsArray, (a,b) -> Person.compareByAge(a,b));
以上代码,因为Lambda表达式调用了一个已经存在的静态方法,根据我们第2节表格中的语法,上面的代码可以最终改写成静态方法引用:
Person[] rosterAsArray = new Person[30];
// 添加数组元素省略
Arrays.sort(rosterAsArray, Person::compareByAge);
public class Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(82,22,34,50,9);
list.sort(Integer::compare);
System.out.println(list);
}
}
对一个Integer列表进行排序,因为Integer中已经存在静态的比较方法compare(),因此可以直接用静态方法引用的方式来调用 ,运行结果为:
[9, 22, 34, 50, 82]
2.2.2 实例方法引用
实例方法引用,顾名思义就是调用已经存在的实例的方法,与静态方法引用不同的是类要先实例化,静态方法引用类无需实例化,直接用类名去调用。
@Data
class User {
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
}
public class TestInstanceReference {
public static void main(String[] args) {
TestInstanceReference test = new TestInstanceReference();
User user = new User("欧阳峰",32);
Supplier<String> supplier = () -> user.getName();
System.out.println("Lambda表达式输出结果:" + supplier.get());
Supplier<String> supplier2 = user::getName;
System.out.println("实例方法引用输出结果:" + supplier2.get());
}
}
输出结果: Lambda表达式输出结果:欧阳峰
实例方法引用输出结果:欧阳峰
2.2.3 对象方法引用
若Lambda参数列表中的第一个参数是实例方法的参数调用者,而第二个参数是实例方法的参数时,可以使用对象方法引用。
String的equals()方法:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
public static void main(String[] args) {
BiPredicate<String,String> bp = (x, y) -> x.equals(y);
BiPredicate<String,String> bp1 = String::equals;
boolean test = bp1.test("xy", "xx");
System.out.println(test);
}
代表了一个两个参数的boolean
值方法
BiPredicate的test()方法接受两个参数,x和y,具体实现为x.equals(y),满足Lambda参数列表中的第一个参数是实例方法的参数调用者,而第二个参数是实例方法的参数,因此可以使用对象方法引用。
2.2.4 构造方法引用
注意:需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致。
如:要获取一个空的User列表:
Supplier<List<User>> userSupplier = () -> new ArrayList<>();
List<User> user = userSupplier.get();
Supplier<List<User>> userSupplier2 = ArrayList<User>::new; // 构造方法引用写法
List<User> user2 = userSupplier.get();
3. Stream流
Java8的两个重大改变,一个是Lambda表达式,另一个就是本节要讲的Stream API表达式。==Stream 是Java8中处理集合的关键抽象概念==,它可以对集合进行非常复杂的查找、过滤、筛选等操作.
3.1 为什么使用Stream流
当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。我们来体验 集合操作数据的弊端,需求如下:
一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰,何线程
需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
代码如下
public class My {
public static void main(String[] args) {
// 一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰
// 需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");
// 1.拿到所有姓张的
ArrayList<String> zhangList = new ArrayList<>(); // {"张无忌", "张强", "张三丰"}
for (String name : list) {
if (name.startsWith("张")) {
zhangList.add(name);
}
}
// 2.拿到名字长度为3个字的
ArrayList<String> threeList = new ArrayList<>(); // {"张无忌", "张三丰"}
for (String name : zhangList) {
if (name.length() == 3) {
threeList.add(name);
}
}
// 3.打印这些数据
for (String name : threeList) {
System.out.println(name);
}
}
}
分析:
这段代码中含有三个循环,每一个作用不同:
首先筛选所有姓张的人;
然后筛选名字有三个字的人;
最后进行对结果进行打印输出。
每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循环 是做事情的方式,而不是目的。每个需求都要循环一次,还要搞一个新集合来装数据,如果希望再次遍历,只能再使 用另一个循环从头开始。
那Stream能给我们带来怎样更加优雅的写法呢?
Stream的更优写法
public class Test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰","何线程");
list.stream()
.filter(item->item.startsWith("张"))
.filter(item->item.length()==3)
.forEach(item-> System.out.println(item));
}
}
对集合的操作语法简洁:性能比传统快。
3.2 Stream流的原理
注意:Stream和IO流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统IO流的固有印象!
Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种==数据结构==,==不保存数据==,而是对数据进行==加工 处理==。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。
Stream不存在数据,只对数据进行加工处理。
3.3 如何获取Stream流对象
public class Test02 {
public static void main(String[] args) {
//通过集合对象调用stream()获取流
List<String> list=new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("王伟");
Stream<String> stream = list.stream();
//通过Arrays数组工具类获取Stream对象
int[] arr={3,4,5,63,2,34};
IntStream stream1 = Arrays.stream(arr);
//使用Stream类中of方法
Stream<String> stream2 = Stream.of("hello", "world", "spring", "java");
//LongStream
LongStream range = LongStream.range(1, 10);
//上面都是获取的串行流。还可以获取并行流。如果流中的数据量足够大,并行流可以加快处速度。
Stream<String> stringStream = list.parallelStream();
//
// stringStream.forEach(item-> System.out.println(item));
stringStream.forEach(System.out::println);
}
}
3.4 Stream流中常见的api
中间操作api: 一个操作的中间链,对数据源的数据进行操作。而这种操作的返回类型还是一个Stream对象。
终止操作api: 一个终止操作,执行中间操作链,并产生结果,返回类型不在是Stream流对象。
3.4.1 中间操作api
Stream中间操作--筛选与切片
-
filter:接收Lambda,从流中排除某些操作;
-
limit:截断流,使其元素不超过给定对象
-
skip(n):跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
-
distinct:筛选,通过流所生成元素的hashCode()和equals()去除重复元素。
Stream中间操作--映射
-
map--接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
-
flatMap--接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
Stream中间操作--排序
-
sorted()--自然排序(Comparable)
-
sorted(Comparator com)--定制排序(Comparator)
3.4.2 终止操作api
-
allMatch--检查是否匹配所有元素
-
anyMatch--检查是否至少匹配一个元素
-
noneMatch--检查是否没有匹配所有元素
-
findFirst--返回第一个元素
-
findAny--返回当前流中的任意元素
-
count--返回流中元素的总个数
-
max--返回流中最大值
-
min--返回流中最小值
这些方面在Stream类中都有说明,这里不一一举例,只对allMatch、max各举一例进行说明。
综合案例
public class Test02 {
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'));
//findFirst match
// Optional<Person> first = personList.parallelStream().filter(item -> item.getSex() == 'F').findAny();
boolean b = personList.parallelStream().filter(item -> item.getSex() == 'F').noneMatch(item -> item.getAge() >= 20);
System.out.println(b);
//搜集方法 collect 它属于终止方法
//年龄大于20且性别为M
// List<Person> collect = personList.stream()
// .filter(item -> item.getAge() > 20)
// .filter(item -> item.getSex() == 'M')
// .collect(Collectors.toList());
//
// System.out.println(collect);
//求集合中所有人的年龄和。参数和返回类型必须一致
// Integer reduce = personList.stream()
// .map(item -> item.getAge())
// .reduce(10, (a, b) -> a + b);
// System.out.println(reduce);
//[18,24,22,20,22,20,21]--->(18,24)->18+24;--->(42,22)->42+22--->(64,20)->64+20
// //查找最大年龄的人. max终止操作
// Optional<Person> max = personList.stream().max((o1, o2) -> o1.getAge() - o2.getAge());
// System.out.println(max.get());
// //对流中元素排序
// personList.stream()
// .sorted((o1,o2)->o1.getAge()-o2.getAge())
// .forEach(System.out::println);
//集合中每个元素只要名.map--->原来流中每个元素转换为另一种格式。
// personList.stream()
// .map(item->{
// Map<String,Object> m=new HashMap<>();
// m.put("name",item.getName());
// m.put("age",item.getAge());
// return m;
// })
// .forEach(System.out::println);
// 1)找到年龄大于18岁的人并输出;filter()过滤器需要一个断言接口函数,断言接口返回true,获取该元素。 foreach(Consumer)
//无论执行多少个中间操作,如果没有执行终止操作,那么中间操作都不会被执行。
// personList.stream()
// .filter(item->item.getAge()>18).forEach(System.out::println);
2)找出所有中国人的数量。--->filter过滤掉其他国家的人 count()终止操作
// long count = personList.stream().filter(item -> item.getCountry().equals("中国")).count();
// System.out.println(count);
}
}
class Person {
private String name;
private Integer age;
private String country;
private char sex;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", country='" + country + '\'' +
", sex=" + sex +
'}';
}
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;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public Person(String name, Integer age, String country, char sex) {
this.name = name;
this.age = age;
this.country = country;
this.sex = sex;
}
}
3.5 规约
规约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。
3.5.1 规约 reduce
//求集合中所有人的年龄和。
Optional<Integer> reduce = personList.stream()
.map(item -> item.getAge())
.reduce((a, b) -> a + b);
System.out.println(reduce.get());
Integer reduce = personList.stream()
.map(item -> item.getAge())
.reduce(10, (a, b) -> a + b);
System.out.println(reduce);
3.5.2 collect搜集 match find
//findFirst match
// Optional<Person> first = personList.parallelStream().filter(item -> item.getSex() == 'F').findAny();
boolean b = personList.parallelStream().filter(item -> item.getSex() == 'F').noneMatch(item -> item.getAge() >= 20);
System.out.println(b);
//搜集方法 collect 它属于终止方法
//年龄大于20且性别为M
// List<Person> collect = personList.stream()
// .filter(item -> item.getAge() > 20)
// .filter(item -> item.getSex() == 'M')
// .collect(Collectors.toList());
//
// System.out.println(collect);
4. 新增了日期时间类
旧的日期时间的缺点:
-
设计比较乱: Date日期在java.util和java.sql也有,而且它的时间格式转换类在java.text包。
-
线程不安全。
新增加了哪些类?
LocalDate: 表示日期类。yyyy-MM-dd
LocalTime: 表示时间类。 HH:mm:ss
LocalDateTime: 表示日期时间类 yyyy-MM-dd t HH:mm:ss sss
DatetimeFormatter:日期时间格式转换类。
Instant: 时间戳类。
Duration: 用于计算两个日期类
综合案例:
public class Test {
public static void main(String[] args) {
LocalDate now = LocalDate.now(); //获取当前日期
LocalDate date = LocalDate.of(2022, 8, 23);//指定日期
LocalTime now1 = LocalTime.now();//当前时间
LocalTime of = LocalTime.of(17, 30, 20, 600);
LocalDateTime now2 = LocalDateTime.now();//获取当前日期时间
LocalDateTime now3 = LocalDateTime.of(2022,6,20,17,45,20);
Duration between = Duration.between(now2, now3);
System.out.println(between.toHours());
DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate parse = LocalDate.parse("1999-12-12", dateTimeFormatter);//把字符串转换为日期格式
String format = parse.format(dateTimeFormatter);
}
}