Heim >Java >javaLernprogramm >Detaillierte Erläuterung von RTTI und Reflexionsmechanismus in Java
Dieser Artikel befasst sich hauptsächlich mit der Codeanalyse von Javas RTTI und dem Reflexionsmechanismus. Während er die Identifizierung von Laufzeittypen vorstellt, zeigt er Ihnen auch Beispiele und zeigt, wann der Reflexionsmechanismus verwendet wird. Freunde können sich darauf beziehen.
RTTI, also Laufzeittypidentifikation, Laufzeittypidentifikation. Die Identifizierung des Laufzeittyps ist ein sehr nützlicher Mechanismus in Java. Während der Java-Laufzeit verwaltet RTTI klassenbezogene Informationen. RTTI kann jeden zur Kompilierungszeit bekannten Typ zur Laufzeit automatisch identifizieren.
Oft ist eine Aufwärtstransformation erforderlich. Beispielsweise leitet die Basisklasse die abgeleitete Klasse ab, aber die vorhandene Methode benötigt nur das Basisobjekt als Parameter, und das, was tatsächlich übergeben wird, ist ein Verweis auf die abgeleitete Klasse. Dann spielt RTTI zu diesem Zeitpunkt eine Rolle. RTTI kann beispielsweise erkennen, dass die Derive-Klasse eine abgeleitete Klasse von Base ist, sodass sie nach oben in Derived umgewandelt werden kann. In ähnlicher Weise wird bei der Verwendung von Schnittstellen als Parameter häufiger eine Aufwärtstransformation verwendet, und RTTI kann bestimmen, ob zu diesem Zeitpunkt eine Aufwärtstransformation durchgeführt werden kann.
Diese Typinformationen werden durch das spezielle Objekt des Klassenobjekts (java.lang.Class) vervollständigt, das klassenbezogene Informationen enthält. Immer wenn eine Klasse geschrieben und kompiliert wird, wird eine .class-Datei generiert, die das Klassenobjekt enthält. Die Java Virtual Machine (JVM), auf der dieses Programm ausgeführt wird, verwendet ein Subsystem, das als Klassenlader (Class Loader) bezeichnet wird. Der Klassenlader lädt nicht alle Klassenobjekte, bevor das Programm ausgeführt wird. Wenn sie nicht geladen wurden, sucht der Standard-Klassenlader anhand des Klassennamens nach der .class-Datei (z. B. kann ein zusätzlicher Klassenlader nach dem Wort suchen). Im Bytecode wird der Bytecode dieser Klasse beim Laden überprüft, um sicherzustellen, dass er nicht beschädigt ist und keinen fehlerhaften Java-Code enthält. Dies ist auch einer der Typensicherheitsmechanismen in Java. Sobald das Klassenobjekt einer bestimmten Klasse in den Speicher geladen ist, können alle Objekte dieser Klasse erstellt werden.
package typeinfo; class Base { static { System.out.println("加载Base类"); } } class Derived extends Base { static { System.out.println("加载Derived类");} } public class Test { static void printerInfo(Class c) { System.out.println("类名: " + c.getName() + "是否接口? [" + c.isInterface() + "]"); } public static void main(String[] args) { Class c = null; try { c = Class.forName("typeinfo.Derived"); } catch (ClassNotFoundException e) { System.out.println("找不到Base类"); System.exit(1); } printerInfo(c); Class up = c.getSuperclass(); // 取得c对象的基类 Object obj = null; try { obj = up.newInstance(); } catch (InstantiationException e) { System.out.println("不能实例化"); System.exit(1); } catch (IllegalAccessException e) { System.out.println("不能访问"); System.exit(1); } printerInfo(obj.getClass()); } /* 输出: 加载Base类 加载Derived类 类名: typeinfo.Derived是否接口? [false] 类名: typeinfo.Base是否接口? [false] */ }
Im obigen Code ist die Methode forName eine statische Methode und der Parameter ist der Klassenname Wird verwendet, um herauszufinden, ob sie existiert. Wenn diese Klasse gefunden wird, gibt sie eine Klassenreferenz zurück. Andernfalls wird eine ClassNotFoundException-Ausnahme ausgelöst.
Wenn sich die Klasse nicht im Standardordner, sondern in einem bestimmten Paket befindet, muss der vorherige Paketname enthalten sein, z. B. hier typeinfo.Derived.
Mit der getSuperclass-Methode können Sie das der Basisklasse entsprechende Class-Objekt zurückgeben. Verwenden Sie die newInstance-Methode, um ein Instanzobjekt gemäß der Standardkonstruktion zu erstellen und es auszulösen, wenn es nicht instanziiert werden kann und nicht darauf zugegriffen werden kann. Es werden InstantiationException und IllegalAccessException ausgelöst.
Java bietet auch eine Möglichkeit, einen Verweis auf ein Klassenobjekt, also ein Klassenliteral, zu generieren. Für das obige Programm entspricht up der Base.class.
Für Wrapper-Klassen grundlegender Datentypen entspricht char.class dem Zeichen.TYPE und int.class dem Integer.TYPE. Die verbleibende ab.class entspricht Ab.TYPE. (Void.class entspricht beispielsweise Void.TYP). Darüber hinaus sind int.class und Integer.class ab Java SE5 dasselbe.
Verallgemeinerte Klassenreferenz, siehe Code unten
Class intClass = int.class; Class<Integer> genericIntClass = int.class; genericIntClass = Integer.class; // 等价 intClass = double.class; // ok // genericIntClass = double.class; // Illegal!
Die Referenz auf das Classc0f559cc8d56b43654fcbe4aa9df7b4a-Objekt gibt ein Integer-Objekt an Es kann nicht auf den Verweis auf double.class verweisen. Um die Einschränkung zu lockern, können Sie das Platzhalterzeichen ? verwenden, also Class6b3d0130bba23ae47fe2b8e8cddf0195. Der Effekt ist der gleiche wie bei Class, aber der Code ist eleganter Verwenden Sie versehentlich oder fahrlässig einen unspezifischen Klassenverweis. Gleichzeitig können die geerbten Klassen eingeschränkt werden, Beispiele sind wie folgt
class Base {} class Derived extends Base {} class Base2 {} public class Test { public static void main(String[] args) { Class<? extends Base> cc = Derived.class; // ok // cc = Base2.class; // Illegal } }
Der Grund für das Hinzufügen einer generischen Syntax zur Klassenreferenz besteht lediglich darin, Kompilierzeit bereitzustellen Typprüfung, damit Typfehler gefunden werden können.
Zusammenfassend umfassen die uns bekannten RTTI-Formulare:
1. Bei der herkömmlichen Typkonvertierung garantiert RTTI die Richtigkeit der Typkonvertierung Die Typkonvertierung löst eine ClassCastException aus.
2 Das Class-Objekt, das den Typ des Objekts darstellt, kann durch Abfragen des Class-Objekts (dh durch Aufrufen der Methode der Class-Klasse) abgerufen werden Informationen zu den Anforderungen.
Die klassische Typkonvertierung in C++ verwendet kein RTTI. Weitere Informationen finden Sie im RTTI-Abschnitt von C++. (Nebenbei bemerkt, als ich C++ lernte, habe ich nur beiläufig einen Blick auf das RTTI-Kapitel geworfen. Jetzt erinnere ich mich, dass Dynamic_cast und andere Dinge speziell für die Typsicherheit hinzugefügt wurden. C++ kann Selektivität in Bezug auf die Sicherheit bieten, genau wie Java. StringBuilder und StringBuffer, Sicherheit und Effizienz können nicht gleichzeitig erreicht werden? Java ist hinsichtlich der Typsicherheit zwingender, ebenso wie der Ausdruck x = 1 nicht implizit in einen booleschen Typ konvertiert werden kann.
Es gibt eine dritte Form von RTTI in Java, nämlich das Schlüsselwort „instanceof“, das einen Booleschen Wert zurückgibt, um anzugeben, ob das Objekt ein Beispiel eines bestimmten Typs ist den folgenden Code.
class Base {} class Derived extends Base {} public class Test { public static void main(String[] args) { Derived derived = new Derived(); System.out.println(derived instanceof Base); // 输出true } }
Verwenden Sie „instanceof“, um bestimmte Typen zu bestimmen. Beispielsweise muss die Basisklasse „Shape“ verschiedene Klassen ableiten (Kreis, Rechteck usw.). Wird für alle Kreise verwendet, und die Eingabeparameter sind eine Reihe von Formobjekten. Zu diesem Zeitpunkt können Sie mit instandof bestimmen, ob das Formobjekt ein Kreisobjekt ist.
RTTI可以识别程序空间的所有类,但是有时候需要从磁盘文件或网络文件中读取一串字节码,并且被告知这些字节代表一个类,就需要用到反射机制。
比如在IDE中创建图形化程序时会使用到一些控件,只需要从本地的控件对应class文件中读取即可,然后再主动修改这些控件的属性。(题外话:大概.net组件就是这样的?学C#时总听到反射,但总没感觉用过,前几天做.net项目的同学也跟我说他从来都没用过委托和事件……)
Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含Field、Method和Constructor类(每个类都实现了Member接口),这些类型的对象都是JVM在运行时创建的,用以表示未知类里对应成员。
这样就可以用Constructor创建未知对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke方法调用与Method对象关联的字段,等等。
// 使用反射展示类的所有方法, 即使方法是在基类中定义的 package typeinfo; // Print类的print方法等价于System.Out.Println,方便减少代码量 import static xyz.util.Print.*; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.regex.Pattern; // {Args: typeinfo.ShowMethods} public class ShowMethods { private static String usage = "usage:\n" + "ShowMethods qualified.class.name\n" + "To show all methods in class or:\n" + "ShowMethods qualified.class.name word\n" + "To search for methods involving 'word'"; // 去掉类名前面的包名 private static Pattern p = Pattern.compile("\\w+\\."); public static void main(String[] args) { if (args.length < 1) { print(usage); System.exit(0); } int lines = 0; try { Class<?> c = Class.forName(args[0]); // 反射获得对象c所属类的方法 Method[] methods = c.getMethods(); // 反射获得对象c所属类的构造 Constructor[] ctors = c.getConstructors(); if (args.length == 1) { for (Method method : methods) print(p.matcher(method.toString()).replaceAll("")); for (Constructor ctor : ctors) print(p.matcher(ctor.toString()).replaceAll("")); } } catch (ClassNotFoundException e) { print("No such class: " + e); } } /* public static void main(String[]) public final void wait() throws InterruptedException public final void wait(long,int) throws InterruptedException public final native void wait(long) throws InterruptedException public boolean equals(Object) public String toString() public native int hashCode() public final native Class getClass() public final native void notify() public final native void notifyAll() public ShowMethods() */ }
简单来说,反射机制就是识别未知类型的对象。反射常用于动态代理中。举例如下:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; class DynamicProxyHandler implements InvocationHandler { private Object proxied; // 代理对象 public DynamicProxyHandler(Object proxied) { // TODO Auto-generated constructor stub this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub System.out.println("代理类: " + proxy.getClass() + "\n" + "代理方法: " + method + "\n" + "参数: " + args); if (args != null) for (Object arg : args) System.out.println(" " + arg); return method.invoke(proxied, args); } } interface Interface { void doSomething(); } class RealObject implements Interface { @Override public void doSomething() { // TODO Auto-generated method stub System.out.println("doSomething"); } } public class DynamicProxyDemo { public static void consumer(Interface iface) { iface.doSomething(); } public static void main(String[] args) { RealObject realObject = new RealObject(); // 使用动态代理 Interface proxy = (Interface)Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[] { Interface.class }, new DynamicProxyHandler(realObject)); consumer(proxy); } /* 输出: 代理类: class $Proxy0 代理方法: public abstract void Interface.doSomething() 参数: null doSomething */ }
代理是基本的设计模式之一,即用代理类为被代理类提供额外的或不同的操作。而动态代理则需要一个类加载器,就像Java实现RTTI时需要类加载器加载类的信息,这样就可以知道类的相关信息。
关键方法是:
Object java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class6b3d0130bba23ae47fe2b8e8cddf0195[] interfaces, InvocationHandler h) throws IllegalArgumentException
传入三个参数:代理接口的加载器(通过Class对象的getClassLoader方法获取),代理的方法接口,代理对象
前两个参数很好理解,就是要代理的方法所属的接口对应的Class对象(主语)的加载器和Class对象本身,主要是参数3,要设计一个实现InvocationHandler接口的类,作为代理对象,一般命名以Handler结尾,Handler翻译为处理者,很形象,就是代替原对象进行处理的处理者(即代理),在程序设计中经常被翻译成“句柄”。
这个类通过传入代理对象来构造,比如这里传入的是Object对象。然后必须覆盖invoke方法。
通过最后输出和invoke方法的具体实现可以发现,return method.invoke(proxied, args);是相当于原对象调用该方法(类似C++的回调函数?)
由于有类加载器,所以代理对象可以知道原对象的具体类名、方法、参数,本示例在调用方法前就输出了这些。
实际应用中可能会针对类名而有所选择。比如接口中有好多个类,你可以选择性的对特定的类、方法、参数进行处理
比如 if(proxied instanceof RealObject) {} 或者 if(method.getName.equals("doSomething")) {}
总结
Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung von RTTI und Reflexionsmechanismus in Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!