【Java8新特性】史上最全Optional实战教程,太厉害了!

news2024/12/29 7:33:59

目录

一、前置基础

二、什么是Optional

2.1理论拓展

三、为什么要用Optional

3.1俄罗斯式套娃判空详解

四、Optional基本知识

4.1API的思考

五、工作中如何正确使用Optional

5.1 orElseThrow

5.2 filter

5.3 orElse和orElseGet

5.4 map和flatMap

5.5 项目实战

实战一

实战二

实战三 简化if.else

实战四 解决checkStyle问题

实战五 Optional提升代码的可读性

实战六 大胆重构代码

实战七 舍弃三目运算

六、Optional操作总结

七、Optional错误使用


一、前置基础

Optional类源码大量使用到:
1.四大函数式接口
2.lambda表达式

二、什么是Optional

1.Java 8新增了一个类 - Optional
2.Optional是一个容器,用于放置可能为空的值,它可以合理而优雅的处理 null。
3.Optional的本质,就是内部储存了一个真实的值,在构造的时候,就直接判断其值是否为空
4.java.util.Optional<T>类本质上就是一个容器,该容器的数值可以是空代表一个值不存在,也可以是非空代表一个值存在。
5.Optional类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。

2.1理论拓展

  Monad 是一种用于处理副作用的编程模式,简单来说就是将一些可能产生副作用的操作封装起来,并在特定的作用域内执行,控制其对程序产生的影响。在函数式编程中经常使用 Monad 模式来处理一些副作用,如 IO 操作、异常处理、状态管理等。
Optional 是 Java 中一个非常典型的 Monad 实现,它的主要作用是避免空指针异常并对可能为空的对象进行封装,并提供一系列函数式的操作,如 map()filter()flatMap()等方法,使代码更加健壮、优雅和安全。就像我们平时经常对空值进行判空处理一样,Optional 提供了一种更加优美和方便的方式,避免了深层次的嵌套判空,同时增加了代码的可读性和可维护性。
  对于函数式编程和 Monad 模式来说,这种方式是非常重要的,因为随着程序的规模增大,副作用也会越来越多,这时候避免副作用对程序的影响就变得尤为重要。通过使用 Monad 模式和类似 Optional 这样的容器类型,我们可以更好地控制副作用,使程序更加稳定和可靠。

三、为什么要用Optional

1.要是用来解决程序中常见的 NullPointerException异常问题。但是在实际开发过程中很多人都是在一知半解的使用 Optional,类似 if (userOpt.isPresent()){...}这样的代码随处可见。如果是这样我更愿意看到老老实实的 null 判断,这样强行使用 Optional反而增加了代码的复杂度。
2.这是一个明确的警示,用于提示开发人员此处要注意null值。
3.不显式的判空,当出现俄罗斯式套娃判空时,代码处理上更加优雅。
4.使用 Optional 有时候可以很方便的过滤一些属性,而且它的方法可以通过链式调用,方法间相互组合使用,使我们用少量的代码就能完成复杂的逻辑。
5.防止空指针(NPE)、简化if...else...判断、减少代码圈复杂度
6.Optional 之所以可以解决 NPE 的问题,是因为它明确的告诉我们,不需要对它进行判空。它就好像十字路口的路标,明确地告诉你该往哪走
7.很久很久以前,为了避免 NPE,我们会写很多类似if (obj != null) {}的代码,有时候忘记写,就可能出现 NPE,造成线上故障。在 Java 技术栈中,如果谁的代码出现了 NPE,有极大的可能会被笑话,这个异常被很多人认为是低级错误。Optional的出现,可以让大家更加轻松的避免因为低级错误被嘲讽的概率。
8.第一是改变我们传统判空的方式(其实就是帮我们包装了一层,判空的代码帮我们写了),用函数式编程和申明式编程来进行对基本数据的校验和处理。第二就是声明式的编程方式对阅读代码的人更友好。

3.1俄罗斯式套娃判空详解

  手动进行 if(obj!=null)的判空自然是最全能的,也是最可靠的,但是怕就怕俄罗斯套娃式的 if判空。
举例一种情况:
为了获取:省(Province)→市(Ctiy)→区(District)→街道(Street)→道路名(Name)
作为一个“严谨且良心”的后端开发工程师,如果手动地进行空指针保护,我们难免会这样写:

public String getStreetName( Province province ) {
    if( province != null ) {
        City city = province.getCity();
        if( city != null ) {
            District district = city.getDistrict();
            if( district != null ) {
                Street street = district.getStreet();
                if( street != null ) {
                    return street.getName();
                }
            }
        }
    }
    return "未找到该道路名";
}
为了获取到链条最终端的目的值,直接链式取值必定有问题,因为中间只要某一个环节的对象为 null,则代码一定会炸,并且抛出 NullPointerException异常,然而俄罗斯套娃式的 if判空实在有点心累。
Optional接口本质是个容器,你可以将你可能为 null的变量交由它进行托管,这样我们就不用显式对原变量进行 null值检测,防止出现各种空指针异常。
Optional语法专治上面的俄罗斯套娃式 if 判空,因此上面的代码可以重构如下:

public String getStreetName( Province province ) {
    return Optional.ofNullable( province )
            .map( i -> i.getCity() )
            .map( i -> i.getDistrict() )
            .map( i -> i.getStreet() )
            .map( i -> i.getName() )
            .orElse( "未找到该道路名" );
}

漂亮!嵌套的 if/else判空灰飞烟灭!
解释一下执行过程:
ofNullable(province ) :它以一种智能包装的方式来构造一个 Optional实例, province是否为 null均可以。如果为 null,返回一个单例空 Optional对象;如果非 null,则返回一个 Optional包装对象
map(xxx ):该函数主要做值的转换,如果上一步的值非 null,则调用括号里的具体方法进行值的转化;反之则直接返回上一步中的单例 Optional包装对象
orElse(xxx ):很好理解,在上面某一个步骤的值转换终止时进行调用,给出一个最终的默认值

四、Optional基本知识

Optional类常用方法:

Optional.of(T t) : 创建一个 Optional 实例。

Optional.empty() : 创建一个空的 Optional 实例。

Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例。

isPresent() : 判断是否包含值。

orElse(T t) : 如果调用对象包含值,返回该值,否则返回t。

orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值。

map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()。

flatMap(Function mapper):与 map 类似,要求返回值必须是Optional。

4.1API的思考

1.of(T value)
一个东西存在那么自然有存在的价值。当我们在运行过程中,不想隐藏NullPointerException。
而是要立即报告,这种情况下就用Of函数。但是不得不承认,这样的场景真的很少。我也仅在写junit测试用例中用到过此函数。

2.get()
直观从语义上来看,get() 方法才是最正宗的获取 Optional 对象值的方法,
但很遗憾,该方法是有缺陷的,因为假如 Optional 对象的值为 null,该方法会抛出 NoSuchElementException 异常。这完全与我们使用 Optional 类的初衷相悖。

五、工作中如何正确使用Optional

5.1 orElseThrow

orElseThrow()方法当遇到一个不存在的值的时候,并不返回一个默认值,而是抛出异常。

public void validateRequest(String requestId) {
    Optional.ofNullable(requestId)
            .orElseThrow(() -> new IllegalArgumentException("请求编号不能为空"));
    // 执行后续操作
}
Optional<User> optionalUser = Optional.ofNullable(null);
User user = optionalUser.orElseThrow(() -> new RuntimeException("用户不存在"));

// 传入 null 参数,获取一个 Optional 对象,并使用 orElseThrow 方法
    try {
        Optional optional2 = Optional.ofNullable(null);
        Object object2 = optional2.orElseThrow(() -> {
                    System.out.println("执行逻辑,然后抛出异常");
                    return new RuntimeException("抛出异常");
                }
        );
        System.out.println("输出的值为:" + object2);
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }

5.2 filter

接收一个函数式接口,当符合接口时,则返回一个Optional对象,否则返回一个空的Optional对象。
例如,我们需要过滤出年龄在25岁到35岁之前的人群,那在Java8之前我们需要创建一个如下的方法来检测每个人的年龄范围是否在25岁到35岁之前。

public boolean filterPerson(Peron person){
    boolean isInRange = false;
    if(person != null && person.getAge() >= 25 && person.getAge() <= 35){
        isInRange =  true;
    }
    return isInRange;
}

public boolean filterPersonByOptional(Peron person){
     return Optional.ofNullable(person)
       .map(Peron::getAge)
       .filter(p -> p >= 25)
       .filter(p -> p <= 35)
       .isPresent();
}
使用Optional看上去就清爽多了,这里,map()仅仅是将一个值转换为另一个值,并且这个操作并不会改变原来的值。

     public class OptionalMapFilterDemo {
    public static void main(String[] args) {
        String password = "password";
        Optional<String>  opt = Optional.ofNullable(password);

        Predicate<String> len6 = pwd -> pwd.length() > 6;
        Predicate<String> len10 = pwd -> pwd.length() < 10;
        Predicate<String> eq = pwd -> pwd.equals("password");

        boolean result = opt.map(String::toLowerCase).filter(len6.and(len10 ).and(eq)).isPresent();
        System.out.println(result);
    }
}

5.3 orElse和orElseGet

结论:当optional.isPresent() == false时,orElse()和orElseGet()没有区别;
而当optional.isPresent() == true时,无论你是否需要,orElse始终会调用后续函数。

若方法不是纯计算型的,使用Optional的orElse(T);
若有与数据库交互或者远程调用的,都应该使用orElseGet(Supplier)。
推荐使用orElseGet ,当存在一些复合操作,远程调用,磁盘io等大开销的动作禁止使用orElse。
原因:当value不为空时,orElse仍然会执行。

public class GetValueDemo {
    public static String getDefaultName() {
        System.out.println("Getting Default Name");
        return "binghe";
    }

    public static void main(String[] args) {
/*        String text = null;
        System.out.println("Using orElseGet:");
        String defaultText = Optional.ofNullable(text).orElseGet(GetValueDemo::getDefaultName);
        assertEquals("binghe", defaultText);
        System.out.println("Using orElse:");
        defaultText = Optional.ofNullable(text).orElse(GetValueDemo.getDefaultName());
        assertEquals("binghe", defaultText);*/

        // TODO: 2023/5/13 重点示例
        String name = "binghe001";

        System.out.println("Using orElseGet:");
        String defaultName = Optional.ofNullable(name).orElseGet(GetValueDemo::getDefaultName);
        assertEquals("binghe001", defaultName);

        System.out.println("Using orElse:");
        defaultName = Optional.ofNullable(name).orElse(getDefaultName());
        assertEquals("binghe001", defaultName);
    }  
}    

运行结果如下所示。
Using orElseGet:
Using orElse:
Getting default name...
可以看到,当使用orElseGet()方法时,getDefaultName()方法并不执行,因为Optional中含有值,而使用orElse时则照常执行。所以可以看到,当值存在时,orElse相比于orElseGet,多创建了一个对象。如果创建对象时,存在网络交互,那系统资源的开销就比较大了,这是需要我们注意的一个地方。

5.4 map和flatMap

        String len = null;
        Integer integer = Optional.ofNullable(len)
                .map(s -> s.length())
                .orElse(0);
        System.out.println("integer = " + integer);


        Person person = new Person("evan", 18);
        Optional.ofNullable(person)
                .map(p -> p.getName())
                .orElse("");

        Optional.ofNullable(person)
                .flatMap(p -> Optional.ofNullable(p.getName()))
                .orElse("");

注意:方法getName返回的是一个Optional对象,如果使用map,我们还需要再调用一次get()方法,而使用flatMap()就不需要了。

5.5 项目实战

实战一

public class OptionalExample {
    /**
     * 测试的 main 方法
     */
    public static void main(String[] args) {
        // 创建一个测试的用户集合
        List<User> userList = new ArrayList<>();

        // 创建几个测试用户
        User user1 = new User("abc");
        User user2 = new User("efg");
        User user3 = null;

        // 将用户加入集合
        userList.add(user1);
        userList.add(user2);
        userList.add(user3);

        // 创建用于存储姓名的集合
        List<String> nameList = new ArrayList();
        List<User> nameList03 = new ArrayList();
        List<String> nameList04 = new ArrayList();
        // 循环用户列表获取用户信息,值获取不为空且用户以 a 开头的姓名,
        // 如果不符合条件就设置默认值,最后将符合条件的用户姓名加入姓名集合
/*
        for (User user : userList) {
            nameList.add(Optional.ofNullable(user).map(User::getName).filter(value -> value.startsWith("a")).orElse("未填写"));
        }
*/

        // 输出名字集合中的值
/*        System.out.println("通过 Optional 过滤的集合输出:");
        System.out.println("nameList.size() = " + nameList.size());
        nameList.stream().forEach(System.out::println);*/


/*        Optional.ofNullable(userList)
                .ifPresent(u -> {
                    for (User user : u) {
                        nameList04.add(Optional.ofNullable(user).map(User::getName).filter(f -> f.startsWith("e")).orElse("无名"));
                    }
                });*/

        Optional.ofNullable(userList)
                .ifPresent(u -> {
                   u.forEach(m->{
                       Optional<String> stringOptional = Optional.ofNullable(m).map(User::getName).filter(f -> f.startsWith("a"));
                       stringOptional.ifPresent(nameList04::add);
                   });
                });
        System.out.println("nameList04.size() = " + nameList04.size());
        nameList04.forEach(System.err::println);


        Optional.ofNullable(userList).ifPresent(nameList03::addAll);
        System.out.println("nameList03.size() = " + nameList03.size());
        nameList03.stream().forEach(System.err::println);

    }

}

实战二

以前写法
public String getCity(User user)  throws Exception{
        if(user!=null){
            if(user.getAddress()!=null){
                Address address = user.getAddress();
                if(address.getCity()!=null){
                    return address.getCity();
                }
            }
        }
        throw new Excpetion("取值错误"); 
    }


    public String getCity(User user) throws Exception{
    return Optional.ofNullable(user)
                   .map(u-> u.getAddress())
                   .map(a->a.getCity())
                   .orElseThrow(()->new Exception("取指错误"));
}

实战三 简化if.else

以前写法
public User getUser(User user) throws Exception{
    if(user!=null){
        String name = user.getName();
        if("zhangsan".equals(name)){
            return user;
        }
    }else{
        user = new User();
        user.setName("zhangsan");
        return user;
    }
}

java8写法
public User getUser(User user) {
    return Optional.ofNullable(user)
                   .filter(u->"zhangsan".equals(u.getName()))
                   .orElseGet(()-> {
                        User user1 = new User();
                        user1.setName("zhangsan");
                        return user1;
                   });
}

实战四 解决checkStyle问题


BaseMasterSlaveServersConfig smssc = new BaseMasterSlaveServersConfig();
if (clientName != null) {
    smssc.setClientName(clientName);
}
if (idleConnectionTimeout != null) {
    smssc.setIdleConnectionTimeout(idleConnectionTimeout);
}
if (connectTimeout != null) {
    smssc.setConnectTimeout(connectTimeout);
}
if (timeout != null) {
    smssc.setTimeout(timeout);
}
if (retryAttempts != null) {
    smssc.setRetryAttempts(retryAttempts);
}
if (retryInterval != null) {
    smssc.setRetryInterval(retryInterval);
}
if (reconnectionTimeout != null) {
    smssc.setReconnectionTimeout(reconnectionTimeout);
}
if (password != null) {
    smssc.setPassword(password);
}
if (failedAttempts != null) {
    smssc.setFailedAttempts(failedAttempts);
}
// ...后面还有很多这种判断,一个if就是一个分支,会增长圈复杂度


改造后:
Optional.ofNullable(clientName).ifPresent(smssc::setClientName);
Optional.ofNullable(idleConnectionTimeout).ifPresent(smssc::setIdleConnectionTimeout);
Optional.ofNullable(connectTimeout).ifPresent(smssc::setConnectTimeout);
Optional.ofNullable(timeout).ifPresent(smssc::setTimeout);
Optional.ofNullable(retryAttempts).ifPresent(smssc::setRetryAttempts);
Optional.ofNullable(retryInterval).ifPresent(smssc::setRetryInterval);
Optional.ofNullable(reconnectionTimeout).ifPresent(smssc::setReconnectionTimeout);
// ...缩减为一行,不但减少了圈复杂度,而且减少了行数

实战五 Optional提升代码的可读性

传统操作:

public class ReadExample {
    //    举个栗子:你拿到了用户提交的新密码,你要判断用户的新密码是否符合设置密码的规则,比如长度要超过八位数,然后你要对用户的密码进行加密。
    private static String newPSWD = "12345679";
    public static void main(String[] args) throws Exception {
        // 简单的清理
        newPSWD = ObjectUtil.isEmpty(newPSWD) ? "" : newPSWD.trim();
        // 是否符合密码策略
        if (newPSWD.length() <= 8) throw new Exception("Password rules are not met: \n" + newPSWD);
        // 加密
        //将 MD5 值转换为 16 进制字符串
        try {
            final MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update(newPSWD.getBytes(StandardCharsets.UTF_8));
            newPSWD = new BigInteger(1, md5.digest()).toString(16);
        } catch (
                NoSuchAlgorithmException e) {
            System.out.println("Encryption failed");
        }
        System.out.println("We saved a new password for the user: \n" + newPSWD);
    }
}

优化版本:

优化一:
public class BetterReadExample {
    //    举个栗子:你拿到了用户提交的新密码,你要判断用户的新密码是否符合设置密码的规则,比如长度要超过八位数,然后你要对用户的密码进行加密。
    private static String newPSWD = "888888888";
    public static void main(String[] args) throws Exception {

        Function<String, String> md = (o) -> {
            try {
                final MessageDigest md5;
                md5 = MessageDigest.getInstance("MD5");
                md5.update(o.getBytes(StandardCharsets.UTF_8));
                return new BigInteger(1, md5.digest()).toString(16);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("Encryption failed");
            }
        };


        String digestpwd;
        digestpwd = Optional.ofNullable(newPSWD)
                            .map(String::trim)
                            .filter(f -> f.length() > 8)
                            .map(md)
                            .orElseThrow(() -> new RuntimeException("Incorrect saving new password"));
        System.err.println("digestpwd = " + digestpwd);

    }
}

优化二:
/**
 *增加可读性
 */
public class BetterReadExample02 {
    //    举个栗子:你拿到了用户提交的新密码,你要判断用户的新密码是否符合设置密码的规则,比如长度要超过八位数,然后你要对用户的密码进行加密。
    private static String newPSWD = "888888888";

    //清除
    private static String clean(String s){
        return s.trim();
    }

    private static boolean filterPw(String s){
        return s.length()>8;
    }

    private static RuntimeException myREx() {
        return new RuntimeException("Incorrect saving new password");
    }

    public static void main(String[] args) throws Exception {
        //项目实战中,把main方法里面的代码再抽出一个独立方法
        Function<String, String> md = (o) -> {
            try {
                final MessageDigest md5;
                md5 = MessageDigest.getInstance("MD5");
                md5.update(o.getBytes(StandardCharsets.UTF_8));
                return new BigInteger(1, md5.digest()).toString(16);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("Encryption failed");
            }
        };

        String digestpwd;
        digestpwd = Optional.ofNullable(newPSWD)
                            .map(BetterReadExample02::clean)
                            .filter(BetterReadExample02::filterPw)
                            .map(md)
                            .orElseThrow(BetterReadExample02::myREx);
        System.err.println("digestpwd = " + digestpwd);

    }
}

实战六 大胆重构代码

//1. map 示例
if ( hero != null){
   return "hero : " + hero.getName() + " is fire...";
 } else { 
   return "angela";
 }
 //重构成
 String heroName = hero
 .map(this::printHeroName)
 .orElseGet(this::getDefaultName);

public void printHeroName(Hero dog){
   return  "hero : " + hero.getName() + " is fire...";
}
public void getDefaultName(){
   return "angela";
}

//2. filter示例
Hero hero = fetchHero();
if(hero != null && hero.hasBlueBuff()){
  hero.fire();
}

//重构成
Optional<Hero> optionalHero = fetchHero();
optionalHero
 .filter(Hero::hasBlueBuff)
 .ifPresent(this::fire);

实战七 舍弃三目运算

//第一种判空
if (Objects.notNull(taskNode.getFinishTime())) {
  taskInfoVo.set(taskNode.getFinishTime().getTime());
}
//第二种判空 保留builder模式
TaskInfoVo
.builder()
.finishTime(taskNode.getFinishTime() == null ? null : taskNode.getFinishTime().getTime())
.build()));

//第三种判空
public Result<TaskInfoVo> getTaskInfo(String taskId){
  TaskNode taskNode = taskExecutor.getByTaskId(String taskId);
  //返回任务视图
  TaskInfoVo taskInfoVo = TaskInfoVo
                      .builder()
                      .taskName(taskNode.getName())
                      .finishTime(Optional.ofNullable(taskNode.getFinishTime()).map(date ->date.getTime()).orElse(null))
             .user(taskNode.getUser())
                     .memo(taskNode.getMemo())
                     .build()));;

  return Result.ok(taskInfoVo);
}

六、Optional操作总结

NPE 之所以讨厌,就是只要出现 NPE,我们就能够解决。但是一旦出现,都已经是事后,可能已经出现线上故障。偏偏在 Java 语言中,NPE 又很容易出现。Optional提供了模板方法,有效且高效的避免 NPE。

接下来,我们针对上面的使用,总结一下:
Optional是一个包装类,且不可变,不可序列化
没有公共构造函数,创建需要使用of、ofNullable方法
空Optional是单例,都是引用Optional.EMPTY
想要获取Optional的值,可以使用get、orElse、orElseGet、orElseThrow

另外,还有一些实践上的建议:
使用get方法前,必须使用isPresent检查。但是使用isPresent前,先思考下是否可以使用orElse、orElseGet等方法代替实现。
orElse和orElseGet,优先选择orElseGet,这个是惰性计算
Optional不要作为参数或者类属性,可以作为返回值
尽量将map、filter的函数参数抽出去作为单独方法,这样能够保持链式调用
不要将null赋给Optional 虽然Optional支持null值,但是不要显示的把null 传递给Optional
尽量避免使用Optional.get()
当结果不确定是否为null时,且需要对结果做下一步处理,使用Optional;
在类、集合中尽量不要使用Optional 作为基本元素;
尽量不要在方法参数中传递Optional;
不要使用 Optional 作为Java Bean Setter方法的参数
因为Optional 是不可序列化的,而且降低了可读性。
不要使用Optional作为Java Bean实例域的类型
原因同上。

七、Optional错误使用

1.使用在 POJO 中

public class User {
    private int age;
    private String name;
    private Optional<String> address;
}

这样的写法将会给序列化带来麻烦,Optional本身并没有实现序列化,现有的 JSON 序列化框架也没有对此提供支持的。

2.使用在注入的属性中
这种写法估计用的人会更少,但不排除有脑洞的。

public class CommonService {
    private Optional<UserService> userService;
    public User getUser(String name) {
        return userService.ifPresent(u -> u.findByName(name));
    }
}

首先依赖注入大多在 spring 的框架之下,直接使用 @Autowired很方便。但如果使用以上的写法,如果 userService set 失败了,程序就应该终止并报异常,并不是无声无息,让其看起来什么问题都没有。

3.直接使用 isPresent() 进行 if 检查
这个直接参考上面的例子,用 if判断和 1.8 之前的写法并没有什么区别,反而返回值包了一层 Optional,增加了代码的复杂性,没有带来任何实质的收益。其实 isPresent()一般用于流处理的结尾,用于判断是否符合条件。

list.stream()
    .filer(x -> Objects.equals(x,param))
    .findFirst()
    .isPresent()

4.在方法参数中使用 Optional
我们用一个东西之前得想明白,这东西是为解决什么问题而诞生的。Optional直白一点说就是为了表达可空性,如果方法参数可以为空,为何不重载呢?包括使用构造函数也一样。重载的业务表达更加清晰直观。

//don't write method like this
public void getUser(long uid,Optional<Type> userType);

//use Overload
public void getUser(long uid) {
    getUser(uid,null);
}
public void getUser(long uid,UserType userType) {
    //doing something
}   

5.直接使用 Optional.get
Optional不会帮你做任何的空判断或者异常处理,如果直接在代码中使用 Optional.get()和不做任何空判断一样,十分危险。这种可能会出现在那种所谓的着急上线,着急交付,对 Optional也不是很熟悉,直接就用了。这里多说一句,可能有人会反问了:甲方/业务着急,需求又多,哪有时间给他去做优化啊?因为我在现实工作中遇到过,但这两者并不矛盾,因为代码行数上差别并不大,只要自己平时保持学习,都是信手拈来的东西。

如对您有帮助,欢迎点赞,嘿嘿 !!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/535059.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

16位单片机去哪儿了?

关注星标公众号&#xff0c;不错过精彩内容 作者 | strongerHuang 微信公众号 | strongerHuang 最近网友问了一个问题&#xff1a;为什么现在很少看见16位单片机了&#xff1f; 你是不是也有这样的疑问&#xff1a;现在市面上大多都是32位Arm Coretx-M内核的单片机&#xff0c;…

〖技术人职业规划白宝书 - 职业规划篇①〗- 大学生选择职业前的自我认知与剖析

历时18个月&#xff0c;采访 850 得到的需求。 不管你是在校大学生、研究生、还是在职的小伙伴&#xff0c;该专栏有你想要的职业规划、简历、面试的答案。说明&#xff1a;该文属于 技术人职业规划白宝书 专栏&#xff0c;购买任意白宝书体系化专栏可加入TFS-CLUB 私域社区&am…

【JS】1684- 重学 JavaScript API - Resize Observer API

❝ 前期回顾&#xff1a; 1. Page Visibility API 2. Broadcast Channel API 3. Beacon API ❞ &#x1f3dd; 什么是 Resize Observer API Resize Observer API[1] 可以帮助我们监听元素尺寸的变化&#xff0c;并在尺寸变化时执行一些操作。例如&#xff0c;我们可以使用 Resi…

突发!骨灰级程序员心梗离世!

大家注意&#xff1a;因为微信最近又改了推送机制&#xff0c;经常有小伙伴说错过了之前被删的文章&#xff0c;比如前阵子冒着风险写的爬虫&#xff0c;再比如一些限时福利&#xff0c;错过了就是错过了。 所以建议大家加个星标&#xff0c;就能第一时间收到推送。&#x1f44…

什么是鉴权?这些postman鉴权方式你又知道多少?

一、什么是鉴权&#xff1f; 鉴权也就是身份认证&#xff0c;就是验证您是否有权限从服务器访问或操作相关数据。发送请求时&#xff0c;通常必须包含相应的检验参数以确保请求具有访问权限并返回所需数据。通俗的讲就是一个门禁&#xff0c;您想要进入室内&#xff0c;必须通过…

PostgreSQL 为什么PG 的适用性很强(译)

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

ChatGPT常用提问技巧

上篇文章《ChatGPT万能提问技巧》中提到的万能提问技巧-RPEP提问法&#xff0c;家人们用ChatGPT能够轻松应对大部分的对话场景&#xff0c;获得更加出色的回答了吧&#xff01;今天&#xff0c;我们在提供两种常用的提问模式&#xff0c;让家人们使用ChatGPT都很轻松获得需要的…

Hive on Spark调优(大数据技术3)

3.1 Executor配置说明 3.1.1 Executor CPU核数配置 单个Executor的CPU核数&#xff0c;由spark.executor.cores参数决定&#xff0c;建议配置为4-6&#xff0c;具体配置为多少&#xff0c;视具体情况而定&#xff0c;原则是尽量充分利用资源。 此处单个节点共有16个核可供Exec…

时尚达人的深度学习:非洲服装检测

介绍 时尚在医疗、教育和农业等不同领域的人工智能领域并没有受到太多关注&#xff0c;包括机器学习、深度学习。这是因为时尚不被认为是一个关键领域. 时尚和文化在 AI 中并没有得到公正的对待。这是一个令人兴奋的项目&#xff0c;我们将在 8 种当地非洲服装之间建立一个分类…

科大讯飞版ChatGPT,厉害了!

前几天科大讯飞的星火认知大模型发布了&#xff0c;我刚好有朋友在科大讯飞工作&#xff0c;于是就第一时间体验了一波。 一番体验下来确实比我预想的效果要好&#xff0c;没想到国产模型的效果还不错&#xff0c;我试了很多方面&#xff0c;比如通用常识功能、写作功能、学习能…

2023 开源之夏来啦!报名 MegEngine 项目,赢取万元奖金!

“开源软件供应链点亮计划-暑期2023”是一项面向高校学生的暑期活动&#xff0c;为高校学生提供了绝佳的、友好开放的交流平台。使学生可以真正投身于开源软件的开发维护&#xff0c;得到资深开源软件开发者指导的机会&#xff0c;获得丰硕的活动奖金&#xff0c;并帮助学生获取…

【送书】前端系列14集-Vue3-setup

送书活动&#xff1a;挑选1名粉丝同学哦 免费包邮送。截止时间&#xff1a;2023/5/18号 19 : 00参与底部评论区说说&#xff1a;请在评论中分享你的阅读收获。 前端工程化&#xff1a;基于Vue.js 3.0的设计与实践实战 页面浏览量&#xff08;Page View&#xff0c;PV&#xff…

【004】C++数据类型之字符类型(char)详解

C数据类型之字符类型详解 引言一、背景知识二、字符常量三、字符变量四、键盘&#xff08;输入设备&#xff09;给字符变量赋值五、字符常量和字符串常量的区别六、案例&#xff1a;字符大小写转换总结 引言 &#x1f4a1; 作者简介&#xff1a;专注于C/C高性能程序设计和开发&…

知识变现海哥|为什么你无法将学到的知识变现

秋叶大叔在公众号里发了这样一篇文章《为什么今天的年轻人更要读大学&#xff1f;》&#xff0c;文章里提到这样一个观点&#xff1a;一个人的专业能力要获得优质的回报&#xff0c;还需要学一样「变现技能」。 我不由得想问一个问题&#xff1a;你真的理解变现能力吗&#xf…

微信公众号服务号客服对接-唯一客服系统文档中心

微信公众号客服功能&#xff0c;需要公众号为认证的服务号&#xff0c;可以实现公众号模板消息提醒&#xff0c;网页授权获取到微信的昵称头像&#xff0c;机器人或AI自动回复功能等 微信公众号后台设置 【设置与开发】【基本配置】 设置好公众号的AppID AppSecret IP白名单 【…

亚马逊云科技助力默沙东,用数字化技术赋能医药市场营销

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 在全球药企中&#xff0c;默沙东是封神的存在。提及其明星药物&#xff0c;我们接种的乙肝疫苗、麻疹疫苗、宫颈癌九价疫苗、快速短效麻醉剂、抗生素青霉素钾等药物皆出自于默沙东。 2022年&#xff0c;全球药企TOP 10营业收…

ChatGPT国内免费使用方法有哪些?

目录 ChatGPT介绍:一、ChatGPT是什么?二、ChatGPT发展:三、ChatGPT 优点:四、国内使用ChatGPT方法五、结语: ChatGPT介绍: 一、ChatGPT是什么? ChatGPT 是一个基于语言模型 GPT-3.5 的聊天机器人&#xff0c;ChatGPT模型是Instruct GPT的姊妹模型&#xff08;siblingmodel&a…

Lighttpd服务器安装

目录 >1服务器安装配置 1.解压 2.进入源码目录&#xff0c;创建文件夹web 3.执行configure脚本文件 4.执行Makefile文件 >2目录创建及文件移动 1.将源码目录lighttpd-1.4.54下web文件夹移动到某个路径下 2.在web目录下创建文件夹&#xff08;config、log、run、www&#…

jsp+springboot酒店客房管理系统 i59uz

&#xff08;1&#xff09;管理员模块&#xff1a;系统记录管理员号以及密码&#xff0c;以及对权限的设置&#xff0c;对酒店进行信息管理&#xff0c;对房客进行信息的登记和修改&#xff0c;以及对酒店的信息进行维护。 Spring Boot 是 Spring 家族中的一个全新的框架&#…

【JVM】2. 类加载子系统

文章目录 2.1. 内存结构概述&#x1f352;2.2. 类加载器与类的加载过程&#x1f352;2.2.1 加载阶段&#x1f353;&#x1f353;2.2.2 链接阶段&#x1f353;&#x1f353;2.2.3 初始化阶段&#x1f353;&#x1f353; 2.3. 类加载器分类&#x1f352;2.3.1. 虚拟机自带的加载器…