如何更好地重构可以在 java 中返回 null 的方法链?

Posted

技术标签:

【中文标题】如何更好地重构可以在 java 中返回 null 的方法链?【英文标题】:How better refactor chain of methods that can return null in java? 【发布时间】:2013-01-04 17:22:13 【问题描述】:

我有这样的代码:

obj1 = SomeObject.method1();
if (obj1 != null) 
  obj2 = obj1.method2();
  if (obj2 != null) 
     obj3 = obj2.method3();
     if (obj3 != null) 
              ............


     return objN.methodM();

   
  
 
....

我有近 10 个步骤。它看起来非常脆弱且容易出错。有没有更好的方法来检查空链方法?

谢谢。

【问题讨论】:

不幸的是它很脆弱,并不是因为它可能返回null。应该避免这样的链,否则您可能会产生依赖关系。 如果空值很少,不要检查它们,使用异常来处理错误。 @Leonidos 返回 null 是否错误取决于合约(例如,如果没有可用的控制台,java.lang.System.console() 可以返回 null)。我要改变的一件事是否定检查并检查结果是否is null,并在这种情况下从方法返回。这避免了范围的深度嵌套(我发现通常很难阅读)。 这些对象都属于同一类型吗? 如果有的话,知道其中哪一个是 null 重要吗? 【参考方案1】:

要很好地回答这个问题,需要更多的上下文。

例如,在某些情况下,我主张将内部 if 语句分解为它们自己的方法,遵循“每个方法应该完全正确地做一件事情”。在这种情况下,调用该方法并检查 null 这一点:如果它为 null,则返回(或抛出,取决于您的实际需要)。如果不是,则调用 next 方法。

不过,我最终怀疑这是一个设计问题,如果不深入了解正在解决的问题,解决方案是不可知的。

就目前而言,这一段代码需要深入了解(我怀疑是)多种职责,这意味着在几乎所有情况下,都需要新类、新模式、新接口或某种组合来实现这两者干净,易于理解。

【讨论】:

【参考方案2】:

我们可以使用 Java8 函数式接口方​​法。

@FunctionalInterface
public interface ObjectValue<V> 
    V get();


static <V> V getObjectValue(ObjectValue<V> objectValue)  
    try 
        return objectValue.get();
     catch (NullPointerException npe) 
        return null;
    


Object obj = getObjectValue(() -> objectA.getObjectB().getObjectC().getObjectD());
if(Objects.nonNull(obj)) 
//do the operation

【讨论】:

好方法,但您不需要创建另一个接口,使用标准的java.util.function.Supplier 接口。您可以在此处查看完整示例:link【参考方案3】:

您可以使用java.util.Optional.map(..) 链接这些检查:

return Optional.ofNullable(SomeObject.method1())
        .map(SomeObject2::method2)
        .map(SomeObject3::method3)
        // ....
        .map(SomeObjectM::methodM)
        .orElse(null);

【讨论】:

您能解释一下使用“map() 链接这些检查的含义吗?”。我不明白。 @likejudo 方法Optional.map(..) 将在存在值的情况下调用提供的方法引用,因此它间接用作空检查【参考方案4】:

像这样写

obj1 = SomeObject.method1();
if (obj1 == null) 
    return;
 obj2 = obj1.method2();
 if (obj2 == null) 
    return;

等等。作为 C 开发人员,这是一个非常常见的范例,并且非常普遍。如果无法将您的代码转换为这种扁平流,那么您的代码首先需要重构,无论它使用哪种语言。

return 替换为您在这些失败的情况下实际执行的操作,无论是return nullthrow 异常等 - 您已经省略了代码的那部分,但它应该是同样的逻辑。

【讨论】:

一个方法的多个退出点不会使代码更清晰。 @GaborSch 在这种情况下它也不会使它变得更复杂:快速失败,并且不会强迫代码阅读器跳过大块代码以查看如果 isn 会发生什么't null,增加可读性。 @DaveNewton 这是我可以接受的一点,但通常最好不要早点跳出来。如果你只是return null;,那没关系,但如果有更复杂的返回语句,如return new("blahblah", 12, methodX(obj.whatever()));,它只会让读者感到困惑(尝试找出 10 个相似行之间的差异)。也会增加出错的机会。 @GaborSch 我同意最后一点,但是 1)如果 OP 在... 的其余代码中这样做,使用建议的样式仍然会更清晰,2)如果OP 无论如何都在这样做,如前所述,无论语言/风格如何,都需要重构代码。 returns 很可能应该替换为 throw IllegalArgumentException,因为该函数具有无法满足的先决条件,或者如果所有代码库都是面向 return null 的,则记录和 return null @GaborSch 我不同意可以进行概括。 IMO 它如果很清楚,很早,就会更容易阅读代码。如果您有一个过于复杂的 return 语句,就像在您的示例中一样,我发现 (a) 它不太可能在所有返回点以相同的方式构造,并且 (b) 如果可以,开发人员不会创建保存该值以供重复使用的局部变量。【参考方案5】:

null 在 java 中的引用很常见。

我更喜欢用&amp;&amp;链接:

if (obj1 != null && obj1.method1() != null && obj1.method1().method2() != null)

【讨论】:

这很好,但不适用于 10 种方法,无法看到代码在做什么。 我们不知道这些方法没有副作用,为了避免多次调用它们,如果你要与&amp;&amp;链接,你应该使用if ((obj2 = obj1.method2()) != null &amp;&amp; (obj3 = obj2.method3()) != null &amp;&amp; ...)。跨度> @NikitaKouevda 是的,+1【参考方案6】:

我想这种问题已经回答了here。尤其是关于Null Object Pattern的第二个回答。

【讨论】:

【参考方案7】:

尝试这样格式化:

obj1 = SomeObject.method1();
if (obj1 != null) 
   obj2 = obj1.method2();

if (obj2 != null) 
    obj3 = obj2.method3();

if (obj3 != null) 
          ............


if (objN != null) 
   return objN.methodM();

return null;

别忘了将你所有的objs 初始化为null

【讨论】:

@Fildor 嵌套块无法查看。 没错。我只是想知道我是否错过了除此之外的任何内容。【参考方案8】:
obj1 = SomeObject.method1();
if (obj1 == null) throw new IllegalArgumentException("...");

obj2 = obj1.method2();
if (obj2 == null) throw new IllegalArgumentException("...");

obj3 = obj2.method3();
if (obj3 == null) throw new IllegalArgumentException("...");

if (objN != null) 
   return objN.methodM();

更多讨论here

【讨论】:

在调用这个函数的函数中;p @Arpit ftr 这不仅仅是一个玩笑,它很可能是正确的设计。 (“可能”是因为如果整个代码库的目标是在出错时返回 null,那么作为一个依赖异常的函数并不是很好。) 是的。我在嘲笑你的';p'【参考方案9】:

您可以链接它们并用 try/catch 包围所有内容并捕获 NPE。

像这样:

try

    Object result = SomeObject.method1().method2().methodN();
    return result;

catch(NullPointerException ex)

     // Do the errorhandling here.

除此之外,我对@Neil 的评论表示赞同:首先要尽量避免这种链条。

编辑:

投票表明这是非常有争议的。我想确保大家理解,我实际上并不推荐这个!

这样的操作有很多副作用,通常应该避免。 我只是将其用于讨论对于 OP 的特殊情况,仅作为实现目标的一种方式(如果不可能的话)!

如果有人觉得他需要这样做:请阅读 cmets 以了解可能的陷阱!

【讨论】:

抛出和捕获异常是很繁重的操作,不要那样做。 这样你就可以吞下NullPointerException,它可以被任何链式方法抛出。 我真的不建议这样做。在这种情况下,您现在如何处理异常,因为 method2 返回 null 或 methodN 由于某些错误而抛出异常? 谁说它很重要?根据 OP 所说,他只需要链成员不为空。 ***.com/questions/3490770/… 阅读此主题会改变我对速度的看法..【参考方案10】:

如果您使用的是 Java 8 或更高版本,请考虑使用Optional

Check chains of "get" calls for null How to avoid checking for null values in method chaining?

【讨论】:

【参考方案11】:

如果您希望对您的源对象如此信任以至于您计划将其中六个链接在一起,那么只需继续信任它们并在抛出异常时捕获它们 - 希望很少。

但是,如果您决定不信任您的源对象,那么您有两个选择:在任何地方添加强制“!= null”检查并且不要链接它们的方法...

或者返回并更改您的源对象类并在根处添加更好的空处理。您可以手动完成(例如,在 setter 中使用 null 检查),或者您可以使用 Java 8 中的 Optional 类(如果您不在 Java 8 上,则使用 Optional in Google's Guava),这提供了一个固执己见空值处理设计模式可帮助您在引入不需要的空值时做出反应,而不是等待一些可怜的消费者稍后遇到它们。

【讨论】:

【参考方案12】:

对于没有参数的getter方法,试试这个:

Util.isNull(person, "getDetails().iterator().next().getName().getFullName()")

在大多数情况下,它运作良好。基本上,它是尝试使用java反射逐层做空检查,直到它到达最后一个getter方法,因为我们做了很多缓存 反思,代码在生产中运行良好。请检查下面的代码。

public static boolean isNull(Object obj, String methods) 
    if (Util.isNull(obj)) 
        return true;
    
    if (methods == null || methods.isEmpty()) 
        return false;
    
    String[] A = methods.split("\\.");
    List<String> list = new ArrayList<String>();
    for (String str : A) 
        list.add(str.substring(0, str.indexOf("(")).trim());
    
    return isNullReflect(obj, list);

public static boolean isNullReflect(Object obj, List<String> methods) 
    if (Util.isNull(obj)) 
        return true;
    
    if (methods.size() == 0) 
        return obj == null;
    
    Class<?> className = Util.getClass(obj);
    try 
        Method method = Util.getMethod(className.getName(), methods.remove(0), null, className);
        method.setAccessible(true);
        if (method.getName().equals("next")
                && !Util.isNull(Util.getMethod(className.getName(), "hasNext", null, className))) 
            if (!((Iterator<?>) (obj)).hasNext()) 
                return true;
            
        
        try 
            return isNullReflect(method.invoke(obj), methods);
         catch (IllegalAccessException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
         catch (IllegalArgumentException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
         catch (InvocationTargetException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        
     catch (SecurityException e) 
        // TODO Auto-generated catch block
        e.printStackTrace();
    
    return false;



public static Boolean isNull(Object object) 
    return null == object;


public static Method getMethod(String className, String methodName, Class<?>[] classArray, Class<?> classObj) 
    // long a = System.nanoTime();
    StringBuilder sb = new StringBuilder();
    sb.append(className);
    sb.append(methodName);
    if (classArray != null) 
        for (Class<?> name : classArray) 
            sb.append(name.getName());
        
    
    String methodKey = sb.toString();
    Method result = null;
    if (methodMap.containsKey(methodKey)) 
        return methodMap.get(methodKey);
     else 
        try 
            if (classArray != null && classArray.length > 0) 
                result = classObj.getMethod(methodName, classArray);
             else 
                result = classObj.getMethod(methodName);
            
            methodMap.put(methodKey, result);
         catch (NoSuchMethodException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
         catch (SecurityException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        
    
    // long b = System.nanoTime();
    // counter += (b - a);
    return result;

    private static Map<String, Method> methodMap = new ConcurrentHashMap<String, Method>();

【讨论】:

这个答案让我很高兴:D 等待允许方法参数的增强版本! 既然我们在这个解决方案上玩得很开心,您能透露一下在isNullReflect(..) 中调用的Util.getClass(obj) 的内部结构吗?

以上是关于如何更好地重构可以在 java 中返回 null 的方法链?的主要内容,如果未能解决你的问题,请参考以下文章

重构此代码以更好地利用 CSharpFunctionalExtensions 库

如何更好地使用Java 8的Optional

当操作返回元页面 html 时,如何更好地诊断 WCF 服务?

如何在Java代码中去掉烦人的“!=null”

.NET 设计:冒泡异常以更好地重构实体消费者,这是一种好习惯吗?

Vue 重构有赞商城