Class
一、概述:
1、Class是Java程序中各个Java类的总称;它是反射的基石,通过Class类来使用反射。
2、Class和class的区别
1)class:Java中的类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则由此类的实例对象确定,不同的实例对象有不同的属性值。
2)Class:指的是Java程序中的各个Java类是属于同一类事物,都是Java程序的类,这些类称为Class。例如人对应的是Person类,Java类对应的就是Class。
3、属性:类名,类的访问属性,类所属包名,字段名称列表,方法名称列表等。
二、对象的创建和使用:
1、创建实例对象:不可用new Class()的方式,因为Class没有这样的构造方法。而是将字节码对象赋值给Class变量。如Class c1 =Person.class。
如Person类,它的字节码:首先要将Person的java文件编译为class文件放于硬盘上,即为二进制代码,再将这些代码加载到内存中,接着用它创建一个个对象。就是把类的字节码加载进内存中,再用此字节码创建一个个对象。当有如Person、Math、Date等等的类,那么这些字节码就是分别的一个Class对象。即Class c2 =Date.class;。
2、获得类的字节码对象:如Class.forName(”java.lang.String”)即获得String.class。得到这个字节码对象有两种情况:
1)此类已经加载进内存:若要得到此类字节码,不需要再加载。
2)此类还未加载进内存:类加载器加载此类后,将字节码缓存起来,forName()方法返回加载进来的字节码。
3、得到各字节码对应的实例对象(Class类型)的方式:
1)类名.class:如System.class,String.class等等
2)对象.class:如new Date().getClass()或者d.getClass()。(Date d = new Date())
3)Class.forName(“类名”):如Class.forName(”java.lang.String”)
当获取类名的时候,是不知道此类的名称的,forName(字符串参数)方法中传入字符串型的变量作为对外访问的入口,即传入什么类名就获得什么类名,从而得知相应的类名。
注:forName()是静态方法,是反射中使用的一种方式获取字节码的实例对象。
每个类的字节码对象只有唯一的一个,如任何字符串对象,对应唯一的String.clas字节码。
4、九个预定义的Class:
1)包括八种基本类型(byte、short、int、long、float、double、char、boolean)的字节码对象和一种返回值为void类型的void.class。
2)Integer.TYPE是Integer类的一个常量,它代表此包装类型包装的基本类型的字节码,所以和int.class是相等的。
基本数据类型的字节码都可以用与之对应的包装类中的TYPE常量表示
数组类型的Class实例对象,可以用Class.isArray()方法判断是否为数组类型的。
5、总结:只要是在源程序中出现的类型都有各自的Class实例对象,如int[].class、void.class等。
三、方法:
1、static Class forName(String className)
---> 返回与给定字符串名的类或接口的相关联的Class对象。
2、Class getClass()
---> 返回的是Object运行时的类,即返回Class对象即字节码对象
3、Constructor getConstructor()
---> 返回Constructor对象,它反映此Class对象所表示的类的指定公共构造方法。
4、Field getField(String name)
---> 返回一个Field对象,它表示此Class对象所代表的类或接口的指定公共成员字段。
5、Field[] getFields()
---> 返回包含某些Field对象的数组,表示所代表类中的成员字段。
6、Method getMethod(String name,Class… parameterTypes)
---> 返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法。
7、Method[] getMehtods()
返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法。
8、String getName()
---> 以String形式返回此Class对象所表示的实体名称。
9、String getSuperclass()
---> 返回此Class所表示的类的超类的名称
10、boolean isArray()
---> 判定此Class对象是否表示一个数组
11、boolean isPrimitive()
---> 判断指定的Class对象是否是一个基本类型。
12、T newInstance()
---> 创建此Class对象所表示的类的一个新实例。
反射
1、概述:把Java类中的各种成分映射成相应的Java类。
如Class中的每一个方法返回的都是一种类(型),即Method对所有方法抽取成了这个类Method,它的每一个对象(如变量methodObj1)代表了一个方法。
2、一个类中的组成成分:
成员变量、方法、构造函数、包等信息,也用一个个java类来表示(如汽车是一个类,其中的发动机,变速箱等也是对应的一个个类),表示Java类的Class类显然要提供一系列的方法来获取其中的变量、方法、构造函数、修饰符、包等信息,这些信息就是用相应的类的实例对象来表示,他们是Field、Method、Contructor、Package等。
3、一个类中的每个成员都可用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可得到这些实例对象。
一、Constructor类
1、Constructor类代表某个类的构造方法
2、得到某个类所有的构造方法:
Constructor[ ]constructor = Class.forName(“java.lang.String”).getConstructors();
3、获取某一个构造方法:
Constructor con =String.class.getConstructor(StringBuffer.class);
4、创建实例对象:
1)通常方式:String str = new String(new StringBuffer (”abc”));
2)反射方式:String str = (String)con.newInstance(new StringBuffer(“abc”));
注:1:得到构造方法需要类型(StringBuffer.class)
2:调用这个方法也需要同样类型的实例对象(StringBuffer(“abc”))
5、Class.newInstance()方法
String Obj = (String)Class.forName("java.lang.String").newInstance();
1)该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象
2)该方法内部用到了缓存机制来保存默认构造方法的实例对象
二、Field类
1、Field类代表某个类中的一个成员变量
2、演示用eclipse自动生成Java类的构造方法
问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。
示例代码:
public class ReflectPoint {
private int x;
public int y;
public String toString(){
return str1+";" + str2 + ";" + str3;
}
}
public class FieldTest(){
ReflectPoint pt1 = new ReflectPoint(3,5);
//fieldX和fieldY并不是对象身上的变量,而是类上的
//要用它去取某个对象上的对应的值,传入什么对象,就取相应对象的值。
Field fieldY = pt1.getClass().getField("y");
System.out.println(fieldY.get(pt1));
Field fieldX = pt1.getClass().getDeclaredField("x");//获取私有的成员变量
fieldX.setAccessible(true);//获取私有的成员变量:暴力反射
System.out.println(fieldX.get(pt1));
}
替换字符:
private static void changeStringValue(Object obj) throws Exception {
Field[] fields = obj.getClass().getFields();
for(Field field : fields){
//此处用==比较,而不用valueof。因为是同一份字节码对象 if(field.getType() == String.class){ String oldValue = (String)field.get(obj);
String newValue = oldValue.replace('b','a');
field.set(obj, newValue);
}
}
}
三、Method类
1、Method类代表某个类中的一个成员方法
2、得到类中的某一个方法
例子:Method methodCharAt = String.class.getMethod("charAt", int.class);
3、调用方法
通常方式:System.out.println(str.charAt(1));
反射方式:System.out.println(methodCharAt.invoke(str, 1));
如果传递给Method对象的invoke方法的第一个参数为null,则表示该Method对象对应的是一个静态方法。
4、用反射方式执行某个main方法:
原因:当程序中的某个类在运行到某处需要去调用其他类的main方法时,如果此程序并不知道此main方法所属类的名称,而只是在程序中接受某一代表此main方法所属类的名称的参数,那么这时候就不能通过“类名.main(String[] args);"这样的方式来完成调用,而需要运用Java的反射机制了,需要编写相关的反射代码来完成对其他类的main方法的调用。
例子:有一个类,类名为ReflectTest,设定在其main方法中接受的String数据参数的第一个,即args[0]为所要调用的main方法所属类的类名,则与之相关的反射代码为:
String startingClassName = args[0];
Method mainMethod = Class.forName(startingClassName).getMethod("main", "String[].class");
mainMethod.invoke(null, (Object)new String[]{"a", "b", "c"});
这里解释一下(Object)new String[]{"a", "b", "c"}这段代码。
启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法设置参数呢?
按jdk 1.5的语法,由于使用的是可变参数(Object类型),设置的数组参数会被作为一个参数进行传递,而按jdk 1.4的语法,此处应设置一个Object数组,数组中的每个元素对应所调用方法的一个参数。 当把一个字符串数组作为参数传递给invoke方式时,编译器会兼容jdk 1.4的语法,即按照1.4的语法进行处理,即把字符串数组打散成为若干个单独的参数,这样就会产生参数个数不匹配的异常。
解决方法:采用上述强制向上转型后,可以是编译器按照正确的方法进行参数处理,即将整个字符串参数作为整体传递给目标main方法。
注:使用new Object[]{new String[]{"a", "b", "c"}}作为invoke方法的第二个参数也可以得到正确的结果。比如在目标main方法中便利字符串数组元素。
四、数组的反射
1、具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
2、代表数组的Class实例对象的getSuperclass()方法返回的父类为Object类对应的Class。
3、基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
public static void arrayTest()throws Exception {
int[] a1 = new int[]{1,2,3};
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
Integer[] ai = new Integer[3];
String[] a4 = new String[]{"a","b","c"};
System.out.println(a1.getClass() == a2.getClass());
System.out.println((Object)a1.getClass() == (Object)a3.getClass());
System.out.println(a3[0].getClass() == a1.getClass());
System.out.println(a3[0].getClass().getSuperclass().getName());
System.out.println(a1.getClass().equals(a3.getClass()));
System.out.println(a1.getClass().equals(a4.getClass()));
System.out.println(a1.getClass().getName());
System.out.println("----:" + a1.getClass());
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(a2.getClass().getSuperclass().getName());
Object obj1 = a1;
Object obj2 = a2;
//Object[] obj3 = a1;//int基本数据类型不是Object的
Object[] obj4 = a3;
Object[] obj5 = a4;
System.out.println(a1);
System.out.println(a4);
System.out.println(Arrays.asList(a1));
System.out.println(Arrays.asList(a4));
System.out.println("-------");
int[] a = new int[3];
Object[] obj = new Object[]{"abc",new Integer(1)};
System.out.println(a.getClass().getName());
System.out.println(obj.getClass().getName());
System.out.println(obj[0].getClass().getName());
}
五、HashSet和与hashCode的分析
1、哈希算法的由来:
若在一个集合中查找是否含有某个对象,通常是一个个的去比较,找到后还要进行equals的比较,对象特别多时,效率很低,通过哈希算法,将集合分为若干个区域,每个对象算出一个哈希值,可将哈希值分组(一般模32为一组),每组对应某个存储区域,依一个对象的哈希码即可确定此对象对应区域,从而减少每个对象的比较,只需在指定区域查找即可,从而提高从集合中查找元素的效率。
示意图:
2、如果不存入是hashCode算法的集合中,那么则不用复写此方法。
3、只有类的实例对象要被采用哈希算法进行存入和检索时,这个类才需要按要求复写hashCode()方法,即使程序可能暂时不会用到当前类的hashCode()方法,但是为提供一个hashCode()方法也不会有什么不好,没准以后什么时候就会用到这个方法,所以通常要求hashCode()和equals()两者一并被覆盖。
4、提示:
1)若同类两对象用equals()方法比较的结果相同时,他们的哈希码也必须是相等的,但反过来就不成立了,如”BB”和”Aa”两字符串用equals()比较式不相等的,但是他们的哈希值是相等的。
2)当一个对象被存储进HashSet集合中,就不能再修改参与计算哈希值的字段,否则对象被修改后的哈希值与最初被存入的HashSet集合中的哈希值就不同了。在这种情况下,即使contains()方法是用对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。
简单说,之前存入的对象和修改后的对象,是具有不同的哈希值,被认为是不同的两个对象,这样的对象不再使用,又不移除,而越来越多,就会导致内存泄露。
补充:
内存泄露:某些对象不再使用了,占用着内存空间,并未被释放,就会导致内存泄露;也就是说当程序不断增加对象,修改对象,删除对象,日积月累,内存就会用光了,就导致内存溢出。
3)对象在调用方法时,对象会先进行一次自身hashCode()方法的调用,再进行操作方法。
5、示例:
覆写hashCode()方法的意义:只有存入的是具有hashCode算法的集合的,覆写hashCode()方法才有价值。
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
public class ReflectTest2 {
public static void main(String [] args){
Collection cons = new HashSet();
ReflectPoint pt1 = new ReflectPoint(3,3);
ReflectPoint pt2 = new ReflectPoint(5,5);
ReflectPoint pt3 = new ReflectPoint(3,3);
cons.add(pt1);
cons.add(pt2);
cons.add(pt3); cons.add(pt1);
cons.remove(pt1);
System.out.println(cons.size());
}
}
public class ReflectPoint {
private int x;
public int y;
public String str1 = "ball";
public String str2 = "basketball";
public String str3 = "itcast";
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
//System.out.println("demo...");//测试
return result;
}
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 str1+";" + str2 + ";" + str3;
}
}