zoukankan      html  css  js  c++  java
  • 类型信息小笔记

    RTTI

    是Runtime type information的缩写,可以让你在程序运行的时候,发现和使用类的类型信息。

    在有了泛型的容器中拿元素出来,就是一个RTTI最基本的体现。因为需要把容器中存的Object对象,转换成你泛型写的那个对象,这个转换的检查是发生在运行时的,所以是RTTI。

    (Shape)强制转型也是RTTI的一个体现

     

    Class Object

    要知道RTTI在Java中是怎么工作的,你总得在运行时知道类型信息是怎么展示的吧。

    这个类型信息在运行时的获取,就是通过这个Class Object 来获取的,这个对象存储着类的信息。

    在你的程序中的每个类,都有一个对应的Class 对象,这个Class Object其实就和普通的对象一样,类名是Class而已。每次你写完并编译一个类,这个类的Class Object也同时被创建,并存储在.class文件中。为了创建这个Class对象,JVM用了个叫类加载器的子系统。

    类加载器子系统包含一条类加载器链。
    但只有一个原生类加载器,这个也是JVM的实现的一部分。
    这个原声类加载器加载的都是可信类,包括Java的API,一般都是直接从本地硬盘上加载的。

    这个链中,一般不需要添加额外的类加载器,除非你有些特殊的需求。

    所以类都是在第一次被使用的时候,被动态加载到JVM中去的。

    类加载器首先检查类对象是否被加载。
    没的话,默认的类加载器根据类名找.class文件;
    附加的类加载器可能会去数据库找字节码。类的字节码在加载的时候,她们会接受验证,保证没有被破坏和不包含坏的Java代码。static的初始化也是在类加载的时候进行的。

    获得Class Object对象引用的方法:

    1. 用Class类的static方法——Class.forName()

    传一个类名String进去,然后可以获得这个类的Class对象的引用。

    在调用这个forName()方法的时候,如果这个类还没被加载,就会加载它,所以这里static初始化语句被执行了。这是forName()方法很重要也很有用的一个功能。

    2.Object类的getClass()方法

    如果你已经有了这个类的对象,那么你可以通过Object类的一个方法—— getClass()来获取这个类的Class对象引用。

    3.类字面常量——.class

    类名.class的形式咯就。

    对于基本数据类型的封装类,有个标准eld叫做TYPE,这个field提供了基本数据类型的Class Object。   比如Integer.TYPE就可以获得基本数据类型int的Class Object或者说是int.class

    意,.class有个和forName方法不一样的地方:.class会返回一个Class Object的引用,但不会自动进行初始化操作。这里的初始化指的是这个类的对象的类加载和一些static的初始化。或者说是类初始化。加载一个类需要三步:

    而第三步初始化类,被延迟到你第一次执行该类的static方法才会进行。这个就和Class.forName()方法不一样了。

    补充:

    Classloader.loadClass(String name)方法得到的class是不进行link也就是链接操作的,所以当然后面的初始化也不会执行到,也就是不会初始化static field和static块。

    通过这个Class Object你可以获得很多类型信息。下面这个例子展示了一些方法:

    interface HasBatteries {}
    interface Waterproof {}
    interface Shoots {}
    
    class Toy {
        // Comment out the following default constructor
        // to see NoSuchMethodError from (*1*)
        Toy() {}
        Toy(int i) {}
    }
    
    class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots {
        FancyToy() { super(1); }
    }
    
    public class ToyTest {
        static void printInfo(Class cc) {
            print("Class name: " + cc.getName() + " is interface? [" + cc.isInterface() + "]");
            print("Simple name: " + cc.getSimpleName());
            print("Canonical name : " + cc.getCanonicalName());
        }
        public static void main(String[] args) {
            Class c = null;
            try {
                c = Class.forName("typeinfo.toys.FancyToy");
            } catch(ClassNotFoundException e) {
                print("Can’t find FancyToy");
                System.exit(1);
            }
            printInfo(c);
            for(Class face : c.getInterfaces())
                printInfo(face);
            Class up = c.getSuperclass();
            Object obj = null;
            try {
                // Requires default constructor:
                obj = up.newInstance();
            } catch(InstantiationException e) {
                print("Cannot instantiate");
                System.exit(1);
            } catch(IllegalAccessException e) {
                print("Cannot access");
                System.exit(1);
            }
            printInfo(obj.getClass());
        }
    } 
    /* Output:
    Class name: typeinfo.toys.FancyToy is interface? [false]
    Simple name: FancyToy
    Canonical name : typeinfo.toys.FancyToy
    Class name: typeinfo.toys.HasBatteries is interface? [true]
    Simple name: HasBatteries
    Canonical name : typeinfo.toys.HasBatteries
    Class name: typeinfo.toys.Waterproof is interface? [true]
    Simple name: Waterproof
    Canonical name : typeinfo.toys.Waterproof
    Class name: typeinfo.toys.Shoots is interface? [true]
    Simple name: Shoots
    Canonical name : typeinfo.toys.Shoots
    Class name: typeinfo.toys.Toy is interface? [false]
    Simple name: Toy
    Canonical name : typeinfo.toys.Toy
    *///:~

    几点要注意的:

    1.Class.forName()一定要填完整的类名。

    2.介个newInstance()方法是Class中一个实现虚拟构造器的方法。例子中,up是一个Class Object的引用,但在编译期不具备任何进一步的类型信息。然后你利用这个newInstance()方法获得了一个Object对象的引用。

    但用这个方法,你这个类要有默认的构造器,想想也知道,这个方法是无参数的,所以你必须要一个无参的构造器呢。

    加上泛型的Class引用

    一个Class引用可以指向一个Class对象,看下面的代码:

    Class intClass = int.class;
    Class<Integer> genericIntClass = int.class;
    genericIntClass = Integer.class; // Same thing
    intClass = double.class;
    // genericIntClass = double.class; // Illegal

    看这个例子就知道要讲什么东西了,本来你可以用一个Class Object的引用去指向任意一个类的Class Object。
    但加上泛型后,像genericIntClass,这个Class Object的引用就只能指向正确的类的Class Object了。

    使用泛型语法,可以让编译器进行额外的类型检查

    哦对,如果你的Class引用有泛型的话,那么执行newInstance()就不再返回Object对象了,会帮你正确转型。

    新的转型语法

    直接看例子:

    //: typeinfo/ClassCasts.java
    class Building {}
    class House extends Building {}
    public class ClassCasts {
        public static void main(String[] args) {
            Building b = new House();
            Class<House> houseType = House.class;
            House h = houseType.cast(b);
            h = (House)b; // ... or just do this.
        }
    } ///:

    说,这个cast方法似乎看起来直接用(House)b这样的强制转型就可以了,但有时候你不是很方便用这种普通的转型,比如在写泛型代码的时候,你有一个Class Object的引用,然后想转型的时候,这个cast就有用了。

    RTTI的第三种形式——instanceof

    前面两种是(Shape)强制 转换还有Class Object。

    instanceof方法有个局限哦,只能比较类的名称,不能是一个Class Object。

    还有个动态的instanceof:

    利用Class Object中的isInstance()后更方便,消除了之前傻逼的instanceof语句,然后而且现在代码设计也更好了。

    instanceof和isInstance()生成的结果完全一样;如果获得Class引用,用equals()和==来检查Class对象是否相等,这两个方法的结果也一样。

    但这两组方法的含义却不同,instanceof系列保持了类型的概念,它指的是”你是这个类吗,或者是你是这个类的派生类吗?“而用equals()或者==比较实际的Class对象,没有考虑继承——它或者是确切的类型,或者不是。

    还有个判断子类的方法:

    又一个判断是不是的方法,
    superClass.isAssignableFrom(childClass) 属于 Class.java。它的对象和参数都是类,意思是“父类(或接口类)判断给定类是否是它本身或其子类”
    子接口也可以判断

    反射

    我们的这个RTTI呢,有个限制,就是这个类型必须是编译时期已知的,换种话说,在编译时,编译器必须知道所有要通过RTTI处理的类。

    但有时候捏,你会获得一个指向并不在你程序空间中的对象的引用,这个时候的类是在编译后过了很久才出现的,所以用RTTI无法知道它的类型信息。

    这个时候就要用反射的机制了。

    Class类还有java.lang.reflect类库一起对反射进行了支持,这个reflect库中有Field、Method、Constructor类,每个类都实现了Member接口。这些几个类都的JVM在运行时才创建的,用以表示未知类的对应成员,其实就把未知类的成员都抽象成类。这样你就可以用Constructor创建新的对象;用get和set方法读取和修改Field对象相关联的字段;用invoke方法调用与Method对象相关联的方法,还有一些很方便的方法等等会介绍。

    这样未知类的信息就可以在运行时知道了,在编译期什么都不用知道。

    当通过反射来和一个未知类型的对象打交道的时候,JVM只是简单地检查这个对象,看它属于哪个类,在进行任何操作之前,必须加载这个类的Class Object;所以这个类的.class文件对于JVM来说必须是可获取的,要么在本地机器上,要么通过网络。

    所以关于RTTI和反射真正的区别是:对于RTTI而言,编译器在编译时打开和检查.class文件;对于反射而言,.class文件在编译时是不可获取的,所以在运行时打开和检查.class文件。

    看个简单的用法,就查看类的相关方法信息的:

    Class<?> c = Class.forName(args[0]);
    Method[] methods = c.getMethods();
    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(""));
        lines = methods.length + ctors.length;
    } else {
        for(Method method : methods)
            if(method.toString().indexOf(args[1]) != -1) {
                print(p.matcher(method.toString()).replaceAll(""));
                lines++;
            }
        for(Constructor ctor : ctors)
            if(ctor.toString().indexOf(args[1]) != -1) {
                print(p.matcher(ctor.toString()).replaceAll(""));
                lines++;
        }
    }

    代码是不完整的哈哈,理解下就是。

    动态代理

    代理是基本设计模式之一。它是为你提供额外的或者是不同的操作,而插入的用来替代实际对象的对象。这些操作通常涉及与实际对象的通信,因此代理经常充当中间人的角色。

    代理的一个很简单的实现的方式就是,和真实对象实现同一个接口,然后就可以充当真实对象了,看个例子:

    interface Interface {
        void doSomething();
        void somethingElse(String arg);
    }
    
    class RealObject implements Interface {
        public void doSomething() { print("doSomething"); }
        public void somethingElse(String arg) {
            print("somethingElse " + arg);
        }
    }
    
    class SimpleProxy implements Interface {
        private Interface proxied;
    
        public SimpleProxy(Interface proxied) {
            this.proxied = proxied;
        }
        public void doSomething() {
            print("SimpleProxy doSomething");
            proxied.doSomething();
        }
        public void somethingElse(String arg) {
            print("SimpleProxy somethingElse " + arg);
            proxied.somethingElse(arg);
        }
    }
    
    class SimpleProxyDemo {
        public static void consumer(Interface iface) {
            iface.doSomething();
            iface.somethingElse("bonobo");
        }
        public static void main(String[] args) {
            consumer(new RealObject());
            consumer(new SimpleProxy(new RealObject()));
        }
    } 
    /* Output:
    doSomething
    somethingElse bonobo
    SimpleProxy doSomething
    doSomething
    SimpleProxy somethingElse bonobo
    somethingElse bonobo
    *///:~
    View Code

    代理可以帮你把一些额外的操作放在别的地方。

    一般都会在代理类中放一个被代理类也就是真实对象的引用,方便通信和操作。

    Java的动态代理就肯定更厉害了,可以动态地创建代理并且动态地处理对代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,Java有一个专门作为这个处理器的接口InvocationHandler;这个处理器的工作就是揭示调用的类型并确定相应的对策。

    下面是用Java动态代理重写的一个例子:

    import java.lang.reflect.*;
    class DynamicProxyHandler implements InvocationHandler {
        private Object proxied; //被代理对象,真实。
        public DynamicProxyHandler(Object proxied) {
            this.proxied = proxied;
        }
    
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args);
            if(args != null)
                for(Object arg : args)
                    System.out.println(" " + arg);
            return method.invoke(proxied, args);
        }
    }
    
    class SimpleDynamicProxy {
        public static void consumer(Interface iface) {
            iface.doSomething();
            iface.somethingElse("bonobo");
        }
        public static void main(String[] args) {
            RealObject real = new RealObject();
            consumer(real);
            // Insert a proxy and call again:
            Interface proxy = (Interface)Proxy.newProxyInstance(
                Interface.class.getClassLoader(),
                new Class[]{ Interface.class },
                new DynamicProxyHandler(real));
            consumer(proxy);
        }
    } 
    /* Output: (95% match)
    doSomething
    somethingElse bonobo
    **** proxy: class $Proxy0, method: public abstract void
    Interface.doSomething(), args: null
    doSomething
    **** proxy: class $Proxy0, method: public abstract void
    Interface.somethingElse(java.lang.String), args:
    [Ljava.lang.Object;@42e816
    bonobo
    somethingElse bonobo
    *///:~

    首先介绍这个调用处理器的接口——InvocationHandler.

    实现这个接口要重写Object invoke()方法。这个方法有三个参数:

    参数1:
      代理的对象,代理人,或者说中间人。就是动态生成的代理类的实例,这个是Java会自动传进来的,这个参数你可以1. 可以使用反射获取代理对象的信息(也就是proxy.getClass().getName());

    2.可以将代理对象返回以进行连续调用,这就是proxy存在的目的。因为this并不是代理对象。所以好像说,在invoke方法内部调用这个proxy代理对象的方法要很小心,因为会被重定向为对代理的调用。
    参数2:
      调用的方法,被执行的方法。这个参数是个Method类的对象,而这个对象有个方法Method.invoke(),通过这个方法你可以把请求转发给被代理的那个对象,并传入需要的参数。
    参数3:
      执行该方法所需要的参数

    理解就是,你用Java帮你动态生成的代理类执行一个方法后,这个方法的执行立刻就会被重定向到这个指定的InvocationHandler的实现类中,然后映射到这个invoke方法中进行处理。

    然后创建动态代理,是通过Proxy.newProxyInstance()方法。这个方法一般也需要三个参数:

    参数1:
      类加载器,可以从已经加载的对象那里拿一个喔。(似乎传的都是要实现的接口的类加载器)。

      理解应该是:指明生成代理对象使用哪个类装载器。

      thinking in Java说,一般可以从已经加载的对象中获取其类加载器然后传递给它喔。
    参数2:
      你想让动态代理类实现的接口。

      这里似乎传的是接口的Class Object列表。
    参数3:
      InvocationHandler接口的实现类。动态代理会把所有调用重定向到这个invocation handler上,所以一般这个invocation handler的实现类的构造器会传一个真正对象的引用。

    通过反射可以使用private的方法,还可以访问和修改private的field

    一个用反射来调用方法的例子:

    public interface A {
        void f();
    } ///:
    
    class HiddenImplementation {
        static void callHiddenMethod(Object a, String methodName) throws Exception {
            Method g = a.getClass().getDeclaredMethod(methodName);
            g.setAccessible(true);
            g.invoke(a);
        }
    }
    
    class InnerA {
        private static class C implements A {
            public void f() { print("public C.f()"); }
            public void g() { print("public C.g()"); }
            void u() { print("package C.u()"); }
            protected void v() { print("protected C.v()"); }
            private void w() { print("private C.w()"); }
        }
        public static A makeA() { return new C(); }
    }
    
    public class InnerImplementation {
        public static void main(String[] args) throws Exception {
            A a = InnerA.makeA();
            a.f();
            System.out.println(a.getClass().getName());
            // Reflection still gets into the private class:
            HiddenImplementation.callHiddenMethod(a, "g");
            HiddenImplementation.callHiddenMethod(a, "u");
            HiddenImplementation.callHiddenMethod(a, "v");
            HiddenImplementation.callHiddenMethod(a, "w");
        }
    } 
    /* Output:
    public C.f()
    InnerA$C
    public C.g()
    package C.u()
    protected C.v()
    private C.w()
    *///:~

    callHidenMethod方法就是对某个对象中的方法的调用,什么方法都可以,利用反射。

    还有访问和修改private filed的例子:

    class WithPrivateFinalField {
        private int i = 1;
        private final String s = "I’m totally safe";
        private String s2 = "Am I safe?";
        public String toString() {
            return "i = " + i + ", " + s + ", " + s2;
        }
    }
    
    public class ModifyingPrivateFields {
        public static void main(String[] args) throws Exception {
            WithPrivateFinalField pf = new WithPrivateFinalField();
            System.out.println(pf);
            Field f = pf.getClass().getDeclaredField("i");
            f.setAccessible(true);
            System.out.println("f.getInt(pf): " + f.getInt(pf));
    
            f.setInt(pf, 47);
            System.out.println(pf);
            f = pf.getClass().getDeclaredField("s");
            f.setAccessible(true);
            System.out.println("f.get(pf): " + f.get(pf));
    
            f.set(pf, "No, you’re not!");
            System.out.println(pf);
            f = pf.getClass().getDeclaredField("s2");
            f.setAccessible(true);
            System.out.println("f.get(pf): " + f.get(pf));
    
            f.set(pf, "No, you’re not!");
            System.out.println(pf);
        }
    } 
    /* Output:
    i = 1, I’m totally safe, Am I safe?
    f.getInt(pf): 1
    i = 47, I’m totally safe, Am I safe?
    f.get(pf): I’m totally safe
    i = 47, I’m totally safe, Am I safe?
    f.get(pf): Am I safe?
    i = 47, I’m totally safe, No, you’re not!
    *///:~

    用反射可以去访问和修改private的field,实验说明只有final的field改不了。

    所以可以说,反射确实可以无视权限… ,有好有坏咯。
    利大于弊这样。

  • 相关阅读:
    AT4119[ARC096C]Everything on It【斯特林数,容斥】
    AT2164[AGC006C]Rabbit Exercise【差分,倍增,数学期望】
    hdu5909Tree Cutting【FWT】
    JavaWeb apache和tomcat是如何配合工作的
    JavaWeb 目录
    SQL Server CLUSTERED
    SQL Server ISNULL
    JavaWeb Servlet教程
    SQL Server DISTINCT
    SQL Server 哈希索引
  • 原文地址:https://www.cnblogs.com/wangshen31/p/10351311.html
Copyright © 2011-2022 走看看