【问题标题】:Getting a Java field through reflection, but not from its String name通过反射获取 Java 字段,而不是通过其字符串名称
【发布时间】:2011-03-21 21:27:16
【问题描述】:

如果我有字段本身,是否可以通过 Java 反射获取字段?这是一个原始的浮动(公共,没问题)。我不想将其名称用作字符串。

例子:

public class TVset {
  public float voltageA;
  public float voltageB;
  public float voltageC;
  public TVset(...) {...} // constructor
  public void function() {...} // it changes voltages
}

class Voltmeter{
  Object theObject;
  Field theField;

  Voltmeter(Object obj) {
    theObject = obj;
    Class theFieldClass = obj.getClass();
    Class theContainerClass = theFieldClass.getDeclaringClass();
    Field theField = ??? // <-- here I don't want to use a String
  }

  float getVoltage() {
    return theField.getFloat(theObject);
  }
}

TVset tv1 = new TVset(...);
TVset tv2 = new TVset(...);

Voltmeter meter = new Voltmeter(tv1.voltageB);
meter.getVoltage();
tv1.function();
meter.getVoltage(); <- should reflect the changed voltage
tv1.function();
meter.getVoltage(); <- should reflect the changed voltage
...

效果类似于通过引用传递浮点数,但没有将其包装到包装类中。

我需要在不同的电视机上测量不同的电压,只需更换线路即可:

Voltmeter meter = new Voltmeter(tv1.voltageB);

到别的地方,比如:

Voltmeter meter = new Voltmeter(tv2.voltageA);

是否可以通过反射来做到这一点?

谢谢

【问题讨论】:

  • 您能否详细说明“我不想使用字符串”?
  • 我只是觉得写 new Voltmeter(tv2, "voltageA") 很难看,因为我确切地知道 TVset 中的字段是什么,我可以写 new(tv2.voltageA)。另外,如果我无意中写了 new(tv2.vltageA),编译器会在运行前对我大喊大叫。

标签: java reflection floating-point field primitive


【解决方案1】:

要使用反射,您必须使用字符串。您可以使用对象来包装可变浮点数或简单的浮点数[1],而不是使用浮点数;

顺便说一句,除非您有充分的理由,否则我不会使用 float,double 的舍入误差要小得多。

public class TVset {
  public double[] voltageA = { 0.0 };
  public double[] voltageB = { 0.0 };
  public double[] voltageC = { 0.0 };
}

class Voltmeter{
  final double[] theField;

  Voltmeter(double[] theField) {
    this.theField = theField;
  }

  double getVoltage() {
    return theField[0];
  }
}
// works just fine.
Voltmeter meter = new Voltmeter(tv1.voltageB);

编辑:使用抽象访问器。这是最快的方法。 AFAIK,差异小于 10 纳秒。

public abstract class Voltmeter{ // or use an interface
  public abstract double get();
  public abstract void set(double voltage);
}

public class TVset {
  private double _voltageA = 0.0;
  private double _voltageB = 0.0;
  private double _voltageC = 0.0;
  public final Voltmeter voltageA = new Voltmeter() {
     public double get() { return _voltageA; }
     public void set(double voltage) { _voltageA = voltage; }
  }
  public final Voltmeter voltageB = new Voltmeter() {
     public double get() { return _voltageB; }
     public void set(double voltage) { _voltageB = voltage; }
  }
  public final Voltmeter voltageC = new Voltmeter() {
     public double get() { return _voltageC; }
     public void set(double voltage) { _voltageC = voltage; }
  }
}

就个人而言,如果速度很关键,我会直接按名称使用字段。您不会比这更简单或更快。

【讨论】:

  • 但这不会引入性能损失吗? TVset 对象每秒需要进行数百万次电压更新。这就是我使用原始浮点数的原因(精度不是问题)。
  • 有一个非常小的惩罚。它比使用反射小得多。如果您愿意,您可以每秒进行 1 亿次更新。
  • 如果你想避免使用数组,你可以使用抽象访问器类。它没有那么优雅,但更高效。
  • 这个抽象访问器类是如何工作的?举个例子好吗?感谢您的耐心等待。 (顺便说一句,我意识到最初的问题非常愚蠢,因为当 tv1.voltageA 作为参数传递时,它已经被解析为普通浮点数,因此接收器类无法知道它来自哪里。)
  • 啊哈。谢谢你的例子。本质上,它的工作方式类似于 Andrew Finnell 的委托解决方案,但不是在现场构建访问器作为 Voltmeter 构造函数的参数,而是建议在 TVset 本身内预构建所有这些访问器。这真好。唯一(绝对次要)的缺点是,对于我决定在电视机中添加的每个新电压 X,我需要记住构建相应的访问器。但这绝对没问题。非常感谢!
【解决方案2】:

为了完整起见,我已经包含了解决此问题的委托方式。我也不建议让您的花车公开访问。

public class stackoverflow_5383947 {

    public static class Tvset {

        public float voltageA;
        public float voltageB;
        public float voltageC;

        public Tvset() {
        }

        public void function() {
            voltageA++;
        }
    };

    public static class Voltmeter {

        private VoltageDelegate _delegate;

        public Voltmeter(VoltageDelegate delegate) {
            _delegate = delegate;
        }

        float getVoltage() {
            return _delegate.getVoltage();
        }
    };

    public static interface VoltageDelegate {

        public float getVoltage();
    }   

    public static void main(String[] args) {
        final Tvset tv1 = new Tvset();
        Voltmeter meter = new Voltmeter(new VoltageDelegate()   {
            public float getVoltage() {
                return tv1.voltageA;
            }
        });

        System.out.println(meter.getVoltage());
        tv1.function();
        System.out.println(meter.getVoltage());
        tv1.function();
        System.out.println(meter.getVoltage());
    }
}

【讨论】:

  • 啊,如果我刚刚发布了 2 秒前... ;)
  • 我明白了。 “新电压表”的东西看起来不像我想要的那么漂亮,但我想它非常有效。谢谢。
  • 哦,这是“抽象访问器类”?凉爽的! :o)
  • @Amenhotep 如果这是 C#,它看起来会更漂亮。如果没有 Lambda,Java 可能会很冗长。
  • 但是如果我需要实例化两个不同的电压表怎么办?考虑到委托是静态的,它会起作用吗?
【解决方案3】:

如果您控制 TVSet 但由于某种原因需要使用反射,避免错误的一个好方法是将您需要的方法/字段名称写为 TVSet 类中的字符串常量。

但是,如果您关心的是性能,那么反射不是可行的方法,因为通过反射访问字段或方法可能比通过 getter 或直接访问要慢得多。

【讨论】:

    【解决方案4】:

    这里有一个变体,您可以在其中提供 float 值而不是字符串。

    class Voltmeter{
      Object container;
      Field theField;
    
      Voltmeter(Object obj, float currentValue) {
        container = obj;
        Class<?> containerClass = obj.getClass();
        Field[] fields = containerClass.getFields();
        for(Field f : fields) {
           if (f.getType() == float.class &&
               f.getFloat(container) == currentValue) {
              this.theField = f;
              break;
           }
        }
      }
    
      float getVoltage() {
        return theField.getFloat(container);
      }
    }
    

    然后这样称呼它:

    Voltmeter meter = new Voltmeter(tv1, tv1.voltageB);
    

    仅当电压表创建时的电压不同(而不是 NaN)时它才有效,因为它采用具有正确值的第一个字段。而且我认为它并没有真正更有效。

    我不会真的推荐这个。

    【讨论】:

    • 我必须同意,如果我真的需要反思,这将是解决方案。但事实证明,我的问题还有其他更有效的解决方案。有人说……不完美。 :o) 谢谢。
    • @Amenhotep:即使使用反射,最好只获取一次 Field 对象,然后将其提供给单独的 Voltmeter 构造函数,而不是通过其值搜索 Field。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-04
    相关资源
    最近更新 更多