反射的基石:Class类
Class类概述:
Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。 Class类中提供了大量操作字节码文件的方法。Class类代表Java类,它的各个实例对象又分别对应对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型就是Class类型
我的理解:其实Class类就是用来操作.class文件的,将.class文件封装成对象,使用Class类中的方法进行操作
获取字节码对应的实例对象的三种方法:
1.类名.class,例如,System.class 通常用于获取已知类或者类型的.class文件2.对象.getClass(),例如,new Date().getClass() 通常用于获取自定义对象的.class文件
|--java.lang.Object|--class<?> getClass() 返回object的运行时类
3.Class.forName("类名"),例如,Class.forName("java.util.Date") 通常用于获取API文档中的.class文件|--java.lang.Class|--static Class<?> forName(String className) 返回与带有给定字符串名的类或接口相关联的 Class 对象。
九个预定义Class实例对象:
分别是:
分别是:
8个基本数据 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
他们的内部都封装了一个TYPE字段,这九个类的包装类对象调用TYPE字段后都会返回一个Class类
他们的内部都封装了一个TYPE字段,这九个类的包装类对象调用TYPE字段后都会返回一个Class类
static Class<Byte> TYPE
表示基本类型 byte 的 Class 实例。
表示基本类型 byte 的 Class 实例。
代码示例:
package com.learn;
public class ReflectionDemo
{
/**
* @param args
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws ClassNotFoundException
{
// TODO Auto-generated method stub
//获取字节码文件的第一种方法
Class cls1 = Thread.class;
//获取字节码文件的第二种方法
Class cls2 = new Thread().getClass();
//获取字节码文件的第三种方法
Class cls3 = Class.forName("java.lang.Thread");
//判断他们是否是同一个字节码文件
System.out.println(cls1 == cls2);
System.out.println(cls2 ==cls1);
//判断该字节码文件是否是基本数据类型的字节码文件
System.out.println(long.class.isPrimitive());
//判断是否是同一个字节码文件
System.out.println(int.class == Integer.TYPE);
//返回值为false
System.out.println(int.class == Integer.class);
//void是9个预定义实例对象中的其中一个
System.out.println(void.class.isPrimitive());
//判断字节码文件是否为数组类型
System.out.println(int[].class.isArray());
}
}
总结:
只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void…
反射
概念:
反射就是把Java类中的各种成分映射成相应的java类。
概述:
一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Constructor、Package等等。
继承体系java.lang.reflect
|--java.lang.reflect.AccessibleObject
|--java.lang.reflect.Field|--java.lang.reflect.Method|--java.lang.reflect.Constructor
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象,对这些实例对象的操作是学习重点
java.lang.reflect.Contructor 用于操作字节码文件中构造函数的类
得到某个类所有的构造方法:
例子:Constructor [] constructors= Class.forName("java.lang.String").getConstructors();
得到某一个构造方法:
例子:Constructor [] constructors= Class.forName("java.lang.String").getConstructors();
得到某一个构造方法:
Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
//获得方法时要用到类型
创建实例对象:
1.java.lang.reflect.Constructor中的 newInstance()方法通常方式:String str = new String(new StringBuffer("abc"));反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc"));//调用获得的方法时要用到上面相同类型的实例对象
2.java.lang.Class中的newInstance()方法Class.newInstance()方法:例子:String obj = (String)Class.forName("java.lang.String").newInstance();该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。
代码示例:
package com.learn;
import java.lang.reflect.Constructor;
public class ReflectDemo2
{
/**
* @param args
* @throws Exception
* @throws SecurityException
*/
public static void main(String[] args) throws SecurityException, Exception
{
// TODO Auto-generated method stub
//遍历Thread类中的所有构造函数
//获取类中的所有构造方法,存入集合中
Constructor[] constructors = Class.forName("java.lang.Thread").getConstructors();
for(Constructor constructor : constructors)
{
System.out.println(constructor);
}
//获取一个指定参数类型的构造函数
//方法一:costructor.newInstance(Type ... args);
Constructor constructor1 = Class.forName("java.lang.String").getConstructor();
String str = (String)constructor1.newInstance();
System.out.println(str.length());
//方法二:Class.newInstance();
String str1 = (String)Class.forName("java.lang.String").newInstance();
System.out.println(str.length());
}
}
总结:
异常会发生在两个状态下1.编译时,JVM编译源文件时,检查等号左边的变量定义2.运行时,执行文件时,检查等号右边的部分
java.lang.reflect.Field
用于操作字节码文件中的定义的变量的类
Field类的特点:
当我们获取指定对象中的Field类时,如果想操作其中的一个Field或者一类Field,那么要指定这个Field所属的对象
代码示例:
package com.learn;
import java.lang.reflect.Field;
public class ReflectDemo3
{
/**
* @param args
* @throws Exception
*
*/
public static void main(String[] args) throws Exception
{
Demo3 d = new Demo3(6,5);
//获取私有的成员变量
Field fieldX = Demo3.class.getDeclaredField("x");
//暴力反射,强制获取私有的成员变量
fieldX.setAccessible(true);
System.out.println(fieldX.get(d));
Field fieldY = Demo3.class.getField("y");
System.out.println(fieldX.get(d));
}
}
public class Demo3
{
private int x = 9;
public int y = 9;
public Demo3(int x, int y)
{
super();
this.x = x;
this.y = y;
}
}
总结:
getField(String str)方法只可以获取字节码中权限修饰符为public的field;
暴力反射:
程序中隐藏的field,通常是不想让别人使用的,才会如此定义。此时我们如果想获取隐藏的field,需使用getDeclaredField()方法获取。然后对此field进行暴力反射:setAccessible(true),即可使用get方法获取此field的值
练习:将字符串中的字符‘b’换成'a'
package com.learn;
import java.lang.reflect.Field;
public class FieldTest
{
/**
* @param args
* @param Demo
*/
public static void main(String[] args) throws Exception
{
// 创建Demo1类的对象
Demo1 d = new Demo1("abc","aabb","cctv");
//将对象传入替换字符的方法中
replaceChar(d);
System.out.println(d);
}
private static void replaceChar(Object obj) throws Exception
{
//获取对象中所有的Field
Field[] fields = obj.getClass().getFields();
//遍历集合中的field
for(Field field : fields)
{
//判断该field是否是String类型
if(field.getType() == String.class)
{
//获取field在指定对象中所对应的字段
String oldstr = (String) field.get(obj);
//替换字段中的字符
String newstr = oldstr.replace('b','a');
//将新的字段添加进对象对应的字段中
field.set(obj, newstr);
}
}
}
}
public class Demo1
{
public String str1 ;
public String str2 ;
public String str3 ;
public Demo1(String str1, String str2, String str3)
{
super();
this.str1 = str1;
this.str2 = str2;
this.str3 = str3;
}
public String toString()
{
return str1+"::"+str2+"::"+str3;
}
}
程序中用到的方法:
java.lang.Class
|--Field getField(String name):获取字节码文件中包含指定未私有变量的Field对象(只能获取未被隐藏的field)|--Field[] getFields():获取字节码文件中所有的Field对象|--Field getDeclaredField:获取字节码文件中变量的Field对象(无论是否隐藏都可以获取,当获取的field是隐藏成员变量时,需进行暴力反射)|--Field[] getDeclaredFields:获取字节码文件中的所有变量
java.lang.reflect.Field
|--Object get(Object obj):返回指定对象上字段的值|--Class getType():返回Field对象中field的数据类型|--void set(Object obj, Object value) :将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
java.lang.reflect
|--void AccessibleObject(boolean flag):对隐藏的field进行暴力反射
java.lang.reflect.Method 用于操作字节码文件中的定义的方法的类
获取类中的方法:
Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1));
注意:
Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1));
注意:
如果传递给Method对象的invoke()方法的第一个参数为null,说明该Method对象对应的是一个静态方法!
代码示例:
package com.learn;
import java.lang.reflect.Method;
public class ReflctMethod
{
public static void main(String[] args) throws Exception
{
Demo1 d = new Demo1("qwer","tyui","asdf");
Method toString = Demo1.class.getMethod("toString");//空参数的方法,只要传入方法名,没参数就不用传入参数类型
System.out.println(toString.invoke(d));//第一个传入的Object是调用方法的对象,第二个传入的是具体的参数,没有就不传
}
}
public class Demo1
{
public String str1 ;
public String str2 ;
public String str3 ;
public Demo1(String str1, String str2, String str3)
{
super();
this.str1 = str1;
this.str2 = str2;
this.str3 = str3;
}
public String toString()
{
return str1+"::"+str2+"::"+str3;
}
}
总结:
当获取的方法是一个空参数的方法时,getMethod(String name,Class DataType)方法的第二个参数不用传入,在调用获取的方法时,invoke(Object obj,Object... args)第二个参数不用传值,也不能写为null。
jdk1.4和jdk1.5的invoke方法的区别:
Jdk1.5:public Object invoke(Object obj,Object... args)
Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。
Jdk1.5:public Object invoke(Object obj,Object... args)
Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。
jdk 1.4版
package com.learn;
import java.lang.reflect.Method;
public class ReflctMethod
{
public static void main(String[] args) throws Exception
{
Demo1 d = new Demo1("qwer","tyui","asdf");
//如果有参数,那么此时的参数类型的字节码文件是Object[].class
Method toString = Demo1.class.getMethod("toString");
//空参数的方法,所以数组中的参数列表中没有元素
System.out.println(toString.invoke(d,new Object[]{}));
}
}
public class Demo1
{
public String str1 ;
public String str2 ;
public String str3 ;
public Demo1(String str1, String str2, String str3)
{
super();
this.str1 = str1;
this.str2 = str2;
this.str3 = str3;
}
public String toString()
{
return str1+"::"+str2+"::"+str3;
}
}
用反射方式执行某个类中的main方法
程序目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
回顾主函数:
main方法的格式是固定的,public static void main(String[] args) 注意:JDK1.5后出现的可变参数:String[]... args从主函数格式中我们知道函数名:main参数类型:String[]参数个数:0长度:0
package com.learn;
import java.lang.reflect.Method;
public class ReflectMain
{
public static void main(String[] args) throws Exception
{
String str = args[0];
Method mainMethod = Class.forName(str).getMethod("main", String[].class);
mainMethod.invoke(null, (Object)new String[]{});
}
}
运行以上程序发现,将会报角标越界异常
这是因为我们没有向数组中传入参数
解决方法:
Run As--> 点击Run Configurations -->点击Arguments选项卡-->输入需要执行的main方法所在类全名即可
此时String[]数组的长度变为1,args[0] = 传入的类全名
以上程序其实就是:Method mainMethod = Class.forName(类全名).getMethod("main",String[].class);
以上程序其实就是:Method mainMethod = Class.forName(类全名).getMethod("main",String[].class);
传入参数后,还会报非法参数异常(IllegalArgumentsException)
原因:
启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。
解决办法:
mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});mainMethod.invoke(null,(Object)new String[]{"xxx"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会将数组打散成若干个参数了
数组的反射
数组的class实例对象的特点:
基本数据类型数组和非基本数据类型的区别1.具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。2.代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
Arrays.asList()方法处理int[]和String[]时的差异。
示例:int[] arr = new int[]{1,2,3};String[] arr1 = new String[]{"a","b","c"};Object obj = Arrays.asList(arr);Object obj1 = Arrays.asList(arr1);System.out.println(arr);System.out.println(arr1);
输出结果:[[I@b6e39f] 因为基本类型的一维数组是Object类型,所以asList()方法将arr数组当做一个对象处理[a, b, c] 而String类型的数组即可作为Object类型也可当做Object[]类型使用,String内部的元素都是对象,输出的是对象的集合
Array工具类用于完成对数组的反射操作。
package com.learn;
import java.lang.reflect.Array;
public class ReflectArray
{
/**
* @param args
*/
public static void main(String[] args)
{
// TODO Auto-generated method stub
String[] str = new String[]{"a","b","c"};
PrintObject(str);
}
private static void PrintObject(Object obj)
{
// 有两种情况,一种是obj是数组类型,一种就是普通对象
Class cls = obj.getClass();
if(cls.isArray())
{
for(int x = 0;x<Array.getLength(obj);x++)
{
System.out.println(Array.get(obj, x));
}
}else
{
System.out.println(obj);
}
}
}
用到的的方法
java.lang.Class
|--isArray():判断Class对象是否是数组类型
java.lang.reflect.Array
|--static int getLength(Object Array):返回指定数组的长度
|--static object get(Object array,int index):返回指定数组角标为的值
ArrayList和HashCode的比较及HashCode分析
实例分析:
ReflectPoint类
package com.learn;
public class ReflectPoint
{
private int x;
private int y;
public ReflectPoint(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;
ReflectPoint other = (ReflectPoint) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}
测试代码:
package com.learn;
import java.util.ArrayList;
import java.util.Collection;
public class ReflectTest
{
/**
* @param args
*/
public static void main(String[] args)
{
// TODO Auto-generated method stub
Collection<ReflectPoint> list = new ArrayList<ReflectPoint>();
ReflectPoint rp = new ReflectPoint(1,2);
ReflectPoint rp1 = new ReflectPoint(2,3);
ReflectPoint rp2 = new ReflectPoint(3,4);
ReflectPoint rp3 = new ReflectPoint(3,4);
list.add(rp);
list.add(rp1);
list.add(rp2);
list.add(rp3);
System.out.println(list.size());
}
}
如果将ArrayList集合改为HashSet集合,因为HashSet集合存取时无序且不可重复,他们通过hashCode()和equals()方法来保证元素的唯一性,先比较对象的hash值,然后用equals()方法判断对象中的内容。最后的结果为3
注意:
当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值也会改变,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。ArrayList集合没有这方面的问题
框架的概念及用反射技术开发框架的原理
框架与框架要解决的核心问题
我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
框架要解决的核心问题
模拟框架示例:我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以 后写的类(门窗)呢? 因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接 new 某个类的实 例对象了,而要用反射方式来做。
配置文件config.properties中的内容:
classname = java.util.ArrayList
package com.learn;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;
public class ReflectTest2
{
/**
* @param args
* 创建一个加载任意集合的框架
*/
public static void main(String[] args) throws Exception
{
//获取文件
InputStream is = new FileInputStream("config.properties");
//创建空列表对象
Properties p = new Properties();
//加载配置文件
p.load(is);
//关闭流资源
is.close();
//获取键对应的值
String classname = p.getProperty("classname");
//使用反射创建配置文件中要建立的对象
Collection collection = (Collection)Class.forName(classname).newInstance();
collection.add(new ReflectPoint(1,2));
collection.add(new ReflectPoint(1,2));
collection.add(new ReflectPoint(1,2));
collection.add(new ReflectPoint(1,2));
System.out.println(collection.size());
}
}
加载配置文件的三种方式:
1,采用类加载器进行加载,使用相对路径的方式
InputStream is=ReflectTest.class.getClassLoader(). getResourceAsStream("com/itheima/day01/config.properties");
2,利用Class方式进行加载,使用相对路径的方式
InputStream is = ReflectTest.class.getResourceAsStream("config.properties");
3,利用Class方式进行加载,使用绝对路径的方式
InputStream is = ReflectTest.class.getResourceAsStream("com/itheima/day01/config.properties");
InputStream is=ReflectTest.class.getClassLoader(). getResourceAsStream("com/itheima/day01/config.properties");
2,利用Class方式进行加载,使用相对路径的方式
InputStream is = ReflectTest.class.getResourceAsStream("config.properties");
3,利用Class方式进行加载,使用绝对路径的方式
InputStream is = ReflectTest.class.getResourceAsStream("com/itheima/day01/config.properties");