При рассмотрении инициализации класса мы все знаем, что при выполнении инициализации подкласса, если родительский класс не инициализирован, подкласс должен быть инициализирован сначала. Тем не менее, все не так просто, как единое предложение.
Во -первых, давайте посмотрим на условия для инициализации, запускающегося на Java:
(1) При использовании новых для создания объектов и доступа к статическим данным и методам, то есть при получении инструкций: новые, GetStatic/Putstatic и Invokestatic;
(2) при использовании отражения, чтобы вызвать класс;
(3) При инициализации класса, если родительский класс не был инициализирован, инициализация родительского класса будет инициирована сначала;
(4) класс, где выполняется основной метод входа, расположен;
(5) класс, в котором рукоятка метода находится в динамической поддержке языка JDK1.7, если инициализация не запускается;
После компиляции генерируется метод <Clinit>, и инициализация класса осуществляется в этом методе. Этот метод выполняется только, и JVM гарантирует это и выполняет управление синхронизацией;
Среди них условие (3), с точки зрения вызова метода, является подклассом <Clinit>, который рекурсивно вызывает родительский класс <Clinit> в начале, что похоже на тот факт, что мы должны сначала вызвать конструктор родительского класса в конструкторе подкласса;
Но следует отметить, что «запуск» не завершает инициализацию, что означает, что возможно, что инициализация подкласса будет заканчиваться заранее до инициализации родительского класса, где находится «опасность».
1. Пример инициализации класса:
В этом примере я использую периферический класс для содержания 2 класса статического члена с отношениями наследования. Поскольку инициализация периферического класса и статических классов членов не имеют причинно -следственной связи, безопасно и удобно показать это так;
Родительский класс A и дочерний класс B соответственно содержат основные функции. Из приведенного выше условия запуска (4) можно видеть, что различные пути инициализации класса запускаются путем вызова этих двух основных функций соответственно;
Проблема с этим примером заключается в том, что родительский класс содержит статическую ссылку на детского класса и инициализирует его в определении:
public class wrapperclass {private static class a {static {System.out.println ("Начал инициализации класса A); } // родительский класс содержит статические ссылки на частный класс дочернего класса b = new b (); Защищенный статический int in in in = 9; static {System.out.println ("Конец инициализации класса A); } public static void main (string [] args) {}} частный статический класс B расширяет {static {System.out.println ("Start start of и инициализация класса B ..."); } // Домен подкласса зависит от домена родительского класса Private Static int bint = 9 + A.aint; public b () {// Статический домен конструктора зависит от Class System.out.println ("Constructor Call для класса B" + "Значение Bint" + bint); } static {System.out.println ("инициализация класса B заканчивается ..." + "Значение in int:" + bint); } public static void main (string [] args) {}}} Сценарий 1: результат вывода, когда запись является основной функцией класса B:
/** * Запускается инициализация класса A ... * Конструктор класса B вызывает значение Bint 0 * Class A Connks Tond ... * Запускается инициализация класса B ... * Заканчивается инициализацией класса B ... не знайте: 18 */
Анализ: Видно, что вызов основной функции запускает инициализацию класса B и входит в метод <Clinit> класса B. класс A, как его родительский класс, начинает сначала инициализация и входит в метод <Clinit>, и есть утверждение нового b (); В настоящее время B будет создан создан, который уже находится в классе <Clinit> B. Основной поток получил блокировку и начал выполнять <Clinit> в классе B. Вначале мы сказали, что JVM обеспечит метод инициализации класса только один раз. После получения новой инструкции JVM больше не введет метод <Clinit> класса B, но будет создан напрямую. Однако в настоящее время класс B не завершил инициализацию класса, поэтому вы можете видеть, что значение Bint равно 0 (это 0 - нулевая инициализация, выполняемая после выделения памяти области метода на этапе подготовки класса загрузки);
Следовательно, можно сделать вывод, что родительский класс содержит статический домен типа ребенка и выполняет действие назначения, что может привести к выполнению экземпляров подкласса до завершения инициализации класса;
Сценарий 2: результат вывода, когда запись является основной функцией класса A:
/** * Запускается инициализация класса A ... * Запускается инициализация класса B ... * Заканчивается инициализацией класса B ... Значение in int: 9 * Конструктор класса B вызывает значение Bint 9 * Class A Заканчивается заканчивается ... *//
Анализ: После анализа сценария 1 мы знаем, что запуск инициализации класса A путем инициализации класса B приведет к выполнению экземпляров переменной класса B в классе A до завершения инициализации класса B. Таким образом, если класс A инициализируется сначала, можно ли запустить класс B сначала при создании переменной класса, так что инициализация производится до инстанции? Ответ - да, но все еще есть проблемы.
Согласно выводу, мы видим, что инициализация класса B выполняется до завершения инициализации класса A, что вызывает переменные, такие как переменная класса, не инициализируется только после того, как класс B будет инициализирована, поэтому значение, полученное доменом Bint в классе B, - «0», а не «18», как мы ожидали;
Заключение: Таким образом, можно сделать вывод, что очень опасно включать классовые переменные типов подкласса в родительский класс и создавать их при их определении. Конкретная ситуация может быть не такой простой, как пример. Вызов методов назначения значений в определении также опасно. Даже если вы хотите включить статические домены типов подкласса, вы также должны назначать значения статическими методами, поскольку JVM может убедиться, что все действия инициализации завершены до того, как будет вызван статический метод (конечно, эта гарантия заключается в том, что вы не должны включать статический B B = новое B (); такое поведение инициализации);
2. Оставленный пример:
Во -первых, вам нужно знать процесс создания объектов:
(1) При столкновении с новой инструкцией проверьте, завершил ли класс загрузку, проверку, подготовку, анализ и инициализацию (процесс анализа - это проанализировать ссылку на символ в прямую ссылку, например, имя метода является ссылкой на символ, который можно выполнять при использовании этой ссылки на символ после завершения инициализации, точное, чтобы поддержать динамическое связывание), эти процессы обрабатываются до завершения;
(2) Распределите память, используйте бесплатный список или метод столкновения указателя и «ноль» вновь выделенную память. Следовательно, все переменные экземпляра инициализируются до 0 по умолчанию (ссылка на нулевую) в этой ссылке;
(3) Выполните метод <int>, включая проверку вызова в метод <int> (конструктор) родительского класса, действия, определяемые переменной экземпляра, экстрематор выполняет в экземпляре и, наконец, вызывая действия в конструкторе.
Этот пример может быть более хорошо известным, то есть он нарушает «не вызовут переопределяемые методы в методе конструктора, клонов и методе чтения». Причина в том, что полиморфизм в Java, то есть динамическое связывание.
Конструктор родительского класса A содержит защищенный метод, а класс B - его подкласс.
публичный класс неправильный институт {частный статический класс a {public a () {dosomething (); } защищенный void dosomething () {System.out.println ("a dosomething"); }} частный статический класс B расширяет {private int bint = 9; @Override Protected void dosomething () {System.out.println ("B Dosomething, bint:" + bint); }} public static void main (string [] args) {b b = new b (); }}Результат вывода:
/** * B's Dosomething, Bint: 0 */
Анализ: Во -первых, вам нужно знать, что, когда нет дисплея, компилятор Java генерирует конструктор по умолчанию и вызовет конструктор родительского класса в начале. Следовательно, конструктор класса B будет называть конструктор класса A первым в начале.
Защищенный метод, что -то вызывается в классе A. Из результата вывода, мы видим, что реализация метода подкласса фактически вызвана, и экземпляр подкласса еще не начался, поэтому Bint не является 9 как «ожидаемым», но 0;
Это связано с динамическим связыванием, что -то является защищенным методом, поэтому он вызывается через директиву Invokevirtual, которая обнаруживает соответствующую реализацию метода, основанную на типе экземпляра объекта (вот объект экземпляра B, а соответствующий метод - это метод реализации класса B), поэтому этот результат.
Заключение: как упоминалось ранее, «не вызывайте переопределяемых методов в методе конструктора, клонов и метода ReadObject».
Выше приведены два «минных полей» в инициализации и экземпляра класса Java. Я надеюсь, что это будет полезно для каждого обучения.