Является ли экземпляр класса Java singleton?

Согласно Java 8 Language Спецификация §15.8.2 (цитата):

[...]

Литерал класса оценивает объект Class для именованного типа (или для void), как определено определяющим загрузчиком класса (§12.2) класса текущего экземпляра.

[...]

В основном «объект Class» намекает, что это или должен быть синглтон. Также в §12.2 говорится:

[...]

Загрузчики классов с правильным поведением поддерживают следующие свойства:

  • С таким же именем хороший загрузчик классов всегда должен возвращать один и тот же объект класса.

[...]

Фактически, при использовании Java 8 * следующий ** выводит true и true:

class Main {
    public static void main(String[] args) {
        Main main1 = new Main();
        Main main2 = new Main();
        System.out.println(main1.getClass().equals(main2.getClass()));
        System.out.println(main1.getClass() == main2.getClass());
    }
}

Всегда ли загрузчик классов ведет себя хорошо и почему (нет)? Другими словами: Class экземпляров являются одиночными? И наоборот: может ли Class того же типа быть другим экземпляром?

Примечания: здесь я не имею в виду одноэлементный шаблон. Однако, если реализация Class следует этому шаблону, это было бы интересно. В качестве побочного, но ни в коем случае не основного вопроса: поскольку законное использование одноэлементного шаблона является предметом споров, будет ли Java Class хорошим кандидатом для применения одноэлементного шаблона?

*:

$ java -version
openjdk version "1.8.0_262"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_262-b10)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.262-b10, mixed mode)

**: моя IDE даже предупреждает меня, что печатные выражения всегда true.


person Erik    schedule 10.08.2020    source источник


Ответы (2)


Вы неправильно используете термин singleton.

singleton подразумевает существование только одного объекта своего собственного класса. Объект Class является экземпляром класса java.lang.Class, и существует более одного экземпляра этого класса. На самом деле невозможно иметь только один Class объект, поскольку наличие Class объекта уже подразумевает существование по крайней мере двух классов, java.lang.Object и java.lang.Class, поэтому в среде выполнения должно быть по крайней мере два Class объекта.

Ваш пример не способен определить, хорошо ли ведет себя загрузчик классов. Вы уже упоминали JLS §12.2

Загрузчики классов с правильным поведением поддерживают следующие свойства:

  • С таким же именем хороший загрузчик классов всегда должен возвращать один и тот же объект класса.

  • Если загрузчик классов L1 делегирует загрузку класса C другому загрузчику L2, то для любого типа T, который встречается как прямой суперкласс или прямой суперинтерфейс C, или как тип поля в C, или как тип поля формальный параметр метода или конструктора в C или как возвращаемый тип метода в C, L1 и L2 должен возвращать один и тот же объект Class.

Злоумышленник-загрузчик классов может нарушить эти свойства. Однако это не могло подорвать безопасность системы типов, потому что виртуальная машина Java защищает от этого.

Обратите внимание на последнее предложение. JVM защитит от нарушений этих требований. С повторяющимися вхождениями одних и тех же символических ссылок в вашем примере кода есть две возможности

  1. JVM запоминает результат первого разрешения этой символической ссылки в этом контексте и просто повторно использует его при следующем появлении, не запрашивая снова загрузчик классов.

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

Поскольку оба подхода подразумевают запоминание результата, первый подход обычно используется, когда дело доходит до разрешения одной и той же ссылки в одном контексте, поскольку он проще и эффективнее. Когда дело доходит до разных символических ссылок, которые разрешаются в классы, использующие одну и ту же ссылку, JVM действительно выдаст ошибку при нарушении ограничений загрузчика классов..

Таким образом, выражения типа Main.class == Main.class или new Main().getClass() == new Main().getClass() никогда не будут оцениваться как false. Скорее всего, разрешение символьной ссылки на Main будет сокращено, используя один и тот же класс среды выполнения, независимо от того, что будет делать загрузчик классов. Но даже если он не принимает сокращенный вариант и ClassLoader ведет себя неправильно, возвращая объект другого класса для следующего запроса, JVM обнаружит его и выдаст ошибку, поэтому выражение вообще не будет давать результат boolean. Ни в том, ни в другом случае это не могло привести к false.

person Holger    schedule 10.08.2020

В одном загрузчике классов объект Class одинаков.

Всегда ли загрузчик классов ведет себя хорошо и почему (нет)?

Это действительно зависит от реализации. Если это сделано намеренно, чтобы загрузчик классов всегда возвращал новый Class-Object, он не будет хорошо себя вести. По крайней мере, все загрузчики классов OpenJDK ведут себя хорошо.

Другими словами: являются ли экземпляры класса одноэлементными? Наоборот: может ли класс одного типа быть другим экземпляром?

В одном загрузчике классов каждый экземпляр класса является синглтоном. С несколькими загрузчиками классов следующее будет оцениваться как false:

ClassLoaderA.getInstance().loadClass("foo.Bar")==ClassLoaderB.getInstance().loadClass("foo.Bar");

Наоборот: может ли класс одного типа быть другим экземпляром?

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

В качестве побочного, но ни в коем случае не основного вопроса: поскольку законное использование одноэлементного шаблона является предметом споров, будет ли Java Class хорошим кандидатом для применения одноэлементного шаблона?

Это в значительной степени основано на мнении, но я думаю, что это не лучший кандидат для применения шаблона синглтона, поскольку большинство синглтонов реализовано следующим образом:

class Foo{
   public static final Foo INSTANCE=new Foo();
   private Foo(){
      ìf(INSTANCE!=null)
         throw new IllegalAccessException("No Foo instances for you!");
   }
}

Более того, это действительно ОДИН объект класса, многие из которых отличаются только некоторыми мелочами, такими как другой загрузчик классов.

person JCWasmx86    schedule 10.08.2020
comment
Если это плохая кастомная реализация, которая всегда возвращает новый класс-объект, она не будет хорошо себя вести. - Здесь вы выдвигаете гипотезу об ошибке JVM. Теоретически это возможно, но на практике ... это произойдет только в том случае, если кто-то сделал это намеренно. - person Stephen C; 10.08.2020
comment
@StephenC Я имел в виду именно это, я просто неправильно это сформулировал. Спасибо, что указали на это - person JCWasmx86; 10.08.2020