JDK15 is coming,Do you konw the new features of java8?(上)

简介: JDK15 is coming,Do you konw the new features of java8?(上)

你发任你发,我用Java8


一、Lambda表达式


lambda 表达式是一个匿名函数。 lambda 表达式允许把一个函数作为参数进行传递。

可能刚看到这两句话时,不知道是什么意思。那么,对比一下 js 中的 setInterval 函数的用法,你就能找到一些感觉了

//每一秒执行一次匿名函数。(模拟时钟)
setInterval(function() {
    console.log("当前时间为:" + new Date());
}, 1000);

如上,function(){}这段,就是一个匿名函数,并且可以把它作为参数传递给 setInterval 函数。

这是因为,在 js 中,函数是一等公民。

然而,在 Java 中,对象才是一等公民。但是,到了 JDK8 我们也可以通过 lambda 表达式表示同样的效果。

lambda 表达式语法如下:

(参数1,参数2) -> { 方法体 }

左边指定了 lambda 表达式所需要的所有参数,右边用来描述方法体。-> 即为 lambda 运算符。

想一下,在之前我们通过匿名内部类的方式来启动一个线程,是怎么做的?


public class LambdaTest {
    @Test
    public void test(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程运行...");
            }
        }).start();
    }
}

现在,若把它改为用 lambda 表达式,则为,


public class LambdaTest {
    @Test
    public void test(){
     // 一行搞定
        new Thread(()->System.out.println("线程运行...")).start();
    }
}


可以发现,明显用 lambda 表达式,写法更简洁了。

其实,Lambda 表达式就是函数式编程的体现。(什么,你还不知道什么是函数式编程?那还不赶快百度去。)


注意事项


1.参数列表的数据类型会自动推断。也就是说,如果匿名函数有参数列表的话,只需要写2.参数名即可,不需要写参数的类型。

3.如果参数列表为空,则左边只需要写小括号即可。

4.如果参数只有一个,则可以省略小括号,只写参数的名称即可。

5.如果方法体中只有一条执行语句,则可以省略右边的大括号。若有返回值,则可以把 6.return 和大括号同时省略。


二、接口默认方法和静态方法


2.1 接口默认方法


我们知道,在 Java 的接口中,只能定义方法名,不能实现方法体的,具体的实现需要子类去做。

但是,到了 JDK8 就不一样了。在接口中,也可以通过 default关键字来实现方法体。

那么,就有小伙伴疑惑了。好端端的,为什么要加入这个奇怪的功能呢,它有什么用?

当然是为了提高代码的重用性了。此外,接口的默认方法可以在不影响原来的继承体系的情况下,进行功能的拓展,实现接口的向下兼容。

我滴天,好抽象。那,就用实例来说明一下吧。

假设各种动物的继承体系如下,

public interface Animal {
    //所有动物都需要吃东西,具体吃什么,让子类去实现
    void eat();
}
public class Bird implements Animal {
    @Override
    public void eat() {
        System.out.println("早起的鸟儿有虫吃!");
    }
}
public class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("小猫爱吃鱼!");
    }
}


现在,需要对 Animal接口拓展功能了。动物不能只会吃东西吧,它也许会奔跑,也许会飞行。那么,我在接口中添加两个方法, run 和 fly 就可以了吧。


这样定义方法虽然是可以的,但是,问题就来了。接口中定义了方法,实现类就要实现它的所有方法。小猫会奔跑,但是不会飞啊。而小鸟会飞,你让它在地上跑不是委屈人家嘛。


所以,这个设计不是太合理。


此时,就可以在接口中定义默认方法。子类不需要实现所有方法,可以按需实现,或者直接使用接口的默认方法。


因此,修改 Animal 接口如下,把 run 和 fly 定义为默认方法,

public interface Animal {
    //所有动物都需要吃东西,具体吃什么,让子类去实现
    void eat();
    default void run(){
        System.out.println("我跑");
    }
    default void fly(){
        System.out.println("我飞");
    }
}
public class Main {
    public static void main(String[] args) {
        Bird bird = new Bird();
        bird.fly();
        Cat cat = new Cat();
        cat.run();
    }
}

在 JDK8 的集合中,就对 Collection 接口进行了拓展,如增加默认方法 stream() 等。既增强了集合的一些功能,而且也能向下兼容,不会对集合现有的继承体系产生影响。


2.2 接口静态方法


另外,在接口中也可以定义静态方法。这样,就可以直接通过接口名调用静态方法。(这也很正常,接口本来就不能实例化)

需要注意的是,不能通过实现类的对象去调用接口的静态方法。

public interface MyStaticInterface {
    static void method(){
        System.out.println("这是接口的静态方法");
    }
}
public class MyStaticInterfaceImpl implements MyStaticInterface {
    public static void main(String[] args) {
        //直接通过接口名调用静态方法,不能通过实现类的对象调用
        MyStaticInterface.method();
    }
}

三、函数式接口


如果一个接口中只有一个抽象方法,则称其为函数式接口。可以使用 @FunctionalInterface 注解来检测一个接口是否为函数式接口。


JDK提供了常见的最简单的四种函数式接口:(必须掌握哦)


1.Consumer,消费型接口。接收一个参数,没有返回值。其方法有:void accept(T t);

2.Supplier,供给型接口。没有参数,带返回值。其方法:T get();

3.Function<T, R>,函数型接口。接收一个参数,返回一个结果。其方法:R apply(T t);

4.Predicate,断言型接口。接收一个参数,返回boolean值。其方法:boolean test(T t);

我这里举例了它们的使用方法


public class LambdaTest {
    @Test
    public void test2(){
        //打印传入的 msg
        printMsg((s)-> System.out.println(s),"听朋友说「烟雨星空」公众号不仅文章好看,还免费送程序员福利,我心动了");
    }
    public void printMsg(Consumer<String> consumer,String msg){
        //消费型,只有传入参数,没有返回值
        consumer.accept(msg);
    }
    @Test
    public void test3(){
        //返回一个 0~99 的随机数
        Integer content = getContent(() -> new Random().nextInt(100));
        System.out.println(content);
    }
    public Integer getContent(Supplier<Integer> supplier){
        //供给型,传入参数为空,带返回值
        return supplier.get();
    }
    @Test
    public void test4(){
        //传入一个字符串,然后把它都转换成大写字母。
        System.out.println(transfer((str) -> str.toUpperCase(), "My wechat : mistyskys"));
    }
    public String transfer(Function<String,String> func,String str){
        // 函数型,传入一个参数,对其进行处理之后,返回一个结果
        return func.apply(str);
    }
    @Test
    public void test5(){
        //定义一个list,用来做筛选
        ArrayList<String> list = new ArrayList<>();
        list.add("zhangsan");
        list.add("lisi");
        list.add("jerry");
        list.add("tom");
        //筛选出集合中,字符串长度大于 3 的,并加入到结果集。
        List<String> filterResult = filter((str) -> str.length() > 3, list);
        System.out.println(filterResult.toString());
    }
    public List<String> filter(Predicate<String> predicate, List<String> list){
        List<String> result = new ArrayList<>();
        for (String str : list) {
            //断言型,传入一个参数,并返回true或者false。
            //这里的逻辑是,若断言为真,则把当前的字符串加入到结果集中
            if(predicate.test(str)){
                result.add(str);
            }
        }
        return result;
    }
   }


还有一些其他函数式接口,都在java.util.function包下,可以自行查看。使用方法都是一样的,不再赘述。


除此之外,JDK 中还有很多函数式接口,例如 Comparator.java。只要类上边看到了 @FunctionalInterface 这个注解,你都可以使用 lambda 表达式来简化写法。


四、方法引用


概念:方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。


这里强调一下已经存在的含义。因为,lambda表达式本质上就是一个匿名函数。我们知道,函数就是做逻辑处理的:拿一些数据,去做一些操作。


如果,我们发现有其他地方(类或者对象)已经存在了相同的逻辑处理方案,那么就可以引用它的方案,而不必重复写逻辑。这就是方法引用。


其实方法引用就是一个lambda表达式的另外一种更简洁的表达方式。也可以说是语法糖。


只不过,这里要求 lambda 表达式需要符合一定的要求。首先,方法体只有一行代码。其次,方法的实现已经存在。此时,就可以用方法引用替换 lambda 表达式。


方法引用的操作符为双冒号::。


下边就以最简单的一个我们非常常见的打印语句为例。


//遍历数组里边的元素,并打印,用lambda表达式
String[] arr = new String[]{"zhangsan","lisi"};
Arrays.asList(arr).forEach((s)-> System.out.println(s));

可以发现,lambda 表达式只有一行代码,且方法体逻辑为打印字符串。而打印字符串的方案,在 System.out 对象中已经存在方法 println() 了。

所以,此处 lambda 表达式可以用方法引用替换。

// 注意:方法引用中的方法名不可带括号。
Arrays.asList(arr).forEach(System.out::println);  


方法引用有以下四种形式


1.对象 :: 实例方法

2.类 :: 静态方法

3.类 :: 实例方法

4.类 :: new

下边举例说明:

public class ReferTest {
    public static void main(String[] args) {
        //函数式接口的抽象方法的参数列表和返回值类型,必须与方法引用对应的方法参数列表和返回值类型保持一致(情况3除外,比较特殊)。
        //======= 1.对象::实例方法 =========
        // lambda 表达式
        Consumer consumer1 = (s) -> System.out.println(s);
        consumer1.accept("hello world");
        //方法引用。Consumer的accept方法,和System.out的println方法结构一样,
        //都是传入一个参数,无返回值。故可以用方法引用。
        Consumer consumer2 = System.out::println;
        consumer2.accept("hello java");
        //======= 2.类::静态方法 =========
        Integer[] arr = new Integer[]{12,20,15};
        List<Integer> list = Arrays.asList(arr);
        // lambda 表达式
        Comparator<Integer> com1 = (o1, o2) -> Integer.compare(o1, o2);
        Collections.sort(list,com1);
        //方法引用。Comparator的compare方法,和Integer的compare静态方法结构一样,
        //都是传入两个参数,返回一个int值,故可以用方法引用。
        Comparator<Integer> com2 = Integer::compare;
        Collections.sort(list,com2);
        //======= 3.类::实例方法 =========
        // lambda表达式
        Comparator<Integer> com3 = (o1, o2) -> o1.compareTo(o2);
        //方法引用。这种形式比较特殊,(o1, o2) -> o1.compareTo(o2) ,
        //当第一个参数o1为调用对象,且第二个参数o2为需要引用方法的参数时,才可用这种方式。
        Comparator<Integer> com4 = Integer::compareTo;
        //======= 4.类::new =========
        // lambda表达式
        Supplier<String> supplier1 = () -> new String();
        //方法引用。这个就比较简单了,就是类的构造器引用,一般用于创建对象。
        Supplier<String> supplier2 = String::new;
    }
}


题外话:方法引用,有时候不太好理解,让人感觉莫名其妙。所以,如果不熟悉的话,用 lambda 表达式完全没有问题。就是习惯的问题,多写就有感觉了。


五、Optional


Optional 类是一个容器类。在之前我们通常用 null 来表达一个值不存在,现在可以用 Optional 更好的表达值存在或者不存在。


这样的目的,主要就是为了防止出现空指针异常 NullPointerException 。


我们知道,像层级关系比较深的对象,中间的调用过程很容易出现空指针,如下代码。

User user = new User();
//中间过程,user对象或者address对象都有可能为空,从而产生空指针异常
String details = user.getAddress().getDetails();


其中,对象的关系如下,


// 地址信息类
public class Address {
    private String province; //省
    private String city; //市
    private String county; //县
    private String details; //详细地址
    public String getProvince() {
        return province;
    }
    public void setProvince(String province) {
        this.province = province;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public String getCounty() {
        return county;
    }
    public void setCounty(String county) {
        this.county = county;
    }
    public String getDetails() {
        return details;
    }
    public void setDetails(String details) {
        this.details = details;
    }
}
//用户类
public class User {
    private String name;
    private Address address;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Address getAddress() {
        return address;
    }
    public void setAddress(Address address) {
        this.address = address;
    }
}


在 Optional 类出现之前,为了防止空指针异常,可以这样做。(每一层都添加判空处理)

private static String getUserAddr(User user){
    if(user != null){
        Address address = user.getAddress();
        if(address != null){
            return address.getDetails();
        }else {
            return "地址信息未填写";
        }
    }else {
        return "地址信息未填写";
    }
}


可以发现,代码冗长,还不利于维护,随着层级关系更深,将会变成灾难(是否依稀记得js的回调地狱)。


那么,有了 Optional 类,我们就可以写出更优雅的代码,并且防止空指针异常。(后边就填坑)


下面,就一起领略一下 Optional 的魅力吧!


5.1 创建 Optional 对象


实际上,Optional 是对原值(对象)的一层包装,我们看下 Optional 的源码就知道了。

它把真正需要操作的对象 T 封装成 value 属性。构造器私有化,并提供三种静态的创建 Optional 对象的方法。

public final class Optional<T> {
    //EMPTY 代表一个值为空的 Optional 对象
    private static final Optional<?> EMPTY = new Optional<>();
    //用 value 来代表包装的实际值
    private final T value;
    //值为null的构造函数
    private Optional() {
        this.value = null;
    }
    //要求值不为null的构造函数,否则抛出空指针异常,见requireNonNull方法
    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }
    /** 此为Objects类的requireNonNull方法
    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }
    */
    // 1. 创建一个值为空的 Optional 对象
    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }
    // 2. 创建一个值不为空的 Optional 对象
    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }
    // 3. 创建一个值可为空的 Optional 对象
    // 如果值 value 为空,则同1,若不为空,则同2
    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }
}


因此,当我们十分确定传入的user对象不为空时,可以用 Optional.of(user)方法。若不确定,则用 Optional.ofNullable(user),这样在后续的操作中可以避免空指针异常(后续map说明)。


5.2 常用方法


1、get方法

public T get() {
    //如果值为null,则抛出异常,否则返回非空值value
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

2、isPresent方法


//判断值是否存在,若值不为空,则认为存在
public boolean isPresent() {
    return value != null;
}


看到这,不知道有没有小伙伴和我当初有一样的疑惑。既然有判空方法 isPresent,还有获取对象的 get 方法。那开头的那个坑,是不是就可以改写为如下

//注意此时user类型为Optional<User>
private static String getUserAddr(Optional<User> user){
    //如果user存在,则取address对象
    if(user.isPresent()){
        Address address = user.get().getAddress();
        //把address包装成Optional对象
        Optional<Address> addressOptional = Optional.ofNullable(address);
        //如果address存在,则取details地址信息
        if(addressOptional.isPresent()){
            return addressOptional.get().getDetails();
        }else {
            return "地址信息未填写";
        }
    }else{
        return "地址信息未填写";
    }
}

这样看起来,好像功能也实现了。但是,我们先不说代码并没有简洁(反而更复杂了),其实是陷入了一个怪圈了。


因为,if(user.isPresent()){}和手动判空处理 if(user!=null){}实质上是没有区别的。这就是受之前一直以来的代码思维限制了。


所以,我们不要手动调用 isPresent 方法 。


不要奇怪,isPresent 方法,其实是为了 Optional 中的其他方法服务的(如map方法),本意并不是为了让我们手动调用。你会在后续多个方法中,见到 isPresent 的身影。


3、ifPresent

//传入一个消费型接口,当值存在时,才消费。
public void ifPresent(Consumer<? super T> consumer) {
    if (value != null)
        consumer.accept(value);
} 

与 isPresent 方法不同, ifPresent 方法是我们推荐使用的。

如可以这样判空

Optional<User> user = Optional.ofNullable(new User());
user.ifPresent(System.out::println);
//不要用下边这种
if (user.isPresent()) {
  System.out.println(user.get());
}

4、orElse 和 orElseGet

public T orElse(T other) {
    return value != null ? value : other;
}
public T orElseGet(Supplier<? extends T> other) {
    return value != null ? value : other.get();
}

这两个方法都是当值不存在时,用于返回一个默认值。如user对象为null时,返回默认值

@Test
public void test1(){
    User user = null;
    System.out.println("orElse调用");
    User user1 = Optional.ofNullable(user).orElse(createUser());
    System.out.println("orElseGet调用");
    User user2 = Optional.ofNullable(user).orElseGet(() -> createUser());
}
private User createUser() {
    //此处打印,是为了查看orElse和orElseGet的区别
    System.out.println("createUser...");
    return new User();
}
//打印结果
orElse调用
createUser...
orElseGet调用
createUser...

以上是user为null时,两个方法是没有区别的。因为都需要创建user对象作为默认值返回。


5、orElseThrow

6、map

7、flatMap

8、filter

目录
相关文章
|
4月前
|
Java
【Java基础面试三十二】、new String(“abc“) 是去了哪里,仅仅是在堆里面吗?
这篇文章解释了Java中使用`new String("abc")`时,JVM会将字符串直接量"abc"存入常量池,并在堆内存中创建一个新的String对象,该对象会指向常量池中的字符串直接量。
|
3月前
|
Java Linux
java基础(3)安装好JDK后使用javac.exe编译java文件、java.exe运行编译好的类
本文介绍了如何在安装JDK后使用`javac.exe`编译Java文件,以及使用`java.exe`运行编译好的类文件。涵盖了JDK的安装、环境变量配置、编写Java程序、使用命令行编译和运行程序的步骤,并提供了解决中文乱码的方法。
68 2
|
18天前
|
安全 Java 编译器
JDK 10中的局部变量类型推断:Java编程的简化与革新
JDK 10引入的局部变量类型推断通过`var`关键字简化了代码编写,提高了可读性。编译器根据初始化表达式自动推断变量类型,减少了冗长的类型声明。虽然带来了诸多优点,但也有一些限制,如只能用于局部变量声明,并需立即初始化。这一特性使Java更接近动态类型语言,增强了灵活性和易用性。
95 53
|
2月前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
27 1
|
3月前
|
Java API 调度
掌握Java线程状态:从NEW到TERMINATED
本文探讨了操作系统与Java中线程的状态及其转换。操作系统层面,线程状态包括初始、就绪、运行、阻塞和终止。Java线程状态则细分为NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED,并详细介绍了各状态的特性和转换条件。此外,还列举了Java中常用的线程方法,如`wait()`、`notify()`、`start()`和`join()`等,帮助理解线程控制机制。
108 3
掌握Java线程状态:从NEW到TERMINATED
|
3月前
|
Oracle Java 关系型数据库
Linux下JDK环境的配置及 bash: /usr/local/java/bin/java: cannot execute binary file: exec format error问题的解决
如果遇到"exec format error"问题,文章建议先检查Linux操作系统是32位还是64位,并确保安装了与系统匹配的JDK版本。如果系统是64位的,但出现了错误,可能是因为下载了错误的JDK版本。文章提供了一个链接,指向Oracle官网上的JDK 17 Linux版本下载页面,并附有截图说明。
Linux下JDK环境的配置及 bash: /usr/local/java/bin/java: cannot execute binary file: exec format error问题的解决
|
3月前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
69 11
|
3月前
|
监控 Java 大数据
【Java内存管理新突破】JDK 22:细粒度内存管理API,精准控制每一块内存!
【9月更文挑战第9天】虽然目前JDK 22的确切内容尚未公布,但我们可以根据Java语言的发展趋势和社区的需求,预测细粒度内存管理API可能成为未来Java内存管理领域的新突破。这套API将为开发者提供前所未有的内存控制能力,助力Java应用在更多领域发挥更大作用。我们期待JDK 22的发布,期待Java语言在内存管理领域的持续创新和发展。
|
3月前
|
Oracle Java 关系型数据库
【颠覆性升级】JDK 22:超级构造器与区域锁,重塑Java编程的两大基石!
【9月更文挑战第6天】JDK 22的发布标志着Java编程语言在性能和灵活性方面迈出了重要的一步。超级构造器和区域锁这两大基石的引入,不仅简化了代码设计,提高了开发效率,还优化了垃圾收集器的性能,降低了应用延迟。这些改进不仅展示了Oracle在Java生态系统中的持续改进和创新精神,也为广大Java开发者提供了更多的可能性和便利。我们有理由相信,在未来的Java编程中,这些新特性将发挥越来越重要的作用,推动Java技术不断向前发展。
|
4月前
|
Java
【Java基础面试二十八】、使用字符串时,new和““推荐使用哪种方式?
这篇文章讨论了在Java中使用字符串时,推荐使用双引号`""`直接量方式而不是使用`new`操作符,因为`new`会在常量池之外额外创建一个对象,导致更多的内存占用。
下一篇
无影云桌面