• 第14章 类型信息


    目录

    前言

    本章主题

    14.1 为什么需要RTTI

    1.1 代码证明具体类型丢失

    1.2 RTTI的三种形式(重要)

    14.2 Class对象

    1.1 RTTI在Java中的工作原理

    1.2 Class对象用来生成常规对象(常规对象:非Class对象)

    1.2.1 类加载器工作流程

    1.2.2 获得Class对象引用的方法

    1.2.4 Class包含的有用的方法

    1.2.5 初始化惰性

    1.3 泛化的Class引用

    1.3.1 Class对象类型限制

    1.3.2 使用通配符?放松对Class对象类型的限制

    1.3.3 类型范围

    1.3.4 泛型确定具体类型

    14.3 类型转换前先做检查

    14.4 注册工厂

    14.5 instanceof 与 Class 的等价性

    14.6 反射:运行时的类信息

    1.1 类方法提取器

    14.7 动态代理

    1.1 代理

    1.2 动态代理

    14.8 空对象

    12.9 接口与类型信息


    前言

            运行时类型信息使得你可以在程序运行时发现和使用类型信息
            本章将讨论Java 是如何让我们在运行时识别对象和类的信息(如何在运行阶段,识别对象和类的信息)。主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型,另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。

    RTTI:在Java中,所有的类型转换都是在运行时进行正确性检查的。这也是RTTI的含义:在运行时,识别一个对象的类型。

    本章主题

    本章的主题围绕着”Java如何让我们在程序在运行的时候,识别出对象和类型信息“问题进行展开的。主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型,另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。

    14.1 为什么需要RTTI

            多态中表现的类型转换是RTTI最基本的使用形式,但这种转换并不彻底。如数组容器实际上将所有元素当作Object持有,取用时再自动将结果转型回声明类型。而数组在填充(持有)对象时,具体类型可能是声明类型的子类,这样放到数组里就会向上转型为声明类型,持有的对象就丢失了具体类型。而取用时将由Object只转型回声明类型,并不是具体的子类类型,所以这种转型并不彻底。
            多态中表现了具体类型的行为,但那只是“多态机制”的事情,是由引用所指向的具体对象而决定的,并不等价于在运行时识别具体类型。
            以上揭示了一个问题就是具体类型信息的丢失!有了问题,就要解决问题,这就是RTTI的需要,即在运行时确定对象的具体类型

    1.1 代码证明具体类型丢失

    1. package net.mrliuli.rtti;
    2. import java.util.Arrays;
    3. import java.util.List;
    4. /**
    5. * Created by leon on 2017/12/3.
    6. */
    7. abstract class Shape{
    8. void draw(){
    9. System.out.println(this + ".draw()");
    10. }
    11. abstract public String toString(); //要求子类需要实现 toString()
    12. }
    13. class Circle extends Shape{
    14. @Override
    15. public String toString() {
    16. return "Circle";
    17. }
    18. public void drawCircle(){}
    19. }
    20. class Square extends Shape{
    21. @Override
    22. public String toString() {
    23. return "Square";
    24. }
    25. }
    26. class Triangle extends Shape{
    27. @Override
    28. public String toString() {
    29. return "Triangle";
    30. }
    31. }
    32. public class Shapes {
    33. public static void main(String[] args){
    34. List<Shape> shapeList = Arrays.asList(
    35. new Circle(), new Square(), new Triangle() // 向上转型为 Shape,此处会丢失原来的具体类型信息!!对于数组而言,它们只是Shape类对象!
    36. );
    37. for(Shape shape : shapeList){
    38. shape.draw(); // 数组实际上将所有事物都当作Object持有,在取用时会自动将结果转型回声明类型即Shape。
    39. }
    40. //shapeList.get(0).drawCircle(); //这里会编译错误:在Shape类中找不到符号drawCircle(),证实了具体类型信息的丢失!!
    41. }
    42. }

            在这个例子中,当把Shape对象放入List的数组时会向上转型。但在向上转型为Shape的时候也丢失了Shape对象的具体类型。对于数组而言,它们只是Shape类的对象。

            在这个例子中,RTTI类型转换并不彻底:Object被转型为Shape,而不是转型为Cirele、Square或者Triangle。这是因为目前我们只知道这个Liste保存的都是Shape。在编译时,将由容器和Java的泛型系统来强制确保这一点,而在运行时,由类型转换操作来确保这一点。

            接下来就是多态机制的事情了,Shape对象实际执行什么样的代码,是由引用所指向的具体对象Circle、Square或者Triangle而决定的。通常,也正是这样要求的,你希望大部分代码尽可能少地了解对象的具体类型,而是只与对象家族中的一个通用表示打交道(在这个例子中Shape)。这样代码会更容易写,更容易读,且更便于维护,设计也更容易实现、理解和改变所以“多态”是面向对象编程的基本目标

    1.2 RTTI的三种形式(重要)

    • 传统的类型转换(多态),由RTTI确保类型转换的正确性,如果执行了错误的类型转换,会抛出一个ClassCastException的异常。
    • 代表对象类型的Class对象,通过查询Class对象获取所需要的信息
    • 类型转换前用instanceof检查

    详细讲解请参考:

    Think in java(五)RTTI的的三种形式、类型信息、class.forname与.class的区别___2018__的博客-CSDN博客

    14.2 Class对象

            要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的

    1.1 RTTI在Java中的工作原理

            要能够在运行时识别具体类型,说明必然有东西在运行时保存了具体类型信息,这个东西就是Class对象,一种特殊对象。即Class对象表示了运行时的类型信息,它包含了与类有关的信息。只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用

    •     事实上Class对象就是用来创建类的所有的“常规”对象的。
    •     每个类(接口、抽象类)都有一个Class对象。换言之,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。
    •     也就是说,Class对象在.java文件编译成.class文件时就生成了,且就保存在这个.class文件中。

    1.2 Class对象用来生成常规对象(常规对象:非Class对象)

             类是程序的一部分,每个类都有一个Class对象(包括接口、抽象类)。换言之,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用被称为“类加载器”的子系统(原生类加载器加载的是所调的可信类,包括lava API类,它们通常是从本地盘加载的)。

            所有的类都是在对其第一次使用时,动态加载到JVM中的。Java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,即使在构造器之前并没有使用static关键字。

    1.2.1 类加载器工作流程

    类加载流程:加载、验证、准备、解析、初始化(静态方法、静态代码块)。

    1. 类加载器首先检查这个类的Class对象是否已经加载。
    2. 如果尚未加载,默认的类加载器就会根据类名查找.class文件(例如,某个附加类加载器可能会在数据库中查找字节码)。
    3. 在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良Java代码(这是Java中用于安全防范目的的措施之一)。

    1.2.2 获得Class对象引用的方法

    • 通过Class.forName(),就是一个便捷途径,这种方式不需要为了获得Class对象引用而持有该类型的对象。(即没有创建过或没有这个类型的对象的时候就可以获得Class对象引用。)
    • 如果已经有一个类型的对象,那就可以通过调用这个对象的getClass()方法来获取它的Class对象引用了。这个方法属于Object,返回表示该对象的实际类型的Class对象引用。
    • 使用类字面常量.class是获取Class对象引用的另一种方法。如 FancyToy.class。建议使用这种方法。

    1.2.4 Class包含的有用的方法

    以下程序展示Class包含的很多有用的方法:

    •     getName() 获取类的全限定名称
    •     getSimpleName() 获取不含包名的类名
    •     getCanonicalName() 获取全限定的类名
    •     isInterface() 判断某个Class对象是否是接口
    •     getInterfaces() 返回Class对象实现的接口数组
    •     getSuperClass() 返回Class对象的直接基类
    •     newInstance() 创建一个这个Class对象所代表的类的一个实例对象。

            Class引用在编译期不具备任何更进一步的类型信息,所以它返回的只是一个Object引用,但是这个Object引用指向的是这个Class引用所代表的具体类型。即需要转型到具体类型才能给它送Object以外的消息
            newInstance()这个方法依赖于Class对象所代表的类必须具有可访问的默认的构造函数(Nullary constructor,即无参的构造器),否则会抛出InstantiationException 或IllegalAccessException 异常
     

    1. package net.mrliuli.rtti;
    2. interface HasBatteries{}
    3. interface Waterproof{}
    4. interface Shoots{}
    5. class Toy{
    6. Toy(){}
    7. Toy(int i){}
    8. }
    9. class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots{
    10. FancyToy(){ super(1); }
    11. }
    12. public class ToyTest {
    13. static void printInfo(Class cc){
    14. System.out.println("Class name: " + cc.getName() + " is interface? [" + cc.isInterface() + "]");
    15. System.out.println("Simple name: " + cc.getSimpleName());
    16. System.out.println("Canonical name: " + cc.getCanonicalName());
    17. }
    18. public static void main(String[] args){
    19. Class c = null;
    20. try{
    21. c = Class.forName("net.mrliuli.rtti.FancyToy");
    22. }catch (ClassNotFoundException e){
    23. System.out.println("Can't find FancyToy");
    24. System.exit(1);
    25. }
    26. printInfo(c);
    27. System.out.println("=============================");
    28. for(Class face : c.getInterfaces()){
    29. printInfo(face);
    30. }
    31. System.out.println("=============================");
    32. Class up = c.getSuperclass();
    33. Object obj = null;
    34. try{
    35. // Requires default constructor:
    36. obj = up.newInstance();
    37. }catch (InstantiationException e){
    38. System.out.println("Cannot instantiate");
    39. System.exit(1);
    40. }catch (IllegalAccessException e){
    41. System.out.println("Cannot access");
    42. System.exit(1);
    43. }
    44. printInfo(obj.getClass());
    45. }
    46. }

    1.2.5 初始化惰性

            初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行,即初始化有效地实现了尽可能 的“惰性”。
            以下程序证实了上述观点。注意,将一个域设置为static 和 final的,不足以成为“编译期量”或“常数静态域”,如 static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);就不是编译期常量,对它的引用将强制进行类的初始化。

    1. package net.mrliuli.rtti;
    2. import java.util.Random;
    3. class Initable{
    4. static final int staticFinal = 47; // 常数静态域
    5. static final int staticFinal2 = ClassInitialization.rand.nextInt(1000); // 非常数静态域(不是编译期常量)
    6. static{
    7. System.out.println("Initializing Initable");
    8. }
    9. }
    10. class Initable2{
    11. static int staticNonFinal = 147; // 非常数静态域
    12. static {
    13. System.out.println("Initializing Initable2");
    14. }
    15. }
    16. class Initable3{
    17. static int staticNonFinal = 74; // 非常数静态域
    18. static {
    19. System.out.println("Initializing Initable3");
    20. }
    21. }
    22. public class ClassInitialization {
    23. public static Random rand = new Random(47);
    24. public static void main(String[] args) throws Exception {
    25. Class initalbe = Initable.class; // 使用类字面常量.class获取Class对象引用,不会初始化
    26. System.out.println("After creating Initable ref");
    27. System.out.println(Initable.staticFinal); // 常数静态域首次引用,不会初始化
    28. System.out.println(Initable.staticFinal2); // 非常数静态域首次引用,会初始化
    29. System.out.println(Initable2.staticNonFinal); // 非常数静态域首次引用,会初始化
    30. Class initable3 = Class.forName("net.mrliuli.rtti.Initable3"); // 使用Class.forName()获取Class对象引用,会初始化
    31. System.out.println("After creating Initable3 ref");
    32. System.out.println(Initable3.staticNonFinal); // 已初始化过
    33. }
    34. }

    1.3 泛化的Class引用

    1.3.1 Class对象类型限制

            Class引用总是指向某个Class对象,此时,这个Class对象可以是各种类型的,当使用泛型语法对Class引用所指向的Class对象的类型进行限定时,这就使得Class对象的类型变得具体,这样编译器编译时也会做一些额外的类型检查工作。如

    1. package net.mrliuli.rtti;
    2. public class GenericClassReferences {
    3. public static void main(String[] args){
    4. Class intClass = int.class;
    5. Class genericIntClass = int.class;
    6. genericIntClass = Integer.class; // Same thing
    7. intClass = double.class;
    8. // genericIntClass = double.class; // Illegal, genericIntClass 限制为Integer 的Class对象
    9. }
    10. }

    1.3.2 使用通配符?放松对Class对象类型的限制

            通配符?是Java泛型的一部分,?表示“任何事物”。以下程序中Class intClass = int.class; 与 Class intClass = int.class; 是等价的但使用Class优于使用Class,因为它说明了你是明确要使用一个非具体的类引用,才选择了一个非具体的版本,而不是由于你的疏忽。

    1. package net.mrliuli.rtti;
    2. public class WildcardClassReferences {
    3. public static void main(String[] args){
    4. Class intClass = int.class;
    5. intClass = double.class;
    6. }
    7. }

    1.3.3 类型范围

     将通配符与extends关键字相结合如Class,就创建了一个范围,使得这个Class引用被限定为Number类型或其子类型

    1. package net.mrliuli.rtti;
    2. public class BoundedClassReferences {
    3. public static void main(String[] args){
    4. Class bounded = int.class;
    5. bounded = double.class;
    6. bounded = Number.class;
    7. // Or anything derived from Number
    8. }
    9. }

    当调用newInstance()方法的时候,返回的是Number类型,非具体类型。 

    1.3.4 泛型确定具体类型

    泛型类语法示例:

    1. package net.mrliuli.rtti;
    2. import java.util.ArrayList;
    3. import java.util.List;
    4. class CountedInteger{
    5. private static long counter;
    6. private final long id = counter++;
    7. public String toString(){
    8. return Long.toString(id);
    9. }
    10. }
    11. public class FilledList {
    12. private Class type;
    13. public FilledList(Class type){
    14. this.type = type;
    15. }
    16. public List create(int nElements){
    17. List result = new ArrayList();
    18. try{
    19. for(int i = 0; i < nElements; i++){
    20. result.add(type.newInstance());
    21. }
    22. }catch(Exception e){
    23. throw new RuntimeException(e);
    24. }
    25. return result;
    26. }
    27. public static void main(String[] args){
    28. FilledList fl = new FilledList(CountedInteger.class); // 存储一个类引用
    29. System.out.println(fl.create(15)); // 产生一个list
    30. }
    31. }

    总结,使用泛型类后

    • 使得编译期进行类型检查
    • .newInstance()将返回确切类型的对象,而不是Object对象

    14.3 类型转换前先做检查

    RTTI有三种形式。

    • 传统的类型转换,由RTTI确保类型转换的正确性,如果执行了错误的类型转换,会抛出一个ClassCastException的异常。
    • 代表对象类型的Class对象,通过查询Class对象获取所需要的信息
    • 类型转换前用instanceof检查

    前面我们讲了前两种,现在我们开始讲解第三种instanceof的形式:

            它返回一个布尔值,告诉我们对象是不是某个特定类型或其子类。如if(x instanceof Dog)语句会检查对象x是否从属于Dog类。
            还一种形式是动态的instanceof:Class.isInstance()方法提供了一种动态地测试对象的途径。Class.isInstance()方法使我们不再需要instanceof表达式。

    14.4 注册工厂

    14.5 instanceof 与 Class 的等价性

            在查询类型信息时,以instanceof的形式(即以instanceof的形式或isInstance0的形式,它们产生相同的结果)与直接比较Class对象有一个很重要的差别。下面的例子展示了这种差别:

    1. //: typeinfo/FamilyVsExactType.java
    2. // The difference between instanceof and class
    3. package typeinfo;
    4. import static net.mindview.util.Print.*;
    5. class Base {}
    6. class Derived extends Base {}
    7. public class FamilyVsExactType {
    8. static void test(Object x) {
    9. print("Testing x of type " + x.getClass());
    10. print("x instanceof Base " + (x instanceof Base));
    11. print("x instanceof Derived "+ (x instanceof Derived));
    12. print("Base.isInstance(x) "+ Base.class.isInstance(x));
    13. print("Derived.isInstance(x) " +
    14. Derived.class.isInstance(x));
    15. print("x.getClass() == Base.class " +
    16. (x.getClass() == Base.class));
    17. print("x.getClass() == Derived.class " +
    18. (x.getClass() == Derived.class));
    19. print("x.getClass().equals(Base.class)) "+
    20. (x.getClass().equals(Base.class)));
    21. print("x.getClass().equals(Derived.class)) " +
    22. (x.getClass().equals(Derived.class)));
    23. }
    24. public static void main(String[] args) {
    25. test(new Base());
    26. test(new Derived());
    27. }
    28. }
    29. /* Output:
    30. Testing x of type class typeinfo.Base
    31. x instanceof Base true
    32. x instanceof Derived false
    33. Base.isInstance(x) true
    34. Derived.isInstance(x) false
    35. x.getClass() == Base.class true
    36. x.getClass() == Derived.class false
    37. x.getClass().equals(Base.class)) true
    38. x.getClass().equals(Derived.class)) false
    39. Testing x of type class typeinfo.Derived
    40. x instanceof Base true
    41. x instanceof Derived true
    42. Base.isInstance(x) true
    43. Derived.isInstance(x) true
    44. x.getClass() == Base.class false
    45. x.getClass() == Derived.class true
    46. x.getClass().equals(Base.class)) false
    47. x.getClass().equals(Derived.class)) true
    48. *///:~
    • instanceofisInstance() 保持了类型的概念,它指的是“你是这个吗,或者你是这个类的派生类吗?
    • ==equals() 没有考虑继承——它要么是这个确切的类型,要么不是。

    14.6 反射:运行时的类信息

    反射的概念:在程序运行时,动态的获取类的属性和方法。

            Class类jaya.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getFields()、getMethods()和getConstructors()等很便利的方法,以返回表示字段、方法以及构造器的对象的数组(在JDK文档中,通过查找Class类可了解更多相关资料)。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。

            而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件

    1.1 类方法提取器

            反射机制提供了一种方法,使我们能够编写可以自动展示完整接口的简单工具。下面就是其工作方式:

    1. //: typeinfo/ShowMethods.java
    2. // Using reflection to show all the methods of a class,
    3. // even if the methods are defined in the base class.
    4. // {Args: ShowMethods}
    5. import java.lang.reflect.*;
    6. import java.util.regex.*;
    7. import static net.mindview.util.Print.*;
    8. public class ShowMethods {
    9. private static String usage =
    10. "usage:\n" +
    11. "ShowMethods qualified.class.name\n" +
    12. "To show all methods in class or:\n" +
    13. "ShowMethods qualified.class.name word\n" +
    14. "To search for methods involving 'word'";
    15. private static Pattern p = Pattern.compile("\\w+\\.");
    16. public static void main(String[] args) {
    17. if(args.length < 1) {
    18. print(usage);
    19. System.exit(0);
    20. }
    21. int lines = 0;
    22. try {
    23. Class c = Class.forName(args[0]);
    24. Method[] methods = c.getMethods();
    25. Constructor[] ctors = c.getConstructors();
    26. if(args.length == 1) {
    27. for(Method method : methods)
    28. print(
    29. p.matcher(method.toString()).replaceAll(""));
    30. for(Constructor ctor : ctors)
    31. print(p.matcher(ctor.toString()).replaceAll(""));
    32. lines = methods.length + ctors.length;
    33. } else {
    34. for(Method method : methods)
    35. if(method.toString().indexOf(args[1]) != -1) {
    36. print(
    37. p.matcher(method.toString()).replaceAll(""));
    38. lines++;
    39. }
    40. for(Constructor ctor : ctors)
    41. if(ctor.toString().indexOf(args[1]) != -1) {
    42. print(p.matcher(
    43. ctor.toString()).replaceAll(""));
    44. lines++;
    45. }
    46. }
    47. } catch(ClassNotFoundException e) {
    48. print("No such class: " + e);
    49. }
    50. }
    51. } /* Output:
    52. public static void main(String[])
    53. public native int hashCode()
    54. public final native Class getClass()
    55. public final void wait(long,int) throws InterruptedException
    56. public final void wait() throws InterruptedException
    57. public final native void wait(long) throws InterruptedException
    58. public boolean equals(Object)
    59. public String toString()
    60. public final native void notify()
    61. public final native void notifyAll()
    62. public ShowMethods()
    63. *///:~

    14.7 动态代理

    1.1 代理

            代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象。这些操作通常涉及与“实际”对象的通信,因此代理通常充当着中间人的角色。下面是一个用来展示代理结构的简单示例:

    1. //: typeinfo/SimpleProxyDemo.java
    2. import static net.mindview.util.Print.*;
    3. interface Interface {
    4. void doSomething();
    5. void somethingElse(String arg);
    6. }
    7. class RealObject implements Interface {
    8. public void doSomething() { print("doSomething"); }
    9. public void somethingElse(String arg) {
    10. print("somethingElse " + arg);
    11. }
    12. }
    13. class SimpleProxy implements Interface {
    14. private Interface proxied;
    15. public SimpleProxy(Interface proxied) {
    16. this.proxied = proxied;
    17. }
    18. public void doSomething() {
    19. print("SimpleProxy doSomething");
    20. proxied.doSomething();
    21. }
    22. public void somethingElse(String arg) {
    23. print("SimpleProxy somethingElse " + arg);
    24. proxied.somethingElse(arg);
    25. }
    26. }
    27. class SimpleProxyDemo {
    28. public static void consumer(Interface iface) {
    29. iface.doSomething();
    30. iface.somethingElse("bonobo");
    31. }
    32. public static void main(String[] args) {
    33. consumer(new RealObject());
    34. consumer(new SimpleProxy(new RealObject()));
    35. }
    36. } /* Output:
    37. doSomething
    38. somethingElse bonobo
    39. SimpleProxy doSomething
    40. doSomething
    41. SimpleProxy somethingElse bonobo
    42. somethingElse bonobo
    43. *///:~

            因为consumer()接受的Interface,所以它无法知道正在获得的到底是RealObject还是SimpleProxy,因为这二者都实现了Interface。但是SimpleProxy已经被插入到了客户端和RealObject之间,因此它会执行操作,然后调用RealObject上相同的方法。

    1.2 动态代理

            Java的动态代理代理的思想更向前迈进了一步,因为它可以动态地创建代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。下面是用动态代理重写的SimpleProxyDemojava:

    1. //: typeinfo/SimpleDynamicProxy.java
    2. import java.lang.reflect.*;
    3. class DynamicProxyHandler implements InvocationHandler {
    4. private Object proxied;
    5. public DynamicProxyHandler(Object proxied) {
    6. this.proxied = proxied;
    7. }
    8. public Object
    9. invoke(Object proxy, Method method, Object[] args)
    10. throws Throwable {
    11. System.out.println("**** proxy: " + proxy.getClass() +
    12. ", method: " + method + ", args: " + args);
    13. if(args != null)
    14. for(Object arg : args)
    15. System.out.println(" " + arg);
    16. return method.invoke(proxied, args);
    17. }
    18. }
    19. class SimpleDynamicProxy {
    20. public static void consumer(Interface iface) {
    21. iface.doSomething();
    22. iface.somethingElse("bonobo");
    23. }
    24. public static void main(String[] args) {
    25. RealObject real = new RealObject();
    26. consumer(real);
    27. // Insert a proxy and call again:
    28. Interface proxy = (Interface)Proxy.newProxyInstance(
    29. Interface.class.getClassLoader(),
    30. new Class[]{ Interface.class },
    31. new DynamicProxyHandler(real));
    32. consumer(proxy);
    33. }
    34. } /* Output: (95% match)
    35. doSomething
    36. somethingElse bonobo
    37. **** proxy: class $Proxy0, method: public abstract void Interface.doSomething(), args: null
    38. doSomething
    39. **** proxy: class $Proxy0, method: public abstract void Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@42e816
    40. bonobo
    41. somethingElse bonobo
    42. *///:~

            通过调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要得到一个类加载器(你通常可以从已经被加载的对象中获取其类加载器,然后传递给它),一个你希望该代理实现的接口列表(不是类或抽象类),以及InvocationHlandler接口的一个实现

            invoke()方法中传递进来了代理对象,以防你需要区分请求的来源,但是在许多情况下,你并不关心这一点。然而,在invoke()内部,在代理上调用方法时需要格外当心,因为对接口的调用将被重定向为对代理的调用。

            通常,你会执行被代理的操作,然后使用Method.invoke()将请求转发给被代理对象,并传人必需的参数。这初看起来可能有些受限,就像你只能执行泛化操作一样。但是,你可以通过传递其他的参数,来过滤某些方法调用:

    1. //: typeinfo/SelectingMethods.java
    2. // Looking for particular methods in a dynamic proxy.
    3. import java.lang.reflect.*;
    4. import static net.mindview.util.Print.*;
    5. class MethodSelector implements InvocationHandler {
    6. private Object proxied;
    7. public MethodSelector(Object proxied) {
    8. this.proxied = proxied;
    9. }
    10. public Object
    11. invoke(Object proxy, Method method, Object[] args)
    12. throws Throwable {
    13. if(method.getName().equals("interesting"))
    14. print("Proxy detected the interesting method");
    15. return method.invoke(proxied, args);
    16. }
    17. }
    18. interface SomeMethods {
    19. void boring1();
    20. void boring2();
    21. void interesting(String arg);
    22. void boring3();
    23. }
    24. class Implementation implements SomeMethods {
    25. public void boring1() { print("boring1"); }
    26. public void boring2() { print("boring2"); }
    27. public void interesting(String arg) {
    28. print("interesting " + arg);
    29. }
    30. public void boring3() { print("boring3"); }
    31. }
    32. class SelectingMethods {
    33. public static void main(String[] args) {
    34. SomeMethods proxy= (SomeMethods)Proxy.newProxyInstance(
    35. SomeMethods.class.getClassLoader(),
    36. new Class[]{ SomeMethods.class },
    37. new MethodSelector(new Implementation()));
    38. proxy.boring1();
    39. proxy.boring2();
    40. proxy.interesting("bonobo");
    41. proxy.boring3();
    42. }
    43. } /* Output:
    44. boring1
    45. boring2
    46. Proxy detected the interesting method
    47. interesting bonobo
    48. boring3
    49. *///:~

            这里,我们只查看了方法名,但是你还可以查看方法签名的其他方面,甚至可以搜索特定的参数值。

            动态代理并非是你日常使用的工具,但是它可以非常好地解决某些类型的问题。你在《Thinking in Patterns》(查看www.MindView.net)和Erich Gamma等人撰写的《DesignPatterns》°这两本书中了解到有关代理和其他设计模式的更多知识。

    14.8 空对象

    12.9 接口与类型信息

    通过使用反射,可以到达并调用一个类的所有方法,包括私有方法!如果知道方法名,就可以在其Method对象上调用setAccessible(true),然后访问私有方法。

      以下命令显示类的所有成员,包括私有成员。-private标志表示所有成员都显示。

    javap -private 类名

      因此任何人都可以获取你最私有的方法的名字和签名,即使这个类是私有内部类或是匿名内部类。

    1. package net.mrliuli.rtti;
    2. /**
    3. * Created by li.liu on 2017/12/6.
    4. */
    5. import java.lang.reflect.Method;
    6. /**
    7. * 通过反射调用所有方法(包括私有的)
    8. */
    9. public class HiddenImplementation {
    10. static void callHiddenMethod(Object obj, String methodName, Object[] args) throws Exception{
    11. Method method = obj.getClass().getDeclaredMethod(methodName);
    12. method.setAccessible(true);
    13. method.invoke(obj, args);
    14. }
    15. public static void main(String[] args) throws Exception{
    16. callHiddenMethod(new B(), "g", null);
    17. }
    18. }
    19. interface A {
    20. void f();
    21. }
    22. class B implements A{
    23. @Override
    24. public void f(){}
    25. private void g(){
    26. System.out.println("B.g()");
    27. }
    28. }

  • 相关阅读:
    Springboot+vue的医患档案管理系统。Javaee项目,springboot vue前后端分离项目。
    【Linux常用命令15】shell脚本
    基于JavaSwing开发扫雷小游戏(不同版本) 课程设计 大作业
    【无百度智能云与实体经济“双向奔赴”: 一场1+1>2的双赢 标题】
    C++——模板进阶
    达梦数据库-锁表
    tcpdump使用方法
    【python基础】input函数
    基于Jeecgboot前后端分离的平台后端系统采用jenkins发布
    金属材料/多肽/多糖/化合物/抗体/量子点/黑磷量子点修饰水凝胶
  • 原文地址:https://blog.csdn.net/qq_43783527/article/details/126032889