zoukankan      html  css  js  c++  java
  • java反射机制

    反射机制

    1.什么是反射

    Everything is object!

    这在java中可以说是一个公理,对象都可以抽象为类,那么类在java中应该也是一种对象,他其实是属于一个叫做Class类的字节码对象,它应该描述的是所有的类,具有所有的类的相同方法等。

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

    简单理解:把类中的每一种成员,都描述成一个新的类.Class: 表示所有的类.Constructor: 表示所有的构造器.Method: 表示所有的方法.Field: 表示所有的字段.

    2.Class类和Classd的实例

    因为Class是对很多类的抽象,为了区分这些类的字节码对象,必须用泛型规范Class

    2.1.获取Class实例的方法

    • 方式一:使用class属性(所有的数据类型都具有)
    Class<java.util.Date> clz1 = java.util.Date.class;
    
    • 方式二:使用对象的getClass() 该方法是Object类中的,说明是任意类型的对象都可调
    java.util.Date data = new java.util.Date();
    Class<?> clz2 = data.getClass();
    
    • 方式三:Class类中的静态方法forName(String className),className是类的权限定名称
    Class<?> clz3 = Class.forName("java.util.Date");
    

    注意:在同一个类中,JVM中只存在一个字节码对象,也就是上述三种方式所创建的字节码对象是一样的

    2.2.内置的Class实例

    基本数据类型没有类的概念,也没有对象,因此不能使用方式二和方式三。只能用第一种,也就是

    Class clz = 数据类型.class	
    

    八大基本数据类型加上一个void,有九大内置class实例.比如:

    System.out.println(int.class);//int
    System.out.println(double.class);//double
    System.out.println(void.class);//void
    

    在基本类型的包装类型中都存在一个TYPE的一个常量,返回该包装类型对应基本类型的字节码对象 static Class TYPE

    Class<Integer> clz = Integer.TYPE;
    System.out.println(clz);
    System.out.println(clz == int.class);//true  
    

    注意:Integer有自己的字节码对象,和int肯定不相同

    2.3.数组的Class实例

    数组也是数据类型,所以可以用方式一和方式二

    int[] arr = {1,1,2,5};  //arr是数组对象
    Class<int[]> clz3 = int[].class;
    arr.getClass();//class [I
    

    API中提到:所有具有相同维数的和相同数据类型的数组在JVM中只有一个字节码对象,也就是和数组中元素没有任何关系

    int[] arr = {1,1,2,5};  //arr是数组对象
    int[] arr2 = {1,1,2,3,4,5};  //arr2是数组对象
    //方式二
    System.out.println(arr.getClass());//class [I		System.out.println(arr2.getClass());//class [I
    //方式一
    Class<int[]> clz3 = int[].class;
    System.out.println(clz3);
    System.out.println(clz3 == arr2.getClass());//true
    

    3.操作构造器

    3.1.获取构造器

    获取所有构造器:

    Constructor<?>[] getConstructors() 返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。
    Constructor<?>[] getDeclaredConstructors()  返回所有的构造方法,没有权限限制
    

    获取指定的一个构造器:

     Constructor<T> getConstructor(Class<?>... parameterTypes) 
     返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。 
     Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 
     返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。 
    
    Class<Person> clz = Person.class;
    Constructor<?>[] con = clz.getConstructors();//获取类中所有的public构造器
    con = clz.getDeclaredConstructors();//获取类中所有的构造器,没有权限限制
    
    Constructor<?> con = clz.getConstructor();//获取类中的无参数构造器
    con = clz.getConstructor(String.class,int.class);//带有String和int类型的构造器
    con = clz.getDeclaredConstructor(String.class);//获取带有private修饰的有参数构造器
    

    3.2.用构造器创建对象

    对于public的构造器

    构造器对象.newInstance(实参);
    

    对于private的构造器

    构造器对象.setAccessible(true)//设置当前构造器可访问,这是Constructor的父类AccessibleObject中方法
    构造器对象.newInstance(实参);
    
    class People{
    	public People(){
    		System.out.println("无参数构造方法");
    	}
    	public People(String name,int age){
    		System.out.println(name + "," + age);
    	}
    	@SuppressWarnings("unused")
    	private People(String name){
    		System.out.println(name);
    	}
    }
    public class CreateObjectDemo {
    	public static void main(String[] args) throws Exception {
    		//获取People类的字节码对象
    		Class<People> clz = People.class;
    		Constructor<?> con = clz.getConstructor();
    		con.newInstance();
    		System.out.println("===============");
    		
    		con = clz.getConstructor(String.class,int.class);
    		con.newInstance("XXX",17);
    	
    		con = clz.getDeclaredConstructor(String.class);
    		con.setAccessible(true);//设置当前构造器可访问,这是Constructor的父类AccessibleObject中方法
    		con.newInstance("OOO");
    	}
    }
    

    4.操作方法

    4.1.获取方法

    获取类中的所有方法:

    Method[] getDeclaredMethods() 获取自身类中所有的方法(不包括继承的,和访问权限无关)
     
    Method[] getMethods() 获取包括自身和继承过来的所有的public方法
    

    获取类中的某一个方法

    Method getDeclaredMethod(String name, Class<?>... parameterTypes) 
    表示调用指定的一个本类中的方法(不包括继承的)
        
    Method getMethod(String name, Class<?>... parameterTypes) 
    表示调用指定的一个公共的方法(包括继承的)
    
    Class<Person> clz = Person.class;
    Method[] me = clz.getMethods();//获取类中所有方法,包括继承过来的public方法
    
    Method m =clz.getMethod("doWork");//获取public的doWork方法
    

    4.2.调用方法

    • 1):获取方法所在类的字节码对象-

    • 2):获取方法对象-

    • 3):使用反射调用方法.

      public 修饰的方法:

      在Method类中有方法:public Object invoke(Object obj,Object... args):表示调用当前Method所表示的方法

      obj表示被调用方法所属的对象,args表示向方法传递的实参,返回也是方法返回的结果

      private修饰的方法

      Method对象.setAccessible(true);//设置当前方法可访问,这是Method的父类AccessibleObject中方法

    class People{
    	public void doWork(){
    		System.out.println("我是无参数的doWork");
    	}
    	public String doWork(String name){
    		System.out.println("我是带参数的doWork");
    		return name;
    	}
    	@SuppressWarnings("unused")
    	private void eat(){
    		System.out.println("我是eat方法");
    	}
    }
    public class MethodInvokeDemo {
    	public static void main(String[] args) throws Exception {
    		//获取类的字节码对象
    		Class<People> clz = People.class;
    		Method m =clz.getMethod("doWork");
    		m.invoke(clz.newInstance());
    		
    		System.out.println("============");
    		
    		m = clz.getMethod("doWork", String.class);
    		Object o = m.invoke(clz.newInstance(), "xxx");
    		System.out.println(o);
    		
    		m = clz.getDeclaredMethod("eat");
    		m.setAccessible(true);//设置方法可执行
    		People p = clz.newInstance();
    		m.invoke(p);
    	}
    }
    

    对于静态方法

    对于static方法,静态方法属于类本身,此时的invoke方法的参数就是incoike(null,Object... args),也就是不需要对象

    对于形参中的可变参数

    对于数组类型的引用类型的参数,底层会自动解包,为了解决该问题,我们使用Object的一维数组把实际参数包装起来.

    Method对象.invoke(方法所属对象,new Object[]{所有的实参});

    class Test{
    	//基本类型可变参数
    	public void doWork(int...arr){
    		System.out.println("doWork方法被调用," + Arrays.toString(arr));
    	}
    	//引用类型可变参数
    	public static void doWork2(String...arr){
    		System.out.println("doWork2方法被调用," + Arrays.toString(arr));
    	}
    }
    public class MethodArrayInvokeDemo {
    	public static void main(String[] args) throws Exception {
    		//获取类的字节码对象
    		Class<Test> clz = Test.class;
    	
    		//基本类型的数组调用,第二个实参传的可以是新建一个int类型的数组。也可以用Object数组进行包装
    		Method m = clz.getMethod("doWork", int[].class);
    	//	m.invoke(clz.newInstance(), new int[]{1,2,3});
    		m.invoke(clz.newInstance(), new Object[]{new int[]{1,2,3,3}});
    		
    		//引用类型的方法调用,第二个实参只能放在Objec类型的数组中
    		m = clz.getMethod("doWork2", String[].class);
    		m = clz.getMethod("doWork2", String[].class);
    		//m.invoke(null, new String[]{"A","B"});//错误的写法
    		m.invoke(null, new Object[]{new String[]{"A","B"}});
    		
    	}
    }
    

    5.操作字段

    5.1.获取字段

    使用反射获取字段:

    • 找到字段所在类的字节码
    • 获取字段

    public Field[] getFields():获取当前Class所表示类中所有的public的字段,包括继承的字段.

    public Field getField(String fieldName):获取当前Class所表示类中该fieldName名字的public字段,包括继承的字段.

    public Field[] getDeclaredFields():获取当前Class所表示类中所有的字段,不包括继承的字段.

    public Field[] getDeclaredField(String name):获取当前Class所表示类中该fieldName名字的字段,不包括继承的字段.

    5.2.操作字段

    给某个类中的字段设置值和获取值:

    • 找到被操作字段所在类的字节码
    • 获取到该被操作的字段对象
    • 设置值/获取值

    void setXX(Object obj, XX value) :为基本类型字段设置值,XX表示基本数据类型
    void set(Object obj, Object value) :表示为引用类型字段设置值

    XX getXX(Object obj) :获取基本类型字段的值,XX表示基本数据类型
    Object get(Object obj) :表示获取引用类型字段的值

    6.其他API

    public class Demo {
    	public static void main(String[] args){
    		String name = Demo.class.getName();//getName()返回该类的权限的权限的名称
    		System.out.println(name);
    		
    		String packagename = Demo.class.getPackage().getName();//getPackage()返回该类所在的包名
    		System.out.println(packagename);
    		
    		int mod = Demo.class.getModifiers();//返回该类的修饰符
    		String type = Modifier.toString(mod);
    		System.out.println(type);
    		
    		String supername = Demo.class.getSuperclass().getName();//返回父类的名称
    		System.out.println(supername);
    		
    		Object arr = new int[]{1,2,2};//判断该字节码对象是否是数组类,也就是实际类型是不是数组
    		System.out.println(arr.getClass().isArray());
    	}
    }
    

    Class类中有方法getClassLoader()可以返回一个类加载器对象,用来加载资源文件。加载资源文件 db.properties文件,只能使用Properties类的load方法.

    对于加载资源文件,这里有三种方式:

    • 使用绝对路径加载,可以加载到bin目录中,这种做法实际不可行

      public static void test1() throws Exception{
      	Properties p = new Properties();
      	InputStream inStream = new FileInputStream("resource/db.properties");			p.load(inStream);
      	System.out.println(p);
          test1();
      }
      
    • 使用相对于classpath根路径加载,用到ClassLoader类加载器加载

      public static void test2() throws Exception{
      		/**
      		 * 这里有两种方式获得类加载器对象
      		 * 1.Class类中有方法getClassLoader()可以返回一个类加载器对象
      		 * 2.Thread类中有方法getContextLoader()也可以返回一个类加载器对象
      		 */
      		Properties p = new Properties();
      		//ClassLoader loader = LoadeResourceDemo.class.getClassLoader();
      		ClassLoader loader = Thread.currentThread().getContextClassLoader();
      		InputStream inStream = loader.getResourceAsStream("db.properties");//返回读取指定资源的输入流
      		p.load(inStream);//记载资源文件
      		System.out.println(p);
      	}
      
    • 相对于当前文件的字节码路径加载

      public static void test3() throws Exception{
          Properties p = new Properties();
          InputStream inStream = 					       
         LoadeResourceDemo.class.getResourceAsStream("db.properties");
      		p.load(inStream);
      		System.out.println(p);
      }
      

      注意第二种和第三种是不一样的,区别是第二种加载的是classpath根路径的也或者是bin目录下的,而第三种加载的文件和当前程序的字节码在一个路径下面

  • 相关阅读:
    spark内存管理这一篇就够了
    spark推测机制及参数设置
    python易错点汇总,不定期更新
    Spark架构与原理这一篇就够了
    MySQL查询这一篇就够了
    pyspark计算最大值、最小值、平均值
    Spark性能调优的方法
    大流量场景下MySQL如何准备
    100台CentOS7要分区怎么办?
    100台CentOS7要升级OpenSSH怎么办?
  • 原文地址:https://www.cnblogs.com/tfper/p/9890971.html
Copyright © 2011-2022 走看看