类型信息RTTI

来源:互联网 发布:700套微信小游戏源码 编辑:程序博客网 时间:2024/05/29 15:11
类型信息主要讲述的是:在运行时类型信息使得你可以在程序运行时发现和使用类型信息
也就是 RTTI(运行时类型识别)
如何在运行时识别对象和类的信息?主要有两种方式:一种是传统的RTTI,它假定我们在编译时已经知道了所有的类型;另一种是反射机制,它允许我们在运行时发现和使用类的信息。
为什么需要RTTI?
答:java中需要在运行时知道对象的具体类型,所以需要用到RTTI
类型信息在运行时如何表示?
由class对象的特殊对象完成,它包含类的相关信息。这里是怎么存呢?①Class.forName("对象名"),这样会返回一个Class对象,②还有一种方法就是:对象.class,具有同样的效果,这种方法叫:类的字面常量。③然而实际java存储类相关信息是使用了一个叫泛化的Class引用(Class<Integer> intclass=int.class) 限定Class引用必须是Integer类型。
类型转换前要先做检查
目前RTTI形式有:传统的类型装换(会抛类型转换异常)、代表对象类型的Class对象(用Class存储对象信息)
RTTI在java中第三中形式是instanceof,告诉我们对象是不是某个特定类型的实例,并返回boolean值
InstanceOf与Class的等价性
查询类型信息时,可以用InstanceOf与Class,那么两者有什么区别?
(x InstanceOf 类A)(x.getClass==类A.class)
InstanceOf比较的是你是不是这个类A或是不是这个类A的派生类,而Class比较则是确切的问你是不是这个类A。

什么是反射?
能够分析类能力的程序(java.lang.reflect)
通过反射我们能对一个类知道哪些内容?如下表所示
类名作用Field代表类的成员变量(成员变量也称为类的属性)。
get() set()方法读取和修改与Field对象关联的字段Method代表类的方法。 用invoke()方法调用与此类关联的方法Constructor代表类的构造方法。
反射用法例子:
public static void main(String[] args) {
  try {
   String name = "com.reflect.Employee";
   Class c = Class.forName(name);
   /**
    * c.getModifiers():返回此类或接口以整数编码的 Java 语言修饰符。
    * Modifier.toString将整数编码转换成字符串 例如 public class Employee{}
    * 返回class往前部分public public final class Employee{} 返回class往前部分public
    * final
    */
   String modifier = Modifier.toString(c.getModifiers());
   /**
    * 返回构造器数组,该对象反映此 Class 对象所表示的类或接口的指定构造方法。
    */
   Constructor[] constructor = c.getDeclaredConstructors();
   for (Constructor c1 : constructor) {
    System.out.println(c1.getName());// 构造器方法名:com.reflect.Employee
    for (int i = 0; i < c1.getParameterTypes().length; i++) {
     System.out.println(c1.getParameterTypes()[i]);// 构造器参数类型
    }
    System.out.println();
   }
   /**
    * 返回方法数组,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
    */
   Method[] method = c.getDeclaredMethods();
   for (Method m1 : method) {
    System.out.println(m1.getReturnType());// 方法返回值类型
    System.out.println(m1.getName());// 方法名
    System.out.println(Modifier.toString(m1.getModifiers()));// 方法修饰符
    // m1.getParameterTypes():返回方法参数数组
    for (int i = 0; i < m1.getParameterTypes().length; i++) {
     System.out.println(m1.getParameterTypes()[i]);
    }
   }
   /**
    * 返回域对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段
    */
   Field[] field = c.getDeclaredFields();
   for (Field f1 : field) {
    System.out.println(f1.getType());// 变量类型
    System.out.println(f1.getName());// 变量名
    System.out.println(Modifier.toString(f1.getModifiers()));// 变量修饰符
   }
  } catch (ClassNotFoundException e) {
   e.printStackTrace();
  }
 }
运行时使用反射分析对象
public static void main(String[] args) throws Exception {
  String str = "hello"; // 实例化一个String类对象
  String s = str; // 用于后面的比较测试
  // 打印字符串和hashCode编码
  System.out.println(str + "::" + str.hashCode());// hello::99162322
  //开始反射动作
  Class<?> cls = String.class;
  Field value = cls.getDeclaredField("value");//获取value这个域
  value.setAccessible(true);//开启运行时动态反射,
  // 反射取得str对象的字符数组
  char[] arr = (char[]) value.get(str);
  // 修改字符数组的内容
  arr[0] = 's';
  // 打印字符串和hashCode编码
  System.out.println(str + "::" + str.hashCode());// sello::99162322
  // 比较两次是否相同
  System.out.println(s == str);// true
 }
反射:运行时的类信息
通过反射能在运行时确定匿名对象的类信息,开头有说过RTTI可以识别对象和类的信息,传统RTTI就是默认运行前已经知道了所有类型,但是假设你获取一个指向某个并不存在你程序空间的对象的引用,或者在网络链接上获得一串字节,并告诉你这个字节是一个类,那怎样才能使用这些类型的类呢?
这时反射就起了作用,Class类和reflect类库一起对反射概念进行了支持,反射可以在运行时获取该类的域、方法、构造器,这样,匿名对象的类信息就能在运行时被确定下来。

RTTI与反射的区别?
RTTI编译时类型必须已知。编译器在编译时打开和检查 .class文件
对于反射,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。

java代理
代理是基本设计模式之一,用来提供额外或不同的操作,
interface Interface{
    void doSomething();
    void somethingElse(String arg);
}
class RealObject implements Interface{ @Override
    public void doSomething() {
        System.out.println("doSomething");
    } @Override
    public void somethingElse(String arg) {
        System.out.println("somethingElse"+arg);
    }
}
class SimpleProxy implements Interface{
    private Interface proxied; public SimpleProxy(Interface proxied) {
        this.proxied = proxied;
    } @Override
    public void doSomething() {
        System.out.println("SimpleProxy doSomething");
        proxied.doSomething();
    } @Override
    public void somethingElse(String arg) {
        System.out.println("SimpleProxy somethingElse"+arg);
        proxied.somethingElse(arg);
    }
}
public class ProxyDemo {
    //一、consumer参数是一个Interface,它不知道具体是RealObject还是SimpleProxy,因为两者都实现Interface
    public static void consumer(Interface iface){
        iface.doSomething();
        iface.somethingElse("aaa");
    } public static void main(String[] args) {
        //二、这里consumer接收的是一个RealObject对象,所以运行RealObject的doSomething和somethingElse方法
        consumer(new RealObject());
        //三、这里consumer接收的是一个SimpleProxy对象,
        // 但是SimpleProxy对象中的方法用到了RealObject对象的的doSomething和somethingElse方法
        consumer(new SimpleProxy(new RealObject()));
    }
}
上面这个例子主旨目的是想要通过SimpleProxy对象去调用RealObject两个实际方法,目的是提供额外的或不同的操作,比如代码中打印的两句话,灵活一点的话,这时我们可以在SimpleProxy对象的方法中做一些额外的操作,这种想法就叫代理。代理通常充当一个中间人的角色,好比我想买个包,但是我不太熟悉包的质量,所以想找个代购帮我买,代购帮我买的时候会帮我查看包的质量,并帮我把包买好。
动态代理
class DynmicProxyHandler implements InvocationHandler{
    
    private Object proxied; public DynmicProxyHandler(Object proxied) {
        this.proxied = proxied;
    } @Override
    /**
     * 动态代理可以将所有调用重定向到调用处理器(下面的invoke方法),
     * 因此通常会向调用处理器的构造器传递一个实际对象的引用(上面的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 o:args)
                System.out.println(" "+o);
       //请求转发,调用proxied对象的method方法
        return method.invoke(proxied,args);
    }
}public class SimpleDynamicProxy {
    public static void consumer(Interface iface){
        iface.doSomething();
        iface.somethingElse(" aaa ");
    }
    public static void main(String[] args) {
        RealObject real=new RealObject();
        consumer(real);
        //创建动态代理。需要一个类加载器、该代理实现的接口、以及InvocationHandler接口的实现
        Interface proxy= (Interface) Proxy.newProxyInstance(
                Interface.class.getClassLoader(),
                new Class[]{Interface.class},
                new DynmicProxyHandler(real));
        consumer(proxy);
    }
}
空对象
当你使用内置对象为null表示缺少对象时,必须在每次引用时都得测试是不是null,不然就有可能报空指针异常,但是有时候空对象思想会很有用,比如我有一个空对象(这里说的空对象不是指A a=null,后面会讲如何实现空对象),他可以接受任何传递给他对象消息,但是这个空对象并不作为,通过这种方式我可以假设所有对象都是有效的,所以就不需要去对他进行判空。
空对象例子:
//创建一个标记接口,所有空对象都要实现这个接口
public interface Null {
}interface Operation {//操作
    String description();//描述
    void command();//指令
}interface Robot{//机器人
    String name();//名字
    String model();//模型
    List<Operation> operations();//描述机器人行为能力
    class Test{
        public static void test(Robot robot){
            //判空
            if (robot instanceof Null)
                System.out.println("【NULL ROBOT】");
            System.out.println("robot name: "+robot.name());
            System.out.println("robot model: "+robot.model());
            for (Operation operation:robot.operations()){
                System.out.println(operation.description());
                operation.command();
            }
        }
    }
}class NullRobotProxyHandler implements InvocationHandler{//定义一个空的机器人的代理
    private String nullName;
    private Robot proxied =new NullRobot();
    //构造器,传入一个继承Robot对象的引用
    public NullRobotProxyHandler(Class<? extends Robot> type) {
        nullName=type.getSimpleName()+" NULL Robot";
    } @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(proxied,args);
    }
    class NullRobot implements Null,Robot{//定义一个空的机器人
        public String name() {
            return nullName;
        }
        public String model() {
            return nullName;
        }
        public List<Operation> operations() {
            return Collections.emptyList();
        }
    }
}class SnowRemovalRobot implements Robot{//定义一个扫雪的机器人
    private String name;
    public SnowRemovalRobot(String name) {this.name = name;}
    public String name() {return name;}
    public String model() {return "扫雪机器人一号";} public List<Operation> operations() {
        return Arrays.asList(new Operation() {
            public String description() {
                return name+" 会扫雪";
            }
            public void command() {
                System.out.println(name+" 正在扫雪");
            }
        },new Operation() {
            public String description() {
                return name+" 会除冰";
            }
            public void command() {
                System.out.println(name+" 正在除冰");
            }
        });
    }
}class Test{
    public static Robot newNullRobot(Class<? extends Robot> type){
        return (Robot) Proxy.newProxyInstance(NullRobotProxyHandler.NullRobot.class.getClassLoader(),
                new Class[]{Null.class,Robot.class},
                new NullRobotProxyHandler(type));
    } public static void main(String[] args) {
        Robot robot1=new SnowRemovalRobot("扫雪号");
        Robot.Test.test(robot1);
        Robot robot2=newNullRobot(SnowRemovalRobot.class);
        Robot.Test.test(robot2);
    }
}输出:                                                                                                                                                    robot name: 扫雪号
robot model: 扫雪机器人一号
扫雪号 会扫雪
扫雪号 正在扫雪
扫雪号 会除冰
扫雪号 正在除冰
【NULL ROBOT】
robot name: SnowRemovalRobot NULL Robot
robot model: SnowRemovalRobot NULL Robot
总结:
RTTI允许通过匿名基类的引用来发现类型信息,但不可以滥用RTTI,面向对象的目的是让我们凡是可以使用的地方都是用多态,但要在必需的时候使用RTTI。











0 0
原创粉丝点击