反射
一、反射
1、反射的描述
程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。
2、reflect包
在java中,包lang.reflect 用于描述反射。java类的反射对象包括Field、Constructor、Method、Packet
二、Class类:
1、Class类概述
(1)、Class类的描述
Class 类的实例表示正在运行的 Java 应用程序中的类和接口(枚举是一种类,注释是一种接口)。Class 没有公共构造方法,Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。
(2)、Class对象是类的字节码
普通类对象:Person p1=new Person() ; Person P2=new Person();
Class实例对象:Class cls1 = Person的字节码 ; Class cls2 = Person的字节
(3)、数组的Class对象
每个数组都属于被映射为 Class 对象的一个类,所有具有相同元素类型和维
数的数组都共享该 Class 对象。
2、Class实例化。
(1)、获取Class对象
对于内存中已经加载的类,直接找到字节码将他返回即可。可以通过类名.class,或者是getClass()方法。
对于jvm还未将其加载到内存中的类,可以使用Class类中的静态方法forName() 。
(2)、方法的举例与说明
a、.class
示例:Class clsPer = Person.class;
说明:内存中加载Person,产生了Person类的字节码,这个字节码就是Class的实例对象
b、getClass( )
示例:Class claP1 = p1.getClass()
说明:Person类的字节码实例化得到p1,p1也可以逆向得到Person的字节码。使用Object类中的getClass()方法。
c、forName()
示例:Class.forName("java.lang.String");
说明:jvm还没有加载字节码,这种情况使用Class类中的forName()方法。这个方法会用类加载器去加载指定的类,加载后,将字节码缓存并返回。使用反射,主要使用这种方法,因为可以将forName()中的字符串参数设置成一个变量,这样写源程序的时候不用知道需要反射的类的名字。运行时要通过主函数将类名作为参数传入。
3、九个预定义的Class对象
(1)、九个预定义的Class对象,(八种基本数据类型和void)
基本的Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象(void.class)。
(2)、基本数据类型的字节码 —— TYPE关键字
int.class==Integer.class——>false 。基本数据类型和它的包装类的字节码对象是不同的类型
int.class==Integer.TYPE——>true 。在基本数据类型的包装类中,定义了属性TYPE,用于表示被包装的数据类型的Class实例。
(3)、判断否为基本数据类型的字节码
使用Class类中的isPrimitive()方法,调用时这样写cls1.isPrimitive();
(4)、判断数组类型的Class对象用的是isArray()方法;
三、Class实例对象中各成分的反射
1、Constructor类:构造方法的反射应用
(1)、得到某个类所有的构造方法
Constructor[] constructors = Class.forName(“java.lang.String”).getConstructors()
(2)、得到某个类特定的构造方法
Constructor constructor = Class.forName(
“java.lang.String”).getConstructor(StringBuffer.class,int.class)
getConstructor(Class<?>... parameterTypes)//这里使用了可变参数的特性。
(3)、newInstance(Object... initargs) ,通过字节码实例化对象。
通常方式:String str = new String(new StringBuffer(“abc”)) ;
反射方式:
Constructor<String> constructor =
Class.forName(“java.lang.String”).getConstructor(StringBuffer.class)
String str = constructor.newInstance(new StringBuffer(“abc”));
//这里通过String的构造方法得到字串”abc”
2、Field类:成员变量的反射
(1)、通过反射获取成员变量的值
import java.lang.reflect.Field;
//get(“对象”)
class Student{
public String name;
private int age;
Student(String name,int age){
this.name = name ;
this.age = age ;
}
}
class FieldTest{
public static void main(String[] args) throws Exception{
Student s = new Student("Mike",17);
//使用get("变量名")方法获取字节码对象的字段值
Field fieldName = s.getClass().getField("name");
// fieldName是类上的变量,而不是对象上的变量
System.out.println(fieldName.get(s/*参数是要获得其值的对象*/));
//暴力反射,获取私有属性值
//或去通过变量名获取字节码对象中的字段。getDeclaredField("变量名")
Field fieldInt = s.getClass().getDeclaredField("age");
//将字段私有设为可以访问,用setAccessible(true)方法
fieldInt.setAccessible(true);
System.out.println(fieldInt.get(s));
}
}
(2)、通过Class对象改变属性值
a、getType()——>得到Field对象的类型。
b、set(“对象”,要替换的内容)——>设置Field对象的值
c、示例代码
import java.lang.reflect.Field;
class ForTest {
//这里必须要用public修饰,反射才能被访问。
public String s1 = "abcdefffff";
public String s2 = "abcdegfffggg";
public String toString(){
return s1+" "+s2;
}
}
class Demo{
public static void main(String[] args) throws Exception {
ForTest ft = new ForTest();
//取得字节码对象的字段属性用getFields()方法
Field[] fields = ft.getClass().getFields() ;
System.out.println(fields.length);
System.out.println("ft1:"+ft.toString());
for (Field field : fields) {
if (field.getType() == String.class) {
//取得字节码对象中字段属性的值用get(对象)方法
String oldStr = (String) field.get(ft);
//改变值用replace方法//这里会将所有的f换成b
String newStr = oldStr.replace('f','b');
//重新设置值用set方法
field.set(ft, newStr);
}
}
System.out.println("ft2:"+ft.toString());
}
}
3、成员方法的反射
(1)、概念
a、方法属于类,而不属于对象。
b、专家模式:谁拥有数据,就具有操作这些数据的方法。
(2)、getMethod(“charAt”,parameterTypes)——>invoke(“对象”,方法的参数)
a、非静态方法:
Method methodCharAt = String.class.getMethod
(“charAt”,parameterTypes)
methodCharAt.invoke(str,1)
b、静态方法
调用静态方法invoke(“null”,”方法的参数”)。静态方法不用建立对象,所以参数对象为null。
c、new Object[]{2},object数组中装了一个Integer类型的对象。
4、对接收数组参数的成员方法进行反射
(1)、普通方式调用main方法:TestArguments.main(new String[] {}) ;
(2)、反射的方式:
a、为什么要用反射的方法调用main方法?有些时候我们并不知道main方法的所属类,这时就没有办法用类名调用
b、main方法的参数是String[]。jdk中有对数组自动拆包的功能,会将String[]拆包,那么要想在main方法中传入String[],就要使用强制转换,将String[]转成Object,或者将其双重打包new Object[]{new String(“1111” , ”2222”,”3333”)}
(3)、示例:
class test{
pulbic static void main(String [] args){
String argsFirst = arg[0] ;
Method mainMethod =
Class.forName(argsFirst).getMethod(“main”,String[].class);
mainMethod.invoke(
null , new Object[](new String[]{“1111” , ”2222” , ”3333”})) ;
}
}
class demo{
public static void main(String[] args){
for(String str : args){
so.p(str);
}
}
}
调用:调用在运行时使用主函数传参的方式传入参数。
五、数组与Object的关系及其反射
1、相同元素,相同维度的数组共性一个Class对象。Object[] 是Object的子类
2、数组字节码的名称,[I :[ ——>表示数组,I——>表示数组中的元素是int型。
Arrays中的list方法参数是Object[] String属于Object而基本数据类型不属于,所有可以将字符串数组转换为list集合 ,而整数会得到一个内存地址值。
六、HashSet与hashCode方法
1、Hash表数据结构存储时的特点
分段存储:根据hashcode分段存储
2、Hash表数据结结构取出的特点
(1)、改变属性值之后无法取出:例如
(2)、内存泄漏——>内存溢出:对象不用了,但仍然占用内存空间,就叫内存泄漏
(3)、
class point {
int x ;
int y ;
point(int x,int y){
this.x = x;
this.y = y;
}
}
class test{
public static void main(String[] args){
Collection c = new HashSet();
Point p1 = new Point (3,5) ;
Point p2 = new Point (3,3) ;
c.add(p1) ;
c.add(p2) ;
p1.x=7 ;
//这时,p1的hashcode改变了
c.remove(p1);
/*这里并不能将原来的p1从集合中取出。造成了内存泄露。内存泄漏达到一定程度,就会造成内存溢出。
*/
}
}
七、反射在框架中的应用
(1)、Properties是HashMap的子类,Properties集合中键和值成对存在。但比HashMap多了将文件由硬盘加载和写入(load())的功能
(2)、配置文件的设置:选中工程——>new——>File——>config.properties ——>配置文件内容
className = java.util.ArrayList//等号后面的内容是可以改变的
加载配置文件
InputStream ips = new FileInputStream(config.properties) ;
Properties props = new Properties() ;
Props.load(ips) ;
ips.close();
String className = props.getProperty(“className”) ;——>得到文件类型
利用反射创建对象
Collection collections =
(Collection)Class.forName(
className/*文件类型java.util.ArrayList */).newInstance() ;
八、用类加载器的方式管理资源和配置文件
工程做完后我们往往是把class文件打成jar包,交给客户。而不是源文件。对于配置文件,我们在开发时不能用相对路径(相对目录是相对当前目录)。
(1)、配置资源文件的绝对路径
对于文件的路径,一般要客户进行配置,然后我们的设置会读取到客户配置的路径。在javaWeb中API中,有一个getRealPath()方法,可以得到安装路径,通过安装路径使配置文件的路径得到补充。getRealPath() ; //金山词霸/内部路径。一定要记住使用完整的路径,但完整的路径不是硬编码,而是运算出来的。
(2)、加载资源文件
a、使用类加载器加载资源文件
类名.class.getClassLoader().getResourceAsStream(“包路径/文件名”);//类加载器会到客户目录下加载.class文件,也可以加载指定的其他文件
注意:Eclipse 会编译java源文件夹下的所有.java文件(保存后编译),并将源文件目录下的其他格式文件原封不动复制到客户目录
b、使用Class类内部方法加载资源
类名.class.getResourceAsStream(“文件名”);