Основы Java: глубокое понимание объектов Class и механизмов отражения

Java

Другие основные статьи по Java:
Базовое изучение Java (справочник)


Глубокое понимание объекта Class

Концепция объектов RRIT и Class

RRIT (идентификация типа во время выполнения) идентификация типа во время выполнения. Упомянутый в главе 14 книги Thinking in Java, его роль заключается в определении информации о типе и классе объекта во время выполнения. Есть два основных способа: один — «традиционный» RTTI, который предполагает, что мы уже знаем все типы во время компиляции; другой — механизм «рефлексии», который позволяет нам обнаруживать и использовать информацию о классах во время выполнения.

Класс является частью программы, и каждый класс имеет объект класса. Другими словами, всякий раз, когда создается и компилируется новый класс, создается объект класса (точнее, сохраняется в файле .class с тем же именем). Все классы динамически загружаются в JVM при первом использовании. Например, мы написали класс Test и сгенерировали Test.class после компиляции.В это время объект Class нашего класса Test сохраняется в файле класса. Когда мы создаем новый объект или обращаемся к статической переменной-члену, подсистема загрузчика классов в виртуальной машине Java (JVM) загрузит соответствующий объект класса в JVM, а затем JVM создаст нужный нам объект класса на основе этого введите информацию.Экземпляры объектов или укажите справочные значения для статических переменных. Важно отметить, что для каждого вручную написанного класса, независимо от того, сколько объектов экземпляра создается, в JVM существует только один объект Class, то есть каждый класс в памяти имеет один и только один соответствующий объект Class.

Test t1 = new Test();
Test t2 = new Test();
Test t3 = new Test();

Как показано выше, на самом деле в памяти JVM находится только один объект класса Test.

Сорт, класс Class также является реальным классом, существующим в пакете java.lang JDK. Экземпляр класса Class представляет класс (класс и перечисление) или интерфейс (интерфейс и аннотацию) среды выполнения приложения Java.(每个java类运行时都在JVM里表现为一个class对象,可通过类名.class、类型.getClass()、Class.forName("类名")等方法获取class对象). Массивы также отображаются как класс объектов класса, которые являются общими для всех массивов с одинаковым типом элемента и размерностью. Примитивные типы boolean, byte, char, short, int, long, float, double и ключевое слово void также ведут себя как объекты класса.

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    private static final int ANNOTATION= 0x00002000;
    private static final int ENUM      = 0x00004000;
    private static final int SYNTHETIC = 0x00001000;

    private static native void registerNatives();
    static {
        registerNatives();
    }

    /*
     * Private constructor. Only the Java Virtual Machine creates Class objects.   //私有构造器,只有JVM才能调用创建Class对象
     * This constructor is not used and prevents the default constructor being
     * generated.
     */
    private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }

Отсюда мы можем извлечь следующую информацию:

  • Класс также является типом класса, который отличается от ключевого слова class.

  • После того, как вручную написанный класс будет скомпилирован, будет сгенерирован объект класса, который представляет информацию о типе созданного класса, и объект класса будет сохранен в файле (файле байт-кода) с тем же именем.

  • Каждый класс, идентифицируемый ключевым словом class, имеет в памяти один и только один соответствующий объект класса для описания информации о его типе.Независимо от того, сколько объектов-экземпляров создается, он основан на объекте класса.

  • Класс Class имеет только частные конструкторы, поэтому соответствующий объект Class может быть создан и загружен только JVM.

  • Роль объекта класса Class заключается в предоставлении или получении информации о типе объекта во время выполнения, что очень важно для технологии отражения (позднее анализ отражения).

Загрузка и получение объектов Class

Загрузка объектов класса

Ранее мы упоминали, что объект Class загружается JVM, так когда же он загружается? На самом деле все классы динамически загружаются в JVM при первом использовании, когда программа создает первую статическую ссылку на класс, загружается используемый класс (на самом деле загружается файл байт-кода для этого класса), обратите внимание что использование оператора new для создания новых объектов-экземпляров класса также будет трактоваться как ссылки на статические члены класса (конструкторы также являются статическими методами класса), поэтому создается впечатление, что Java-программы в них загружены не полностью в память перед запуском, а различные его части загружаются по требованию, поэтому при использовании этого класса загрузчик класса сначала проверит, был ли загружен объект Class этого класса (экземплярный объект класса создается в соответствии с Класс Информация о типе в объекте завершена), если он не был загружен, загрузчик класса по умолчанию сначала будет искать файл .class в соответствии с именем класса (скомпилированный объект класса сохраняется в файле .class того же имя), в байтах этого класса. Когда файлы кода загружены, они должны быть проверены, чтобы убедиться, что они не повреждены и не содержат плохой код Java (это обнаружение механизма безопасности Java), и будут динамически загружаться в память после вообще нет проблем.Объект Class загружается в память (ведь файл байт-кода .class сохраняет объект Class), и его же можно использовать для создания всех экземплярных объектов этого класса.

Процесс загрузки класса:
1. Загрузить
На этапе загрузки виртуальная машина должна сделать 3 вещи:
(1) Получить поток двоичных байтов (файл .class), определяющий этот класс, через полное имя класса (org/fenixsoft/clazz/TestClass);
(2) преобразовать статическую структуру хранения, представленную этим потоком байтов, в структуру данных времени выполнения области метода;
(3) Создать объект java.lang.Class, представляющий этот класс в памяти в качестве записи доступа к различным данным этого класса в области методов;
2. Проверка
Этап проверки очень важен. Является ли этот этап строгим или нет, напрямую определяет, сможет ли виртуальная машина Java противостоять атакам вредоносного кода. С точки зрения производительности выполнения рабочая нагрузка этапа проверки занимает другую часть подсистемы загрузки классов виртуальная машина значительная часть. Этап проверки примерно завершает следующие четыре этапа действий по проверке:
(1) Проверка формата файла
Убедитесь, что поток байтов соответствует спецификации формата файла класса и может быть обработан текущей версией виртуальной машины;
Проверка на этом этапе основана на двоичном потоке байтов.Только после прохождения проверки на этом этапе поток байтов попадет в область метода памяти для хранения, поэтому следующие три этапа проверки все основаны на хранении структура области метода. , и больше не будет напрямую манипулировать потоком байтов.
(2) Проверка метаданных
Семантический анализ выполняется для информации, описанной байт-кодом, чтобы гарантировать, что описанная информация соответствует требованиям спецификации языка Java, и что нет информации метаданных, которая не соответствует спецификации языка Java;
(3) Проверка байт-кода
Путем анализа потока данных и потока управления определяется, что программа является семантически законной и логичной, гарантируя, что проверенный метод не вызовет событий, угрожающих безопасности виртуальной машины во время выполнения;
(4) Проверка символьной ссылки
Его можно рассматривать как совпадающую информацию, отличную от самого класса (различные ссылки на символы в пуле констант), чтобы гарантировать нормальное выполнение действия синтаксического анализа;
3. Подготовьте
Подготовительный этап — это этап формального выделения памяти для переменных класса и установки начального значения переменных класса, память, используемая этими переменными, будет выделена в области методов. Выделение памяти здесь предназначено только для переменных класса (переменных, измененных статическими), не включая переменные экземпляра, которые будут выделены в куче Java вместе с объектом при создании экземпляра объекта;
4. Анализ
Фаза разрешения — это процесс, посредством которого виртуальная машина заменяет символические ссылки в пуле констант прямыми ссылками. Действие синтаксического анализа в основном выполняется для 7 типов ссылок на символы, таких как класс или интерфейс, поле, метод класса, тип метода, дескриптор метода и квалификатор места вызова;
5. Инициализация
Фаза инициализации действительно начинает выполнять программный код Java (или байт-код), определенный в классе. Как запускается инициализация:
(1) При встрече с 4 инструкциями new, getstatic, putstatic или involestatic;
(2)使用 java.lang.reflect 包的方法对类进行反射调用的时候;
(3) При инициализации класса, если родительский класс не был инициализирован, сначала запускается инициализация родительского класса;
(4) При запуске виртуальной машины пользователю необходимо указать основной класс для выполнения (класс, содержащий метод main()), и виртуальная машина сначала инициализирует этот основной класс;
(5) Если окончательным результатом анализа экземпляра java.lang.invoke.MethodHandle является дескриптор метода REF_getStatic, REF_putStatic и REF_invokeStatic, если класс, соответствующий дескриптору, не был инициализирован, он будет инициализирован;

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

Получение объекта класса

Существует три основных способа получения объектов класса:

  • Получен методом экземпляра getClass()
  • Метод Class.forName для получения
  • Литеральная константа класса get

Получен методом экземпляра getClass()

    Test t1 = new Test();
    Class clazz=test.getClass();
    System.out.println("clazz:"+clazz.getName());

getClass() наследуется от класса Object верхнего уровня и возвращает ссылку на объект класса, представляющую фактический тип объекта.

Метод Class.forName для получения

    try{
      //通过Class.forName获取Test类的Class对象
      Class clazz=Class.forName("com.hiway.Test");
      System.out.println("clazz:"+clazz.getName());
    }catch (ClassNotFoundException e){
      e.printStackTrace();
    }

Метод forName является статическим методом-членом класса Class. Помните, что все объекты класса являются производными от этого класса класса, поэтому методы, определенные в классе класса, будут адаптироваться ко всем объектам класса. Здесь с помощью метода forName мы можем получить ссылку на объект Class, соответствующую классу Test. Обратите внимание, что при вызове метода forName вам необходимо поймать исключение с именем ClassNotFoundException, потому что метод forName не может определить, существует ли класс, соответствующий строке, переданной методом forName (независимо от того, существует ли файл .class), и может быть только выполняется во время работы программы.Проверьте, если он не существует, будет выброшено исключение ClassNotFoundException.

Использование метода forName вызовет инициализацию класса по сравнению с использованием литеральной константы класса для получения

Литеральная константа класса get

//字面常量的方式获取Class对象
Class clazz = Test.class;

Это не только проще, но и безопаснее, поскольку проверяется во время компиляции (и, следовательно, его не нужно помещать в блок try). Кроме того, он устраняет вызов метода forName(), что также делает его более эффективным. Интересно отметить, что при использовании ".class" для создания ссылки на объект класса, объект класса не инициализируется автоматически. Подготовка к использованию класса фактически состоит из трех шагов:

  1. Загрузка, которую выполняет загрузчик классов, ищет байт-коды (обычно в пути, указанном classpath, но это не обязательно) и создает объект класса из этих байт-кодов.
  2. Ссылка на сайт. Байт-код в классе будет проверен на этапе компоновки, место для хранения будет выделено для статических полей, а все ссылки на другие классы, созданные этим классом, будут разрешены при необходимости.
  3. инициализация. Если у класса есть суперкласс, он инициализируется, выполняются статические инициализаторы и блоки статических инициализаторов.
class Initable{
     static final int staticFinal = 47;
     static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
     static {
          System.out.ptintln("Initializing Initable");
     }
}

class Initable2 {
     static int staticNonFinal = 147;
     static {
          System.out.println("Initializing Initable2");
     }
}

class Initable3 {
     static int staticNonFinal = 74;
     static {
          System.out.println("Initializing Initable3");
     }
}

public class ClassInitialization {
     public static Random rand = new Random(47);
     public static void main(String[] args) throws Exception {
          Class initable = Initable.class;
          System.out.println("After creating Initable ref");
          System.out.println(Initable.staticFinal);
          System.out.println(Initable.staticFinal2);
          System.out.println(Initable2.staticNonFinal);
          Clas initable3 = Class.forName("Initable3");
          System.out.println("After creating Initable3 ref");
          System.out.println(Initable3.staticNonFinal);
     }
}

/* output
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable ref
74

Если статическое конечное значение编译器常量, как и Initable.staticFinal, то это значение можно прочитать без инициализации класса Initable. Однако просто сделать поле статическим и окончательным недостаточно для обеспечения такого поведения, например, доступ к Initable.staticFinal2 вызовет принудительную инициализацию класса, поскольку он не является константой времени компиляции.

Если статическое поле не является окончательным,Затем при доступе к нему всегда требуется, чтобы перед его чтением要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间), как видно из доступа к Initable2.staticNonFinal. Из результатов вывода видно, что получение объекта Class класса Initable с помощью метода получения буквальной константы не вызывает инициализацию класса Initable.Это также подтверждает предыдущий анализ.В то же время обнаружено, что инициализация не запускается при вызове переменной Initable.staticFinal, поскольку staticFinal является статической константой времени компиляции, а константа staticFinal класса Initable хранится в файле с именемNotInitializationВ константном пуле класса ссылка на константу staticFinal класса Initable в будущем фактически преобразуется в ссылку на константный пул класса NotInitialization.Следовательно, после периода компиляции ссылки на константы времени компиляции будут получены в пуле констант класса NotInitialization, что является важной причиной того, что ссылка на статические константы времени компиляции не запускает инициализацию класса Initable. сорт.
Но после вызова переменной Initable.staticFinal2 срабатывает инициализация класса Initable,Обратите внимание, что хотя staticFinal2 изменяется с помощью static и final, его значение не может быть определено во время компиляции, поэтому staticFinal2 не является константой времени компиляции.Чтобы использовать эту переменную, сначала необходимо инициализировать класс Initable. И классы Initable2, и Initable3 являются статическими переменными-членами, которые не являются константами времени компиляции, и ссылки вызывают инициализацию. Что касается метода forName для получения объекта Class, то он обязательно вызовет инициализацию, которая была проанализирована ранее.

Эквивалентность instanceof и класса

Что касается ключевого слова instanceof, оно возвращает значение логического типа, которое предназначено для того, чтобы сообщить нам, является ли объект экземпляром определенного типа. Как показано ниже, используйте instanceof, чтобы определить, является ли obj экземпляром объекта типа Animal перед принудительным преобразованием.Если он возвращает true, выполните преобразование типа, что может избежать создания исключения преобразования типа (ClassCastException).

public void cast2(Object obj){
    if(obj instanceof Animal){
          Animal animal= (Animal) obj;
      }
}

Метод isInstance является методом Native в классе Class, и он также используется для определения типа объекта.Давайте рассмотрим простой пример:

public void cast2(Object obj){
        //instanceof关键字
        if(obj instanceof Animal){
            Animal animal= (Animal) obj;
        }

        //isInstance方法
        if(Animal.class.isInstance(obj)){
            Animal animal= (Animal) obj;
        }
  }

На самом деле методы instanceOf и isInstance дают один и тот же результат.

class A {}

class B extends A {}

public class C {
  static void test(Object x) {
    print("Testing x of type " + x.getClass());
    print("x instanceof A " + (x instanceof A));
    print("x instanceof B "+ (x instanceof B));
    print("A.isInstance(x) "+ A.class.isInstance(x));
    print("B.isInstance(x) " +
      B.class.isInstance(x));
    print("x.getClass() == A.class " +
      (x.getClass() == A.class));
    print("x.getClass() == B.class " +
      (x.getClass() == B.class));
    print("x.getClass().equals(A.class)) "+
      (x.getClass().equals(A.class)));
    print("x.getClass().equals(B.class)) " +
      (x.getClass().equals(B.class)));
  }
  public static void main(String[] args) {
    test(new A());
    test(new B());
  } 
}

/* output
Testing x of type class com.zejian.A
x instanceof A true
x instanceof B false //父类不一定是子类的某个类型
A.isInstance(x) true
B.isInstance(x) false
x.getClass() == A.class true
x.getClass() == B.class false
x.getClass().equals(A.class)) true
x.getClass().equals(B.class)) false
---------------------------------------------
Testing x of type class com.zejian.B
x instanceof A true
x instanceof B true
A.isInstance(x) true
B.isInstance(x) true
x.getClass() == A.class false
x.getClass() == B.class true
x.getClass().equals(A.class)) false
x.getClass().equals(B.class)) true

отражение

Механизм отражения заключается в том, что в рабочем состоянии для любого класса можно знать все свойства и методы класса, для любого объекта можно вызывать любые его методы и свойства, информацию, полученную динамически, и динамически вызывающий объект. Функция метода называется механизмом отражения языка java. Технология отражения всегда была ярким моментом в Java, которая также является основой большинства современных фреймворков (таких как Spring/Mybatis и т. д.). В Java класс Class вместе с библиотекой классов java.lang.reflect полностью поддерживает технологию отражения. В пакете отражения обычно используемые классы в основном включают класс Constructor, который представляет метод построения класса, представленного объектом класса, который можно использовать для динамического создания объектов во время выполнения Поле представляет переменные-члены класса, представленного объект класса.Значения атрибутов переменных-членов (включая частные) могут быть динамически изменены во время выполнения, а метод представляет собой метод-член класса, представленный объектом класса, с помощью которого метод объекта (включая частный) может вызываться динамически. Далее будут описаны эти важные классы. Объясните отдельно.

Класс конструктора и его использование

Класс Constructor существует в пакете отражения (java.lang.reflect) и отражает метод построения класса, представленного объектом Class. Объект Constructor получается через методы класса Class.Основные методы класса Class, относящиеся к конструктору, следующие:

возвращаемое значение метода имя метода Описание метода
static Class<?> forName(String className) Возвращает объект класса, связанный с классом или интерфейсом с заданным строковым именем.
Constructor getConstructor(Class<?>... parameterTypes) Возвращает объект конструктора указанного типа параметра с открытым доступом
Constructor<?>[] getConstructors() Возвращает массив объектов Constructor для всех конструкторов с открытым доступом.
Constructor getDeclaredConstructor(Class<?>... parameterTypes) Возвращает указанный тип параметра, все объявленные (включая приватные) объекты конструктора
Constructor<?>[] getDeclaredConstructor() Возвращает все объявленные (включая частные) объекты конструктора
T newInstance() Вызывает конструктор без аргументов для создания нового экземпляра класса, представленного этим объектом Class.

Давайте рассмотрим простой пример, чтобы понять использование объектов Constructor:

public class ConstructionTest implements Serializable {
    public static void main(String[] args) throws Exception {

        Class<?> clazz = null;

        //获取Class对象的引用
        clazz = Class.forName("com.example.javabase.User");

        //第一种方法,实例化默认构造方法,User必须无参构造函数,否则将抛异常
        User user = (User) clazz.newInstance();
        user.setAge(20);
        user.setName("Jack");
        System.out.println(user);

        System.out.println("--------------------------------------------");

        //获取带String参数的public构造函数
        Constructor cs1 =clazz.getConstructor(String.class);
        //创建User
        User user1= (User) cs1.newInstance("hiway");
        user1.setAge(22);
        System.out.println("user1:"+user1.toString());

        System.out.println("--------------------------------------------");

        //取得指定带int和String参数构造函数,该方法是私有构造private
        Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class);
        //由于是private必须设置可访问
        cs2.setAccessible(true);
        //创建user对象
        User user2= (User) cs2.newInstance(25,"hiway2");
        System.out.println("user2:"+user2.toString());

        System.out.println("--------------------------------------------");

        //获取所有构造包含private
        Constructor<?> cons[] = clazz.getDeclaredConstructors();
        // 查看每个构造方法需要的参数
        for (int i = 0; i < cons.length; i++) {
            //获取构造函数参数类型
            Class<?> clazzs[] = cons[i].getParameterTypes();
            System.out.println("构造函数["+i+"]:"+cons[i].toString() );
            System.out.print("参数类型["+i+"]:(");
            for (int j = 0; j < clazzs.length; j++) {
                if (j == clazzs.length - 1)
                    System.out.print(clazzs[j].getName());
                else
                    System.out.print(clazzs[j].getName() + ",");
            }
            System.out.println(")");
        }
    }
}


class User {
    private int age;
    private String name;
    public User() {
        super();
    }
    public User(String name) {
        super();
        this.name = name;
    }

    /**
     * 私有构造
     * @param age
     * @param name
     */
    private User(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

/* output 
User{age=20, name='Jack'}
--------------------------------------------
user1:User{age=22, name='hiway'}
--------------------------------------------
user2:User{age=25, name='hiway2'}
--------------------------------------------
构造函数[0]:private com.example.javabase.User(int,java.lang.String)
参数类型[0]:(int,java.lang.String)
构造函数[1]:public com.example.javabase.User(java.lang.String)
参数类型[1]:(java.lang.String)
构造函数[2]:public com.example.javabase.User()
参数类型[2]:()

Ниже приведены некоторые распространенные методы самого класса Constructor (только часть, другие API можно проверить).

возвращаемое значение метода имя метода Описание метода
Class getDeclaringClass() Возвращает объект Class, представляющий класс, объявляющий конструктор, представленный этим объектом Constructor, который фактически возвращает реальный тип (без параметров).
Type[] getGenericParameterTypes() Возвращает набор объектов Type в порядке объявления, и возвращается тип параметра конструктора объекта Constructor.
String getName() Возвращает имя этого конструктора в виде строки.
Class<?>[] getParameterTypes() Возвращает набор объектов Class в порядке объявления, то есть возвращает типы формальных параметров конструктора, представленного объектом Constructor.
T newInstance(Object... initargs) используйте конструктор, представленный этим объектом Constructor, для создания нового экземпляра
String toGenericString() Возвращает строку, описывающую этот Конструктор, включая параметры типа.

Демонстрация кода выглядит следующим образом:

        Constructor cs3 = clazz.getDeclaredConstructor(int.class,String.class);
        System.out.println("-----getDeclaringClass-----");
        Class uclazz=cs3.getDeclaringClass();
//Constructor对象表示的构造方法的类
        System.out.println("构造方法的类:"+uclazz.getName());

        System.out.println("-----getGenericParameterTypes-----");
//对象表示此 Constructor 对象所表示的方法的形参类型
        Type[] tps=cs3.getGenericParameterTypes();
        for (Type tp:tps) {
            System.out.println("参数名称tp:"+tp);
        }
        System.out.println("-----getParameterTypes-----");
//获取构造函数参数类型
        Class<?> clazzs[] = cs3.getParameterTypes();
        for (Class claz:clazzs) {
            System.out.println("参数名称:"+claz.getName());
        }
        System.out.println("-----getName-----");
//以字符串形式返回此构造方法的名称
        System.out.println("getName:"+cs3.getName());

        System.out.println("-----getoGenericString-----");
//返回描述此 Constructor 的字符串,其中包括类型参数。
        System.out.println("getoGenericString():"+cs3.toGenericString());

/* output 
-----getDeclaringClass-----
构造方法的类:com.example.javabase.User
-----getGenericParameterTypes-----
参数名称tp:int
参数名称tp:class java.lang.String
-----getParameterTypes-----
参数名称:int
参数名称:java.lang.String
-----getName-----
getName:com.example.javabase.User
-----getoGenericString-----
getoGenericString():private com.example.javabase.User(int,java.lang.String)

Класс поля и его использование

Поле предоставляет информацию об одном поле класса или интерфейса и динамическом доступе к нему. Отраженное поле может быть полем класса (статическим) или полем экземпляра. Таким же образом мы можем получить объект Field, представляющий информацию о поле, с помощью методов, предоставляемых классом Class Методы, связанные с классом Class и объектом Field, следующие:

возвращаемое значение метода имя метода Описание метода
Field getDeclaredField(String name) Получить поле с указанным именем (включая приватную модификацию), исключая унаследованные поля
Field[] getDeclaredField() Получить все (включая измененные в частном порядке) поля класса или интерфейса, представленного объектом класса, за исключением унаследованных полей.
Field getField(String name) Получить поля с указанным именем и общедоступной модификацией, включая унаследованные поля
Field[] getField() Получить поля с модификатором public, включая унаследованные поля

Следующий код демонстрирует использование вышеуказанного метода

public class ReflectField {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class<?> clazz = Class.forName("reflect.Student");
        //获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段,
        // 否则抛NoSuchFieldException
        Field field = clazz.getField("age");
        System.out.println("field:"+field);

        //获取所有修饰符为public的字段,包含父类字段,注意修饰符为public才会获取
        Field fields[] = clazz.getFields();
        for (Field f:fields) {
            System.out.println("f:"+f.getDeclaringClass());
        }

        System.out.println("================getDeclaredFields====================");
        //获取当前类所字段(包含private字段),注意不包含父类的字段
        Field fields2[] = clazz.getDeclaredFields();
        for (Field f:fields2) {
            System.out.println("f2:"+f.getDeclaringClass());
        }
        //获取指定字段名称的Field类,可以是任意修饰符的自动,注意不包含父类的字段
        Field field2 = clazz.getDeclaredField("desc");
        System.out.println("field2:"+field2);
    }
    /**
      输出结果: 
     field:public int reflect.Person.age
     f:public java.lang.String reflect.Student.desc
     f:public int reflect.Person.age
     f:public java.lang.String reflect.Person.name

     ================getDeclaredFields====================
     f2:public java.lang.String reflect.Student.desc
     f2:private int reflect.Student.score
     field2:public java.lang.String reflect.Student.desc
     */
}

class Person{
    public int age;
    public String name;
    //省略set和get方法
}

class Student extends Person{
    public String desc;
    private int score;
    //省略set和get方法
}

Следует отметить в приведенном выше методе, что если мы не ожидаем получения полей его родительского класса, нам нужно использовать методы getDeclaredField/getDeclaredFields класса Class для получения полей getField/getFields, но только общедоступные модифицированные поля можно получить, а приватные поля родительского класса получить нельзя. Далее будет присвоено значение указанному атрибуту класса через метод самого класса Field Код демонстрируется следующим образом:

//获取Class对象引用
Class<?> clazz = Class.forName("reflect.Student");

Student st= (Student) clazz.newInstance();
//获取父类public字段并赋值
Field ageField = clazz.getField("age");
ageField.set(st,18);
Field nameField = clazz.getField("name");
nameField.set(st,"Lily");

//只获取当前类的字段,不获取父类的字段
Field descField = clazz.getDeclaredField("desc");
descField.set(st,"I am student");
Field scoreField = clazz.getDeclaredField("score");
//设置可访问,score是private的
scoreField.setAccessible(true);
scoreField.set(st,88);
System.out.println(st.toString());

//输出结果:Student{age=18, name='Lily ,desc='I am student', score=88} 

//获取字段值
System.out.println(scoreField.get(st));
// 88

Метод set(Object obj, Object value) — это метод самого класса Field, который используется для установки значения поля, а метод get(Object obj) — для получения значения поля. существуют и другие часто используемые методы для класса Field:

возвращаемое значение метода имя метода Описание метода
void set(Object obj, Object value) Устанавливает поле, представленное этим объектом Field в указанной объектной переменной, на указанное новое значение.
Object get(Object obj) Возвращает значение поля, представленного этим полем в указанном объекте.
Class<?> getType() Возвращает объект Class, который идентифицирует объявленный тип поля, представленного этим объектом Field.
boolean isEnumConstant() Значение true, если это поле представляет элемент типа перечисления; в противном случае — значение false.
String toGenericString() Возвращает строку, описывающую это Поле (включая его общий тип)
String getName() возвращает имя поля, представленного этим объектом Field
Class<?> getDeclaringClass() Возвращает объект Class, представляющий класс или интерфейс, который объявляет поля, представленные этим объектом Field.
void setAccessible(boolean flag) Установите флаг доступности этого объекта в указанное логическое значение, т.е. установите его доступность

Вышеупомянутые методы могут использоваться чаще, ведь в методе установки значений класс Field также предоставляет методы специально для базовых типов данных, таких как setInt()/getInt(), setBoolean()/getBoolean, setChar() /getChar() и другие методы, не все из них перечислены здесь, при необходимости вы можете проверить документацию по API. Следует отметить, что поле Поле, модифицированное ключевым словом final, является безопасным и может принимать любые изменения во время выполнения, но его фактическое значение не изменится в конце.

Класс метода и его использование

Метод предоставляет информацию об одном методе класса или интерфейса (и о том, как получить доступ к этому методу), а отраженный метод может быть методом класса или методом экземпляра (включая абстрактные методы). Ниже приведены методы, относящиеся к классу Class для получения объекта Method:

возвращаемое значение метода имя метода Описание метода
Method getDeclaredMethod(String name, Class<?>... parameterTypes) Возвращает объект Method с указанными параметрами, который отражает указанный объявленный метод класса или интерфейса, представленного этим объектом Class.
Method[] getDeclaredMethod() Возвращает массив объектов Method, которые отражают все методы, объявленные классом или интерфейсом, представленным этим объектом Class, включая общедоступные, защищенные, доступ по умолчанию (пакет) и частные методы, но не унаследованные методы.
Method getMethod(String name, Class<?>... parameterTypes) Возвращает объект Method, отражающий указанный открытый метод-член класса или интерфейса, представленного этим объектом класса.
Method[] getMethods() Возвращает массив, содержащий объекты Method, которые отражают общедоступные члены класса или интерфейса, представленного этим объектом класса (включая объявленные этим классом или интерфейсом и унаследованные от суперклассов и суперинтерфейсов).

Вышеупомянутый метод также продемонстрирован на примере:

import java.lang.reflect.Method;

public class ReflectMethod  {


    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {

        Class clazz = Class.forName("reflect.Circle");

        //根据参数获取public的Method,包含继承自父类的方法
        Method method = clazz.getMethod("draw",int.class,String.class);

        System.out.println("method:"+method);

        //获取所有public的方法:
        Method[] methods =clazz.getMethods();
        for (Method m:methods){
            System.out.println("m::"+m);
        }

        System.out.println("=========================================");

        //获取当前类的方法包含private,该方法无法获取继承自父类的method
        Method method1 = clazz.getDeclaredMethod("drawCircle");
        System.out.println("method1::"+method1);
        //获取当前类的所有方法包含private,该方法无法获取继承自父类的method
        Method[] methods1=clazz.getDeclaredMethods();
        for (Method m:methods1){
            System.out.println("m1::"+m);
        }
    }

/**
     输出结果:
     method:public void reflect.Shape.draw(int,java.lang.String)

     m::public int reflect.Circle.getAllCount()
     m::public void reflect.Shape.draw()
     m::public void reflect.Shape.draw(int,java.lang.String)
     m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
     m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
     m::public final void java.lang.Object.wait() throws java.lang.InterruptedException
     m::public boolean java.lang.Object.equals(java.lang.Object)
     m::public java.lang.String java.lang.Object.toString()
     m::public native int java.lang.Object.hashCode()
     m::public final native java.lang.Class java.lang.Object.getClass()
     m::public final native void java.lang.Object.notify()
     m::public final native void java.lang.Object.notifyAll()

     =========================================
     method1::private void reflect.Circle.drawCircle()

     m1::public int reflect.Circle.getAllCount()
     m1::private void reflect.Circle.drawCircle()
     */
}

class Shape {
    public void draw(){
        System.out.println("draw");
    }

    public void draw(int count , String name){
        System.out.println("draw "+ name +",count="+count);
    }

}
class Circle extends Shape{

    private void drawCircle(){
        System.out.println("drawCircle");
    }
    public int getAllCount(){
        return 100;
    }
}

Когда объект Method получен с помощью метода getMethods, также получаются методы родительского класса, и методы класса Object распечатываются в приведенном выше результате вывода. Методы getDeclaredMethod/getDeclaredMethods могут получать только методы текущего класса. Мы можем выбрать в зависимости от ситуации, когда мы его используем. Далее будет продемонстрирован вызов метода указанного класса через объект Method:

Class clazz = Class.forName("reflect.Circle");
//创建对象
Circle circle = (Circle) clazz.newInstance();

//获取指定参数的方法对象Method
Method method = clazz.getMethod("draw",int.class,String.class);

//通过Method对象的invoke(Object obj,Object... args)方法调用
method.invoke(circle,15,"圈圈");

//对私有无参方法的操作
Method method1 = clazz.getDeclaredMethod("drawCircle");
//修改私有方法的访问标识
method1.setAccessible(true);
method1.invoke(circle);

//对有返回值得方法操作
Method method2 =clazz.getDeclaredMethod("getAllCount");
Integer count = (Integer) method2.invoke(circle);
System.out.println("count:"+count);

/**
    输出结果:
    draw 圈圈,count=15
    drawCircle
    count:100
*/

Для вызова метода в приведенном выше коде используется invoke(Object obj, Object... args) класса Method.Первый параметр представляет вызываемый объект, а второй параметр передает параметры вызывающего метода. На этом динамический вызов метода класса завершен.

возвращаемое значение метода имя метода Описание метода
Object invoke(Object obj, Object... args) Вызывает базовый метод, представленный этим объектом Method, для указанного объекта с указанными параметрами.
Class<?> getReturnType() Возвращает объект Class, описывающий формальный возвращаемый тип метода, представленного этим объектом Method, то есть возвращаемый тип метода.
Type getGenericReturnType() Возвращает объект Type, представляющий формальный возвращаемый тип метода, представленного этим объектом Method, который также является возвращаемым типом метода.
Class<?>[] getParameterTypes() Возвращает в порядке объявления массив объектов Class, описывающих типы параметров метода, представленного этим объектом Method. То есть массив типов параметров возвращаемого метода
Type[] getGenericParameterTypes() Возвращает массив объектов Type в порядке объявления, которые описывают типы формальных параметров метода, представленного этим объектом Method, а также являются типами параметров возвращаемого метода.
String getName() Возвращает в виде строки имя метода, представленного этим объектом Method, то есть имя возвращаемого метода.
boolean isVarArgs() Определяет, имеет ли метод переменные параметры, и возвращает true, если метод объявлен с переменным числом параметров; в противном случае возвращает false.
String toGenericString() Возвращает строку, описывающую этот метод, включая параметры типа.

И метод getReturnType, и метод getGenericReturnType получают возвращаемый тип метода, представленного объектом Method, но первый возвращает тип Class, а второй возвращает Type (который был проанализирован ранее). по умолчанию добавлен в Java 8. Метод реализован, и возвращаемая информация о типе параметра

public interface Type {
    //1.8新增
    default String getTypeName() {
        return toString();
    }
}

То же самое верно для getParameterTypes/getGenericParameterTypes, оба из которых предназначены для получения типа параметра метода, представленного объектом Method.Другие методы аналогичны предыдущим Field и Constructor.