Lambda表达式

语法:

(param)-> expression 或 (param)->{statements;}

特征:

  1. 可选类型声明:不需要声明参数类型,编译器可统一识别参数值;
  2. 可选的参数圆括号:一个参数无需定义(),但多个参数需要定义;
  3. 可选的大括号:如果主体包含了一个语句,就不需要大括号;
  4. 可选的返回关键字:如果主体只有一个表达式单回值则编译器会自动返回值,大括号需要指定表达式返回一个数值。

@FunctionalInterface

****修饰函数式接口的注解,要求接口中抽象方法只有一个,和lambda表达式结合使用。

使用:

  1. 创建函数式接口并使用**@FunctionalInterface**注解;
  2. 在方法中引用该注解使用;
/**
 * @ProjectName: ssm-framework
 * @Package: learn.com.jdk.lambda
 * @ClassName: ReturnMultiParam
 * @Author: zd
 * @Description: 多参有返回
 * @Date: 2021/9/16 10:46
 * @Version: 1.0
 */
@FunctionalInterface
public interface ReturnMultiParam {
    String method(int a, String b);
}
 
public static void main(String[] args) {
    //多参有返回
        ReturnMultiParam returnMultiParam = (a,b) -> a+b;
        System.out.println("ReturnMultiParam:"+returnMultiParam.method(2, "3"));
}

Stream流

特性

  1. Stream流不是一种数据接口,不保存数据,只是在原数据集上定义了一组操作。
  2. 这些操作每当访问到流中的一个元素,才会在层次元素上执行这一组操作。
  3. Sream不保存数据,故每个Stream流只可使用一次。
  4. 流的操作分为两种:中间操作和终止操作;

中间操作的返回结果都是Stream流,故可以多个中间操作叠加;

终端操作用于返回一个新的集合或值,只能有一个终端操作。

Stream流接口的继承关系:

画板

流的Intermediate方法(中间操作)

filter(T boolean)

过滤数据,保留 boolean 为 true 的元素,返回一个集合;

@Test
public void demo3(){
    List<String> list =Arrays.asList("a","b","c","ab","ba","abc");
    list = list.stream().filter(s -> s.contains("a")).collect(Collectors.toList());
    System.out.println(list.toString());
}

map(T R)

转换操作符,可以做数据转换,比如:把字符串转换成int、long、double,或者把一个实体转换成另外一个实体。包含:map,mapToInt、mapToLong、mapToDouble

@Test
public void demo4(){
    List<String> list =Arrays.asList("1","2","3");
    List<Integer> listInt = new ArrayList<>();
    listInt = list.stream().map(s -> Integer.parseInt(s)).collect(Collectors.toList());
    System.out.println(listInt.toString());
 
    list.stream().mapToInt(str ->str.length()).forEach(System.out::println);
}

flatMap(T Stream)

将流中的每一个元素 T 映射为一个流,再把每一个流连接成为一个流

@Test
public void demo5(){
    List<List<String>> list = new ArrayList<>();
    list.add(Lists.newArrayList("a","b","c"));
    list.add(Lists.newArrayList("d","e","f"));
    List<String> list1 = list.stream().flatMap(List::stream).collect(Collectors.toList());
    System.out.println(list1.toString());
}

distinct

去重,类似于msql中的distinct的作用,底层使用了equals方法做比较。

 @Test
public void demo6(){
    List<String> list = Arrays.asList("a","a","b","c","b");
    list=list.stream().distinct().collect(Collectors.toList());
    System.out.println(list.toString());
}

sorted

对元素进行排序,前提是比较集合中的对象实现Comparable接口重写compareTo方法,或在流中自定义比较器。

@Test
public void demo7(){
    List<String> list =Arrays.asList("1","5","3","2");
    list = list.stream().sorted().collect(Collectors.toList());
    System.out.println(list.toString());
    List<String> stringList = Arrays.asList("zd","zc","zb","za");
    stringList = stringList.stream().sorted().collect(Collectors.toList());
    System.out.println(stringList.toString());
    List<User> listUser = new ArrayList<>();
    listUser.add(new User().setId(2).setName("张四").setAddress("西安"));
    listUser.add(new User().setId(1).setName("张三").setAddress("西安"));
    listUser.add(new User().setId(3).setName("张五").setAddress("西安"));
    //listUser = listUser.stream().sorted((a,b)->a.getId()- b.getId()).collect(Collectors.toList());
    listUser = listUser.stream().sorted((a,b)->a.getName().compareToIgnoreCase(b.getName())).collect(Collectors.toList());
    listUser.forEach(user -> System.out.println(user.toString()));
}

limit

限流操作,有点类似于mysql中的limit功能,比如:有10个元素,只取前面3个元素.

@Test
public void demo8(){
    List<String> list = Lists.newArrayList("1","2","3","4","5");
    list = list.stream().limit(3).collect(Collectors.toList());
    System.out.println(list.toString());
}

skip

跳过操作,比如:有个10个元素,从第5个元素开始去后面的元素

@Test
public void demo9(){
    List<String> list = Lists.newArrayList("1","2","3","4","5");
    list = list.stream().skip(2).collect(Collectors.toList());
    System.out.println(list.toString());
}

peek

允许开发者在流的处理过程中对每个元素执行观察或副作用操作,而不会改变流的元素内容。peek()作为中间操作,需要结合终端操作(如collect(), forEach()等)才能触发流的执行。若仅调用peek()而无终端操作,则不会产生任何输出或效果 。

/**
 * 在流的处理过程中对每个元素执行观察或副作用操作 1. 调试与日志记录 2. 更新共享状态 3. 验证与异常处理
 */
public class LambdaPeek {
    public static void main(String[] args) {
        //1. 调试与日志记录
        Stream.of("apple", "banana", "cherry")
                .peek(s -> System.out.println("Before filter: " + s))
                .filter(s -> s.length() > 5)
                .peek(s -> System.out.println("After filter: " + s))
                .collect(Collectors.toList());
 
        //2. 更新共享状态
        int[] count = {0};
        Stream.of("apple", "banana", "cherry")
                .peek(s -> { if (s.startsWith("b")) count[0]++; })
                .forEach(System.out::println);
        System.out.println("Count: " + count[0]); // 输出:Count: 1
 
        //3. 验证与异常处理
        IntStream.range(1, 6)
                .peek(i -> { if (i == 3) throw new RuntimeException("Invalid value"); })
                .sum();
    }
}

流的Terminal方法(终结操作)

forEach

forEach:支持并行处理

forEachOrdered:是按顺序处理的,遍历速度较慢

collect

收集操作,将所有的元素收集起来,Collectors 提供了非常多收集器。包含:toMap、toSet、toList、joining,groupingBy,maxBy,minBy等操作。

toMap:将数据流转换为map,里面包含的元素是用key/value的形式的

List<User> listUser = new ArrayList<>();
listUser.add(new User().setId(2).setName("张四").setAddress("西安"));
listUser.add(new User().setId(1).setName("张三").setAddress("西安"));
listUser.add(new User().setId(3).setName("张五").setAddress("西安"));
Map<Integer, String> collect1=listUser.stream().collect(Collectors.toMap(User::getId, User::getName));

toSet:将数据流转换为set,里面包含的元素不可重复

toList:将数据流转出为list,里面包含的元素是有序的

joining:拼接字符串

String collect2=list.stream().collect(Collectors.joining(","));
System.out.println(collect2);
List<String> stringList =Lists.newArrayList("1","2","3");
System.out.println(StringUtils.join(stringList, ","));

groupingBy:分组,可以将list转换map

Map<String, List<User>> collect3=listUser.stream().collect(Collectors.groupingBy(User::getAddress));

couting:统计元素数量

maxBy:获取最大元素

minBy:获取最小元素

summarizingInt: 汇总int类型的元素,返回IntSummaryStatistics,再调用具体的方法对元素进行统计:getCount(统计数量),getSum(求和),getMin(获取最小值),getMax(获取最大值),getAverage(获取平均值)

List<String> list = Lists.newArrayList("1","11","3","4","9","6");
long count=list.stream().collect(Collectors.summarizingInt(x -> Integer.parseInt(x))).getCount();
System.out.println(count);

summarizingLong:汇总long类型的元素,用法同summarizingInt

summarizingDouble:汇总double类型的元素,用法同summarizingInt

averagingInt:获取int类型的元素的平均值,返回一个double类型的数据

List<String> list = Lists.newArrayList("1","11","3","4","9","6");
Double collect5=list.stream().collect(Collectors.averagingInt(value -> Integer.parseInt(value)));
System.out.println(collect5);

averagingLong:获取long类型的元素的平均值,用法同averagingInt

averagingDouble:获取double类型的元素的平均值,用法同averagingInt

mapping:获取映射,可以将原始元素的一部分内容作为一个新元素返回

listUser.stream().collect(Collectors.toMap(User::getId,e->e,(a,b)->a));

find

查找操作,包含:findFirst、findAny

findFirst:找到第一个,返回的类型为Optional

findAny:使用 stream() 时找到的是第一个元素,使用 parallelStream() 并行时找到的是其中一个元素,返回的类型为Optional

String s=list.stream().findFirst().orElse(null);
System.out.println(s);
 
String s1=list.parallelStream().findAny().orElse(null);
System.out.println(s1);

match

匹配操作,包含:allMatch、anyMatch、noneMatch

allMatch:所有元素都满足条件,返回boolean类型

anyMatch:任意一个元素满足条件,返回boolean类型

noneMatch:所有元素都不满足条件,返回boolean类型

boolean b=listUser.stream().allMatch(user -> user.getId() == 1);
 
boolean b1=listUser.stream().anyMatch(user -> user.getId() == 1);
 
boolean b2=listUser.stream().noneMatch(user -> user.getId() == 11);

min、max

min:获取最小值,返回Optional类型的数据

max:获取最大值,返回Optional类型的数据

@Test
public void demo12(){
    List<Integer> list = Arrays.asList(1,4,2,3);
    Optional<Integer> max=list.stream().max((a, b) -> a.compareTo(b));
    System.out.println(max.get());
    Optional<Integer> min=list.stream().min((a, b) -> a.compareTo(b));
    System.out.println(min.get());
}

reduce

规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。

reduce 操作可以实现从Stream中生成一个值,其生成的值不是随意的,而是根据指定的计算模型。

@Test
public void testReduce() {
    List<Integer> list = Lists.newArrayList(2, 3, 5, 7);
    Integer sum1 = list.stream().reduce(0, Integer::sum);
    //结果:17
    System.out.println(sum1);
 
    Optional<Integer> reduce = list.stream().reduce((a, b) -> a + b);
    //结果:17
    System.out.println(reduce.get());
 
    Integer max = list.stream().reduce(0, Integer::max);
    //结果:7
    System.out.println(max);
 
    Integer min = list.stream().reduce(0, Integer::min);
    //结果:0
    System.out.println(min);
 
 
    Optional<Integer> reduce1 = list.stream().reduce((a, b) -> a > b ? b : a);
    //2
    System.out.println(reduce1.get());
}

toArray

数组操作,将数据流的元素转换成数组。

@Test
public void demo13(){
    List<Integer> list = Lists.newArrayList(1,4,2,3);
    String[] objects=list.stream().map(integer -> integer.toString()).toArray(String[]::new);
    for (String object : objects) {
        System.out.println(object);
    }
}

stream和parallelStream的区别 stream:是单管道,称其为流,其主要用于集合的逻辑处理。

parallelStream:是多管道,提供了流的并行处理,它是Stream的另一重要特性,其底层使用Fork/Join框架实现

/**
     * 从两个集合中找相同的元素。
     * 一般用于批量数据导入的场景,先查询出数据,再批量新增或修改。
     */
@Test
public void testDemo1(){
    List<String> listA=Arrays.asList("1", "2", "4", "6");
    List<String> listB=Arrays.asList("1", "2", "7");
    List<String> listC=listA.stream().filter(a -> listB.stream().anyMatch(b -> a.equals(b))).collect(Collectors.toList());
    System.out.println(listC.toString());
}
/**
     * 有两个集合a和b,过滤出集合a中有,但是集合b中没有的元素。
     * 这种情况可以使用在假如指定一个id集合,根据id集合从数据库中查询出数据集合,
     * 再根据id集合过滤出数据集合中不存在的id,这些id就是需要新增的。
     */
@Test
public void testDemo2(){
    List<Integer> listId = Arrays.asList(1,2,3);
    List<User> listUser = Arrays.asList(
        new User().setId(1).setName("张三").setAddress("西安"),
        new User().setId(2).setName("张四").setAddress("延安"),
        new User().setId(3).setName("张五").setAddress("渭南"),
        new User().setId(4).setName("张六").setAddress("临潼")
    );
    List<User> userList=listUser.stream().filter(user -> listId.stream().noneMatch(id -> id == user.getId())).collect(Collectors.toList());
    System.out.println(userList.toString());
}
/**
     * 根据条件过滤数据,并且去重做数据转换
     */
@Test
public void testDemo3(){
    List<User> listUser = Arrays.asList(
        new User().setId(1).setName("张三").setAddress("西安"),
        new User().setId(2).setName("张四").setAddress("延安"),
        new User().setId(2).setName("张四").setAddress("延安"),
        new User().setId(3).setName("张五").setAddress("渭南"),
        new User().setId(4).setName("张六").setAddress("临潼")
    );
    List<String> list=listUser.stream().filter(user -> !user.getAddress().equals("西安")).map(User::getName).distinct()
        .collect(Collectors.toList());
    System.out.println(list.toString());
}
/**
     * 统计指定集合中,姓名相同的人中年龄最小的id
     */
@Test
public void testDemo4(){
    List<User> listUser = Arrays.asList(
        new User().setId(1).setName("张三").setAddress("西安"),
        new User().setId(1).setName("张四").setAddress("延安"),
        new User().setId(2).setName("张四").setAddress("延安"),
        new User().setId(3).setName("张四").setAddress("渭南"),
        new User().setId(4).setName("张六").setAddress("临潼")
    );
    listUser.stream().collect(Collectors.groupingBy(User::getName)).forEach((name,list)->{
        User user=list.stream().collect(Collectors.minBy((a, b) -> a.getId() > b.getId() ? 1 : 0)).get();
        System.out.println(user);
    });
}

常用函数式接口

Function<T, R>

Function<T, R> 是一个功能转换型的接口,可以把将一种类型的数据转化为另外一种类型的数据

/**
 * lambda函数接口 接受一个输入参数,返回一个输出参数
 * Function<T, R> 是一个功能转换型的接口,可以把将一种类型的数据转化为另外一种类型的数据
 */
public class LambdaFunction {
    public static void main(String[] args) {
        //获取每个字符串长度并返回
        Function<String, Integer> function = String::length;
        Stream<String> stream = Stream.of("张三", "李四", "王五");
        Stream<Integer> result = stream.map(function);
        result.forEach(System.out::println);
    }
}

Consumer

<font style="color:rgb(40, 202, 113);">Consumer<T></font>是一个消费性接口,通过传入参数,并且无返回的操作。

Predicate

<font style="color:rgb(40, 202, 113);">Predicate<T></font>是一个判断型接口,并且返回布尔值结果.

Supplier

<font style="color:rgb(40, 202, 113);">Supplier<T></font>是一个供给型接口,无参数,有返回结果。

Optional类

拷贝

将一个对象的属性赋值到另一个对象的情况,这种情况就叫做拷贝

引用拷贝

引用拷贝会生成一个新的对象引用地址,但是两个最终指向依然是同一个对象。当引用对象的属性值发生改变时,被引用对象也会发升改变,内存地址相同,且不重写equals和hashcode方法时也相等。

/**
     * 引用拷贝
     */
@Test
public void demo24(){
    Demo demo = new Demo("张三",12);
    Demo demo1 = demo;
    System.out.println(demo);//com.pojo.Demo@61e717c2
    System.out.println(demo1);//com.pojo.Demo@61e717c2
    demo1.name="李四";
    System.out.println(demo1.name);//李四
    System.out.println(demo.name);//李四
    System.out.println(demo == demo1);//true
}

浅拷贝

浅拷贝会创建一个新对象,新对象和原对象本身没有任何关系。新对象和原对象不等,但是新对象的属性和老对象相同。

  • 如果属性是基本类型(int,double,long,boolean等),拷贝的就是基本类型的值;
  • 如果属性是引用类型,拷贝的就是内存地址(即复制引用但不复制引用的对象) ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

/**
     * 浅拷贝
     */
@Test
public void demo25() throws CloneNotSupportedException {
    Father father = new Father("test");
    Son s1 = new Son("s1",12);
    s1.father = father;
    Son s2=(Son) s1.clone();
    System.out.println(s1);
    System.out.println(s2);
    System.out.println("s1==s2:"+(s1 == s2));//不相等
    System.out.println("s1.name==s2.name:"+(s1.name == s2.name));//相等
    System.out.println();
 
    //但是他们的Father father 和String name的引用一样
    s1.age=12;
    s1.father.name="smallFather";//s1.father引用未变
    s1.name="son222";//类似 s1.name=new String("son222") 引用发生变化
    System.out.println("s1.Father==s2.Father:"+(s1.father == s2.father));//相等
    System.out.println("s1.name==s2.name:"+(s1.name == s2.name));//不相等
    System.out.println(s1);
    System.out.println(s2);
}

深拷贝

在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量。

重写clone()方法

如果使用重写clone()方法实现深拷贝,那么要将类中所有自定义引用变量的类也去实现Cloneable接口实现clone()方法。对于字符类可以创建一个新的字符串实现拷贝。

//Father clone()方法
@Override
protected Father clone() throws CloneNotSupportedException {
    return (Father) super.clone();
}
//Son clone()方法
@Override
protected Son clone() throws CloneNotSupportedException {
    Son son= (Son) super.clone();//待返回拷贝的对象
    son.name=new String(name);
    son.father=father.clone();
    return son;
}

方法引用

分类:

类型语法对应的Lambda表达式
静态方法引用类名::staticMethod(args) 类名.staticMethod(args)
实例方法引用inst::instMethod(args) inst.instMethod(args)
对象方法引用类名::instMethod(inst,args) 类名.instMethod(args)
构建方法引用类名::new(args) new 类名(args)

日期API

新API基于ISO标准⽇历系统,java.time包下的所有类都是不可变类型⽽且线程安全。

编号类的名称描述
1Instant时间戳
2Duration持续时间,时间差
3LocalDate只包含日期,比如:2018-02-05
4LocalTime只包含时间,比如:23:12:10
5LocalDateTime包含日期和时间,比如:2018-02-05 23:14:21
6Period时间段
7ZoneOffset时区偏移量,比如:+8:00
8ZonedDateTime带时区的时间
9Clock时钟,比如获取目前美国纽约的时间
10java.time.format.DateTimeFormatter时间格式化
/**
 * JAVA8新日期类
 */
public class DateDemo {
    public static void main(String[] args) {
        //获取今天的日期
        LocalDate now = LocalDate.now();
        System.out.println("今天的日期:"+now);
        System.out.println("-----------------------------");
        //获取年、月、日
        System.out.println("年:"+now.getYear());
        System.out.println("月:"+now.getMonthValue());
        System.out.println("日:"+now.getDayOfMonth());
        System.out.println("-----------------------------");
        //处理特定日期
        LocalDate date = LocalDate.of(2018, 10, 10);
        System.out.println("特定日期:"+date);
        System.out.println("-----------------------------");
        //判断两个日期是否相等
        LocalDate date1 = LocalDate.of(2018, 10, 10);
        LocalDate date2 = LocalDate.now();
        System.out.println("两个日期是否相等:"+date1.equals(date2));
        System.out.println("-----------------------------");
        //判断周期性日期是否一致(月日是否一致判断)
        LocalDate localDate = LocalDate.of(1997, 10, 17);
        LocalDate localDate1 = LocalDate.of(2025, 10, 17);
        MonthDay monthDay = MonthDay.of(localDate.getMonthValue(), localDate.getDayOfMonth());
        MonthDay monthDay1 = MonthDay.from(localDate1);
        System.out.println("周期性日期是否一致:"+monthDay.equals(monthDay1));
        System.out.println("-----------------------------");
        //获取当前时间
        LocalTime time = LocalTime.now();
        System.out.println("获取当前日期,不含日期:"+time);
        System.out.println("-----------------------------");
        //获取当前时间前后5分钟的时间
        LocalTime time1 = LocalTime.now();
        LocalTime time2 = time1.plusMinutes(5);
        System.out.println("获取当前时间前后5分钟的时间:"+time2);
        System.out.println("-----------------------------");
        //获取一周后的日期
        LocalDate date3 = LocalDate.now();
        System.out.println("今天的日期是:"+date3);
        LocalDate plus = date3.plus(1, ChronoUnit.WEEKS);
        System.out.println("一周后的日期是:"+plus);
        System.out.println("-----------------------------");
        //计算一年前或一年后的日期
        LocalDate date4 = LocalDate.now();
        System.out.println("今天的日期是:"+date4);
        LocalDate minus = date4.minus(1, ChronoUnit.YEARS);
        System.out.println("一年前的日期是:"+minus);
        System.out.println("一年后的日期是:"+date4.plus(1, ChronoUnit.YEARS));
        System.out.println("-----------------------------");
        //Clock时钟类(获取时间戳)
        Clock clock = Clock.systemUTC();
        System.out.println("时间戳:"+clock.millis());
        Clock clock1 = Clock.systemDefaultZone();
        System.out.println("时间戳:"+clock1.millis());
        System.out.println("-----------------------------");
        //比较时间大小
        LocalDate now1 = LocalDate.now();
        LocalDate now2 = LocalDate.of(2018, 10, 10);
        if (now1.isAfter(now2)){
            System.out.println("now1比now2晚");
        }
        if (now1.isBefore(now2)){
            System.out.println("now1比now2早");
        }
        System.out.println("-----------------------------");
        //计算两个日期之间的天数
        LocalDate date5 = LocalDate.of(2018, 10, 10);
        LocalDate date6 = LocalDate.of(2020, 10, 20);
        Period between = Period.between(date5, date6);
        Period between1 = Period.between(date6, date5);
        System.out.println("两个日期之间的天数:"+between.getDays());
        System.out.println("两个日期之间的天数:"+between1.getDays());
        System.out.println("-----------------------------");
        //日期格式化
        String  format = "20180101";
        LocalDate parse = LocalDate.parse(format, DateTimeFormatter.BASIC_ISO_DATE);
        System.out.println("日期格式化:"+parse);
        System.out.println("-----------------------------");
        //字符串日期互转
        LocalDate now3 = LocalDate.now();
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        //日期转字符串
        String format1 = now3.format(dateTimeFormatter);
        System.out.println("日期转字符串:"+format1);
        //字符串转日期
        DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        LocalDate parse1 = LocalDate.parse(format1, dateTimeFormatter1);
        System.out.println("字符串转日期:"+parse1);
    }
}