Синтетический метод доступа - PullRequest
13 голосов
/ 21 декабря 2010

Я сделал несколько новых настроек предупреждений в 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 ]

25 голосов
/ 26 апреля 2013

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

Начиная с класса 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.

3 голосов
/ 21 декабря 2010

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

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

0 голосов
/ 29 февраля 2016

Это один из редких случаев, когда используется видимость по умолчанию в 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. Таким образом, он делает то же самое, за исключением минимальных накладных расходов на дополнительный метод.

0 голосов
/ 21 декабря 2010

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

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

0 голосов
/ 21 декабря 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...