----------- android培训、java培训、期待与您交流! ------------
理解Class类
类是用于描述一类事物,而Class类用于描述java类,它对应着java的字节码。
Class类没有构造器,那么如何获取Class对象呢?
获取Class对象的三种方式:
类名.class
Class.forName("类名");
对象名.getClass()
前两种都是通过指定类名来获取对应的Class对象
第三种是获取某一对象所对应的Class类,即创建这个对象的字节码。
前两种的区别:
第一种适合已知要获取Class对象的类的名称,在程序中直接写死了。
第二种适合运行时还未知类名的状况,一般在方法参数中传递一个变量。
注意1:同一个类对应的Class对象是唯一的,即相当于是单例,因此可以用==来比较。
注意2:java中的每个类都有所对应的Class对象,而基本数据类型也有对象的Class对象,void也具有Class对象。
int.class
void.class
示例代码:
//以下代码说明,不管用什么方式,操作的都是同一份字节码,说明一个java类在内存中只存在一份字节码
String s="abc";
Class cls1=String.class;
Class cls2=s.getClass();
Class cls3=Class.forName("java.lang.String");
System.out.println(cls1==cls2);//true
System.out.println(cls1==cls3);//true
//判断字节码是否为原始类型:isPrimitive()
System.out.println(cls1.isPrimitive());//false
System.out.println(int.class.isPrimitive());//true
System.out.println(int.class==Integer.class);//false
//Integer里面的常量TYPE,代表包装类型所包装基本类型的字节码
System.out.println(int.class==Integer.TYPE);//true
//任何类型都可以用class来表示,因为任何类型在内存里面都是一份字节码
System.out.println(int[].class.isPrimitive());//false
//判断一个类型【Class】是不是数组用isArray方法
System.out.println(int[].class.isArray());//true
注:
使用Class对象的isPrimitive()方法可以判断此class对应的是不是基本类型。(包括void.class)
Integer.TYPE此静态字段相当于int.class
数组也可以返回class实例对象,如int[].class
可以使用Class.isArray()来判断是否是数组
反射:
反射就是把Java类中的各种成分映射成相应的Java类。
通过反射来获取构造器并创建实例对象:
Constructor<String> con =String.class.getConstructor(StringBuffer.class);
String s = con.newInstance(newStringBuffer(“abc”));
获取实例对象的另一种简单方法:
String s = (String)String.class.newInstance();
此方法直接使用无参构造器来获取实例对象。
注意:
1. 调用Constructor对象的getName方法,返回的是不带参数的方法名,即类名。
2. Constructor是可以加泛型的,如果不加泛型,则创建的实例对象会向上转型为Object,在使用时需要强制类型转换。
3. 反射会导致程序性能下降
通过反射来获取成员属性:
Field类代表某个类中的一个成员变量
产生构造方法快捷键:ctrl+shift+s选择Generate Constructor using Fields 或者
右键--source--Generate Constructor using Fields
public class RefletPoint{
private intx;
public inty;
public RefletPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
Filed类是字节码里面的变量,不代表某个对象身上的变量,知道有y的变量以后,在各个对象上去对象里的y
RefletPointpt1=new RefletPoint(7, 5);
FieldfiledY=pt1.getClass().getField("y");
//filedY的值是多少?5!错!
//filedY不是对象身上的变量,而是类字节码上的变量,要用它去取某个对象上对应的值
System.out.println(filedY.get(pt1));//5
FieldfiledX=pt1.getClass().getDeclaredField("x");
filedX.setAccessible(true);//强制设置私有的属性可以访问
System.out.println(filedX.get(pt1));//7
实例:将任意一个对象中所有String类型的成员变量所对应的字符串内容中的"b"改成"a"
思路:扫描给定对象上所有Strng类型的成员变量,然后找到字符串上要替换的字符并换掉
代码如下:
import java.lang.reflect.Field;
public class ReplaceTest{
public String str1="ball";
public String str2="basketball";
public String str3="itcast";
public static void main(String[] args) throws Exception{
ReplaceTest rep=new ReplaceTest();
changeStringValue(rep);
System.out.println(rep);
}
private static void changeStringValue(Object obj) throws Exception{
Field[] fields=obj.getClass().getFields();
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);//把对象上的值改为新值
}
}
}
@Override
public String toString() {
return "str1:"+str1+",str2:"+str2+",str3:"+str3;
}
}
成员方法的反射
Method类代表某个类中的一个成员方法
如str1.charAt();str2.charAt();--说明方法与对象无关,是属于类的,要调用charAt必须通过类的对象
知道:
Method指字节码里面的方法:通过Method得到的方法是属于类的,而调用类【Class】里的方法,必须通过类的实例对象【先得到方法,再用获得的方法作用于某个实例对象】
得到类中的某一个方法:
例子: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对象对应的是一个静态方法。
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",newObject[]{1})的形式
/* Method getMethod(String name, Class<?>...parameterTypes)
返回一个 Method 对象,第一个参数:要调用的方法名,第二个参数,方法里所有 参数的参数类型
加上第二个参数的原因:一个类里重名的方法有多种重载形式,要从这些重载方法中选哪 一个呢?就要用参数的列表和个数来识别
*/
//调用String对象str上面的方法charAt():str1.charAt(1);
MethodmethodCharAt=String.class.getMethod("charAt", int.class);
/*因为方法一定是在类的实例对象上调用的,那么java.lang.reflect.Method类中的方法
Object invoke(Object obj, Object... args)
第一个参数是实例对象名称,第二个参数是调用方法里实际传入的参数
调用静态方法不需要对象,所以第一个参数传入null
*/
System.out.println(methodCharAt.invoke(str1,1));//b
//JDK1.4语法:new Object[]{2}表示2是Object数组里面的一个元素,相当于new int[]{4,6,8}
System.out.println(methodCharAt.invoke(str1, new Object[]{2}));//c
如:画圆:画的动作是在圆身上
人在黑板上画圆:三个对象:人、黑板、圆;画圆需要的参数:圆心,半径;圆心,半径是圆上面的,假若圆心,半径私有,只有把画圆的方法分配给圆【圆的那个动作“画我”】circle.draw()
列车司机刹车:司机给列车发信号,停车的动作由列车来做
人关门:关门是门的动作
专家模式:变量为私有,这个变量在谁身上,操作变量的方法就在谁身上。如果谁要操作这个变量,就要访问拥有这个变量的对象【谁拥有数据,谁就是做与这个数据有关操作的专家,相应方法就应该分配在这个专家身上】
对接收数组参数的成员方法进行反射
需求:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法
import java.lang.reflect.Method;
public class TakeMain{
public static void main(String[] args) throws Exception{
//用普通方式根据类名获得main方法
//TestMain.main(new String[]{"ax","bx","cx"});
/*该程序在调用另一个类的main方法时传入了一些参数,假设调用的方法里带有“哥们,你去启动哪个类”的信息,又假设方法里的第一个参数就是类的名字,告诉了要执行的类,那么args[0]就是要启动的className*/
String startClassName=args[0];
Method method=Class.forName(startClassName).getMethod("main",String[].class);
//因为main方法要接收一个字符串数组,但是JDK1.4不会把传进去的字符数组当作一个参数,所以把数组强转为一个Object类型
method.invoke(null,(Object)new String[]{"aa","bb","cc"}); 正确
//method.invoke(null,new Object[]{new String[]{"aa","bb","cc"}}};正确
/*此处报告数组角标越界,手动将要加载的类完整名称"com.test.reflect.TestMain"作为第一个参数填入右键--RunAs-->Run Configurations--Arguments的框中,并应用于程序中。
此操作等效于在cmd窗口中运行javaTakeMain com.test.reflect.TestMain即运行TakeMain时传递了一个特殊的字符串,而字符串正好又是类的完整名称,TakeMain内部用args[0]遍历取到此字符串。TakeMain中又声明“StringstartClassName=args[0];”即假设把args[0]代表的字符串当作类的名字,用这个字符串找到类的字节码,再用字节码找到Main方法,拿到方法以后就可以执行*/
}
}
class TestMain{
public static void main(String[]args) {
for(String arg:args){
System.out.println(arg);
}
}
}
通过反射来调用main方法,因为main的参数是一个数组,如何为invoke方法传递参数?JDK1.5认为整个数组是一个参数,而JDK1.4认为数组中的每个元素就是一个参数,要想办法不让编译器把数组当成多个参数。JDK1.5为了兼容JDK1.4,有两种方案:A、把数组打包成另外一个数组(拆完包剩下的就是那个数组);B、直接类型转换成Object,当作Object,不要拆。
数组与Object的关系及其反射类型
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象
JDK文档的描述:如果用反射的话具有相同的元素类型以及相同维度的每一个数组都属于同一个Class【维数指:一维数组,二维数组等】
/*
* 验证具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象
*/
int[] a1=newint[3];
int[] a2=newint[5];
int[][] a3=newint[8][5];
String[] a4=new String[3];
System.out.println(a1.getClass()==a2.getClass());//true
//后面两句编译失败
// System.out.println(a1.getClass()==a3.getClass());
// System.out.println(a1.getClass()==a4.getClass());
//此句返回[I,[代表数组,I表示是int类型的整数
System.out.println(a1.getClass().getName());
l 数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class
/**
* 验证数组字节码的父类是Object,以下都打印出java.lang.Object
*/
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(a3.getClass().getSuperclass().getName());
System.out.println(a4.getClass().getSuperclass().getName());
l 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当作Object类型使用,又可以当作Object[]类型上使用。
/* 验证非基本类型的一维数组,既可以当作Object使用,也可以当Object[]类型使用,
* 基本类型的一维数组只能当作Object类型使用
* 基本类型不属于Object,如int。基本类型的一维数组不能转换为Object类型的数组, * 因为Object[]里面装的是int,不是Object,而一维数组int[]是Object类型的
*/
Object obj1=a1;
Object obj2=a4;
//Object[]obj3=a1;编译错误
Object[] obj4=a3;
Object[] obj5=a4;
l Arrays.asList()方法处理int[]和String时的差异。
/* 验证Arrays.asList()方法处理int[]和String时的差异
* 打印数组,java.util.Arrays类
* JDK1.4static asList(Object[] obj)
* JDK1.5static <T> List<T> asList(T... a)
*/
int[]a=newint[]{5,8};
String[] str=new String[]{"aa","bb"};
System.out.println(a);//[I@465863,int类型的数组,hashCode值为@465863
System.out.println(Arrays.asList(a));//[[I@465863],因为int[]不能当作JDK1.4中Object[]使用
System.out.println(Arrays.asList(str));//[aa, bb],字符串成功转换为List对象
l Array工具类用于完成对数组的反射操作
获取数组的值,设置数组的值,得到数组的长度
/**数组反射类java.lang.reflect.Array完成对数组的反射
* 数组反射,打印Object,该Object有可能是单个对象,也有可能是数组,
* 如果是数组那么就要把数组中每个元素都打印
*/
printObject(str);
printObject(10);
private static void printObject(Objectobj) {
//先判断传入的是普通对象还是数组
Class clazz=obj.getClass();
if(clazz.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);
}
}
思考:怎么得到数组中的元素类型?
无法实现,形如Object[]obj=new Object[]{"aa",10};只能得到具体元素如obj[0].getClass.getName()的类型,不能得到整个数组的元素类型