Java8新特性详解(一)

本文介绍了Java8中的Lambda表达式,包括它是匿名内部类的简写形式,以及不同场景下的使用方式。同时讲解了函数式接口的概念,四大核心接口如Consumer、Supplier、Function和Predicate的作用。此外,还提到了方法引用和构造器引用的使用,以及数组引用的创建。文章旨在帮助读者理解Java8的新特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本系列博客包含如下内容,此篇帖子介绍前3篇

一,Lambda表达式

二,函数式接口

三,方法引用与构造器引用

四,Stream API

链接 https://blog.youkuaiyun.com/weixin_43833851/article/details/129571127

五,接口中的默认方法与静态方法

六,新的日期、时间API

七,其他新特性

本文介绍前三节

Lambda表达式

  1. lambda是什么?

官方的概念就不说了,不太好理解,以我自己的理解而言,它就是一种简写的匿名内部类。

先简单对比一下:

1.1 使用匿名内部类实现Comparator

Comparator<Integer> comparator1 = new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
};

1.2 使用Lambda 实现Comparator

Comparator<Integer> comparator2 = (o1,o2) -> o1.compareTo(o2);

实际上就是对匿名内部类进一步进行了代码量的削减,省略了类名、方法名、参数类型,只保留形参和方法体,如下图:

1.3 为什么类名、方法名、参数类型可以省略?

因为进行了自动推导,具体展开来说

1.3.1 为什么类名可以省略

因为 等号 左边已经写了类型是Comparator,所以可以推导出 右边的匿名子对象一定是Comparator的实现类,所以可以省略类名;

1.3.2 为什么方法名可以省略

因为 Comparator 接口中,仅有一个需要实现的方法,所以方法名也是可以推导出来的,

这也是Lambda使用的一个限制,那就是对于接口而言,仅只有一个需要实现的方法时才能用Lambda表达式

只有一个抽象方法的interface称之为 "函数式接口",给interface加上注解 @FunctionalInterface可强制申明为 函数式接口,若定义多个抽象方法,就会报编译错误,如下:

1.3.2.1只定义一个方法时不报错

1.3.2.2定义多个方法时就报错了

1.3.3 为什么参数类型可以省略

既然类名、方法名都可以推导出来,那么方法的参数类型也是可以推导出来的,所以参数类型也可以省略;

那为什么参数名(比如o1和o2)不能省略呢?

因为你需要在方法体中使用参数,没有参数名,你咋使用这个参数了,比如compare方法的o1.compareTo(o2), 如果不保留参数名o1和o2,方法体就没法写了;

当然参数类型也是可以保留的,提升阅读性,比如这样写也是可以的:

Comparator<Integer> comparator2 = (Integer o1,Integer o2) -> o1.compareTo(o2);

1.3.4 小结

所以,我对于Lambda的理解,就认为是对匿名内部类的一种能省则省的简写(语法糖),它包含一个右箭头,箭头左边是参数,箭头右边是方法体,在不同场景,它有一些不同的写法,下面具体介绍一下它的不同写法

2,Lambda表达式的几种写法

2.1 场景一:需要覆盖的方法 无入参 、 无返回值、方法体仅有一句代码

例如Runnable的run方法,可以这样实现:

//使用匿名内部类实现Runnable
Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类创建线程");
            }
};
new Thread(runnable1).start();


//使用Lambda实现Runnable
Runnable runnable2 = ()-> System.out.println("Lambda创建线程");
new Thread(runnable2).start();

//也可直接写成一行,看起来就像把一段代码在作为参数传递
new Thread(()-> System.out.println("Lambda创建线程")).start();

因为没有入参,但是箭头左边必须有参数,所以左边仅写一对小括号即可,箭头右边是方法体,因为只有一句代码,所以方法体的 大括号 也省略了,这里给方法体加上大括号也是可以的。

2.2 场景二:需要覆盖的方法 只有1个入参、无返回值、方法体仅有一句代码

例如实现 java.util.function.Consumer 类,如下:

//使用匿名内部类实现 java.util.function.Consumer 类
Consumer<Integer> consumer1= new Consumer<Integer>() {
    @Override
    public void accept(Integer x) {
        System.out.println(x);
    }
};

//使用Lambda实现 java.util.function.Consumer 类
Consumer<Integer> consumer2 = x-> System.out.println(x);

//使用Lambda实现 java.util.function.Consumer 类,也可以保留参数类型
Consumer<Integer> consumer2 = (Integer x)-> System.out.println(x);

因为只有一个参数,所以连方法的小括号也省略了

2.3 场景三:需要覆盖的方法 有多个入参、有返回值、方法体只有一句代码

例如上面的 Comparator 接口,如下:

//匿名内部类实现
Comparator<Integer> comparator1 = new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
};

//Lambda实现, 省略return
Comparator<Integer> comparator2 = (o1,o2) -> o1.compareTo(o2);

//Lambda实现, 不省略return
Comparator<Integer> comparator3 = (o1,o2) -> {return o1.compareTo(o2);};

因为有多个入参,所以多个入参都需要写出来,此时小括号不能省略

由于方法体只有一句代码,所以方法体的大括号可以省略;

注意:

方法体只有一句代码时,return 也可以省略,因为就一句代码,如果需要return,那么return的一定是这句代码的结果,所以return可以省略,但是这样写是错误的:

//这样写是错误的
Comparator<Integer> comparator3 = (o1,o2) -> return o1.compareTo(o2);

但如果是多句代码,return 和 大括号都不可省略

2.4 场景四:需要覆盖的方法 有多个入参、有返回值、方法体有多句代码

例如上面的 Comparator 接口,如下:

Comparator<Integer> comparator3 = (o1,o2) -> {
    System.out.println("这是多行代码的示例");
    return o1.compareTo(o2);
};

此时方法体的大括号、return 都不能省略。

2.5 小结

对于上述几种场景,不用死记硬背,只是在说参数的括号、方法体的括号、return 什么时候可以省略,如果记不住不省略也是可以的,写的时候开发工具也是有提示的,不用纠结。

3,Lambda表达式的四大核心函数式接口

3.1 为什么要有这4个接口

先看下面的铺垫便于理解,如果不想看,直接跳到3.2

示例:对一个数字进行多种处理,比如取平方、取立方、取负数等,分别看下几种实现方式

第一种做法:每种处理写一个方法,

public static void main(String[] args) {
    pingFang(5);
    liFang(5);
    fuShu(5);
}
//取平方
public static int pingFang(int x){
    return x*x;
}

//取立方
public static int liFang(int x){
    return x*x*x;
}

//取负数
public static int fuShu(int x){
    return -x;
}

第二种做法:定义一个接口,处理逻辑写在接口的实现类中(策略模式)

//定义一个接口
public interface NumberProcess {
    int process(int x);
}

//测试类
public class Test{
    public static void main(String[] args) {
        //取平方
       int res= calc(5, new NumberProcess() {
            @Override
            public int process(int x) {
                return x*x;
            }
        });

        //取立方
        res= calc(5, new NumberProcess() {
            @Override
            public int process(int x) {
                return x*x*x;
            }
        });
    }

    //定义一个公共的计算方法
    public static int calc(int x,NumberProcess p){
        return p.process(x);
    }
}

第二种比第一种稍好一些,不需要有N个处理就写N个方法,但是需要额外定义一个接口,如果能把这个接口省略就更好了,所以java就定义了一些通用功能的接口,比如接口 java.util.function.Function;

其源码如下:

这是一个函数式接口(接口内只有一个抽象方法),接口内定义了一个apply方法,有两个泛型,第一个泛型T是入参类型,第二个泛型R是返回值类型,所以我们就可以不用自己定义上面的NumberProcess接口了,使用Function接口代替,因此有了第三种做法。

第三种做法:使用Function接口:

入参和返回值都是Integer类型

//测试类
public class Test{
    public static void main(String[] args) {
        //取平方
       int res = calc(5, new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer x) {
                return x*x;
            }
        });

        //取立方
        res = calc(5, new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer x) {
                return x*x*x;
            }
        });
    }

    //定义一个公共的计算方法
    public static int calc(int x,Function<Integer,Integer> f){
        return f.apply(x);
    }
}

这个写法,就可以使用Lambda进行简写,由此产生第四种做法。

第四种做法:使用Lambda对第三种进行简写

//测试类
public class Test{
    public static void main(String[] args) {
        //取平方
       int res = calc(5, x -> x*x);

        //取立方
        res = calc(5, x -> x*x*x);
    }

    //定义一个公共的计算方法
    public static int calc(int x,Function<Integer,Integer> f){
        return f.apply(x);
    }
}

小结:

为了简化一些策略模式的功能开发,java为我们提供了4种接口,上面用到的Function接口就是其中之一,让我们不需要再定义自己的接口就可以实现一些策略模式的功能。

3.2 有哪4个核心函数式接口?各有什么用途?

这4个接口的 入参个数 和 返回值类型 不一样

① java.util.function.Consumer;

有一个入参,无返回值时可以使用这个接口, 之所以叫消费者,就是因为它只进不出,比如上面3.1的例子就不适合使用它,因为3.1的例子有入参也有返回值(传入一个数字x,返回x的平方或立方)

示例:

Consumer<Integer> consumer = id->{
    System.out.println("在这里做无返回值的操作");
    System.out.println("比如执行delete语句,删除id="+id+"的数据");
};
consumer.accept(123);

② java.util.function.Supplier;

无入参,但是有返回值时可以使用这个接口,之所以叫提供者,就是因为它能无中生有,比如产生随机数的方法就可以用它来做

示例:

//产生随机数的例子,当然这里是为了使用而使用,旨在演示
Supplier<Integer> supplier = ()-> new Random().nextInt();
int randNum = supplier.get();

③ java.util.function.Function;

有一个入参和一个返回值,示例可参考3.1的第四种做法

还有个类似接口java.util.function.BiFunction; 它有两个入参,前两个泛型T 、U都是入参

如果入参个数超过2个,可以考虑使用集合类型作为入参、或者自己定义一个Bean

④ java.util.function.Predicate;

一个入参,返回值为boolean, 用来实现一些判断逻辑

示例:

这里使用到了stream方法,看不懂不要紧,主要关注Predicate接口:它只有一个入参、返回值为boolean

public class Test1 {

    static List<User> userList = Arrays.asList(new User("A",20),
            new User("B",25),new User("C",30));
    
    public static void main(String[] args) {

        //需求:获取age>20的user,存入新的list集合中
        
        //第一种方式:通过Predicate的内部类实现
        List<User> list = userList.stream().filter(new Predicate<User>() {
            @Override
            public boolean test(User user) {
                return user.getAge() > 20;
            }
        }).collect(Collectors.toList());

        //第二种方式:通过lambda简化内部类写法
        list = userList.stream().filter(user -> user.getAge() > 20).collect(Collectors.toList());
    }
}

小结:

这四大核心接口先了解即可,它们在Stream API中会大量使用

4,方法引用、构造器引用、数组引用

4.1 方法引用

就是双冒号的用法( :: 的用法), 怎么理解它呢?看个例子:

//User类
public class User {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

//测试方法,用到了java.util.function.Supplier类
public static void main(String[] args) {
    User user = new User();
    user.setName("tom");
    
    //使用匿名内部类写
    Supplier<String> supplier2 = new Supplier<String>() {
        @Override
        public String get() {
            return user.getName();
        }
    };
    String name = supplier2.get();


    //使用方法引用进行改写,引用user类的getName方法作为Supplier的实现
    Supplier<String> supplier = user::getName;
    name = supplier.get();
}

实例中使用了两种写法,是等效的, 我对于方法引用的理解

它也是一种Lambda表达式,前面说Lambda是一种匿名内部类的简写,那么 “方法引用”既然也是一种Lambda表达式,它必然也是一种匿名内部类的简写,即对接口实现的简写,只不过以往的 接口实现代码 都是我们自己在写,而这个“方法引用” 就是我们不用自己写接口实现的代码了,直接告诉它接口的实现代码 是哪个类的哪个方法(告诉它Supplier接口的实现代码就是user对象的getName方法),对象和方法名之间用双冒号隔开。

注意:引用某个对象的某个方法作为 接口的实现代码,那么一定要保证: 接口的方法的入参类型和返回值类型 一定要与你引用的方法 的入参类型和返回值类型 一样,否则不可以引用,如下:

//User类
public class User {
    private String name;
    private int 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;
    }
}

//MyInterface 接口
@FunctionalInterface
public interface MyInterface {
    String hello();
}

测试方法如下:

可以看到user::getAge报错了,该方法不能作为MyInterface接口的实现,因为MyInterface的返回值类型是String,而getAge返回int,所以getAge不能被引用做MyInterface的实现方法

方法引用有三种使用方式:

① 对象::实例方法名

示例:

public class User {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public static void main(String[] args) {
    User user = new User();
    user.setName("tom");

    //引用user对象的getName方法作为Supplier的实现
    Supplier<String> supplier = user::getName;
    String name = supplier.get();
}

方法引用时,方法的入参是可以省略的,把上面的user.setName用Consumer接口改写,加深理解,如下:

public static void main(String[] args) {
    User user = new User();

    //引用user对象的setName方法作为Consumer的实现
    Consumer<String> consumer = user::setName;
    consumer.accept("Tom");//类似调用了user。setName("Tom")

    //引用user对象的getName方法作为Supplier的实现
    Supplier<String> supplier = user::getName;
    String name = supplier.get();

    System.out.println("name="+name);
}

方法引用,说简单点就是不用写接口的实现代码了,直接引用某个类的某个方法作为实现代码。

使用条件:

接口的方法的入参类型和返回值类型 一定要与你引用的方法 的入参类型和返回值类型 一样。

② 类名::静态方法名

public static void main(String[] args) {
    //这两种写法等价
    Comparator<Integer> comparator1 = (x,y) -> Integer.compare(x,y);
    Comparator<Integer> comparator2 = Integer::compare;
}

使用条件:

接口的方法的入参类型和返回值类型 一定要与你引用的方法 的入参类型和返回值类型 一样。

③ 类名::实例方法名

public static void main(String[] args) {
    //这三种写法等价
    Comparator<String> comparator1 = new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o1.compareTo(o2);
        }
    };

    Comparator<String> comparator2 = (x,y) -> x.compareTo(y);

    Comparator<String> comparator3 = String::compareTo;
    
}

使用条件:

1) 接口的方法的入参类型和返回值类型 一定要与你引用的方法 的入参类型和返回值类型 一样。比如上例中入参都是2个String类型,返回值都是int类型。

2) 都有2个入参,且第一个参数用来调用 引用的方法,将第二个参数作为被引用方法的入参传入

可以看出这种使用方式的条件更为特殊.

4.2 ,构造器引用

一种特殊的方法引用,用法:类名::new

示例:

//User对象
public class User {
    private String name;
    private int age;
    public User() {
    }
    public User(String name) {
        this.name = name;
    }
    public User(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 void main(String[] args) {
    //因为Supplier接口的方法是无参的,因此匹配User的无参构造
   Supplier<User> supplier1 = User::new;
   User user1 = supplier1.get();

    //因为Function接口的方法有一个入参,且泛型为String,因此匹配User(String) 的构造
   Function<String,User> function1 = User::new;
   User user2 = supplier1.get();

    //因为BiFunction接口的方法有2个入参,且入参泛型为String和Integer,因此匹配User(String,Integer) 的构造
    BiFunction<String,Integer,User> function2 = User::new;
    User user3 = supplier1.get();
}

4.3,数组引用

一种特殊的方法引用,用于创建数组,用法: 数组类型[]::new

public static void main(String[] args) {
    //创建一个长度为x的User[]几种写法

    //第一种
    User[] xx = new User[3];

    //第二种
    Function<Integer,User[]> function1 = x->new User[x];
    xx = function1.apply(3);

    //第三种,数组引用
    Function<Integer,User[]> function2 = User[]::new;
    xx = function2.apply(3);

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值