Синтетический метод доступа

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

Вот моя проблема с примером кода

public class Test {
    private String testString;

    public void performAction() {

        new Thread( new Runnable() {
            @Override
            public void run() {
                testString = "initialize"; // **
            }
        });
    }
}

Строка с ** дает мне предупреждение в затмении

Read access to enclosing field Test.testString is emulated by a synthetic accessor method. 
Increasing its visibility will improve your performance.

Проблема в том, что я не хочу менять модификатор доступа testString, Кроме того, не хочу создавать геттер для него.

Какие изменения должны быть сделаны?


More descriptive example 

public class Synthetic
{
    private JButton testButton;

    public Synthetic()
    {
        testButton = new JButton("Run");
        testButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae)
                {
                    /* Sample code */
                    if( testButton.getText().equals("Pause") ) {
                        resetButton(); // **    
                    } else if( testButton.getText().equals("Run") ) {
                        testButton.setText("Pause"); // **  
                    }

                }
            }
        );
    }

    public void reset() {
        //Some operations
        resetButton();
    }

    private void resetButton() {
        testButton.setText("Run");
    }
}

Линии с ** дает мне то же самое предупреждение.

5 ответов

Решение

В вашем втором примере нет необходимости доступа testButton непосредственно; Вы можете получить к нему доступ, найдя источник события действия.

Для resetButton() метод, вы можете добавить аргумент для передачи объекта для действия, если вы сделали, что это не такая уж большая проблема, снижая ограничения доступа.

Что такое "синтетический" метод?

Начиная с Method класс (и это родитель, Member) мы узнаем, что синтетические члены " введены компилятором ", и что JLS §13.1 расскажет нам больше. Это отмечает:

Конструкция, испускаемая компилятором Java, должна быть помечена как синтетическая, если она не соответствует конструкции, объявленной явно или неявно в исходном коде.

Поскольку в этом разделе обсуждается двоичная совместимость, на JVMS также стоит ссылаться, а в JVMS §4.7.8 добавлено немного больше контекста:

Член класса, который не отображается в исходном коде, должен быть помечен с помощью Synthetic атрибут, иначе он должен иметь свой ACC_SYNTHETIC флаг установлен. Единственными исключениями из этого требования являются сгенерированные компилятором методы, которые не считаются артефактами реализации....

Synthetic атрибут был введен в JDK 1.1 для поддержки вложенных классов и интерфейсов.

Другими словами, "синтетические" методы являются артефактом реализации, который вводит компилятор Java для поддержки языковых функций, которые сама JVM не поддерживает.

В чем проблема?

Вы столкнулись с одним из таких случаев; вы пытаетесь получить доступ к private поле класса из анонимного внутреннего класса. Язык Java допускает это, но JVM не поддерживает его, и поэтому компилятор Java генерирует синтетический метод, который предоставляет private поле для внутреннего класса. Это безопасно, потому что компилятор не позволяет другим классам вызывать этот метод, однако он создает две (маленькие) проблемы:

  1. Дополнительные методы объявляются. Это не должно быть проблемой для подавляющего большинства случаев использования, но если вы работаете в ограниченной среде, такой как Android, и генерируете множество таких синтетических методов, у вас могут возникнуть проблемы.
  2. Доступ к этому полю осуществляется косвенным путем через синтетический метод, а не напрямую. Это тоже не должно быть проблемой, за исключением случаев использования с высокой чувствительностью к производительности. Если вы не хотите использовать метод получения здесь из соображений производительности, вам не нужен синтетический метод получения. Это редко проблема на практике.

Короче говоря, они действительно не плохие. Если у вас нет конкретной причины избегать синтетических методов (т.е. вы окончательно определили, что они являются узким местом в вашем приложении), вы просто должны позволить компилятору сгенерировать их так, как они считают нужным. Попробуйте отключить предупреждение Eclipse, если оно вас беспокоит.

Что мне с ними делать?

Если вы действительно хотите запретить компилятору генерировать синтетические методы, у вас есть несколько вариантов:

Вариант 1: изменить разрешения

Пакет-частный или protected поля доступны для внутренних классов напрямую. Особенно для чего-то вроде приложения Swing это должно быть хорошо. Но вы говорите, что хотите избежать этого, так что мы идем.

Вариант 2: создать геттер

Оставьте свои поля в покое, но явно создайте protected или же public получить, и использовать это вместо. По сути, это то, что компилятор делает для вас автоматически, но теперь у вас есть прямой контроль над поведением метода.

Вариант 3. Используйте локальную переменную и поделитесь ссылкой с обоими классами.

Это больше кода, но это мой личный фаворит, так как вы делаете отношения между внутренним и внешним классом явными.

public Synthetic() {
  // Create final local instance - will be reachable by the inner class
  final JButton testButton = new JButton("Run");
  testButton.addActionListener(
      new ActionListener() {
        public void actionPerformed(ActionEvent ae) {
          /* Sample code */
          if( testButton.getText().equals("Pause") ) {
            resetButton();
          } else if( testButton.getText().equals("Run") ) {
            testButton.setText("Pause");
          }
        }
      });
  // associate same instance with outer class - this.testButton can be final too
  this.testButton = testButton;
}

Это не всегда то, что вы хотите сделать. Например, если testButton может измениться, чтобы указать на другой объект позже, вам нужно восстановить ActionListener еще раз (хотя это также приятно более явно, так что, возможно, это особенность), но я считаю, что этот вариант наиболее четко демонстрирует его намерения.


Помимо безопасности нитей

Ваш пример Test класс не является потокобезопасным - testString устанавливается в отдельном Thread но вы не синхронизируете это назначение. маркировка testString как volatile было бы достаточно, чтобы все потоки увидели обновление. Synthetic пример не имеет этой проблемы, так как testButton устанавливается только в конструкторе, но так как это было бы целесообразно отметить testButton как final,

Это один из тех редких случаев, когда используется видимость по умолчанию в Java (также называемая "закрытым пакетом").

public class Test {
    /* no private */ String testString;

    public void performAction() {

        new Thread( new Runnable() {
            @Override
            public void run() {
                testString = "initialize"; // **
            }
        });
    }
}

Это сделает следующее:

  • testString теперь доступен для всех классов в том же пакете, что и внешний класс (Test).
  • Поскольку внутренние классы на самом деле генерируются как OuterClassPackage.OuterClassName$InnerClassName они также находятся в одной упаковке. Поэтому они могут получить доступ к этому полю напрямую.
  • В отличие от создания этого поля protected видимость по умолчанию не сделает это поле доступным для подклассов (кроме случаев, когда они находятся в одном пакете, конечно). Поэтому вы не загрязняете свой API для внешних пользователей.

Когда используешь private Вместо этого javac сгенерирует синтетический метод доступа, который сам по себе является просто методом получения с видимостью по умолчанию в Java. Таким образом, он делает то же самое, за исключением минимальных затрат на дополнительный метод.

Я думаю, что проблема в том, что вы устанавливаете строку в родительском классе. Это повлияет на производительность, потому что поток должен посмотреть, где эта переменная снова. Я думаю, что более чистый подход использует Callable, который возвращает String, а затем выполняет.get() или что-то, что возвращает результат. После получения результата вы можете вернуть данные в родительский класс.

Идея заключается в том, что вы хотите убедиться, что Thread выполняет только одно и только одно, а не устанавливает переменные в других классах. Это более чистый подход и, вероятно, более быстрый, потому что внутренний поток не имеет доступа к чему-либо вне себя. Что означает меньше блокировки.:)

Учитывая контекст (вы назначаете переменную один раз как часть довольно дорогой операции), я не думаю, что вам нужно что-то делать.

Другие вопросы по тегам