2

我是一个 Objective-C 人,开始从事一个小型个人 Java 项目。在我的 Objective-C 项目中,我练习防御性编码,并经常使用Wil Shipley 的IsEmpty(id thing)方法在使用前检查 null 或空值:

static inline BOOL IsEmpty (id thing)
{
    return thing == nil
        || ([thing respondsToSelector:@selector(length)] && [(NSData *)thing length] == 0)
        || ([thing respondsToSelector:@selector(count)] && [(NSArray *)thing count] == 0);
}

我知道 Apache Commons 会做这样的事情(尽管在我看来,它仅适用于 String 对象),并且我尝试在 Java 中实现类似的东西,但找不到调用length()或方法的size()方法在编译时类未知的对象上。

我是在尝试将方形钉子装入圆孔吗?如果不是,那么经验丰富的 Java 开发人员将如何实现通用检查以确保大量对象不为空,并且如果它们实现了.length()or.size()方法,它们也不为空?

编辑:虽然一些答案侧重于实现等效isEmpty方法(pgreen2 的答案似乎确实有效),但CodaFiThe Tosters强调的真正问题似乎是利用弱类型的 Objective-C 编码实践可能无法很好地转化为强类型范式,如 Java(“方钉,圆孔”)。这并不是说任何一种打字方法都是可取的。它们只是不同,需要不同的做法。

4

3 回答 3

3

在我看来,这是java中非常糟糕的习惯。由于许多 instanceof 检查或 try/catch 递归,此方法将“非常繁重”。此外,如果集合为 NULL 或它只是空的,上面的一些示例会导致您丢失信息。在 OBJ-C 中这没有问题,因为您总是可以在 nil 指针上调用方法,但是这对于 java 是非常不同的。我相信这可能会导致对算法进行适当控制的许多问题。例如,您可能会做类似的条件

if (Helper.isEmpty(someList) == true) {
    someList = new ArrayList<>();
}

如果这是一个数组,它被几个方法调用作为参数的缓冲区传递下来,这可能会导致很难找到的错误。

基本上在输入您的方法时,您知道出现什么类型的对象,因此您可以立即进行检查,而无需进入长时间的类型检查方法,该方法在几次 if 后会发现您从一开始就知道的参数类型。

于 2013-07-11T19:41:33.647 回答
2

我没有经验,但看起来它的打字比java弱得多。您的问题的第一部分非常简单。

//this checks for null objects
public static boolean isEmpty(Object o) {
  return o == null;
}

但是,在您的示例代码中,您正在检查具有特定名称的特定字段。这在java中要复杂得多。您可以使用反射,但您需要检查方法和字段。然后,您需要检查适当的返回类型:Number、int、long、short、float、double。所以,这是可能的,但需要大量的工作并且会很慢。

一个更简单但更具限制性的机制是只检查常见类型:( 更新以修复枚举错误并添加数组;由于数组在 java 中的工作方式,必须为每个基元分解数组)

public static boolean isEmpty(Object o) {
    if (o == null) {
        return true;
    }
    else if (o instanceof Object[]) {
        return ((Object[]) o).length <= 0;
    }
    else if (o instanceof boolean[]) {
        return ((boolean[]) o).length <= 0;
    }
    else if (o instanceof byte[]) {
        return ((byte[]) o).length <= 0;
    }
    else if (o instanceof short[]) {
        return ((short[]) o).length <= 0;
    }
    else if (o instanceof char[]) {
        return ((char[]) o).length <= 0;
    }
    else if (o instanceof int[]) {
        return ((int[]) o).length <= 0;
    }
    else if (o instanceof long[]) {
        return ((long[]) o).length <= 0;
    }
    else if (o instanceof float[]) {
        return ((float[]) o).length <= 0;
    }
    else if (o instanceof double[]) {
        return ((double[]) o).length <= 0;
    }
    else if (o instanceof CharSequence) {
        return ((CharSequence) o).length() <= 0;
    }
    else if (o instanceof Collection) {
        return ((Collection) o).isEmpty();
    }
    else if (o instanceof Map) {
        return ((Map) o).isEmpty();
    }
    else if (o instanceof Enumeration) {
        return !((Enumeration) o).hasMoreElements();
    }
    else if (o instanceof Dictionary) {
        return ((Dictionary) o).isEmpty();
    }
    else if (o instanceof Iterable) {
        // NOTE: may not be efficient because an iterator is created
        return !((Iterable) o).iterator().hasNext();
    }

  return false;
}

更新:以下是以前版本的反射检查方法,代码可以扩展以支持字段。处理返回类型并不像我最初想象的那么困难。即使使用反射,它也会使自动装箱工作。我还检查了对象是否具有 isEmpty() 布尔方法。

public static boolean isEmpty(final Object o) {
    if (o == null) {
        return true;
    }
    else if (o instanceof Object[]) {
        return ((Object[]) o).length <= 0;
    }
    else if (o instanceof boolean[]) {
        return ((boolean[]) o).length <= 0;
    }
    else if (o instanceof byte[]) {
        return ((byte[]) o).length <= 0;
    }
    else if (o instanceof short[]) {
        return ((short[]) o).length <= 0;
    }
    else if (o instanceof char[]) {
        return ((char[]) o).length <= 0;
    }
    else if (o instanceof int[]) {
        return ((int[]) o).length <= 0;
    }
    else if (o instanceof long[]) {
        return ((long[]) o).length <= 0;
    }
    else if (o instanceof float[]) {
        return ((float[]) o).length <= 0;
    }
    else if (o instanceof double[]) {
        return ((double[]) o).length <= 0;
    }
    else if (o instanceof CharSequence) {
        return ((CharSequence) o).length() <= 0;
    }
    else if (o instanceof Collection) {
        return ((Collection) o).isEmpty();
    }
    else if (o instanceof Map) {
        return ((Map) o).isEmpty();
    }
    else if (o instanceof Enumeration) {
        return !((Enumeration) o).hasMoreElements();
    }
    else if (o instanceof Dictionary) {
        return ((Dictionary) o).isEmpty();
    }
    else if (o instanceof Iterable) {
        // NOTE: may not be efficient because an iterator is created
        return !((Iterable) o).iterator().hasNext();
    }

    // reflection code

    final Number length = retrieveNumberFromMethod(o, "length");
    if (length != null) {
        return length.shortValue() <= 0;
    }

    final Number size = retrieveNumberFromMethod(o, "size");
    if (size != null) {
        return size.shortValue() <= 0;
    }

    final Boolean isEmpty = retrieveBooleanFromMethod(o, "isEmpty");
    if (isEmpty != null) {
        return isEmpty;
    }

    return false;
}

static Number retrieveNumberFromMethod(final Object o, final String methodName) {
    try {
        final Number number = (Number) o.getClass().getMethod(methodName).invoke(o);
        return number;
    }
    catch (final IllegalArgumentException e) {
        throw new IllegalStateException("Unable to retrieve number from " + methodName + " on " + o, e);
    }
    catch (final SecurityException e) {
        throw new IllegalStateException("Unable to retrieve number from " + methodName + " on " + o, e);
    }
    catch (final InvocationTargetException e) {
        throw new IllegalStateException("Unable to retrieve number from " + methodName + " on " + o, e);
    }
    catch (final IllegalAccessException e) {
        return null;
    }
    catch (final NoSuchMethodException e) {
        return null;
    }
}

static Boolean retrieveBooleanFromMethod(final Object o, final String methodName) {
    try {
        final Boolean bool = (Boolean) o.getClass().getMethod(methodName).invoke(o);
        return bool;
    }
    catch (final IllegalArgumentException e) {
        throw new IllegalStateException("Unable to retrieve boolean from " + methodName + " on " + o, e);
    }
    catch (final SecurityException e) {
        throw new IllegalStateException("Unable to retrieve boolean from " + methodName + " on " + o, e);
    }
    catch (final InvocationTargetException e) {
        throw new IllegalStateException("Unable to retrieve boolean from " + methodName + " on " + o, e);
    }
    catch (final IllegalAccessException e) {
        return null;
    }
    catch (final NoSuchMethodException e) {
        return null;
    }
}
于 2013-07-09T18:17:23.067 回答
1

查看 Java 中的类Class和类Method,这使我得到了以下实现isEmpty

public static boolean isEmpty(Object o) {
   try {
      return o == null ||
            (0 == (int)(((Class<? extends Object>) (o.getClass()))
              .getMethod("size", (Class[]) null).invoke(o, (Object[]) null)));
   } catch (IllegalAccessException | IllegalArgumentException
         | InvocationTargetException | SecurityException e) {
      e.printStackTrace();
      return true;
   } catch (NoSuchMethodException e) {
      return true;
   }
}

对于带参数的方法,根据 and 的文档替换and中的nulls 。getMethod()invoke()MethodClass

附录

我知道这会返回一个 int,所以我可以不受惩罚size()地投射结果。invoke()这个修改后的实现isEmpty()更清晰,更明确地说明了它在做什么:

public static boolean isEmpty(Object o) {
   if (o != null) {
      Class<? extends Object> c = o.getClass();
      try {
         Method m = c.getMethod("size", (Class[]) null);
         Integer result = (Integer) m.invoke(o, (Object[]) null);
         if (result.intValue() > 0) {
            return false;
         }
      } catch (NoSuchMethodException e) {
         // o doesn't have a "size", so we'll quietly move on to return true
         // indicating this object is non-accessible
      } catch (IllegalAccessException | IllegalArgumentException
            | InvocationTargetException | SecurityException e) {
         // got something unexpected, let's show how we got here and then
         // return true, indicating this object is non-accessible
         e.printStackTrace();
      }
   }
   return true;
}
于 2013-07-09T21:44:59.377 回答