1.Lambda 表达式
Lambda 是一个匿名函数;
->
操作符称为Lambda操作符或箭头操作符。它将Lambda 分为两个部分:
左侧:指定 Lambda表达式需要的所有参数
右侧:指定 Lambda 体,即Lambda 表达式要执行的功能。
// 一般匿名类写法
Comparator<Integer> comparator1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// a negative integer, zero, or a positive integer as the first argument is less
// than, equal to, or greater than the second.
return o1 == o2 ? 0 : (o1 > o2 ? 1 : -1);
}
};
// Lambda 表达式写法
Comparator<Integer> comparator2 = (o1, o2) -> o1 == o2 ? 0 : (o1 > o2 ? 1 : -1);
// 一般匿名类写法
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello Runnable1");
}
};
Thread thread1 = new Thread(runnable1);
thread1.start();
// Lambda 表达式写法
Runnable runnable2 = () -> System.out.println("Hello Runnable2");
Thread thread2 = new Thread(runnable2);
thread2.start();
// Lambda 表达式写法
Thread thread3 = new Thread(() -> System.out.println("Hello Thread"));
thread3.start();
// Lambda 表达式写法
TreeSet<String> set = new TreeSet<String>((p1, p2) -> Integer.compare(p1.length(), p2.length()));
// 格式一:无参数,无返回值
Runnable runnable = () -> System.out.println("Hello Runnable");
// 格式二:
// ①若只有一个参数,并且无返回值;
Consumer<String> consumer1 = (p) -> System.out.println("Hello " + p);
// ②若只有一个参数,小括号可以省略不写
Consumer<String> consumer2 = p -> System.out.println("Hello " + p);
// 格式三:
// ①有两个及以上的参数,且有返回值,且Lambda体中有多条语句
Comparator<Integer> comparator1 = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
// ②当Lambda体只有一条语句时,return与大括号可以省略
Comparator<Integer> comparator2 = (x, y) -> Integer.compare(x, y);
// 格式四:Lambda表达式的参数列表的数据类型可以省略不写,由JVM编译器通过上下文推断出数据类型,即“类型推断”
// BinaryOperator<Integer> bo1 = (Integer p1, Integer p2) -> p1 + p2;
BinaryOperator<Integer> bo2 = (p1, p2) -> p1 + p2;
2.函数式接口
1)只包含一个抽象方法的接口,称为函数式接口。
2)任意函数式接口上可使用@FunctionalInterface注解检查它是否是一个函数式接口。
2.1 Java内置四大核心函数式接口
函数式接口 | 用途 |
---|---|
Consumer<T> | 对T类型对象应用操作。 method: void accept(T t) |
Supplier<T> | 返回T类型对象。 method: T get() ; |
Function<T,R> | 对T类型对象操作,返回R类型对象。 method: R apply(T t) ; |
Predicate<T> | 对T类型对象操作是否满足某约束。 method: boolean test(T t) ; |
其他常用函数式接口
函数式接口 | 用途 |
---|---|
BiFunction<T,U,R> | 对类型为T,U对象操作,返回R类型对象。 method: R apply(T t, U u) ; |
UnaryOperator<T> (Function子接口) | 对类型为T对象进行一元运算,返回T类型对象。 method: T apply(T t) ; |
BinaryOperator<T> (BiFunction子接口) | 对类型为T的对象进行二元运算,并返回T类型的结果。 method: T apply(T t1,T t2) ; |
BiConsumer<T,U> | 对类型为T,U参数应用操作。 method: void accept(T t, U u) |
BiPredicate<T,U> | 对类型为T,U是否满足某约束。 method: boolean test(T t, U u); |
对于基本类型boolean, int, long和double,为避免装箱/拆箱,Java 8提供了一些专门的函数,例如:
函数式接口 | 用途 |
---|---|
IntConsumer | 对int类型对象应用操作。 method: void accept(int value) |
IntSupplier | 返回int类型对象。 method: int getAsInt(); ; |
IntPredicate | 对int类型对象操作是否满足某约束。 method: boolean test(int value) ; |
ToIntFunction<T> ToLongFunction<T> ToDoubleFunction<T> | 分别计算int、long、double、值的函数int applyAsInt(T value) long applyAsLong(T value) |
IntFunction<R> LongFunction<R> DoubleFunction<R> | 参数分别为int、long、double类型的函数R apply(int value) R apply(long value) |
函数式接口也是接口,但只能有一个抽象方法。
Lambda表达式可以赋值给函数式接口:
Comparator<File> comparator = (f1, f2) -> f1.getName().compareTo(f2.getName());
3. 方法引用与构造器引用
3.1 方法引用
::
方法引用操作符,对象名/类名::方法名
;
若传递给Lambda体的操作,已经有实现,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)
方法引用有如下三种主要使用情况:
1)对象::实例方法
2)类::静态方法
3)类::实例方法
Collection<String> collection = Arrays.asList("中国", "美国", "英国");
// collection.forEach((x) -> System.out.println(x));
collection.forEach(System.out::println);
(或无参)是实例方法的参数时,格式: ClassName::MethodName
// BiPredicate<String, String> bp = (x, y) -> x.equals(y);
BiPredicate<String, String> bp = String::equals;
System.out.println(bp.test("中国", "美国"));
// ①方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
// ②若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数
Comparator<Integer> comparator1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
Comparator<Integer> comparator2 = (o1, o2) -> Integer.compare(o1, o2);
Comparator<Integer> comparator3 = Integer::compare;
方法引用示例
public class MyStream {
public static void main(String[] args) {
// 静态方法 引用
Supplier<String> supplier1 = Student::solidStr;
Supplier<String> supplier2 = () -> Student.solidStr();
System.out.println(supplier1.get());// 哈哈哈,我没有形参哦!
System.out.println(supplier2.get());// 哈哈哈,我没有形参哦!
// Student::solidStr;
// 实例方法 引用
Student s1 = new Student("李黑666", 666);
Student s2 = new Student("李黑777", 777);
Function<Student, Integer> function1 = Student::getAge;
Function<Student, Integer> function2 = (Student t) -> t.getAge();
Integer age1 = function1.apply(s1);
Integer age2 = function2.apply(s2);
System.out.printf("age1 = %s \n", age1);// age1 = 666
System.out.printf("age2 = %s \n", age2);// age2 = 777
Student ss1 = new Student("李黑666", 666);
Student ss2 = new Student("李黑777", 777);
BiConsumer<Student, Integer> biConsumer1 = Student::setAge;
BiConsumer<Student, Integer> biConsumer2 = (t, age) -> t.setAge(age);
biConsumer1.accept(ss1, 6999);
biConsumer2.accept(ss2, 7999);
System.out.printf("ss1.getAge() = %s \n", ss1.getAge());// ss1.getAge() = 6999
System.out.printf("ss2.getAge() = %s \n", ss2.getAge());// ss2.getAge() = 7999
// 通过变量引用方法
Student t = new Student("白居易", 666);
Supplier<String> s11 = t::getName;
Supplier<String> s12 = () -> t.getName();
System.out.println("s11.get() = " + s11.get());// s11.get() = 白居易
System.out.println("s12.get() = " + s12.get());// s12.get() = 白居易
Consumer<String> consumer11 = t::setName;
Consumer<String> consumer12 = (name) -> t.setName(name);
consumer11.accept("李太白");
System.out.println("t.getName() = " + t.getName());// t.getName() = 李太白
consumer12.accept("青莲居士");
System.out.println("t.getName() = " + t.getName());// t.getName() = 青莲居士
}
}
class Student {
private String name;
private int age;
public Student() { }
public Student(String name, int age) {
this.name = name;
this.age = age;
}
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 static String solidStr() { return "哈哈哈,我没有形参哦!"; }
public static void solidStr(String val) { System.out.println(val); }
@Override
public String toString() { return "Student [name=" + name + ", age=" + age + "]"; }
@Override
public int hashCode() { ... }
@Override
public boolean equals(Object obj) { ... }
}
3.2 构造器引用 :构造器的参数列表
与函数式接口中参数列表
保持一致!
类名 :: new
类型[] :: new
// String str = new String("Hello China");
Function<String, String> fun = String::new;
String str = fun.apply("Hello China");
System.out.println(str);
// 定义实体
public class Student {
private Long id;
private String name;
private Integer age;
private Integer score;
public Student(Long id, String name, Integer age, Integer score) {
this.id = id;
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() { return "Student [id=" + id + ", name=" + name + ", age=" + age + ", score=" + score + "]"; }
}
// 定义函数式接口
@FunctionalInterface
public interface FourInputOneOut<A, B, C, D, R> {
R apply(A a, B b, C c, D d);
}
// 测试类
public class Demo {
@Test
public void testConstructorReference() {
// Student student = new Student(1001L, "李太白", 18, 100);
FourInputOneOut<Long, String, Integer, Integer, Student> r = Student::new;
Student student = r.apply(1001L, "李太白", 18, 100);
System.out.println(student);
}
}
4.Stream API
4.1 Stream的操作流程
1、创建Stream
一个数据源(如:集合、数组),获取一个流。
2、中间操作
一个中间操作链,对数据源的数据进行处理。
3、终止操作(终端操作)
一个终止操作,执行中间操作链,并产生结果。
4.2 创建Stream(如何生成Stream)
4.2.1 由Collection集合创建Stream
Collection<String> coll = new ArrayList<String>();
Stream<String> stream = coll.stream();// 顺序流
Stream<String> parallelStream = coll.parallelStream();// 并行流
4.2.2 由数组创建流; 使用Arrays的静态方法stream()
Stream<String> stream2 = Arrays.stream(new String[] { "Michael", "Michelle", "William" });
DoubleStream stream3 = Arrays.stream(new double[] { 3, 6, 68 });
//Arrays.stream方法生成的流是数值流【即IntStream】而不是Stream<Integer>。
//数值流可以避免计算过程中拆箱装箱,提高性能。
int[] intArr = new int[]{1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(intArr);
//Stream API提供了mapToInt、mapToDouble、mapToLong三种方式将对象流转换为数值流
//提供了boxed方法将数值流转换为对象流
List<Student> list = new ArrayList<Student>();
IntStream intStream = list.stream().mapToInt(item -> item.getRanking());
Stream<Integer> boxed = intStream.boxed();
4.2.3 由值创建流; 使用静态方法Stream.of()
Stream<String> stream31 = Stream.of("Michael", "Michelle", "William");
Stream<Long> stream32 = Stream.of(1L, 2L, 3L, 4L, 5L);
4.2.4 由函数iterate和generate创建流:创建无限流
=============== iterate ===============
//iterate方法接受两个参数,第一个为初始化值,第二个为函数操作,
//因为iterator生成的流为无限流,通过limit方法对流进行了截断,只生成5个元素
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
Stream<String> limit = Stream.iterate("William", new UnaryOperator<String>() {
@Override
public String apply(String t) {
return t;
}
}).limit(5);
Stream<String> limit = Stream.iterate("William", (t) -> {
return t;
}).limit(5);
//limit.peek(item -> System.out.println(item)).count();
limit.peek(System.out::println).count();
//limit.forEach(item->System.out.println(item));
limit.forEach(System.out::println);
//limit.forEach(System.out::println); //Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
=============== generate ===============
//generate方法接受一个参数,方法参数类型为Supplier。
//generate生成的流也是无限流,因此通过limit对流进行了截断
// public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(new Supplier<String>() {
@Override
public String get() {
return "William";
}
}).limit(5).forEach(new Consumer<String>() {
@Override
public void accept(String t) {
System.out.println(t);
}
});
Stream<Double> stream = Stream.generate(Math::random).limit(5);
stream.forEach(System.out::println);
4.2.5 通过文件生成
//import java.nio.charset.Charset;
//import java.nio.file.Files;
//import java.nio.file.Paths;
Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset());
4.3 Stream 的中间操作
多个中间操作可以连接起来,中间操作不会执行任何逻辑,而在终止操作时一次性全部处理,称为“惰性求值”。
筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收Lambda ,从流中排除某些元素。 |
distinct() | 筛选,通过流所生成元素的hashCode() 和equals() 去除重复元素 |
limit(long maxSize) | 截断流,使其元素不超过给定数量。 |
skip(long n) | 跳过元素,返回一个扔掉了前n 个元素的流。若流中元素不足n 个,则返回一个空流。与limit(n) 互补 |
// 老方法
stream.filter(new Predicate<String>() {
@Override
public boolean test(String t) {
return t.startsWith("M");
}
}).forEach(new Consumer<String>() {
@Override
public void accept(String t) {
System.out.println(t);
}
});
// Lambda表达式
stream.filter((p) -> p.startsWith("M")).forEach(System.out::println);
map,flatMap映射
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream。 |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
List<String> names1 = list.stream().map(Student::getName).collect(Collectors.toList());
List<String> names2 = list.stream().map((item) -> item.getName()).collect(Collectors.toList());
Set<String> names3 = list.stream().map(Student::getName).collect(Collectors.toSet());
list.stream().forEach((item) -> System.out.println(item));
list.stream().forEach(System.out::println);
List<String> collect = list.stream().flatMap((item) -> {
return Arrays.stream(item.getName().split(""));
}).collect(Collectors.toList());
System.out.println(collect);//[黎, 明, 1, 李, 白, 2, 李, 煌, 3, 李, 煌, 4, 李, 黑, 5, 李, 黑, 6]
// Creating a list of Strings
List<String> list = Arrays.asList("1", "2", "3", "4", "5");
long sum = list.stream().mapToLong(num -> Long.parseLong(num)).sum();
System.out.println(sum);//15 //即 1+2+3+4+5=15
字符串收集器
String result = Stream.of("我", "爱", "中国").collect(Collectors.joining("$", "<", ">"));
System.out.println(result);
group by分组
class Student {
private String name;
private String grade;
private int age;
}
List<Student> list = new ArrayList<>();
list.add(new Student("黎明1", "11",55));
list.add(new Student("李白2", "11",22));
list.add(new Student("李白2", "22",33));
list.add(new Student("李白2", "33",44));
list.add(new Student("李白2", "22",77));
list.add(new Student("李白2", "11",99));
Map<String, List<Student>> collect1 = list.stream().collect(Collectors.groupingBy(Student::getGrade));
Map<String, List<Student>> collect2 = list.stream().collect(Collectors.groupingBy((item) -> item.getGrade()));
for (Entry<String, List<Student>> entrySet : collect1.entrySet()) {
System.out.println(entrySet);
}
Map<String, Long> collect3 = list.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.counting()));
for (Entry<String, Long> entrySet : collect3.entrySet()) {
System.out.println(entrySet);
}
Map<String, Optional<Student>> collect41 = list.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.maxBy(Comparator.comparing(Student::getAge))));
for (Entry<String, Optional<Student>> entrySet : collect41.entrySet()) {
System.out.println(entrySet);
}
System.out.println("========================================");
Map<String, Optional<Student>> collect42 = list.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.maxBy((item1, item2) -> item1.getAge() - item2.getAge())));
for (Entry<String, Optional<Student>> entrySet : collect42.entrySet()) {
System.out.println(entrySet);
}
Map<String, Student> collect6 = list.stream().collect(Collectors.groupingBy(Student::getGrade,
Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparing(Student::getAge)),
Optional::get))
);
for (Entry<String, Student> entrySet : collect6.entrySet()) {
System.out.println(entrySet);
}
排除重复数据
# 分组
list.stream().collect(Collectors.groupingBy(Student::getGrade));
# java8有一个collectingAndThen可以根据多个字段去重
list.stream()
.collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(o -> o.getProfessionId() + ";" + o.getGrade()))), ArrayList::new));
#通过hashSet去重:
# 该种去重是bean完全相同的时候算重复数据
List<String> classNameList = new ArrayList(new HashSet(list));
排序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,其中按自然顺序排序 |
sorted(Comparator comp) | 产生一个新流,其中按比较器顺序排序 |
4.4 Stream 的终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void 。
4.4.1 查找与匹配
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意元素 |
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代(使用Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代—它帮你把迭代做了) |
4.4.2 归约
方法 | 描述 |
---|---|
reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回T |
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回Optional<T> |
4.4.3 收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法 |
Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。Collectors工具类提供的静态方法可方便地创建常见收集器实例: |
5.接口中的默认方法与静态方法
5.1 Interface的默认方法
JDK1.8之后新添加了默认方法,可以定义新的默认方法来对该接口进行扩展,而且不强制要求在实现类中重写该默认方法。
eg:一个类实现了多个接口,且这些接口拥有相同的默认方法签名。
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
}
public interface FourWheeler {
default void print(){
System.out.println("我是一辆四轮车!");
}
}
第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法:
public class Car implements Vehicle, FourWheeler {
default void print(){
System.out.println("我是一辆四轮汽车!");
}
}
第二种解决方案可以使用 super 来调用指定接口的默认方法:
public class Car implements Vehicle, FourWheeler {
public void print(){
Vehicle.super.print();
}
}
5.2 Interface的静态方法
Java 8 的另一个特性是接口可以声明(并且可以提供实现)静态方法。
public interface Vehicle {
// 静态方法
static void blowHorn(){
System.out.println("按喇叭!!!");
}
}
6.新时间日期API
- LocalDate:表示日期,包含:年月日。格式为:2020-03-04。
- LocalTime:表示时间,包含:时分秒。格式为:16:18:51.107
- LocalDateTime:表示日期时间,包含:年月日 时分秒。格式为:2020-03-04T16:18:51.112。
- DateTimeFormatter:日期时间格式化类。
- Instant:时间戳类。
- Duration:用于计算 2 个时间(LocalTime,时分秒)之间的差距。
- Period:用于计算 2 个日期(LocalDate,年月日)之间的差距。
- ZonedDateTime:包含时区的时间。
方法 | 描述 |
---|---|
now() | 静态方法,根据当前时间创建对象 |
of() | 静态方法,根据指定日期/时间创建对象 |
plusDays,plusWeeks,plusMonths,plusYears | 向当前LocalDate对象添加几天、几周、几个月、几年 |
minusDays,minusWeeks,minusMonths,minusYears | 从当前LocalDate对象减去几天、几周、几个月、几年 |
plus,minus | 添加或减少一个Duration或Period |
withDayOfMonth,withDayOfYear,withMonth,withYear | 将月份天数、年份天数、月份、年份修改为指定的值并返回新的LocalDate对象 |
getDayOfMonth | 获得月份天数(1-31) |
getDayOfYear | 获得年份天数(1-366) |
getDayOfWeek | 获得星期几(返回一个DayOfWeek枚举值) |
getMonth | 获得月份,返回一个Month枚举值 |
getMonthValue | 获得月份(1-12) |
getYear | 获得年份 |
until | 获得两个日期之间的Period对象,或者指定ChronoUnits的数字 |
isBefore,isAfter | 比较两个LocalDate |
isLeapYear | 判断是否是闰年 |
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime=LocalDateTime.now();
LocalDate localDate = LocalDate.of(2016, 10, 26);
LocalTime localTime = LocalTime.of(02, 22, 56);
LocalDateTime localDateTime = LocalDateTime.of(2016, 10, 26, 12, 10, 55);
// java.time.LocalDateTime类型 按指定格式 转为字符串 年月日时分秒毫秒
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
LocalDate localDate = LocalDate.parse("20210731", dateTimeFormatter);
System.out.println(localDate.toString());//2021-07-31
String localDateStr = localDate.plusDays(1L).format(dateTimeFormatter);
System.out.println(localDateStr);//20210801
Instant 时间戳
用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算
Duration 和Period
Duration:用于计算两个“时间”间隔
Period:用于计算两个“日期”间隔
日期的操纵
TemporalAdjuster
: 时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。
TemporalAdjusters
: 该类通过静态方法提供了大量的常用TemporalAdjuster 的实现。
// 获取下个周日
TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.SUNDAY);
LocalDate localDate = LocalDate.now().with(temporalAdjuster);
解析与格式化
java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:
预定义的标准格式
语言环境相关的格式
自定义的格式
时区的处理
Java8 中加入了对时区的支持,带时区的时间为分别为:
ZonedDate、ZonedTime、ZonedDateTime
其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式
例如:Asia/Shanghai 等
ZoneId:该类中包含了所有的时区信息
getAvailableZoneIds() : 可以获取所有时区时区信息
of(id) : 用指定的时区信息获取ZoneId 对象