●对比提问: Person类代表人,它的实例对象就是张三,李四这样一个个具体的人, Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。对比提问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?
·人——Person
·Java类——Class
●Class类代表Java类,它的各个实例又对应什么呢?
·对应各个类在内存中的字节码,例如Person类的字节码,ArrayList类的字节码等等
·一个类被加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码文件,不同的类的字节码文件不同,所以它们在内存中的内容也是不一样的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型。
●class和Class的区别
·class:Java语言中的关键字,用于的定义类,把事物共性抽取出来定义成类,再通过类对象调用
·Class:Java中的类,Java中的各个类是归属于Class类,Class类是程序中各个java类的总称。
●如何得到各个字节码文件对应的示例对象?
方式一:类名.class;——示例:System.class;
方式二:对象.getClass();——示例:new Date().getClass();
方式三:Class.forName(“类名”);——示例:Class.forName(“java.util.Date”);
示例:
class ReflectDemo {
public static void main(String[] args) throwsException {
//以字符串为例
String s = "abcdefg";
//方式一:类名.class
Class c1 = String.class;
//方式二:对象名.getClass()
Class c2 = s.getClass();
//方式三:Class.forName("类名")
Class c3 =Class.forName("java.lang.String");
System.out.println(c1); // 结果:class java.lang.String
System.out.println(c2); // 结果:class java.lang.String
System.out.println(c3); // 结果:class java.lang.String
System.out.println(c1== c2); // 结果:true
System.out.println(c1== c3); // 结果:true
}
}
●九个预定义Class实例对象:
包括八中基本类型(byte、short、int、long、float、double、char、boolean)的字节码对象和一种返回值为void类型的void.class
示例:
class ReflectDemo {
public static void main(String[] args) throws Exception {
System.out.println(byte.class); // 结果:byte
//通过isPrimitive方法判断是否为基本类型
System.out.println(byte.class.isPrimitive()); // 结果:true
System.out.println(short.class); // 结果:short
System.out.println(int.class); // 结果:int
System.out.println(long.class); // 结果:long
System.out.println(float.class); // 结果:folat
System.out.println(double.class); // 结果:double
System.out.println(char.class); // 结果:char
System.out.println(boolean.class); // 结果:boolean
System.out.println(void.class); // 结果:void
System.out.println("------");
System.out.println(Byte.class); // 结果:class java.lang.Byte
//通过isPrimitive方法判断是否为基本类型
System.out.println(Byte.class.isPrimitive()); // 结果:false
System.out.println(Short.class); // 结果:class java.lang.Short
System.out.println(Integer.class); // 结果:class java.lang.Integer
System.out.println(Long.class); // 结果:class java.lang.Long
System.out.println(Float.class); // 结果:class java.lang.Float
System.out.println(Character.class); // 结果:class java.lang.Character
System.out.println(Boolean.class); // 结果:class java.lang.Boolean
System.out.println(Void.class); // 结果:class java.lang.Void
System.out.println("------");
System.out.println(Byte.TYPE); // 结果:byte
//通过isPrimitive方法判断是否为基本类型
System.out.println(Byte.TYPE.isPrimitive()); // 结果:true
System.out.println(Short.TYPE); // 结果:short
System.out.println(Integer.TYPE); // 结果:int
System.out.println(Long.TYPE); // 结果:long
System.out.println(Float.TYPE); // 结果:float
System.out.println(Character.TYPE); // 结果:char
System.out.println(Boolean.TYPE); // 结果:boolean
System.out.println(Void.TYPE); // 结果:void
}
}
附注:
根据上面的示例我们可以发现:byte.class==Byte.TYPE;int.class== Integer.TYPE……
●数组类型的Class实例对象:
Class.isArray();
示例:
class ReflectDemo {
public static void main(String[] args) {
System.out.println(int[].class); // 结果:class [I
//通过isPrimitive方法判断是否为基本类型
System.out.println(int[].class.isPrimitive()); // 结果:false
//通过isArray方法判断是否为数组
System.out.println(int[].class.isArray()); // 结果:true
System.out.println(int.class.isArray()); // 结果:false
System.out.println("---------");
System.out.println(byte[].class); // 结果:class [B
//通过isPrimitive方法判断是否为基本类型
System.out.println(byte[].class.isPrimitive()); // 结果:false
//通过isArray方法判断是否为数组
System.out.println(byte[].class.isArray()); // 结果:true
System.out.println(byte.class.isArray()); // 结果:false
}
}
总结:
只要在源程序中出现的类型,都有各自的Class实例对象,例如:int[],int,void……
2.反射
●反射就是把Java类中的各种成分映射成相应的Java类,例如:一个Java类中用一个Class类的对象来表示,一个类中的组成部分包括:成员变量、方法、构造方法、包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示Java类的Class类显然要提供一系列的方法,来获取其中的变量、方法、构造方法、修饰符、包等信息,这些信息就是用相应类的实例对象来表示,它们分别是Field、Method、Contructor、Package等等。
●一个类中的每个成员都可以用相应的反射API类的一个示例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?如何用?这就是我们学习的重点。
●Class常用方法:
static Class forName(String className)——返回与给定字符串名的类或接口的相关联的Class对象
Class getClass()——返回的是Object运行时的类,即返回Class对象即字节码对象
Constructor<?>[] getConstructors()——返回Constructor对象数组,它反映此Class对象所表示的类的所有构造方法
Constructor getConstructor()——返回Constructor对象,它反映此Class对象所表示的类的指定公共构造方法
Field getField(String name)——返回一个Field对象,它表示此Class对象所代表的类或接口的指定公共成员字段
Field[] getFields()——返回包含某些Field对象的数组,表示所代表类中的成员字段
Method getMethod(String name,Class… parameterTypes)——返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法。
Method[] getMehtods()——返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法。
String getName()——以String形式返回此Class对象所表示的实体名称。
String getSuperclass()——返回此Class所表示的类的超类的名称
boolean isPrimitive()——判断指定的Class对象是否是一个基本类型
boolean isArray()——判定此Class对象是否表示一个数组
T newInstance()——创建此Class对象所表示的类的一个新实例。
3.Constructor类
Constructor类代表某个类中的一个构造方法
●得到某个类所有的构造方法:
Constructor[] constructors=Class.forName(“java.lang.String”).getConstructors();
示例:
class ReflectDemo {
public static void main(String[] args) throwsException {
//获取String类中的构造函数
Constructor[] cs =Class.forName("java.lang.String").getConstructors();
//打印个数
System.out.println("String类共有构造函数"+cs.length+"个");
for(Constructor c : cs) {
System.out.println(c);
}
}
}
运行结果:
String类共有构造函数15个
public java.lang.String(byte[],int,int)
publicjava.lang.String(byte[],java.nio.charset.Charset)
public java.lang.String(byte[],java.lang.String)throws java.io.UnsupportedEncodingException
publicjava.lang.String(byte[],int,int,java.nio.charset.Charset)
publicjava.lang.String(byte[],int,int,java.lang.String) throwsjava.io.UnsupportedEncodingException
public java.lang.String(java.lang.StringBuilder)
publicjava.lang.String(java.lang.StringBuffer)
public java.lang.String(byte[])
public java.lang.String(int[],int,int)
public java.lang.String()
public java.lang.String(char[])
public java.lang.String(java.lang.String)
public java.lang.String(char[],int,int)
public java.lang.String(byte[],int)
public java.lang.String(byte[],int,int,int)
●得到某一个构造方法:(获得方法时要用到参数类型)
Constructor constructor=Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
示例:
class ReflectDemo {
public static void main(String[] args) throwsException {
Constructor c =Class.forName("java.lang.String").getConstructor(StringBuilder.class);
System.out.println(c);
}
}
运行结果:
publicjava.lang.String(java.lang.StringBuilder)
●创建实例对象:(调用获得的方法时要用到上面相同类型的实例对象)
·通常方式:String str=new String(newStringBuffer(“abc”));
·反射方式:String str=(String)constructor.newInstance(new StringBuffer(“abc”));
示例:
class ReflectDemo {
public static void main(String[] args) throwsException {
//通常方式:
String s1 = new String(new StringBuilder("abcdefg"));
System.out.println(s1.charAt(1));
System.out.println(s1);
//反射方式
Constructor c =Class.forName("java.lang.String").getConstructor(StringBuilder.class);
String s2 = (String) c.newInstance(new StringBuilder("abcdefg"));
System.out.println(s2.charAt(1));
System.out.println(s2);
//判断s1与s2是否相等
System.out.println(s1.equals(s2));
}
}
运行结果:
b
abcdefg
b
abcdefg
true
●Class.newInstance()方法:
String obj=(String)Class.forName(“java.lang.String”).newInstance();
该方法内部得到默认的构造方法,然后用该构造方法创建示例对象。
该方法内部的具体代码是怎样写的呢?——用到了缓存机制来保存默认构造方法的实例对象。
附注:
·创建实例时newInstance方法中的参数列表必须与获取Constructor的方法getConstructor方法中的参数列表一致。
·newInstance():构造出一个实例对象,每调用一次就构造一个对象。
·利用Constructor类来创建类实例的好处是可以指定构造函数,而Class类只能利用无参构造函数创建类实例对象。
4.Field类
Field类代表某个类中的一个成员变量
示例:
class ReflectPoint {
private int x;
public int y;
ReflectPoint(int x, int y) {
this.x =x;
this.y= y;
}
}
class ReflectDemo {
public static void main(String[] args) throws Exception{
ReflectPoint rp1 = new ReflectPoint(4, 7);
Field y=rp1.getClass().getField("y");
//y不是对象上的变量,而是类上的成员
System.out.println(y);
//用y去取对象上面对应的值
System.out.println(y.get(rp1));
//因为ReflectPiont中的x成员是私有的,所以反射不成功
//Field x=rp1.getClass().getField("x");
//System.out.println(x);
//通过getDeclaredField方法获取私有成员
Field x=rp1.getClass().getDeclaredField("x");
System.out.println(x);
//因为x是私有成员,还必须通过setAccessible设置其可访问才可以获取对象的值
x.setAccessible(true);
System.out.println(x.get(rp1));
}
}
运行结果:
publicint Demo.ReflectPoint.y
7
privateint Demo.ReflectPoint.x
4
注意:
问:得到的Field对象是对应类上面的成员变量还是对应到对象上的成员变量呢?
答:类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象就说不清了,所以字段filed X代表的是x的定义,而不是具体的x的变量。
练习:(将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a")
代码实现:
class ReflectPoint {
private int x;
public int y;
public String a="ball";
public String b="basketball";
public String c="itcast";
ReflectPoint(int x, int y) {
this.x =x;
this.y= y;
}
public String toString(){
return a+"--"+b+"--"+c;
}
}
class ReflectDemo {
publicstatic void main(String[] args) throws Exception {
ReflectPointrp = new ReflectPoint(5, 6);
changValue(rp);
System.out.println(rp);
}
public static void changValue(Object obj) throws Exception {
Field[] fs = obj.getClass().getFields();
for(Field d : fs) {
if(d.getType()==String.class){
String old=(String)d.get(obj);
String n=old.replace('b', 'a');
d.set(obj,n);
}
}
}
}
运行结果:
aall--aasketaall--itcast
附注:
问:changValu为什么不用equals比较?
答:因为字节码只有一份,两个都是一份,所以用==比较
5.Method类
Method类代表某个类中的一个成员方法
●得到类中的某一个方法:
MethodcharAt=Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);
示例:
class ReflectDemo {
public static void main(String[] args) throws Exception {
//方法不带参数
Method m1 =String.class.getMethod("isEmpty");
System.out.println(m1);
//方法带参数
Method m2 =String.class.getMethod("indexOf", int.class);
System.out.println(m2);
}
}
运行结果:
public boolean java.lang.String.isEmpty()
public int java.lang.String.indexOf(int)
●调用方法:
·通常方式:System.out.println(str.charAt(1));
·反射方式:System.out.println(charAt.invoke(str.1));
示例:
class ReflectDemo {
public static void main(String[] args) throwsException {
String s = "abcdefghi";
//通常方式
System.out.println(s.charAt(3)); // 结果:d
//反射方式(JDK1.5invoke方法)
Method m =String.class.getMethod("charAt", int.class);
System.out.println(m.invoke(s,3)); // 结果:d
//反射方式(JDK1.4invoke方法)
System.out.println(m.invoke(s,new Object[] { 3 })); // 结果:d
//当invok方法中第一个参数为null时,表明该方法是静态方法,类名可以直接调用
Method m2 =String.class.getMethod("valueOf", int.class);
System.out.println(m2.invoke(null,4)); // 结果:4
}
}
附注:
如果传递给Method对象的invoke方法的第一个参数为null,这有着什么含义呢?——说明该Method对象对应的一个静态方法。
●JDK1.4和JDK1.5的invoke方法的区别:
·JDK1.5:public Object invoke(Objectobj,Object…args){}
·JDK1.4:public Object invoke(Objectobj,Object[] args){}
即按JDK1.4的语法,需要将一个数组作为参数传递给invoke方法,数组中的每一个元素分别对应被调用方法的一个参数,所以调用charAt方法的代码也可以用JDK1.4改写为charAt.invoke(“str”,newObject[]{1})形式。
6.用反射方式执行某个类中的main方法
●目标:
写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法,用普通方式调用完后。
示例:
class ForEach {
public static void main(String[] args) {
for(String s : args) {
System.out.println(s);
}
}
}
class ReflectDemo {
public static void main(String[] args) throwsException {
String start = args[0];//args[0]= ForEach
Method m =Class.forName(start).getMethod("main", String[].class);
m.invoke(null,new Object[] { new String[] { "abc1-1","abc2-1","abc3-1"} });
//或者采用下一种方式
m.invoke(null,(Object) new String[] {"abc1-2", "abc2-2", "abc3-2" });
}
}
运行结果:(运行时间ForEach作为参数传递进去)
abc1-1
abc2-1
abc3-1
abc1-2
abc2-2
abc3-2
●问题:
启动Java程序的main方法的参数是一个字符串数字,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按照JDK1.5的语法,整个数组是一个参数,而按照JDK1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?JDK1.5肯定要兼容JDK1.4的语法,会按JDK1.4的语法进行处理,即把数组打散成为若干单独的参数。所以,在给main放传递参数时,不能使用代码mainMethod.invoke(null,new
String[]{“xxx”}),javac只能把它当作JDK1.4的语法进行理解,而不把它当作JDK1.5的语法解释,因此会出现参数类型不对的问题。
●解决办法:
m.invoke(null,new Object[]{“xxx”});
m.invoke(null,(Object)new String[]{“xxx”});编译器会作特殊处理,编译时不把参数当作数组看待,也就是数组打散成若干个参数了
7.数组的反射
·具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象(此处比较与值无关)
·代表数组的Class实例对象的getSuperClass()方法返回的是父类Object类对应的Class。
·基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;
非基本类型的一维数组,既可以当作Object类型使用,又可以当作Object[]类型使用。
示例一:
class ReflectDemo {
public static void main(String[] args) {
int[] a1 = new int[]{1,2,3};
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[4];
System.out.println(a1.getClass()); // 结果:class [I
System.out.println(a3.getClass()); // 结果:class [[I
System.out.println(a4.getClass()); // 结果:class [Ljava.lang.String;
System.out.println(a1.getClass().equals(a2.getClass())); // 结果:true
System.out.println(a1.getClass().equals(a3.getClass())); // 结果:false
System.out.println(a1.getClass().equals(a4.getClass())); // 结果:false
System.out.println(a3.getClass().equals(a4.getClass())); // 结果:false
System.out.println(a1.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
//int[]、String[]类型是object
Object obj1=a1;
Object obj2=a2;
Object obj3=a3;
Object obj4=a4;
//a1,a2中是int类型不是Object类型,编译报错
//Object[] obj5=a1;
//Object[] obj6=a2;
//a3其实是数组的数组
Object[] obj7=a3;
//String[]中的元素属于Object
Object[] obj8=a4;
}
}
示例二:(数组反射)
class ReflectDemo {
public static void main(String[] args) {
int[] x = new int[] { 1, 2, 3 };
String[] y = new String[] { "a", "b","c" };
System.out.println(x);
System.out.println(y);
printArray(x);
System.out.println("----");
printArray(y);
System.out.println("----");
printArray("afeg");
System.out.println("----");
printArray(newObject[] { 2, x, "a", 7 });
System.out.println("----");
// Arrays.asList()处理int[]数组
System.out.println(Arrays.asList(x));
System.out.println("----");
//Arrays.asList()处理String[]数组
System.out.println(Arrays.asList(y));
}
private static void printArray(Object obj) {
Class c = obj.getClass();
if(c.isArray()) {
int len = Array.getLength(obj);
for(int x = 0; x < len; x++) {
System.out.println(Array.get(obj,x));
}
}else {
System.out.println(obj);
}
}
}
运行结果:
[I@1db9742
[Ljava.lang.String;@106d69c
1
2
3
----
a
b
c
----
afeg
----
2
[I@1db9742
a
7
----
[[I@1db9742]
----
[a, b,c]
·Arrays.asList()方法处理int[]和String[]时的差异?
因为此方法在JDK1.4版本中,接收的Object类型的数组,而String[]作为Object数组传入,int[]不可以作为Object数组传入,所以只能按照JDK1.5来处理,在JDK1.5中,传入的是可变参数列表,所以int[]被当作是一个Object传入,作为一个单个参数,而不是数组,所以打印的结果是哈希值。
·Array工具类用于完成对数组的反射操作。
通过Array.get(obj, x)获取元素。
8.反射的作用(实现框架功能)
●框架与框架要解决的核心问题
我做房子卖给用户住,由用户自己安装门和窗,我做的房子就是框架,用户需要使用我的框架,把门窗插入我提供的框架中。框架与工具类有区别——工具类被用户的类调用;框架则是调用用户提供的类。
●框架要解决的核心问题:
我在写框架(房子)时,你这个用户可能暂时还不具备使用的能力,那我写的框架程序怎样能调用到以后写的类(门窗)中呢?
因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象了,而要用反射方式来做。
●综合案例:
·先直接用new语句创建ArrayList和HashSet的实例对象,覆盖equals和hashCode方法,比较两个集合的运行结果差异;
·然后改为采用配置文件加反射的方式创建ArrayList和HashSet的实例对象,比较观察运行结果差异。
示例:(采用配置文件)
//config.properties文件:
className=java.util.HashSet
class ReflectPoint {
private int x;
public int y;
public String a="ball";
public String b="basketball";
public String c="itcast";
ReflectPoint(int x, int y) {
this.x= x;
this.y= y;
}
<span style="color:#FF0000;"> //hash算法将集合分成多干个存储区域,每个对象可以计算出一个哈希值,可以将哈希码分组,
//每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象存储在哪个区域</span>
@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;
ReflectPointother = (ReflectPoint) obj;
if(x != other.x)
return false;
if(y != other.y)
return false;
return true;
}
publicString toString(){
return a+"--"+b+"--"+c+"--"+x+y;
}
}
class ReflectTest {
public static void main(String[] args) throwsException {
FileInputStream fis = new FileInputStream("config.properties");
Properties p = new Properties();
p.load(fis);
fis.close();
String className =p.getProperty("className");
Collection c = (Collection)Class.forName(className).newInstance();
ReflectPoint rp1 = new ReflectPoint(3, 5);
ReflectPoint rp2 = new ReflectPoint(4, 7);
ReflectPoint rp3 = new ReflectPoint(9, 6);
c.add(rp1);
c.add(rp2);
c.add(rp3);
for(Iterator it = c.iterator();it.hasNext();) {
System.out.println(it.next());
}
}
}
运行结果:
ball--basketball--itcast--35
ball--basketball--itcast--47
ball--basketball--itcast--96