java之反射

来源:互联网 发布:真实挂机赚钱软件 编辑:程序博客网 时间:2024/06/06 00:18


反射技术

01-反射机制(概述&应用场景)

1、Java的反射机制:是指在运行状态中,对于任意一个类(Class文件),都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。

    这种动态获取的信息以及动态调用对象的方法的功能,称为Java语言的反射机制。

2、简单的说就是:动态获取类中的信息。可以理解为——对类的解剖。

3、这玩意有什么用?

    比如我现在做好了一个应用软件。用户想往里面搞点自己的小需求。他能搞吗?有同学说,弄呗。你搞一个类,创建个对象,完了里面再new个对象不就完事了么?!貌似是可以的。但是这应用程序已经做好了,你能在我这里面随便new吗?不可能,你都没拿到我的源代码,你怎么去new你这对象?!不行!

    那我想扩展功能怎么办?

    通常我有一应用软件,我想扩展其功能。前期在设计的时候,一般都会对外提供一个借口。我对外暴露接口,别人实现这接口,我再使用符合该接口的对象。像笔记本电脑一样,他就对外暴露了很多接口。

    那么对于应用程序来说,我们又将如何做呢?这应用程序已经可以独立运行了,它对外提供接口,是为了功能扩展。当我们需要扩展这功能的时候,我们搞一个类,实现这接口。但现在,我这类产生的对象,怎么被他应用呢?

4、图解

    以前我们就是这么搞的。但是如果不能new这个Demo,我们该怎么办呢?凭什么不能new?以前我们能new,是因为源代码在你手里。你能随便在QQ里面new对象吗?连想都别想。这对象你new不成,那该如何解决?

    你不用去了解我这应用程序里面怎么做的,也跟你没关系。你只要把这名字告诉我就行了。我这应用程序读完这文件以后,我会根据你写的这个Demo这名称,我就寻找对应的class文件。我如果找到了,我就加载这文件并获取该文件里边所有的内容。一获取到以后,我就可以对这文件进行调用。

    我能拿到你这*.class字节码文件,就意味着我连创建对象的资格都有了。因为这里面有构造函数。

5、我如何根据这一个名称,就能去找类文件,并进行加载。还能在这里面产生字节码文件的对象,调用其构造函数以及这个类中所定义的那些功能呢?利用反射技术。

6、所以,我在写软件的时候,其实就已经先把这反射技术写好了。写完以后,你不用在我这里面new对象,你也new不了。你就只能把你这类名告诉我,你什么都不用管。什么new内容,什么调用方法,我全帮你办完。你只要把你这类名告诉我就完了。

7、反射技术大大提高了程序的扩展性。

8、举个例子:

    Tomcat服务器。Tomcat提供了处理请求和应答的方式。可是怎么去处理请求,怎么去解决应答,是你说了算。因为具体的处理动作不同,所以对外提供了接口。由开发者来实现具体请求和应答处理。这个接口就是Servlet(服务端脚本片段)。他除了提供Servlet以外,你每创建一个Web应用程序,他都有一个配置文件跟着。

9、反射一般得有接口和配置文件。

10、这种开发方式现在特别常见。所以,在后面学习框架的时候,第一:知道这个框架是干吗的。第2:这框架的配置文件怎么用。第3:框架中一些常用对象的用法。剩下的就是框架的底层原理(即基本实现方式)了。

11、反射技术提高了我们程序的扩展性。因为给用户提供的桥梁是配置文件。用户在也不用面对程序了。面对这配置文件简单多了。就算是开发者,他面对配置文件也比面对着源代码强。配置文件一顿配,就能跑得起来。

02-反射机制(细节&Class对象)

1、配置文件只是记录参数。下面我们来研究如何利用反射技术去解析一个*.class文件。

2、图示

3、字节码文件,你看不懂,但是虚拟机看得懂。玩反射,无非就是拿到字节码文件中的内容。想要对一个类文件进行解剖,只要获取到该类的字节码文件对象即可。

4、图示

03-反射机制(获取Class对象的3种方式)

1、如何拿到一个类的字节码文件对象呢?这里主要介绍三种方式。

2、获取字节码文件对象的方式1Object类中的getClass方法

3、此处全部以类Person为例来演示:

public class Person {    privateintage;    private String name;    public Person(String name,int age){//有参构造       super();       this.age = age;       this.name = name;       System.out.println("Person param run..."+this.name+";"+this.age);    }    public Person(){//无参构造       super();       System.out.println("person run");    }    publicvoid show(){       System.out.println(name+"...show run..."+age);    }    privatevoid privateMethod(){       System.out.println("method run");    }    publicvoid paramMethod(String str,int num){       System.out.println("paramMethod run..."+str+";"+num);    }    publicstaticvoid staticMethod(){       System.out.println("static nethod run...");    }}<span style="font-family:Arial;color:black;BACKGROUND-COLOR: #ffffff"></span>

4、代码示例1

publicclass GetClassDemo01 {    publicstaticvoid main(String[] args) {       getClassObject1();    }    publicstaticvoid getClassObject1(){       Person p = new Person();       Class clazz = p.getClass();       Person p2 = new Person();       Class clazz1 = p2.getClass();       System.out.println(clazz==clazz1);    }}


运行结果:person run

         person run

         true(因为Pp1都是依赖于同一个class完成的)

5、方式1的弊端:想要用这种方式,必须要明确具体的类,并创建对象。麻烦!

6、获取字节码文件对象的方式2:类的静态属性class

任何数据类型都聚美一个静态的属性——.class,来获取其对应的对象。

7、代码示例2

publicclass GetClassDemo01 {    publicstaticvoid main(String[] args) {       getClassObject2();    }    publicstaticvoid getClassObject2(){       Class clazz1 = Person.class;       Class clazz2 = Person.class;       System.out.println(clazz1==clazz2);    }}


运行结果:true

8、方式2的弊端:相对简单,但是还是要明确用到类中的静态成员。还是不够扩展。

9、怎么才叫扩展?我只要把这个类的名字给你,你来帮我做这件事。

10、获取字节码文件对象的方式3:只要通过给定的类的字符串名字就可以获取该类,更为扩展。可以用Class类中的静态方法完成,该方法就是forName方法。

11、类Class<T>

static Class<?>

forName(String name, boolean initialize,ClassLoader loader)
          使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的 Class 对象。

12、代码示例3

public class GetClassDemo01 {    publicstaticvoid main(String[] args) throws ClassNotFoundException {       getClassObject3();    }    publicstaticvoid getClassObject3() throws ClassNotFoundException{       String className = "Person";       Class clazz = Class.forName(className);       System.out.println(clazz);    }}


运行结果:报错

Exception in thread "main" java.lang.Error: Unresolved compilation problem:

    Unhandled exception type ClassNotFoundException

    at day01.GetClassDemo01.main(GetClassDemo01.java:5)

13、怎么回事?注意!!!他要找这个类的时候会去这个指定路径下去找。去他的classPath去找。你这类名得写全,得写包名!你就算是导了包,也不行。导了包,你执行的是这个类。而我这里面扔的可是字符串!跟导不导包一点关系没有!

14、此处应写全

public class GetClassDemo01 {    publicstaticvoid main(String[] args) throws ClassNotFoundException {       getClassObject3();    }    public static void getClassObject3() throws ClassNotFoundException{       String className = "MyPackage.Person";       Class clazz = Class.forName(className);       System.out.println(clazz);    }}运行结果:<span style="color:black;">class MyPackage.Person  </span><span style="font-family:宋体;"><span style="color:black;">哦了</span></span>

15、方式3可以把“Person”写到配置文件里面,这样我就再也不需要去明确这个类到底是什么了。你只要给我名称,哥们我就自动找这个类去。

    这种方式,只要有名称即可,更为方便,扩展性更强。

    所以,反射的时候,以方式3为主,获取类的字节码文件对象。

04-反射机制(获取Class中的构造函数)

1、我拿到这个字节码文件对象了,我想解剖他那是易如反掌。要解剖哪一块,随你的便。

2、首先,如何通过字节码文件对象产生这个类的对象?利用Class类的newInstance方法。

3、类Class

 T

newInstance()
          创建此 Class 对象所表示的类的一个新实例。如同用一个带有一个空参数列表的 new 表达式实例化该类。如果该类尚未初始化,则初始化这个类。

    这个newInstance(),其实他也在用new Instance()这个方法在调用CLass里的这个空参构造函数。

4、产生类对象的两种方式,代码示例:

public class GetClassDemo01 {    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {       //方式1:       Person p = new Person();       //方式2:       String name = "MyPackage.Person";       Class clazz = Class.forName(name);       Object obj = clazz.newInstance();    }}


运行结果:person run

         person run

    以上两种方式都会初始化,即打印出“person run”。

5、还没完,我们创建对象的时候,一定要用空参吗?不一定!万一我的Person类中没有空参呢?你再去Object obj = clazz.newInstance()调用空参,就完了。

6、代码示例:

public class GetClassDemo01 {    public static void main(String[] args) {       //有参构造       Person p = new Person("小强",39);    }}


    注意:Class中,能new对象的方法就newInstance这么一个,调用空参。一般反射的类都有空参,这样获取实例更方便。但要是没有空参怎么办呢?

    既然是通过指定的构造函数进行对象的初始化。所以应该先获取到该构造函数。这通过字节码文件即可完成,该方法是getConstructor(paramterTypes)

7、类Class

 Constructor<T>

getConstructor(Class<?>... parameterTypes)
          返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。

 Constructor<?>[]

getConstructors()
          返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。

Constructor<T>

getDeclaredConstructor(Class<?>... parameterTypes)
          返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。

 Constructor<?>[]

getDeclaredConstructors()
          返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法。

8、我这里先用getConsturctor(parameterTypes)ParameterTypes是参数类型。我们要想获取构造函数,首先构造函数的名称都是一致的。但是参数列表不一样。所以我们要想获取某一个构造函数,得明确其参数列表。我哪知道这构造函数的参数类型是什么样的啊?

9、代码示例:——获取有参构造函数,并用此函数的构造器实例化对象

public class CreatObject {    public static void main(String[] args) throws Exception {       creatNewObject();    }    public static void creatNewObject() throws Exception{       String name = "Person";       //找寻该名称类文件,并加载进内存,产生class对象       Class clazz = Class.forName(name);       //获取到了指定的构造函数对象       Constructor constructor = clazz.getConstructor(String.class,int.class);       //通过该构造器对象的newInstance方法进行对象的初始化       Object obj = constructor.newInstance("小明",38);    }}


运行结果:Person param run...小明;38

10java.lang.reflect

Constructor

 T

newInstance(Object... initargs)
          使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。

11、上例中的creatNewObject方法功能同Person p = new Person(“小明”,38);功能是等同的。扩展性很强。

05-反射机制(获取Class中的字段)

1java.lang

    Class<T>

 Field

getField(String name)
          返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。

 Field[]

getFields()
          返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。

 Field

getDeclaredField(String name)
          返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。

 Field[]

getDeclaredFields()
          返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。

2、代码示例——有问题

public class GetField {    public static void main(String[] args) throws Exception {       getFieldDemo();    }    public static void getFieldDemo() throws Exception{       String name = "Person";       Class clazz = Class.forName(name);       Field field = clazz.getField("age");       System.out.println(field);    }}运行报错:

Exception in thread "main" java.lang.NoSuchFieldException: age

    at java.lang.Class.getField(Unknown Source)

    at GetField.getFieldDemo(GetField.java:11)

    at GetField.main(GetField.java:6)

3、抛异常了,怎么回事?明明有age。问题是你这个age是私有的。凡是写了“get内容”的这些方法,它返回的都只能是公共的。想拿私有的,得用“getDeclaredField”。凡是Declared都可以拿类中所有的内容。,包括私有的!

4、代码示例2——拿字段对象

public class GetField {    public static void main(String[] args) throws Exception {       getFieldDemo();    }    public static void getFieldDemo() throws Exception{       String name = "Person";       Class clazz = Class.forName(name);       Field field = clazz.getDeclaredField("age");       System.out.println(field);    }}


运行结果:private int Person.age

5、这个字段被我拿到了,我能不能操作它?字段至少有两个动作可以做:设置值、获取值。而怎么设置值、获取值,字段自己最清楚。

6java.lang.reflect

Field

Object

get(Object obj)
          返回指定对象上此 Field 表示的字段的值。

7、你获取就获取吧,怎么参数还是一个对象?因为要明确是拿哪个对象的成员变量。

8、代码示例3——拿字段的值

public class GetField {    public static void main(String[] args) throws Exception {       getFieldDemo();    }    public static void getFieldDemo() throws Exception{       String name = "Person";       Class clazz = Class.forName(name);       Object obj = clazz.newInstance();       Field field = clazz.getDeclaredField("age");       Object o = field.get(obj);       System.out.println(o);    }}


运行结果:

person run

Exception in thread "main" java.lang.IllegalAccessException: Class GetField can not access a member of class Person with modifiers "private"

    at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)

    at java.lang.reflect.Field.doSecurityCheck(Unknown Source)

    at java.lang.reflect.Field.getFieldAccessor(Unknown Source)

    at java.lang.reflect.Field.get(Unknown Source)

    at GetField.getFieldDemo(GetField.java:13)

    at GetField.main(GetField.java:6)

9、一运行,挂了。又怎么了?这个类不能访问Person类中的成员age,因为他是私有的(private)。

10、那我就想拿这个私有字段的值,怎么办?

java.lang.reflect中的这个类

AccessibleObject 类是 FieldMethod Constructor对象的基类。它提供了将反射的对象标记为在使用时取消默认 Java语言访问控制检查的能力。对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 FieldMethod Constructor对象来设置或获取字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。

在反射对象中设置 accessible标志允许具有足够特权的复杂应用程序(比如 Java Object Serialization或其他持久性机制)以某种通常禁止使用的方式来操作对象。

void

setAccessible(boolean flag)
          将此对象的 accessible 标志设置为指示的布尔值。

11、以上到底是什么意思?对私有的字段、方法、构造器取消权限检查!

12、代码示例:暴力访问!我利用上面的就可以获取私有化字段的值

public class GetField {    public static void main(String[] args) throws Exception {       getFieldDemo();    }    public static void getFieldDemo() throws Exception{       String name = "Person";       Class clazz = Class.forName(name);       Object obj = clazz.newInstance();       Field field = clazz.getDeclaredField("age");       //对私有字段的访问取消权限检查,暴力访问!       field.setAccessible(true);//取消权限       Object o1 = field.get(obj);       System.out.println(o1);       field.set(obj, 88);//设置字段的值       Object o2 = field.get(obj);       System.out.println(o2);    }}


运行结果:

person run

0

88

06-反射机制(获取Class中的方法)

1java.lang

Class

Method

getDeclaredMethod(String name,Class<?>... parameterTypes)
          返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。

 Method[]

getDeclaredMethods()
          返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。

 Method

getMethod(String name,Class<?>... parameterTypes)
          返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。

 Method[]

getMethods()
          返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共member 方法。

2、代码示例1:获取Class中的公共方法

public class GetMethod {    public static void main(String[] args) throws Exception {       getMethodDemo();    }    public static void getMethodDemo() throws Exception{       String name = "Person";       Class clazz = Class.forName(name);       Method[] methods = clazz.getMethods();       for(Method m:methods){           System.out.println(m);       }    }}


运行结果:

public void Person.show()

public void Person.paramMethod(java.lang.String,int)

public static void Person.staticMethod()

public native int java.lang.Object.hashCode()

public final native java.lang.Class java.lang.Object.getClass()

public final void java.lang.Object.wait() throws java.lang.InterruptedException

public final void java.lang.Object.wait(long,int) throwsjava.lang.InterruptedException

public final native void java.lang.Object.wait(long) throwsjava.lang.InterruptedException

public boolean java.lang.Object.equals(java.lang.Object)

public final native void java.lang.Object.notify()

public final native void java.lang.Object.notifyAll()

public java.lang.String java.lang.Object.toString()

3、由结果可见,getMethods方法,获取的都是公有的方法,父类中的也拿。

4、私有的怎么拿呢?使用getDeclaredMethods方法。

5、代码示例2——获取Class中声明的所有方法(但不包括继承过来的方法)

public class GetMethod {    public static void main(String[] args) throws Exception {       getMethodDemo();    }    public static void getMethodDemo() throws Exception{       String name = "Person";       Class clazz = Class.forName(name);       Method[] methods = clazz.getDeclaredMethods();       for(Method m:methods){           System.out.println(m);       }    }}


运行结果:

public void Person.show()

private void Person.privateMethod()

public void Person.paramMethod(java.lang.String,int)

public static void Person.staticMethod()

6、我现在想拿其中的1个方法,可以利用getMethod方法和getDeclaredMethod方法。

7、代码示例3——拿Class中的一个无参方法,以拿Person类中的show方法为例。

public class GetMethod2 {    public static void main(String[] args) throws Exception {       getMethodDemo2();    }    public static void getMethodDemo2() throws Exception{       String name = "Person";       Class clazz = Class.forName(name);       Constructor constructor = clazz.getConstructor(String.class,int.class);       Object obj = constructor.newInstance("小明",37);       //拿方法与只拿构造不同,拿构造只需要参数列表。而拿方法,需要方法名+参数列表       Method method = clazz.getMethod("show", null);//获取名为show的空参构造方法       //我拿到了这个Method对象,怎么运行他自己最清楚,Method的invoke方法       //另外,你以前是Person p = new Person();p.show();方法得拿对象去调用       method.invoke(obj, null);    }}


运行结果:

Person param run...小明;37

小明...show run...37

8、代码示例4——拿Class中的一个有参方法,以拿Person类中的paramMethod方法为例。

public class GetMethod2 {    public static void main(String[] args) throws Exception {       getMethodDemo2();    }    public static void getMethodDemo2() throws Exception{       String name = "Person";       Class clazz = Class.forName(name);       Constructor constructor = clazz.getConstructor(String.class,int.class);       Object obj = constructor.newInstance("小明",37);       //拿方法与只拿构造不同,拿构造只需要参数列表。而拿方法,需要方法名+参数列表       Method method = clazz.getMethod("paramMethod", String.class,int.class);//获取名为show的空参构造方法       //我拿到了这个Method对象,怎么运行他自己最清楚,Method的invoke方法       //另外,你以前是Person p = new Person();p.show();方法得拿对象去调用       method.invoke(obj, "小强",89);    }}


运行结果:

Person param run...小明;37

paramMethod run...小强;89

07-反射机制(反射练习)

1、下面以一个实例来感受一下反射的扩展性

2、比如,我现在有一台电脑

class MainBoard {    public static void run(){       System.out.println("main board run...");    }}class ReflectTest{    public static void main(String[] args) {       MainBoard mb = new MainBoard();       mb.run();    }}


运行结果:main board run...(很简单)

3、这后来,我又买了一个声卡

public class SoundCard {    public void open(){       System.out.println("sound open");    }    public void close(){       System.out.println("sound close");    }}


4、那我如何把他加进来,使用它呢?再在MainBoard类里面写一个方法?

class MainBoard {    public static void run(){       System.out.println("main board run...");    }    public static void useSoundCard(SoundCard s){       s.open();       s.close();    }}


    不合适!万一后面又来一网卡,你再写一个useNetCard方法?!这么写扩展性没有。你在不断的修改原来的代码。这些东西都是后来的,我怎么知道这后面有什么。

5、具体的东西我们不知道,但我们可以对外提供一个规则。你只要按我这规则做,你的功能我就能用。

6、我在主板上搞一个接口。让主板对外暴露这个接口,那么后面无论什么设备,只要符合这个规则,就都可以开启和关闭。(后面的设备一定要实现这个接口才行!!!

7、走到这一步,我就可以在主板上利用接口来接收后面来的东西了。

interface PCI{    public abstract void open();    public abstract void close();}class MainBoard {    public static void run(){       System.out.println("main board run...");    }    public static void usePCI(PCI p){       p.open();       p.close();    }}


8、但是麻烦又来了,你使用声卡:usePCI(new SoundCard());你使用网卡:usePCI(new NetCard());每次添加一个设备都需要修改代码,传递一个新创建的对象进来。能不能不修改代码就完成这个动作?

    可以。不用new来完成,而是只获取其class字节码文件,在内部实现创建对象的动作。

9、建立一个配置文件。这里先不用xml,先用properties

10、代码示例——反射

interface PCI{    public abstract void open();    public abstract void close();}class MainBoard {    public static void run(){       System.out.println("main board run...");    }    public static void usePCI(PCI p){       p.open();       p.close();    }}class ReflectTest{    public static void main(String[] args) throws Exception {       MainBoard mb = new MainBoard();       mb.run();       File configFile = new File("pci.properties");       Properties prop = new Properties();       FileInputStream fis = new FileInputStream(configFile);       prop.load(fis);       for(int x = 0;x<prop.size();x++){           String pciName = prop.getProperty("pci"+(x+1));           Class clazz = Class.forName(pciName);           PCI p = (PCI)clazz.newInstance();           mb.usePCI(p);       }       fis.close();    }}


    我写完这程序,以后我就再也不用动了。

11、运行结果:

pci.properties文件中我什么都不写,一运行:main board run...

pci.properties文件中我写pci1=com.itheima.SoundCard,一运行:

       main board run...

       sound open

        sound close

pci.properties文件中我写pci1=com.itheima.SoundCard

                         pci2= com.itheima.NetCard

,一运行:

    main board run...

    sound open

    sound close

    net open

    net close

12、我现在,只需不断的修改这个配置文件,并把我这个子类对象的名称写在这里就OK了!!!

 

0 0