黑马程序员--反射Reflect

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
注:视频来源,张孝祥老师的 JAVA 高薪技术。

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

例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。

一、Class.forName()

Class.forName() 的作用是用来返回字节码。

1)返回字节码的方式有两种:

1、这份字节码曾经被加载过,已经存在于 java 虚拟机中。

2、虚拟机中还没有字节码,需要使用类加载器加载,把加载的字节码放在 java 虚拟机中。

2)获取字节码对应的实例对象( Class类型),获取字节码的三种方式

1、类名.class,例如,System.class。虚拟中已经存在该类的字节码,只有用类名.class 就可以得到。

2、对象.getClass()。例如,new Date().getClass()。用这个方式去获取一个类的字节码。

3、Class.forName(“类名”)。例如,Class.forName(“java.util.Date”);主要用于反射。

3)一个奇怪的问题:加载了字节码,并调用了其getMethods之类的方法,但是没有看到类的静态代码块被执行,只有在第一个实例对象被创建时,这个静态代码才会被执行。准确的说,静态代码块不是在类加载时被调用的,而是第一个实例对象被创建时才执行的。

4)isPrimitive():判断是否是基本数据类型的字节码。不是,返回 false。是 ,返回 true。

5)字节码的使用注意:

1、int 的字节码不等于 Integer 的字节码 ,返回 false。

如:System.out.println(int.class == Integer.class);

2、int是 Integer 的包装类型中的,所以是同一份字节码,返回 true。

如:System.out.println(int.class == Integer.TYPE);

3、数组类型的Class 实例对象Class.isArray();

如:System.out.println(int[].class.isArray());

总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void

二、Constructor类:构造器

1、 如果指定的类中没有空参数的构造函数,或者要创建的类对象需要通过指定的构造函数进行初始化。这时怎么办呢?这时就不能使用Class类中的newInstance方法了。既然要通过指定的构造函数进行对象的初始化。就必须先获取这个构造函数——Constructor。Constructor代表某个类的构造方法。

2、 得到某个类的所有构造方法,有如下两种写法,他们的作用是一样的。
1)得到这个类的所有构造方法:如得到上面示例中Person类的所有构造方法
Constructor[] cons = Class.forName(“cn.itheima.Person”).getConstructors();
2)获取某一个构造方法:
Constructor con=Person.class.getConstructor(String.class,int.class);
Demo:

package day26;

import java.lang.reflect.Constructor;

/**
 * 
 *@author XiaLei
 */
public class Demo {

    public static void main(String[] args) throws Exception{
        String str=new String(new StringBuffer("ads"));
        //通过获得带参数的构造函数创建对象。
        Constructor<?> constructor=Class.forName("java.lang.String").getConstructor(StringBuffer.class);
        String str1=(String)constructor.newInstance(new StringBuffer("ads"));
        System.out.println(str);
        //空参数构造函数创建对象
        String str2=(String)Class.forName("java.lang.String").newInstance();
        System.out.println(str2);

    }

}

三、Field类

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

问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。

得到字节码,再通过getField得到对应的变量名,如果成员变量被私有化,或者未定义,那么久会报错,java.lang.NoSuchFieldException: x

注意:

fieldy不是对象身上的变量,是类上的,要用它去取某个对象上对应的值。

想要访问私有成员变量,就得使用getDeclaredField 和 setAccessible。这就是暴力反射,不管是否私有化,都能通过该方法获取。

code:

package day26;

import java.lang.reflect.Field;

/**
 * 
 *@author XiaLei
 */
public class FieldDemo {

    public static void main(String[] args)throws Exception {

        ReflectPoint pt1=new ReflectPoint(3,5);
        //得到字节码,再通过getField得到对应的变量名,如果成员变量被私有化,或者未定义,  
        //那么久会报错,java.lang.NoSuchFieldException: x 
        Field fieldX=pt1.getClass().getDeclaredField("x");//暴力反射,不管是否私有化,都能通过该方法获取 
        //那 fieldy 的值是多少呢? 5?错  
        //fieldy不是对象身上的变量,是类上的,要用它去取某个对象上对应的值  
        fieldX.setAccessible(true);
         //想要访问私有成员变量,就得使用getDeclaredField 和 setAccessible
        System.out.println(fieldX.get(pt1));
        Field fieldY=pt1.getClass().getField("y");
        System.out.println(fieldY.get(pt1));

        System.out.println(fieldX.getName());

        changeStringValue(pt1);
        System.out.println(pt1);
    }

    private static void changeStringValue(Object obj)throws Exception {

        Field[] fields=obj.getClass().getFields();

        for (Field field : fields){
            if (field.getType()==String.class){//注意这里用== 而不用equals的区别:字节码(二进制数值)用 == 比较,不要用 equals 比较  

                String oldValue=(String)field.get(obj);//从对象身上拿到值。
                String newValue=oldValue.replace('b', 'a');
                field.set(obj, newValue);
            }
        }
    }   
}
class ReflectPoint {

    public int y;
    private int x;
    public String str1="bascketball";
    public String str2="baby";
    public String str3="sdfa";
    public ReflectPoint(int y, int x) {
        super();
        this.y = y;
        this.x = x;
    }
    public String toString(){
        return str1+":"+str2+":"+str3;
    }
}

四、Method类

Method 类代表某个类中的一个成员方法。

1、得到类中的某一个方法:

Method charAt = Class.forName(“java.lang.String”).getMethod(“charAt”, int.class);

2、调用方法:

通常方式:System.out.println(str.charAt(1));

反射方式: System.out.println(charAt.invoke(str, 1));

3、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})形式。

4、使用注意:

如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法。

Class.getMethod(name,Class… args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。

package day26;

import java.lang.reflect.Method;

/**
 * 
 *@author XiaLei
 */
public class MethodDemo {

    public static void main(String[] args) throws Exception{

        String str="abc";
        Method charAtMethod=String.class.getMethod("charAt", int.class);
        System.out.println(charAtMethod.invoke(str, 2));

        String classStartingName = args[0];

        Method mainMethod = Class.forName(classStartingName).getMethod("main", String[].class);

         //方式一:强制转换为超类Object,不用拆包  
        mainMethod.invoke(null, (Object)new String[]{"111","222","333"});  

        //方式二:将数组打包,编译器拆包后就是一个String[]类型的整体   
       // methodMain.invoke(null, new Object[]{new String[]{"111","222","333"}});  
    }

}
class MethodTestDemo{

    public static void main(String[] args) {

        for (String str : args){

            System.out.println(str);
        }
    }
}

五、ArrayReflect数组的反射

1、具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。

2、基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。

3、Arrays.asList()方法处理int[]和String[]时的差异。

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

Demo:

package day26;

import java.lang.reflect.Array;

/**
 * 
 *@author XiaLei
 */
public class ArrayDemo {

    public static void main(String[] args) {

          int[] a1 = new int[3];  
            int[] a2 = new int[4];  
            int[][] a3 = new int[3][4];  
            String[] s1 = new String[3];  
            System.out.println(a1==a2);  
            //相同数据类型的数组,当维数相同时,他们的字节码是相同的  
            System.out.println(a1.getClass()==a2.getClass());  
            //System.out.println(a1.getClass()==a3.getClass());  
            System.out.println(a1.getClass().getName()+"=="+a2.getClass().getName());  
            System.out.println(a1.getClass().getSuperclass().getName());  
            System.out.println(a3.getClass().getSuperclass().getName());  
            Object o1 = a1;  
            Object o2 = a2;  
            Object o3 = a3;  
            Object o4 = s1;  
            Object[] o5 = a3;  
            Object[] o6 = s1;  
            System.out.println(o3);  
            System.out.println("-----------------------------");  
            printObject(a1);  
            //Object 数组的反射  
            Object[] oo = new Object[]{"a",1,'a'};  
            String str = oo[1].getClass().getName();  
            System.out.println(str);  
        }  
        public static void printObject(Object obj){  
            Class clazz = obj.getClass();  
            if(clazz.isArray()){  
                //获取array 的长度  
                int len = Array.getLength(obj);  
                //for 循环  
                for(int i = 0;i<len;i++){  
                    System.out.println((i+1)+":"+Array.get(obj, i));  
                }  
            }else{  
                System.out.println("OBJ:"+obj);  
            }  
        }  
    } 

六、CollectionReflect:集合反射

1、一句话总结:配置文件的加载往往是用类加载器的方式加载。以后学到的框架都是这么加的。

2、类加载器:用类加载器加载配置文件。

–>类.class.getClassLoader().getResourceAsStream

说明:就是在classPath 下逐一的去查找你要加载的那个文件。

3、扩展知识: eclipse 会把源目录(src)下的所有的 .java 文件,自动编译成 .class 你一保存它就编译了,然后存放到 classPath 指定的目录下去。同时,它也把所有的非 .java 文件,原封不动的再保存一份到 classPath 下,那么你就会发现,config.properties存在与两个不同的文件目录下。但是当你真正在获取配置文件的时候,它其实是在classPath下找的。

4、使用注意:创建一个文件读取流,读取配置文件中的字符流数据。一定要记住用完整路径,但是完整路径不是硬编码,而是运算出来的。

Demo:

package day26;

import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;

/**
 * 
 *@author XiaLei
 */
public class CollectionReflect {

    public static void main(String[] args) throws Exception{

         //创建一个文件读取流,读取配置文件中的字符流数据  
        //那么,读取properties 配置文件的路径怎么取呢?  
        //一定要记住用完整路径,但是完整路径不是硬编码,而是运算出来的。  
        //InputStream ips = new FileInputStream("config.properties");  
        //在 classPath 根目录下逐一的找 config.properties 文件  
        //那么,如果把配置文件放在包内,那么就要加上。。  
        //直接获取配置文件资源,并未使用类加载器,这种方法:只需要写上配置文件名,不需要写上目录名  
        //InputStream ips = CollectionReflect.class.getResourceAsStream("config.properties");  
        //当把配置文件整合到一个 resources 资源包下,那么只需要在获取的路径上加上当前的包名(下级目录)  
        InputStream ips = CollectionReflect.class.getResourceAsStream("resources/config.properties");  
        //初始化一个配置文件对象properties 。  
        Properties prop = new Properties();  
        //加载文件读取流  
        prop.load(ips);  
        //马上关闭文件读取流,这是一个习惯。如果没有及时关闭,那么会造成内存泄漏  
        //并不是因为 ips 对象不被释放,而是这个对象关联的系统资源没有被释放。这是两个概念  
        //总结:释放资源  
        ips.close();  
        String className = prop.getProperty("className");  
        System.out.println("className的值是:"+className);  
        //newInstance():不带参数的构造方法  
        Collection cols = (Collection) Class.forName(className).newInstance();  
        //Collection cols = new HashSet();      //得到的集合长度是3  
        //Collection cols = new ArrayList();    //得到的集合长度是4  
        ReflectPoint pt1 = new ReflectPoint(3, 4);  
        ReflectPoint pt2 = new ReflectPoint(2, 5);  
        ReflectPoint pt3 = new ReflectPoint(3, 4);  
        //给集合添加了两次的pt1。  
        Cols.add(pt1);  
        Cols.add(pt1);  
        Cols.add(pt1);  
        //如果重新给pt1的变量 y 赋值,那么当 remove 了pt1后,就会造成 hashCode 的内存泄露的现象  
        //pt1.y = 7;  
        //cols.remove(pt1);  
        System.out.println(cols.size());  
        //某个对象,我不要了,但是却一直在用,没有被释放掉。这就是内存泄漏。  
    }  
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值