zoukankan      html  css  js  c++  java
  • 反射

    • 为什么要学习反射?

    首先说一点:反射是比较低级的一个知识,如果是单纯的撸码来是实现功能的话,反射基本用不到,而且反射的性能呢也肯定不怎么好的,但是我个人觉得反射是一个很重要的知识点,在我老早学习的时候就是这么认为的,主要的原因有2点:

    1,java的编译和运行。java是强类型语言,运行java每一个对象都会出现2个种类型:编译时类型和运行时类型,好多的知识点都只是关于编译时类型的,比如说多态,比如说泛型。java在运行时那个对象是自己实实在在的那个对象,并不是说编译时候的类型,所以要研究好反射,就可以得到这个实实在在的那个对象了,这对于理解java的工作原理是很重要的。举个例子:如果程序需要在运行时发现类和对象的真实信息,而且在编译阶段就根本不知道这个对象确切的属于哪个类,那么程序只能依靠运行时信息来发现该对象和类的真实情况,就必须要用反射了。注意:在反射中没有简单数据类型,所有的编译时类型都是对象。反射把编译时应该解决的问题留到了运行时。

    2,框架的底层。我记得在老早的时候,几个搞android的是朋友就说框架不好的,怎么怎么地的,性能了什么的,不好了怎么怎么样,我觉得很不可理解。在以前我个人也觉得框架是一个比较深奥的东西,现在看来框架其实也没有什么的。说白了,框架就2个东西,一个是解析配置文件,一个是反射动态来获得对象。先说配置文件:这个可能就是j2ee和.net最大的区别,j2ee领域存在大量的配置,不管是XML配置文件,还是通过注解方式,其实就是把一些不关于业务逻辑的代码抽离出来,方便以后的改动,如果你不这样子实现的话,那只能硬码写4了,几乎没有任何可变性,那就是不是框架了。然后动态获得对象,除去性能了,安全了,可维护等等多个有利条件,框架就是把所谓的一些通用的步骤整理起来,这样子就彻底的解放了码农,码农要做的只是把自己的业务逻辑代码放进去,这样子就可以跑了,那么问题来了:框架都是T前写好的jar文件,已经编译过的,预先已经定义好了好多的对象在那里运行呢,那么我们自己放进去的对象如何才能插入到框架里面跑起来呢?很简单:反射。所以要认真的研究好反射,这样子对于理解框架的底层很重要的。说实际的,现在框架对于我来说并不是什么很高深的东西了,无非就是通过一些很好的设计模式,来完成一些通用功能。我们在使用的时候,写一些配置文件和自己的业务逻辑代码,框架会根据配置文件,来解析XML或者注解,把我们的代码参与到运行阶段,这就是任何一个框架的原理。


    • Class类

    定义:在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。虚拟机利用类型标识选用相应的方法执行。可以通过专门的类访问这些信息。保存这些信息的类被称为Class(类)。

    每一个类被加载之后,系统都会为该类生成一个Class对象,通过该Class对象就可以访问JVM中的这个类,说白了,这个Class就是JVM的2进制文件,JDK中是这么说:Class就是表示Class 对象建模的类,用这个类在创建对象的。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。Class 对象只能由系统建立对象,一个类在 JVM 中只会有一个Class实例,每个类的实例都会记得自己是由哪个 Class 实例所生成。

    注意:对象照镜子后可以得到的信息:某个类的数据成员名、方法和构造器、某个类到底实现了哪些接口。这句话其实也说出了反射的最准备和直观的意义:就是通过JVM中的这个类的一部分来获得所有的关于这个类的信息,并为之转换成一个其他的可以使用的类型。

      

    现在很明显了,玩反射第一步就是要或者这个对象或者是类在JVM中的那个Class对象,然后调用他的方法来获得其他的东西。那么如何获得Class对象?有3种方式:
        (1)通过类名获得类对象 Class c1 = String.class; //类.Class;
        (2)通过对象获得类对象 Class c2 = s.getClass(); //类对象.getClass();
        (3)通过Class.forName(“类的全名”)来获得类对象    //Class.forName(包名.类名);        Class c3 = Class.forName(“Java.lang.String”);//这会强行加载类到内存里,前两种不加载。 注:第三种方式是最常用最灵活的方式。第三种方式又叫强制类加载。

    关于这3种方式的取舍:如果现在我们得到的是一个对象,那么就只能用getClass()这个方法了,如果现在给了类名了,那么我们就可以使用这个类名打点.class这个属性,其实一般使用这个最多。如果我们现在拿到的只是一个普通的字符串,我们需要获得这个字符串对应的Class对象,就只能使用Class类的静态方法:ForName()了。

    package linkin;
    
    
    public class Linkin 
    {
    	
    	public static void main(String[] args) throws ClassNotFoundException
    	{
    		//通过类的Class属性来获取,这个方法最安全可靠,程序的性能也是最高的
    		Class<String> class1 = String.class;
    		//通过一个对象来获取
    		Class<String> class2 = (Class<String>) "LinkinPark...".getClass();
    		//通过Class类的静态方法来获取,这里要抛异常的
    		Class<String> class3 = (Class<String>) Class.forName("java.lang.String");
    	}
    }
    
    

    • 一旦获得某个类所对应的Class对象后,就可以调用Class类的方法来获得这个类的所有的真实信息了,包括构造器,属性,方法,注解,泛型。
    构造器:


    方法:


    属性:


    注解:


    declared 申明的,公然的,容易发现上面API中,后面的方法都是在前面的方法签名上加了这个单词,意思就是可以获取所有的对应的成员,与这个成员的访问权限无关,要是直接使用前面的话,就只能获取这个类对应的用public来修饰的成员。此外,其他的方法不是经常使用,比如获得有关内部类的情况,继承的是父类,实现的接口,类的修饰符,所在包,类名等等,要是用的话自己去翻API。代码都比较简单的,这里就不敲了。注意一点:对于只能在源代码上保留的注释,使用运行时获得Class对象无法访问该注释对象。


    • 通过Class对象可以得到大量的Method,Constructor,Field等对象,然后可以通过这些对象来执行实际的功能,比如说调用方法,创建实例。
    1,创建对象。
    通过反射来生成对象有2中方式:(1),使用Class对象的newInstance()方法。要求该CLass类必须要有默认的构造器。这里创建实例实际调用的就是这个默认的构造器。(2),先使用Class对象来获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方式可以使用指定的构造器来创建实例。

    package linkin;
    
    import java.lang.reflect.Constructor;
    
    
    public class Linkin 
    {
    	private String name;
    	
    	public Linkin()
    	{
    		
    	}
    	
    	public Linkin(String name)
    	{
    		this.name = name;
    	}
    	
    	public String getName()
    	{
    		return name;
    	}
    
    	public void setName(String name)
    	{
    		this.name = name;
    	}
    
    	public static void main(String[] args) throws Exception
    	{
    		Class<Linkin> class1 = Linkin.class;
    		//调用默认的构造器
    		class1.newInstance();
    		//调用自己指定的构造器,注意后面方法的参数是传入的参数类型。
    		Constructor<Linkin> cons = class1.getConstructor(String.class);
    		cons.newInstance("LinkinPark");
    	}
    }
    
    

    2,调用方法
    调用方法就一种方式,先获取这个方法,然后再用这个方法的invoke()方法。

    package linkin;
    
    import java.lang.reflect.Method;
    
    
    public class Linkin 
    {
    	private String name;
    	
    	public Linkin()
    	{
    		
    	}
    	
    	public Linkin(String name)
    	{
    		this.name = name;
    	}
    	
    	public String getName()
    	{
    		return name;
    	}
    
    	public void setName(String name)
    	{
    		this.name = name;
    	}
    	
    	public static void test(String str)
    	{
    		System.out.println("这里是该类的静态方法:"+str);
    	}
    	
    	public static void main(String[] args) throws Exception
    	{
    		Class<Linkin> class1 = Linkin.class;
    		Linkin linkin = class1.newInstance();
    		Method mth = class1.getMethod("setName", String.class);
    		mth.invoke(linkin, "LinkinPark..");
    		System.out.println(linkin.getName());
    		Method mth1 = class1.getMethod("test", String.class);
    		//要是这个方法时一个静态方法,那么传入主调是一个null就好了
    		mth1.invoke(null, "Binger");
    	}
    }
    
    

    3,访问属性值
    这个也很简单,通过Class对象可以获得对象的属性。


    如果是基本类型的话,就在get和set后面加上相应的XXX类型就可以。

    package linkin;
    
    import java.lang.reflect.Field;
    
    class Linkin 
    {
    	public String name = "LinkinPark...";
    	private int age;
    	private String andress = "hangzhou";
    	
    	public String getAndress()
    	{
    		return andress;
    	}
    
    	public void setAndress(String andress)
    	{
    		this.andress = andress;
    	}
    
    	public String getName()
    	{
    		return name;
    	}
    
    	public void setName(String name)
    	{
    		this.name = name;
    	}
    
    	public int getAge()
    	{
    		return age;
    	}
    
    	public void setAge(int age)
    	{
    		this.age = age;
    	}
    	
    }
    
    public class Test
    {
    	public static void main(String[] args) throws Exception
    	{
    		Class<Linkin> class1 = Linkin.class;
    		Linkin linkin = class1.newInstance();
    		Field name = class1.getField("name");
    		String nameValue = (String) name.get(linkin);
    		System.out.println(nameValue);
    		//不管限定符,可以获取所有的属性
    		Field andress = class1.getDeclaredField("andress");
    		//如果是私有的,或者是其他不能访问的,这里就设置可以到达,这样子就可以访问了
    		andress.setAccessible(true);
    		String andressValue = (String) andress.get(linkin);
    		System.out.println(andressValue);
    		Field age = class1.getDeclaredField("age");
    		age.setAccessible(true);
    		age.setInt(linkin, 25);
    		System.out.println(linkin.getAge());
    	}
    }
    
    
    
    

    总结:以上3个知识点经常用到,有2点注意,1,一旦在调反射得到某一个成员的时候说没有这个成员(Exception in thread "main" java.lang.NoSuchFieldException: age),那么就把原来的方法上加上declared ,这样子就不会去管这个成员上面的限定符了。2,要是说某一个私有的东西不能到达或者访问,(Class linkin.Test can not access a member of class linkin.Linkin with modifiers "private")那么就设置这个成员的setAccessible()为true。

    另外还有2个东西可以了解下,不是很常用到:

    1,操作数组:java.lang.reflect下还提供了一个Array类,这个类的对象可以代表所有的数组。下面的方法全是都是静态的。

      这个方法没有使用泛型,自己可以封装下:public static <T> T[] newInstance(Class<T> type,int len);


    package linkin;
    
    import java.lang.reflect.Array;
    
    public class Test
    {
    	public static void main(String[] args) throws Exception
    	{
    		Object arr = Array.newInstance(String.class, 5);
    		//下面的get和set方法全部都是静态的,使用起来不是很方便耶
    		Array.set(arr, 0, "LinkinPark...");
    		Array.set(arr, 1, "Binger...");
    		String linkin = (String) Array.get(arr, 0);
    		String binger = (String) Array.get(arr, 1);
    		System.out.println(linkin+":"+binger);
    		//这里强转上面那个object对象,就可以使用循环了。这个方法有点恶心,既然有了泛型了,为什么不整成泛型的呢?
    		String[] huhu = (String[]) arr;
    		for (String string : huhu)
    		{
    			System.out.println(string);
    		}
    	}
    }
    
    
    
    

    2,操作泛型:通过在反射中使用泛型,可以避免使用反射生成的对象做强转。

    package linkin;
    
    public class Test
    {
    	public static <T> T getInstance(Class<T> cls) throws InstantiationException, IllegalAccessException
    	{
    		return cls.newInstance();
    	}
    	public static void main(String[] args) throws Exception
    	{
    		Class<String> cls = String.class;
    		//这里要强转
    		String str1 = (String) cls.newInstance();
    		//这里不需要强转了
    		String str2 = Test.getInstance(String.class);
    	}
    }
    
    
    
    

    3,使用反射来获取泛型信息


    前面所讲的获取属性后,属性里面有一个方法:getType();这个方法只是对普通的属性有效,要是这个属性使用了泛型的话,就要使用getGenericType()方法。获得对应的Type类型后,就可以掉上面的方法来获得具体的泛型类型了,其实这也是就hibernate中根据配置文件来读取泛型实体属性的类型的原理。


    package linkin;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    import java.util.Map;
    
    public class Test
    {
    	private Map<String,Integer> map;
    	
    	public static void main(String[] args) throws Exception
    	{
    		Class<Test> cla = Test.class;
    		Field map = cla.getDeclaredField("map");
    		Class<?> type1 = map.getType();
    		System.out.println(type1);//interface java.util.Map
    		Type type2 = map.getGenericType();
    		if(type2 instanceof ParameterizedType)
    		{
    			//强转成为泛型参数化类型
    			ParameterizedType pType2 = (ParameterizedType) type2;
    			//原始类型
    			Type rType = pType2.getRawType();
    			System.out.println(rType);//interface java.util.Map
    			Type[] tTypes = pType2.getActualTypeArguments();
    			for (Type type : tTypes)
    			{
    				//class java.lang.String    class java.lang.Integer
    				System.out.println(type);
    			}
    		}
    		else
    		{
    			System.out.println("获取属性的泛型类型出错了吆...");
    		}
    	}
    }
    
    
    
    

  • 相关阅读:
    Kubernetes学习之路(21)之网络模型和网络策略
    Kubernetes学习之路(16)之存储卷
    Kubernetes学习之路(18)之statefulset控制器
    Kubernetes学习之路(28)之镜像仓库Harbor部署
    Kubernetes学习之路(六)之创建K8S应用
    Kubernetes学习之路(十五)之Ingress和Ingress Controller
    nali一款linux下显示IP来源的小工具
    Redis 及RedisBloom 安装
    对java注解与反射的理解
    Java 8 stream的详细用法
  • 原文地址:https://www.cnblogs.com/LinkinPark/p/5233121.html
Copyright © 2011-2022 走看看