——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
反射的基石 Class类
Java程序中的各个Java类,它们都属于同一类事物,比如每一个类中有构造方法,所属的包,方法,类名等属性,
既然是同一种事物,那么就可以对它们进行描述,封装成类,Class类就是这个描述各种Java类的类。
比如每一个类中有构造方法,所属的包,方法,类名等属性
Person类描述人 实例对象:张三,李四,奥巴马,拿破仑等等
Class类描述java中的类 实例对象:String.class,Integer.class,Person.class等等
Class类的实例对象代表内存中的字节码。 类名.class eg:String.class
获取字节码的三种方式:
1.Class.forName("类名") //反射的时候最经常用到
eg:Class.forName("java.lang.String")
该方法捕获ClassNoFoundException异常
面试题:Class.forName的作用?
作用就是返回类字节码,如果这个类的字节码已经存在了内存当中,就直接获取
如果没存在于内存当中,该方法会使用类加载器去加载该类,加载进内存以后将该类的字节码以后返回。
2.类名.class
eg:System.class String.class
3.对象.getClass()
eg:new Date().getClass
8个基本数据类型和void分别对应了9个预定义的Class对象
int.class==Integer.TYPE
void.class==Void.TYPE
double.class==Double.TYPE
数组类型的Class实例对象,使用Class类中的isArray()判断
总之,只要在源程序中出现的类型,都有各自的Class实例对象。eg:int[],void
可以使用Class类中的isPrimitive()方法判断该类是否是基本数据类型
字节码之间比较使用==
反射: 反射就是把Java类中的各种成分映射成相应的java类。
一个java类用一个Class类的对象来表示,
一个类的组成部分有:成员变量,构造方法,方法,包等等信息也用一个个java类来表示。
Class类中提用了一系列方法来获取其中的成分。
几个重要的成分对应的类如下:
Field 成员变量
Method 方法
Contructor 构造方法
Package 包
通过调用Class类中的方法得到这些实例对象后有什么用?该怎么用?这就是学习反射的要点。
使用反射创建实例对象:Constructor类
Constructor类代表某一个类中的一个构造方法
Constructor[] getConstructors()
通过Class类中的方法获取一个类的所有的构造方法
eg:Constructor[] cons=
Class.forName("java.lang.String").getConstructors();
Constructor getConstructor(Class<?>... parameterTypes)
通过Class类中的方法获取一个类指定参数的构造方法
eg:Constructor con=
Class.forName("java.lang.String").getConstructor(StringBuffer.class);
使用Constructor中的方法创建实例对象:
T newInstance(Object... initargs)
eg:String str=(String)con.newInstance(new StringBuffer("abc");)
int getModifiers() 获取构造方法的修饰符 默认、public、private
Class<T> getDeclaringClass() 得到该够造方法所属的类的字节码
/*
使用Constructor创建一个String对象
*/
public static void main(String[] args) throws Exception
{
Constructor con=Class.forName("java.lang.String").getConstructor(StringBuffer.class);
String str=(String)con.newInstance(new StringBuffer("abc"));
System.out.println(str);
}
在Class类中也提供了创建实例对象的方法:
T newInstance()
该方法内部先得到默认的构造方法,然后用该默认构造方法创建示例对象。
底层使用缓存机制,得到构造方法时,先将其示例对象缓存起来,下次再调用就不需要再加载了。
体现出反射比较消耗性能,降低程序的效率。
成员变量的反射:
1.得到Field对象
Field getField(公开的成员变量名)
2.通过对象名得到私有变量
Object get(Object obj)
Class类体提供了得到私有成员变量的方法
Field getDeclaredField(String name)
暴力反射:使用反射对对象中的私有变量进行操作
步骤:
1.通过Class类的getDeclareadField()返回一个Field类对象
Field getDeclareadField(String name);
2.将Field类对象设置为可访问
boolean setAccessible(true);
3.通过对象名得到私有变量
Object get(Object obj)
成员变量的反射简单演示:
import java.lang.reflect.*;
/*
这个类下面也会用到
*/
class ReflectPoint
{ public String s1="absbfgrbbb";
public String s2="basketball";
public String s3="haha";
private int x;
public int y;
ReflectPoint (int x,int y)
{
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;
}
public String toString()
{
return new String("s1:"+s1+"s2:"+s2+"s3:"+s3+" "+x+" "+y);
}
}
class FieldReflect
{
/*
成员变量的反射
*/
public static void main(String[] args) throws Exception
{
ReflectPoint rf=new ReflectPoint(3,5);
Field fieldY=rf.getClass().getField("y");//不能获取私有变量
//fieldY 不代表一个具体的值,只代表一个变量
//fieldY不是对象身上的变量,而是类上。要用它去取某个对象上对饮的值
System.out.println(fieldY.get(rf));
Field fieldX=rf.getClass().getDeclaredField("x"); //无论私有或者非私有都能获取(暴力反射)
//fieldY 不代表一个具体的值,只代表一个变量
//fieldY不是对象身上的变量,而是类上。要用它去取某个对象上对饮的值
fieldX.setAccessible(true);//设置为可以访问
System.out.println(fieldX.get(rf));
}
}
成员变量反射的一个小综合应用:
/*
练习:将任意一个对象中的所有String类型的成员变量
所对应的字符串内容中的"b"改为"a"
思路:1.得到Field数组
2.对数组进行遍历,判断成员变量类型是否是String.class
3.如果是String.class,就获取值,然后替换,设置
*/
public static Object method_2(Object obj)throws Exception
{
//1.得到Field数组
Field[] fields=obj.getClass().getFields();
// 2.对数组进行遍历,判断成员变量类型是否是String.class
for (Field field: fields)
{
//3.如果是String.class,就获取值,然后替换,设置
//字节码比较使用==比
if(field.getType()==(String.class))
{
String oldValue=(String)field.get(obj);
String newValue=oldValue.replace('b','a');
//设置新值
field.set(obj,newValue);
}
}
return obj;
}
成员方法的反射 Method类
1.得到类中的某一个方法
Method getMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Method[] getMethods()
返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。
eg:Method charAt=Class.forName("java.lang.String").getMethod("charAt",int.class);
2.调用方法
Object invoke(Object obj, Object... args)
对带有指定参数的指定对象调用由此 Method 对象表示的底层方法
eg:char ch=charAt.invoke(str,1)
如果Method对象的指定对象参数为null,说明该Method对应的方法是静态方法。
如果Method对应的方法是无参的,可以将参数列表传递null
jdk1.4和1.5的invoke方法的区别:
jdk1.5: public Object invoke(Object obj, Object… args)
jdk1.4:public Object invoke(Object obj, Object[] args)
按照jdk1.4的语法
charAt.invoke(str,1)方法就该写成charAt.invoke(str,new Object[]{1})
如果某个方法中参数是数组又该怎么传递呢?
比如某个程序的main方法参数列表就是数组
通过Method mainMethod=Class.forName(ClassName).getMethod("main",String[].class);
获取Method后首先想到的两种传递方式是下面这两种:
mainMethod.invoke(null,"hahh","hihi","gaoxing");
mainMethod.invoke(null,new String[]{"hahh","hihi","gaoxing"});
这里如果这样传递,编译器会按照jdk1.4的语法将数组拆分,去匹配三个String参数的方法的,造成参数错误
可以通过下面两种方式解决:
mainMethod.invoke(null,new Object[]{new String[]{"hahh","hihi","gaoxing"}});
mainMethod.invoke(null,(Object)new String[]{"hahh","hihi","gaoxing"});
数组的反射:
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象
eg: int[] a1=new int[3];
int[] a2=new int[4];
a1.getClass()==a2.getClass(); //true
代表数组的Class实例对象getSuperClass()方法返回的父类为Object对应的Class
基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用。
非基本类型的一维数组既可以当做Object类型使用,也可以当做Object[]类型使用。
Object obj=new int[3];
Object[] obj=new int[3];//错误
Object obj=new String[]{"a","b"."c"};
Object[] obj=new String[]{"a","b","c"};
为什么Arrays工具类中的asList方法,在转换时,如果传递的是基本数据类型,则会把数组转换成集合中的一个元素,而如果传递的是非基本类型数组,则会把数组中的多个元素转换为集合中的多个元素?
因为asList方法
public static <T> List<T> asList(T... a) 方法1
在jdk1.5版本以前是
public static list asList(Object[] obj) 方法2
当传入的参数是非基本类型的数组时,按照方法2进行处理,
而传入的参数是基本数据类型时,由于基本数据类型不属于Object[]类型,
所以虚拟机把这个数组作为方法1可变数组数组中只有1个元素的情况处理,
所以基本数据类型的数组被asList方法转换为了集合中的一个元素。
Array类用于完成数组的反射操作
数组反射的应用:
怎样的到数组中元素的类型?
arg[0].getClass().getName();
数组反射练习:
//提供一个打印方法,如果是数组,就把数组中的各个元素打印出来。
public static void printObject(Object obj)
{
Class clazz=obj.getClass();
if(clazz.isArray())
{
int len=Array.getLength(obj);
for(i=0;i<len;i++)
{
System.out.println(Array.get(obj),i);
}
}
else
{
System.out.println(obj);
}
}
两个面试题:
hashCode的作用:
想知道hashCode的作用,要先了解哈希表算法,这种算法将集合分成若干个区域,每个对象可以计算出一个哈希值。将哈希值分组,每组对应某个区域,根据对象的哈希值,就可以知道对象在那个区域。hashCode返回的就是对象的哈希值。
内存泄露案例:
当一个对象被存储进了哈希表数据结构的集合中以后,就不能修改这个对象中参与计算哈希值的字段了,否则对象修改后的哈希值与最初存储进哈希表数据结构集合中时的哈希值就不同了,这种情况下,即时使用contains该对象的当前引用作为参数去哈希表数据结构集合中检索对象,也将返回找不到对象的结果,这就导致无法从哈希表数据结构集合中单独删除当前对象,造成内存泄露。
个人对hashSet中修改了计算哈希值的字段造成不能删除检索的理解:
集合中存放的是对象的地址,hashSet中对象的地址是根据对象的哈希值分配的, 比如对象被存入到hashSet中,存入到了0x0023这个地址,此时改变改变参与运算 hashcode值的变量后,hashCode值发生变化,hashSet根据这个值分配的内存地址可能是0x0088,但是对象此刻存的位置却是0x0023,当调用remove,contains方法时,都走的是hashCode方法,得到的地址是0x0088,hashSet中这个位置没有对应的对象,那么就返回false,操作失败。但是这个对象还是在hashSet中的,所以遍历的到。
/*
内存泄露演示
*/
public static void method()
{
Collection c=new HashSet();
ReflectPoint rf=new ReflectPoint(5,8);
c.add(rf);
rf.y=9;
System.out.println(c.remove(rf));//false
System.out.println(c.size());
System.out.println(c.contains(rf));//false
for (Object obj: c)
{
System.out.println(obj);
}
System.out.println(rf);
}
使用反射的一个小框架:
/*
从配置文件中读取类名,创建对象
*/
public static void main(String[] args) throws Exception
{
//method();
InputStream in=new FileInputStream("config.properties");
Properties prop=new Properties();
prop.load(in);
in.close();
String className=prop.getProperty("className");
Class clazz=Class.forName(className);
//Constructor cons=clazz.getConstructor();
Collection c=(Collection)clazz.newInstance();
c.add(new ReflectPoint(3,4));
c.add(new ReflectPoint(54,23));
c.add(new ReflectPoint(3,7));
c.add(new ReflectPoint(2,343));
for (Object obj: c)
{
System.out.println(obj);
}
}