Язык программирования Java Дмитриев Андрей Владиславович Май 2007
Часть 9. Информация о классах во время выполнения Введение Пакет java.lang.reflect RTTI - run-time type identification
Введение Это механизм, используемый для отслеживания или изменения состояния приложения, выполняемого внутри JVM Приложение может использовать любой член любого доступного внешнего класса по его имени
Недостатки Производительность приложения, использующего рефлексию обычно ниже, чем у приложения, ее не использующего Требует соответствующего разрешения для работы Нарушение целостности кода за счет возможности обращения к методам и полям, возможно, не предназначенным для прямого изменения
Типы объектов Каждый объект это либо ссылка(и наследник Object), либо примитивный тип Для каждого типа объекта JVM создает немодифицируемый экземпляр класса java.lang.Class
Возможности java.lang.Class Предоставляет API для доступа ко всем свойствам объекта Позволяет создавать экземпляры класса
Получение экземпляра Class Object.getClass().class Class.forName() Поле TYPE класса-обертки
Получение экземпляра Class(getClass()) Class c = "foo".getClass(); byte[] bytes = new byte[1024]; Class c = bytes.getClass();
Получение экземпляра Class(.class) Class c = boolean.class; // верно Class c = java.io.PrintStream.class; Class c = int[][][].class; boolean b; Class c = b.getClass(); //ошибка компиляции
Получение экземпляра Class(forName()) Class c = Class.forName("com.console.MyButton"); Class cStringArray = Class.forName("[[Ljava.lang.String;"); Class cDoubleArray = Class.forName("[D"); Должно быть известно имя класса Нельзя применять для примитивных типов
Получение экземпляра Class(TYPE) Class c = Double.TYPE; //аналог double.class Class c = Void.TYPE;
Методы для доступа к классам Class.getSuperclass() Class c = mypack.MyButton.class.getSuperclass(); Class.getClasses() – все внутренние классы Class [] c = Character.class.getClasses(); Class.getDeclaredClasses() – все внутренние классы, описанные в текущем классе Class.getEclosingClass() – содержащий класс public class MyClass { static Object o = new Object() { public void m() {} }; static Class =o.getClass().getEnclosingClass(); }
Параметры класса(с 5.0) Class c = Class.forName(MyButton); TypeVariable[] tv = c.getTypeParameters(); Type[] intfs = c.getInterfaces(); Class ancestor = c.getSuperclass();
Доступ к членам класса Package Class.getPackage() Contructor ctors [] Class.getConstructors() Field fields[] Class.getFields() Method methods Class.getMethods() Method methods2 Class.getDeclaredMethods()
Создание объекта class Cls { private Cls() {} } Class c = Class.forName("Cls"); c.newInstance(); Вывод: java.lang.IllegalAccessException: Class TestReflect can not access a member of class Cls with modifiers "private"
java.lang.reflect.Member Field –Получение типа, модификаторов, чтение и установка значения Method –Получение типа, модификаторов, вызов Constructor –Поиск по параметрам, получение модификаторов, создание экземпляра
Field try { Class c = Class.forName(ClassName); Field f = c.getField(FieldName); System.out.println(f.getType()); System.out.println(f.isSynthetic()); System.out.println(f.getModifiers()); //для разбора использовать java.lang.reflect.Modifier } catch (ClassNotFoundException x) { } catch (NoSuchFieldException x) {}
Установка значения Class c = book.getClass(); Field chap = c.getDeclaredField("chapters"); Field chars = c.getDeclaredField("characters"); chap.setAccessible(true); //при необходимости изменять final, private Field t = c.getDeclaredField(aField"); chap.setLong(book, 111); String[] newChars = { "Queen", "King" }; chars.set(book, newChars); t.set(book, new Object());
Method Class c = Class.forName(MyClass); Method[] allMethods= c.getDeclaredMethods(); Method m = allMethods[0]; System.out.println(m.getReturnType()); Class[]pType = m.getParameterTypes(); Class[]xType = m.getExceptionTypes(); System.out.println( m.getModifiers() );
Вызов метода(1/2) Class c = Class.forName(args[0]); Object obj = c.newInstance(); m.setAccessible(true); Object result = m.invoke(obj, Hello);
Вызов метода(2/2) Class c = Class.forName(TimeTable); Class[] argTypes = new Class[] { String[].class }; Method m = c.getDeclaredMethod("main", argTypes); String[] mArgs = new String[]{one, two}; main.invoke(null, (Object)mArgs);
Constructor Class c = Class.forName(name); Constructor[] allConstructors = c.getDeclaredConstructors(); for (Constructor ctor : allConstructors){ Class[] pType = ctor.getParameterTypes(); for (int i = 0; i < pType.length; i++) { //выбор конструктора по параметрам if (pType[i].equals(seekArgs)) {…} … }
Модификаторы конструктора Class c = Class.forName(name); Constructor[] allConstructors = c.getDeclaredConstructors(); Constructor ctor = allConstructors[0]; System.out.println(ctor.getModifiers());
Создание экземпляра Class.newInstance() –Разрешен вызов только конструктора без параметров –Пробрасывает исключение, если оно возникло во время создания –Требуется видимый конструктор Constructor.newInstance() –Может вызывать скрытые конструкторы
Использование экземпляра Constructor ctor = MyClass.class.getDeclaredConstructor(Ob ject.class); ctor.setAccessible(true); MyClass mc = (MyClass)ctor.newInstance(stub); mc.printField();
Необходимость RTTI
Пример иерархии классов import java.util.*; class Shape { void draw() { System.out.println(this + ".draw()"); } class Circle extends Shape { public String toString() { return "Circle"; } } class Square extends Shape { public String toString() { return "Square"; } } class Triangle extends Shape { public String toString() { return "Triangle"; } }
Использование иерархии public class Shapes { public static void main(String[] args) { ArrayList s = new ArrayList(); s.add(new Circle()); s.add(new Square()); s.add(new Triangle()); Iterator e = s.iterator(); while(e.hasNext()) ((Shape)e.next()).draw(); }
Механизм RTTI Приведение к базовому классу происходит при добавлении e.hasNext() возвращает значение java.lang.Object Попытка приведения к типу Shape приводит к проверке приведения на корректность. Это и есть RTTI –проверка на корректность по время исполнения Проявление полиморфизма: вызов соответствующего метода классов Square, Circle и Rectangle Пока программа заинтересована только в типе Shape
Если нужно знать точный тип во время выполнения информация о типе представляется с помощью специального типа объекта Class, который содержит информацию о классе Объект Class существует для каждого класса, который является частью программы Во время компиляции класса создается объект типа Class и хранится в виде одноименного файла с расширением.class Во время выполнения при попытке создания любого объекта JVM проверяет, загружен ли объект Class этого класса. Т.о. программа не загружается полностью перед запуском
Классы, демонстрирующие момент загрузки class Candy { static { System.out.println("Loading Candy");//выполнится при загрузке } class Gum { static { System.out.println("Loading Gum"); //выполнится при загрузке }
Время загрузки классов new Candy(); //класс загружен, т.к. создан объект этого класса System.out.println("After creating Candy"); try { Class.forName("Gum"); //Один из способов получить ссылку на объект } catch(ClassNotFoundException e) { e.printStackTrace(System.err); } System.out.println("After Class.forName(\"Gum\")");
Получение объекта типа Class С помощью литералов объекта: Gum.class Этот способ безопаснее, т.к. проверка происходит во время компиляции Литералы объектов Class работают с классами, интерфейсами, массивами и примитивными типами
Доступ через класс-оболочку boolean.class Boolean.TYPE char.class Character.TYPE byte.class Byte.TYPE short.class Short.TYPE int.class Integer.TYPE long.class Long.TYPE float.class Float.TYPE double.class Double.TYPE void.class Void.TYPE
Определение типа объекта Стандартное приведение выбрасывает исключение ClassCastException при попытке произвести неправильное приведение Объект Class может быть опрошен для получения информации Оператор instanceof
Использование instanceof проверка, является ли объект obj экземпляром класса Circle перед приведением объекта obj к типу Cirlce Рекомендуется использовать instanceof перед нисходящим приведением if (obj instanceof Circle){ //вызов специфичного для класса Circle метода radius = ((Circle)obj).getRadius(); }
Сравнение классов(1) //class Base {} //class Derived extends Base {} Base base = new Base(); base.getClass() instanceof Base) //true base.getClass()instanceof Derived) //false Base.class.isInstance(base) //true Derived.class.isInstance(base) //false base.getClass() == Base.class) //true base.getClass() == Derived.class) //false base.getClass().equals(Base.class) //true base.getClass().equals(Derived.class)) //false
Сравнение классов(2) Derived derived = new Derived(); derived.getClass() instanceof Base) //true derived.getClass()instanceof Derived) //true Base.class.isInstance(derived ) //true Derived.class.isInstance(base) //true derived.getClass() == Base.class) //false derived.getClass() == Derived.class) //true derived.getClass().equals(Base.class) //false derived.getClass().equals(Derived.class)) //true
Методы класса Class getInterfaces() – возвращает массив объектов типа Class, реализованных данным классом getSuperclass() – возвращает класс предка newInstance() – создание экземпляра класса getName() – имя класса isInterface() – является ли данный класс интерфейсом
Правда ли что… Следующее присваивание не скомпилируется: Class intClass = int.class; С помощью рефлексии для доступа ко всем методам и полям всех классов необходим экземпляр класса В структуре класса есть поля и методы, явно не определенные в программе