Java----反射

本文深入探讨Java反射机制的概念、实现方式及其实现过程,包括Class类的使用、反射对象的操作、Constructor、Field和Method类的应用,并通过具体示例展示如何利用反射进行对象属性修改、方法调用和类加载等操作。
摘要由CSDN通过智能技术生成
高新技术 反射
一、Class
Class类:Java程序中的各个Java类属于同一类事物,描述这类事物的Java类就是Class
疑问如何获得对象的字节码?
1.对象.getClass();
2.类名.class();
3.Class.fotNamre("类名");
package cn.itcast.day1;

public class ReflectTest {

	public static void main(String[] args) throws Exception {
		String s = "abcd";
		Class c1 = s.getClass();
		Class c2 = String.class;
		Class c3 = Class.forName("java.lang.String");//会抛异常,因为可能不存在。
		System.out.println(c1 == c2);
		System.out.println(c1 == c3);
		System.out.println(c2 == c3);
	}
}

解析:说明他们用的都是同一份字节码文件。
package cn.itcast.day1;

public class ReflectTest {

	public static void main(String[] args) throws Exception {
		String s = "abcd";
		Class c1 = s.getClass();
		Class c2 = String.class;
		Class c3 = Class.forName("java.lang.String");
		System.out.println(c1 == c2);
		System.out.println(c1 == c3);
		System.out.println(c2 == c3);
		System.out.println("是否是原始类型:"+c1.isPrimitive());//是否是元素类型(即基本数据类型)。
		System.out.println("是否是原始类型:"+int.class.isPrimitive());
		System.out.println("是否相同:"+(int.class == Integer.class));//判断int和Integer的字节码是否相同。
		System.out.println("是否相同:"+(int.class == Integer.TYPE));//TYPE代表所包装的基本数据类型。
		System.out.println("是否是原始类型:"+(int[].class.isPrimitive()));//判断数组类型是否是原始类型。
		System.out.println("是否是数组类型:"+(int.class.isArray()));//判断是否是数组类型。
	}
}

:只要是在源程序中出现的类型,都有各自的Class实例对象,例如int[]void
二、反射
疑问神马是反射
反射:就是把Java类中的各种成分映射成相应的类。例如,一个Java中用一个Class类的对象来表示。一个类中的组成部分:成员变量方法构造方法等信息也用一个个Java类来表示。表示Java类的Class类显然要提供一系列的方法,来获取其中的变量方法构造方法修饰符等信息,这些信息就是用相应的实例对象来表示。它们是FieldMethodConstructorPackage等。
Constructor类:代表某个类中的一个构造方法。
Constructor<T> getConstructor(Class<?>... parameterTypes) 
          返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。 
Constructor<?>[] getConstructors() 
          返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。
T newInstance() 
          创建此 Class 对象所表示的类的一个新实例。 

package cn.itcast.day1;

import java.lang.reflect.Constructor;

public class ConstructorTest {
	
	public static void main(String[] args) throws Exception {
		//调用方法时,要用到类型
		Constructor constructor = String.class.getConstructor(StringBuffer.class);
		//调用获得的方法时,要用到上面相同类型的实例对象。
		String s = (String) constructor.newInstance(new StringBuffer("abcd"));
		System.out.println(s.charAt(2));
	}
}

Filed类:代表某个类中的一个成员变量。
Field getField(String name) 
          返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。 
Field[] getFields() 
          返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。 
Field getDeclaredField(String name) 
          返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。 
Field[] getDeclaredFields() 
          返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。 
package cn.itcast.day1;

import java.lang.reflect.Field;

public class FieldTest {

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		ReflectPoint fc = new ReflectPoint(3, 5);
		Class clazz = fc.getClass();
		Field fieldX = clazz.getField("x");//获取x字段封装成Field。
		System.out.println(fieldX.get(fc));//获取对象fc上的Field指定字段上的值。
		Field fieldY = clazz.getField("y");
		System.out.println(fieldY.get(fc));
	}

}

抛出异常找不到变量x,因为x是私有变量。要想获取到想,就要使用getDeclaredField()方法。要访问到私有变量还必须使用setAccessible(true)方法暴力反射。
改进
package cn.itcast.day1;

import java.lang.reflect.Field;

public class FieldTest {

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		ReflectPoint fc = new ReflectPoint(3, 5);
		Class clazz = fc.getClass();
		Field fieldX = clazz.getDeclaredField("x");//将变量x封装成Field,即使该变量是私有的
		fieldX.setAccessible(true);//将该Field对象设置为暴力反射
		System.out.println(fieldX.get(fc));
		Field fieldY = clazz.getField("y");
		System.out.println(fieldY.get(fc));
	}

}

练习:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的“b”改成“a”。
package cn.itcast.day1;

import java.lang.reflect.Field;

public class Demo {

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		Test test = new Test("bb", "bccb", 20);
		Class clazz = test.getClass();
		Field[] fields = clazz.getDeclaredFields();//获取所有成员变量数组
		for(Field field : fields){
			field.setAccessible(true);//将field设置为暴力反射,访问私有变量
			if(field.getType() == String.class){//将变量类型字节码和String的字节码比较,因为String字节码只有一份,比较的是两引用地址,所有用“==”连接
				String oldstr = (String) field.get(test);//获取字符串
				String newstr = oldstr.replaceAll("b", "a");
				field.set(test, newstr);//将新字符串存覆盖旧字符串
			}
		}
		System.out.println(test);
	}

}

Method类:代表某个类中的一个成员方法。
Method getMethod(String name, Class<?>... parameterTypes) 
          返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 
Method[] getMethods() 
          返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。 
Object invoke(Object obj, Object... args) 
          对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。 
package cn.itcast.day1;

import java.lang.reflect.Method;

public class Demo {

	public static void main(String[] args) throws Exception {
		String str = "abcd";
		Method method_charat = str.getClass().getMethod("charAt", int.class);//获取类的方法
		System.out.println(method_charat.invoke(str, 3));//将方法作用到对象上
	}

}

:如果invoke()第一个参数为null说明,该方法是静态的。
练习:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
package cn.itcast.day1;

import java.lang.reflect.Method;

public class Demo {

	public static void main(String[] args) throws Exception {
		String className = args[0];
		Class clazz = Class.forName(className);
		Method methodmain = clazz.getMethod("main", String[].class);//获取main方法
		methodmain.invoke(null, new String[]{});//调用main方法。
	}

}
package cn.itcast.day1;

public class Test {
	public static void main(String[] args) {
		System.out.println("Hello World");
	}
}

抛出数组角标越界异常,原因是没有传入类名。
点击Run Demo的下拉菜单——>Run Configurations

转到Arguments——>输入cn.itcast.day1.Test——Apply——>Run


抛出错误的参数个数,因为JDK1.5为了兼容1.4版本,虽然传入的是一个字符串数组,但是虚拟机会将其拆包,因而变成多个参数,也就发生了错误的参数个数异常。为了解决这一问题,需要把字符串数组装入Object数组中,这样虚拟机就只拆掉Object数组,而保留字符串数组了。
改进
package cn.itcast.day1;

import java.lang.reflect.Method;

public class Demo {

	public static void main(String[] args) throws Exception {
		String className = args[0];
		Class clazz = Class.forName(className);
		Method methodmain = clazz.getMethod("main", String[].class);//获取main方法
		methodmain.invoke(null, new Object[]{new String[]{}});//调用main方法。
	}

}

也可以将数组强转为Object
package cn.itcast.day1;

import java.lang.reflect.Method;

public class Demo {

	public static void main(String[] args) throws Exception {
		String className = args[0];
		Class clazz = Class.forName(className);
		Method methodmain = clazz.getMethod("main", String[].class);//获取main方法
		methodmain.invoke(null, (Object)new String[]{});//调用main方法。
	}

}
告诉虚拟机传入的是一个Object,避免被拆包。
数组反射:具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class
package cn.itcast.day1;

public class Demo {

	public static void main(String[] args) throws Exception {
		int[] a1 = new int[3];
		int[] a2 = new int[4];
		int[][] a3 = new int[2][3];
		String[] a4 = new String[3];
		System.out.println(a1.getClass() == a2.getClass());
		//System.out.println(a1.getClass() == a3.getClass());不相等,因为维数不同
		//System.out.println(a1.getClass() == a4.getClass());不相等,因为类型不同
	}

}

:基本数据类型的一维数组可以被当作Object类型使用,不能当做Object[]类型使用;非基本数据类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
package cn.itcast.day1;

public class Demo {

	public static void main(String[] args) throws Exception {
		int[] a1 = new int[3];
		int[] a2 = new int[4];
		int[][] a3 = new int[2][3];
		String[] a4 = new String[3];
		System.out.println(a1.getClass() == a2.getClass());
		//System.out.println(a1.getClass() == a3.getClass());//不相等,因为维数不同
		//System.out.println(a1.getClass() == a4.getClass());//不相等,因为类型不同
		System.out.println(a1.getClass().getSuperclass().getName());
		System.out.println(a4.getClass().getSuperclass().getName());
		Object obj1 = a1;//相当于把int[]数组封装成Object
		Object obj2 = a4;//相当于把String[]数组封装成Object
		//Object[] obj3 = a1;//相当于把int[]数组转换成Object[],这是不行的,因为基本数据类型的父类不是Object
		Object[] obj4 = a3;//把a3看作是一个一维数组里面装着一维数组。
		Object[] obj5 = a4;//相当于把String[]数组封装成Object[]数组。
	}

}
Arrays.asList()方法处理int[]String[]时的差异:
package cn.itcast.day1;

import java.util.Arrays;

public class Demo {

	public static void main(String[] args) throws Exception {
		int[] a1 = new int[]{1,2,3,4};
		String[] a2 = new String[]{"a","b","c","e"};
		int a = 6;
		System.out.println(Arrays.asList(a1));
		System.out.println(Arrays.asList(a2));
	}
}

解析JDK1.5asList(T...a),是用可变参数接收。JDK1.4则是用asList(Object[] a),是用Object数组接收。当String[]数组传入时,用的是1.4版本。而int[]传入时,Object[]数组不能接收。所以用1.5版本接收。这时可变参数是把int[]数组当作一个对象,而非一个数组。所以打印的就是地址了。
练习:用反射原理打印数组或非数组。
package cn.itcast.day1;

import java.lang.reflect.Array;
public class Demo {

	public static void main(String[] args) throws Exception {
		int[] a1 = new int[]{1,2,3,4};
		String a2 = "abcd";
		printArrys(a1);
		printArrys(a2);
	}

	private static void printArrys(Object obj) {
		// TODO Auto-generated method stub
		Class clazz = obj.getClass();
		if (clazz.isArray()) {
			int length = Array.getLength(obj);
			for(int i=0;i<length;i++){
				System.out.println(Array.get(obj, i));
			}
		} else {
			System.out.println(obj);
		}
	}

}

知识回顾:
ArrayList:底层是数组结果,元素可重复。比较元素的时候依赖于equals()方法。
HashSet:底层是哈希表结构,元素不可重复。比较元素的时候,先比较hashCode(),如果相同再比较equals()方法。
内存泄露
package cn.itcast.day1;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashSet;

public class Demo {

	public static void main(String[] args) throws Exception {
		Collection hs = new HashSet();
		Test t1 = new Test(3, 3);
		Test t2 = new Test(5, 5);
		Test t3 = new Test(3, 3);
		
		hs.add(t1);
		hs.add(t2);
		hs.add(t3);
		System.out.println(hs.size());
		
		t1.x=8;
		
		System.out.println(hs.remove(t1));
		System.out.println(hs.size());
	}

}
package cn.itcast.day1;

public class Test {
	
	public int x;
	public int y;

	public Test(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Test other = (Test) obj;
		if (x != other.x)
			return false;
		if (y != other.y)
			return false;
		return true;
	}
	
}

:当一个对象被存储进HashCode集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中的哈希值就不同了。在这种情况下,即使在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露
练习:小型框架
package cn.itcast.day1;

import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Properties;

public class Demo {

	public static void main(String[] args) throws Exception {
		//读取配置文件
		Properties prop = new Properties();
		InputStream in = new FileInputStream("cn/itcast/day1/config.properties");
		prop.load(in);
		//关闭资源,防止内存泄露
		in.close();
		
		Class clazz = Class.forName(prop.getProperty("classname"));
		Constructor constructor = clazz.getConstructor(null);
		Collection coll = (Collection) constructor.newInstance(null);
		Method method = clazz.getMethod(prop.getProperty("addfun"), Object.class);
		method.invoke(coll, "a");
		method.invoke(coll, "b");
		method.invoke(coll, "c");
		method.invoke(coll, "d");
		
		System.out.println(coll.size());
	}

}


类加载器的方式管理资源和配置文件:
package cn.itcast.day1;

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Properties;

public class Demo {

	public static void main(String[] args) throws Exception {
		//读取配置文件
		Properties prop = new Properties();
		InputStream in = Demo.class.getResourceAsStream("config.properties");
		prop.load(in);
		//关闭资源,防止内存泄露
		in.close();
		
		Class clazz = Class.forName(prop.getProperty("classname"));
		Constructor constructor = clazz.getConstructor(null);
		Collection coll = (Collection) constructor.newInstance(null);
		Method method = clazz.getMethod(prop.getProperty("addfun"), Object.class);
		method.invoke(coll, "a");
		method.invoke(coll, "b");
		method.invoke(coll, "c");
		method.invoke(coll, "d");
		
		System.out.println(coll.size());
	}

}
因为配置文件要和.class文件一同打成jar包,所以把配置文件放到classpath目录下,用类加载器加载资源和配置文件。
技术选型 【后端】:Java 【框架】:springboot 【前端】:vue 【JDK版本】:JDK1.8 【服务器】:tomcat7+ 【数据库】:mysql 5.7+ 项目包含前后台完整源码。 项目都经过严格调试,确保可以运行! 具体项目介绍可查看博主文章或私聊获取 助力学习实践,提升编程技能,快来获取这份宝贵的资源吧! 在当今快速发展的信息技术领域,技术选型是决定一个项目成功与否的重要因素之一。基于以下的技术栈,我们为您带来了一份完善且经过实践验证的项目资源,让您在学习和提升编程技能的道路上事半功倍。以下是该项目的技术选型和其组件的详细介绍。 在后端技术方面,我们选择了Java作为编程语言。Java以其稳健性、跨平台性和丰富的库支持,在企业级应用中处于领导地位。项目采用了流行的Spring Boot框架,这个框架以简化Java企业级开发而闻名。Spring Boot提供了简洁的配置方式、内置的嵌入式服务器支持以及强大的生态系统,使开发者能够更高效地构建和部署应用。 前端技术方面,我们使用了Vue.js,这是一个用于构建用户界面的渐进式JavaScript框架。Vue以其易上手、灵活和性能出色而受到开发者的青睐,它的组件化开发思想也有助于提高代码的复用性和可维护性。 项目的编译和运行环境选择了JDK 1.8。尽管Java已经推出了更新的版本,但JDK 1.8依旧是一种成熟且稳定的选择,广泛应用于各类项目中,确保了兼容性和稳定性。 在服务器方面,本项目部署在Tomcat 7+之上。Tomcat是Apache软件基金会下的一个开源Servlet容器,也是应用最为广泛的Java Web服务器之一。其稳定性和可靠的性能表现为Java Web应用提供了坚实的支持。 数据库方面,我们采用了MySQL 5.7+。MySQL是一种高效、可靠且使用广泛的关系型数据库管理系统,5.7版本在性能和功能上都有显著的提升。 值得一提的是,该项目包含了前后台的完整源码,并经过严格调试,确保可以顺利运行。通过项目的学习和实践,您将能更好地掌握从后端到前端的完整开发流程,提升自己的编程技能。欢迎参考博主的详细文章或私信获取更多信息,利用这一宝贵资源来推进您的技术成长之路!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值