• java 反射详解


    反射

    为什么需要反射?

    背景知识: Java引用变量有两种类型,一种编译时类型,一种运行时类型。编译时类型由声明变量使用的类型决定。运行时类型由实际赋给他的变量决定。两个类型不一致的话,就可能出现“多态”的情况。

    package Test01;
    class Base{
    	final static int book=6;
    	public void base(){
    		System.out.println("老爸有的函数.......");
    	} 
    	public void test() {
    		System.out.println("普通有的函数.......");
    	}
    }
    public class SubClass extends Base{
    	final static String book="哈哈,我修改了book";
    	public void test() {
    		System.out.println("子类重写的函数.......");
    	}
    	public void sub(){
    		System.out.println("子类才有的函数.......");
    	}
    	public static void main(String[] args) {
    		
    		Base base =new Base();
    		base.base();
    		base.test();
    		System.out.println("base里的book是"+base.book); 
    		SubClass sub =new SubClass();
    		sub.base();
    		sub.test();
    		System.out.println("sub里的book是"+sub.book);
    		//编译和引用类型不一样的
    		Base b =new SubClass();
    		b.base();
    		b.test();
    		System.out.println("b里的book是"+b.book);
    		//b编译时是Base类型,没提供sub()方法
    	/*	b.sub();*/
    		
    	}
    }
    

      以上代码说明:

    1.对象的方法具有多态性。最后一行注释的代码不能通过编译,虽然b的引用变量确实包含了sub方法,但是编译时它仍是Base类型,不能执行 SubClass里的方法(提示:可使用反射来执行该方法)。

    2.对象的实例变量不具有多态性。b里的book不是输出子类的book,而是父类的。

    运行结果如下:

           总结下可能出现的问题:程序运行会出现编译和运行的类型不一致的情况,比如Person p =new Student(); 该行代码产生一个p变量,编译时 是Person类型,运行时是Student类型。更不好的例子是程序如果接收外部的一个对象,这个对象编译时是Object,但是运行时却调用对象运行类型时的方法。

    解决 程序需要知道 运行时对象和类的信息 情况  这个问题的办法有:

    1.假设 运行和编译就完全已经知道对象和类的信息,则强制类型转换,将编译时类型强转为运行时类型。强转时注意:基本数据类型只能在数值类型之间转换(整型,字符型,浮点型),数值型不能喝布尔型转。存在继承关系才父子类之间才能进行强转,否则会CastClassException。因此,在引用强转时候加上instanceof(instanceof用法不清楚的可以搜一下)判断下再转,能增加代码的健壮性。

    2假设 只能运行 才能发现对象和类的信息,就得靠 反射解决问题。

    下面进入重点啦:

    如何获得Class对象呢?

    使用Class类的静态方法foreName(String clazzName)方法传入的字符串必须是类的全限定名,包括包名。

    1   Class a= Class.forName("Test01.Classloader");
    2    System.out.println(a);

    类的class属性。Person.class.

    1  Class a= Base.class;

    调用某对象的getClass()方法。该方法是java.lang.object类里的方法,所以所有的对象都能使用。

    Base b =new Base();
    Class a= b.getClass();

    对比下,前两种方法都是根据类取得类指定Class对象,但是第二个更好,因为不需要调用方法,而且代码更安全,编译阶段就检查了要访问的该Class对象是否存在。

    获得Class对象了还没结束,还需要调用里面的方法来获得Class对象和该类的详细信息。

      获得Class对象里的构造器,Constructor (在获得类里必须有对应的构造器)

       Class a= Class.forName("Test01.Classloader");
       //无参数
       Constructor  c = a.getConstructor(null);
       //有参数
       Constructor  m = a.getConstructor(String.class,int.class); //数据类型在前,class后面,具体看构造参数
       System.out.println(m);

         获得Class对象里的方法,Method

    Class a= Class.forName("Test01.Classloader");
    Method  m = a.getMethod("test", String.class); //第一个是方法名字,后面的是参数类型
       System.out.println(m);

          获得Class对象里的实例变量,Field(注:如果同一个包下的两个类,一个被访问类里的成员变量省略修饰符,会报找不到成员变量的错)

     Class a= Class.forName("Test01.Classloader");
      
       Field  m = a.getField("name"); //第一个是方法名字,后面的是参数的类型
       System.out.println(m);

    拿到了Class对象里的方法,如果不想到去调用它,拿到了也没用啊。所以,Method对象里有invoke方法。该例子适用适用于默认的构造器。 如果是有参数的构造器,则应该先拿到Constructor 对象再调用这个对象的newInstance()(注意:此方法带参数)构造该Class对象的实例。

    package Test01; 
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
     
    public class SubClass{
     
    	public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, NoSuchFieldException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            Class a= Class.forName("Test01.Classloader");
      
           Object object    = a.newInstance(); //一个实例
           Method test =a.getMethod("test", String.class);
           test.invoke(object, "zyz");
     	}
    }
    

     修改公共的成员变量,引用举例

      Class a= Class.forName("Test01.Classloader");
    	   Object object    = a.newInstance(); //一个实例
    	   Field f =a.getField("name");
    	   f.set(object, "zyz");
    

     修改公共的成员变量,基本类型 setXxx(Object obj,Xxx val),getXxx(Object obj) Xxx是基本类型举例

     Class a= Class.forName("Test01.Classloader");
           Object object    = a.newInstance(); //一个实例
           Field f =a.getField("name");
           f.setXxx(object, "zyz");

    小注意:上述讨论的都是和公共的构造器,方法,成员变量。私有的(private)就不能同样处理

    原理是通过setAccessible方法,避开了对类的加载器对类的检查。

    反例:

     正确的:(对比运行结果)

    Classloader类 长这样

    package Test01;
    public class Classloader{
        public String name;
        public int i;
         private   Classloader(String name) {
             this.name =name;
            System.out.println(name);
        }
    
    }

    以上讨论了引用类型,基本类型,没涉及,如果想反射创建数组怎么办呢?

    对于数组java.lang.reflect包里有个Array类,它能代表所有的数组,利用该类能动态创建数组,操作数组元素。

    Array类里有

    静态方法 Object newInstance(Class.class,int length)创建有指定实例的数组

    静态方法 void setXxx(Object arr,int index,int val)  为arr 数组的第index设值

    静态方法 xxx getXxx(Object arr,int index) 返回array数组的第Index个元素

    下面代码举例说明

    package Test01;
    
    
    import java.lang.reflect.Array;
    
    
    public class ArrayTest {
     public static void main(String[] args) {
         //创建一个有指定元素的数组(有点疑问为嘛数组也 是Object,某个返回元素类型元素Object)
        Object object = Array.newInstance(String.class, 3);
        Array.set(object, 2, "小张");
        Object name =Array.get(object, 2);
        System.out.println(name);
    }
    }

    下面是对反射的一个实际应用举例

    准备 1.txt 文件,里面是 

    a= java.util.Date
    b=javax.swing.JFrame

    package Test01;
    //实现了从txt文件中读取key-value,并根据value创建对象,放入hashmap里,即一个简单的对象池功能
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Properties;
    
    
    public class ObjectPoolFactory {
         HashMap<String, Object> objectPool =new HashMap<>();
         //创建对象
         public  Object createObject(String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
            Class<?> object = Class.forName(className);
             return object.newInstance();
        }
         public void initPool(String fileName) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
             FileInputStream inputStream =new FileInputStream(fileName);
            Properties properties =new Properties();
            properties.load(inputStream);
            for(String name:properties.stringPropertyNames()) {
                objectPool.put(name,createObject(properties.getProperty(name)));
            }
        }
         //从对象池里读取对象
         public Object getObject(String name) {
            return objectPool.get(name);
        }
         public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException, IOException {
             ObjectPoolFactory factory =new ObjectPoolFactory();
             factory.initPool("D://1.txt");
             System.out.println(factory.getObject("a"));
             System.out.println(factory.getObject("b"));
        }
    }

     注意 1.txt是这样的 

    不要画蛇添足 ,变成这样,否则会找不到文件的。(低级错误)

    可得到如下 的运行结果输出两个对象。

    上面的例子就是Spring框架采用的方式,简化Javaee的开发。但是不同的是从属性文件读的信息太少,spring 是读xml格式的文件。


    给之前的对象池工厂增加 配置对象的成员变量 的值 (待续.....)

    准备1.txt 文件,里面是 

    a= java.util.Date
    b=javax.swing.JFrame
    #set the title of a
    a%title=Test Title

  • 相关阅读:
    暑假团队学习第一周
    Python快速入门(3)
    Python快速入门(2)
    走入PHP-类与对象
    走入PHP-declare、ticks、encoding、include
    走入PHP-变量、运算符
    XAMPP安装报错及解决
    走入PHP-数据类型和字符串语法
    走入PHP-初次见面
    剑指offer-替换空格
  • 原文地址:https://www.cnblogs.com/yizhizhangBlog/p/9258560.html
Copyright © 2020-2023  润新知