第十四章:类型信息

类型信息

  • 本章讨论Java如何让我们在运行时识别对象和类的信息。主要有两种方式:一种是传统的RTTIRun-Time Type Information)(运行时识别类型信息),它假定我们在编译时已经知道了所有的类型。另一种是反射机制,它允许我们在运行时发现和使用类的信息。

RTTI

  • 其实之前讲过的多态就是使用了RTTI的特性。我们可以不必知道对象的具体类型,而使用更加通用的类型(基类)与这些对象打交道。运行时自有RTTI帮我们去识别对象的具体类型,然后绑定需要执行的方法。

Class对象

  • 要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作是由成为Class对象的特殊对象完成的,它包含了与类有关的信息。事实上,Class对象就是用来创建类的所有常规对象的。
  • 类是程序的一部分,每个类都有一个Class对象。为了生成这个对象,运行这个程序的Java虚拟机将使用被称为类加载器的子系统。
  • 类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载的是所谓的可信类,包括Java API类,它们通常是从本地盘加载的。在这条链中,通常不需要添加额外的类加载器。但是如果你有特殊需求(例如以某种特殊的方式加载类,以支持Web服务器应用,或者在网络中加载类),那么你有一种方式可以挂接额外的类加载器
  • 所有的类都是在对其第一次使用时,动态加载JVM中。首次使用即指调用类的静态属性或静态方法。首次使用构造器(可以看成是静态方法)也会执行类加载
  • 类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器(原生类加载器)就会根据类名查找.class文件(如果是附加类加载器,就可以去数据库读取字节码)。接下来字节码会被验证是否损坏,并且不包含不良代码(这部分可以去看另外一本书《深入理解Java虚拟机》)。
  • 一旦某个类的Class对象被载入内存,它就会被用来创建这个类的所有对象。
public class ClassTest {
    static {
        System.out.println("ClassTest loaded");
    }
    public static void main(String args[]) {
        System.out.println("run main()");
        System.out.println("before create A");
        new A();
        System.out.println("before load B");
        try {
            //返回一个Class对象,此处我们只是为了进行类加载。
            Class.forName("test.B");//我的代码是在test包下的,如果是在别的包下请修改成别的前缀
        } catch (ClassNotFoundException e) {
            //Class.forName可以会抛出该异常
            e.printStackTrace();
        }

    }
}
class A {
    static {
        System.out.println("A loaded");
    }
}
class B {
    static {
        System.out.println("B loaded");
    }
}
-----------------运行结果:
ClassTest loaded
run main()
before create A
A loaded
before load B
B loaded
  • 接下来我们稍稍了解一下Class类的一些常用方法:
class AB extends Test implements AC, AD {
    //private AB() {}//can't access
    //如果没有默认构造函数,即注释掉上一句,会抛出另外一个异常
    public AB(int i) {}//can't instantiate
}
interface AC {}
interface AD {}
public class Test {
    public static void main (String args[]) {
        try {
            //Class AB = AB.class;//AB.class称为 类字面常量
            Class AB = Class.forName("test.AB");
            printInfo(AB);
            for (Class face : AB.getInterfaces()) {
                printInfo(face);//实现的接口类
            }
            Class test = AB.getSuperclass();//父类
            printInfo(test);
            try {
                Object obj = AB.newInstance();
            } catch (InstantiationException e) {
                System.out.println("can't instantiate");
            } catch (IllegalAccessException e) {
                System.out.println("can't access");
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public static void printInfo(Class cc) {
        System.out.println("Class name: " + cc.getName());
        System.out.println("is Interface?: " + cc.isInterface());
        System.out.println("Simple name ?: " + cc.getSimpleName());
        System.out.println("Canonical name ?: " + cc.getCanonicalName());
        System.out.println("------------------");
    }
}

类字面常量

  • 我们可以用Class cc = Integer.class;的方式去获得某个类型的Class对象的引用,不过该操作不会自动地初始化(下面的第三个步骤)该Class对象。为了使用类而做的准备工作实际包含三个步骤:
    1. 加载,这是由类加载器执行的。该步骤将查找字节码,并从这些字节码中创建一个Class对象。
    2. 链接,在该阶段将验证类中的字节码,为静态域分配存储空间,如果必须的话,将解析这个类创建的对其他类的所有引用。
    3. 初始化,如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。
  • 初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域( static final int i = 1;//这个是常数静态域)进行首次引用时才执行。
  • 看了上面的介绍你可能会云里雾里的,下面来看一个例子:
package test;
import java.util.Random;
public class Test {
    public static void main (String args[]) throws ClassNotFoundException {
        //T1不会初始化,其父类Tfather也不会得到初始化
        Class t1 = T1.class;
        System.out.println("after T1.class");
        //访问常数静态域 T1不会初始化
        System.out.println(T1.a);
        //访问非常数静态域 T1得到初始化,因为T1有父类,其父类会先得到初始化
        System.out.println(T1.b);
        System.out.println("------------------");
        Class t2 = T2.class;
        //访问非常数静态域 T2得到初始化
        System.out.println(T2.a);
        System.out.println("------------------");
        //使用class.forName直接初始化
        Class t3 = Class.forName("test.T3");
        System.out.println("after Class.forName(\"test.T3\")");
        System.out.println(T3.a);
    }
}
class Tfather {
    static {
        System.out.println("Tfather 静态块");
    }
}
class T1 extends Tfather {
    static final int a = 1;//常数静态域
    static final int b = new Random().nextInt(20);//非常数静态域
    static {
        System.out.println("T1 静态块");
    }
}
class T2 {
    static int a = 2;//普通静态域
    static {
        System.out.println("T2 静态块");
    }
}
class T3 {
    static int a = 3;//普通静态域
    static {
        System.out.println("T3 静态块");
    }
}
------------------执行结果:
after T1.class
1
Tfather 静态块
T1 静态块
14
------------------
T2 静态块
2
------------------
T3 静态块
after Class.forName("test.T3")
3

泛化的Class引用

  • 之前我们都是直接Class cc;去声明一个Class类,在Java SE5 引入泛型后,我们可以利用泛型对Class引用所指向的Class对象的类型进行限定。来看下面的例子:
public class Test {
    public static void main(String args[]) {
        Class intclass = int.class;//这种声明类似Class<?> 据说Class<?>写法更加规范
        Class<Integer> genericIntClass = int.class;//此处因为是包装类,所以不会报错
        genericIntClass = Integer.class;//不会报错
        intclass = double.class;//没有泛型限制,不会报错
        //genericIntClass = double.class;//编译不通过!
    }
}
  • 如果你希望放松一下这种限制,我们可以使用通配符?加上superextends关键字做一些限制。
class O {}
class A extends O {}
class A1 extends A {}

public class Test {
    public static void main(String args[]) {
        //Class<O> aclass = A.class;//这样是行不通的。
        Class<? extends O> t1 = A.class;//利用通配符和extends即可
        t1 = A1.class;//隔两级以上也没事
        Class<? super A1> t2 = A.class;//利用通配符和super即可
        t2 = O.class;//隔两级以上也没事
    }
}
  • 使用泛型的好处除了能使编译器为我们进行检查外,通过newInstance()也能自动为我们向上转型。
class O {}
class A extends O {}
class A1 extends A {}

public class Test {
    public static void main(String args[]) {
        Class<?> t0 = A.class;
        Class<? extends O> t1 = A1.class;//利用通配符和extends即可
        Class<A1> t2 = A1.class;//利用通配符和extends即可
        //Class<A> t3 = t2.getSuperclass();//这样写是不允许的
        Class<? super A1> t3 = t2.getSuperclass();
        try {
            O o1 = (O) t0.newInstance();//必须强转
            System.out.println(o1.getClass().getName());
            O o2 = t1.newInstance();//返回一个O类型
            System.out.println(o2.getClass().getName());
            O o3 = (O) t3.newInstance();//super获得的也是Object
            System.out.println(o3.getClass().getName());
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
  • 除此之外,Class类还有一个cast方法,它能代替强转去完成一些无法直接通过强转的转换。
import java.util.ArrayList;

public class Test {
    public static void main(String args[]) throws Exception {
        ArrayList<Integer> a = new ArrayList<Integer>();
        a.add(1);
        a.getClass().getMethod("add", Object.class).invoke(a, "ssss");//这个是后话,此处只要知道可以通过反射插入其他类型而躲过编译。
        System.out.println(a.get(1));//这里对返回结果向上转型成Object了 所以不会报错
        //String ssss = (String) a.get(1);//编译不通过
        //Integer ssss = a.get(1);//运行时发生CCE异常
        String ssss = String.class.cast(a.get(1));//代替强转躲过编译
        System.out.println(ssss);
    }
}

类型转换前先做检查

  • C++中,经典的类型转换“(ClassName)”并不使用RTTI。它只是简单的告诉编译器将这个对象作为新的类型对待。而Java要执行类型检查,这通常被称为“类型安全的向下转型”。如果执行了一个错误的类型转换,就会抛出一个ClassCaseException。所以为了防止错误发生,我们可以使用instanceof进行类型检查。
import java.util.ArrayList;

public class Test {
    public static void main(String args[]) throws Exception {
        ArrayList<Integer> a = new ArrayList<Integer>();
        a.add(1);
        a.getClass().getMethod("add", Object.class).invoke(a, "ssss");
        System.out.println(a.get(1));//这里对返回结果向上转型成Object了 所以不会报错
        System.out.println(a.get(1) instanceof Integer);//false
        //String ssss = (String) a.get(1);//编译不通过
        //Integer ssss = a.get(1);//运行时异常
        String ssss = String.class.cast(a.get(1));
        System.out.println(String.class.isInstance(ssss));//true
        System.out.println(ssss);
    }
}

递归计数

  • instanceof除了可以检测一个对象的类型是否是右值的类型或子类型外,还可以用Class.isAssignableFrom(Class c)去检测传入的类型是否是相同或是其子类型。下面就举例对一个继承结构的类型进行计数。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
class O {}
class A extends O {}
class B extends O {}
class A1 extends A {}
class A2 extends A {}
class B1 extends B {}
class B2 extends B {}
public class Test {
    private static List<O> createData(int length) {
        List<O> l = new ArrayList<O>(length);
        Random r = new Random();
        for (int i = 0; i < length; i++) {
            switch (r.nextInt(7)) {
                case 0:
                    l.add(new O());
                    break;
                case 1:
                    l.add(new A());
                    break;
                case 2:
                    l.add(new A1());
                    break;
                case 3:
                    l.add(new A2());
                    break;
                case 4:
                    l.add(new B());
                    break;
                case 5:
                    l.add(new B1());
                    break;
                case 6:
                    l.add(new B2());
                    break;
            }
        }
        return l;
    }
    public static void main(String args[]) throws Exception {
        MyMap map = new MyMap(O.class);
        List<O> l = createData(20);
        for (O o : l) {
            map.count(o);
        }
        System.out.println(map);
    }
    private static class MyMap extends HashMap<Class<?>, Integer> {
        private Class<?> baseType;
        public MyMap(Class c) {
            baseType = c;
        }
        public void count(Object obj) {
            Class<?> type = obj.getClass();
            if (!baseType.isAssignableFrom(type)) {
                throw new RuntimeException(obj + " incorrect type:" + 
                        type + ", should be type or subtype of " + baseType);
            }
            countClass(type);
        }
        private void countClass(Class<?> type) {
            Integer quantity = get(type);
            put(type, quantity == null ? 1 : 1 + quantity);
            Class<?> superClass = type.getSuperclass();
            if (superClass != null && 
                    baseType.isAssignableFrom(superClass)) {
                countClass(superClass);
            }
        }
        public String toString() {
            StringBuilder sb = new StringBuilder("{");
            for (Map.Entry<Class<?>, Integer> pair : entrySet()) {
                sb.append(pair.getKey().getSimpleName());
                sb.append("=");
                sb.append(pair.getValue());
                sb.append(", ");
            }
            sb.delete(sb.length() - 2, sb.length());
            sb.append("}");
            return sb.toString();
        }
    }
}

反射:运行时的类信息

  • 如果不知道某个对象的确切类型,RTTI可以告诉你。但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI去识别它,并利用这些信息做一些有用的事。换句话说,在编译时,编译器必须知道所有要通过RTTI来处理的类。
  • 初看起来这似乎不是个限制,但是假设你获取了一个指向某个并不在你的程序空间中的对象的引用。事实上,在编译时你的程序根本没法获知这个对象所属的类。例如,假如你从磁盘文件,或者网络连接中获取了一串字节,并且你被告知这些字节代表了一个类。那么怎样才能使用这个类呢?
  • 基于构件的编程方式便是一个例子。它是一种可视化编程方法,可用通过将代表不同组件的图标拖拽到表单中来创建程序。然后在编程时通过设置构件的属性值来配置它们。这种设计时的配置,要求构件都是可实例化的,并且暴露其部分信息,以允许程序员读取和修改构件的属性。反射提供了一种机制——用来检查可用的方法,并返回方法名。Java通过JavaBeans提供了基于构件的编程架构(22章)。
  • Class类java.lang.reflect类库一起对反射概念进行了支持,该类库包含了FieldMethod以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getFields()getMethods()getConstructors()等很便利的方法,以返回表示字段、方法以及构造器的对象的数组。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。
  • 重要的是,要认识到反射机制并没有什么神奇之处。当通过反射与一个未知类型的对象打交道时,我们同样还是得先知道它属于哪个类(就像RTTI一样),并且加载其Class对象。所以反射RTTI的根本区别在于,对RTTI来说,编译器在编译时打开和检查.class文件,对反射机制来说,.class文件在编译时是不可获取的,所以是运行时打开和检查.class文件。

类方法提取器

  • 在进行了那么长的概念介绍后,我们来做几个有关反射的实验。平常我们在查阅某个具体类的定义时,只能看见其新定义重写的方法。而有些方法我们可以需要去父类或者其父类的父类去查阅。在反射机制中,它给我们提供了一个很便利的方法,下面是例子(当然对于开发工具如此便利的现在来说,根本不需要这样的方法):
package test;
import java.lang.reflect.*;

public class Test {
    private void test1() {}
    protected void test2() {}
    void test3() {}
    public void test4() {}//只有public的方法才会被打印
    public static void main(String[] args) {
        analysis("test.Test");
        System.out.println("---------");
        analysis("java.lang.String");
    }
    public static void analysis(String path) {
        try {
            Class<?> c = Class.forName(path);
            Method[] methods = c.getMethods();
            Constructor[] constructors = c.getConstructors();
            for (Method method : methods) {
                //replaceAll去除包的路径
                System.out.println(method.toString().replaceAll("\\w+\\.", ""));
            }
            for (Constructor constructor : constructors) {
                //replaceAll去除包的路径
                System.out.println(constructor.toString().replaceAll("\\w+\\.", ""));
            }
        } catch (ClassNotFoundException e) {
            System.out.println("No such class:" + e);
        }
    }
}

其他反射样例

  • 书上关于反射的用法就到此为止了,下面我再举一个例子,以熟悉反射中的一些方法。
package test;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.Arrays;
class Father {
    private String father_field1 = "father_field1";
    String father_field2 = "father_field2";
    protected String father_field3 = "father_field3";
    public String father_field4 = "father_field4";
    private void father_method1() {
        System.out.println("father_method1");
    }
    void father_method2() {
        System.out.println("father_method2");
    }
    protected void father_method3() {
        System.out.println("father_method3");
    }
    public void father_method4() {
        System.out.println("father_method4");
    }
}
class Son extends Father {
    private String field1 = "field1";
    String field2 = "field2";
    protected String field3 = "field3";
    public String field4 = "field4";
    public static String field5 = "field5";
    //这个标签只是为了测试
    @Deprecated 
    private void method1(int i) {
        System.out.println("method1");
    }
    void method2() {
        System.out.println("method2");
    }
    protected void method3() {
        System.out.println("method3");
    }
    public void method4() {
        System.out.println("method4");
    }
    public static void method5() {
        System.out.println("method5");
    }
    private Son(int i) {
        System.out.println("constructor1 number is: " + i);
    }
    Son(String i) {
        System.out.println("constructor2");
    }
    protected Son(double i) {
        System.out.println("constructor3");
    }
    public Son() {}
}
public class Test {
    /**
     * 自定义打印数组
     * @param objs
     */
    private static void printArray(Object[] objs) {
        System.out.println(Arrays.toString(objs).replaceAll("\\w+\\.", ""));
    }
    /**
     * 自定义打印某对象
     * @param obj
     */
    private static void printObj(Object obj) {
        System.out.println(obj.toString().replaceAll("\\w+\\.", ""));
    }
    private static void analysis(Object obj) 
            throws NoSuchFieldException, SecurityException,
            IllegalArgumentException, IllegalAccessException,
            NoSuchMethodException, InvocationTargetException,
            InstantiationException {
        Class<?> c = obj.getClass();
        Field[] fields1 = c.getDeclaredFields();//这种方式能获得该类型定义的所有属性
        Field[] fields2 = c.getFields();//这种方式只能获得自身及其基类所有public的属性
        printArray(fields1);
        printArray(fields2);
        /*关于Field数组的遍历就省略了,下面是一些关于Field的用法:*/
        /* 下面的方法可能会抛 NoSuchFieldException, SecurityException */
        Field field1 = c.getDeclaredField("field1");//获得该类定义的Field
        //Field field2 = c.getDeclaredField("father_field1");//java.lang.NoSuchFieldException
        Field field3 = c.getField("father_field4");//只能获得public的Field(包括基类)
        //Field field4 = c.getField("field2");//java.lang.NoSuchFieldException
        /* get可能会抛IllegalArgumentException, IllegalAccessException */
        //printObj(field1.get(obj));//因为field是private的,此处会抛 java.lang.IllegalAccessException
        field1.setAccessible(true);//设置成可访问
        printObj(field1.get(obj));//这样即使是private的也可以正常访问了。
        printObj(field3.get(obj));
        printObj(c.getField("field5").get(null));//如果是static变量,直接传null也行
        if (String.class.isAssignableFrom(field1.getType())) {
            field1.set(obj, "123");//必须要有访问权限
            System.out.println(field1.get(obj));
        }
        /*还有一些专门get set某种类型的我在此就省略了,还有返回注解的一些进行架构很有用的方法 */
        /*下面看看Method和Constructor的一些方法 */
        Method[] methods1 = c.getDeclaredMethods();//只获得新定义的任何权限修饰符的Method
        Method[] methods2 = c.getMethods();//只获得public的Method
        printArray(methods1);
        printArray(methods2);
        Method method1 = c.getDeclaredMethod("method1", int.class);//可能会抛NoSuchMethodException
        //注解类,这里只是打个照面,后面章节会有介绍
        Annotation[] as = method1.getDeclaredAnnotations();
        printArray(as);
        //invoke方法非常有用!
        method1.setAccessible(true);//private必须设成true才能访问
        method1.invoke(obj, 1);//可能会抛InvocationTargetException
        c.getDeclaredMethod("method5").invoke(null);//static方法
        /*Constructor的用法大致和Method类似,下面是newInstance的方法*/
        Constructor[] constructors1 = c.getConstructors(); 
        Constructor[] constructors2 = c.getDeclaredConstructors();
        Constructor constructor1 = c.getDeclaredConstructor(int.class);
        constructor1.setAccessible(true);
        Object obj2 = constructor1.newInstance(1);//可能会抛InstantiationException
        //接下来也可以对obj2去invoke一些Method,获取get Field
    }
    public static void main(String args[]) throws Exception {
        analysis(new Son());//我们假设Son是一个从未知获取的对象
    }
}

动态代理

  • 代理之前我们已经说过了,那么什么是动态代理呢,请看下面的例子:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

interface A {
    void a();
}
interface B {
    String b(int i);
    void b(int i, int j);
}
class ABImpl implements A, B {
    public String b(int i) {
        return i + "";
    }
    public void b(int i, int j) {
    }
    public void a() {
    }
}
class BImpl implements B {
    public String b(int i) {
        return i + "";
    }

    public void b(int i, int j) {
        for (; i<j; i++);
    }
}
class MethodSelector implements InvocationHandler {
    Object obj;
    public MethodSelector(Object obj) {
        this.obj = obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        //注意:第一个参数是用来区分来源的,一般没有什么用。
        //我们可以在此做很多有意义的事情,比如说将每次调用的记录存入数据库?
        //对每个方法的调用性能做出评估? 总之这是动态代理的好处
        long time = System.currentTimeMillis();
        Object o = method.invoke(obj, args);
        System.out.printf("method:%s args:%s\nruntime: %d\n",
                method.getName(), Arrays.toString(args), 
                System.currentTimeMillis() - time);
        return o;
    }

}
public class Test {
    public static void consumerA(A a) {
        a.a();
    }
    public static void consumerB(B b) {
        System.out.println("return value: " + b.b(1));
        b.b(1, 100000000);
    }
    public static void main(String args[]) throws Exception {
        Object o1 = Proxy.newProxyInstance(
                Test.class.getClassLoader(),//默认类加载器
                new Class[]{A.class, B.class},
                new MethodSelector(new ABImpl()));
        consumerA((A) o1);
        consumerB((B) o1);
        Object o2 = Proxy.newProxyInstance(
                Test.class.getClassLoader(),//默认类加载器
                new Class[]{B.class},
                new MethodSelector(new BImpl()));
        consumerB((B) o2);
    }
}
  • Proxy.newProxyInstance()的方法能动态生成接口,好处就是中间走了一层代理。有兴趣的可以再研究研究。

接口与类型信息

  • 我们都知道,interface关键字的一个重要目标是允许程序隔离构件,进而降低耦合性。比如A类和B类都实现了C接口。我们将其认定为C接口的类型,就可以写出共用的代码。但是这是不是意味着我们在只知道它实现了C接口就无法去调用A类的特有方法了呢?
interface A {
    void a();
}
class Son1 implements A {
    public void b() {System.out.println("b1");}
    public void a() {System.out.println("a1");}
}
public class Test {
    public static void main(String args[]) {
        A a = new Son1();
        //假设我们不知道它的类型是Son1
        //我们还是能调用到Son1的特有方法,只要进行instanceof检查即可
        if (a instanceof Son1) {
            ((Son1) a).b();
        }
    }
}
  • 那这可不行啊,我既然告诉你是A类型,你就不能使用除A类型以外的方法,不然不是不安全了吗。于是我们想到只要把Son1定义成私有的内部类不就行了么?更严格一点,我定义成匿名内部类,这下你连强转都没法写了!不仅如此,我还要把方法定义成private的,看你还怎么调用!
interface A {
    void a();
}
class Helper {
    public static A getA() {
        return new A() {
            private final int a = 1;
            private int b = 2;
            private void b() {
                System.out.println(b);
            }
            public void a() {
                System.out.println(a);
            }
        };
    }
}
public class Test {
    public static void main(String args[]) {
        A a = Helper.getA();
        //a.b();
    }
}
  • 那是否我们就没办法操作了呢?可别忘了,我们刚刚学过反射呀!只要我们知道你有b方法(通过javap反编译,或者反射获得所有的Method),那要调你不是简简单单吗?
import java.lang.reflect.*;

interface A {
    void a();
}
class Helper {
    public static A getA() {
        return new A() {
            private final int a = 1;
            private int b = 2;
            private void b() {
                System.out.println(b);
            }
            public void a() {
                System.out.println(a);
            }
        };
    }
}
public class Test {
    public static void main(String args[]) throws Exception {
        A a = Helper.getA();
        Class<?> c = a.getClass();
        Method method_b = c.getDeclaredMethod("b");
        method_b.setAccessible(true);//当然别忘记设置为可见的
        method_b.invoke(a);
        //我们还可以修改域
        Field field_b = c.getDeclaredField("b");
        field_b.setAccessible(true);
        field_b.set(a, 520);
        method_b.invoke(a);
        //可以尝试修改final值,但是没有效果
        //这里并不完全正确,在这里有个问题
        Field field_a = c.getDeclaredField("a");
        field_a.setAccessible(true);
        field_a.set(a, 520);
        a.a();
    }
}
  • 综上所述,并没有绝对的安全。然后对于最后一个修改final值的例子,我遇到了一个很诡异的问题,我个人是猜测两种初始化的方式和运行前绑定造成的。大家可以帮忙解决这个问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值