Получение поля Java через отражение, но не из его имени String

Можно ли получить поле через отражение 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);

Можно ли это сделать с отражением?

Спасибо


person Amenhotep    schedule 21.03.2011    source источник
comment
Не могли бы вы уточнить, я не хочу использовать строку?   -  person BalusC    schedule 22.03.2011
comment
Я просто считаю некрасивым писать новый вольтметр(tv2,voltageA), так как я точно знаю, что такое поля в TVset, и я мог бы написать новый(tv2.voltageA). Кроме того, если я непреднамеренно напишу new(tv2.vltageA), компилятор будет кричать на меня -- до выполнения.   -  person Amenhotep    schedule 22.03.2011


Ответы (4)


Чтобы использовать отражение, вы должны использовать String. Вместо использования float вы можете использовать объект для переноса изменяемого float или простого float[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);

РЕДАКТИРОВАТЬ: Использование абстрактного средства доступа. Это самый быстрый способ сделать это. Насколько я знаю, разница составляет менее 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; }
  }
}

Лично, если скорость критична, я бы просто использовал поля напрямую по имени. Вы не станете проще или быстрее, чем это.

person Peter Lawrey    schedule 21.03.2011
comment
Но не приведет ли это к снижению производительности? Объекты TVset должны делать миллионы обновлений напряжения каждую секунду. Вот почему я использовал примитивные числа с плавающей запятой (точность не имеет значения). - person Amenhotep; 22.03.2011
comment
Там очень маленький штраф. Это намного меньше, чем использование отражения. Вы можете делать 100 миллионов обновлений в секунду, если хотите. - person Peter Lawrey; 22.03.2011
comment
если вы хотите избежать использования массива, вы можете использовать абстрактный класс доступа. Это не так элегантно, но более эффективно. - person Peter Lawrey; 22.03.2011
comment
Как работает этот абстрактный класс доступа? Пример, пожалуйста? Спасибо за терпеливость. (Кстати, я понял, что первоначальный вопрос был довольно глупым, потому что к тому времени, когда tv1.voltageA передается в качестве параметра, он уже разрешается как обычное число с плавающей запятой, поэтому класс приемника не может знать, откуда он взялся.) - person Amenhotep; 22.03.2011
comment
Ага. Спасибо за пример. По сути, это работает так же, как решение делегирования Эндрю Финнелла, но вместо того, чтобы создавать метод доступа на месте в качестве аргумента для конструктора Voltmeter, вы предлагаете предварительно создать все эти методы доступа внутри самого телевизора. Мило. Единственный (совсем незначительный) недостаток в том, что для каждого нового напряжения X, которое я решаю добавить в телеке, мне нужно не забывать строить соответствующий аксесуар. Но это абсолютно нормально. Большое спасибо! - person Amenhotep; 22.03.2011
comment
Но... Как мне это использовать? На самом деле вольтметр гораздо сложнее простого геттера (строит гистограммы и т.д.). Как создать новый вольтметр и как подключить его к телевизору? - person Amenhotep; 22.03.2011
comment
Теперь я понимаю. То, что вы назвали вольтметром, на самом деле было бы своего рода зондом. Затем я создаю экземпляр нового вольтметра и даю ему объект Probe, который уже подключен к определенному напряжению внутри телевизора. Прохладно. - person Amenhotep; 22.03.2011
comment
Я сделал это абстрактным классом, предполагая, что ваш пример более сложный. Для более простого варианта использования вы можете использовать интерфейс. Я предполагаю, что вы не можете просто использовать массив float или double для хранения всех напряжений. Это позволит вам индексировать значение и создать один общий вольтметр. - person Peter Lawrey; 22.03.2011

Просто для полноты я включил способ делегирования решения этой проблемы. Я бы также не рекомендовал иметь ваши поплавки с публичным доступом.

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());
    }
}
person Andrew T Finnell    schedule 21.03.2011
comment
Аргх, если бы я написал на 2 секунды раньше... ;) - person Kevin K; 22.03.2011
comment
Я понимаю. Новый вольтметр выглядит не так красиво, как мне хотелось, но я думаю, что он довольно эффективен. Спасибо. - person Amenhotep; 22.03.2011
comment
О, это абстрактный класс доступа? Прохладно! :о) - person Amenhotep; 22.03.2011
comment
@Amenhotep Если бы это был C #, это выглядело бы намного красивее. Без Lambda Java может быть многословным. - person Andrew T Finnell; 22.03.2011
comment
Но что, если мне нужно создать два разных вольтметра? Будет ли это работать, учитывая, что делегат статичен? - person Amenhotep; 22.03.2011

Если вы управляете TVSet, но по какой-то причине вам нужно использовать отражение, хороший способ избежать ошибок — написать имена методов/полей, которые вам нужны, как строковые константы в классе TVSet.

Однако, если вы беспокоитесь о производительности, отражение не подходит, потому что доступ к полю или методу через отражение может быть намного медленнее, чем доступ через геттеры или напрямую.

person ggf31416    schedule 21.03.2011

Вот вариант, в котором вы можете указать свое значение 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), так как принимает первое Поле с правильным значением. И я думаю, что это не более эффективно.

Я бы не очень рекомендовал это.

person Paŭlo Ebermann    schedule 21.03.2011
comment
Я должен согласиться, что это было бы решением, если бы мне действительно нужно было подумать. Но оказывается, есть и другие, гораздо более эффективные решения моей проблемы. Что было сказано... несовершенно. :о) Спасибо. - person Amenhotep; 22.03.2011
comment
@Amenhotep: Даже при использовании отражения было бы лучше получить объект Field только один раз, а затем передать его отдельному конструктору вольтметра вместо поиска поля по его значению. - person Paŭlo Ebermann; 22.03.2011