- lambda表达式
- 函数式接口
- 方法引用
- stream流
- 日期时间类
1.lambda表达式
lambda表达式允许把函数作为一个方法的参数(函数作为方法参数进行传递),将代码像数据一样传递。特殊的匿名内部类,语法更加简洁
注意:函数式接口接口中只有一个抽象方法
传统方式
public class Test01 {
public static void main(String[] args) {
old task01 = new old();
Thread tread01 = new Thread(task01);
tread01.start();
//匿名内部类
Runnable task02 = new Runnable() {
public void run() {
System.out.println("这是匿名内部类");
}
};
Thread thread02 = new Thread(task02);
thread02.start();
}
}
class old implements Runnable{
public void run() {
System.out.println("自定义接口类");
}
}
- Thread类需要Runnable接口作为参数,其中抽象run方法是用来指定线程任务内容的核心
- 为了制定run的方法体,不得不需要Runnable接口的实现类
- 为了省去定义一个Runnable实现类的麻烦,不得不使用匿名内部类
- 而使用匿名内部类必须覆盖重写run方法,所以方法名称,方法参数,方法返回值不得不再写一遍,且不能写错
- 而实际上,似乎只有方法体才是关键所在;
这是可以使用lambda表示完成上面的要求
//lombda表达式
Runnable task03 = () -> {
System.out.println("这是lombda表达式");
};
Thread thread03 = new Thread(task03);
thread03.start();
前提是必须是函数式接口
.无参无返回值的lambda表达式
public class Test02 {
public static void main(String[] args) {
MyInterface myInterface = new MyInterface(){
@Override
public void say() {
System.out.println("这是使用匿名实现类");
}
};
test01(myInterface);
/*========================================*/
/*那是不是可以这样写*/
MyInterface myInterface1 = ()->{
System.out.println("这是使用lambda表达式01");
};
test01(myInterface1);
/*========================================*/
/*那是不是也可以这样写*/
/*lambda表达式允许把函数作为一个方法的参数*/
test01(()->{
System.out.println("这是使用lambda表达式02");
});
}
public static void test01(MyInterface myInterface) {
myInterface.say();
}
interface MyInterface {
public void say();
}
}
.有参无有返回值的lambda表达式
public class Test03 {
public static void main(String[] args) {
ArrayList<User> users01 = new ArrayList<>();
ArrayList<User> users02 = new ArrayList<>();
users01.add(new User("张三", 20, "男"));
users01.add(new User("李四", 21, "女"));
users01.add(new User("王五", 22, "男"));
users02.add(new User("张三", 20, "男"));
users02.add(new User("李四", 21, "女"));
users02.add(new User("王五", 22, "男"));
//对集合按年龄大小排列
//传统做法:Comparator:排列规则接口
/*>0 o2比o1大
* =0 o1和o2一样大
* <0 o2比o1小*/
Comparator<User> userComparator01 = new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o2.getAge() - o1.getAge();
}
};
Collections.sort(users01, userComparator01);
/*================================================================*/
/*Lambda表达式*/
Collections.sort(users02, (User o1, User o2)->{
return o2.getAge() - o1.getAge();
});
}
}
/*实体类*/
class User{
public String name;
public int age;
public User(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public String sex;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
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 String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
注意事项
- 形参列表的数据类型会自动推断
- 如果形参列表为空,只需保留()-——参考无参
- 如果形参只有1个,()可以省略,只需要参数的名称即可
- 如果执行语句只有一句,且无返回值,{}可以省略,若有返回值,若想省去{},则必须同时省略return,且执行语句也保证只有一句
- Lambda不会生成一个单独的内部类文件
Lambda表达式在编译时不生成独立的
.class
文件,而是被转换为一个合成类,并嵌入在包含Lambda的主类字节码中。这种设计减少了磁盘空间使用,减轻了类加载器的负担,并简化了类路径管理。
在运行时,JVM通过动态类型生成机制处理Lambda表达式,利用
invokedynamic
指令动态创建和调用必要的类实例。
2.函数式接口
如果一个接口只有一个抽象方法,则该接口称之为函数式接口,函数式接口
可以使用Lambda表达式,Lambda表达式会被匹配到这个抽象方上
@Functionallnterface 注解检测接口是否符合函数式接口。
public class Test01 {
public static void main(String[] args) {
fun(arr -> {
int sum = 0;
for (int n: arr) {
sum += n;
}
System.out.println("数组的和为"+sum);
});
}
public static void fun(Operater operater){
int[] arr = {1,2,3,4,5,6,7,8,9,10};
operater.getSum(arr);
}
}
@FunctionalInterface
interface Operater {
//求数组的和
public abstract void getSum(int[] arr);
}
分析
我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名,抽象方法名,只关心抽 象方法的参数列表和返回估类里,因此为了让我们使用Lambda方便,JDK提供了大量常用的函数式接口
常见的函数接口
2.1 Consumer<T> 消费性接口
有参无返回值时,使用 Consumer<T> 消费性接口
public class Test02 {
public static void main(String[] args) {
Consumer<Double> consumer = money->{
System.out.println("消费了"+money+"元");
};
fun(consumer,1000);
}
public static void fun(Consumer<Double> consumer,double money){
consumer.accept(money);
}
}
2.2 Supplier<T> 供给型接口
无参,但想要返回结果的函数式接口时,使用Supplier<T> 供给型接口
T代表返回结果的泛型
public class Test03 {
public static void main(String[] args) {
Supplier<Integer> supplier = () -> new Random().nextInt(10);
fun(supplier);
}
public static void fun(Supplier<Integer> supplier) {
Integer result = supplier.get();
System.out.println("内容为" +result);
}
}
2.3 Function<T,R> 函数型接口
有参,有返回值时,使用 Function<T,R> 函数型接口
T:参数类型的泛型
R:函数返回结果的泛型
传入一个字符串把小写转化为大写
public class Test04 {
public static void main(String[] args) {
fun(s->s.toUpperCase(),"hello");
}
public static void fun(Function<String,String> function,String str){
String s = function.apply(str);
System.out.println("结果为" +s);
}
}
2.4 Predicate <T> 断言型接口
当传入一个参数,需要对该参数进行判断时,使用Predicate <T> 断言型接口;
T:传入参数的泛型
public class Test05 {
public static void main(String[] args) {
fun(s -> s.length() > 3, "小鸟游六花");
}
public static void fun(Predicate<String> predicate, String name) {
boolean test = predicate.test(name);
System.out.println("该名字长吗" +test);
}
}
3.方法引用
3.1 lambda表达式的冗余
public class Test06 {
public static void main(String[] args) {
Integer[] arr1 = {1,2,3,4,5};
Consumer<Integer[]> consumer = (arr2->{
int sum = 0;
for(Integer a:arr2){
sum+=a;
}
System.out.println("数组的和为"+sum);
});
fun(consumer,arr1);
}
public static void fun(Consumer<Integer[]> consumer, Integer[] arr){
consumer.accept(arr);
}
public static void sum(Integer[] arr){
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
System.out.println("数组的和为"+sum);
}
}
如上,如果我们在Lambda中所需的功能已经在另一个方法中写出,直接引用过去就好了
public class Test06 {
public static void main(String[] args) {
Integer[] arr1 = {1,2,3,4,5};
//双冒号::用法。这被称为方法引用,是一种新的语法,允许您引用类中的方法。
Consumer<Integer[]> c=Test06::sum;
fun(c,arr1);
}
public static void fun(Consumer<Integer[]> consumer, Integer[] arr){
consumer.accept(arr);
}
public static void sum(Integer[] arr){
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
System.out.println("数组的和为"+sum);
}
}
3.2 方法引用的分类
方法引用是Lambda表达式的一种简写形式。如果lambda表达式方法体中只是一个调用一个特点定的已经存在的方法,则可以使用方法引用。
3.2.1 静态方法引用
public class Test07 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(3);
list.add(1);
list.add(7);
list.add(2);
//使用传统方法
/*Comparator<Integer> comparator01 =(o1,o2)->Integer.compare(o1,o2);
list.sort(comparator01);*/
//使用静态方法引用
//如果在lambda表达式中有且仅有一条语句,而且这条语句是对方法的调用。
//这时可以考虑使用方法引用。
Comparator<Integer> comparator02 = Integer::compare;
list.sort(comparator02);
System.out.println(list);
}
}
3.2.2 实例方法引用
实例方法引用,顾名思义就是调用已经存在的方法,与静态方法引用不同的是类要先实例化,静态方法引用类无需实例化,直接用类名去调用.
public class Test08 {
public static void main(String[] args) {
Student student = new Student(18, "张三");
// 传统做法
Supplier<String> s1 = ()->student.getName();
//实例化方法引用
Supplier<String> s2 = student::getName;
fun(s2);
}
public static void fun(Supplier<String> supplier){
String s = supplier.get();
System.out.println("姓名是" +s);
}
}
class Student{
public int age;
public String name;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3.2.3 对象方法引用
若lambda参数列表中的第一个参数是实例方法的参数调用者, 而第二个参数是实例方法的参数时,可以使用对象方法引用。
public class Test09 {
public static void main(String[] args) {
//传统方法
//判断字符串长度
Function<String,Integer> function=(s -> s.length());
Integer hello = function.apply("hello");
System.out.println(hello);
//对象方法引用
Function<String,Integer> function1=String::length;
Integer hello1 = function1.apply("hello");
System.out.println(hello1);
}
}
public class Test09 {
public static void main(String[] args) {
//传统方法
//判断两个字符串相同
BiFunction<String,String,Boolean> function=(s, s1) -> s.equals(s1);
boolean apply = function.apply("hello", "hell");
System.out.println(apply);
//对象方法引用
BiFunction<String,String,Boolean> function1=String::equals;
boolean hello1 = function1.apply("hello", "hell");
System.out.println(hello1);
}
}
3.2.4 构造方法引用
构造方法引用允许我们以更简洁的方式表达创建对象的过程,而不是显式地调用构造函数。
public class Test10 {
public static void main(String[] args) {
//传统方法
Function<String,Person> function1 = (n) -> {
return new Person(n);
};
Person person1 = function1.apply("张三");
System.out.println(person1);
//构造方法引用
Function<String,Person> function2 = Person::new;
Person person2 = function2.apply("李四");
System.out.println(person2);
}
}
class Person{
public String name;
public Person(String name) {
this.name = name;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4.Stream流
java8的两个重大改变,应该是lambda表达式,另一个就是Stream API表达式。Stream是java8中处理集合的关键抽象概念,它可以对集合进行法非常复杂的查找,过滤,筛选等操作。
4.1 为什么使用Stream流
当我们需要对集合中的元素进行操作时,除了必要的添加,删除,获取外,最典型的就是集合遍历,我们来体验集合操作的弊端,需求如下:
public class Test01 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","周芷若","赵敏","张强","张三丰");
//1.拿到所有姓张的
ArrayList<String> zhangList = new ArrayList<>();
for (String name : list) {
if(name.startsWith("张")){
zhangList.add(name);
}
}
System.out.println(zhangList);
//2.拿到名字长度长度为3的
ArrayList<String> threeList = new ArrayList<>();
for (String name : zhangList) {
if(name.length() == 3){
threeList.add(name);
}
}
System.out.println(threeList);
//打印这些数据
for (String name : threeList) {
System.out.println(name);
}
}
}
这串代码中含有三个循环,每一个作用不同:
- 首先筛选所有姓张的人;
- 然后筛选名字有三个字的人;
- 最后进行对结果进行打印输出。
每当我们对集合中的元素进行操作时候,总是需要进行循环,循环,再循环,这是理所当然的吗?不是。循环式做事情的方式,而不是目的。每个需求都要循环一次,还要搞一个新集合来装数据,如果希望再次遍历,只能再使用一个循环从头开始。
那Stream能带给我们怎样更加优雅的写法呢?
public class Test02 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","周芷若","赵敏","张强","张三丰");
//将集合转为stream流
list.stream()
//filter断言型接口
.filter(s -> s.startsWith("张")).
filter(s -> s.length() == 3)
.forEach(System.out::println);
}
}
优点:对集合的操作简洁,性能比传统快,适合多线程
4.2 Stream流的原理
注意:Stream和IO流没有任何关系,请暂时忘记对传统IO流的固有印象
Stream流思想类似于工厂车间大的“生产流水线”Stream流不是一种你数据结构,不保存数据,而是对数进行加工处理。Stream可以看作流水线的一个工序。在流水线是,通过多个工序让一个原材料加工成一个商品。
Stream不存在数据,只对数据进行加工处理。
Stream有如下三个操作步骤
一,创建Stream
从一个数据源,如集合,数组中获取流
二,中间操作
一个操作的中间链,对数据源的数据进行操作
三,终止操作
一个终止操作,执行中间操作链,并产生结果
4.3 Stream流对象的获取方式
- 通过Collection对象的stream()或parallelStream()方法
- 通过Arrays类的stream方法
- 通过Stream接口的of(),iterate(),generate()方法
- 通过IntStream,LongStream,DoubleStream接口中的of,range,rangeClosed方法
//通过集合对象调用stream()获取流对象
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张三","李四","王五","赵六","田七");
Stream<String> stream = list.stream();
//通过数组(Arrays)工具类获取流对象
int [] arr = {1,2,3,4,5,6,7,8,9,10};
IntStream stream1 = Arrays.stream(arr);
//使用Stream类的静态方法of()获取流对象
Stream<String> stream2 = Stream.of("张三","李四","王五","赵六","田七");
//通过LongStream工具类获取流对象
LongStream range = LongStream.range(1, 10);
上面都是获取的串行流(顺序流),该可以获取并行流
stream和parallelStream的简单区分:stream是顺序流,由主线程按顺序执行操作,而parallelStream是并行流,内部以多线程并行的方法对流进行操作,但前提是流中数据处理没有顺序要求。例如筛选集合中的奇数。如果流中的数据量足够大,并行流可以加快处速读。两者的处理的不同之处:
//上面都是获取的串对象,还可以获取并行流对象
Stream<String> parallelStream = list.parallelStream();
//使用实例方法forEach()遍历流对象
stream.forEach(System.out::println);
}
4.4 Stream流中常见的api
中间操作api:一个操作的中间链,对数据源的数据进行操作,而这种操作的返回类型还是一个Stream对象。
终止操作api:一个终止操作,执行中间操作链,并产生结果,返回类型不在是Stream流对象。
标签:name,void,特性,JDK8,static,println,public,String From: https://blog.csdn.net/m0_72156649/article/details/140226748map--接收lambda,将元素转换成其它形式或者提取信息。接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射为一个新的元素。