java程序员从笨鸟到菜鸟之(四十五)反射初涉

1  什么是java语言反射机制?

       反射机制是在运行状态中,对于任意一个,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类的信息以及动态调用对象的方法的功能称为java语言的反射机制。是动态语言的关键,反射机制允许程序在运行期间借助于Reflect API取得类的内部信息,并能直接操作任意对象的内部属性和方法

       反射就是通过获取到该类的字节码文件对象---->即:Class类对象,通过Class类对象获取该类里面的一些属性(成员变量)、构造方法、成员方法等----如何获取等

class文件简介及加载:点击打开链接

Java类的加载过程:点击打开链接点击打开链接点击打开链接点击打开链接点击打开链接

说明:两种理解方式均可

反射的是如何理解的?(可能不太准确)

理解:要结合正常创建对象的过程→(1)引入需要的类的全路径→(2)new实例化→(3)取得实例化对象→(4)类的结构(调用)

反射方式:(1)先实例化对象→(2)getClass()方法→(3)得到完整的"包类"名称→(4)类的结构

2   Java反射机制的功能

(1)运行期间判断一个对象所属的类别;是Student类的对象还是Teacher类的对象

(2)运行期间构造任意类的对象;不管构造方法是否私有化

(3)在运行期间判断任意一个类所具有的成员变量和方法

(4)在运行期间调用任意一个对象的成员变量和方法

(5)生成动态代理

通俗的理解:通过反射,任意类都是完全暴露的,可以获取类的完整结构

编写的某个类→javac.exe 编译→.class文件→java.exe加载(JVM的类加载器)到内存中→此时.class文件就是一个运行时的类,存方在缓存区→这个运行类本身就是一个Class的实例,每个Class类的实例对应着一个运行时的类。每个运行时的类只加载一次!

3  面试题:如何获取类的字节码文件对象(Class类的实例)?并且有几种方式呢?

1)Object类中的getClass()方法

说明:通过运行时类的对象获取,调用其getClass()返回其运行时的类(打印可以看出)

2)数据类型的class属性  举例:String.class、Student.class;调用运行时类本身的.class属性

3)Class类中的特有方法:forName(String className)---(重点,数据库加载驱动:Drivers)---静态方法获得类名对应的Class对象

说明:开发中常使用的方式,因为第三种里面的参数是一个字符串;一个Class对象表示特定类的对象(属性)

注意:第三种方式的是类的全路径名称的字符串形式;第一种还需要创建对象,第二种可能导包导错(相同的类处于不同的包中),如果没有此类,编译时就报错

实例1 

package demo;

public class Demo1 {

	public static void main(String[] args) throws ClassNotFoundException {
		Person person=new Person();//补充person与clazz等无法进行比较(不在一个层面上)
		Class<Person> person1=Person.class;//得到Person.class字节码文件对象
		Class person2=person.getClass();//获取的是类的字节码文件对象
		Class<?> person3 = Class.forName("demo.Person");//加载并且获得Person.class字节码文件对象
		//如何方式3找不到类的全限定名:出现java.lang.ClassNotFoundException异常
	}
}

说明1:出现黄色警告线原因---Class类实际是一个泛型类,大多数情况可以忽略类型参数,而使用原始的Class类;

说明2:Person类的字节码文件在内存中只有一份,所以三种方式通过"=="判断都是true;

注意:一个Class对象实际是一个类型,而此类型未必是一个类;int不是类,但int.class是一个Class类型的对象

回顾:在反射之前,在启动程序时,包含main方法的类首先被加载,它会加载所有需要的类,被加载的类又会继续加载它需要的类,以此类推;对于大型应用程序来说,类的层层加载会消耗很多时间,用户不耐烦。

反射应用:给用户一个启动速度比较快的幻觉---首先保证包含main方法的类没有显示地引用其它的类,在显示一个启动画面,然后通过调用Class.forName手工地加载其它的类

通过三种方式可以获取运行时Class类的实例,那么有了Class类的实例以后,我们可以干啥呢?

(1)*创建对应的运行时类的对象

(2)获取对应运行时类的完整结构(构造器,属性、方法、内部类、包命、注解、异常等),但无法获取代码块

(3)*调用运行时类的指定结构(构造器、属性、方法)

(4)反射的应用:动态代理

Class类

在程序运行期间(前提),Java运行时系统始终为所有的对象维护着一个被称为运行时的标识类型,这个信息(标识类型的信息)跟踪着每个对象所属的类;虚拟机利用运行时类型信息选择相应的方法执行。

理解:虚拟机为每个类型管理一个Class对象;注意:不是每个对象,通过"=="运算符的比较也可以看出。

那么如何访问这些信息?通过专门的java类访问这些信息,保存这些信息的类被称为Class。

提前说明:JVM将类中的所有信息都进行封装,以相应类的形式进行封装

常用获取构造器的方法

(1)getName---返回类的全限定类名----补充的

(2)public Constructor<?>[] getConstructors()---返回的是所有公共构造方法所在数组

(3)public Constructor<?>[] getDeclaredConstructors():获取的是当前字节码文件对象中所有的(静态的,私有的,受保护的)构造方法数组

(4)public Constructor<T> getConstructor(Class<?>... parameterTypes):它反映此 Class 对象所表示的类的某个指定公共构造方法

参数说明:参数为对应构造方法的数据类型的字节码文件对象,形如String.class或int.class;可变参数类型

(5)public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):表示获取类或者接口中的指定的(任意一个)构造方法

实例2

package demo;

import java.lang.reflect.Constructor;

public class Demo2 {

	public static void main(String[] args) throws  Exception{
		//方式1---public Constructor<?>[] getConstructors(),返回的是一个构造方法所在数组
		//加"s"获取所有的公共的(public修饰的)构造方法
		Constructor<?>[] constructors = person.getConstructors();
		//遍历构造方法
		for(Constructor con:constructors){
			System.out.println(con);
		}
		System.out.println("---------------------");
		//方式2
		Constructor<?>[] dc = person.getDeclaredConstructors();
		for(Constructor con:dc){
			System.out.println(con);
		}
		System.out.println("----------------------");
		//方式3---获取某一个公共的构造方法
		Constructor<?> con = person.getConstructor(String.class,String.class);
		System.out.println(con);
		//方式4---获取某个构造方法,我们就获取个私有的方法
		Constructor<?> dcr = person.getDeclaredConstructor(String.class,int.class);
		System.out.println(dcr);
		//了解一下打印的内容:public demo.Person(java.lang.String,int)
	}
}

动态的创建一个类的实例----不经过编译时期的检查(翻墙)

(6)public 类名 newInstance():

说明:可以直接通过Class的newInstance()的方法,特点:对无参数默认构造器初始化并创建对象,必须强转类型

除了Class的此方法,还有没有其它的途径创建对象?当然有

Constructor类

理解上:其实类似于将int类型封装成Integer类那样,将构造方法封装成Constructor类,即将类中的每个成分都封装成对象

(1)public T newInstance(Object... initargs)

说明:传递的是实际参数(可变参数),该方法等效于创建一个类的对象(动态)

实例3 创建对象动态和静态(公共的构造方法)

package demo;

import java.lang.reflect.Constructor;

public class Demo3 {

	public static void main(String[] args) throws Exception {
		//(1)静态
		Person person = new Person();
		//System.out.println(person);
		//(2)动态
		Person person2 = Person.class.newInstance();
		System.out.println(person==person2);
		//(3)动态
		Class<?> person3 = Class.forName("demo.Person");
		Constructor<?> dc = person3.getDeclaredConstructor();//向上转型--?的位置
		Object obj = dc.newInstance();//最常用的,返回值也最特殊,注意使用时最好强转
		//(4)动态
		Class<? extends Person> class1 = person.getClass();
		Constructor<? extends Person> dc1 = class1.getDeclaredConstructor();//无参--向下转型--?的位置
		Person person4 = dc1.newInstance();
		
	}
}

注意:(2)和(3)以及(4)的newInstance()方法的返回值

那么问题来了,私有的构造方法能否创建对象?

(2)public void setAccessible(boolean flag):在访问的时候取消java语言访问检查(强制性)---常常是私有的构造方法;常常"flag=true" ----在给构造创建实例对象之前就应该取消检查

实例4

package demo;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Demo4 {

	public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		//动态的创建类对象,并初始化(私有的和公共的)
		//1:私有的方式
		String str="demo.Person";
		Class<?> c = Class.forName(str);
		Constructor<?> dc = c.getDeclaredConstructor(String.class,int.class);
		dc.setAccessible(true);//私有的构造方法不能直接访问
		Object obj = dc.newInstance("小王",27);
		System.out.println(obj);//调用Person类的toString()方法
		//2:公共的方式
		Constructor<?> con = c.getConstructor(String.class,String.class);
		Object ne = con.newInstance("小王","0012");
		System.out.println(ne);
	}
}

说明:注意构造方法的两个用途--创建对象和成员变量初始化

Class类中的关于变量的方法

通过反射获取成员变量并使用:成员变量--->Field

(1)获取所有的公共的成员变量public Field[] getFields()

说明:所有的公共的可访问的字段,返回的是Field对象数组

(2)public Field[] getDeclaredFields()
说明:获取当前字节码文件对象中所有的(公共的或者私有等)的字段

(3)public Field getField(String name)

说明:获取公共的指定的字段,参数为当前成员变量名称的字符串形式

(4)public Field getDeclaredField(String name)

说明:获取类或接口中已经声明的指定的字段(一般私有的)

Field类中的方法

注意1:如何修改obj实例对象的成员变量?即将指定对象变量上此 Field对象表示的字段设置为指定的新值---修改成员变量?

(1)public void set(Object obj, Object value)

说明:给obj实例对象里面的成员变量设置一个实际参数;obj为动态的对象,forName等其他形式创建的字节码文件对象

思考:为什么修改成员变量,除了要修改值,还得要指定对象?

原因:可能内存中有好多通过反射创建的对象,你是针对具体的对象中的变量进行修改的,JVM不可能那么智能

注意2:如果想对私有成员变量设置值,必须在设置值之前取消Java语言的访问检查:Field类的方法---setAccessible(true);

注意3:输出每个对象不是地址的原因:多态

实例5

package demo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

/**
 * 通过反射获取成员变量并使用
 * 成员变量--->Field
 * 
 * @author Orange
 * @version 1.8
 */
public class Demo5 {

	public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		//(1)创建Class对象
		Class<?> person = Class.forName("demo.Person");
		//(2)获取所有的公共的成员变量public Field[] getFields():所有的公共的可访问的字段,返回的是Field对象数组
		Field[] fields = person.getFields();
		//遍历
		for(Field fie:fields){
			System.out.println(fie);
		}
		System.out.println("--------------------------");
		//(3)public Field[] getDeclaredFields():获取当前字节码文件对象中所有的公共的或者私有的字段
		Field[] df = person.getDeclaredFields();
		for(Field fie:df){
			System.out.println(fie);
		}
		//(4)重点---获取单个公共的成员变量(Field类对象)
		//public Field getField(String name):获取公共的指定的字段,参数为当前成员变量名称"address"
		Field field = person.getField("age");
		//(5)动态创建一个对象
		Constructor<?> con = person.getConstructor();
		Object obj = con.newInstance();
		//有了Field对象,给当前obj实例对象设置一个参数
		//将指定对象变量上此 Field对象表示的字段设置为指定的新值public void set(Object obj, Object value)
		//给obj实例对象里面的成员变量设置一个实际参数---->value
		field.set(obj, 27);
		System.out.println(obj);
		//(6)私有的成员变量
		Field de = person.getDeclaredField("name");
		de.setAccessible(true);
		de.set(obj, "小王");
		System.out.println(obj);
	}
}

Class类中关于成员方法的方法

(1)  public Method[] getDeclaredMethods():

说明:得到此类对象本身的所有方法(公共、受保护、默认、私有),但不包括继承,理解上:已经获取所有的了,继承的就算了吧

(2)  public Method[] getMethods():获取当前该字节码文件对象(Person.class)中自己本身以及它父类中所有的公共成员方法

说明:除了自身的公共方法以外,还有继承的公共方法

(3)  public Method getMethod(String name,Class<?>... parameterTypes):指定公共成员方法

参数1:表示方法名字符串形式

参数2:参数类型数的class形式;例如:String.class(非实参),原因:由于可能出现重载的形式,只有方法名无法判断是哪个方法

说明:返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法(无继承的方法,自身类中特有的公共方法)。

(4)  public Method getDeclaredMethod(String name,Class<?>... parameterTypes)

说明:得到该对象(无继承方法)反映此 Class 对象所表示的类或接口的指定已声明方法(可以私有化)

Method类

(1)  public Object invoke(Object obj, Object... args)----对封装的方法进行调用→真正调用方法

      参数1:表示当前针对哪个实例对象进行方法的调用----疑问静态方法不需要obj吧(回头查阅)!!!

      参数2:当前调用该方法的时候里面传递的实际

说明:必须调用的动态对象,然后传递参数才能调用此对象的方法。

注意:如果是私有的方法,必须取消Java语言的访问检查---setAccessible(true)

补充一点:如果不知道该方法的修饰符,而想调用,就取消访问权限吧!!!

实例6  通过反射获取成员变量并使用,之前使用对象调用成员方法

package demo;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Demo6 {
	/**
	 * 需求:
	 * 通过反射获取成员变量并使用,之前使用对象调用成员方法
	 * 类似:
	 * Person p = new Person() ;
	 * p.show() ; 
	 */
	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
		Class<?> person = Class.forName("demo.Person");
		Constructor<?> con = person.getConstructor();
		Object instance = con.newInstance();//动态的创建了一个对象
		Method[] met = person.getMethods();
		Method me = person.getMethod("method1");
		Object inv = me.invoke(instance);
		System.out.println(inv);
			
	}
}

反射的应用

(1)  给你ArrayList<Integer>的一个对象,我想在这个集合中添加一个字符串数据,如何实现呢?

package reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class UseReflect {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

        //给你ArrayList<Integer>的一个对象,我想在这个集合中添加一个字符串数据,如何实现呢?

        ArrayList<Integer> list = new ArrayList<>();

        list.add(520);//自动拆装箱
        list.add(521);
        //list.add("hah");//编译时期不通过

        Class<? extends ArrayList> clazz = list.getClass();

        /**
         * 不需要通过构造方法再创建一个对象
         *
         * 原因:因为我们是往同一个对象中添加的,此对象没有用
         */

        //ArrayList arrayList = clazz.newInstance();

        Method method = clazz.getDeclaredMethod("add", Object.class);
        //注意:通过API看此add方法的参数类型,而不是写入自己添加数据的类型--String.class

        method.setAccessible(true);//不管私有与否,取消检查

        method.invoke(list,"你好");
        //注意:此时遍历的类型变了,不是Integer了
        for(Object lt:list){
            System.out.println(lt);
        }
    }
}

分析:反射是在运行时完成一系列的动作,泛型只在编译时期检查,在运行时泛型已经擦除,因此可以添加其他内容

应用2:读取配置文件,在运行时动态的获取不同的类,进而创建不同的类对象。

(2)  动态代理(下一篇章介绍)

相关链接:

如何给面试官讲反射---点击打开链接

反射机制的应用场景:点击打开链接

反射详解:点击打开链接

框架中Java反射机制在Spring IOC中的应用:点击打开链接

深层:点击打开链接

补充类加载器:点击打开链接点击打开链接点击打开链接点击打开链接点击打开链接

未完待续。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值