黑马程序员:java中的Class类和反射(一)
----------------------------Android培训、java培训、期待与您交流!------------------------------
(本文主要对java加强课程中相应内容进行总结。)
1,java中的Class类
public class ClassTest {
/*java程序中的各个java类属于同一类事物,描述这类事物的java类名就是Class java类用于描述一类事物的共性。该类事物有什么属性,没有什么属性,但是 这个属性的值是什么则是由这个类的实例对象来确定的。不同的实例对象有不同的 属性值。java程序中的各个java类也属于同一类事物,用一个类来描述这类事物, 这个类就是Class类。它描述了类的名字,类的访问属性,类所属的包名,字段名称列表,方法名称列表等。学习反射,首先要明白Class这个类。*/
/*Class类代表java类,它的各个实例对象对应了各个类在内存中的字节码
* 一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以他们在内存中的内容是不一样的。这一个个空间可分别用一个个对象来表示, 这些对象显然具有相同的类型。
* 例如可以这样写:
* Person ps1=new Person();
* Person ps2=new Person();
* 但不可以这样写:
* Class cl1=new Class();--->应为 Class cl1=字节码1;--->例如Class cl1=Person.class
* Class cl2=new Class();--->应为 Class cl2=字节码2;
* 首先,Class类是没有构造方法的,另外,Class类的每一个实例化对象对应的是一个字节码文件
* 关于字节码:当创建一个类时,如Person类,首先就是将这个类的二进制文件加载到内存中,才可以用它来实例化一个个对象。
* 总之,字节码就是一个对象,它的类型就是Class*/
/*获得各个字节码对应的实例对象的三种方法:
* 1,类名.class
* 2,可以使用对象的getClass方法来获得这个对象代表的类的Class。例如ps1.getClass()
* 3,利用Class类的forName方法--->Class.forName("类名")。forName中的参数在程序中可以变为一个字符串类型
* 的变量,类名我们可以不知道,但在程序运行时,作为参数传递进来*/
/*九个预定义的Class实例对象:八个基本数据类型int, long, char, double, float, byte, short, boolean,和void关键字。它们分别对应了一个Class实例对象,如 Class cls1=void.class*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String str="abc";
Class cl1=str.getClass();
Class cl2=String.class;
Class cl3 = null; //此处如果不预先定义cl3,那么在try,catch语句中的cl3因为是局部变量所以不会被
try { //后面的System.out.println来访问。另外,预定义的cl3也必须被初始化,否则报错。
cl3=Class.forName("java.lang.String"); //必须写类的全名。
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(cl1==cl2);
System.out.println(cl2==cl3);
//以上三个变量对应的字节码是同一份。
System.out.println(cl1.isPrimitive()); //String是一个类,而不是一个基本数据类型。
System.out.println(int.class.isPrimitive());
//注意区分下面的两种情况:int和Integer分别代表的是不同的类型,所以对应的字节码是不同的。但是Integer
//这个封装类封装的是int这种基本数据类型,所以其包装的基本数据类型的字节码(Integer.TYPE)和int.class相同。
System.out.println(int.class==Integer.class);
System.out.println(int.class==Integer.TYPE);
//九种基本类型都有各自的封装类,使用每个封装类的TYPE常量就可以获得封装类所封装的基本类型的Class。
System.out.println(int[].class.isPrimitive());//数组是引用类型变量,其字节码不是基本类型的。
System.out.println(int[].class.isArray());//数组类型的实例对象使用的是Class的isArray方法。
}
//总之,只要是在源程序中出现的类型都有各自的Class实例对象,如int[],void……
}
2,反射基本知识和Constructor类
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Reflect {
/*反射
* java的反射就是将java类中的每一个成分映射成为一个相应的类。表示java类的Class类需要提供一系列方法来获得其中的变量Field,方法Method,构造方法Constructor,修饰符Modifier,包Package等信息。这些信息就是用相应的实例对象来表示。
* 举例来说:System类中有的方法如System.exit,System.getProperties()等,它们都属于Method这个类型
* 而每个方法对应的是methodobj1或者methodobj2等等,即是这个类的一个对象(即Method的对象对应的是一个具体的方法)。*/
/*一个类的所有成员都可以使用反射API类的一个实例对象来表示。通过调用Class类的方法获得这些实例对象后,就要对这些对象进行使用--->学习反射的要点*/
/*Constructor类
* Constructor类代表一个类中的一个构造方法
* 得到一个类中所有的构造方法:Constructor[] cst=Class.forName("类名").getConstructors(),而要获得某一个构造方法使用getConstructor()方法。
* 一个类里面会有多个构造方法,可以根据参数的类型和个数获得需要的方法(构造方法是没有顺序的)。例如要得到
* String类中String(byte[] b,int offset, int length, String charSetName){}这个构造方法,就可以这样做:
* Constructor ctr1=String.class.getConstructor(byte[].class, int.class, int.class, String.class);因为
* JDK1.5以上的版本具有可变参数的新特性,所以可以接受多于原始参数个数的参数。但是参数的个数是不一定的,如何指定需要的参数的个数呢?通过可变参数args来指定。
* 得到的ctr1是一个Constructor类的对象,而这种类的对象所具有的的方法可以从Constructor的说明文档中了解,然后根据需要调用。例如getDeclaringClass(),getModifier(),getParameterTypes(),newInstance(Object... initargs)等。其中newInstance方法,可以利用这个构造器对象所代表的构造器类型来产生这个构造器申明类的一个实例,并对其
进行初始化(使用可变参数实现) */
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// TODO Auto-generated method stub
//普通实现方式:先new一个StringBuffer,作为参数传递给String的这个构造方法。再利用这个构造方法new一个String对象。
String str=new String(new StringBuffer("abc"));
/*利用反射实现:首先将String的某种构造方法映射为一个构造器类对象,然后利用这个构造器类对象产生String实例对象。该构造器类对象可以多次使用。*/
Constructor ctr1=String.class.getConstructor(StringBuffer.class);
String str1=(String)ctr1.newInstance(new StringBuffer("abc"));
System.out.println(str1.charAt(2));
//下面两句执行时报错。但编译时不提示错误。
String str2=(String)ctr1.newInstance("abc");
System.out.println(str2.charAt(2));
/*第一个StringBuffer的引用是为了指定要产生哪种构造器对象,第二个StringBuffer是在使用已经产生的构造器实例化对象时传递这样一个参数。注意newInstance方法返回的是Object,所以需要进行强制类型转换。
编译过程和执行过程的区别:
1,编译器只看变量的定义,不看代码的执行。对于Constructor ctr1=String.class.getConstructor(StringBuffer.class);
String str1=(String)ctr1.newInstance(new StringBuffer("abc"));之所以要使用强制类型转换就是因为
在编译阶段,在第一句中等号右边的部分是不执行的,编译器不知道我们生成的构造器对象时String类型的。在第二句中编译器检查语法时知道newInstance方法返回的是一个Object类型的对象,要赋给String类型的变量就必须要进行强制类型转换。
2,对于String str2=(String)ctr1.newInstance("abc");编译时并不会报错,但是在执行时就会出错,因为需要
传递一个StringBuffer类型的参数,但却传递了一个String。*/
/*总结一下,利用反射创建对象步骤:a,获得构造方法(需要参数的类型)b,调用构造方法(传递同样类型的对象作为参数)*/
/*Class类的newInstance()方法:通过查阅java中Class的源文件可以知道,在这一方法内部实际是将默认的
* 即不带参数的构造方法加载到缓存中,当需要利用这种构造方法时,直接利用缓存的构造器对象来实例化该类
* 的对象。这样可以避免应用反射的繁琐步骤(class--->constructor--->new object),提高程序性能。*/
}
}