第一讲:透彻分析反射的基础_Class类
一,反射的基石——Class类的了解:
- 此类的由来:Java程序中java类属于同一类事物,描述这一类同一事物的类就是Class类。
- 类的定义:public final class Class<T>extends Object implements Serializable, GenericDeclaration, Type, AnnotatedElement ==在包java.lang中==
- Class类的作用:通过Class类可以得到一个类的方方面面的信息,例如:父类,方法,成员,实现的接口。
- 当使用一个类的时候,java虚拟机将类的字节码文件加载到内存中,在内存中的字节码文件,即是这个类的Class对象的内容。
二,得到字节码的三种方式:
- 类名.class 例:System.class ==编译阶段识别==
- 对象.getClass() 例: new Date().getClass(); ====此方法在Object类中定义===
- Class.forName() 例:Class.forName("java.util.Date"); ===注意:此处必须明确指出类名,路径名===
三,八个基本数据类型都有对应的Class对象,并且与其包装类所对应的Class对象不同。void 也有对应的Class对象。
四,代码练习:
1 public class ReflectTest { 2 public static void main(String[] args) throws ClassNotFoundException { 3 4 //创建一个String对象 5 String str = "abc"; 6 7 //通过三个方式获得String的Class对象。 8 Class c1 = str.getClass(); 9 Class c2 = String.class; 10 Class c3 = Class.forName("java.lang.String"); 11 12 //判断是否是同一份字节码。 13 System.out.println(c1==c2); 14 System.out.println(c2==c3); 15 16 //是否是基本类型 17 System.out.println(c1.isPrimitive()); 18 19 //基本类型和包装类型的字节码文件是不同的 20 System.out.println(int.class==Integer.class); 21 22 //可以通过包装类型获得其包装的基本类型的字节码 23 System.out.println(int.class==Integer.TYPE); 24 25 //判断数字是否是一个原始类型 26 System.out.println(int[].class.isPrimitive()); 27 28 //判断一个字节码是否是数组类型 29 System.out.println(int[].class.isArray()); 30 31 } 32 }
第二讲:理解反射的概念
一,反射:把java类中的各种成分映射成响应的java类。
- 概念:Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
- 作用:反射技术可以对类进行解剖。大大提高了程序的扩展性。
二,Class类中获取信息的方法:
- public ClassLoader getClassLoader() 返回该类的类加载器。
- public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException 返回一个
Constructor
对象,它反映此Class
对象所表示的类的指定公共构造方法。 - public Constructor <?>[] getConstructors() throws SecurityException 返回一个包含某些
Constructor
对象的数组,这些对象反映此Class
对象所表示的类的所有公共构造方法。 - public Constructor<?>[] getDeclaredConstructors() throws SecurityException 返回
Constructor
对象的一个数组,这些对象反映此Class
对象表示的类声明的所有构造方法。 - public Field getField(String name) throws NoSuchFieldException, SecurityException 返回一个
Field
对象,它反映此Class
对象所表示的类或接口的指定公共成员字段。 - public Method[] getMethods() throws SecurityException 返回一个包含某些
Method
对象的数组
第三讲:构造方法的反射应用
一,Constructor类的了解:
- public final class Constructor<T>extends AccessibleObject implements GenericDeclaration, Member
Constructor
提供关于类的单个构造方法的信息以及对它的访问权限。 - public Class<T> getDeclaringClass() 返回
Class
对象,该对象表示声明由此Constructor
对象表示的构造方法的类。 - public int getModifiers() 以整数形式返回此
Constructor
对象所表示构造方法的 Java 语言修饰符。 - public String getName() 以字符串形式返回此构造方法的名称。它总是与构造方法的声明类的简单名称相同。
- public Class <?>[] getParameterTypes() 按照声明顺序返回一组
Class
对象,这些对象表示此Constructor
对象所表示构造方法的形参类型。如果底层构造方法不带任何参数,则返回一个长度为 0 的数组。 - public boolean isVarArgs() 如果声明此构造方法可以带可变数量的参数,则返回 true;否则返回 false。
- public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException 使用此
Constructor
对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
- public final class Constructor<T>extends AccessibleObject implements GenericDeclaration, Member
二,明确程序的,编译时和运行时:
- 编译时:在程序的编译期间起作用,简单的作一些翻译工作。检查有没有粗心写错关键字,词法分析,语法分析之类的过程。
- 运行时:就是代码跑起来了.被装载到内存中去了。在内存中做些操作,做些判断。
三,创建一个对象的三种方式:
- 通过new 关键字和构造方法进行实例化。例: String s = new String("abc");
- 通过Class.newInstance ,进行实例化。此方法调用类中的无参构造方法。 例: String s = String.class.newInstance(); ==注:这种方式只会使用无参构造方法===
- 通过类的构造方法的 Constructor 对象的 newInstance(Object... initargs) 方法进行实例化 例: Constructor c = String.class.getConstructro(null); String s = c.newInstance(null); ==注:此种方式可以使用特定构造方法===
四,代码练习:
1 import java.lang.reflect.*; 2 3 4 public class Reflect_Method { 5 public static void main(String[] args) throws Exception { 6 7 //获取一个Class对象的指定构造方法 8 Constructor m = String.class.getConstructor(StringBuffer.class); 9 10 // 通过方法对象,创建类的对象 11 String s = (String) m.newInstance(new StringBuffer("abc")); 12 13 //通过Class对象创建类对象 14 String s2 = String.class.newInstance(); 15 16 s2="a"; 17 18 //直接实例化 19 String s3 = new String("abc"); 20 21 22 System.out.println(s.charAt(2)); 23 24 System.out.println(s2); 25 26 System.out.println(s3); 27 } 28 }
第四讲,第五讲:成员变量的反射,成员变量反射的综合案例
一,Field 类的了解:
- public final class Fieldextends AccessibleObject implements Member F
ield
提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。 - 方法:public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException 返回指定对象上此
Field
表示的字段的值。如果该值是一个基本类型值,则自动将其包装在一个对象中。 - 方法:public boolean getBoolean(Object obj) throws IllegalArgumentException, IllegalAccessException 获取一个静态或实例
boolean
字段的值。====此方法根据多种数据类型有多个重载方法。==== - 方法:public int getModifiers() 以整数形式返回由此
Field
对象表示的字段的 Java 语言修饰符。应该使用Modifier
类对这些修饰符进行解码。 - 方法:public String getName() 返回此
Field
对象表示的字段的名称。 - 方法:public void setBoolean(Object obj,boolean z) throws IllegalArgumentException, IllegalAccessException 将字段的值设置为指定对象上的一个
boolean
值。。====此方法根据多种数据类型有多个重载方法。====
- public final class Fieldextends AccessibleObject implements Member F
=========Field对象表示的不是对象的成员,而是类的成员==========
二,代码练习:
1 import java.lang.reflect.*; 2 3 4 //定义用来反射操作的类 5 class ReflectPoint { 6 private int x; 7 public int y; 8 9 public String str1 = "ball"; 10 public String str2 = "basketball"; 11 public String str3 = "itcase"; 12 13 public ReflectPoint(int x, int y) { 14 this.x = x; 15 this.y = y; 16 } 17 18 public String toString(){ 19 return str1+"::"+str2+"::"+str3; 20 } 21 }
1 import java.util.Arrays; 2 3 4 public class Reflect_Array { 5 public static void main(String[] args) { 6 7 //分别定义不同维数,不同类型的数组 8 int[] a1 = new int[]{1,2,3}; 9 int[] a2 = new int[5]; 10 int[][] a3 = new int[2][4]; 11 String[] a4 = new String[]{"a","b","c"}; 12 13 //具有相同维数相同类型的数组是共享一个字节码对象的 14 System.out.println(a1.getClass() == a2.getClass()); 15 16 //下面这句话编译期出错 17 //System.out.println(a1.getClass() == a4.getClass()); 18 19 //获取数组的字节码名字即类名 20 System.out.println(a1.getClass().getName()); 21 System.out.println(a4.getClass().getName()); 22 23 //获取数组的父类名字 24 System.out.println(a4.getClass().getSuperclass().getName()); 25 26 27 //数组是Object的子类,向上转型 28 Object obj = a1; 29 Object obj2 = a3; 30 31 //Object[] objs = a1; 32 33 //引用类型数组,可以向父类型数组转型 34 Object[] objs2 = a4; 35 Object[] objs3 = a3; 36 37 System.out.println(obj.getClass().getName()); 38 39 System.out.println(obj2.getClass().getName()); 40 41 System.out.println(objs2.getClass().getName()); 42 43 //使用Arrays操作数组 44 System.out.println(Arrays.asList(a4)); 45 } 46 }
第六讲:成员方法的反射
一,Method类的了解:
- public final class Methodextends AccessibleObject implements GenericDeclaration, Member M
ethod
提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。 - 方法:public int getModifiers() 以整数形式返回此
Method
对象所表示方法的 Java 语言修饰符。应该使用Modifier
类对修饰符进行解码。 - 方法:public String getName() 以
String
形式返回此Method
对象表示的方法名称。 - 方法:public Class<?>[] getParameterTypes() 按照声明顺序返回
Class
对象的数组,这些对象描述了此Method
对象所表示的方法的形参类型。如果底层方法不带参数,则返回长度为 0 的数组。 - 方法:public Class<?> getReturnType() 返回一个
Class
对象,该对象描述了此Method
对象所表示的方法的正式返回类型。 - 方法:public boolean isBridge() 如果此方法是 bridge 方法,则返回 true;否则,返回 false。
- 方法:public boolean isVarArgs() 如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false。
- 方法:public Object invoke(Object obj,Object... args) throws IllegalAccessException,IllegalArgumentException, InvocationTargetException 对带有指定参数的指定对象调用由此
Method
对象表示的底层方法。个别参数被自动解包,以便与基本形参相匹配,基本参数和引用参数都随需服从方法调用转换。
- public final class Methodextends AccessibleObject implements GenericDeclaration, Member M
二,jdk1.4与jdk1.5 invoke 方法的区别:
jdk1.4 public Object invoke(Object obj,Object[] args)
jdk1.5 public Object invoke(Object obj,Object... args) ====注:这是jdk1.5的新特性。可变参数====
三,专家设计模式:
专家模式:谁调用这个数据,就是谁在调用它的专家。
如人关门:
调用者:是门调用关的动作,对象是门,因为门知道如何执行关的动作,通过门轴之类的细节实现。
指挥者:是人在指挥门做关的动作,只是给门发出了关的信号,让门执行。
总结:变量使用方法,是方法本身知道如何实现执行的过程,也就是“方法对象”调用方法,才执行了方法的每个细节的。
四,代码练习:
1 import java.lang.reflect.*; 2 3 public class Reflect_Method { 4 public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 5 6 //定义Method对象,表示String中的charAt方法 7 Method m = String.class.getMethod("charAt", int.class); 8 9 //实例化String对象 10 String str = "abcd"; 11 12 //通过Method对象调用方法 13 System.out.println(m.invoke(str, 1)); 14 15 //jdk1.4 方法调用 16 System.out.println(m.invoke(str, new Object[]{2})); 17 } 18 }
第七讲:对接收数组参数的成员方法进行反射
一,练习目标:
写一个程序实现根据用户提供的类名去动态的调用它的main() 方法。
二,为什么要使用反射:
在写源程序时,并不知道使用者传入的类名是什么,但是虽然传入的类名不知道,而知道的是这个类中的方法有main这个方法。所以可以通过反射的方式,通过使用者传入的类名(可定义字符串型变量作为传入类名的入口,通过这个变量代表类名),内部通过传入的类名获取其main方法,然后执行相应的内容。
三,遇到的问题:
通过反射方式来调用这个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的语法解释,因此会出现参数类型不对的问题。
四,解决办法:
mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
mainMethod.invoke(null,(Object)new String[]{"xxx"});
五,代码练习:
1 import java.lang.reflect.Method; 2 3 4 //定义一个测试类 5 class Test{ 6 public static void main(String[] args){ 7 for(String arg : args){ 8 System.out.println(arg); 9 } 10 } 11 } 12 13 14 //用反射方式根据用户提供的类名,去执行该类中的main方法。 15 public class PerformedMain{ 16 17 public static void main(String[] args) throws Exception { 18 //普通方式 19 Test.main(new String[]{"123","456","789"}); 20 System.out.println("-----------------------------"); 21 22 //反射方式 23 String className=args[0]; 24 Class clazz=Class.forName(className); 25 26 Method method_Main=clazz.getMethod("main",String[].class); 27 //方式一:强制转换为超类Object,不用拆包 28 method_Main.invoke(null, (Object)new String[]{"123","456","789"}); 29 //方式二:将数组打包,编译器拆包后就是一个String[]类型的整体 30 method_Main.invoke(null, new Object[]{new String[]{"123","456","789"}}); 31 } 32 }
============此示例用eclipse运行时,需要在Run As——>RunConfigurations——>Arguments——>Program arguments中添加要执行的类名,如:Reflect.Test。========
第八讲:数组与Object的关系及其反射类型
一,具有相同Class 文件的数组判定 :具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。数组字节码的名字:有[和数组对应类型的缩写,如int[]数组的名称为:[I
二、Object[]与String[]没有父子关系,Object与String有父子关系,所以new Object[]{“aaa”,”bb”}不能强制转换成new String[]{“aaa”,”bb”}; Object x =“abc”能强制转换成String x =“abc”。
=======注:所有数组的父类是Object 所以数组类可以转换为Object对象:Object obj = new int[3]; 所有引用类型都是Object 的子类。所以引用类型的数组可以转换为Object的数组: Object obj[] = new String[3];。==========
三,如何得到某个数组中的某个元素的类型,
例:
int a = new int[3];Object[] obj=new Object[]{”ABC”,1};
无法得到某个数组的具体类型,只能得到其中某个元素的类型,
如:
Obj[0].getClass().getName()得到的是java.lang.String。
四,Array工具类用于完成对数组的反射操作。
Array.getLength(Object obj);//获取数组的长度
Array.get(Object obj,int x);//获取数组中的元素
五,代码练习:
1 import java.util.Arrays; 2 3 4 public class Reflect_Array { 5 public static void main(String[] args) { 6 7 //分别定义不同维数,不同类型的数组 8 int[] a1 = new int[]{1,2,3}; 9 int[] a2 = new int[5]; 10 int[][] a3 = new int[2][4]; 11 String[] a4 = new String[]{"a","b","c"}; 12 13 //具有相同维数相同类型的数组是共享一个字节码对象的 14 System.out.println(a1.getClass() == a2.getClass()); 15 16 //下面这句话编译期出错 17 //System.out.println(a1.getClass() == a4.getClass()); 18 19 //获取数组的字节码名字即类名 20 System.out.println(a1.getClass().getName()); 21 System.out.println(a4.getClass().getName()); 22 23 //获取数组的父类名字 24 System.out.println(a4.getClass().getSuperclass().getName()); 25 26 27 //数组是Object的子类,向上转型 28 Object obj = a1; 29 Object obj2 = a3; 30 31 //下面这个表达式不成立编译期出错 32 //Object[] objs = a1; 33 34 //引用类型数组,可以向父类型数组转型 35 Object[] objs2 = a4; 36 Object[] objs3 = a3; 37 38 System.out.println(obj.getClass().getName()); 39 40 System.out.println(obj2.getClass().getName()); 41 42 System.out.println(objs2.getClass().getName()); 43 44 //使用Arrays操作数组 45 System.out.println(Arrays.asList(a4)); 46 } 47 }
第九讲:数组的反射应用
一,学习目标:
通过Array 类实现对数组的反射操作。
二,Arrays.asList() 方法处理 int[] 和 String[] 的差异:
因为该方法参数定义为public static <T> List<T> asList(T... a) ,因为String[] 可以转换为Object[] 所以处理它是按照jdk1.5新特性,可变参数处理的,它作为一个参数传入。而int[] 由于不能转换为Object[] 所以他的处理是按照jdk1.4的规则作为多个参数进行处理,即把数组展开了。
三,代码练习:
1 import java.lang.reflect.Array; 2 3 4 5 public class Reflect_Array2 { 6 public static void main(String[] args) { 7 8 //创建一个数组作为参数传入 9 String[] s = new String[]{"abc","d","feg"}; 10 11 //普通对象 12 String str = "lisi"; 13 printObject(s); 14 printObject(str); 15 } 16 17 public static void printObject(Object obj){ 18 if(obj.getClass().isArray()){ 19 int len = Array.getLength(obj); 20 for(int i = 0 ; i<len; i++){ 21 System.out.println(Array.get(obj,i)); 22 } 23 }else{ 24 System.out.println(obj); 25 } 26 } 27 }
第十讲:ArrayList_HashSet的比较及Hashcode分析
一、哈希算法的由来:
若在一个集合中查找是否含有某个对象,通常是一个个的去比较,找到后还要进行equals的比较,对象特别多时,效率很低。有这么一种HashCode算法,有一个集合,把这个集合分成若干个区域,每个存进来的对象,可以算出一个hashCode值,根据算出来的值,就放到相应的区域中去。当要查找某一个对象,只要算出这个对象的hashCode值,看属于第几个区域,然后到相应的区域中去寻找,看是否有与此对象相等的对象。这样查找的性能就提高了。
二、要想HashCode方法有价值的话,前提是对象存入的是hash算法这种类型的集合当中才有价值。如果不存入是hashCode算法的集合中,则不用复写此方法。
三、如果没有复写hashCode方法,对象的hashCode值是按照内存地址进行计算的。这样即使两个对象的内容是想等的,但是存入集合中的内存地址值不同,导致hashCode值也不同,被存入的区域也不同。所以两个内容相等的对象,就可以存入集合中。
所以就有这样的说法:如果两个对象equals相等的话,你应该让他们的hashCode也相等。如果对象存入的不是根据hash算法的集合中,就不需要复写hashCode方法。
四、当一个对象存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则对象修改后的哈希值与最初存储进HashSet集合中的哈希值就不同了。在这种情况下,调用contains方法或者remove方法来寻找或者删除这个对象的引用,就会找不到这个对象。从而导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。
五,代码练习:
1 import java.util.*; 2 3 public class HashCodeDemo { 4 public static void main(String[] args) { 5 6 HashCodeTest h1 = new HashCodeTest(4,4); 7 HashCodeTest h2 = new HashCodeTest(4,4); 8 HashCodeTest h3 = new HashCodeTest(5,5); 9 10 Collection collections = new HashSet(); 11 12 collections.add(h1); 13 collections.add(h2); 14 collections.add(h3); 15 System.out.println(collections.size()); 16 } 17 } 18 19 class HashCodeTest{ 20 private int x; 21 private int y; 22 23 24 public HashCodeTest(int x, int y) { 25 this.x = x; 26 this.y = y; 27 } 28 29 30 @Override 31 public int hashCode() { 32 final int prime = 31; 33 int result = 1; 34 result = prime * result + x; 35 result = prime * result + y; 36 return result; 37 } 38 39 40 @Override 41 public boolean equals(Object obj) { 42 if (this == obj) 43 return true; 44 if (obj == null) 45 return false; 46 if (getClass() != obj.getClass()) 47 return false; 48 HashCodeTest other = (HashCodeTest) obj; 49 if (x != other.x) 50 return false; 51 if (y != other.y) 52 return false; 53 return true; 54 } 55 56 57 }