------- android培训、java培训、期待与您交流! ----------
第一部分:反射概述以及透彻分析反射的基础Class类
1、反射:反射就是在运行状态时候,对于任意一个类(class文件),都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法称为java语言的反射机制。
动态获取类中的信息,就是java反射,可以理解为对类的解剖。
如果想要使用对指定名称的字节码文件进行加载并获取其中的内容并调用,这是就使用到了反射技术。
2、Class类
Java的类class用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,是由这个类的实例确定的
但是Java的类也属于属于同一类事物,描述这类事物的Java类名就是Class类。
字节码:字节码是Class类的世纪对象。用于描述字节码的类就是Class类。通过Class类创建对象,可以提取出字节码文件中的内容,如字段、构造函数、一般函数。
该类就可以获取字节码中的所有内容,反射就是依靠Class类来完成。想要对一个类文件进行解剖,只要获取到该类的字节码文件对象。
第二部分:字节码的获取方式
1、获取字节码有三种方式(以Data类为例):
a、类名.class
Class cls = Data.class
弊端:这种方式还需要明确用到类中的静态成员,不够扩展。
b、对象.getClass()
Class cls = new Data().getClass()
弊端:用这种方式,必须要明确具体的类,并创建对象,麻烦。
c、Class.forName("类名")
Class cls = Class.forName("java.util.Data")
优点:该方法只要有名称就可以,更为方便,扩展性更好。反射时主要用到第三种方法。
下面是代码示例
String str1 = "abc";
Class cls1 = str1.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");
2、有一些需要注意的api:
isprimitive():这个方法判断的是不是基本数据类型
System.out.println(cls1.isPrimitive());//这个方法是表示是不是基本类型,String不是基本数据类型,返回false
TYPE的使用:
System.out.println(int.class == Integer.class);//false
System.out.println(int.class == Integer.TYPE);//TYPE代表Integer包装的基本类型字节码,返回true
判断是否是数组类型:
System.out.println(int[].class.isArray());//判断是否是数组类型Class.isArray() 返回true
第三部分:构造方法的反射应用
1、Constructor:通过Constructor可以获取字节码里的构造方法。
2、如何通过反射来获取构造方法。
关键api:
getConstructor(Class cls):得到一个构造方法。
newInstance():这个方法内部会先得到默认的构造方法,然后用该构造方法创建实例对象。
步骤(String为例):
a、先获取一个构造方法,构造方法使用的参数是一个字节码对象
Constructor constructor = String.class.getConstructor(StringBuffer.class);
//StringBuffer 表示获得String的构造方法时要用到的类型
b、创建实例对象
String str = (String)constructor.newInstance(new StringBuffer("abc"));
3、下面是使用反射的方式实现new String(new StringBuffer("abc"));
class
{
public static void main(String[] args)
{
String.class.getConstructor(StringBuffer.class,int.class);//这个表示获取接收两个参数的构造方法
//使用反射的方式实现new String(new StringBuffer("abc"));
Constructor constructor1 = String.class.getConstructor(StringBuffer.class);//String.class的字节码是constructor1
String str = (String)constructor1.newInstance(new StringBuffer("abc"));
//第一个StringBuffer表示选择使用哪个参数的构造方法,第二个用这个构造方法时还得传StringBuffer的对象进去
System.out.println(str.charAt(2));
}
}
第四部分:成员变量的反射
1、Field:Field类代表某一个类中的成员变量关键api:
getField("变量名"):获取一个类中的公有成员变量,私有的获取不到。返回一个Field类对象。
setAccessible(true):参数为true,可以针对私有成员进行暴力访问。
getDeclaredField("变量名"):可以获取到私有和公有的成员变量。
set(Object obj,value):设置value到对象中,相当于给成员变量赋值
2、需求:通过反射方法,获取自定义的类中公有和私有的成员变量。
class
{
public static void main(String[] args)
{
ReflectDemo pt1 = new ReflectDemo(3,5);
Field fieldY = pt1.getClass().getField("y");
//fieldY代表类字节码上的变量,没有对应到对象上。fieldY不是对象身上的变量,
//而是类上的变量,要用它去取某个对象上的对应的值
System.out.println(fieldY.get(pt1));
Field fieldX = pt1.getClass().getDeclaredField("x");
fieldX.setAccessible(true);//这个叫做暴力反射,对于private修饰符
System.out.println(fieldX.get(pt1));
}
}
public class ReflectDemo {
private int x;
public int y;
public ReflectDemo(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
3、综合案例:将任意一个对象中的所有String类型的成员变量所对应字符串内容中的“b”改成“a”
class
{
public static void main(String[] args)
{
ReflectDemo pt1 = new ReflectDemo(3,5);
changeStringValue(pt1);
System.out.println(pt1);
}
private static void changeStringValue(Object obj) throws Exception {
Field[] fields = obj.getClass().getFields();//obj.getClass()返回的是类的字节码
for(Field field : fields)
{
if(field.getType() == (String.class))//对字节码的比较应该用==
{
String oldValue = (String)field.get(obj);
String newValue = oldValue.replace("b", "a");
field.set(obj, newValue);
}
}
}
}
public class ReflectDemo {
private int x;
public int y;
public String str1 = "ball";
public String str2 = "basketball";
public String str3 = "iasdf";
public ReflectDemo(int x, int y) {
super();
this.x = x;
this.y = y;
}
public String toString()
{
return str1+":"+str2+":"+str3;
}
}
第五部分:成员函数的反射
1、Method类:代表某个类中的一个成员方法。
str1.charAt
str2.charAt
以上说明,方法与对象是没有关系的,它是属于类(Method)的。
2、关键api
getMethod("参数名",数据类型.class):获取方法。返回Method类对象。
invoke():对获取到的方法进行调用
3、步骤:
a、得到类中的一个方法:
Method methodCharAt = String.class.getMethod("charAt", int.class);
b、调用方法:
methodCharAt.invoke(str, 1)
普通方式调用方法是:
str.charAt[1]
4、使用反射方法实现charAt[1]的示例:
class
{
public static void main(String[] args)
{
//str1.charAt(1)
String str1 = "abc";
Method methodCharAt = String.class.getMethod("charAt", int.class);
//第二个参数parameterTypes表示使用哪一种类型参数的重载函数
System.out.println(methodCharAt.invoke(str1, 1));//invoke方法表示调用,1代表有一个参数
//按照jdk1.4来调用,new Object[]{2}表示将2进行装箱,类型是Integer,代表有int类型2
System.out.println(methodCharAt.invoke(str1, new Object[]{2}));
}
}
注:如果传递给Method对象的invoke方法的第一个参数是null,说明Method对象对应的是一个静态方法。
5、需求:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法
问题:当启动java程序的main方法的参数是一个字符数组。如何为invoke方法传递参数呢?按照1.5的语法,整个数组是一个参数,
而按照1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给jdk1.4的语法,
因为1.5要兼容1.4,会按1.4的语法进行处理,即把数组打散成若干个独立参数。
所以再给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{"xxx"}),因为会出现参数类型不对。
代码示例:
class
{
public static void main(String[] args)
{
//用静态的方法调用main方法
//TestArguments.main(new String[]{"111","123"});
//用反射的方式调用
String startingClassName = args[0];//这里实际传入的参数是cn.javatest.demo.TestArguments
Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);
//两种解决方法
mainMethod.invoke(null,new Object[]{new String[]{"111","123"}});
mainMethod.invoke(null,(Object)new String[]{"111","123"});
}
}
class TestArguments
{
public static void main(String[] args)
{
for(String arg : args)
{
System.out.println(arg);
}
}
}
第六部分:数组与Object的关系及其反射关系
数组:
1 具有相同元素类型和相同维度的数组属于同一种类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperclass方法返回的父类为Object类对应的Class
2 基本数据类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用。
非基本数据类型(String)的一维数组,既可以当作Object类型使用,也可以当作Object[]类型使用
3 Arrays.asList()方法处理int[]和String[]时的差异
4 没有办法统一得到数组里元素类型的办法
例如
Object[] a = new Object[]{"a",1}
//只能得到具体某个元素类
a[0].getClass().getName()
5、代码演示实例
class
{
public static void main(String[] args)
{
int[] a1 = new int[]{1,2,3};
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[]{"a","b","c"};
System.out.println(a1.getClass() == a2.getClass());//返回true
System.out.println(a1.getClass().getName());//[I [表示数组 I表示整数
System.out.println(a1.getClass().getSuperclass().getName());//获取父类名字
System.out.println(a4.getClass().getSuperclass().getName());
Object aObj1 = a1;
Object aObj2 = a4;
//Object[] aObj3 = a1;一位数组是不能转成Object数组的,因为Object里面装的是int
Object[] aObj4 = a3;
Object[] aObj5 = a4;
System.out.println(Arrays.asList(a1));//输出[[I@7852e922]
System.out.println(Arrays.asList(a4));//输出[a, b, c]
}
}
6、数组的反射应用:输出字符串数组{“a”,"b","c"}
class
{
public static void main(String[] args)
{
String[] a4 = new String[]{"a","b","c"};
printObject(a4);//String[]数组会被拆包,里面是单独的Object
printObject("xyz");
}
private static void printObject(Object obj) {
Class cls = obj.getClass();
if(cls.isArray())
{
int len = Array.getLength(obj);
for(int i = 0;i<len;i++)
{
System.out.println(Array.get(obj, i));
}
}else{
System.out.println(obj);
}
}
}
第七部分:框架的概念及用反射技术开发框架的原理
1、反射的作用:实现框架功能框架和工具类有区别:工具类是被用户的类调用,而框架则是调用用户提供的类
因为在写程序时无法知道要被调用的类名,所以在程序中无法直接new某个类的实例对象,而要用反射方式做
2、需求:用配置文件加反射的方式创建ArrayList和HashSet的实例对象。
public class ReflectTest2 {
public static void main(String[] args)throws Exception {
InputStream ips = new FileInputStream("config.properties");
Properties props = new Properties();
props.load(ips);
ips.close();
String className = props.getProperty("className");
Collection collection = (Collection)Class.forName(className).newInstance();
ReflectDemo pt1 = new ReflectDemo(3,3);
ReflectDemo pt2 = new ReflectDemo(5,5);
ReflectDemo pt3 = new ReflectDemo(3,3);
collection.add(pt1);
collection.add(pt2);
collection.add(pt3);
collection.add(pt1);
pt1.y = 7;
collection.remove(pt1);//修改了y后,原pt1的哈希值修改了,
//则原来的pt1还在内存中。这个就叫内存泄漏
System.out.println(collection.size());
}
}
在配置文件中