Предупреждение о синтетическом методе доступа

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

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

public class Test {
    private String testString;

    public void performAction() {

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

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

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");
    }
}

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


person Rites    schedule 21.12.2010    source источник
comment
Поищите на сайте, много сообщений об этом предупреждении.   -  person khachik    schedule 21.12.2010


Ответы (5)


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

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

person rsp    schedule 21.12.2010

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

Начиная с _1 _ класс (и его родительский элемент, 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. Измените разрешения

Поля Package-private или 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.

person dimo414    schedule 26.04.2013

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

person NPE    schedule 21.12.2010
comment
Это просто пример. Я не выполняю это действие. - person Rites; 21.12.2010
comment
И если вы делаете значительное количество раз, современные JVM будут встраивать это. - person Tom Hawtin - tackline; 21.12.2010

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

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

person Amir Raminfar    schedule 21.12.2010
comment
Спасибо Амир. Но это всего лишь пример. Собираюсь отредактировать свой вопрос, который будет более подробным - person Rites; 21.12.2010

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

person orsg    schedule 28.02.2016