天天看点

java初学笔记10-反射一.反射(Java Reflection)概述二.Class类三.通过反射调用类的完整结构四.动态代理

反射

  • 一.反射(Java Reflection)概述
  • 二.Class类
    • 1.Class类概述
    • 2.实例化Class类对象(四种方法)
  • 三.通过反射调用类的完整结构
    • 1.通过反射调用类构造方法(创建对象)
    • 2.通过反射调用类方法
    • 3.通过反射调用类属性
    • 4.通过反射调用类中的指定方法
    • 5.通过反射调用类中的指定属性
  • 四.动态代理

一.反射(Java Reflection)概述

即通过类名信息, 可以取得类的内部信息

Reflection (反射) 是被视为动态语言的关键, 反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息, 并能直接操作任意对象的内部属性和方法。

Java 反射机制提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的成员变量和方法
  • 生产动态代理

Java反射机制研究及应用

反射相关的主要API:

  • java.lang.Class: 代表一个类
  • java.lang.reflect.Method: 代表类的方法
  • java.lang.reflect.Filed: 代表类的成员变量
  • java.lang.reflect.Constructor: 代表类的构造方法

二.Class类

1.Class类概述

Class类是所有类的高度抽象, 是一个可以描述所有类的类

在Object类中定义了以下的方法, 此方法将被所有子类继承:

  • public final Class getClass()

该方法的返回值类型是一个Class类,

Class类是Java反射的源头, 从程序的运行结果来看, 即通过对象反射求出类的名称。

反射可以得到的信息:

某个类的属性、方法和构造器、某个类到底实现了哪些接口。

对于每个类而言, JRE都为其保留一个不变的 Class 类型的对象。

一个Class对象包含了特定某个类的有关信息。

  • Class本身也是个类
  • Class对象只能由系统建立对象
  • 一个类在JVM中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件
  • 每个类的实例都会记得自己是由哪个Class实例所生成
  • 通过Class可以完整地得到一个类中的完整结构
java初学笔记10-反射一.反射(Java Reflection)概述二.Class类三.通过反射调用类的完整结构四.动态代理

2.实例化Class类对象(四种方法)

1.通过类名.class创建

  • Class cls = Person.class;
  • 前提:已知具体的类, 通过类的class属性获取, 该方法最为安全可靠, 程序性能最高

2.通过类的实例对象.getClass() 方法获取

  • Class cls = p.getClass();
  • 前提: 已知某个类的实例对象

3.通过Class类的静态方法forName() 获取(常用方法)

  • Class cls = Class.forName(“day10.Person”);
  • 前提: 已知一个类的全路径(包名.类名), 可能抛出ClassNotFoundException

4.通过ClassLoader(了解)

  • ClassLoader cl = this.getClass().getClassLoader();
  • Class cls = cl.loadClass(“day10.Person”);
public class TestClass {
	public static void main(String[] args) {
		Person p = new Person();
		p.getClass();
		Class<? extends Person> cls = p.getClass();//cls对象就包含对象p所属Person类的所有信息
		
		Class c1 = Person.class;//通过类名.class创建指定类的Class实例
		
		Class c2 = p.getClass();//通过一个类的实例对象.getClass()方法,获取对应类的Class实例
		
		//通过Class的静态方法forName(String className)来获取一个类的class实例
		//参数String className是类的全路径(包名.类名)
		//这是获取Clsss实例的常用方式
		try {
			Class c3 = Class.forName("day20210411.Person");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}	
           

三.通过反射调用类的完整结构

Field 属性

Method 方法

Constructor 结构

Superclas 父类

Interface 接口

Annotation 注解

使用反射可以取得:

类所在的包

  • public Package getPackage()

    返回此对象所在的包

1.实现的全部Interfaces(接口)

  • public Class<?>[] getInterfaces()

    返回此Class对象所表示的类或接口 所实现的所有接口

2.所继承的Superclass(父类)

  • public Class<? Super T>getSuperclass()

    返回表示此Class 所表示的实体(类、接口、基本类型)的父类的Class

3.全部的Constructors(构造器)

  • public Constructor[] getConstructors()

    返回此对象所表示的类的全部public构造方法

  • public Constructor[] getDeclaredConstructors()

    返回此对象所表示的类声明的全部构造方法

Constructor类中:

  • 取得修饰符: public int getModifiers(); //返回值1代表public,2代表private
  • 取得方法名称: public String getName();
  • 取得参数的类型: public Class<?>[] getParameterTypes();

4.全部的Methods(方法)

  • public Method[] getMethods()

    返回此对象所表示的类或接口的全部public方法

  • public Constructor[] getDeclaredMethods()

    返回此对象所表示的类或接口的全部方法

Method类中:

  • 取得修饰符: public int getModifiers();
  • 取得方法名称: public String getName();
  • 取得全部的参数类型: public Class<?>[] getParameterTypes();
  • 取得全部的返回值类型: public Class<?>[] getReturnTypes();

5.全部的Fields(属性)

  • public Field[] getFields()

    返回此Class对象所表示的类或接口的public Field

  • public Field[] getDeclaredFields()

    返回此Class对象所表示的类或接口的全部Field

Field类中:

  • 取得修饰符: public int gerModifiers();
  • 取得Field的名称: public String getName();
  • 取得Field的属性类型: public Class<?> getType();
import java.lang.reflect.Constructor;

public class Test {
	public static void main(String[] args) {
		try {
			Class<?> cls = Class.forName("day20210411.Student");//获取Student类的Class对象
			
			Class<?> superCls = cls.getSuperclass();//获取当前类的父类
			System.out.println("父类:"+ superCls.getName());
			
			Class<?>[] interfaces = cls.getInterfaces();//获取当前类的所有接口(数组)
			for(Class<?> c : interfaces ) {
				System.out.println("接口:"+ c.getName());
			}
			
//			Constructor<?>[] cons = cls.getConstructors();//获得类的公有的构造方法
			Constructor<?>[] cons = cls.getDeclaredConstructors();//获得类的所有的构造方法

			for(Constructor<?> c : cons) {
				System.out.println("构造方法:"+ c.getName());//获取方法名称
				System.out.println("构造方法:"+ c.getModifiers());//获取方法修饰符
				//方法修饰符: 返回数值 1代表public, 2代表private
				Class<?>[] types = c.getParameterTypes();//获取参数类型
				for(Class<?> c1 : types) {
					System.out.println("参数类型:"+c1.getName());
				}
			}
			
		} catch (ClassNotFoundException e) {
			e.printStackTrace();	
		}
	}
}
           

1.通过反射调用类构造方法(创建对象)

首先要获取类的Class对象

  • Class<?> cls = Class.forName(“day20210411.Student”);

1.调用类的public无参构造

  • Object obj = cls.getConstructor().newInstance();

    //返回的是Object类对象,需要向下转型

  • Student stu = (Student)obj;

2.调用类的public有参构造(根据参数类型)

  • Constructor<?> c = cls.getConstructor(String.class);

    //调用有一个String类型参数的构造器

  • Student stu1 = (Student)c.newInstance(“第一中学”);

    //通过newInstance()实例化对象,Object类对象,需要向下转型

3.强制调用所有本类定义的有参构造

  • Constructor<?> c2 = cls.getDeclaredConstructor(String.class,

    int.class);

    //调用有(String,int)类型参数的构造方法

  • c2.setAccessible(true);

    //解除私有化封装,下面就可以对这个私有方法强制调用

  • Student stu2 = (Student)c2.newInstance(“李白”,16);

    //传入实参,实例化对象

  • c2.setAccessible(false);//重新对构造进行私有封装
  • System.out.println(stu2.school);//私有封装后已实例化的对象仍然可以访问
import java.lang.reflect.Constructor;

public class Test {
	public static void main(String[] args) {
		try {
			Class<?> cls = Class.forName("day20210411.Student");//获取Student类的Class对象

			/**
			 * 如何用反射的构造方法来创建对象
			 */
			//相当于调用Student类的公有无参构造,需要抛出异常
			Object obj = cls.getDeclaredConstructor().newInstance();//返回Object类对象,需要向下转型
			Student stu = (Student)obj;
			
			//相当于调用Student类的公有有参构造,
			Constructor<?> c = cls.getConstructor(String.class);//有一个String类型参数
			Student stu1 = (Student)c.newInstance("第一中学");//通过newInstance()实例化对象,Object类对象,需要向下转型
			
			//通过反射机制可以强制调用私有的构造方法
			Constructor<?> c2 = cls.getDeclaredConstructor(String.class, int.class);//调用有(String,int)类型参数的构造方法
			c2.setAccessible(true);//解除私有的封装,下面就可以对这个私有方法强制调用
			Student stu2 = (Student)c2.newInstance("李白",16);//传入实参,实例化对象
			c2.setAccessible(false);//重新对构造进行私有封装
			System.out.println(stu2.school);//私有封装后已实例化的对象仍然可以访问
			
		} catch (Exception e) {
			e.printStackTrace();
		}
			
	}
}
           

2.通过反射调用类方法

同上:

  • punlic Method[] getMethods()
  • 返回此对象所表示的类或接口的全部public方法, 包括从父类继承的public方法
  • public Constructor[] getDeclaredMethods()
  • 返回此对象所表示的类或接口的本类/接口中定义的所有方法, 不受权限修饰符的限制, 不包括从父类中继承的方法

Method类中:

  • 取得修饰符: public int getModifiers();
  • 取得方法名称: public String getName();
  • 取得全部的参数类型: public Class<?>[] getParameterTypes();
  • 取得全部的返回值类型: public Class<?>[] getReturnTypes();
import java.lang.reflect.Method;

public class TestMethods {
	public static void main(String[] args) {
		Class<Student> cls = Student.class;
//		Method[] methods = cls.getMethods();//获取类的所有的公有方法
		Method[] methods = cls.getDeclaredMethods();//获取类的所有方法	
		for(Method m : methods) {
			System.out.print("方法:"+m.getName()+",返回值:"+m.getReturnType()+",修饰符:"+m.getModifiers());	
			Class<?>[] tp= m.getParameterTypes();
			if(tp != null && tp.length > 0) {
				for(Class<?> c : tp) {
					System.out.print(",参数:"+c);
				}
			}
			System.out.print('\n');
		}
	}
}

           

3.通过反射调用类属性

同上:

  • public Field[] getFields()

    返回此Class对象所表示的类或接口的public Field, 包括从父类中继承的public 属性

  • public Field[] getDeclaredFields()

    返回此Class对象所表示的类或接口的本类/接口中定义的全部Field, 不受权限修饰符限制, 但不包括从父类中继承的属性

Field类中:

  • 取得修饰符: public int gerModifiers();
  • 取得Field的名称: public String getName();
  • 取得Field的属性类型: public Class<?> getType();
  • 取得Field值, (需要先实例化对象) public Object get(obj);
import java.lang.reflect.Field;

public class TestFileds {
	public static void main(String[] args) {
		try {
			Class<?> cls = Class.forName("day20210411.Student");
			Student stu = new Student();//访问属性值需要先实例化对象
//			Field[] fld = cls.getFields();//获取当前类的public属性(default(修饰符0)修饰的属性无法访问)
			Field[] fld = cls.getDeclaredFields();//获取当前类的全部属性
			for(Field f : fld) {
				System.out.println("类型:"+f.getType()+",属性:"+f.getName()+",修饰符:"+f.getModifiers());
				f.setAccessible(true);//解除私有属性的封装
				try {
					Object value = f.get(stu);//访问stu对象的属性值
					System.out.println(value);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			Package pkg = cls.getPackage();//获取当前类所在的包
			System.out.println(pkg.getName());
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}
           

4.通过反射调用类中的指定方法

通过反射, 调用类中的方法, 通过Method类完成。

步骤:

  • 通过Class类的getMethod(String name, Class…ParaeterTypes)方法, 通过指定方法名称、参数类型、数量, 来取得指定方法的一个Method对象
  • 使用Object invoke(Object obj, Object[] args)方法进行调用, 并向调用的方法中传递要设置的obj对象以及实参信息
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class TestMethods {
	public static void main(String[] args) {
		Class<Student> cls = Student.class;		
		/**
		 * 调用指定方法
		 * 注意:下面调用的都是obj对象的方法,obj对象实际上就是Student对象
		 */
		try {
			Constructor<Student> con = cls.getConstructor();
			Object obj = con.newInstance();//调用无参构造,实例化对象
//			Person p = (Person)obj;
			
			//调用公有方法
			Method m = cls.getMethod("setInfo",String.class,String.class);//获取指定方法getMethod(方法名,参数类型)
			m.invoke(obj,"李白","清华大学");//调用有参构造需要实例化对象。参数1是对象,后面的参数是实参
			
			//调用私有方法
			Method m1 = cls.getDeclaredMethod("test", String.class);
			m1.setAccessible(true);//解除私有化封装
			m1.invoke(obj,"李白");
			
			//有返回值的方法
			Method m3 = cls.getMethod("getSchool");//获取方法名为getSchool(),且没有参数的方法
			String school = (String) m3.invoke(obj);//调用有返回值的方法,返回值为Object类,需要向下转型为String类型
			System.out.println(school);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
           

5.通过反射调用类中的指定属性

在反射机制中, 可以直接通过Field类操作类中的属性, 通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。

  • public Field getField(String name)

    返回此Class对象所表示的类或接口的指定的public 的Field

  • public Field getDeclaredField(String name)

    返回此Class对象所表示的类或接口的指定的Field

在Field类中:

  • 取得指定对象obj上此Field属性内容: public Object get(Object obj);
  • 设置指定对象obj上此Field属性内容: public void set(Object obj,Object value);
  • 使私有属性解除私有化封装: public void setAccessible(true);

注: 在类中属性都设置为private的前提下, 在使用set()和get()方法时, 首先要使用Field类中的setAccessible(true)方法将要操作的属性设置为可以被外部访问。

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

public class TestFileds {

	public static void main(String[] args) {
		try {
			Class<?> cls = Class.forName("day20210411.Student");
			/**
			 * 调用指定属性
			 */
			//首先反射创建一个对象
			
			Constructor<?> con = cls.getConstructor();
//			Object obj = con.newInstance();
			Student stu1 = (Student)con.newInstance();
				
			Field fld1 = cls.getField("school");//获取名称为school的public的属性
			fld1.set(stu1,"北京大学");//对stu1的school属性设置值
			String school = (String) fld1.get(stu1);//获取stu1的school属性的值
			System.out.println(school);
				
			//调用私有的属性
			Field fld2 = cls.getDeclaredField("secret");//获取名称为secret的private(或default)的属性
			fld2.setAccessible(true);//解除fld2(即secret)属性的私有化封装
			fld2.set(stu1, "密码");
			String secret = (String) fld2.get(stu1);
			System.out.println(secret);
							
		} catch (Exception e) {
				e.printStackTrace();
		}
	}
}
           

四.动态代理

JDK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的。

但是,JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高。

Proxy类:

专门完成代理的操作类, 是所有动态代理的父类。

通过此类为一个或多个接口动态地生成实现类。

创建一个动态代理类所对应的Class对象

static Object newProxyInstance(
 ClassLoader loader,
 Class<?>[] interfaces,
 InvocationHandler h)
 * 参数1是代理对象的类加载器,handler.getClass().getClassLoader()
 * 参数2是被代理对象的接口,test.getClass().getInterfaces()
 * 参数3是代理对象,handler
 * 
 * 返回值为Object类,就是成功被代理后的对象
 * 根据当时情况进行类型转换
           

动态代理步骤:

  1. 创建一个实现接口InvocationHandler的类, 它必须实现invoke方法, 以完成代理的具体操作
  2. 创建被代理的类及接口
  3. 通过Proxy的静态方法Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)创建一个Subject接口代理
  4. 通过Subject代理调用RealSubject实现类的方法
  • 注意:如果一个对象需要通过Proxy.newProxyInstance()方法被代理。那么这个对象的类必须有相应的接口, 就像本例中的ItestDemo接口和实现类TestDemoImpl

1.定义接口。(动态代理实现的, 是实现类继承自接口中定义的方法)

public interface Study {
	//采用Proxy.newProxyInstance()方法进行动态代理
	//是对一个类所实现接口中定义的方法进行代理
	void study1();
	void study2();	
}
           

2.定义实现类。

public class Student implements Study{
	@Override
	public void study1() {
		System.out.println("执行study1方法");	
	}
	@Override
	public void study2() {
		System.out.println("执行study2方法");		
	}
}
           

3.创建动态代理类, 实现代理的具体操作

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
 * 动态代理类
 * @author chenyao
 */
public class ProxyDemo implements InvocationHandler {//代理类必须实现InvocationHandler接口
	Object obj;//定义被代理的对象
	public ProxyDemo(Object obj) {//定义构造方法从外部传入对象
		this.obj = obj;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println(method.getName()+"方法开始执行");
		Object result = method.invoke(this.obj, args);//正常通过反射调用类方法的返回结果
		System.out.println(method.getName()+"方法执行完毕");
		return result;//返回invoke调用的方法执行结果
	}
}
           

4.通过动态代理对象调用实现类重写的接口的方法

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
	public static void main(String[] args) {
//		TestDemoImpl test = new TestDemoImpl();
		Study stu = new Student();//对象的多态
		
		//直接调用方法
		stu.study1();
		stu.study2();
		System.out.println("=======================");
		/**
		 * 需求:
		 * 在执行study1和study2方法时加入相应动作
		 * 在执行方法前打印study1或study2开始执行
		 * 在执行方法后打印study1或study2执行完毕
		 */	
		/**
		 * 注意:如果一个对象需要通过Proxy.newProxyInstance()方法被代理
		 * 这个对象的类必须有相应的接口,
		 * 就像本例中的Student接口和实现类Study
		 */
		//创建代理器, 使用代理对象调用方法
//		ProxyDemo handler = new ProxyDemo(stu);
		InvocationHandler handler = new ProxyDemo(stu);//传入被代理的对象
		/**
		 * Proxy.newProxyInstance(ClassLoader loader,
		 * Class<?> interfaces,
		 * InvocationHandler h)
		 * 参数1是代理对象的类加载器,handler.getClass().getClassLoader()
		 * 参数2是被代理对象的接口,test.getClass().getInterfaces()
		 * 参数3是代理对象,handler
		 * 
		 * 返回值为Object类,就是成功被代理后的对象
		 * 根据当时情况进行类型转换为实现的接口类型
		 */
		Study s = (Study) Proxy.newProxyInstance(
			handler.getClass().getClassLoader(), 
			stu.getClass().getInterfaces(),
			handler);	//创建接口对象
		s.study1();//通过代理对象代理实现接口的方法
		s.study2();
	}
}