【黑马程序员】反射

 

欢迎讨论,学习交流java

 

参考思维导图             张孝祥老师高新视频反射部分17-27的总结

 

一、反射的概念

     java特点,面向对象,可移植,多线程,动态

    “程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。

   

   Reflection-反射【百度百科】

    反射Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知称的class的内部信息,包括其modifierspublic, static 等等)、superclass(例如Object)、实现之interfaces(例如Serializable),也包括fieldsmethods的所有信息,并可于运行时改变fields内容或调用methods

 

 一句话总结反射技术可以对类进行解剖。

大神版总结:反射就是把Java类中的各种成分映射成相应的java

 

以下是摘自某科技文摘对反射的解析,

     通过以下语句对反射有一个更清楚的认识:Person p=new Person(); 这是什么?当然是实例化一个对象了,开始这种实例化对象方法存在一个问题,就是必须要知道类名Person,才可以实例化它的对象,这样在应用方面会受到限制,,那么有没有有一种方式可以不通过这个类的类名就可以实例化它的对象呢? 当然有,可以采用反射技术来实现。

 

如何获得这些属性呢?-------掌握反射使用的类

 java提供了java.lang.reflect包,用于实现反射机制,

            java类的Class类提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息

          这些信息就是用相应类的实例对象来表示,它们是Field类Method类Contructor类Package类等等

 

 

通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?

这正是学习和应用反射的要点。

 

二.反射的基石类,也可以称为反射的入口 -------Class类

 
       Clas类  是java反射机制的起源,针对任何想探查的类,只有先为它产生一个Class对象,接下来才能由这个Class对象调用为数十多个反射方法,因此,利用反射方法动态访问某个类 要经过下面三步:
 
  1.         创建一个CLass对象,
  2.          利用创建的Class对象获取类的内部信息,
  3.          利用java.lang.refelect包的反射方法,动态处理 上一步所获得的类内部信息
 
简单来说:两步骤,1,获取字节码对象,2,调用方法操作未知类的内部信息,下面开始分析
 
      1.Class类代表Java类,它的各个实例对象又分别对应什么呢?
 
对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?
答案就是 Class类型【所有的字节码对象 都属于 Class类型】
 
      2.如何得到各个字节码对应的实例对象(Class类型)?或者问如何获取字节码对象?获取字节码对象的方式有几种?
1.   类名.class,例如,System.class     ,String.class                                               小写class表示文件
2.   对象.getClass(),例如,new Date().getClass()      ,new String().getClass();    大写Class表示类型
3.   Class.forName("类名"),例如,Class.forName("java.util.Date");              注意forName方法只接收字符串数据!
注意: 引号中的类名可以是未知的;引号中的类名必须包含包的路径,否则会出错,
 
/*
 * 反射的测试类
 * 测试获取字节码文件的三中方式
 */
public class TestDemo {

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		String str="abc";
		//获取字节码文件的3种方式
		Class cs1 = str.getClass();
		Class cs2 = String.class;
		Class cs3 = Class.forName("java.lang.String");
		//测试三种方式获取的是否是同一个字节码文件
		System.out.println(cs1==cs2);//true
		System.out.println(cs1==cs3);//true
		System.out.println(cs1.equals( cs3));//true
		
 	}

}

 
九个预定义Class实例对象:
参看Class.isPrimitive方法的帮助
包括八种基本类型( byteshortintlongfloatdoublecharboolean)的字节码对象和一种返回值为void类型的void.class
Int.class == Integer.TYPE

数组类型的Class实例对象
Class.isArray()
总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void…
 
      3、 Class 类中的方法

       static Class forName(String className)

        返回与给定字符串名的类或接口的相关联的Class对象,方法是静态的,可以直接被类名调用,

        Class getClass()

        返回的是Object运行时的类,即返回Class对象即字节码对象

         C  onstructor 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对象所表示的实体名称。

 

         boolean isArray()

        判定此Class对象是否表示一个数组

        boolean isPrimitive()

        判断指定的Class对象是否是一个基本类型。

       

         T newInstance()

        创建此Class对象所表示的类的一个新实例。

 

4.通过 Class对象获取类实例

   CLass类是没有构造方法的,因此只能通过方法获取类实例对象。之前我们用的已知类,创建对象的做法:

      如:Person p=new Person(); 

        (1)查找并加载XX.class文件进内存,并将该文件封装成Class对象。

       (2)再依据Class对象创建该类具体的实例。

        (3)调用构造函数对对象进行初始化。

 

现在用Class对象获取类实例对象的做法:

 

               1.创建字节码对象关联指定的类

                    String className="包名.Person";

                     Class clazz=Class.forName(className);

                  2.通过newInstance()方法会使用该类的空参数构造函数进行初始化

                     Object obj=clazz.newInstance();

//Person类  
public class Person {  
    private String name;  
    public int age;  
    public Person(){  
        System.out.println("Person is run");  
    }  
    public Person(String name,int age){  
        this.age=age;  
        this.name=name;  
    }  
      
    public String toString(){  
        return name+":"+age;  
    }  
}  
 //Class 
public class CreateClassDemo {  
    public static void main(String[] args) throws Exception {  
        createPersonClass();  
    }  
    //通过Class对象创建类实例方法  
    public static void createPersonClass() throws Exception{  
        //获取Person类的Class对象  
        String className="Person";  
        Class clazz=Class.forName(className);  
        //通过Class类的newInstance方法获取Person类的无参构造函数实例  
        Person p=(Person)clazz.newInstance();  
    }  
}  


三。Constructor类

 

1.Constructor 代表某个类中的一个构造方法
2.得到某个类所有的构造 方法:
例:  通过一个数组接收
Constructor [] constructors= Class.forName("java.lang.String").getConstructors();
3.得到某一个构造方法:
例:
 Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
注:获得方法时要用到类型,用于表示你要获得指定参数的构造方法!
 
4.创建实例对象:
通常方式:String str = new String(new StringBuffer("abc"));
反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc"));
调用获得的方法时要用到上面相同类型的实例对象
利用Constructor类来创建类实例的好处是可以指定构造函数,而Class类只能利用无参构造函数创建类实例对象。
 
 
 
5、Class.newInstance()方法:.没有理解的部分:
例:String obj = (String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。

 

通过Constructor创建Peron类实例对象:

 

public static void createPersonClass_2()throws Exception
    {
    	String className="Person";
    	Class clazz=Class.forName(className);//获取Person类字节码文件
    	
    	//获取Person类构造函数的对象
    	java.lang.reflect.Constructor con=clazz.getConstructor(String.class,int.class);
    	Person p=(Person)con.newInstance("lisi",40);
    	System.out.println(p);
    }


 

 四。Field类

 

Field类代表某个类中的一个成员变量,也叫做字段
 
 
 
 

该类中常用方法:

        Field getField(String s);//只能获取公有字段

        Field getDeclaredField(String s);//获取该类中任意成员变量,包括私有

        setAccessible(ture);// 如果是私有字段,要先将该私有字段进行取消权限检查的能力。也称暴力反射访问。

public static void getPersonField()throws Exception
    {
    	//获取字节码文件
    	Class cls=Class.forName("Person");
    	//用反射方法实例化Person类的对象
    	Person p=(Person)cls.newInstance();
    	
    	//获取所有的成员变量,需要import java.lang.reflect.Field;
    	Field[] fields=cls.getDeclaredFields();
    	for(Field f:fields)
    	{
    		System.out.println(f);
    	/* 输出结果:
    	 * private java.lang.String Person.name
     	   public int Person.age
     	*/
    	}
   	
    	//获取指定的成员变量age
    	Field field1=cls.getField("age");
    	System.out.println(field1);
    	//输出结果public int Person.age
    	
    	//通过Field类的set方法   设置公有成员age的值并显示
    	field1.set(p,10);
        //System.out.println(p.age);方法1 通过对象P输出age
    	//方法2,通过Field类的get方法 获得 Person对象,输出
    	System.out.println(field1.get(p));
    
    	
       //暴力反射访问
       //获取指定的成员name,再暴力访问Person类的私有成员name
    	//Field fname=cls.getField("name");//错误
    	Field fname=cls.getDeclaredField("name");//正确
    	fname.setAccessible(true);//暴力反射
    	fname.set(p, "chaofan");
    	System.out.println(fname.get(p));
    }

 
 
作业未完:将任意一个对象中的所有String类型的成员变量所对一个的字符串内容中的字符“b”改为字符“a”
 
 
 

五、Method

1、Method类某个类中的一个成员方法

2、得到类中的某一个方法:
例子:     Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
3、调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1));
如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!
 
4、常用 方法

        Method[] getMethods();//只获取公共和父类中的方法。

        Method[] getDeclaredMethods();//获取本类中所有方法包含私有。

        Method   getMethod("方法名",参数.class(如果是空参可以写null));

        Object invoke(Object obj ,参数);//调用方法

        如果方法是静态,invoke方法中的对象参数可以为null

 

public static void getPersonMethod()throws Exception
    {
    	//获取Person字节码文件,反射创Person类对象
    	Class cls=Class.forName("Person");
    	Person p=(Person)cls.newInstance();
    	
    	//获取所有方法,导入import java.lang.reflect.Method;
    	Method[] m1=cls.getMethods();
    	for(Method m:m1)
    	{
    		System.out.println(m+"hahahaha");
    	}
    	//通过输出上述getMethods方法,居然输出了父类的方法,学习到了!
    	
    	//获取指定的方法, 记住格式  【“方法名不带括号”,参数列表或者null】
    	Method m2=cls.getMethod("toString", null);
    	
    	System.out.println(m2.invoke(p, null));
    }


5补充:jdk1.4和jdk1.5的invoke方法的区别:

Jdk1.5:public Object invoke(Object obj,Object... args)
Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。---------------------------摘自张孝祥老师的PPT课件。

      

                                                                                    

 

6.用反射方式执行某个类中的main方法

在写源程序时,并不知道使用者传入的类名是什么,但是虽然传入的类名不知道,而知道的是这个类中的方法有 main 这个方法。所以可以通过反射的方式,通过使用者传入的类名(可定义字符串型变量作为传入类名的入口,通过这个变量代表类名),内部通过传入的类名获取其 main 方法,然后执行相应的内容。
问题:
启动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的语法解释,因此会出现参数类型不对的问题。
 
解决办法:
mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
mainMethod.invoke(null,(Object)new String[]{"xxx"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了

 

 具体代码有一个错误一直解决不了,看了N遍老师视频也不懂错误原理,日后开发若遇到这个错误再来捉摸

import java.lang.reflect.Method;


class Test{
	
	public static void main(String[] args){
		for(String arg:args){
			System.out.println(arg);
		}
	}
}

public class TestMain {

	public static void main(String[] args) throws Exception{
		// TODO Auto-generated method stub
		//普通方式:类名.静态方法
		Test.main(new String[]{"111","11112"});
		System.out.println("----------");
		
		//反射方式
		Class cls=Class.forName(args[0]);
		Method methodMain=cls.getMethod("main", String[].class);
		methodMain.invoke(null, (Object)new String[]{"111","222","2122"});
	}

}

Exception in thread "main" 111
11112
----------
java.lang.ArrayIndexOutOfBoundsException: 0
	at TestMain.main(TestMain.java:22)


 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值