JavaSE基础视频28_反射机制

一、反射机制——概述&应用场景

	概念:
		1、java反射机制是在运行状态中,对于任意一个类(class文件),都能够知道这个类的所有属性和方法;
		2、对于任意一个对象,都能够调用它的任意方法和属性;
		3、这种动态获取的信息以及动态调用对象的方法和功能称为java语言的反射机制。
		4、反射技术出现于JDK1.2版本。
		5、反射应用一般需要有接口和配置文件。
		6、反射技术也可以理解为对类的解剖。

	反射一般用于框架的开发及应用
		学习框架:1、看这个框架是干什么的;2、看这个框架的配置文件怎么用;3、学框架中常用对象的用法。

二、反射机制——细节&Class 对象

1、什么是字节码文件? 例如: 当我们在程序中使用到Person这个类的时候,首先要从硬盘上把Person这个类的二进制代码加载到内存中,然后通过这个字节码创建出一个一个的对象。 当我们用到Date这个类,Math这个类,那么这两个类也会加载进内存。 那么这时,内存中就有了3个字节码。 那么,每一个字节码就是 Class 的实例对象。 例如: Person p1 = new Person(); Person p2 = new Person(); Date  Math Class cls1 = Date.class //Date.class就是一个字节码 Class cls2 = Person.class //Person.class就是一个字节码 Class cls3 = Math.class 2、 Class 类描述 java.lang.Object  -java.lang.Class<T>  public final class Class<T> extends Object implements Serializable, GenericDeclaration, Type, AnnotatedElement  Class 类的实例表示正在运行的 Java 应用程序中的类和接口。 该类可以获取.class 字节码文件中的所有内容。 3、九个预定义Class实例对象 基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。  例如: Class cls1 = boolean.class  Class cls2 = int.class  Class cls3 = void.class  ...... 只要是在源程序中出现的类型,都有各自的Class实例对象。

三、反射机制——获取 Class 对象的三种方式以及一些小细节

想要对字节码文件进行解剖,必须要有字节码文件对象。 如何获取字节码文件对象呢?字节码文件可以理解为这个 Class 对象。 获取 Class 对象的三种方式: 1、获取字节码对象的方式一: Object类中的getClass()方法; 想要用这种方式,必须要明确具体的类,并创建对象。 2、方式二: 任何数据类型都具备都具备一个静态的属性 .class ,可以用它来获取其对应的 Class 对象。 相对简单,但还是需要明确用到类中的静态成员。 3、方式三: 只要通过给定的类的字符串名称就可以获取该类,这样更为扩展。 可以用 Class类中的方法来完成。 该方法就是 Class 类中的静态方法: static Class forName(String className); 4、示例: String str = "abc"; Class cls1 = str.getClass();//方式一 Class cls2 = String.class;//方式二 Class cls3 = Class.forName("java.lang.String");//方式三 System.out.println(cls1 == cls2);//true System.out.println(cls2 == cls3);//true cls1、cls2、cls3都表示Class的一个实例对象。都是 String 类加载进内存内存后的一份字节码,而且该字节码只有一个。 5、 Class 类中有一些方法: boolean isPrimitive():判定指定的 Class 对象是否表示一个基本类型。  例如: System.out.println(String.class.isPrimitive());//结果是false System.out.println(int.class.isPrimitive());//结果是true System.out.println(int.class == Integer.class);//结果是false;因为int是基本数据类型,Integer是类类型 System.out.println(int[].class.isPrimitive());//结果是false;因为数组是引用数据类型 boolean isArray():判定此 Class 对象是否表示一个数组类。 示例: System.out.println(int[].class.isArray());//结果是true 6、 Integer 类中有一个字段摘要: static Class<Integer> TYPE :表示基本类型 int 的 Class 实例。  示例: System.out.println(int.class == Integer.TYPE);//结果是true  示例代码:
	class ClassDemo 
	{
		public static void main(String[] args) throws ClassNotFoundException 
		{
			getClassObject_3();
		}

		/*
		获取字节码对象的方式一:
			Object类中的getClass()方法;
			想要用这种方式,必须要明确具体的类,并创建对象。
		*/
		public static void getClassObject_1()
		{
			Person p = new Person();
			Class clazz = p.getClass();

			Person p1 = new Person();
			Class clazz1 = p1.getClass();

			System.out.println(clazz==clazz1);//true
		}

		/*
		方式二:
			任何数据类型都具备都具备一个静态的属性 .class 来获取其对应的 Class 对象。
			相对简单,但还是需要明确用到类中的静态成员。
		*/
		public static void getClassObject_2()
		{
			Class clazz = Person.class;

			Class clazz1 = Person.class;

			System.out.println(clazz==clazz1);//true
		}

		/*
		方式三:
			只要通过给定的类的字符串名称就可以获取该类,这样更为扩展。
			可以用 Class类中的方法来完成。
			该方法就是forName(String className);
		*/
		public static void getClassObject_3() throws ClassNotFoundException 
		{
			String className = "Person";

			Class clazz = Class.forName(className);

			System.out.println(clazz);//class Person 
		}
	}

四、反射机制——获取 Class 中的构造函数 

1、 Class 类中获取指定字节码文件中构造函数的方法(该方法只能获取 public 修饰的):
Constructor<T> getConstructor(Class<?>... parameterTypes); 获取单个

2、获取所有 public 构造方法:
Constructor<?>[] getConstructors(); 

3、获取指定的构造方法,包括私有的:
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes);

4、获取所有的构造方法,包括私有的:
Constructor<?>[] getDeclaredConstructors();

5、获取一个 Class 对象后,可以调用 Class 对象的 newInstance();方法创建该Class所表示的类的空参数的实例对象。
如果要创建一个非空参数的实例对象,只能先获取到对应的构造方法对象后,才能通过指定的构造方法对象创建对象。
步骤:
(1)获取Class对象;
(2)调用Classd对象的getConstructor方法(以上四个中的一个),并返回 Constructor 对象;
(3)调用Constructor对象的newInstance方法。

6、示例代码:
	import java.lang.reflect.*;

	class ReflectDemo 
	{
		public static void main(String[] args) throws Exception 
		{
			createNewObject_2();
		}

		public static void createNewObject() throws ClassNotFoundException, IllegalAccessException, InstantiationException 
		{
			String name = "Person";

			Class clazz = Class.forName(name);

			//创建空参数的对象
			Object obj = clazz.newInstance();
		}

		public static void createNewObject_2() throws Exception 
		{
			String name = "Person";

			Class clazz = Class.forName(name);

			//获取指定构造函数对象。
			Constructor constructor = clazz.getConstructor(String.class, int.class);

			//通过该构造器的newInstance方法进行对象的初始化。
			Object obj = constructor.newInstance("小明", 32);
		}
	}

五、反射机制——获取 Class 中的字段

Class 类中获取字段(包括私有)的方法:
Field getDeclaredField(String name); 获取指定字段

Field[] getDeclaredFields(); 获取所有字段对象

Field getField(String name); 和  Field[] getFields(); 只能获取 public 修饰的字段。

1、获取字段,并给字段赋值
步骤:
1、获取Class对象;
2、调用Class对象的getField方法(以上四个中的一个),并返回Field对象;
3、创建字节码文件所表示的类对象obj;
4、给指定对象obj字段赋值。

2、暴力反射:
当字节码文件中某些字段被private修饰后,通常不可以被访问;
这时,可以调用该字段对象的setAccessible方法,并设置为true;
这时就可以访问该字段了。

3、setAccessible方法:
void setAccessible(boolean flag) 将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
该方法是 AccessibleObject 类的一个方法。 AccessibleObject 类是 Constructor 、 Field 、 Method 的共同父类。
那么对于私有构造函数和私有函数也可以调用该方法进行暴力反射。

示例代码:
	import java.lang.reflect.*;

	class ReflectDemo2
	{
		public static void main(String[] args) throws Exception 
		{
			getFieldDemo();
		}

		public static void getFieldDemo() throws Exception 
		{
			Class clazz = Class.forName("Person");

			//获取私有字段
			Field field = clazz.getDeclaredField("age");

			//对私有字段的访问取消权限检查(暴力访问)
			field.setAccessible(true);

			//创建空参数的对象
			Object obj = clazz.newInstance();

			//设置字段的值
			field.set(obj,89);

			//获取obj对象中的field字段。
			Object o = field.get(obj);

			System.out.println("field: "+field); //private int Person.age
			System.out.println("obj: "+obj); //Person@6d06d69c
			System.out.println("o: "+o); //89
		}
	}

六、反射机制——获取 Class 中的方法 

Class 类中同样有获取字节码文件所表示的类的方法,获取方式与 Constructor 和 Field 相同。
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 

Method[] getDeclaredMethods() 

Method getMethod(String name, Class<?>... parameterTypes) 

Method[] getMethods() 

不同的是,获取到 Method 对象后,调用的是 Method 对象的 invoke()方法去调用字节码文件类的方法。

代码:
	import java.lang.reflect.*;

	class ReflectDemo3 
	{
		public static void main(String[] args) throws Exception 
		{
			getMethodDemo_3();
		}

		public static void getMethodDemo() throws Exception 
		{
			Class clazz = Class.forName("Person");

			//Method[] methods = clazz.getMethods();//获取所有公有的方法(包括继承的方法)

			Method[] methods = clazz.getDeclaredMethods();//只获取本类中的所有方法,包括私有方法。不包括继承的方法。

			for(Method method : methods)
			{
				System.out.println(method);
			}
		}

		public static void getMethodDemo_2() throws Exception 
		{
			Class clazz = Class.forName("Person");

			Method method = clazz.getMethod("show",null);

			System.out.println(method);

			//Object obj = clazz.newInstance();
			Constructor constructor = clazz.getConstructor(String.class, int.class);
			Object obj = constructor.newInstance("小明",22);

			method.invoke(obj,null);//调用show()方法
		}

		public static void getMethodDemo_3() throws Exception 
		{
			Class clazz = Class.forName("Person");

			Method method = clazz.getMethod("paramMethod",String.class,int.class);

			Object obj = clazz.newInstance();

			method.invoke(obj,"小强",55);
		}
	}

七、反射机制——反射练习

加入反射技术,优化day08电脑主板示例代码:
配置文件信息:
	pci1=SoundCard
	pci2=NetCard

	import java.io.File;
	import java.io.FileInputStream;
	import java.util.Properties;

	public class ReflectTest {

		public static void main(String[] args) throws Exception {

			MainBoard mb = new MainBoard();
			mb.run();
			
			//mb.usePCI(new SoundCard());//以前是直接new一个PCI传入方法参数。
			
			//利用反射技术,首先加载进一个配置文件
			File configFile = new File("pci.properties");
			
			Properties prop = new Properties();
			
			FileInputStream fis = new FileInputStream(configFile);
			
			prop.load(fis);
			
			//扫描这个配置文件,并获取配置文件信息,也就是类名
			for (int x = 0; x < prop.size(); x++) {
				String className = prop.getProperty("pci"+(x+1));
				
				//创建Class对象
				Class clazz = Class.forName(className);
				
				//创建PCI对象
				PCI p = (PCI)clazz.newInstance();
				
				//调用方法
				mb.usePCI(p);
			}
			fis.close();
		}
	}

八、对接收数组参数的成员方法进行反射

示例代码:
	import java.lang.reflect.*;
	class Demo
	{
		public static void main(String[] args)throws Exception
		{
			String className = args[0];
			Method mainMethod = Class.forName(className).getMethod("naim",String[].class);
			mainMethod.invoke(null,new String[]{"111","222","333"});//这样传入参数会出现参数个数异常
			//mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});//正确写法
			//mainMethod.invoke(null,(Object)new String[]{"111","222","333"});//正确写法二
		}
	}
	class Test
	{
		public static void main(String[] args)
		{
			for (int x=0 ;x<args.length ;x++ )
			{
				System.out.println(args);
			}
		}
	}
该代码存在问题点:
1、运行时需要传入一个字符串参数: java Demo Test
2、JDK1.5后,增加了可变参数的新特性,但是还需要兼容JDK1.4版本;当调用一个参数为数组类型的方法时,传入的数组将会自动拆包为一个一个的元素。
该例中,mainMethod.invoke(null,new String[]{"111","222","333"});参数中的String类型的数组会被拆包成一个一个的String类型的元素;
所以运行结果会出现参数个数异常。
应该改为:mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});
或者改为:mainMethod.invoke(null,(Object)new String[]{"111","222","333"});

九、数组与Object的关系及其反射类型

如果两个数组的元素类型,以及数组的维数都是相同的,则这两个数值的字节码文件是同一个。
代码示例:
	class Demo2
	{
		public static void main(String[] args) 
		{
			int[] a1 = new int[3];
			int[] a2 = new int[4];
			int[][] a3 = new int[2][3];
			String[] a4 = new String[4];

			System.out.println(a1.getClass() == a2.getClass());//true
			//System.out.println(a1.getClass() == a3.getClass());//编译失败,因为不能进行比较
			//System.out.println(a1.getClass() == a4.getClass());//编译失败,因为不能进行比较
			System.out.println(a1.getClass().getSuperclass());//class java.lang.Object
		}
	}

十、数组的反射应用

	import java.lang.reflect.*;
	class Demo3 
	{
		public static void main(String[] args) 
		{
			int[] arr = {2,5,8};
			printObject(arr);
		}
		public static void printObject(Object obj)
		{
			Class cls = obj.getClass();

			if(cls.isArray())
			{
				int len = Array.getLength(obj);
				for (int x=0 ;x<len ;x++ )
				{
					System.out.println(Array.get(obj,x));
				}
			}
			else
			{
				System.out.println(obj);
			}
		}
	}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值