Newinstance java что это
Иногда это может быть требуемым, чтобы получить внутреннее состояние от объекта, который только устанавливается после конструкции. Рассмотрите сценарий, где необходимо получить внутренний набор символов, используемый java.io.Console . ( Console набор символов сохранен в частном поле и является не обязательно тем же самым как набором символов значения по умолчанию виртуальной машины Java, возвращенным java.nio.charset.Charset.defaultCharset() ). ConsoleCharset пример показывает, как это могло бы быть достигнуто:
import java.io.Console; import java.nio.charset.Charset; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import static java.lang.System.out; public class ConsoleCharset < public static void main(String. args) < Constructor[] ctors = Console.class.getDeclaredConstructors(); Constructor ctor = null; for (int i = 0; i < ctors.length; i++) < ctor = ctors[i]; if (ctor.getGenericParameterTypes().length == 0) break; >try < ctor.setAccessible(true); Console c = (Console)ctor.newInstance(); Field f = c.getClass().getDeclaredField("cs"); f.setAccessible(true); out.format("Console charset : %s%n", f.get(c)); out.format("Charset.defaultCharset(): %s%n", Charset.defaultCharset()); // production code should handle these exceptions more gracefully >catch (InstantiationException x) < x.printStackTrace(); >catch (InvocationTargetException x) < x.printStackTrace(); >catch (IllegalAccessException x) < x.printStackTrace(); >catch (NoSuchFieldException x) < x.printStackTrace(); >> >
Отметьте:
Class.newInstance() только успешно выполнится, если конструктор будет, имеет нулевые параметры и уже доступен. Иначе, необходимо использовать Constructor.newInstance() как в вышеупомянутом примере.
Пример выводил для системы Unix:
$ java ConsoleCharset Console charset : ISO-8859-1 Charset.defaultCharset() : ISO-8859-1
Пример выводил для системы Windows:
C:\> java ConsoleCharset Console charset : IBM437 Charset.defaultCharset() : windows-1252
Другое общее применение Constructor.newInstance() должен вызвать конструкторов, которые берут параметры. RestoreAliases пример находит определенного конструктора единственного параметра и вызывает это:
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import java.util.Set; import static java.lang.System.out; class EmailAliases < private Setaliases; private EmailAliases(HashMap h) < aliases = h.keySet(); >public void printKeys() < out.format("Mail keys:%n"); for (String k : aliases) out.format(" %s%n", k); >> public class RestoreAliases < private static MapdefaultAliases = new HashMap(); static < defaultAliases.put("Duke", "duke@i-love-java"); defaultAliases.put("Fang", "fang@evil-jealous-twin"); >public static void main(String. args) < try < Constructor ctor = EmailAliases.class.getDeclaredConstructor(HashMap.class); ctor.setAccessible(true); EmailAliases email = (EmailAliases)ctor.newInstance(defaultAliases); email.printKeys(); // production code should handle these exceptions more gracefully >catch (InstantiationException x) < x.printStackTrace(); >catch (IllegalAccessException x) < x.printStackTrace(); >catch (InvocationTargetException x) < x.printStackTrace(); >catch (NoSuchMethodException x) < x.printStackTrace(); >> >
Этот пример использование Class.getDeclaredConstructor() найти конструктора с единственным параметром типа java.util.HashMap . Отметьте, что достаточно передать HashMap.class начиная с параметра любому get*Constructor() метод требует class только в целях типа. Должный ввести стирание , следующее выражение оценивает к true :
HashMap.class == defaultAliases.getClass()
Пример тогда создает новый экземпляр class, используя этого конструктора с Constructor.newInstance() .
$ java RestoreAliases Mail keys: Duke Fang
Newinstance java что это
Метод newInstance() позволяет создать объект указанного класса. Но что ВАЖНО отметить — такой вызов возможен только в случае, если класс имеет конструктор БЕЗ ПАРАМЕТРОВ. Если такого конструктора в вашем классе нет, то воспользоваться таким простым вызовом не получится — придется действовать сложнее. Так что иметь конструктор без параметров — это неплохая идея.
Как вы уже возможно догадались, вызов возвращает объект класса Object и нам надо вручную привести его к нужному типу. Т.к. мы полагаемся на “порядочность” нашего класса, которая выражается в поддержке интерфейса FinanceInfoBuilder, то мы к нему и приводим наш объект.
Теперь для смены класса для загрузки финансовых показателей достаточно просто отредактировать файл builder.properties. Ничего больше. Далее следуют бурные продолжительные аплодисменты.
Проект в NetBeans можно загрузить здесь: FinanceExample
Класс Class
Мы посмотрели вариант загрузки класса и создание объекта нужного нам класса. На самом деле такой вариант использования рефлексии весьма распространен. Так что очень рекомендую запомнить это решение. Теперь мы посмотрим, как можно не только создать объект нужного класса, но и как можно обратиться к полям или методам.
Определим простой класс для наших экспериментов
* Класс для демонстрации рефлексии
package edu . javacourse . reflection ;
public class SimpleClass
private String first ;
public String getFirst ( ) <
return first ;
public String simple ( ) <
return "Method1" ;
public String concat ( String s1 , String s2 ) <
return s1 + s2 ;
И теперь “поиграем” с нашим классом — обратимся к его полю и методам через Reflection API
* Класс для демонстрации рефлексии
package edu . javacourse . reflection ;
import java . lang . reflect . Field ;
import java . lang . reflect . Method ;
public class SimpleReflectionExample
public static void main ( String [ ] args ) <
//Пример обращения к полям и методам класса
demoReflection ( ) ;
> catch ( Exception ex ) <
ex . printStackTrace ( System . out ) ;
private static void demoReflection ( ) throws Exception <
// Заружаем описание класса
Class example = Class . forName ( "edu.javacourse.reflection.SimpleClass" ) ;
SimpleClass sc = ( SimpleClass ) example . newInstance ( ) ;
// Обращение к полю
demoReflectionField ( example , sc ) ;
// Обращение к методу
demoReflectionMethod ( example , sc ) ;
private static void demoReflectionField ( Class example , SimpleClass sc ) throws Exception <
// Получить обхект типа Field - обратите внимание, что поле private
Field f = example . getDeclaredField ( "first" ) ;
// Выставить разрешение для доступа к полю
f . setAccessible ( true ) ;
// Получить значение поля - оно у нас пока NULL
String test = ( String ) f . get ( sc ) ;
System . out . println ( "Field before SET:" + sc . getFirst ( ) ) ;
// Установить значение поля
f . set ( sc , "Test" ) ;
System . out . println ( "Field after SET:" + sc . getFirst ( ) ) ;
private static void demoReflectionMethod ( Class example , SimpleClass sc ) throws Exception <
// Вызов метода без параметров
// Получить обхект типа Method по имени
Method method1 = example . getMethod ( "simple" ) ;
// Вызвать метод с помощью invoke - передать туда только объект
String simple = ( String ) method1 . invoke ( sc ) ;
System . out . println ( "Simple:" + simple ) ;
// Вызов метода с параметрами
// Сначала надо определить список параметров - вспоминаем overloading
// У нас это две строки - String
Class [ ] paramTypes = new Class [ ] < String . class , String . class >;
// Получить обхект типа Method по имени и по списку параметров
Method concat = example . getMethod ( «concat» , paramTypes ) ;
// Вызвать метод — передать туда объект и два параметра типа строка
String answer = ( String ) concat . invoke ( sc , «1» , «2» ) ;
System . out . println ( «Concat:» + answer ) ;
Итак, давайте разбираться. Метод demoReflcetion уже знакомым нам способом создает объект типа SimpleClass. В принципе этот момент был не обязателен и выглядит достаточно натянуто — смысл загружать класс и следующей строкой его же и использовать, но для демонстрации подойдет. Дальше начинаются гораздо более интересные вещи.
Метод demoReflectionField показывает способ обращения к полю (причем к приватному полю). Самое главное — это получение обхекта типа Field по имени, с помощью которого можно уже работать с конкретным полем. Дальше код демонстрирует такие возможности.
Особо хочу отметить вызов setAccessible(true), который позволяет работать с приватным полем.
Метод demoReflectionMethod демонстрирует вариант получения метода по имени и по имени и параметрам — вспоминаем, что такое overloading. Здесь уже используется другой тип — Method — с помощью которого можно вызвать конкретный метод объекта. Дальше я предлагаю вам самим запустить пример а также прочитать код и комментарии к нему.
Проект в NetBeans можно загрузить здесь: SimpleReflection
Аннотации
В версии Java 1.5 появился очень интересный и очень мощный инструмент — аннотации. По сути аннотация — это именованный блок информации, который содержит набор именованных параметров и этот блок можно “прикрепить” к классу, методу, полю и даже параметру в методе. Другими словами — у аннотации есть имя и у нее есть список параметров с именами, которые можно выставить в определенные значения.
И теперь еще раз подумайте — вы можете прикрепить к основным артефактам кода (класс, метод, поле) блок с информацией и ЭТОТ БЛОК ДОСТУПЕН через рефлексию.
И что в этом таког, можете спросить вы ? Дело в том, что теперь есть возможность написать библиотеку (набор классов), которая может обрабатывать классы с определенными аннотациями. Например, именно так работает система ORM (Object Relation Mapping) — система сохранения объектов в базу данных (если честно, то не только так). С появлением аннотаций это теперь очень несложно сделать — вы аннотируете класс, который надо сохранить в базу данных специальным набором аннотаций и все. Дальше библиотека смотрит по аннотациям в какую таблицу и какое поле этого объекта к какую колонку записывается. Там еще можно “навесить” дополнительные условия, связи и много чего еще. Скорость разработки возрастает, написание системы упрощается, становится более лаконичной.
Точно также можно делать EJB, сервлеты. JUnit работает по этому принципу. Системы автоматического создания набора нужных объектов (IoC/DI — Inversion of Control/Dependency Injection) с нужными значениями полей использует аннотации. Веб-сервисы строятся на основе аннотаций, работа с XML и много чего еще.
По сути, библиотека просто говорит вам: “если у твоего класса есть такие-то аннотации, то я смогу произвести над ним нужную тебе работу. Просто напиши нужные аннотации с нужными параметрами”.
Аннотации настолько “вросли” в различные технологии Java, что на сегодня без них работать гораздо сложнее.
Для примера я создал свою аннотацию (хотя прикладной программист чаще всего использует уже готовые) для расширения нашего примера с финансовой информацией.
Вот как выглядит аннотация (это файл .java)
Кофе-брейк #174. Различные способы создания объекта в Java.String в Int на Java — как преобразовать строку в целое число


Источник: Medium В этом руководстве мы изучим различные способы создания объекта в Java. Объект Java — это экземпляр класса Java. Каждый объект имеет состояние, поведение и идентификатор. Поля (переменные) хранят состояние объекта, тогда как методы (функции) показывают действие объекта. Классы служат “чертежами”, из которых во время выполнения создаются экземпляры объектов.
Создание объекта в Java
- с помощью ключевого слова new
- методом newInstance()
- методом clone()
- десериализацией объекта
Ключевое слово new
Это наиболее распространенный способ создания объекта в Java. Ключевое слово new создает экземпляр класса, выделяя память для нового экземпляра указанного типа. После new идет конструктор — специальный метод, отвечающий за создание объекта и инициализацию полей создаваемого объекта. Объект создается оператором new и инициализируется конструктором. Перед вами пример создания объекта Java с оператором new :
Date today = new Date();
Это выражение генерирует новый объект Date ( Date — это класс внутри пакета java.util ). Это единственное предложение в коде выполняет три операции: объявление, инстанцирование и инициализацию. Date today — это объявление переменной, которое информирует компилятор о том, что today будет ссылаться на объект типа Date . Оператор new создает экземпляр класса Date (создавая при этом новый объект Date в памяти), а Date() инициализирует объект. Рассмотрим пример, приведенный ниже:
public class Person < private String name; private int uid; public Person() < this.name = "Michael Cole"; this.uid = 101; >public Person(String name, int uid) < super(); this.name = name; this.uid = uid; >// getters and setters. public static void main(String[] args) < Person p1 = new Person(); Person p2 = new Person("John Bodgan", 102); System.out.println("Name: " + p1.getName() + " UID: " + p1.getUid()); System.out.println("Name: " + p2.getName() + " UID: " + p2.getUid()); >>
- Объект p1 вызывает непараметризованный конструктор, в котором значение имени переменной установлено на “Michael Cole”, а UID — на 101.
- Объект p2 вызывает параметризованный конструктор, где он передает конструктору значение “John Bodgan” и 102. Затем этим значениям присваиваются имя переменной и UID.
Использование метода newInstance()
- Метод newInstance() из API java.lang.Class
- Метод newInstance() из API java.lang.reflect.Constructor
Использование newInstance() из Class API
Чтобы создать объект класса во время выполнения, мы должны вызвать метод newInstance() из Class API, который возвращает объект этого класса. Метод newInstance() класса java.lang.Class не учитывает никаких параметров или аргументов и может называться конструктором без аргументов для этого класса. Рассмотрим пример кода для создания объекта класса Person с использованием метода newInstance() класса java.lang.Class :
public class Person < private String name; private int uid; public Person() < this.name = "Carl Max"; this.uid = 101; >// getters and setters. public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException < Class c = Class.forName("com.medium.option2.Person"); @SuppressWarnings("deprecation") Person p = (Person) c.newInstance(); System.out.println("Name: " + p.getName()); System.out.println("UID: " + p.getUid()); >>
- И Class.forName() , и newIstance() генерируют исключения, которые необходимо обрабатывать либо с помощью блоков try и catch , либо с помощью ключевого слова throws .
- Метод newInstance() из Class API устарел, начиная с версии Java 9.
Использование newInstance() из Constructor API
Метод newInstance() класса Constructor ( java.lang.reflect.Constructor ) аналогичен методу newInstance() класса Class , за исключением того, что он принимает параметры для параметризованных конструкторов. Давайте продемонстрируем этот подход, создав объект класса Person с помощью метода newInstance() класса java.lang.reflect.Constructor :
public class PersonTwo < private String name; private int uid; public PersonTwo() < this.name = "Maya Kumari"; this.uid = 101; >public PersonTwo(String name) < this.name = name; this.uid = 102; >public PersonTwo(String name, Integer uid) < this.name = name; this.uid = uid; >// getters and setters. public static void main(String[] args) < try < Class.forName("com.medium.option2.PersonTwo"); Constructor c1 = PersonTwo.class.getConstructor(); PersonTwo p1 = (PersonTwo) c1.newInstance(); System.out.println("Name: " + p1.getName()); System.out.println("UID: " + p1.getUid()); Constructor c2 = PersonTwo.class.getConstructor(String.class); PersonTwo p2 = (PersonTwo) c2.newInstance("James Gunn"); System.out.println("Name: " + p2.getName()); System.out.println("UID: " + p2.getUid()); Constructor c3 = PersonTwo.class.getConstructor(String.class, Integer.class); PersonTwo p3 = (PersonTwo) c3.newInstance("Mark Brown", 103); System.out.println("Name: " + p3.getName()); System.out.println("UID: " + p3.getUid()); >catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) < // TODO Auto-generated catch block e.printStackTrace(); >> >
В приведенном выше коде сначала нам нужно загрузить класс, используя метод Class.forName() . Далее мы вызовем метод getConstructor() для сопоставления типов данных передаваемых параметров. Наконец, в методе newInstance() мы передаем требуемый параметр (null в случае отсутствия аргумента). Метод newInstance() вернет новый объект класса PersonTwo , вызвав соответствующий конструктор.
Использование метода clone()
Метод clone() является частью класса Object и используется для создания копии существующего объекта. Он создает объект класса без вызова какого-либо конструктора класса. Чтобы клонировать метод, соответствующий класс должен иметь реализованный интерфейс Cloneable , который является интерфейсом маркера. Сейчас мы создадим объект класса Person , а затем клонируем его в другой объект класса Person :
public class Person implements Cloneable < @Override protected Object clone() throws CloneNotSupportedException < return super.clone(); >private String name; private int uid; public Person(String name, int uid) < super(); this.name = name; this.uid = uid; >// getters and setters. public static void main(String[] args) < Person p1 = new Person("Ryan", 101); try < Person p2 = (Person) p1.clone(); System.out.println("Name: " + p2.getName()); System.out.println("UID: " + p2.getUid()); >catch (CloneNotSupportedException e) < e.printStackTrace(); >> >
Примечание. Клонированный объект будет ссылаться на тот же исходный объект через ссылку p2 . Однако клонированный объект будет иметь отдельное назначение памяти. Это означает, что любые изменения, сделанные в объекте Person по ссылке p2 , не изменят исходный объект Person , на который ссылается ссылка p1 . Это связано с тем, что метод clone() создает поверхностную копию объектов.
Использование десериализации объектов
Десериализация объекта — это процесс извлечения объекта из серии потоков байтов. Сериализация делает обратное. Его основная цель — извлечь сохраненный объект из базы данных/сети обратно в память. Если мы хотим сериализовать или десериализовать объект, нам нужно реализовать интерфейс Serializable (интерфейс маркера). Рассмотрим пример, приведенный ниже:
public class PersonDriver < public static void main(String[] args) < Person p1 = new Person("Max Payne", 101); FileOutputStream fileOutputStream; try < fileOutputStream = new FileOutputStream("link to text file"); ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream); outputStream.writeObject(p1); outputStream.flush(); outputStream.close(); >catch (IOException e) < e.printStackTrace(); >FileInputStream fileInputStream; try < fileInputStream = new FileInputStream("link to text file"); ObjectInputStream inputStream = new ObjectInputStream(fileInputStream); Person p2 = (Person) inputStream.readObject(); System.out.println("Name: " + p2.getName()); System.out.println("UID: " + p2.getUid()); inputStream.close(); >catch (IOException | ClassNotFoundException e) < e.printStackTrace(); >> >
Здесь мы сначала сериализуем объект Person по ссылке p1 в текстовый файл. Метод writeObject() запишет поток байтов объекта в текстовый файл. Затем, используя десериализацию объекта, мы извлекаем объект Person обратно в p2 . Точно так же метод readObject() будет считывать объект из потока ввода объекта. Наконец, мы выведем данные из объекта Person в консоль.
Заключение
В этой статье мы узнали о различных способах создания объекта в Java. Во-первых, мы рассмотрели создание объектов с использованием ключевого слова new , которое является наиболее распространенным способом. Затем мы изучили метод newInstance() из классов Class и Constructor , который является еще одним популярным способом создания объектов. Затем мы использовали метод clone() , который создает поверхностную копию существующего объекта вместо создания нового объекта. И, наконец, мы использовали концепцию сериализации и десериализации объектов для создания объектов в Java.
String в Int на Java — как преобразовать строку в целое число

Источник: FreeCodeCamp Сегодня вы узнаете, как преобразовать строку в целое число в Java с помощью двух методов класса Integer — parseInt() и valueOf() . Это поможет вам при выполнении математических операций с использованием значения строковой переменной.
Как преобразовать строку в целое число в Java с помощью Integer.parseInt
Этот вариант предполагает, что метод parseInt() берет строку для преобразования в целое число (integer) в качестве параметра:
Integer.parseInt(string_varaible)
Прежде чем рассматривать пример его использования, давайте посмотрим, что происходит, когда вы добавляете строковое значение и целое число без какого-либо преобразования:
class StrToInt < public static void main(String[] args) < String age = "10"; System.out.println(age + 20); // 1020 >>
В этом коде мы создали переменную age со строковым значением “10”. При прибавлении к целочисленному значению (integer value) числа 20 мы ошибочно получили 1020 вместо правильного ответа 30. Исправить это можно, используя метод parseInt() :
class StrToInt < public static void main(String[] args) < String age = "10"; int age_to_int = Integer.parseInt(age); System.out.println(age_to_int + 20); // 30 >>
Здесь, чтобы преобразовать переменную age в целое число, мы передали ее как параметр методу parseInt() — Integer.parseInt(age) — и сохранили в переменной с именем age_to_int . Теперь при добавлении к другому целому числу мы получили правильное сложение: age_to_int + 20 .
Как преобразовать строку в целое число в Java с помощью Integer.valueOf
Метод valueOf() работает так же, как метод parseInt() . В качестве параметра он принимает строку, которую нужно преобразовать в целое число. Вот пример:
class StrToInt < public static void main(String[] args) < String age = "10"; int age_to_int = Integer.valueOf(age); System.out.println(age_to_int + 20); // 30 >>
- Мы передали строку в качестве параметра valueOf() : Integer.valueOf(age) . Он был сохранен в переменной с именем age_to_int .
- Затем мы добавили 10 к созданной переменной: age_to_int + 20 . В результате получилось 30 вместо 1020.
Заключение
В этой статье мы обсудили преобразование строк в целые числа на языке Java. Для преобразования строки в целое число использовались два метода класса Integer — parseInt() и valueOf() . Удачного кодирования!
Инстанцируем java.lang.Class

Конструктор java.lang.Class является одной из самых охраняемых сущностей в языке Java. В спецификации чётко сказано, что объекты типа Class может создавать только сама JVM и что нам тут делать нечего, но так ли это на самом деле?
Предлагаю погрузиться в глубины Reflection API (и не только) и выяснить, как там всё устроено и насколько трудно будет обойти имеющиеся ограничения.
Эксперимент я провожу на 64-битной JDK 1.8.0_151 с дефолтными настройками. Про Java 9 будет в самом конце статьи.
Уровень 1. Простой
Начнём с самых наивных попыток и пойдём по нарастающей. Сперва посмотрим врагу в лицо:
private Class(ClassLoader loader)
Ничего особенного этот конструктор собой не представляет. Компилятор не делает для него никаких исключений, и в байткоде конструктор тоже присутствует. Поэтому попробуем поступить так же, как мы бы поступили с любым другим классом:
Constructor constructor = Class.class.getDeclaredConstructor(ClassLoader.class); constructor.setAccessible(true); Class clazz = constructor.newInstance(ClassLoader.getSystemClassLoader());
Вполне ожидаемо данный код не будет работать и выдаст следующую ошибку:
Exception in thread "main" java.lang.SecurityException: Cannot make a java.lang.Class constructor accessible at java.lang.reflect.AccessibleObject.setAccessible0(. ) at java.lang.reflect.AccessibleObject.setAccessible(. ) at Sample.main(. )
С первой же попытки мы попали на первое предупреждение из метода setAccessible0 . Оно захардкожено специально для конструктора класса java.lang.Class :
private static void setAccessible0(AccessibleObject obj, boolean flag) throws SecurityException < if (obj instanceof Constructor && flag == true) < Constructorc = (Constructor) obj; if (c.getDeclaringClass() == Class.class) < throw new SecurityException("Cannot make a java.lang.Class" + " constructor accessible"); >> obj.override = flag; >
Не проблема, ведь ключевой строкой в этом методе является последняя — установка поля override в значение true . Это легко сделать, используя грубую силу:
Field overrideConstructorField = AccessibleObject.class.getDeclaredField("override"); overrideConstructorField.setAccessible(true); overrideConstructorField.set(constructor, true);
Уровень 2. Посложнее
Естественно, установка флага override — это не единственное ограничение, но теперь мы можем хотя бы продвинуться чуть дальше в работе метода newInstance . Достаточно далеко, чтобы спланировать дальнейшие действия. В этот раз ошибка будет следующая:
Exception in thread "main" java.lang.InstantiationException: Can not instantiate java.lang.Class at sun.reflect.InstantiationExceptionConstructorAccessorImpl.newInstance(. ) at java.lang.reflect.Constructor.newInstance(. ) at Sample.main(. )
Нас занесло прямиком в класс пакета sun.reflect , а мы знаем, что основная магия должна происходить именно там. Самое время заглянуть в реализацию newInstance класса Constructor и узнать, как мы туда попали:
public T newInstance(Object . initargs) throws . < . ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) < ca = acquireConstructorAccessor(); >@SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; >
Из реализации становится понятно, что 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 , а значит, именно его и попробуем создать вручную:
Class nativeCAClass = Class.forName("sun.reflect.NativeConstructorAccessorImpl"); Constructor nativeCAConstructor = nativeCAClass.getDeclaredConstructor(Constructor.class); nativeCAConstructor.setAccessible(true); ConstructorAccessor constructorAccessor = (ConstructorAccessor) nativeCAConstructor.newInstance(constructor);
Здесь нет никаких подвохов: объект создаётся без лишних ограничений, и всё, что нам остаётся, так это с его помощью инстанцировать java.lang.Class :
Class clazz = (Class) constructorAccessor.newInstance( new Object[]);
Но тут ждёт сюрприз:
# # A fatal error has been detected by the Java Runtime Environment: # # SIGSEGV (0xb) at pc=0x00007f8698589ead, pid=20537, tid=0x00007f8699af3700 # # JRE version: Java(TM) SE Runtime Environment (8.0_151-b12) (build 1.8.0_151-b12) # .
Кажется, JVM не ожидает от пользователя столь нелогичных действий, особенно после всех предупреждений. Тем не менее, данный результат можно по праву считать достижением — завалил JVM, ни разу не воспользовавшись классами пакета sun.misc !
Уровень 4. Магический
Нативный вызов не работает — значит, теперь нужно разобраться с GeneratedConstructorAccessor .
На самом деле, это не просто класс, а целое семейство классов. Для каждого конструктора в рантайме генерируется своя уникальная реализация. Именно поэтому в первую очередь используется нативная реализация: генерировать байткод и создавать из него класс дело затратное. Сам процесс генерации класса запрятан в метод generateConstructor класса sun.reflect.MethodAccessorGenerator . Вызвать его вручную не составит труда:
Class methodAccessorGeneratorClass = Class.forName("sun.reflect.MethodAccessorGenerator"); Constructor methodAccessorGeneratorConstructor = methodAccessorGeneratorClass.getDeclaredConstructor(); methodAccessorGeneratorConstructor.setAccessible(true); Object methodAccessorGenerator = methodAccessorGeneratorConstructor.newInstance(); Method generateConstructor = methodAccessorGeneratorClass .getDeclaredMethod("generateConstructor", Class.class, Class[].class, Class[].class, int.class); generateConstructor.setAccessible(true); ConstructorAccessor constructorAccessor = (ConstructorAccessor) generateConstructor.invoke(methodAccessorGenerator, constructor.getDeclaringClass(), constructor.getParameterTypes(), constructor.getExceptionTypes(), constructor.getModifiers());
Как и в случае с NativeConstructorAccessorImpl , тут нет подводных камней — данный код отработает и сделает ровно то, что от него ждут. Но давайте задумаемся на минутку: ну сгенерировали мы какой-то класс, откуда у него возьмутся права на вызов приватного конструктора? Такого быть не должно, поэтому мы просто обязаны сдампить сгенерированный класс и изучить его код. Сделать это несложно — встаём отладчиком в метод generateConstructor и в нужный момент дампим нужный нам массив байт в файл. Декомпилированная его версия выглядит следующим образом (после переименования переменных):
public class GeneratedConstructorAccessor1 extends ConstructorAccessorImpl < public Object newInstance(Object[] args) throws InvocationTargetException < Class clazz; ClassLoader classLoader; try < clazz = new Class; if (args.length != 1) < throw new IllegalArgumentException(); >classLoader = (ClassLoader) args[0]; > catch (NullPointerException | ClassCastException e) < throw new IllegalArgumentException(e.toString()); >try < clazz.(classLoader); return clazz; > catch (Throwable e) < throw new InvocationTargetException(e); >> >
Такой код, естественно, обратно не скомпилируется, и этому есть две причины:
- вызов new Class без скобочек. Он соответствует инструкции NEW , которая выделяет память под объект, но конструктор у него не вызывает;
- вызов clazz.(classLoader) — это как раз вызов конструктора, который в таком явном виде в языке Java невозможен.
Данные инструкции разнесены для того, чтобы находиться в разных try-блоках. Почему сделано именно так, я не знаю. Вероятно, это был единственный способ обрабатывать исключения так, чтобы они полностью соответствовали спецификации языка.
Если закрыть глаза на нетипичную обработку исключений, то во всём остальном данный класс абсолютно нормален, но всё ещё непонятно, откуда у него вдруг права на вызов приватных конструкторов. Оказывается, всё дело в суперклассе:
abstract class ConstructorAccessorImpl extends MagicAccessorImpl implements ConstructorAccessor
В JVM есть известный костыль под названием sun.reflect.MagicAccessorImpl . Всякий его наследник обладает неограниченным доступом к любым приватным данным любых классов. Это именно то, что нужно! Раз класс магический, то он поможет получить инстанс java.lang.Class . Проверяем:
Class clazz = (Class) constructorAccessor.newInstance( new Object[]);
и опять получаем исключение:
Exception in thread "main" java.lang.IllegalAccessError: java.lang.Class at sun.reflect.GeneratedConstructorAccessor1.newInstance(. ) at Sample.main(. )
Вот это уже действительно интересно. Судя по всему, обещанной магии не произошло. Или я ошибаюсь?
Стоит рассмотреть ошибку внимательнее и сравнить её с тем, как должен себя вести метод newInstance . Будь проблема в строке clazz.(classLoader) , мы бы получили InvocationTargetException . На деле же имеем IllegalAccessError , то есть до вызова конструктора дело не дошло. С ошибкой отработала инструкция NEW , не позволив выделить память под объект java.lang.Class . Здесь наши полномочия всё, окончены.
Уровень 5. Современный
Reflection не помог решить проблему. Может быть, дело в том, что Reflection старый и слабый, и вместо него стоит использовать молодой и сильный MethodHandles? Думаю, да. Как минимум, стоит попробовать.
И как только я решил, что Reflection не нужен, он тут же пригодился. MethodHandles — это, конечно, хорошо, но с помощью него принято получать лишь те данные, к которым есть доступ. А если понадобился приватный конструктор, то придётся выкручиваться по старинке.
Итак, нам нужен MethodHandles.Lookup с приватным доступом к классу java.lang.Class . На этот случай есть очень подходящий конструктор:
private Lookup(Class lookupClass, int allowedModes)
Constructor lookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class); lookupConstructor.setAccessible(true); MethodHandles.Lookup lookup = lookupConstructor .newInstance(Class.class, MethodHandles.Lookup.PRIVATE);
Получив lookup , можно получить объект MethodHandle , соответствующий требуемому нам конструктору:
MethodHandle handle = lookup.findConstructor(Class.class, MethodType.methodType(Class.class, ClassLoader.class));
После запуска этого метода я был откровенно удивлён — lookup делает вид, что конструктора вообще не существует, хотя он точно присутствует в классе!
Exception in thread "main" java.lang.NoSuchMethodException: no such constructor: java.lang.Class.(ClassLoader)Class/newInvokeSpecial at java.lang.invoke.MemberName.makeAccessException(. ) at java.lang.invoke.MemberName$Factory.resolveOrFail(. ) at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(. ) at java.lang.invoke.MethodHandles$Lookup.findConstructor(. ) at Sample.main(Sample.java:59) Caused by: java.lang.NoSuchFieldError: method resolution failed at java.lang.invoke.MethodHandleNatives.resolve(. ) at java.lang.invoke.MemberName$Factory.resolve(. ) at java.lang.invoke.MemberName$Factory.resolveOrFail(. ) . 3 more
Странно то, что причина исключения — NoSuchFieldError . Загадочно.
В этот раз ошибся именно я, но далеко не сразу это понял. Спецификация findConstructor требует, чтобы тип возвращаемого значения был void , несмотря на то, что у результата MethodType будет ровно таким, как я описал (всё потому, что метод , отвечающий за конструктор, действительно возвращает void по историческим причинам).
Так или иначе, путаницы можно избежать, ведь у lookup есть второй метод для получения конструктора, и он называется unreflectConstructor :
MethodHandle handle = lookup.unreflectConstructor(constructor);
Данный метод уж точно корректно отработает и вернёт тот handle, который должен.
Момент истины. Запускаем метод инстанцирования:
Class clazz = (Class) handle. invoke(ClassLoader.getSystemClassLoader());
Думаю, вы уже догадались, что ничего хорошего не произойдёт, но давайте хоть глянем на ошибку. Сейчас это что-то новенькое:
Exception in thread "main" java.lang.IllegalAccessException: java.lang.Class at sun.misc.Unsafe.allocateInstance(. ) at java.lang.invoke.DirectMethodHandle.allocateInstance(. ) at java.lang.invoke.LambdaForm$DMH/925858445.newInvokeSpecial_L_L(. ) at java.lang.invoke.LambdaForm$MH/523429237.invoke_MT(. ) at Sample.main(. )
По умолчанию stacktrace отображается укороченным, поэтому я добавил
-XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames в параметры запуска. Так становится проще понять, в какое странное место мы попали.
Не буду углубляться в то, какие классы генерирует MethodHandles , да это и не принципиально. Важно совсем другое — мы наконец-то докопались до использования sun.misc.Unsafe , и даже он не в силах создать объект java.lang.Class .
Метод allocaeInstance используется в тех местах, где нужно создать объект, но не вызывать у него конструктор. Такое бывает полезно, например, при десериализации объектов. По сути, это та же инструкция NEW , но не обременённая проверками прав доступа. Почти не обременённая, как мы только что увидели.
Раз даже Unsafe не смог, мне остаётся лишь прийти к печальному заключению: аллоцировать новый объект java.lang.Class невозможно. Интересно выходит — думал, что запрещён конструктор, а запрещена аллокация! Попробуем это дело обойти.
Уровень 6. Небезопасный
Предлагаю создать пустой объект и взглянуть, из чего же он состоит. Для этого возьмём Unsafe и аллоцируем новенький java.lang.Object :
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) theUnsafeField.get(null); Object object = unsafe.allocateInstance(Object.class);
На текущей JVM результатом будет область памяти в 12 байт, выглядящая вот так:

То, что вы здесь видите, это «заголовок объекта». По большому счёту, он состоит из двух частей — 8 байт markword, которые нас не интересуют, и 4 байта classword, которые важны.
Каким образом JVM узнаёт класс объекта? Она делает это путём чтения области classword, которая хранит указатель на внутреннюю структуру JVM, описывающую класс. Значит если в данное место записать другое значение, то и класс объекта изменится!
Дальнейший код очень, очень плохой, никогда так не делайте:
System.out.println(object.getClass()); unsafe.putInt(object, 8L, unsafe.getInt(Object.class, 8L)); System.out.println(object.getClass());
Мы прочитали classword объекта Object.class и записали его в classword объекта object . Результат работы следующий:
class java.lang.Object class java.lang.Class
С натяжкой можно считать, что java.lang.Class мы аллоцировали. Мы молодцы! Теперь надо вызвать конструктор. Вы можете смеяться, но сейчас мы будем с помощью ASM генерировать класс, умеющий вызывать нужный конструктор. Естественно, при этом нужно унаследоваться от MagicAccessorImpl .
Так начинается создание класса (константы импортированы статически, так короче):
ClassWriter cw = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS); cw.visit(V1_8, ACC_PUBLIC, "sun/reflect/MyConstructorInvocator", null, "sun/reflect/MagicAccessorImpl", null);
Так ему создаётся конструктор:
MethodVisitor init = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); init.visitCode(); init.visitVarInsn(ALOAD, 0); init.visitMethodInsn(INVOKESPECIAL, "sun/reflect/MagicAccessorImpl", "", "()V", false); init.visitInsn(RETURN); init.visitMaxs(-1, -1); init.visitEnd();
А так создаётся метод void construct(Class, ClassLoader) , который внутри себя вызывает конструктор у объекта Class :
MethodVisitor construct = cw.visitMethod(ACC_PUBLIC, "construct", "(Ljava/lang/Class;Ljava/lang/ClassLoader;)V", null, null); construct.visitCode(); construct.visitVarInsn(ALOAD, 1); construct.visitVarInsn(ALOAD, 2); construct.visitMethodInsn(INVOKESPECIAL, "java/lang/Class", "", "(Ljava/lang/ClassLoader;)V", false); construct.visitInsn(RETURN); construct.visitMaxs(-1, -1); construct.visitEnd();
Класс готов. Осталось загрузить, инстанцировать и вызвать нужный метод:
byte[] bytes = cw.toByteArray(); Class myCustomInvocator = unsafe.defineClass(null, bytes, 0, bytes.length, ClassLoader.getSystemClassLoader(), null); Object ci = myCustomInvocator.newInstance(); Method constructMethod = myCustomInvocator.getDeclaredMethod("construct", Class.class, ClassLoader.class); Class clazz = (Class) object; constructMethod.invoke(ci, clazz, ClassLoader.getSystemClassLoader());
И это работает! Точнее так: повезло, что работает. Можно проверить, запустив следующий код:
System.out.println(clazz.getClassLoader());
Вывод будет таким:
sun.misc.Launcher$AppClassLoader@18b4aac2
О том, в какую область памяти записался этот 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 нужно намного сильнее постараться, чтобы выстрелить себе в ногу, даже если именно это и является главной целью. Думаю, это и к лучшему.
Выводы:
- при желании в Java можно творить абсолютно безумные вещи, это забавно;
- Reflection API не так уж и сложно устроен;
- MagicAccessorImpl может не всё;
- sun.misc.Unsafe может не всё, но почти;
- Java 9 ещё сильнее старается вас обезопасить.
Не стоит слишком серьёзно воспринимать всё описанное. Сама по себе задача инстанцирования java.lang.Class совершенно бессмысленна. Здесь важны знания, полученные в процессе её решения.