Java基础--反射

反射

Class类

  1. 反射的基石-Class类;
    • java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class;
    • Class类代表Java类,对应各个类在内存中的字节码;
  2. 得到各个类字节码对应的实例对象(Class类型);
    • 类名.class;例:System.class;;
    • 对象.getClass();例:new Data().getClass();
    • Class.forName(“类名”);例:Class.forName(“java.util.Data”);
  3. 注:forName的作用:返回类字节码,两种方式:a、已加载,直接返回;b、还没加载,则用类加载器加载到内存中;
    Class.forName(“……”);该方法中的String参数可以换成一个字符串类型的变量,该变量的值可通过配置文件来完成(一般用于框架开发等);

  4. 9个预定义实例对象(8个基本数据类型+void)
    例:Class cls=void.class;
    注:内存中仅有一份字节码,就算同一类的多个对象也是同一份字节码;

  5. 基本数据类型包装类中的常量TYPE对应的是该基本数据类型的字节码,即:int.class==Integer.TYPE;//true;
    注:基本数据类型的字节码与其对应的基本数据类型包装类的字节码不是同一份,级:int.class==Integer.class;//false;

总之:只要是在源程序中出现的类型,都有各自的Class实例对象,例:int[],void;
注:int[].class与int.class的字节码不同,数组是一个类的对象与其基本数据类型不同;

Class类中的常用方法

  • forName(String className);返回相应参数类名的类对象;
  • isArray();判断此class对象是否是一个数组类;
  • isPrimitive();判定指定的class对象是否表示一个基本类型;
  • getConstructor(Class< ?>… parameterTypes);返回某个类指定构造方法;
  • getConstructors();返回某个类的所有构造方法;
  • getField(String name);返回一个类中由name指定的Field对象,name-字段名;
  • getFields();返回一个类中的成员变量;
  • getDeclaredField(String name);
  • getDeclaredFields();同上,这两个方法的区别前者反映的是公共(public)字段,后者可反映私有或默认字段;注:反映私有成员时,需设置对象的访问检查值:使用函数setAccessible(boolean flag)当值为true时取消访问检查,而后可访问该成员(暴力反射);
  • getMethod(String name,Class< T>… ParamerTypes);返回一个Method对象,它反映此Class对象所表示的类或接口的指定公共成员方法;
  • getMethods();返回也给包含某些Method对象的数组;
  • getDeclaredMethod(String name,Class< ?>… ParamerTypes);返回一个Method对象,该对象反映此class对象所表示的类接口的指定已声明方法,可以是公共、保护、默认和私有方法,但不包括继承的方法;name-方法名,ParamerTypes-方法参数列表(方法参数的类类型列表)
  • getDeclaredMethods();……数组……所有方法……;
  • getSuperclass();获取此Class类的超类class;
  • getName();一String的形式返回此Class对象所表示的实例名称;
    注:获取公有和获取所有的方法的区别在于获取公有的方法可获取父类中的成员,获取私有的方法只能获取本类中的成员;

反射

  1. 概述:反射就是把Java类中的各种成分映射成相应的java类(即:java类是由很多信息或成员组成,例:包名.类名.方法,成员变量等信息,这些不同性质的成员又由其对应的类来描述,反射就是通过Class类中的方法,得到java类中的这些成员,获取的结果也是其对应的java类),例:方法的类是Method,包的类是Package;

Constructor类

代表某个类中的一个构造方法

  1. 获取某个类所有的构造方法:
    Constructor constructor[]=Class.forName(“java.lang.String”).getConstructor();
  2. 得到某一个构造方法:
    Constructor constructor=Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
    注:获取某个类中的某一构造方法,可通过指定参考类型和参数个数,如:以上示例中参数个数为1个,类型为StringBuffer;
  3. 常用方法:
    newInstance(Object… initargs);
    • 使用此Constructor对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例(该实例的参数必须与创建该构造方法时的参数列表保持一致);
    • 注:newInstance返回类型不确定,故在调用newInstance方法时要在前面使用类型强转,为对应的类型;
    • Class类中也有newInstance()方法,使用方式:
      String str=(String)Class.forName(“java.lang.String”).newInstance();
      该方法内部先得到默认的构造方法,然后改构造方法创建实例对象(创建不带参数的新实例,可用此方法);

Field类

  1. Field类代表某个类中的一个成员变量(代表字节码中的变量,不代表对象中的变量);获取一个类的实例对象上的某个字段(成员变量)的值的方式:

    • 创建该类的字节码class对象;
    • 通过class中的方法:getField(String name);公共字段;
      getDeclaredField(String name);非公共字段;
      由指定的字段名创建Field对象(非公共字段此时仅可查看,但不可获取)若要获取非公共字段的值,则可通过Field父类AccessibleObject中的方法serAccessible(boolean flag)将值设为true取消访问检查;
    • 调用Field中的get(Object obj);方法返回指定对象上此Field表示的字段的值,obj-要提取值的所属对象;
  2. 常用方法:

    • get(Object obj);obj-要提取的对象;
    • 相关方法:
      父类:setAccessible(boolean flag);
      class类:getField(String name);公共字段
    • 暴力反射:
      getDeclaredField(String name);非公共字段;
      setAccessible(boolean flag);
    • getType();返回一个class对象,它标识了此Field对象所表示字段的声明类型;
    • set(Object obj,Object value);将指定对象变量上此Field对象表示的字段设置为你指定的新值;
    • equals(Object obj);将此Field与指定对象比较;

Method类

  1. Method类代表某个类中的一个成员方法;
    得到类中的某一个方法:
    例: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));
      如果传递给Method对象的inoke()方法的第一个参数为null,说明该Method对象对应的是一个静态方法;
  3. invoke方法在1.4与1.5中的区别:

    • JDK1.5 invoke(Object obj,Object… args);
    • JDK1.4 invoke(Object obj,Object[] args);即按1.4的语法需要将一个数组作为参数传递给invoke方法,数组中的每个元素分别对应被调用方法中的一个参数;
  4. 常用方法:

    • invoke(Object obj,Object… args);对带有指定参数指定对象调用由此Method对象表示的底层方法;obj-从中调用底层方法的对象,静态方法传入null,args-用于方法调用的参数(如果是空参数的函数则不需要填写);
    • getName();以String形式返回此Method对象表示的方法名称;

AccessibleObject

  1. Field、Method、Constructor都是AccessibleObject的子类;
  2. 该类的常用方法:
    setAccessible(boolean flag);将此对象的accessible标识设置为指示的布尔值,值为true则指示反射的对象在使用时应该取消Java语言访问检查;

对接收数组参数的成员方法进行发射

例:用反射的方式执行某个类中的main方法;
启用Java程序的main方法的参数时一个字符串数组,即:public static void main(String[] args),通过反射方式来调用这个main方法时,为invoke方法传递参数只能使用以下两种方式:
mainMethod.invoke(null,new Object[]{new String[]{“……”}});
mainMethod.invoke(null,(Object)new String[]{“……”});
而不能直接传入一个”String[]”,因为JDK1.5的语法整个数组作为一个参数,但JDK1.4会将数组拆开,将数组中的元素视为多个参数,此时将于main方法的参数列表不符,JDK1.5兼容JDK1.4,直接传入数组时将按JDK1.4的语法处理,所以需要将数组封装成一个数组或不让其拆包;

数组与Object的关系及其反射类型

  1. 具有相同维数和元素类型的数据属于同一个类型,即具有相同的Class实例对象;
    例:int[] a1=new int[3];
    int[] a2=new int[5];
    a1.getClass()==a2.getClass();//true;

  2. 代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class,即:数组也是Object的子类;

  3. 基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;非基本类型的一维数组,即可以当做Object类型使用,又可以当做Object[]类型使用,即:
    • 基本数据类型不是Object子类,非基本类型都是;
    • 数组可以视为Object的子类;
    • 综合a,b以下示例可作用Object[]类型使用;
      int[][] a3=new int[3][5];
      String[] a4=new String[3]{“a”,”b”,”c”};
      Object[] obj1=a3;元素为a3中的一维数组;
      Object[] obj2=a4;元素为a4中 的String;但Object[] obj3!=a2;一维的基本类型数组不可作为Object[]使用,因为基本类型不是Object类;
  4. Arrays.asList()方法处理int[]和String[]时的结果:前者打印出Hash值,后者打印出String[]中的元素列表,因为Arrays.asList()在JDK1.4版本中的参数列表为:
    • JDK1.4 asList(Object[] obj);在JDK1.5中为可变参数列表为:
    • JKD1.5 asList(T…a);
      根据以上特点当传入为int[]参数时,因为int[]中的元素int不是Object子类,不被JDK1.4的语法所识别,故使用JKD1.5的语法执行,则int[]被当做一个参数,当传入String[]时,String[]中的元素String是Object子类,按JDK1.4的语法执行,则String[]中的所有元素被视为多个参数传入,故可打印出数组中的元素列表,传入int的多维数组时与String[]同样的执行方式打印的是每个(子)维的哈希值;

对数组的反射

对数组的反射可使用Array类来实现;

Array类

  1. 该类在java.lang.reflect包下,该类提供了动态创建和访问Java数组的方法,该类的所有方法都是静态的;
  2. 常用方法:
    • get(Object array,int index);返回指定数组对象中指定索引的值;
    • getLength(Object array);以int形式返回指定数组对象的长度;

ArrayList、HashSet的比较以及Hashcode分析

  1. HashSet集合在存储元素时,根据元素的HashCode值将元素存储在不同的区域段中,以便提高查找效率,hashCode方法默认采用对象的内存地址值进行计算,所以一般情况下,自定义对象都要复写equals,hashcode方法,以便按自定义规则判断对象是否相同。
  2. 因为HashSet存储特性,可能导致在一下情况谁发生内存泄露,即:如果复写了equals方法,但没有复写hashcode方法,则可能导致存入两个不同对象但值相同的元素,因为两个元素可能存储在不同的区段中,在调用equals时同一区段没有相同元素则存入,故:为了防止内存泄露,当一个对象被存储进Hashcode集合以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进hashSet集合中的哈希值就不同,此时可能就不在同一区段中,此时即使在contains方法使用该对象的当前引用作为的参数去导致无法从HashSet集合中单独删除当前对象,从而造成内存泄漏;

附:
内存泄漏:用动态存储分配函数动态开辟的空间,在使用完毕后来释放,结果导致一直占据该内存单元,直到程序结束,即:内存使用完毕之后来释放;

import java.lang.reflect.*;
import java.util.*;
class ReflectTest {
    public static void main(String[] args)throws Exception{
        String str="abc";
        int b=2;
        int[] i=new int[0];
        Class cls1=String.class;
        Class cls2=str.getClass();
        Class cls3=Class.forName("java.lang.String");
        System.out.println(cls1==cls2);
        System.out.println(cls1==cls3);
        Class cls4=int.class;
        Class cls5=Integer.class;
        Class cls6=Integer.TYPE;//该字段等效于int.class;
        System.out.println(cls4==cls5);
        System.out.println(cls4==cls6);
        System.out.println(cls4.isPrimitive());
        Class cls7=i.getClass();
        System.out.println(cls7.isArray());
        Constructor cons=cls1.getConstructor(StringBuffer.class);
        String string=(String)cons.newInstance(new StringBuffer("abcd"));
        System.out.println(string.charAt(2));       
        //使用反射的方式获取类中的成员变量
        TestDemo td=new TestDemo(6,8);
        Class tdclass=td.getClass();
        Field fieldy=tdclass.getField("y");//公有化成员可以直接使用getField也可使用getDeclaredField方法返回Field方法;
        System.out.println(fieldy.get(td));
        Field fieldx=tdclass.getDeclaredField("x");//非公有化成员需用getDeclaredField方法返回Field对象;
        fieldx.setAccessible(true);//私有化成员需用setAccessible方法取消访问检查(暴力反射)
        System.out.println(fieldx.get(td));
        changeStringValue(td);
        System.out.println(td.toString());
        //获取类中的所有方法
        Method[] methods=TestDemo.class.getDeclaredMethods();
        for(Method method:methods){
            System.out.println(method);
        }
        //  使用反射的方式获取类中的方法,并调用该方法;
        Method concat=str.getClass().getMethod("concat",String.class);
        String str8=(String)concat.invoke(str,"def");
        System.out.println(str8);       
        //使用反射的方式获取类中的多个参数的方法;
        Method substring=str8.getClass().getMethod("substring",int.class,int.class);
        System.out.println(substring.invoke(str8,1,4));
        Method valueOf=String.class.getMethod("valueOf",int.class);
        System.out.println(valueOf.getName());
        System.out.println(valueOf.invoke(null,2));
        //使用反射的方式获取类中的空参数方法;
        Method getBytes=str8.getClass().getMethod("getBytes");
        byte[] bs=(byte[])getBytes.invoke(str8);
        for(byte by:bs){
            System.out.println((char)by);
        }
        System.out.println(getBytes.getName());
        //用反射的方法调用类中的main方法,并给main传递参数;main的参数是个String[] 在JDK1.5中可使用以下方式传递多个参数;
        Method mainMethod=Class.forName("TestDemo").getMethod("main",String[].class);
        mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});//JDK1.5兼容JDK1.4在传入数组时会将数组中的内容视为多个参数而不是一个数组,故需要将数组打包成一个Object[]传入,或使用下面的方式
        //以上书写等效于:mainMethod.invoke(null,(Object)new String[]{"111","222","333"});
        //数组与Object的关系及其反射类型
        int[] a1=new int[]{1,2,3,4};
        int[] a2=new int[]{3,4};
        int[][] a3=new int[2][4];
        String[] a4=new String[]{"a","b","c"};
        String[] a5=new String[]{"a","b","c"};
        System.out.println(a1.getClass() == a2.getClass());
        //System.out.println(int.class== int[][].class);
        //System.out.println(a1.getClass() == a4.getClass());
        //System.out.println(a5.getClass() == a4.getClass());
        System.out.println(a2.getClass().getSuperclass().getName());
        System.out.println(a3.getClass().getSuperclass().getName());
        System.out.println(a4.getClass().getSuperclass().getName());
        //Object[] obj1=a1;//基本数据类型不是Object类;所以int[]无法转换为Object[];
        Object[] obj2=a3;//数组是Object类,所以二维数组int[][]可转换为Object[]的一维数组,内部元素就是数组;
        Object[] obj3=a4;//String是Object类……;
        //System.out.println(Arrays.asList(obj1));
        System.out.println(Arrays.asList(obj2));//asList方法传入数组时按JDK1.4版本语法执行(兼容)                                            //当出入基本数据类型的数组时,因以上原因int不被视为Object,则按JKD1.5版本语法执行,数组被视为一个参数!
        System.out.println(Arrays.asList(obj3));
        //数组的反射
        arrayReflect(a1);
        arrayReflect(a4);
        arrayReflect("xyz");
    }   
    //以反射的方式获取给定数组中的所有元素,如果不是数组则直接打印;
    public static void arrayReflect(Object obj){
        if(obj.getClass().isArray()){
            for(int x=0;xout.println(Array.get(obj,x));
            }
        }else{
            System.out.println(obj);
        }
    }
    public static void changeStringValue(Object obj)throws Exception{
        Class tdcla=obj.getClass();
        Field[] fields=tdcla.getFields();
        for(Field field:fields){
            if(field.getType()==String.class){//if(field.getType().equals(String.class))此处可使用equals方法判断
                                            //使用==是因为同一类字节码在内存中为同一份,所以用==比更专业;      
                String oldValue=(String)field.get(obj);//获取指定对象中Field的值;
                String newValue=oldValue.replace('b','a');//替换字符串中的值;
                field.set(obj,newValue);//给指定的对象的该Field字段设置新值;
            }
        }
    }
}
class TestDemo{
     int x;
    public int y;
    public String str1="ball";
    public String str2="basketball";
    public String str3="itcast";
    TestDemo(int x,int y){
        this.x=x;
        this.y=y;
    }
    private void show(){
        System.out.println("siyounengkanjianma");
    }
    public String toString(){
        return str1+"::"+str2+"::"+str3;
    }
    public static void main(String[] args){
        for(String arg:args){
            System.out.println(arg);
        }
    }
    public boolean equals(Object obj){
        if(!(obj instanceof TestDemo))
            return false;
        if(obj==null)
            return false;
        if(getClass()!=obj.getClass())
            return false;
        final TestDemo oth=(TestDemo)obj;
        if(this.x!=oth.x)
            return false;
        if(this.y!=oth.y)
            return false;
        return true;    
    }
    public int hashCode(){
        int prime=31;
        int result=23;
        result=result*prime+x;
        result=result*prime+y;
        return result;
    }
}

反射的作用–实现框架功能

  1. 框架的概念:
    因为在写程序时无法知道要被调用的类名,所以在程序中无法直接new某个类的实例对象,而要使用反射方式来做,具体实现方式:
    • 配置文件:制作一个配置文件,后期将写好的类的名称与getProperty()读取的名称以键值对的形式存入配置文件中;
    • 建立一个读取流对象,读取配置文件;
    • 建立一个Properties对象,调用其load()方法将其与读取流相关联;
    • 通过Properties的getProperty()方法通过指定键获取配置文件中的类名;
    • 通过反射创建获取到的类的实例对象

例:使用反射的方式创建一个HashSet集合;
InputStream input=new FileInutStream(“properties.txt”);读取配置文件
properties pro=new Properties();
pro.load(input);
input.close();
String className=pro.getProperty(“className”);通过配置文件中的指定键获取值;
Collection hs=(Collection)Class.forName(className).newInstance();//创建集合对象;

import java.util.*;
import java.io.*;
class ReflectTest2 {
    public static void main(String[] args) throws Exception{
        //Collection al=new ArrayList();
        //Collection hs=new HashSet();
        InputStream input=new FileInputStream("Properties.txt");
        Properties pro=new Properties();
        pro.load(input);
        input.close();
        String classNameSet=pro.getProperty("classNameSet");
        String classNameList=pro.getProperty("classNameList");
        Collection hs=(Collection)Class.forName(classNameSet).newInstance();
        Collection al=(Collection)Class.forName(classNameList).getConstructor().newInstance();
        TestDemo td1=new TestDemo(3,4);
        TestDemo td2=new TestDemo(3,5);
        TestDemo td3=new TestDemo(4,6);
        TestDemo td4=new TestDemo(3,4);
        al.add(td1);
        al.add(td2);
        al.add(td3);
        al.add(td4);
        al.add(td1);
        System.out.println(al.size());
        hs.add(td1);
        hs.add(td2);
        hs.add(td3);
        hs.add(td4);
        hs.add(td1);
        //td3.y=18;
        //td3.x=8;//在添加集合之后修改参与hashcode运算的参数值,将影响hashcode值
        hs.remove(td3);//删除时将找不到该元素,导致删除失败,即:内存泄漏;
        System.out.println(hs.size());
    }
}

你可能感兴趣的:(java基础,java,反射)