Java 反射 -超详细讲解(附源码)

1:反射概述

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

  实际上,我们创建的每一个类也都是对象,即类本身是java.lang.Class类的实例对象。这个实例对象称之为类对象,也就是Class对象。那么,Class对象又是什么对象呢?

2:Class对象特点

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


   从图中可以得出以下几点:

Class 类的实例对象表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有很多的实例,每个类都有唯一的Class对象。

Class 类没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机自动构造的。也就是说我们不需要创建,JVM已经帮我们创建了。

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的构造方法不是无参构造方法时,获取构造器对象略有不同,见下面测试:

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

示例:

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);

}


通过反射机制获取对象

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是对象,不是类对象

示例:

新增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());

    }

}


获取属性并修改

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: 获取成员方法并使用

获取HeroPlus类的对象 h

获取成员方法:

public Method getMethod(String name ,Class<?>… parameterTypes):获取"公有方法";(包含了父类的方法也包含Object类)

public Method getDeclaredMethods(String name ,Class<?>… parameterTypes) :获取成员方法,包括私有的(不包括继承的)

参数解释:

  name : 方法名;

  Class … : 形参的Class类型对象

调用方法

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方法并使用

示例:

HeroPlus 新增main方法

public static void main(String[] args) {

System.out.println("执行main方法");

}


通过下面步骤获取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 的依赖注入,反转控制之后,才会对反射有更好的理解,但是刚学到这里的同学,不一定接触了Spring,所以在这里举两个例子,来演示一下反射的一种实际运用。

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");

    }

}


当需要从第一个业务方法切换到第二个业务方法的时候,使用非反射方式,必须修改代码,并且重新编译运行,才可以达到效果

package service;

public class CommonTest {

  public static void main(String[] args) {

  //new Service1().doService1();

  //必须重新修改代码

        new Service2().doService2();

    }

}


使用反射方式则方便很多

首先准备一个配置文件,就叫做spring.txt吧, 放在src目录下。

里面存放的是类的名称,和要调用的方法名。首先准备一个配置文件,就叫做spring.txt吧, 放在src目录下。里面存放的是类的名称,和要调用的方法名。

在测试类Test中,首先取出类名称和方法名,然后通过反射去调用这个方法。

当需要从调用第一个业务方法,切换到调用第二个业务方法的时候,不需要修改一行代码,也不需要重新编译,只需要修改配置文件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);

}

}

}



原文链接:https://blog.csdn.net/lililuni/article/details/83449088

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容