使用Java8(可选)重构遗留代码<;T>;

使用Java8(可选)重构遗留代码<;T>;,java,lambda,refactoring,java-8,optional,Java,Lambda,Refactoring,Java 8,Optional,假设我有一个简单的模型UserInfo和Passport: public class UserInfo { private int age; private String passportId; // getters, setters } public class Passport { // empty } 如果用户年龄大于20岁,我希望看到他的护照,在Java7中,我会: UserInfo userInfo = new UserInfo();

假设我有一个简单的模型UserInfo和Passport:

public class UserInfo {
    private int age;
    private String passportId;
    // getters, setters
}

public class Passport {
    // empty
}
如果用户年龄大于20岁,我希望看到他的护照,在Java7中,我会:

    UserInfo userInfo = new UserInfo();
    userInfo.setAge(22);
    userInfo.setPassportId(null);

    final Passport passport;
    if (userInfo.getAge() > 20) {
        if (userInfo.getPassportId() == null) {
            throw new IllegalArgumentException("Set passport");
        }
        else {
            passport = findPassportById(userInfo.getPassportId());
        }
    }
这非常简单,但我想使用Java8可选的

如果您只需要使用nullable执行一个操作,则Optional似乎非常方便:

  • 使用.orelsethorn()
  • 使用.ifPresent()
  • 但当我尝试同时做这两件事时,我得到了一些丑陋的东西:

        String passportId = Optional.of(userInfo)
                .filter(x -> x.getAge() > 20)
                .map(UserInfo::getPassportId)
                .orElseThrow(() -> new IllegalArgumentException("Set passport"));
    
        final Passport passport;
        // null checks again? I don't want to!
        if (passportId != null) {
            passport = findPassportById(passportId);
        }
    
    那么,有没有任何实践可以以更干净的方式重构这种常见情况


    谢谢

    如果
    getPassportId
    返回null,
    orelsetrow
    将触发异常。在您的代码中,
    passportId
    将永远不会为
    null
    。您可以将其组合在一起,如下所示:

    Passport passport = Optional.of(userInfo)
      .filter(x -> x.getAge() > 20)
      .map(UserInfo::getPassportId)
      .map(this::findPassportById)  // or wherever findPassportById is defined
      .orElsethrow(...);
    

    如果
    getPassportId
    返回空值,
    orelsetrow
    将触发异常。在您的代码中,
    passportId
    将永远不会为
    null
    。您可以将其组合在一起,如下所示:

    Passport passport = Optional.of(userInfo)
      .filter(x -> x.getAge() > 20)
      .map(UserInfo::getPassportId)
      .map(this::findPassportById)  // or wherever findPassportById is defined
      .orElsethrow(...);
    

    你的主要误解是,你在课堂之外检查年龄
    UserInfo
    。事实上,您的用户信息类只是一个记录或结构

    这显然违反了OOP范式(即,这是过程风格)。什么,如果你也需要在其他地方检查这个年龄?是否确实要将此代码段复制到所有这些位置?如果年龄限制更改为另一个值(例如德国的18岁),会发生什么情况

    结论:年龄检查行为只能在一个地方进行。这被称为单一责任原则(SRP)。在这里,此功能显然属于类
    UserInfo

    因此,您可以将其更改为:

    public class UserInfo {
        private int age;
        private String passportId;
    
        ...
    
        public Optional<String> getPassportId() {
            checkPassport();
            return Optional.ofNullable(passportId);
        }
    
        private void checkPassport() {
            if (age > 20 && (passportId == null || passportId.isEmpty())) {
                throw ...
            }
        }
    }
    
    公共类用户信息{
    私人互联网;
    私有字符串密码;
    ...
    公共可选getPassportId(){
    支票护照();
    返回可选。不可用(passportId);
    }
    私人作废支票护照(){
    如果(年龄>20&(passportId==null | | passportId.isEmpty()){
    投
    }
    }
    }
    
    现在调用getter将导致异常或可能封装passport id的可选异常。抛出异常是否是正确的处理方式是另一个问题

    请注意,我没有详细阐述这个问题。例如,年龄检查本身应该再次外包到一个项目中


    另外,我强烈建议你去上这门课。这样做时,应该考虑在构建对象时检查参数。但这取决于您的应用程序要求。

    您的主要误解是,您检查了班级以外的年龄
    UserInfo
    。事实上,您的用户信息类只是一个记录或结构

    这显然违反了OOP范式(即,这是过程风格)。什么,如果你也需要在其他地方检查这个年龄?是否确实要将此代码段复制到所有这些位置?如果年龄限制更改为另一个值(例如德国的18岁),会发生什么情况

    结论:年龄检查行为只能在一个地方进行。这被称为单一责任原则(SRP)。在这里,此功能显然属于类
    UserInfo

    因此,您可以将其更改为:

    public class UserInfo {
        private int age;
        private String passportId;
    
        ...
    
        public Optional<String> getPassportId() {
            checkPassport();
            return Optional.ofNullable(passportId);
        }
    
        private void checkPassport() {
            if (age > 20 && (passportId == null || passportId.isEmpty())) {
                throw ...
            }
        }
    }
    
    公共类用户信息{
    私人互联网;
    私有字符串密码;
    ...
    公共可选getPassportId(){
    支票护照();
    返回可选。不可用(passportId);
    }
    私人作废支票护照(){
    如果(年龄>20&(passportId==null | | passportId.isEmpty()){
    投
    }
    }
    }
    
    现在调用getter将导致异常或可能封装passport id的可选异常。抛出异常是否是正确的处理方式是另一个问题

    请注意,我没有详细阐述这个问题。例如,年龄检查本身应该再次外包到一个项目中


    另外,我强烈建议你去上这门课。这样做时,应该考虑在构建对象时检查参数。但这取决于您的应用程序需求。

    我假设
    UserInfo
    不会为
    null
    ,因此将其包装成
    可选的
    是没有意义的。当年龄低于20岁时,你也不想继续

    另外需要记住的是,
    可选
    的要点是避免异常。如果
    passportId
    是一项基本属性,那么无论如何它都应该是强制性的(如果年龄>20岁)。否则,您将首先处理损坏的数据。另一方面,如果它不是强制性的,那么抛出异常就没有多大意义

    如果您不想强制执行,我建议对
    passportId
    设置一个
    可选的
    ,可以直接通过
    UserInfo
    本身。如果您仍然需要传统的getter,那么您可以使用属性名称添加一个新方法(这似乎是常见的做法,即使在JDK中也是如此)


    我假设
    UserInfo
    不会是
    null
    ,因此将其包装成
    可选的
    是没有意义的。当年龄低于20岁时,你也不想继续

    另外需要记住的是,
    可选
    的要点是避免异常。如果
    passportId
    是一项基本属性,那么无论如何它都应该是强制性的(如果年龄>20岁)。否则,您将首先处理损坏的数据。另一方面,如果它不是强制性的,那么抛出异常就没有多大意义

    如果您不想强制执行,我建议对
    passportId
    设置一个
    Optional