Java如何使用反射检查字段是否已初始化或为默认值?

13

所以,这应该是一个简单的问题。

假设我有一个具有许多字段的类,例如:

String thizz;
long that;
boolean bar;

我如何使用反射机制判断字段thizzthatbar是否已被初始化或留在它们的默认值null、0和false?


为什么需要反射?难道你就不能直接检查吗? - Paul Bellora
因为我不知道其他对象有多少个字段和是什么,所以它经常发生变化... - rapadura
3
请注意,Boolean 的默认值是 null。也许您想要使用的是 boolean 类型? - BalusC
等一下,所以您是在问如何动态查找对象的字段和值吗?那有点不同的问题。 - Paul Bellora
这是Java类型的默认值:http://download.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html - Lynch
6个回答

22

你只需要检查7种基本类型和1种引用类型。如果将所有数字类型分组在一起,则只需检查4个值。

Object o =
for (Field field : o.getClass().getDeclaredFields()) {
 Class t = field.getType();
 Object v = field.get(o);
 if(t == boolean.class && Boolean.FALSE.equals(v)) 
   // found default value
 else if(t == char.class && ((Character) v).charValue() == 0)
   // found default value
 else if(t.isPrimitive() && ((Number) v).doubleValue() == 0)
   // found default value
 else if(v == null)
   // found default value
}  

@Rahul 这就是代码中间的检查的作用,没错。 - Peter Lawrey
char 怎么办?(Character 不是一个 Number - DodgyCodeException
@DodgyCodeException 一个 char 是一个无符号的16位值。例如,你可以写成 char ch = 48; 或者 Character ch = 48; 或者 char ch = '0'; ch /= 0.9; 在初始化之前它是 0 或者 '\0' - Peter Lawrey
1
@PeterLawrey 即使如此,您仍将在 else if(t.isPrimitive() && ((Number) v).doubleValue() == 0) 中获得 ClassCastException,因为 v 是 Character 类,无法转换为 Number。 - DodgyCodeException
@DodgyCodeException 感谢您的纠正。我已经添加了这个案例。 - Peter Lawrey

8

Peter Lawrey的回答 对我很有帮助,除了原始数据类型char的字段外,在我的代码中会引发以下异常:

java.lang.ClassCastException: java.lang.Character cannot be cast to java.lang.Number

所以我添加了char案例:
Object o =
for (Field field : o.getClass().getDeclaredFields()) {
 Class t = field.getType();
 Object v = field.get(o);
 if (boolean.class.equals(t) && Boolean.FALSE.equals(v)) 
   // found default value
 else if (char.class.equals(t) && ((Character) v) != Character.MIN_VALUE)
   // found default value
 else if (t.isPrimitive() && ((Number) v).doubleValue() == 0)
   // found default value
 else if(!t.isPrimitive() && v == null)
   // found default value
}  

Character.MIN_VALUE'\u0000',根据Java基本数据类型文档,这是 char 的默认值。


很有帮助。谢谢! - JR Sahoo.'JS'

6
您不需要使用反射技术...
if (thizz == null) {
    //it's not initialized
}
if (that == 0) {
   //it's not initialized
}
if(bar == false) {
    //it's not initialized
}

然而,它们可以被初始化然后重置为默认值。如果您真的想知道它们是否已被初始化,您可以像这样做:

private boolean isFooInitialized = false;
private Foo foo;
public void setFoo(Foo foo) {
    this.foo = foo;
    isFooInitialized = foo != null;
}

要获取类中的所有字段,请查看Class.getDeclaredFields()。这将提供每个字段,而不仅仅是公共字段。
从这里,您可以检查字段的类型并获取其值:
Foo foo = ...
Field[] fooFields = foo.getClass().getDeclaredFields();
for (Field fooField : fooFields) {
    Class<?> fooFieldClass = fooField.getClass();
    if (fooFieldClass.equals(int.class)) {
        if (fooField.getInt(foo) == 0) {
            // not initialized
        }
    } else if (fooFieldClass.equals(double.class)) {
        if (fooField.getDouble(foo) == 0) {
            // not initialized
        }
    } else if (fooFieldClass.equals(boolean.class)) {
        if (fooField.getBoolean(foo) == false) {
            // not initialized
        }
    } else if (fooFieldClass.equals(float.class)) {
        if (fooField.getFloat(foo) == 0) {
            // not initialized
        }
    } else if (fooFieldClass.equals(char.class)) {
        if (fooField.getChar(foo) == 0) {
            // not initialized
        }
    } else if (fooFieldClass.equals(byte.class)) {
        if (fooField.getByte(foo) == 0) {
            // not initialized
        }
    } else if (fooFieldClass.equals(long.class)) {
        if (fooField.getLong(foo) == 0) {
            // not initialized
        }
    } else if (fooField.get(foo) == null) {
        // not initialized
    }
}

啊,让我重新陈述我的问题,实际上我并不关心这个值是否已经被初始化并且后来被设置为null或0,我只需要检查所有Java可能类型的默认值,包括基本类型和java.lang对象。 - rapadura
在这种情况下,很容易--只有大约7个(可能会错一两个)不同的“默认值”。每个原始类型和每个非原始类型都有一个。一个简单的重载方法可以轻松执行检查: bool IsDefault(bool b), bool IsDefault(long i), 等等(某些类型可以重复使用,如int/longfloat/double),根据需要修改反射。 - user166390
@Jeffrey,是的,那差不多就是我所做的,除了我曾经检查过Number、Boolean和String的实例,其余的我都认为是false... - rapadura
在我提出问题后,我这样做了,因为我想我可以尝试一下,结果它起作用了,但仍然可能有更聪明的方法 :) 我认为应该有一种方法来询问一个对象是否为null false或0,而不必对其值进行instanceof以适应不同类型。 - rapadura
@Jeffrey,大多数值都可以与0进行比较,您不需要那么多的检查。请看我的答案。 - Peter Lawrey
显示剩余3条评论

6
您可以通过反射创建一个新的实例(自动初始化为默认值),并将您的对象与这个默认实例进行比较。
//default Object filled with default values:
Object defaultObject = yourObject.getClass().newInstance();
for (final Field field : yourObject.getClass().getDeclaredFields()) {
  final Object value = field.get(yourObject);
  if (value == null || value.equals(field.get(defaultObject))) {
    // found default value
  }
}

PS: 这个解决方案还解决了Christophe Weis提到的关于字符的问题。


1
这个解决方案应该有更多的赞。如果您创建默认对象而没有静态类型,甚至不必依赖于Foo。 可以这样做:Object defaultObject = yourObject.getClass().newInstance();(如果获取所有构造函数并创建具有最少参数的构造函数,则可以更加精细)。 - Hash
这将会抛出一个 java.lang.NoSuchMethodException 异常,因为该类没有默认构造函数,例如 Integer。 - abarazal

1

这应该让你朝着正确的方向前进。

    Class clazz = Class.forName("your.class");
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
      String dataType = field.getType().getName();
      if (dataType.equals("java.lang.String")) {
          System.out.println("found a string");
      }
    }

1

它的要点是:

Field[] fields = yourObject.getClass().getFields();
for(Field f : fields)
{
  Class<?> k = f.getType();
  // depending on k, methods like f.getInt(yourObject),
  // f.getFloat(yourObject), 
  // f.getObject(hourObject) to get each member.
}

现在,这只允许您读取公共字段。

或者,如果您的对象遵循getX/setX命名约定,您可以使用getMethods(),并查找名为“getXxx”和“setXxx”的方法,以推断可设置字段的存在--并调用那些getter来查找预期的默认值。


2
请注意,getFields() 仅返回公共字段。 - Paul Bellora
getDeclaredFields()会返回所有字段。 - Matthieu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接