Java 反射 -超详细讲解(附源码)「建议收藏」

Java 反射 -超详细讲解(附源码)「建议收藏」  之前也有学习过反射,可是很快就忘了,也不知道有什么用,怎么用,故特此写下此文,以加深对java反射的理解1:反射概述  JAVA反射机制是在运行状态中1,对于任意一个类,都

大家好,又见面了,我是你们的朋友全栈君。

  学到spring框架的时候,发现反射思想很重要,故特此写下此文,以加深理解。

1:反射概述

  JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
  实际上,我们创建的每一个类也都是对象,即类本身是java.lang.Class类的实例对象。这个实例对象称之为类对象,也就是Class对象。那么,Class对象又是什么对象呢?


2:Class对象特点

  下图是Class类的api(图片来自于Java基础之—反射(非常重要)

在这里插入图片描述
   从图中可以得出以下几点:

  1. Class 类的实例对象表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有很多的实例,每个类都有唯一的Class对象。
  2. Class 类没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机自动构造的。也就是说我们不需要创建,JVM已经帮我们创建了。
  3. Class 对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法

3:反射的使用

  假设我们现在有一个Hero类

package pojo;
public class Hero { 
   
	public String name; //姓名
    public float hp; //血量
    public float armor; //护甲
    public int moveSpeed; //移动速度
}

1:获取类对象

获取类对象有3种方式

  1. Class.forName()常用
  2. Hero.class
  3. new Hero().getClass()

在一个JVM中,一种类,只会有一个类对象存在。所以以上三种方式取出来的类对象,都是一样。(此处准确是在ClassLoader下,只有一个类对象)

示例:

package pojo;
public class ObjectTest { 
   
	
	public static void main(String[] args) { 
   
        String className = "pogo.Hero";
		try { 
   
        	//获取类对象的第一种方式
            Class pClass1 = Class.forName(className);
            //获取类对象的第二种方式
            Class pClass2 = Hero.class;
            //获取类对象的第三种方式
            Class pClass3 = new Hero().getClass();
            System.out.println(pClass1==pClass2);//输出true
            System.out.println(pClass1==pClass3);//输出true
        } catch (ClassNotFoundException e) { 
   
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 }
}

  三种方式中,常用第一种,第二种需要导入类的包,依赖太强,不导包就抛编译错误。第三种对象都有了还要反射干什么。一般都第一种,一个字符串可以传入也可写在配置文件中等多种方法。


2 利用反射机制创建对象

基本步骤
  与传统的通过new 来获取对象的方式不同反射机制,反射会先拿到Hero的“类对象”,然后通过类对象获取“构造器对象”再通过构造器对象创建一个对象,具体步骤:

1.获取类对象 Class class = Class.forName("pojo.Hero");
2.获取构造器对象 Constructor con = clazz.getConstructor(形参.class);
3 获取对象 Hero hero =con.newInstance(实参);

上面是最简单的获取方法,当Hero的构造方法不是无参构造方法时,获取构造器对象略有不同,见下面测试:

构造方法不同时,获取构造器对象的方法

示例:

  1. Hero类添加6种构造方法
//---------------构造方法-------------------
	//(默认的构造方法)
	Hero(String str){ 
   
		System.out.println("(默认)的构造方法 s = " + str);
	}
	
	//无参构造方法
	public Hero(){ 
   
		System.out.println("调用了公有、无参构造方法执行了。。。");
	}
	
	//有一个参数的构造方法
	public Hero(char name){ 
   
		System.out.println("姓名:" + name);
	}
	
	//有多个参数的构造方法
	public Hero(String name ,float hp){ 
   
		System.out.println("姓名:"+name+"血量:"+ hp);
	}
	
	//受保护的构造方法
	protected Hero(boolean n){ 
   
		System.out.println("受保护的构造方法 n = " + n);
	}
	
	//私有构造方法
	private Hero(float hp){ 
   
		System.out.println("私有的构造方法 血量:"+ hp);
	}
  1. 通过反射机制获取对象
package test;
public class ConstructorTest { 
   
	
	 
	/* * 通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员; * * 1.获取构造方法: * 1).批量的方法: * public Constructor[] getConstructors():所有"公有的"构造方法 public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有) * 2).获取单个的方法,并调用: * public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法: * public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有; * * 2.创建对象 * Constructor对象调用newInstance(Object... initargs) */

	 
	public static void main(String[] args) throws Exception { 
   
		//1.加载Class对象
		Class clazz = Class.forName("pojo.Hero");
		
		
		//2.获取所有公有构造方法
		System.out.println("**********************所有公有构造方法*********************************");
		Constructor[] conArray = clazz.getConstructors();
		for(Constructor c : conArray){ 
   
			System.out.println(c);
		}
		
		
		System.out.println("************所有的构造方法(包括:私有、受保护、默认、公有)***************");
		conArray = clazz.getDeclaredConstructors();
		for(Constructor c : conArray){ 
   
			System.out.println(c);
		}
		
		System.out.println("*****************获取公有、无参的构造方法*******************************");
		Constructor con = clazz.getConstructor(null);
		//1>、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型
		//2>、返回的是描述这个无参构造函数的类对象。
		System.out.println("con = " + con);
		//调用构造方法
		Object obj = con.newInstance();
		
		
		System.out.println("******************获取私有构造方法,并调用*******************************");
		con = clazz.getDeclaredConstructor(float.class);
		System.out.println(con);
		//调用构造方法
		con.setAccessible(true);//暴力访问(忽略掉访问修饰符)
		obj = con.newInstance(100);
	}
	

}

输出:

**********************所有公有构造方法*********************************
public pojo.Hero(java.lang.String,float)
public pojo.Hero(char)
public pojo.Hero()
************所有的构造方法(包括:私有、受保护、默认、公有)***************
private pojo.Hero(float)
protected pojo.Hero(boolean)
public pojo.Hero(java.lang.String,float)
public pojo.Hero(char)
public pojo.Hero()
pojo.Hero(java.lang.String)
*****************获取公有、无参的构造方法*******************************
con = public pojo.Hero()
调用了公有、无参构造方法执行了。。。
******************获取私有构造方法,并调用*******************************
private pojo.Hero(float)
私有的构造方法   血量:100.0

总结:

1.获取构造器对象方法:
  1).批量的方法:
public Constructor[] getConstructors():所有”公有的”构造方法
public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
  2).获取单个的方法:
public Constructor getConstructor(Class… parameterTypes): 获取单个的”公有的”构造方法
public Constructor getDeclaredConstructor(Class…parameterTypes):获取”某个构造方法”可以是私有的,或受保护、默认、公有;


3: 获取成员变量并使用

基本步骤

1.获取HeroPlus类的对象 new方法/第2章中的方法 h
2. 获取属性 Field f1 = h.getDeclaredField("属性名")
3. 修改属性 f1.set(h,实参),注意这里的h是对象,不是类对象

示例:

  1. 新增HeroPlus类
package pojo;
public class HeroPlus { 
   
	public String name;
    public float hp;
    public int damage;
    public int id;
     
    public String getName() { 
   
        return name;
    }
    public void setName(String name) { 
   
        this.name = name;
    }
    public HeroPlus(){ 
   
         
    }
    public HeroPlus(String string) { 
   
        name =string;
    }
 
    @Override
    public String toString() { 
   
        return "Hero [name=" + name + "]";
    }
    public boolean isDead() { 
   
        // TODO Auto-generated method stub
        return false;
    }
    public void attackHero(HeroPlus h2) { 
   
        System.out.println(this.name+ " 正在攻击 " + h2.getName());
    }
}
  1. 获取属性并修改
package test;
public class ParaTest { 
   
	 public static void main(String[] args) { 
   
         HeroPlus h =new HeroPlus();
         //使用传统方式修改name的值为garen
         h.name = "garen";
        
         try { 
   
             //获取类HeroPlus的名字叫做name的字段
             Field f1= h.getClass().getDeclaredField("name");
             //修改这个字段的值
             f1.set(h, "teemo");
             //打印被修改后的值
             System.out.println(h.name);
              
         } catch (Exception e) { 
   
             // TODO Auto-generated catch block
             e.printStackTrace();
         }
 }
}

补充
getField和getDeclaredField的区别
   getField 只能获取public的,包括从父类继承来的字段。
   getDeclaredField 可以获取本类所有的字段,包括private的,但是 不能获取继承来的字段。 (注: 这里只能获取到private的字段,但并不能访问该private字段的值,除非加上setAccessible(true))


4: 获取成员方法并使用

  1. 获取HeroPlus类的对象 h
  2. 获取成员方法:
    public Method getMethod(String name ,Class<?>… parameterTypes):获取”公有方法”;(包含了父类的方法也包含Object类)
    public Method getDeclaredMethods(String name ,Class<?>… parameterTypes) :获取成员方法,包括私有的(不包括继承的)
    参数解释:
      name : 方法名;
      Class … : 形参的Class类型对象
  3. 调用方法
    Method –> public Object invoke(Object obj,Object… args):
    参数说明:
      obj : 要调用方法的对象;
      args:调用方式时所传递的实参;

示例:

package test;
public class MethodTest { 
   
	public static void main(String[] args) { 
   
	
	 HeroPlus h = new HeroPlus();
	 
     try { 
   
         // 获取这个名字叫做setName,参数类型是String的方法
         Method m = h.getClass().getMethod("setName", String.class);
         // 对h对象,调用这个方法
         m.invoke(h, "盖伦");
         // 使用传统的方式,调用getName方法
         System.out.println(h.getName());

     } catch (Exception e) { 
   
         // TODO Auto-generated catch block
         e.printStackTrace();
     }

 }
}


5: 获取main方法并使用

示例:

  1. HeroPlus 新增main方法
public static void main(String[] args) { 
   
		System.out.println("执行main方法");
	}
  1. 通过下面步骤获取main方法1
package test;
public class MainTest { 
   
	public static void main(String[] args) { 
   
		try { 
   
			//1、获取HeroPlus对象的字节码
			Class clazz = Class.forName("pojo.HeroPlus");
			
			//2、获取main方法,第一个参数:方法名称,第二个参数:方法形参的类型,
			 Method methodMain = clazz.getMethod("main", String[].class);
			//3、调用main方法
			// methodMain.invoke(null, new String[]{"a","b","c"});
			//第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数
			//这里拆的时候将 new String[]{"a","b","c"} 拆成3个对象。所以需要将它强转。
			 methodMain.invoke(null, (Object)new String[]{ 
   "a","b","c"});//方式一
			// methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二
			
		} catch (Exception e) { 
   
			e.printStackTrace();
		}
		
		
	}
}

4 : 关于反射的用法举例

  反射非常强大,但是从上面的记录来看,反而觉得还不如直接调用方法来的直接和方便2

  通常来说,需要在学习了Spring 的依赖注入,反转控制之后,才会对反射有更好的理解,所以先这里举两个例子,来演示一下反射的一种实际运用3

1:通过反射运行配置文件内容

  1. 首先准备两个业务类
package service;
public class Service1 { 
   
    public void doService1(){ 
   
        System.out.println("业务方法1");
    }
}

package service;
public class Service2 { 
   
	public void doService2(){ 
   
        System.out.println("业务方法2");
    }
}

  1. 当需要从第一个业务方法切换到第二个业务方法的时候,使用非反射方式,必须修改代码,并且重新编译运行,才可以达到效果
package service;
public class CommonTest { 
   
	  public static void main(String[] args) { 
   
		  //new Service1().doService1();
		  //必须重新修改代码
	        new Service2().doService2();
	    }
}
  1. 使用反射方式则方便很多
  1. 首先准备一个配置文件,就叫做spring.txt吧, 放在src目录下。
    里面存放的是类的名称,和要调用的方法名。首先准备一个配置文件,就叫做spring.txt吧, 放在src目录下。里面存放的是类的名称,和要调用的方法名。
  2. 在测试类Test中,首先取出类名称和方法名,然后通过反射去调用这个方法。
  3. 当需要从调用第一个业务方法,切换到调用第二个业务方法的时候,不需要修改一行代码,也不需要重新编译,只需要修改配置文件spring.txt,再运行即可。

示例:
spring.txt内容

class=reflection.Service1
method=doService1

测试类

package service;
public class ReflectTest { 
   
	@SuppressWarnings({ 
    "rawtypes", "unchecked" })
    public static void main(String[] args) throws Exception { 
   
 
        //从spring.txt中获取类名称和方法名称
        File springConfigFile = new File("H:\\eclpise-workspace\\reflect-demo\\src\\spring.txt");
        Properties springConfig= new Properties();
        springConfig.load(new FileInputStream(springConfigFile));
        String className = (String) springConfig.get("class");
        String methodName = (String) springConfig.get("method");
         
        //根据类名称获取类对象
        Class clazz = Class.forName(className);
        //根据方法名称,获取方法对象
        Method m = clazz.getMethod(methodName);
        //获取构造器
        Constructor c = clazz.getConstructor();
        //根据构造器,实例化出对象
        Object service = c.newInstance();
        //调用对象的指定方法
        m.invoke(service);
         
    }
}

2:通过反射越过泛型检查

  泛型是在编译期间起作用的。在编译后的.class文件中是没有泛型的。所有比如T或者E类型啊,本质都是通过Object处理的。所以可以通过使用反射来越过泛型。

示例:

package test;
public class GenericityTest { 
   
	public static void main(String[] args) throws Exception{ 
   
		
	ArrayList<String> list = new ArrayList<>();
	list.add("this");
	list.add("is");
	
	// strList.add(5);报错
	
	/********** 越过泛型检查 **************/
	
	//获取ArrayList的Class对象,反向的调用add()方法,添加数据
	Class listClass = list.getClass(); 
	//获取add()方法
	Method m = listClass.getMethod("add", Object.class);
	//调用add()方法
	m.invoke(list, 5);
	
	//遍历集合
	for(Object obj : list){ 
   
		System.out.println(obj);
		}
	}

}


GitHub源码:java反射


  1. Java基础之—反射(非常重要) ↩︎

  2. 反射有什么用 ↩︎

  3. 深入分析Java方法反射的实现原理
    后记:
      本文部分内容引用自csdn的敬业的小码哥How2jJava,如有侵权,请联系我 ↩︎

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/136804.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • C#操作XML的完整例子——XmlDocument篇「建议收藏」

    C#操作XML的完整例子——XmlDocument篇「建议收藏」这是一个用c#控制台程序下, 用XmlDocument进行XML操作的的例子,包含了查询、增加、修改、删除、保存的基本操作。较完整的描述了一个XML的整个操作流程。适合刚入门.netXML操作的朋友参考和学习。假设有XML文件:books.xmlxml version=”1.0″ encoding=”UTF-8″?>books> book>  name>哈里波特name>  pr

  • 常见分布式id生成方案_分布式id生成方案

    常见分布式id生成方案_分布式id生成方案文章目录一、为什么要用分布式ID1、什么是分布式ID2、那么分布式ID需要满足哪些条件二、分布式ID有哪些生成方式1、基于UUID2、基于数据库自增ID3、基于数据库集群模式4、基于数据库的号段模式5、基于Redis模式6、基于雪花算法(Snowflake)模式7、百度(uid-generator)8、美团(Leaf)号段模式snowflake模式9、滴滴(Tinyid)Http方式接入Java客户端方式接入三、总结一、为什么要用分布式ID在说分布式ID的具体实现之前,我们来简单分析一下为什么用分布式

    2022年10月25日
  • linux安装pycharm报错:Unable to detect graphics environment[通俗易懂]

    linux安装pycharm报错:Unable to detect graphics environment[通俗易懂]执行shpycharm.sh时,报错:StartupErrorUnabletodetectgraphicsenvironment解决方法:重新开启一个终端,以用户而非root身份登入,重新执行shpycharm.sh很神奇,但是成功了!

    2022年10月10日
  • ingress什么意思_k8s kong

    ingress什么意思_k8s kongk8sIngress介绍Http代理Https代理Ingress介绍我们已经知道,Service对集群之外暴露服务的主要方式有两种:NodePort和LoadBalancer,但是这两种方式,都有一定的缺点:NodePort方式的缺点是会占用很多集群机器的端口,那么当集群服务变多的时候,这个缺点就愈发明显。LoadBalancer的缺点是每个Service都需要一个LB,浪费,麻烦,并且需要kubernetes之外的设备的支持。基于这种现状,kubernetes提供了Ingress资源对象,I

  • android之SharedPreferences简介[通俗易懂]

    SharedPreferences是Android平台上一个轻量级的存储类,主要是保存一些常用的配置比如窗口状态,一般在Activity中重载窗口状态onSaveInstanceState保存一般使用SharedPreferences完成,它提供了Android平台常规的Long长整型、Int整型、String字符串型的保存.2个activity 之间的数据传递除了可以通过intent来传递,

  • 在线发送免费传真(五个网上免费发传真网站介绍)

    在线发送免费传真(五个网上免费发传真网站介绍)首先我们看到的是免费网络传真:faxZERO  官方网址是:http://faxzero.com/  这个免费传真适用于美国和加拿大,发送的传真上会自动附上广告,每次发传真只能发三页,也每天只可以发两次。  faxZERO无需你拥有一台传真机,也不需要注册用户即可在线发送免费网络传真,每天可发送2个免费网络传真,每个免费网络传真最多可发3个页面,支持.DOC(Word)和.

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号