7.反射的基石—Class类
-------结合网络资源整理
Java程序中的各个Java类属于同一类事物,描述这类事物的Java类就是Class类。
Class类代表Java类,它的实例对象对应的是:各个类在内存中的字节码。
如何得到各个字节码对应的实例对象(Class类型)
1.类名.class,例如System.class
2.对象.getClass(),例如new Date().getClass()
3.Class.forName(“类名”),例如Class.forName(“java.util.Date”)
前2种方法是类字节码已经被加载,第3种类字节码还未加载。
九个预定义Class实例对象
boolean, byte, char, short, int, long, float, double和void是Primitive的Class对象
int.class==Integer.TYPE
数组类型的Class实例对象:Class.isArray()
9反射
反射就是把Java类中的各种成分映射成相应的java类。
例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。
表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。
什么是Java的反射呢?
大家都知道,要让Java程序能够运行,那么就得让Java类要被Java虚拟机加载。Java类如果不被Java虚拟机加载,是不能正常运行的。现在我们运行的所有的程序都是在编译期的时候就已经知道了你所需要的那个类的已经被加载了。
Java的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类。这样的特点就是反射。
Java反射有什么作用呢?
假如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码能否通过编译呢?这是不能通过编译的。利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。
Java的反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。大家都用过Jcreator和eclipse。当我们构建出一个对象的时候,去调用该对象的方法和属性的时候。一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。
java的反射机制大大的提高了程序的拓展性.
class中的一些方法:
a)获取构造器
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。
Constructor<?>[] getDeclaredConstructors()
返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法。
Constructor<T> getConstructor(Class<?>... parameterTypes)
返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
Constructor<?>[] getConstructors()
返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。
b)获取成员变量
Field getDeclaredField(String name)返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。Field[] getDeclaredFields()返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。Field getField(String name)返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。Field[] getFields()返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。
c)获取成员方法
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
Method[] getDeclaredMethods()
返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
Method getMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Method[] getMethods()
返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。
如图所示 通过反射获得test的main方法 可在传递参数是出现异常
异常分析:
异常提醒传递的参数数量不对
可传的明明是一个string[]数组 没有错啊?
可以看出数组形式传参数是jdk1.5之前的写法 而jdk1.5为了向下兼容 在遇到数组形式传递参数时会内部把它处理成一个个参数(即把数组内的对象取出)
所以会提醒传递的参数数量不正确
再看如下
如图所示把string[]数组强制转换成了object 结果程序就正常运行了
可以想像 当强制转换成了object时相当于告诉jvm 这里只有一个对象 jvm当然不会再去把它拆开
这里还有另一种解决方法
new Object[]{new String[]{"xxx"}}
分析:当jdk1.5遇到数组形式参数 它内部处理(把数组拆开) 即得到了string[]数组 并作为第一个参数 所以不会抛出异常
六、数组的反射(摘自网络资源)
- 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
- int []a1=new int[3];
- int []a2=new int[4];
- int [][]a3=new int[3][4];
- String []a4=new String[3];
- System.out.println(a1.getClass()==a2.getClass());//true
- System.out.println(a1.getClass()==a3.getClass());//false
- System.out.println(a1.getClass()==a4.getClass());//false
- 代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
- System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object
- System.out.println(a2.getClass().getSuperclass().getName());//java.lang.Object
- System.out.println(a3.getClass().getSuperclass().getName());//java.lang.Object
- System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object
- 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
- Object aObj1 = a1;
- Object aObj2 = a4;
- //Object[ ] aObj3 = a1; ERROR!!!
- Object[ ] aObj4 = a3; //一维数组可以转成object 二维数组转成object[]
- Object[ ] aObj5 = a4;
- Arrays.asList()方法处理int[]和String[]时的差异。(为了兼容jdk1.4造成的,jdk1.4中asList(Object []obj) 处理不了int[]只能给jdk1.5+处理,当成一个参数用Object接收了)
- String []a4=new String[]{"a","b","c"};
- System.out.println(Arrays.asList(a1));//[[I@422ede]
- System.out.println(Arrays.asList(a4));//[a, b, c]
- Array工具类用于完成对数组的反射操作。
- public 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);
- }
- }
一、hashCode()的作用(摘自网络资源)
hashCode()是用来计算哈希值的,在对数据进行存储的时候,假如有成千上万个数据,每当存入一个数据,就需要比较多次,这样效率就很低,采用哈希值算法得到哈希值后,根据哈希值划分为不同的区域,这样就能大大减少比较次数,提高效率。如图所示:(学过数据结构的人应该都了解hash表)
二、ArrayList_HashSet的比较及Hashcode分析
在自定义类中类型添加到HashSet中通常要要重写hashCode方法和equlas方法,依此来判断元素是否重复。在进行判断元素是否相同时,先调用hashCode方法,如果哈希值一样,再调用equals方法继续比较判断。如果哈希值不同,则不再调用equals方法进行判断。
注意:哈希值相同,两个对象不一定相等,两个对象相等,这两个对象的哈希值一定相等。
内存泄露:对象不需要再使用了,却没有引用指向它,未进行回收。
见代码:
- public class ReflectPoint
- {
- public int x;
- public int y;
- public ReflectPoint(int x, int y)
- {
- super();
- this.x = x;
- this.y = y;
- }
- public String toString()
- {
- return this.x+" "+this.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;
- final ReflectPoint other = (ReflectPoint) obj;
- if (x != other.x)
- return false;
- if (y != other.y)
- return false;
- return true;
- }
- }
- public class ReflectTest1
- {
- public static void main(String[] args)
- {
- Collection al=new ArrayList();
- ReflectPoint rp1=new ReflectPoint(3,3);
- ReflectPoint rp2=new ReflectPoint(5,6);
- ReflectPoint rp3=new ReflectPoint(3,3);
- al.add(rp1);
- al.add(rp2);
- al.add(rp3);
- al.add(rp1);
- System.out.println("list:"+al.size());//list:4
- Collection hs=new HashSet();
- hs.add(rp1);
- hs.add(rp2);
- hs.add(rp3);
- hs.add(rp1);
- //重写了hashCode方法和equals方法,rp1重复添加了,只添加一个rp1,rp1和rp3也重复,不添加rp3
- System.out.println("hs:"+hs.size());//hs:2 [集合内存放的是rp1和rp2]
- System.out.println(hs);//[3 3, 5 6]
- //集合中添加了元素rp1后,修改对象rp1的成员变量值,这个成员变量是参与了hashCode()值的运算,进行移除rp1操作
- rp1.x=100;
- hs.remove(rp1);//移除元素
- //发现还是2个元素,remove方法因为哈希值被修改了,无法移除原来的rp1对象,造成内存泄露!!
- System.out.println(hs);
- }
- }
如图所示 在操作集合元素过程中 因为修改了参与hash值计算的元素属性 导致了remove()(根据hash值来执行移除操作)操作失败 造成了内存泄漏!!!
结论:集合一旦添加了元素 就不能再修改参与hash值计算的的元素属性