黑马程序员_java高新技术总结【2】(反射)

                                                        --------------android培训java培训、期待与您交流! --------------

一、反射的概念

反射:

        Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

        简单一句话:反射技术可以对类进行解剖。

       
       反射就是把Java类中的各种成分映射成相应的java类。

       例如:一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。

        表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。

一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。


二、反射的基石Class类

1、
Person类代表人,它的实例对象就是张三,李四这样一个个具体的人, Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。对比提问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?
人Person
Java类Class

2、Class类代表Java类,它的各个实例对象又分别对应什么呢?

对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?
如何得到各个字节码对应的实例对象(Class类型)
类名.class,例如,System.class
对象.getClass(),例如,new Date().getClass()
Class.forName("类名"),例如,Class.forName("java.util.Date");

九个预定义Class实例对象:
参看Class.isPrimitive方法的帮助
包括八种基本类型( byte short int long float double char boolean )的字节码对象和一种返回值为 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对象即字节码对象

        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 isArray()

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

        boolean isPrimitive()

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

        T newInstance()

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


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

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

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

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

        (3)调用构造函数对对象进行初始化。如:Person p=new Person();

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

       (1)查找并加载指定名字的字节码文件进内存,并被封装成Class对象。

      ( 2)通过Class对象的newInstance方法创建该Class对应的类实例。

       (3)调用newInstance()方法会去使用该类的空参数构造函数进行初始化。

             如:

                     String className="包名.Person";

                     Class clazz=Class.forName(className);

                     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);  
        //通过newInstance方法获取类的无参构造函数实例  
        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类实例对象:
//通过Constructor对象来创建类实例方法  
public static void createPersonClass_2() throws Exception{  
    //获取Person类的Class对象  
    String className="Person";  
    Class clazz=Class.forName(className);   
          
    //获取指定构造函数的类实例  
    Constructor con=clazz.getConstructor(String.class,int.class);  
    Person p=(Person) con.newInstance("lisi",30);  
    System.out.println(p.toString());  
}  

四、
Field类

Field类代表某个类中的一个成员变量
方法:

 Field getField(String s);//只能获取公有和父类中公有

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

        setAccessible(ture);

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

        set(Object obj, Object value);//将指定对象变量上此Field对象表示的字段设置为指定的新值。

        Object get(Object obj);//返回指定对象上Field表示的字段的值。

//获取Person对象的成员变量  
public static void getPersonField() throws Exception{     
    Class clazz=Class.forName("Person");  
    Person p=(Person)clazz.newInstance();  
          
    //获取所有的成员变量  
    Field[] fs=clazz.getFields();  
    for(Field f:fs){  
        System.out.println(f);  
    }  
          
    //获取指定的成员变量  
    Field fage=clazz.getField("age");  
    Field fname=clazz.getDeclaredField("name");  
          
    //显示改变后的值  
    fage.set(p, 20);  
    System.out.println(fage.get(p));  
          
    //暴力访问私有变量  
    fname.setAccessible(true);  
    fname.set(p, "zhangsan");  
    System.out.println(fname.get(p));  
}  

五、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、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})形式。
5、 方法

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

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

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

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

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

//获取Person类中的方法  
public static void getPersonMethod() throws Exception{  
    Class clazz=Class.forName("Person");  
    Person p=(Person)clazz.newInstance();  
          
    //获取所有方法  
    Method[] mes=clazz.getMethods();//只获取公共的和父类中的。  
    //mes=clazz.getDeclaredMethods();//获取本类中包含私有。  
    for(Method me:mes){  
        System.out.println(me);  
    }  
          
    //获取单个方法  
    Method me=clazz.getMethod("toString", null);  
    Object returnVaule=me.invoke(p, null);  
    System.out.println(returnVaule);      
}  

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"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了
package cn.itheima;  
//定义一个测试类  
class Test{  
    public static void main(String[] args){  
        for(String arg : args){  
            System.out.println(arg);  
        }  
    }  
}  
//用反射方式根据用户提供的类名,去执行该类中的main方法。  
import java.lang.reflect.Method;  
  
public class PerformedMain{  
  
    public static void main(String[] args) throws Exception {  
        //普通方式  
        Test.main(new String[]{"123","456","789"});  
        System.out.println("-----------------------------");  
                  
        //反射方式  
        String className=args[0];  
        Class clazz=Class.forName(className);  
                  
        Method methodMain=clazz.getMethod("main",String[].class);  
        //方式一:强制转换为超类Object,不用拆包  
        methodMain.invoke(null, (Object)new String[]{"123","456","789"});  
        //方式二:将数组打包,编译器拆包后就是一个String[]类型的整体   
        methodMain.invoke(null, new Object[]{new String[]{"123","456","789"}});  

六、数组的反射
1、具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
2、基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
Arrays.asList()方法处理int[]和String[]时的差异:
int [] a1 = new int[]{1,2,3};      
String [] a2 = new String[]{"a","b","c"}; 
System.out.println(a1);//[I@4caaf64e    
System.out.println(a2);//[Ljava.lang.String;@6c10a234    
System.out.println(Arrays.asList(a1));//[I@4caaf64e    
System.out.println(Arrays.asList(a2));//[a, b, c]    
            
/* Arrays.asList()方法处理int[]和String[]时的差异。  
 * 打印Arrays.asList(a1);还是跟直接打印a1是一样的  
   打印Arrays.asList(a2);就会把a2的元素打印出来。  
   这是因为此方法在JDK1.4版本中,接收的Object类型的数组,  
   而a2可以作为Object数组传入。但是a1不可以作为Object数组传入,所以只能按照JDK1.5版本来处理。  
   在JDK1.5版本中,传入的是一个可变参数,所以a1就被当作是一个object,也就是一个参数,  
   而不是数组传入,所以打印的结果还是跟直接打印a1一样。  
*/  

Array工具类用于完成对数组的反射操作:

Array.getLength(Object obj);//获取数组的长度

Array.get(Object obj,int x);//获取数组中的元素


七、反射的作用-实现框架功能
1、框架与框架要解决的核心问题
我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。

2、框架要解决的核心问题
我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new 某个类的实例对象了,而要用反射方式来做。
3、 简单框架程序的步骤:

(1)右击项目File命名一个配置文件如:config.properties,然后写入配置信息。如键值对:className=java.util.ArrayList,等号右边的配置键,右边是值。

( 2)代码实现,加载此文件:

                ①将文件读取到读取流中,要写出配置文件的绝对路径。

                    如:InputStream is=new FileInputStream(“配置文件”);

                ②用Properties类的load()方法将流中的数据存入集合。

                ③关闭流:关闭的是读取流,因为流中的数据已经加载进内存。

( 3)通过getProperty()方法获取className,即配置的值,也就是某个类名。

( 4)用反射的方式,创建对象newInstance()。

( 5)执行程序主体功能







































































































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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值