黑马程序员---java基础-Java之反射
一、反射作用
u 1、为什么要用反射
Java程序中的许多对象在运行时都会出现两种类型:编译时的类型和运行时的类型,如Person p=new Student(); 这行代码将会生成一个p的变量,该变量编译时类型是Person,运行时的类型是Student;除此之外,还有更极端的情形,程序在运行时接受到外部传入的一个对象,该对象的编译时的类型是Object,但是程序又需要调用该对象运行时类型的方法。
为了解决这些问题,程序需要在运行时发现对象和类的真实信息。为了解决这个问题,我们有一下两种做法:
1、假设在编译时和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接先使用instanceof运算符进行判断,再利用强制类型转换将其转换成其运行时类型的变量即可。
2、编译时根本无法预知该对象和类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。
u 2、什么是反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射就是把Java类中的各种成分,映射成相应的Java类,例如一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等信息也用一个个的Java类来表示,就想汽车是一个类,汽车中的发动机,变速箱等等也是一个个类。表示Java类的Class类显然要提供一系列的方法,来获得其中的变量,方法、构造函数、修饰符、包等信息,这些信息就是用相应类的实例对象来表示,他们是Field/Method/Constructor/Package等等
u 3、反射作用
在运行时判断任意一个对象所属的类;
在运行时构造任意一个类的对象;
在运行时判断任意一个类所具有的成员变量和方法;
在运行时调用任意一个对象的方法;
生成动态代理。
序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields(成员属性)设值、或唤起其methods成员方法。
反射可以程序运行时,改变程序结构或变量类型,比如为一个Integer集合添加String元素。也可以改变或获取成员属性(字段),获取构造方法,获取并调用成员方法。
1、获得Class对象(即返回字节码的方法)
--->System.class、 Person.getClass()、Class.forName("java.util.data")
大家都知道,每个类被加载之后,系统就会对该类生成一个对应的class对象,通过该class 对象就可以访问到JVM中的这个类。
在Java程序中获得Class对象通常有如下3中方式:
· 使用Class类的forName(String clazzName)静态方法。该方法需要传入字符串参数, 该字符串参数的值是某个类的全限定类名(必须添加完整包名)
String className ="cn.itcast.bean.Person"; //这里要注意:是包名.类名的字符串表现形式,这与导不导包没有关系 Class clazz = Class.forName(className);
· 调用某个类的class属性来获取该类对应的Class对象。例如Person.class将会返回 Person类对应的class对象。
相对简单,但是还是要明确用到类中的静态成员,缺点:不够扩展
public static void getClassObject_2() { Class clazz = Person.class;
· 调用某个对象的getClass()方法。该方法是java.lang.Object类中的一个方法,所以 所有的Java对象都可以调用该方法,该方法将会返回该对象所属类对应的Class 对象。想要用这种方式,必须要明确具体的类,并创建对象。缺点是不够简单:
1 String className ="cn.itcast.bean.Person"; 2 3 //这里要注意:是包名.类名的字符串表现形式,这与导不导包没有关系 4 5 Class clazz = Class.forName(className);
对于第一种和第二种方式都是直接根据类来获取该类的class对象,相比之下,第二种方式有如下两种优势
·代码更安全。程序在编译阶段就可以检查需要访问的Class对象是否存在
·程序性能更好。因为这种方式无需调用方法,所以性能更好
大部分时候我们都应该使用第二种方式来获取指定类的Class对象。但是如果我们只有一个字符串,如“java.lang.String”,若需要获取该字符串对应的Class对象,则只能使用第一种方法,使用Class 的forName(String clazzName)方法来获取Class对象时,该方法可能会抛出一个ClassNotFoundException异常。所以第一种和第二种是最常用的方法。
一旦获得了某个类所对应的Class对象之后,程序就可以调用Class对象的方法来获得该对象和该类的真实信息了。
1 import java.lang.reflect.Array; 2 import java.lang.reflect.Constructor; 3 import java.lang.reflect.Field; 4 import java.lang.reflect.Method; 5 import java.util.Arrays; 6 7 public class ReflectText { 8 /** 9 * @param args 10 */ 11 public static void main(String[] args)throws Exception { 12 String str1 = "abc"; 13 Class cls1 = str1.getClass(); 14 Class cls2 = String.class; 15 Class cls3 = Class.forName("java.lang.String"); 16 System.out.println(cls1==cls2);true 17 System.out.println(cls1==cls2);true 18 19 System.out.println(cls1.isPrimitive());//cls1是否是基本类型字节码 String非基本类型 20 System.out.println(int.class.isPrimitive());//基本类型 21 System.out.println(int.class==Integer.class);//Integer类型的字节码 22 System.out.println(int.class==Integer.TYPE);//是否和包中的基本类型的字节码相同 23 System.out.println(int[].class.isPrimitive());//是一个数组 ,但是不是原始类型 数组也是一种类型 24 System.out.println(int[].class.isArray());//是否是个数组呢 25 } 26 }
总之只要是在源程序中出现的类型都有各自的Class实例对象,例如int[] void都有各自的Class实例对象,可以调用isArray() 或者isPrimitive()等方法!
二、反射应用
u 1、Constructor类---->构造方法的反射应用
得到了构造方法,我们就可以得到自己所属的类getDeclaringClass(),得到前边的修饰符getModifiors(),最重要的是构造一个实例对象newInstance()而我们new 一个构造方法的时候一定要传一个相应的对象进去
Constuctor constructor1=String.class.getConstructor(StringBuffer.class);//StringBuffer表示选择那个构造方法
String str1=(String)constructor1.newInstance(StringBuffer("abc"));//表示用这个构造方法的时候还得传一个StringBuffer的对象进去,两个必须一致
编译的时候是机器语言11000111之类的,并不知道这个构造方法的返回值是String,所以得加上(String)的强制类型转换,只有在运行的时候才知道。
Constructor constructor1=String.class.getConstructor(StringBuffer.class); //获得方法时
String str2=(String)constructor1.newInstance(new StringBuffer("abc"));//调用获得的方法时
//输出str2的第三个字符
System.out.println(str2.charAt(2));
过程:得到构造方法,再用构造方法newInstance()这就是反射
·得到某个类所有的构造方法:
Constructor[] Constructor=Class.forName("java.lang.String").getConstructors();
·得到某一个构造方法 :Constructor
Constructor=Class.forName("java.lang.String").getConstructor(StringBuffer.class);
(根据接受的参数类型获得制定的构造方法,参数类型是StringBuffer,里面就加上StringBuffer.class)
里面也可以写多个参数类型的class对象
Class.newInstance()方法 :
·例子:String Obj=(String)class.forName("java.lang.String").newInstance();
·该例子内部先得到默认的构造方法,然后用该构造方法创建实例对象
·该方法内部的具体代码是用到了缓存机制来保存默认构造方法的实例对象。
u 2、Filed类----->成员变量的反射应用
1 import java.lang.reflect.Field; 2 3 public class ReflectText{ 4 public static void main(String[] args) throws Exception{ 5 ReflectPoint pt1=new ReflectPoint(3,5); 6 Field fieldY=pt1.getClass().getField("y"); 7 Field fieldX=pt1.getClass().getDeclaredField("x"); 8 fieldX.setAccessible(true); 9 System.out.println(fieldY.get(pt1)); 10 System.out.println(fieldX.get(pt1)); 11 changeString(pt1); 12 System.out.println(pt1); 13 } 14 static void changeString(Object obj) throws Exception{ 15 //得到所有的字段 16 Field[] fields=obj.getClass().getFields(); 17 //对字段进行迭代 18 for(Field field : fields){ 19 // if(field.getType().equals(String.class)){} 20 //只要是比较字节码的比较应该用“==”比较,因为是同一份字节码 21 //意思是只要是String类型的就要取得他的值,然后进行替换 22 if(field.getType()==(String.class)){ 23 String oldValue=(String)field.get(obj); 24 //将取得的字符串其中的"b"改成"a",调用String的方法replace() 25 String newValue=oldValue.replace('b', 'a'); 26 field.set(obj,newValue); 27 } 28 } 29 } 30 } 31 class ReflectPoint { 32 private int x; 33 public int y; 34 @Override 35 public String toString() { 36 return "ReflectPoint [str1=" + str1 + ", str2=" + str2 + ", str3=" 37 + str3 + "]"; 38 } 39 public String str1="ball"; 40 public String str2="basketball"; 41 public String str3="acdefg"; 42 //alt+shift+s为x和y生成构造方法 43 public ReflectPoint(int x, int y) { 44 super(); 45 this.x = x; 46 this.y = y; 47 } 48 }
上边的changeString()方法应牢记,懂得了这个方法,就能掌握反射了!
u 3、Method类----->成员方法的反射应用
普通的调用方法:
1 class ReflectText5 2 { 3 public static void main(String[] args) 4 { 5 //用普通方法调用 6 TestArguments.main(new String[]{"111","222","333"}); 7 } 8 } 9 class TestArguments 10 { 11 public static void main(String[] args) 12 { 13 for(String arg : args){ 14 System.out.println(arg); 15 } 16 }
反射调用:
1 import java.lang.reflect.Method; 2 class ReflectText5 { 3 public static void main(String[] args) throws Exception { 4 // 用普通方法调用 5 // TestArguments.main(new String[]{"111","222","333"}); 6 // 用反射方法调用 7 String startingClassName = args[0]; 8 Method mainMethod = Class.forName(startingClassName).getMethod("main", 9 String[].class); 10 // main是静态的,调用静态的方法不用传递参数,所以是null 11 // 下面两种方法都可以 12 // mainMethod.invoke(null, new Object[]{new String[]{"111","222","333"}}); 13 mainMethod.invoke(null, (Object)new String[]{"111","222","333"}); 14 } 15 } 16 17 class TestArguments { 18 public static void main(String[] args) { 19 for (String arg : args) { 20 System.out.println(arg); 21 } 22 } 23 }
u 4、数组的反射
具有相同维数和元素类型的数组属于同一类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass方法返回的父类为Object类对应的Class.
基本类型的一维数组可以被当做Object类型使用,不能当作Object[]类型使用,非基本类型(int)的一维数组,既可以当作Object类型使用,又可以当作Object[]类型使用
Array.asList()方法处理int[],String[]的区别
Array工具类用于完成对数组的反射操作
1 import java.util.ArrayList; 2 import java.util.Collection; 3 import java.util.HashMap; 4 import java.util.HashSet; 5 public class ReflectText8 { 6 public static void main(String[] args) { 7 // Collection collections=new ArrayList(); 8 //打印出来是4 9 Collection collections=new HashSet(); 10 //打印出来是3 11 ReflectPoint pt1=new ReflectPoint(3,3); 12 ReflectPoint pt2=new ReflectPoint(5,5); 13 ReflectPoint pt3=new ReflectPoint(3,3); 14 collections.add(pt1); 15 collections.add(pt2); 16 collections.add(pt3); 17 collections.add(pt1); 18 System.out.println(collections.size()); 19 } 20 }