假设我有一个带有静态字段且具有设置默认值的类:
public class MyClass {
public static int numb = 10;
}
现在,在程序运行时期间,我如何检查MyClass
的{{1}}字段是否设置为其默认值(在此情况下,numb
)?
从我的角度来看,在代码中,我不知道默认情况下该字段的设置是什么,而我不是设置字段的人,所以我不能“简单地检查是否10
。”我必须能够检查它是否设置为源代码中声明的内容。
答案 0 :(得分:7)
单个字段无法实现。我建议添加一个常量来保持默认值,并将其与numb
进行比较,看看它是否已更改:
private static final int DEFAULT = 10;
public static int numb = DEFAULT;
public static boolean isChanged() {
return numb != DEFAULT;
}
答案 1 :(得分:1)
Kröw,您对问题的最新澄清,以下是否满足您的要求?
public class MyClass {
public static int numb = 10;
}
public class MyTest {
private static int default_numb = MyClass.numb;
public static boolean isChanged() {
return MyClass.numb != default_numb;
}
public static void main(String[] args) {
System.out.println(isChanged() + " " + MyClass.numb);
MyClass.numb = 20;
System.out.println(isChanged() + " " + MyClass.numb);
MyClass.numb = 10;
System.out.println(isChanged() + " " + MyClass.numb);
}
}
运行MyTest的输出是:
$ java MyTest
false 10
true 20
false 10
答案 2 :(得分:0)
问题描述说“从我的角度来看,在代码中,我不知道该字段默认设置为什么,”
如果在运行时提供 numb 默认值,则以下代码将完成您要查找的内容。
public class MyClass {
private static int numb;
private static int def_numb;
private static boolean initialized = false; // track numb initialize
public MyClass(int default_value) {
if (!initialized) {
numb = default_value;
def_numb = numb;
initialized = true;
} else {
numb = default_value;
}
}
public void setNumb(int value) {
numb = value;
}
public int getNumb() {
return numb;
}
public boolean isChanged() {
return numb != def_numb;
}
public static void main(String[] args) {
MyClass m2 = new MyClass(5);
System.out.println(m2.isChanged() + " " + m2.getNumb());
MyClass m3 = new MyClass(6);
System.out.println(m2.isChanged() + " " + m2.getNumb());
System.out.println(m3.isChanged() + " " + m3.getNumb());
MyClass m4 = new MyClass(5);
System.out.println(m3.isChanged() + " " + m3.getNumb());
}
}
运行此程序将显示以下内容。
$ java MyClass
false 5
true 6
true 6
false 5
答案 3 :(得分:0)
如果您不知道默认值,则应跟踪变量以进行任何更改。由于它是公共和静态变量,我认为不可能。但是,您可以按照以下建议更改解决方案。
使用MyValueChangeListener
或Java调试器API
答案 4 :(得分:0)
当我提出这个问题的时候,我希望得到一个新人的答案,这个人可以反思地发现变量初始化的价值。这有点像反射会支持的东西。但在我发布问题之后,我意识到Java没有充分的理由将Java的初始值永久存储在内存中。
无论如何,我伪造了一个在运行时反射访问变量当前值的方法,然后读取程序的字节码以检查该类初始化时的变量。为此,它模拟了类的初始化(<clinit>
),然后简单地检查变量的当前值和声明值是否相等。 (它有很多挫折;见下文。)
/**
* Checks if the current value of a variable is the same as the value it was
* declared as during class initialization. Rules dictating whether or not this
* method can check for the default value of a given variable are as follows:
* <ol>
* <li>The variable must be static.</li>
* <li>The variable must have been initialized to the value of a constant
* expression.</li>
* <li>The variable must be a primitive type.</li>
* </ol>
*
* @param varName
* The name of the variable to be checked. This variable must be
* static and belong to the {@link Class}, <code>clas</code>.
* @param clas
* The class of which a static variable, named <code>varName</code>,
* belongs.
* @return <code>true</code> if the specified variable's value as set in the
* bytecode is the same as it is when retrieved reflectively at the
* beginning of this method call, <code>false</code> otherwise.
* @throws UnsupportedOperationException
* If the specified variable's type is not primitive.
* @throws SecurityException
* As thrown by {@link Class#getDeclaredField(String)} and any other
* reflective operation executed by this method.
* @throws NoSuchFieldException
* If attempting to access the field with the given arguments to
* this method, throws a {@link NoSuchFieldException}.
* @throws IllegalArgumentException
* In case the <code>varName</code> parameter does refer to a field
* of the given class, but the field is not static.
* @throws RuntimeException
* In case an unknown or unexpected error occurs.
* @throws IOException
* If an IOException occurs while reading from the
* <code>.class</code> file of the specified class.
*/
public static boolean isOriginalValue(String varName, Class<?> clas) throws UnsupportedOperationException,
NoSuchFieldException, SecurityException, IllegalArgumentException, RuntimeException, IOException {
Object currentValue;
// Check for the current value reflectively:
Field field = clas.getDeclaredField(varName);
if (!field.getType().isPrimitive())
throw new UnsupportedOperationException("The specified variable's type is not primitive.");
field.setAccessible(true);
try {
// We pass in null because we assume that the field is static. If it isn't, an
// IllegalArgumentException is thrown.
currentValue = field.get(null);
} catch (IllegalAccessException e) {
throw new RuntimeException("The current value of the variable could not be accessed.", e);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Map used to store constant pool values:
Map<Integer, Object> constants = new HashMap<>();
// Local classes used to represent certain constant pool items:
class CPoolClass {
public CPoolClass(int internalNameIndex) {
}
}
class CPoolNameAndType {
public final int nameIndex;
public CPoolNameAndType(int nameIndex, int typeIndex) {
this.nameIndex = nameIndex;
}
public String getName() {
return (String) constants.get(nameIndex);
}
}
class CPoolField {
public final int nameAndTypeIndex;
public CPoolField(int classIndex, int nameAndTypeIndex) {
this.nameAndTypeIndex = nameAndTypeIndex;
}
public CPoolNameAndType getNameAndType() {
return (CPoolNameAndType) constants.get(nameAndTypeIndex);
}
}
class CPoolString {
public CPoolString(int stringIndex) {
}
}
// Now to read the bytecode of the class for the original value.
// First, we open a stream to the .class file of clas.
DataInputStream reader = new DataInputStream(clas.getResourceAsStream(clas.getSimpleName() + ".class"));
// A .class file starts with the magic hexadecimal code: 0xCafeBabe.
if (reader.readInt() != 0xCafeBabe)
throw new RuntimeException("Failed to parse the given class's .class file header.");
// Next, the file contains four bytes denoting its version.
reader.readInt();// Read and ditch the four bytes; we don't need them.
// Parse the constant pool size (2 bytes). We'll go through this many items in
// the constant pool, and cache the ones we need.
int constantPoolSize = reader.readShort();
// Constant pool is of size constantPoolSize-1, so we iterate from 1 to the
// size.
for (int i = 1; i < constantPoolSize; i++) {
byte tag = reader.readByte();
switch (tag) {
default:
throw new RuntimeException(
"Could not parse the .class file's constant pool; couldn't determine the initial value of the variable.");
case 1:// String tag
constants.put(i, reader.readUTF());
continue;
case 3:// Int tag
constants.put(i, reader.readInt());
continue;
case 4:// Float tag
constants.put(i, reader.readFloat());
continue;
case 5:// Long tag
constants.put(i, reader.readLong());
i++;
continue;
case 6:// Double tag
constants.put(i, reader.readDouble());
i++;
continue;
case 7:// Class item
constants.put(i, new CPoolClass(reader.readShort()));
continue;
case 8:// Literal String (e.g. "abc", "potato", "a string")
constants.put(i, new CPoolString(reader.readShort()));
continue;
case 9:// Field item
constants.put(i, new CPoolField(reader.readShort(), reader.readShort()));
continue;
case 10:// Method (We don't need to cache this)
case 11:// Abstract method
reader.readInt();
continue;
case 12:// NameAndType item
constants.put(i, new CPoolNameAndType(reader.readShort(), reader.readShort()));
continue;
case 15:// Method Handle
reader.readByte();
reader.readShort();
continue;
case 16:// Method Type
reader.readShort();// Skip 2 bytes
continue;
case 18:// Invoke Dynamic
reader.readInt();
continue;
}
}
// After the constant pool, there are a few, 2 byte-sized values, a list of
// interfaces (that the class implements), a list of fields, then the list of
// methods.
//
// We're gonna skip to the methods.
reader.readInt();// Skip 4 bytes
reader.readShort();// Skip 2 bytes
int interfaceListSize = reader.readShort();
for (int i = 0; i < interfaceListSize; i++)
reader.readShort();
int fieldListSize = reader.readShort();
for (int i = 0; i < fieldListSize; i++) {
// Skip 6 bytes (total)
reader.readShort();
reader.readInt();
int attributeCount = reader.readShort();
// Skip through all the attributes.
for (int j = 0; j < attributeCount; j++) {
reader.readShort();
int attributeSize = reader.readInt();
for (int k = 0; k < attributeSize; k++)
reader.readByte();
}
}
int methodCount = reader.readShort();
for (int i = 0; i < methodCount; i++) {
// Skip the method's modifiers.
reader.readShort();
// Static variables are initialized via the <clinit> method, whether or not they
// are placed in static initialization blocks in the source code. Because of
// this, we'll have to check the code in this method.
boolean isClinit = "<clinit>".equals(constants.get((int) reader.readShort()));
reader.readShort();// Skip over the method descriptor.
short attributeCount = reader.readShort();
if (isClinit) {
// This is the method we want. Iterate over each attribute it has.
for (int j = 0; j < attributeCount; j++) {
if ("Code".equals(constants.get((int) reader.readShort()))) {
reader.readInt();// Skip over the attribute size. We only need the code table size.
reader.readInt();// Skip max stack size and max locals.
int codeLength = reader.readInt();
// Doubles and longs in the stack will have a null value after them. This
// emulates the numbering of an actual stack.
Stack<Object> stack = new Stack<Object>() {
private static final long serialVersionUID = 1L;
// Handles the push method
@Override
public void addElement(Object item) {
super.addElement(item);
if (item instanceof Double || item instanceof Long)
super.addElement(null);
}
// Handles the add method
@Override
public synchronized void insertElementAt(Object obj, int index) {
if (obj instanceof Double || obj instanceof Long)
super.insertElementAt(null, index);
super.insertElementAt(obj, index);
}
@Override
public synchronized Object pop() {
Object item = super.pop();
if (item == null)
return super.pop();
return item;
}
@Override
public synchronized Object remove(int index) {
Object item = super.remove(index);
if (item == null)
item = super.remove(index - 1);
else if (item instanceof Double || item instanceof Long)
super.remove(index);
return item;
}
@Override
public synchronized Object peek() {
Object item = super.peek();
if (item == null)
return super.get(size() - 2);
return item;
}
};
int[] bytes = new int[codeLength];
for (int k = 0; k < codeLength; k++)
bytes[k] = reader.readUnsignedByte();
// Stack<Object> locals = new Stack<>();
// If I supported local vars, I'd feel obligated to support arrays here, as
// well, and, right now, I don't have enough time for that. Feel free to edit
// this question. Otherwise, I may edit this method to support more opcodes
// later.
Object variable = null;
// Here is where we simulate the class's initialization. This process doesn't
// handle things like arrays, method calls, or other variables.
for (int k = 0; k < codeLength; k++) {
// When parsing through mnemonics, we ignore anything that only puts a reference
// onto the stack or removes a reference from the stack, since this method only
// supports primitive types.
switch (bytes[k]) {
default:
continue;
// Pushing
case 0x10:// bipush
stack.push(bytes[++k]);
break;
case 0x11:// sipush
stack.push((bytes[++k] << 8) + bytes[++k]);
break;
case 0x12:// ldc
case 0x14:// ldc2_w
Object item = constants.get((bytes[++k] << 8) + bytes[++k]);
if (item instanceof Integer || item instanceof Float || item instanceof Long
|| item instanceof Double)
stack.push(item);
break;
// Doubles
case 0xe:// dconst_0
stack.push((double) 0);
break;
case 0xf:// dconst_1
stack.push((double) 1);
break;
case 0x63:// dadd
stack.push((double) stack.pop() + (double) stack.pop());
break;
case 0x6f:// ddiv
stack.push((double) stack.pop() / (double) stack.pop());
break;
case 0x6b:// dmul
stack.push((double) stack.pop() * (double) stack.pop());
break;
case 0x77:// dneg
stack.push(-(double) stack.pop());
break;
case 0x73:// drem
stack.push((double) stack.pop() % (double) stack.pop());
break;
case 0x67:// dsub
stack.push((double) stack.pop() - (double) stack.pop());
break;
case 0x59:// dup
stack.push(stack.peek());
break;
case 0x5a:// dup_x1
case 0x5d:// dup2_x1
stack.add(stack.size() - 2, stack.peek());
break;
case 0x5b:// dup_x2
case 0x5e:// dup2_x2
stack.add(stack.size() - 3, stack.peek());
break;
// Floats
case 0xb:// fconst_0
stack.push(0f);
break;
case 0xc:// fconst_1
stack.push(1f);
break;
case 0xd:// fconst_2
stack.push(2f);
break;
case 0x62:// fadd
stack.push((float) stack.pop() + (float) stack.pop());
break;
case 0x6e:// fdiv
stack.push((float) stack.pop() / (float) stack.pop());
break;
case 0x6a:// fmul
stack.push((float) stack.pop() * (float) stack.pop());
break;
case 0x76:// fneg
stack.push(-(float) stack.pop());
break;
case 0x72:// frem
stack.push((float) stack.pop() % (float) stack.pop());
break;
case 0x66:// fsub
stack.push((float) stack.pop() - (float) stack.pop());
break;
// Integers
case 0x2:// iconst_m1
stack.push(-1);
break;
case 0x3:// iconst_0
stack.push(0);
break;
case 0x4:// iconst1
stack.push(1);
break;
case 0x5:// iconst_2
stack.push(2);
break;
case 0x6:// iconst_3
stack.push(3);
break;
case 0x7:// iconst_4
stack.push(4);
break;
case 0x8:// iconst_5
stack.push(5);
break;
case 0x60:// iadd
stack.push((int) stack.pop() + (int) stack.pop());
break;
case 0x7e:// iand
stack.push((int) stack.pop() & (int) stack.pop());
break;
case 0x6c:// idiv
stack.push((int) stack.pop() / (int) stack.pop());
break;
case 0x68:// imul
stack.push((int) stack.pop() * (int) stack.pop());
break;
case 0x74:// ineg
stack.push(-(int) stack.pop());
break;
case 0x80:// ior
stack.push((int) stack.pop() | (int) stack.pop());
break;
case 0x70:// irem
stack.push((int) stack.pop() % (int) stack.pop());
break;
case 0x78:// ishl
stack.push((int) stack.pop() << (int) stack.pop());
break;
case 0x7a:// ishr
stack.push((int) stack.pop() >> (int) stack.pop());
break;
case 0x64:// isub
stack.push((int) stack.pop() - (int) stack.pop());
break;
case 0x7c:// iushr
stack.push((int) stack.pop() >>> (int) stack.pop());
break;
case 0x82:// ixor
stack.push((int) stack.pop() ^ (int) stack.pop());
break;
// Longs
case 0x9:// lconst_0
stack.push(0l);
break;
case 0xa:// lconst_1
stack.push(1l);
break;
case 0x61:// ladd
stack.push((long) stack.pop() + (long) stack.pop());
break;
case 0x7f:// land
stack.push((long) stack.pop() & (long) stack.pop());
break;
case 0x6d:// ldiv
stack.push((long) stack.pop() / (long) stack.pop());
break;
case 0x69:// lmul
stack.push((long) stack.pop() * (long) stack.pop());
break;
case 0x75:// lneg
stack.push(-(long) stack.pop());
break;
case 0x81:// lor
stack.push((long) stack.pop() | (long) stack.pop());
break;
case 0x71:// lrem
stack.push((long) stack.pop() % (long) stack.pop());
break;
case 0x79:// lshl
stack.push((long) stack.pop() << (long) stack.pop());
break;
case 0x7b:// lshr
stack.push((long) stack.pop() >> (long) stack.pop());
break;
case 0x65:// lsub
stack.push((long) stack.pop() - (long) stack.pop());
break;
case 0x7d:// lushr
stack.push((long) stack.pop() >>> (long) stack.pop());
break;
case 0x83:// lxor
stack.push((long) stack.pop() ^ (long) stack.pop());
break;
// Stack manipulation
// nop is handled by this switch's default case.
case 0x58:// pop2
stack.pop();
case 0x57:// pop
stack.pop();
break;
case 0x5f:// swap
Object top = stack.pop(), bottom = stack.pop();
stack.push(top);
stack.push(bottom);
break;
// Variable assignment
case 0xb3:// putstatic
if (varName.equals(((CPoolField) constants.get((bytes[++k] << 8) + bytes[++k]))
.getNameAndType().getName()))
variable = stack.pop();
break;
// Casting
case 0x90:// d2f
stack.push((float) (double) stack.pop());
break;
case 0x8e:// d2i
stack.push((int) (double) stack.pop());
break;
case 0x8f:// dtl
stack.push((long) (double) stack.pop());
break;
case 0x91:// i2b
stack.push((byte) (int) stack.pop());
break;
case 0x92:// i2c
stack.push((char) (int) stack.pop());
break;
case 0x87:// i2d
stack.push((double) (int) stack.pop());
break;
case 0x86:// i2f
stack.push((float) (int) stack.pop());
break;
case 0x85:// i2l
stack.push((long) (int) stack.pop());
break;
case 0x93:// i2s
stack.push((short) (int) stack.pop());
break;
case 0x8a:// l2d
stack.push((double) (long) stack.pop());
break;
case 0x89:// l2f
stack.push((float) (long) stack.pop());
break;
case 0x88:// l2i
stack.push((int) (long) stack.pop());
break;
case 0x8d:// f2d
stack.push((double) (float) stack.pop());
break;
case 0x8b:// f2i
stack.push((int) (float) stack.pop());
break;
case 0x8c:// ftl
stack.push((long) (float) stack.pop());
break;
// Comparisons
case 0x98:// dcmpg
stack.push((double) stack.pop() > (double) stack.pop());
break;
case 0x96:// fcmpg
stack.push((float) stack.pop() > (float) stack.pop());
break;
case 0x97:// dcmpl
stack.push((double) stack.pop() < (double) stack.pop());
break;
case 0x95:// fcmpl
stack.push((float) stack.pop() < (float) stack.pop());
break;
case 0x94:// lcmp
stack.push(((Long) stack.pop()).compareTo((Long) stack.pop()));
break;
}
}
return variable.equals(currentValue);
} else
// This isn't the "Code" attribute. Skip it.
for (int k = 0; k < reader.readInt(); k++)
reader.readByte();
}
// If we iterated through each
throw new RuntimeException(
"Couldn't find the static initialization bytecode for the specified class. Any initial assignments or declarations to the specified variable could not be found.");
} else {
// We don't want this method.
for (int j = 0; j < attributeCount; j++) {
reader.readShort();// Skip the attribute name. We don't care for this method.
for (int k = 0; k < reader.readInt(); k++)
reader.readByte();// Skip the attribute data.
}
}
}
throw new RuntimeException("The declared value could not be found");
}
此方法通常适用于具有简单声明值的变量(例如public static int x = 56;
)。它不支持大量的opcodes,包括一些带参数的(如果这些操作码存在于类的字节码中,则此方法无法正确读取它们)。这种方法可以做到&#34;工作&#34;在非常简单的情况下,更多的是为了概念证明。