Что такое instance в java
Перейти к содержимому

Что такое instance в java

Understanding Class Members

In this section, we discuss the use of the static keyword to create fields and methods that belong to the class, rather than to an instance of the class.

Class Variables

When a number of objects are created from the same class blueprint, they each have their own distinct copies of instance variables. In the case of the Bicycle class, the instance variables are cadence , gear , and speed . Each Bicycle object has its own values for these variables, stored in different memory locations.

Sometimes, you want to have variables that are common to all objects. This is accomplished with the static modifier. Fields that have the static modifier in their declaration are called static fields or class variables. They are associated with the class, rather than with any object. Every instance of the class shares a class variable, which is in one fixed location in memory. Any object can change the value of a class variable, but class variables can also be manipulated without creating an instance of the class.

For example, suppose you want to create a number of Bicycle objects and assign each a serial number, beginning with 1 for the first object. This ID number is unique to each object and is therefore an instance variable. At the same time, you need a field to keep track of how many Bicycle objects have been created so that you know what ID to assign to the next one. Such a field is not related to any individual object, but to the class as a whole. For this you need a class variable, numberOfBicycles , as follows:

Class variables are referenced by the class name itself, as in

This makes it clear that they are class variables.

You can use the Bicycle constructor to set the id instance variable and increment the numberOfBicycles class variable:

Class Methods

The Java programming language supports static methods as well as static variables. Static methods, which have the static modifier in their declarations, should be invoked with the class name, without the need for creating an instance of the class, as in

A common use for static methods is to access static fields. For example, we could add a static method to the Bicycle class to access the numberOfBicycles static field:

Not all combinations of instance and class variables and methods are allowed:

  • Instance methods can access instance variables and instance methods directly.
  • Instance methods can access class variables and class methods directly.
  • Class methods can access class variables and class methods directly.
  • Class methods cannot access instance variables or instance methods directly—they must use an object reference. Also, class methods cannot use the this keyword as there is no instance for this to refer to.

Constants

The static modifier, in combination with the final modifier, is also used to define constants. The final modifier indicates that the value of this field cannot change.

For example, the following variable declaration defines a constant named PI , whose value is an approximation of pi (the ratio of the circumference of a circle to its diameter):

Constants defined in this way cannot be reassigned, and it is a compile-time error if your program tries to do so. By convention, the names of constant values are spelled in uppercase letters. If the name is composed of more than one word, the words are separated by an underscore (_).

The Bicycle Class

After all the modifications made in this section, the Bicycle class is now:

Инстанцируем java.lang.Class

Конструктор java.lang.Class является одной из самых охраняемых сущностей в языке Java. В спецификации чётко сказано, что объекты типа Class может создавать только сама JVM и что нам тут делать нечего, но так ли это на самом деле?

Предлагаю погрузиться в глубины Reflection API (и не только) и выяснить, как там всё устроено и насколько трудно будет обойти имеющиеся ограничения.

Эксперимент я провожу на 64-битной JDK 1.8.0_151 с дефолтными настройками. Про Java 9 будет в самом конце статьи.

Уровень 1. Простой

Начнём с самых наивных попыток и пойдём по нарастающей. Сперва посмотрим врагу в лицо:

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

Вполне ожидаемо данный код не будет работать и выдаст следующую ошибку:

С первой же попытки мы попали на первое предупреждение из метода setAccessible0 . Оно захардкожено специально для конструктора класса java.lang.Class :

Не проблема, ведь ключевой строкой в этом методе является последняя — установка поля override в значение true . Это легко сделать, используя грубую силу:

Уровень 2. Посложнее

Естественно, установка флага override — это не единственное ограничение, но теперь мы можем хотя бы продвинуться чуть дальше в работе метода newInstance . Достаточно далеко, чтобы спланировать дальнейшие действия. В этот раз ошибка будет следующая:

Нас занесло прямиком в класс пакета sun.reflect , а мы знаем, что основная магия должна происходить именно там. Самое время заглянуть в реализацию newInstance класса Constructor и узнать, как мы туда попали:

Из реализации становится понятно, что Constructor делегирует всю работу по инстанцированию другому объекту типа ConstructorAccessor . Он инициализируется ленивым образом и в дальнейшем не меняется. Внутренности метода acquireConstructorAccessor описывать не стану, скажу лишь, что в результате он приводит к вызову метода newConstructorAccessor объекта класса sun.reflect.ReflectionFactory . И именно для конструктора класса java.lang.Class (а ещё для абстрактных классов) данный метод возвращает объект InstantiationExceptionConstructorAccessorImpl . Он не умеет ничего инстанцировать, а только бросается исключениями на каждом обращении к нему. Всё это означает лишь одно: правильный ConstructorAccessor придётся инстанцировать самим.

Уровень 3. Нативный

Время узнать, каких вообще типов бывают объекты ConstructorAccessor (помимо описанного выше):

  • BootstrapConstructorAccessorImpl :
    используется для инстанцирования классов, которые сами являются реализацией ConstructorAccessor . Вероятно, спасает какой-то код от бесконечной рекурсии. Штука узкоспециализированная, трогать я её не буду;
  • GeneratedConstructorAccessor :
    самая интересная реализация, о которой я расскажу подробно, но позже;
  • связка NativeConstructorAccessorImpl и DelegatingConstructorAccessorImpl :
    то, что возвращается по умолчанию, и поэтому рассмотрится мною в первую очередь. DelegatingConstructorAccessorImpl попросту делегирует свою работу другому объекту, хранящемуся у него в поле. Плюс данного подхода в том, что он позволяет подменить реализацию на лету. Именно это на самом деле и происходит — NativeConstructorAccessorImpl для каждого конструктора отрабатывает максимум столько раз, сколько указано в системном свойстве sun.reflect.inflationThreshold (15 по умолчанию), после чего подменяется на GeneratedConstructorAccessor . Справедливости ради стоит добавить, что установка свойства sun.reflect.noInflation в значение «true» по сути сбрасывает inflationThreshhold в ноль, и NativeConstructorAccessorImpl перестаёт создаваться в принципе. По умолчанию это свойство имеет значение «false» .

Итак, для самого обычного класса при самых обычных обстоятельствах мы бы получили объект
NativeConstructorAccessorImpl , а значит, именно его и попробуем создать вручную:

Здесь нет никаких подвохов: объект создаётся без лишних ограничений, и всё, что нам остаётся, так это с его помощью инстанцировать java.lang.Class :

Но тут ждёт сюрприз:

Кажется, JVM не ожидает от пользователя столь нелогичных действий, особенно после всех предупреждений. Тем не менее, данный результат можно по праву считать достижением — завалил JVM, ни разу не воспользовавшись классами пакета sun.misc !

Уровень 4. Магический

Нативный вызов не работает — значит, теперь нужно разобраться с GeneratedConstructorAccessor .

На самом деле, это не просто класс, а целое семейство классов. Для каждого конструктора в рантайме генерируется своя уникальная реализация. Именно поэтому в первую очередь используется нативная реализация: генерировать байткод и создавать из него класс дело затратное. Сам процесс генерации класса запрятан в метод generateConstructor класса sun.reflect.MethodAccessorGenerator . Вызвать его вручную не составит труда:

Как и в случае с NativeConstructorAccessorImpl , тут нет подводных камней — данный код отработает и сделает ровно то, что от него ждут. Но давайте задумаемся на минутку: ну сгенерировали мы какой-то класс, откуда у него возьмутся права на вызов приватного конструктора? Такого быть не должно, поэтому мы просто обязаны сдампить сгенерированный класс и изучить его код. Сделать это несложно — встаём отладчиком в метод generateConstructor и в нужный момент дампим нужный нам массив байт в файл. Декомпилированная его версия выглядит следующим образом (после переименования переменных):

Такой код, естественно, обратно не скомпилируется, и этому есть две причины:

  • вызов new Class без скобочек. Он соответствует инструкции NEW , которая выделяет память под объект, но конструктор у него не вызывает;
  • вызов clazz.<init>(classLoader) — это как раз вызов конструктора, который в таком явном виде в языке Java невозможен.

Данные инструкции разнесены для того, чтобы находиться в разных try-блоках. Почему сделано именно так, я не знаю. Вероятно, это был единственный способ обрабатывать исключения так, чтобы они полностью соответствовали спецификации языка.

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

В JVM есть известный костыль под названием sun.reflect.MagicAccessorImpl . Всякий его наследник обладает неограниченным доступом к любым приватным данным любых классов. Это именно то, что нужно! Раз класс магический, то он поможет получить инстанс java.lang.Class . Проверяем:

и опять получаем исключение:

Вот это уже действительно интересно. Судя по всему, обещанной магии не произошло. Или я ошибаюсь?

Стоит рассмотреть ошибку внимательнее и сравнить её с тем, как должен себя вести метод newInstance . Будь проблема в строке clazz.<init>(classLoader) , мы бы получили InvocationTargetException . На деле же имеем IllegalAccessError , то есть до вызова конструктора дело не дошло. С ошибкой отработала инструкция NEW , не позволив выделить память под объект java.lang.Class . Здесь наши полномочия всё, окончены.

Уровень 5. Современный

Reflection не помог решить проблему. Может быть, дело в том, что Reflection старый и слабый, и вместо него стоит использовать молодой и сильный MethodHandles? Думаю, да. Как минимум, стоит попробовать.

И как только я решил, что Reflection не нужен, он тут же пригодился. MethodHandles — это, конечно, хорошо, но с помощью него принято получать лишь те данные, к которым есть доступ. А если понадобился приватный конструктор, то придётся выкручиваться по старинке.

Итак, нам нужен MethodHandles.Lookup с приватным доступом к классу java.lang.Class . На этот случай есть очень подходящий конструктор:

Получив lookup , можно получить объект MethodHandle , соответствующий требуемому нам конструктору:

После запуска этого метода я был откровенно удивлён — lookup делает вид, что конструктора вообще не существует, хотя он точно присутствует в классе!

Странно то, что причина исключения — NoSuchFieldError . Загадочно.

В этот раз ошибся именно я, но далеко не сразу это понял. Спецификация findConstructor требует, чтобы тип возвращаемого значения был void , несмотря на то, что у результата MethodType будет ровно таким, как я описал (всё потому, что метод <init> , отвечающий за конструктор, действительно возвращает void по историческим причинам).
Так или иначе, путаницы можно избежать, ведь у lookup есть второй метод для получения конструктора, и он называется unreflectConstructor :

Данный метод уж точно корректно отработает и вернёт тот handle, который должен.

Момент истины. Запускаем метод инстанцирования:

Думаю, вы уже догадались, что ничего хорошего не произойдёт, но давайте хоть глянем на ошибку. Сейчас это что-то новенькое:

По умолчанию stacktrace отображается укороченным, поэтому я добавил
-XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames в параметры запуска. Так становится проще понять, в какое странное место мы попали.

Не буду углубляться в то, какие классы генерирует MethodHandles , да это и не принципиально. Важно совсем другое — мы наконец-то докопались до использования sun.misc.Unsafe , и даже он не в силах создать объект java.lang.Class .

Метод allocaeInstance используется в тех местах, где нужно создать объект, но не вызывать у него конструктор. Такое бывает полезно, например, при десериализации объектов. По сути, это та же инструкция NEW , но не обременённая проверками прав доступа. Почти не обременённая, как мы только что увидели.

Раз даже Unsafe не смог, мне остаётся лишь прийти к печальному заключению: аллоцировать новый объект java.lang.Class невозможно. Интересно выходит — думал, что запрещён конструктор, а запрещена аллокация! Попробуем это дело обойти.

Уровень 6. Небезопасный

Предлагаю создать пустой объект и взглянуть, из чего же он состоит. Для этого возьмём Unsafe и аллоцируем новенький java.lang.Object :

На текущей JVM результатом будет область памяти в 12 байт, выглядящая вот так:

То, что вы здесь видите, это «заголовок объекта». По большому счёту, он состоит из двух частей — 8 байт markword, которые нас не интересуют, и 4 байта classword, которые важны.

Каким образом JVM узнаёт класс объекта? Она делает это путём чтения области classword, которая хранит указатель на внутреннюю структуру JVM, описывающую класс. Значит если в данное место записать другое значение, то и класс объекта изменится!

Дальнейший код очень, очень плохой, никогда так не делайте:

Мы прочитали classword объекта Object.class и записали его в classword объекта object . Результат работы следующий:

С натяжкой можно считать, что java.lang.Class мы аллоцировали. Мы молодцы! Теперь надо вызвать конструктор. Вы можете смеяться, но сейчас мы будем с помощью ASM генерировать класс, умеющий вызывать нужный конструктор. Естественно, при этом нужно унаследоваться от MagicAccessorImpl .

Так начинается создание класса (константы импортированы статически, так короче):

Так ему создаётся конструктор:

А так создаётся метод void construct(Class<?>, ClassLoader) , который внутри себя вызывает конструктор у объекта Class<?> :

Класс готов. Осталось загрузить, инстанцировать и вызвать нужный метод:

И это работает! Точнее так: повезло, что работает. Можно проверить, запустив следующий код:

Вывод будет таким:

О том, в какую область памяти записался этот ClassLoader и откуда потом прочитался, я тактично умолчу. И, как ожидалось, вызов практически любого другого метода на данном объекте приводит к немедленному краху JVM. А в остальном — цель выполнена!

Что там в Java 9?

В Java 9 всё почти так же. Можно проделать все те же действия, но с несколькими оговорками:

  • в параметры компилятора надо добавить —add-exports java.base/jdk.internal.reflect=sample (где sample — это имя вашего модуля);
  • в параметры запуска надо добавить:
    —add-opens java.base/jdk.internal.reflect=sample
    —add-opens java.base/java.lang=sample
    —add-opens java.base/java.lang.reflect=sample
    —add-opens java.base/java.lang.invoke=sample
    —add-opens java.base/jdk.internal.reflect=java.base
  • в зависимости модуля надо добавить requires jdk.unsupported ;
  • у конструктора java.lang.Class поменялась сигнатура, надо учесть.

Так же стоит учесть, что sun.reflect перенесли в jdk.internal.reflect и что класс MyConstructorInvocator теперь надо грузить тем же загрузчиком, что у MagicAccessorImpl .
ClassLoader.getSystemClassLoader() уже не сработает, у него не будет доступа.

Ещё исправили странную багу с NoSuchFieldError : теперь на его месте NoSuchMethodError , который там и должен быть. Мелочь, но приятно.

В целом, в Java 9 нужно намного сильнее постараться, чтобы выстрелить себе в ногу, даже если именно это и является главной целью. Думаю, это и к лучшему.

Что такое Instance (инстансы) в Java?

Добрый день!
Недавно начал изучать Java (по урокам Hexlet), и столкнулся с таким вопросом:
«Что такое Instance в Java и для чего они вообще нужны»?
Как я понял, экземпляр класса в Java создаётся так:

class Main <
public static void main(String. args) <
Game game = new Game();
>
>

Первое слово Game — это инстанс?
Почему нельзя написать так:

Просто до этого изучал php, там экземпляры классов примерно так объявлялись.
Пока из-за этой темы не могу перейти к следующим урокам, так там «эти» инстансы везде используются.
Объясните пожалуйста новичку :))

instance это экземпляр класса (объект). Т. е. game это ссылка на instance (а поскольку в java все объекты доступны только по ссылке, можно говорить просто instance).

Game game = new Game();
объявляется переменная game типа Game и инициализируется созданным здесь же новым экземпляром (инстансом) Game

Почему нельзя написать так: game = new Game(); можно, если game объявлена ранее — язык то строго типизированный.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *