Java——反射:运行时的类信息

时间:2021-12-10 03:07:20
  1. RTTI的使用

    如果不知道某个对象的确切类型,RTTI会告诉我们,但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,并利用这些信息做一些有用的事情。

    2.什么情况下需要反射

    假设你获取了一个指向某个并不在你的程序空间的对象的引用;或者,你从磁盘文件,或者网络连接中获取了一串字节,并且你被告知这些字节代表一个类,想要使用这些类就需要反射。

   3.反射和RTTI的区别

    当通过反射与一个未知类型的对象打交道的时候,JVM只是简单的检查这个对象,看它属于那个特定的类。然后在使用这个对象之前先加载那个类的class对象,这个.class要么从本地机器获得,要么从网络中获得。RTTI和反射的区别就是,对RTTI来说,编译器在编译时打开和检查.class文件,而对于反射来说,.class在编译时是不可获取的,是在运行的时候打开和检查.class文件。

    Class类和java.lang.reflect类库一起对反射的概念进行了支持,该类库包括Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。

一、类方法提取器

import java.lang.reflect.*;
import java.util.*;
import java.util.regex.*;
class Ex1{
public Ex1(){
}
public void fun() {
System.out.println(
"fun()");
}
}
public class Ex {
public static void main(String[] args) {
Scanner in
= new Scanner(System.in);
String name
= in.nextLine();
try {
Class
<?> c = Class.forName(name);
Method[] methods
= c.getMethods();
Constructor[] ctors
= c.getConstructors();
System.out.println(
"methods:");
for(Method method: methods) {
System.out.println(method.toString());
}
System.out.println();
System.out.println(
"Constructor:");
for(Constructor ctor:ctors) {
System.out.println(ctor.toString());
}
}
catch(ClassNotFoundException e) {
System.out.println(
"ClassNotfound");
}
}
}

输出:

Ex1
methods:
public void Ex1.fun()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

Constructor:
public Ex1()

 Class.forName()生成的结果是编译时未知的,然后Class的getMethods()和getConstructors()分别返回Method对象的数组和Constructors对象的数组。Constructors就是构造器。

 

2、动态代理

代理: 代理模式(Proxy)就是为一个对象创建一个替身,用来控制对当前对象的访问。目的就是为了在不直接操作对象的前提下对对象进行访问。

interface Interface{
void doSomething();
void SomethingElse(String arg);
}
class RealObject implements Interface{

public void doSomething() {
System.out.println(
"doSomething1");
}
public void SomethingElse(String arg) {
System.out.println(
"SomethingElse1" + arg);
}
}
class SimpleProxy implements Interface{
private Interface proxied;
public SimpleProxy(Interface proxied) {
this.proxied = proxied;
}

public void doSomething() {
System.out.println(
"doSomething2");
proxied.doSomething();
}


public void SomethingElse(String arg) {
System.out.println(
"SomethingElse2" + arg);
proxied.SomethingElse(arg);
}

}
public class Ex2 {
public static void consumer(Interface iface) {
iface.doSomething();
iface.SomethingElse(
"xxxx");
}
public static void main(String[] args) {
consumer(
new RealObject());
consumer(
new SimpleProxy(new RealObject()));
}
}

输出:

doSomething1
SomethingElse1xxxx
doSomething2
doSomething1
SomethingElse2xxxx
SomethingElse1xxxx

这里的SimpleProxy和RealObject都实现了Interface,然后在SimpleProxy中创建一个RealObject的替身,用来控制对RealObject对象的访问,这样的使用称为代理模式。

代理模式的好处就是在某些情况下,一个客户不想或者不能直接引用一个对象,代理对象就再客户端和被代理对象之间起到中介的作用。就好比你在北京租房,初来乍到,人生地不熟,找房子遍地都是中介,想找房东可没那么容易(基本算得上是找不到房东直租的)。问题来了,找不到房东直租,但房子又是一切的基础,so....走中介,房东能租房,中介也能租房,不过是你通过中介去将房东的房子租给自己。OK,这就是一个活生生的代理模式的例子,相必在外漂泊的小年轻们都感同身受吧。

这样的代理在运行之前,就确定好代理类、被代理类之间的关系,称之为静态代理。像上面的代码就是一个中介只代理一个房东,如果想增加中介代理的房东就需要额外的编写代码所以出现了动态代理

Java的动态代理可以动态地创建代理并动态地处理对代理方法的调用。动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作就是揭示调用的类型并确定相应的对策。

import java.lang.reflect.*;

class DynamicProxyHandler implements InvocationHandler{

private Object proxied;
public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(proxied, args);
return null;
}

}

public class Ex3 {
public static void consumer(Interface iface) {
iface.doSomething();
iface.SomethingElse(
"xxxx");
}

public static void main(String[] args) {
RealObject real
= new RealObject();
SimpleProxy por
= new SimpleProxy(real);
Interface proxy1
= (Interface)Proxy.newProxyInstance(
Interface.
class.getClassLoader(),
new Class[] { Interface.class },
new DynamicProxyHandler(real));
consumer(proxy1);
System.out.println();
Interface proxy2
= (Interface)Proxy.newProxyInstance(
Interface.
class.getClassLoader(),
new Class[] { Interface.class },
new DynamicProxyHandler(por));
consumer(proxy2);

}
}

首先通过实现InvocationHandler接口创建动态代理类,实现代码运行过程中对各种真实对象的动态代理,构造器中将传入一个想要代理的类对象,在应该被实现的invoke方法中通过method.invoke(proxied, args)实现代理的类的方法

然后在main中调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要得到一个类加载器,一个希望该代理实现的接口列表,以及一个InvocationHandler接口的一个实现。

输出:

doSomething1
SomethingElse1xxxx

doSomething2
doSomething1
SomethingElse2xxxx
SomethingElse1xxxx

 

3、空对象

有时引入空对象的思想会很有用,它可以接受传递给它的所代表的对象的信息,但是返回值表示为实际上并不存在的任何“真实”对象的值,假如我们需要查询某个学生的信息,我们输入学号来进行查询,如果没有这个学生的话,我们就可以返回一个空对象。

interface Null{}
class Person{
public final String name;
public Person(String name){
this.name = name;
}
public String toString() {
return "the person is" + name;
}
public static class NullPerson extends Person implements Null{

private NullPerson() {
super("None");
}
public String toString() {
return "NullPerson";
}
}
public static final Person NULL = new NullPerson();
}

先创建一个标志接口,然后实现这个接口,并继承Person类,将name设为None,最后在创建一个静态final的NULL的Person对象。这个NULL就是一个空对象,使用是可以直接使用equals或者是==来与Person.NULL进行比较,还可以选择使用instanceof来探测泛化的NULL还是更具体的NullPerson。

 

5、接口与类型信息

interface A{
public void f();
}
class B implements A{

public void f() {
System.out.println(
"f()");
}

public void g() {
System.out.println(
"g()");
}
}
public class Ex5 {
public static void main(String[] args) {
A a
= new B();
System.out.println(a.getClass().getName());
if(a instanceof B) {
B b
= (B) a;
b.g();
}
}
}

输出:

B
g()

这里B是一个实现A接口的类,在main中我们可以发现a是被当作B实现的,然后我们还可以通过转型为B从而调用g()方法。这样会试代码的耦合程度超过你的期望,最简单的方法是实现使用包访问权限,在包外部的就不能使用它。

interface A{
public void f();
}
class B implements A{

public void f() {
System.out.println(
"f()");
}

public void g() {
System.out.println(
"g()");
}
}

public class Ex5 {
public static A makeA() {
return new B();
}
}

这个class中只有Ex5部分是public的,这个方法将返回一个当作B实现的A,

public class Ex6 {
public static void main(String[] args) throws Exception {
A a
= Ex5.makeA();
a.f();
System.out.println(a.getClass().getName());
/*
* B = (B) a
* 当试图将其向下转型为B时,则将会被禁止,因为没有任何的B类型可以用
*/
}
}

 但其实通过反射还是能够调用所有的方法的:

import java.lang.reflect.Method;

public class Ex6 {
public static void main(String[] args) throws Exception {
A a
= Ex5.makeA();
a.f();
System.out.println(a.getClass().getName());
callEx5Method(a,
"g");
}
static void callEx5Method(Object a, String MethodName) throws Exception {
Method g
= a.getClass().getDeclaredMethod(MethodName);
g.setAccessible(
true);
g.invoke(a);
}
}

callEx5Method方法传入一个对象,和一个String类型的MethodName(方法名),通过调用a.getClass().getDeclaredMethod(MethodName)传入方法名获得想要调用的方法,然后调用setAccessible()并传入true这样就可以通过反射访问私有方法,最后使用invoke()运行该方法。

输出:

f()
B
g()