Lambda表达式
-
Lambda表达式是一个匿名函数,可以理解为一段可以传递的代码。
-
优点:简化了匿名内部类的使用,语法更加简单,是简化匿名内部类的一种方式。
-
Lambda使用的前提条件:
-
方法的参数或局部变量类型必须为接口才能使用Lambda
-
接口中有且仅有一个抽象方法(@Functionallinterface)
-
-
格式:
(参数类型 参数名) -> { 代码体; }
-
省略写法规则:
-
小括号内的参数类型可以省略;
-
如果小括号内有且仅有一个参数,则小括号可以省略;
-
如果大括号内有且仅有一条语句,可以同时省略大括号,return关键字及语句分号;
// 不简写 test((int age) -> { return age + 1; }, 23); // 简写 test(age -> age + 1, 23);
-
-
原理(了解):
-
匿名内部类在编译的时候会产生一个class文件;
-
在类中新增了一个方法,这个方法的方法体就是Lambda表达式中的代码;
-
还会形成一个匿名内部类,实现接口,重写抽象方法;
-
在接口中重写方法会调用新生成的方法;
-
-
Lambda与匿名内部类的对比:
-
所需类型不一样:
-
匿名内部类的类型可以是类,抽象类,接口;
-
Lambda表达式需要的类型必须是接口;
-
-
抽象方法的数量不一样:
-
匿名内部类所需的接口中的抽象方法的数量是随意的;
-
Lambda表达式所需的接口中只能有一个抽象方法;
-
-
实现原理不一样:
-
匿名内部类是在编译后形成一个class;
-
Lambda表达式是在程序运行的时候动态生成class;
-
-
函数式接口
使用Lambda表达式的前提是需要有函数式接口,而Lambda表达式使用时不关心接口名,抽象方法名。只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda表达式更加的方法,在JDK中提供了大量常用的函数式接口。
-
注解 @FunctionalInterface :使用此注解修饰的接口,只能有一个抽象方法,声明此接口是一个函数式接口;
-
四大父函数接口:
-
Supplier(无参,有返回值):
// 源码: @FunctionalInterface public interface Supplier<T> { T get(); } // 应用 public static void main(String[] args) { int[] arr = { 12, 16, 55, 48, 22, 45, 66 }; test(() -> { Arrays.sort(arr); return arr[arr.length - 1]; }); } public static void test(Supplier<Integer> supplier) { System.out.println(supplier.get()); }
-
Consumer(有参,无返回值):
// 源码: @FunctionalInterface public interface Consumer<T> { void accept(T t); } // 应用 public static void main(String[] args) { test(str -> { System.out.println(str.toLowerCase()); }, "HEllO,WorLD"); } public static void test(Consumer<String> consumer, String str) { consumer.accept(str); }
-
Function(有参,有返回值):
// 源码: @FunctionalInterface public interface Function<T, R> { R apply(T t); } // 应用 public static void main(String[] args) { test(str -> Integer.parseInt(str), "123"); } public static void test(Function<String, Integer> function, String str) { Integer apply = function.apply(str); System.out.println(apply); }
-
Predicate(有参,返回值为布尔类型):
// 源码: @FunctionalInterface public interface Predicate<T> { boolean test(T t); } // 应用 public static void main(String[] args) { test(num -> num == 0, 10); } public static void test(Predicate<Integer> predicate, Integer num) { boolean test = predicate.test(num); System.out.println(test); }
-
方法的引用(双冒号的使用)
-
符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
-
应用场景:在使用Lambda表达式的时候,也会出现代码冗余的情况,比如:用Lambda表达式求一个数组的和。如果在Lambda表达式中要执行的代码和我们另一个方法中的代码是一样的,这时就没有必要重写一份逻辑了,这时我们就可以"引用"重复代码。
-
语法格式:
-
instanceName::methodName(对象::方法名):
// 完整格式 Supplier<Long> s1 = () -> date.getTime(); System.out.println(s1.get()); // 方法引用;方法不需要加 () Supplier<Long> s2 = date::getTime; System.out.println(s2.get());
-
ClassName::staticMethodName(类名::静态方法):
// 完整格式 Supplier<Long> s3 = () -> System.currentTimeMillis(); System.out.println(s3.get()); // 方法引用 Supplier<Long> s4 = System::currentTimeMillis; System.out.println(s4.get());
-
ClassName::methodName(类名::普通方法):
// 完整格式 BiFunction<String, Integer, String> b1 = (str, i) -> str.substring(i); System.out.println(b1.apply("helloworld", 5)); // 方法引用:类名引用普通方法 BiFunction<String, Integer, String> b2 = String::substring; System.out.println(b2.apply("helloworld", 5));
-
ClassName::new(类名::new 调用的构造器):
// 完整格式 BiFunction<String, Integer, Student> b3 = (name, age) -> new Student(name, age); System.out.println(b3.apply("张三", 23)); // 方法引用:类名引用构造器 BiFunction<String, Integer, Student> b4 = Student::new; System.out.println(b4.apply("李四", 41));
-
TypeName[]::new(String[]::new 调用数组的构造器):
// 完整格式 Function<Integer, String[]> b5 = (length) -> new String[length]; System.out.println(b5.apply(5).length); // 方法引用:数组引用构造器 Function<Integer, String[]> b6 = String[]::new; System.out.println(b6.apply(6).length);
-
-
注意:
-
方法引用是对Lambda表达式符合特定情况下的一种缩写方式,它使得我们的Lambda表达式更加的精简,也可以理解为lambda表达式的缩写形式,不过要注意的是方法引用只能引用已经存在的方法。
-
被引用的方法,参数要和接口中的抽象方法的参数一样;
-
当接口抽象方法有返回值时,被引用的方法也必须有返回值;
-
Optional类
Optional使用了final修饰,所以是一个没有子类的工具类,Optional是一个可以为 null 的容器对象,它的主要作用就是为了避免Null检查,防止空指针异常的出现。
-
Optional创建对象的方式:
-
of() 方法(不支持传入null值):
Optional<String> o1 = Optional.of("hello");
-
ofNullable() 方法(可以传入null值):
Optional<String> o2 = Optional.ofNullable("test");
-
empty() 方法(创建一个空对象):
Optional<Object> o3 = Optional.empty();
-
-
Optional的常用方法:
-
isPresent():判断Optional对象中是否有值;如果有,返回ture。
-
get():获取Optional中的值,如果值为空,抛出NoSuchElementException;通常结合isPresent()使用。
-
orElse(T other):如果调用者中值不为null,则返回该值;若值为null,则返回other参数自己。
-
orElseGet(Supplier other):如果调用者中值不为null,则返回该值;否则,返回Lambda表达式的返回值。
-
orElseThrow(Supplier exceptionSupplier):如果调用者中值不为null,则返回该值;否则抛出自定义异常。
-
ifPresent(Consumer consumer):如果有值,则自定义Lambda表达式,做对应的事情。
-
map(Function mapper):如果有值,则使用Lambda表达式处理值(处理后的Optional可以为空值),否则返回一个空的Optional对象。
-
flatMap(Function mapper):如果有值,则使用Lambda表达式处理值(处理后的Optional不为空值),否则返回一个空的Optional对象。
-
filter(Predicate predicate):如果有值,则使用Lambda表达式自定义判断该值,否则返回一个空的Optional对象。
-
Stream Api(流式编程)
-
思想概述:
-
Stream用于简化循环,是一种更加高效的处理方式。
-
Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理。
-
Stream流可以快速完成许多复杂的操作,如筛选、切片、映射、查找、去重、统计、匹配和归约。
-
-
Stream对象的创建:
-
Collection获取(Collection接口加入了default修饰的方法stream()):
-
单列集合:
// 1.List集合 Stream<Object> stream = list.stream(); // 2.Set集合 Stream<Object> stream2 = set.stream();
-
双列集合:
// 1.keySet获取键的Stream Stream<Object> stream4 = map.keySet().stream(); // 2.values获取值的Stream Stream<Object> stream5 = map.values().stream(); // 3.entrySet获取键值对的Stream Stream<Entry<Object, Object>> stream6 = map.entrySet().stream();
-
-
Stream静态方法获取(为数组对象提供的方法):
Stream<String> stream = Stream.of("hello","world","test","hahaha"); String[] arr1 = {"12","aa","ss"}; Stream<String> stream1 = Stream.of(arr1); Integer[] arr2 = {1,3,4,0}; Stream<Integer> stream2 = Stream.of(arr2);
-
-
方法分类:
-
终结方法:返回值类型不是Stream类型的方法,不再支持链式调用。
-
非终结方法:返回值类型仍然是Stream类型的方法,支持链式调用。
-
-
常用API:
方法名 方法作用 返回值类型 方法种类 count 统计元素个数 long 终结 forEach 逐一处理 void 终结 match 匹配元素 boolean 终结 find 查找元素 Optional 终结 min / max 最大值 / 最小值 Optional 终结 reduce 将结果归纳为一个值,通常结合map T t 终结 collect 使用Collector类将流进行处理 T t 终结 filter 过滤 Stream 函数拼接 limit 截取前几个 Stream 函数拼接 skip 跳过前几个 Stream 函数拼接 map 映射,类型转换 Stream 函数拼接 sorted 排序 Stream 函数拼接 distinct 去重(重写equals和hashCode) Stream 函数拼接 mapToInt 数据转换,减少不必要的拆装箱 IntStream 函数拼接 concat 两个Stream合并为一个Stream Stream 函数拼接 -
注意:
-
Stream只能操作一次;
-
Stream方法返回的是新的流;
-
Stream不调用终结方法,中间的操作不会执行;
-
-
Collector类常用方法:
-
收集结果到集合:
-
List收集:
Collectors.toList()
; -
Set收集:
Collectors.toSet()
; -
Collection收集:
Collectors.toCollection(ArrayList::new)
,入参为自定义的集合; -
Map收集:
Collectors.toMap(String::new, String::new, String::concat)
;-
参数一为key的生成策略;
-
参数二为value的生成策略;
-
参数三(可省略)为key冲突时,对value的处理策略;
-
-
-
收集结果到数组:
-
默认Object数组收集:
Stream.of("aa","bb").toArray()
; -
自定义数组收集:
Stream.of("aa","bb").toArray(String[]::new)
;
-
-
聚合计算:
-
maxBy(Comparator):最大值;
-
minBy(Comparator):最小值;
-
summingInt(Function):求和;
-
averagingInt(Function):平均值;
-
counting():求总数量;
-
-
分组操作:
-
groupingBy(Function):分组;
-
groupingBy(Function,Collectors.groupingBy(Function)):多级分组。如先分学校,再分班级。
-
-
拼接组合操作:
-
joining():直接拼接;
-
joining(分隔符):使用分隔符做拼接;
-
joining(分隔符,前缀,后缀):使用分隔符做拼接,并在最终结果上加入前缀、后缀;
-
-
-
并行流:
-
说明:
-
我们前面使用的Stream流都是串行,也就是在一个线程上面执行。
-
并行流(SerialStream)通过默认的ForkjoinPool,系统会使用多线程去执行,可以提高执行速度。
-
并行处理的过程会分而治之,也就是将一个大的任务切分成了多个小任务,这表示每个任务都是一个线程操作。
-
-
获取方式:
-
通过集合获取:
parallelstream()
方法;ArrayList<Object> list = new ArrayList<>(); Stream<Object> stream = list.parallelStream();
-
通过流获取:
parallel()
转换现有的流;Stream<String> stream = Stream.of("aa", "bb").parallel();
-
-
并行流的线程安全问题:
-
操作时,在同步代码块中执行;
-
使用线程安全的容器,如Vector、HashTable等;
-
将原容器转为线程安全的容器,如使用 Collections 工具类中的
synchronizedXxxx()
方法; -
使用Stream中的
toArray()
或collect()
方法来操作;
-
-
新日期时间类
-
旧版日期类存在的问题:
-
设计不合理,在
java.util
和java.sql
的包中都有日期类,java.util.Date
同时包含日期和时间,而java.sql.Date
仅仅包含日期。此外,用于格式化和解析的类在java.text
包下。 -
java.util.Date
是非线程安全的,所有日期类都是可变的,这是java日期类最大的问题之一。 -
时区处理麻烦,日期类并不提供国际化,没有时区支持。
-
-
新的日期及时间API位于 java.time 包中,下面是一些关键类:
-
LocalDate
:表示日期,包含年月日,格式为2019-10-16;// 1.获取日期 LocalDate now = LocalDate.now(); LocalDate date = LocalDate.of(2023, 12, 23); // 年,月,日 // 2.根据日期获取信息 int year = date.getYear(); // 获取年份 Month month = date.getMonth(); // 获取月份,返回枚举对象 int dayOfMonth = date.getDayOfMonth(); // 获取所在月的天数 DayOfWeek dayOfWeek = date.getDayOfWeek(); // 获取星期,返回枚举对象 int dayOfYear = date.getDayOfYear(); // 获取所在年的天数
-
LocalTime
:表示时间,包含时分秒,格式为16:38:54.158549300;// 1.获取时间 LocalTime now = LocalTime.now(); LocalTime time = LocalTime.of(23, 59, 59, 999); // 时,分,秒,纳秒 // 2.根据时间获取信息 int hour = time.getHour(); // 获取时 int minute = time.getMinute(); // 获取分 int second = time.getSecond(); // 获取秒 int nano = time.getNano(); // 获取纳秒
-
LocalDateTime
:表示日期时间,包含年月日,时分秒,格式为2018-09-06T15:33:56.750;// 1.获取日期时间 LocalDateTime dateTime = LocalDateTime.now(); LocalDateTime dateTime2 = LocalDateTime.of(2023, 12, 23, 23, 59, 59, 999); LocalDateTime dateTime3 = LocalDateTime.of(LocalDate.now(), LocalTime.now()); // 2.根据日期时间获取信息 int year = dateTime.getYear(); // 获取年份 Month month = dateTime.getMonth(); // 获取月份,返回枚举对象 int dayOfMonth = dateTime.getDayOfMonth(); // 获取所在月的天数 DayOfWeek dayOfWeek = dateTime.getDayOfWeek(); // 获取星期,返回枚举对象 int dayOfYear = dateTime.getDayOfYear(); // 获取所在年的天数 int hour = time.getHour(); // 获取时 int minute = time.getMinute(); // 获取分 int second = time.getSecond(); // 获取秒 int nano = time.getNano(); // 获取纳秒
-
DateTimeFormatter
:日期时间格式化类;// a.日期格式化对象,使用正则表达式 DateTimeFormatter fm = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:ss:mm"); // b.日期格式化对象,使用常量 DateTimeFormatter fm = DateTimeFormatter.ISO_LOCAL_DATE_TIME; // 1.日期转字符串 String format = LocalDateTime.now().format(fm); // 2.字符串转日期 LocalDateTime date = LocalDateTime.parse("2023-05-25 15:25:59", fm);
-
Instant
:时间戳,表示一个特定的时间瞬间;// 计算程序时间差 Instant old = Instant.now(); Thread.sleep(5000); Instant now = Instant.now(); System.out.println("耗时:" + (old.getNano() - now.getNano()));
-
Duration
:用于计算2个时间(LocalTime,时分秒)的距离;LocalTime t1 = LocalTime.of(0, 0, 0, 0); LocalTime t2 = LocalTime.of(23, 59, 59, 999); Duration duration = Duration.between(t1, t2); // 获取支持的时间单位集合(秒/纳秒) List<TemporalUnit> units = duration.getUnits(); long seconds = duration.getSeconds(); // 获取秒速的差 int nano = duration.getNano(); // 获取纳秒的差 // 获取时间差后转换为指定的时间单位 long toDays = duration.toDays();
-
Period
:用于计算2个日期(LocalDate,年月日)的距离;LocalDate d1 = LocalDate.of(2022, 12, 31); LocalDate d2 = LocalDate.of(2025, 10, 30); Period period = Period.between(d1, d2); // 获取支持的时间单位集合(年/月/日) List<TemporalUnit> periodUnits = period.getUnits(); int years = period.getYears(); // 获取年份之差 int months = period.getMonths(); // 获取月份之差 int days = period.getDays(); // 获取天数之差 // 获取时间差后转换为指定的时间单位 long toTotalMonths = period.toTotalMonths();
-
ZonedDateTime
:带时区的日期时间类分别为:ZonedDate
、ZonedTime
、ZonedDateTime
,其中每个时区都对应着ID,ID的格式为"区域/城市"。例:Asia/Shanghai
等。// 1.获取世界所有时区的集合,使用ZoneId类获取 Set<String> zoneIds = ZoneId.getAvailableZoneIds(); // 2.获取计算机默认日期 ZonedDateTime now = ZonedDateTime.now(); // 3.获取标准日期 ZonedDateTime now = ZonedDateTime.now(Clock.systemUTC()); // 4.获取指定时区的日期 ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Asia/Shanghai")); // 5.将LocalDateTime转为ZonedDateTime ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("Asia/Shanghai")); // 6.将LocalDate与LocalTime合并转为ZonedDateTime ZonedDateTime.of(LocalDate.now(), LocalTime.now(), ZoneId.of("Asia/Shanghai"));
-
-
日期的比较及修改(修改将返回一个新的日期对象):
-
更改值:
-
更改日期值
// 1.使用日期单位的枚举类 LocalDate date = LocalDate.now(); LocalDate year = date.with(ChronoField.YEAR, 2024); LocalDate month = date.with(ChronoField.MONTH_OF_YEAR, 12); // 2.指定日期单位 LocalDate date = LocalDate.now(); LocalDate withYear = date.withYear(2024); LocalDate withDayOfYear = date.withDayOfYear(11);
-
更改时间值:
// 1.使用时间单位的枚举类 LocalDate phours = date.plus(2, ChronoUnit.HOURS); LocalDate pseconds = date.plus(50, ChronoUnit.SECONDS); LocalDate mhours = date.minus(2, ChronoUnit.HOURS); LocalDate mseconds = date.minus(50, ChronoUnit.SECONDS); // 2.指定时间单位 LocalDate pyears = date.plusYears(2); LocalDate pmonths = date.plusMonths(2); LocalDate pyears = date.minusYears(2); LocalDate pmonths = date.minusMonths(2);
-
-
比较值:
// 1.比较当前日期是否在指定日期之后 boolean after = d1.isAfter(d2); // 2.比较当前日期是否在指定日期之前 boolean before = d1.isBefore(d2); // 3.比较当前日期是否等于指定日期 boolean equal = d1.isEqual(d2);
-
-
时间矫正器:
-
TemporalAdjuster
接口:时间日期类的父类接口;使用重写adjustInto()
方法来更改时间;LocalDateTime now = LocalDateTime.now(); // 矫正为下个月份的第一天 LocalDateTime date = now.with(temporal -> { LocalDateTime time = (LocalDateTime) temporal; return time.plusMonths(1).withDayOfMonth(1); });
-
TemporalAdjusters
类:该类的静态方法提供了大量的常用TemporalAdjuster
的实现;LocalDateTime now = LocalDateTime.now(); // 矫正为下个月份的第一天 TemporalAdjuster adjuster = TemporalAdjusters.firstDayOfNextMonth(); LocalDateTime date = now.with(adjuster);
-
-
历法:Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。此外java8还提供了4套其他历法的类,分别是:
-
ThaiBuddhistDate
:泰国佛教历; -
MinguoDate
:中华民国历; -
JapaneseDate
:日本历; -
HijrahDate
:伊斯兰历;
-
-
JDK新的日期和时间API的优势:
-
新版日期时间API中,日期和时间对象是不可变的,操作日期不会影响原来的值,而是生成新的实例;
-
提供不同的两种方式,有效的区分了人和机器的操作;
-
TemporalAdjuster
接口可以更精确的操作日期,还可以自定义日期调整期; -
线程安全的;
-