Java反射基础

Java作为一门动态的语言,有非常成熟的框架技术以供我们使用,而这一切都离不开Java反射技术的支持。通过Java反射,我们可以动态的创建和使用对象,使用灵活,没有反射机制,就无法实现框架技术。

Java程序在计算机的三个阶段

首先,需要了解一下Java程序在计算机的三个阶段,如图所示,图片来源——【零基础 快速学Java】韩顺平 零基础30天学会Java

[https://www.bilibili.com/video/BV1fh411y7R8?p=1]:

Java反射机制

  • 通过Java反射机制,程序在执行期间可以借助于 RefelectionAPI 取得任何类的内部信息,如成员变量、构造器、成员方法等,并能够操作对象的属性及方法。反射在设计模式和框架底层都会用到;
  • 加载完类之后,在堆中就产生了一个类的 Class 的对象,这个对象包含了类的完整结构信息,通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构。

比如:

Cat.java

public class Cat {
    public String name = "喵喵";
    public String age = "20";
    public Cat(String name){
        this.name = name;
    }
    public Cat(){}
    public void cry(){
        System.out.println(name + "喵喵叫...");
    }
}

Cat.class

public class Cat {
    public String name = "喵喵";
    public String age = "20";

    public Cat(String name) {
        this.name = name;
    }

    public Cat() {
    }

    public void cry() {
        System.out.println(this.name + "喵喵叫...");
    }
}

Java反射相关的类

java.lang.Class//代表一个类,Class对象表示某个类加载后在堆中的对象
java.lang.refelect.Method//代表类的方法,Methon对象表示某个类的方法
java.lang.refelect.Field//代表类的成员变量,Field对象表示某个类的成员变量
java.lang.refelect.Constructor//代表类的构造器,Construvtor表示构造器
    //getFiled不能得到私有的属性

反射调用优化

反射的优点和缺点

优点:反射可以动态的创建和使用对象,使用灵活,没有反射机制,就无法实现框架技术;

缺点:使用反射基本是解释执行,对执行速度有影响

反射调用优化 — — 关闭访问检查

  • Method和Field、Constructor对象都有setAcceseible()方法
  • setAcceseible作用是启动和禁用访问安全检查的开关
  • 参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查

示例代码:

package com.blue.reflection;

import com.blue.Cat;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
@SuppressWarnings({"all"})

public class Reflection2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        m1();
        m2();
        m3();
    }
    //传统方法来调用hi
    public static void m1(){
        Cat cat = new Cat();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 90000000; i++) {
            cat.hi();
        }
        long end = System.currentTimeMillis();
        System.out.println("m1= "+(end - start));
    }
    //反射机制调用方法hi
    public static void m2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class cls = Class.forName("com.blue.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");

        long start = System.currentTimeMillis();
        for (int i = 0; i < 90000000; i++) {
            hi.invoke(o);
        }
        long end = System.currentTimeMillis();
        System.out.println("m2= "+(end - start));
    }

    //反射调用优化
    public static void m3() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class cls = Class.forName("com.blue.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        //取消在反射调用方法时的访问检测
        hi.setAccessible(true);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 90000000; i++) {
            hi.invoke(o);
        }
        long end = System.currentTimeMillis();
        System.out.println("m3= "+(end - start));
    }
}

Java Class类分析

  • Class也是类,因此也继承Object类
  • Class类对象不是new出来的,而是系统创建的
  • 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
  • 每个类的实例都会记得自己是由哪个Class类所生成
  • 通过Class可以完整地得到一个类的完整结构,通过一系列的API
  • Class对象是存放在堆的
  • 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括 方法代码、变量名、方法名、访问权限等)

Java Class类的继承类与实现接口关系

Class常用方法

package com.blue.class_;

import com.blue.Car;

import java.lang.reflect.Field;

/**
 * User: Blueshadow
 * Date&Time: 2021/11/14 20:49
 * 演示Class类常用的方法
 */
public class class_ {
    public static void main(String[] args) throws Exception {
        String classAllPath = "com.blue.Car";
        //获取到Car类对应的Class对象
        //<?>表示不确定的Java类型
        Class<?> cls = Class.forName(classAllPath);
        //输出cls
        System.out.println(cls);//显示cls对象,是哪个类的Class对象(class com.blue.Car)
        System.out.println(cls.getClasses());//输出运行类型([Ljava.lang.Class;@4554617c)
        System.out.println(cls.getPackage());//得到包名(package com.blue)
        System.out.println(cls.getName());//得到类名(com.blue.Car)
        //通过cls创建对象实例
        Car car = (Car)cls.newInstance();
        System.out.println(car);//(Car{brand='宝马', price=500000, color='red'})
        //通过反射获取属性
        Field brand = cls.getField("brand");
        System.out.println(brand.get(car));//私有属性则会报错
        //通过反射给属性赋值
        brand.set(car,"奔驰");//重新赋值
        System.out.println(brand.get(car));
        //得到所有的属性
        Field[] fields = cls.getFields();
        for (Field f : fields){
            System.out.println(f.getName());//依次输出所有字段属性的名称
            //brand
            //price
            //color
        }
    }
}

获取Class类对象的六种方式

package com.blue.class_;

import com.blue.Car;

import java.util.Calendar;

/**
 * User: Blueshadow
 * Date&Time: 2021/11/15 16:18
 * 演示得到Class对象的各种方式
 */
public class GetClass_ {
    public static void main(String[] args) throws ClassNotFoundException {
        //前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出		    ClassNotFoundException。
		//应用场景:多用于配置文件,读取类全路径,加载类。
        String classAllPath = "com.blue.Car";
        Class<?> cls1 = Class.forName(classAllPath);
        System.out.println(cls1);
        
        //前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高。
	    //应用场景:多用于参数传递,比如通过反射得到对应构造器对象。
        Class cls2 = Car.class;
        System.out.println(cls2);
        
        //前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
        //应用场景:通过创建好的对象,获取Class对象
        Cat cat = new Cat();
        Class cls3 = cat.getClass();
        System.out.println(cls3);
        
        //前提:通过类加载器来获取到类的Class对象
        //有四种类加载器
        //先得到类加载器
        ClassLoader classLoader = cat.getClass().getClassLoader();
        //通过类加载器得到Class对象
        Class<?> cls4 = classLoader.loadClass(classAllPath);
        System.out.println(cls4);
        
        //基本数据类型
        Class<Integer> integerClass = int.class;
        System.out.println(integerClass);//int
        
        //基本数据类型对应的包装类,可以通过.TYPE 得到Class类对象
        Class<Integer> type = Integer.TYPE;
        System.out.println(type);//int
        
        //输出对象哈希值
        System.out.println(integerClass.hashCode());//1163157884
        System.out.println(type.hashCode());//1163157884
        //说明两者自动进行装箱和拆箱
    }
}

哪些类型有Class对象

  • 外部类、成员内部类、静态内部类、局部内部类、匿名内部类
  • interface接口
  • 数组
  • enum枚举
  • annotation注释
  • 基本数据类型
  • void
package com.blue.class_;

import java.io.Serializable;

/**
 * User: Blueshadow
 * Date&Time: 2021/11/15 17:06
 * 演示哪些类型有Class对象
 */
public class AllTypeClass_ {
    public static void main(String[] args) {
        Class<String> cls1 = String.class;//外部类
        Class<Serializable> cls2 = Serializable.class;//接口
        Class<Integer[]> cls3 = Integer[].class;//数组
        Class<float[][]> cls4 = float[][].class;//二维数组
        Class<Deprecated> cls5 = Deprecated.class;//注解
        Class<Thread.State> cls6 = Thread.State.class;//枚举//State是指线程状态
        Class<Void> cls7 = void.class;//void
        Class<Class> cls8 = Class.class;//Class类

        System.out.println(cls1);//class java.lang.String
        System.out.println(cls2);//interface java.io.Serializable
        System.out.println(cls3);//class [Ljava.lang.Integer;
        System.out.println(cls4);//class [[F
        System.out.println(cls5);//interface java.lang.Deprecated
        System.out.println(cls6);//class java.lang.Thread$State
        System.out.println(cls7);//void
        System.out.println(cls8);//class java.lang.Class
    }
}

类加载

基本说明

反射机制是Java实现动态语言的关键,也就是通过反射实现类动态加载。

  • 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强;
  • 动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性。

类加载时机

  • 当类 创建对象时(new)
  • 当子类被加载时
  • 调用类中的静态成员时
  • 通过反射
package com.blue.classload;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Scanner;

/**
 * User: Blueshadow
 * Date&Time: 2021/11/15 17:33
 */
public class ClassLoad_ {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入key:");
        String key = scanner.next();
        switch (key){
            case "1":
                Dog dog = new Dog();//静态加载
                dog.cry();
                break;
            case "2":
                Class<?> person = Class.forName("Person");//加载类
                Object o = person.newInstance();
                Method m = person.getMethod("hi");
                m.invoke(o);
                System.out.println("ok");
                break;
            default:
                System.out.println("do nothing..");
        }
    }
}

反射暴破创建实例

通过反射创建对象

  • 方式一:调用类中public修饰的无参构造器
  • 方式二:调用类中的指定构造器
  • Class类相关方法
    • newInstance:调用类中的无参构造器,获取对应类的对象
    • getConstructor(Class...clazz):根据参数列表,获取对应的public构造器对象
    • getDecalaredConstrictor(Class...clazz):根据参数列表,获取对应的所有构造器对象
  • Constructor类相关方法
    • setAccessible:暴破
    • newInstance(Object...onj):调用构造器
package com.blue;

import java.lang.reflect.Constructor;

/**
 * User: Blueshadow
 * Date&Time: 2021/11/15 21:41
 * 演示通过反射机制创建实例
 */
public class RefelectCreateInstance {
    public static void main(String[] args) throws Exception {
        //先获取到User类的Class对象
        Class<?> userClass = Class.forName("com.blue.User");

        //通过public的无参构造器创建实例
        Object o = userClass.newInstance();
        System.out.println(o);

        //通过public的有参构造器创建实例
        /**
         * constructor 就是
         * public User(String name){
         *         this.name = name;
         *     }
         */
        //先得到对应的构造器,再创建实例,传入形参
        Constructor<?> constructor = userClass.getConstructor(String.class);
        Object o1 = constructor.newInstance("王帅");
        System.out.println(o1);

        //通过非公有的有参构造器创建实例
        //先得到私有的构造器对象
        Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(int.class, String.class);
        declaredConstructor.setAccessible(true);//反射暴破,使用反射可以访问私有构造器
        Object o2 = declaredConstructor.newInstance(100, "王昭君");
        System.out.println(o2);
    }
}

class User{
    private int age = 18;
    private String name = "王帅";
    public User(){}
    public User(String name){
        this.name = name;
    }
    private User(int age, String name) {
        this.age = age;
        this.name = name;
    }

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

通过反射访问类中的属性

  • 根据属性名获取Field对象
  • 暴破
  • 访问
  • 如果是静态属性,则set和get中的参数 o 可以写成null
package com.blue;

import java.lang.reflect.Field;

/**
 * User: Blueshadow
 * Date&Time: 2021/11/16 19:21
 * 通过反射访问类中的成员
 */
public class RefelctAccessProperty {
    public static void main(String[] args) throws Exception {
        //得到Student类对应的Class对象
        Class<?> cls = Class.forName("com.blue.Student");
        //创建对象
        Object o = cls.newInstance();//o的运行类型就是Student类型
        System.out.println(o.getClass());
        Field age = cls.getField("age");
        //通过反射来操作属性
        age.set(o,88);//通过反射来操作属性
        System.out.println(o);
        System.out.println(age.get(o));//直接返回属性的值

        //使用反射操作静态、私有属性
        //得到Field对象
        Field name = cls.getDeclaredField("name");
        //对 private name 进行暴破
        name.setAccessible(true);
        name.set(o,"blue");
        System.out.println(o);

    }
}

通过反射访问类中的方法

  • 根据方法名和参数列表获取Method方法对象
  • 获取对象
  • 暴破
  • 访问
  • 注意:如果是静态方法,则invoke的参数o,可以写成null
package com.blue;

import java.lang.reflect.Method;

/**
 * User: Blueshadow
 * Date&Time: 2021/11/16 20:33
 * 演示通过反射调用方法
 */
public class RefelctAccessMethod {
    public static void main(String[] args) throws Exception {
        //得到Class对象
        Class<?> cls = Class.forName("com.blue.Boos");
        //创建一个对象
        Object o = cls.newInstance();
        //调用public的方法
//        Method hi = cls.getMethod("hi",String.class);
        //得到Method对象
        Method hi = cls.getDeclaredMethod("hi",String.class);
        //调用
        hi.invoke(o,"blue");


        //调用private方法
        //获取到方法对象
        Method say = cls.getDeclaredMethod("say", int.class, String.class, char.class);
        //因为say方法是私有的,所以需要暴破磁能使用
        say.setAccessible(true);
        System.out.println(say.invoke(o,18,"blue",'男'));

        //因为say方法是static,所以可以...可以给对象参数传值为空
        System.out.println(say.invoke(null,200,"李四",'女'));

        //返回值问题
        //在反射中,如果方法有返回值,统一返回Object对象
        Object returnValue = say.invoke(null, 300, "王五", '男');
        System.out.println(returnValue.getClass());//运行类型和方法中定义返回类型是一样的
    }
}

class Boos{
    public int age;
    private static String name;
    public Boos(){}
    private static String say(int n,String s,char c){
        return n + ""+s+""+c;
    }
    public void hi(String s){
        System.out.println("hi "+s);
    }
}
原文地址:https://www.cnblogs.com/nanfengashuai/p/15568860.html