类型信息(运行时发现和使用类型信息)


首先理解RTTI,假设Circle、Square、Triangle继承于Shape,当我们用一个List<Shape> list = Array.asList(new Circle(),new Square(),new Triangle()),这个时候Circle、Square、Triangle三个会自动向上转型Shape,这个时候我们还在编写代码,List容器只是持有Shape的对象(Circle、Square、Triangle),还没用启动程序,所以这个时候只是相当于容器还是把(Circle、Square、Triangle)这三个当成是Object来持有,只是由容器和泛型系统来检查才会给我们写代码的时候报编译错误,如果你传入的不是Shape类型,这是检查。上面说了Circle、Square、Triangle对象当成Object持有,真正到当你需要把List<Shape> list这个容器中的对象抽出来调用的时候,RTTI才会检查并把Object转换成Circle、Square、Triangle里面具体的类型信息(如成员啊,方法啊),才能够调用各自的方法实现多态。这才是RTTI的作用所在!(书里面术语太多差点没把我搞懵B)

目录

 

Class对象

泛化Class引用

类型转换前先做检查

注册工厂

instanceof与Class的等价性

反射:运行时的类信息

动态代理

空对象(先放一边)

接口与类型信息

 


Class对象

当编译过后,一个类对应一个class对象(被保存在一个同名的.class文件之中),而这个通过JVM的“类加载器”的子系统加载。而当某个类的class对象被载入内存中,就可以创建这个类的所有对象。

class Candy {
  static { print("Loading Candy"); }
}

class Gum {
  static { print("Loading Gum"); }
}

class Cookie {
  static { print("Loading Cookie"); }
}

public class SweetShop {
  public static void main(String[] args) {	
    print("inside main");
    new Candy();
    print("After creating Candy");
    try {
      Class.forName("Gum");
    } catch(ClassNotFoundException e) {
      print("Couldn't find Gum");
    }
    print("After Class.forName(\"Gum\")");
    new Cookie();
    print("After creating Cookie");
  }
} /* Output:
inside main
Loading Candy
After creating Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After creating Cookie
*///:~

“当程序创建第一个对类的静态成员的引用时,就会加载这个类”,所以这里我们可以看到使用new之后调用默认构造器,这也就是说明了构造器也是类的静态方法只是没有显式的写出来。并且这里面还有一个比较特殊的Class.forName就可以调用到Gum这个类中的静态成员。刚开始我看的时候对这个Class.forName是有一点不理解的,这个有什么用呢。因为Class.forName是不返回对象的,这个主要作用是加载类里面的静态成员。如果你使用过数据库连接,可能会对Class.forName有印象。比如我们Class.forName("com.mysql.jdbc.Driver");  主要是为了加载Driver这个类里面的静态方法。

package com.mysql.jdbc   
  
public class Driver extends NonRegisteringDriver implements java.sql.Driver {   
// ~ Static fields/initializers   
// --------------------------------------------- //   
// Register ourselves with the DriverManager   
//   
static {   
     t ry {   
               java.sql.DriverManager.registerDriver(new Driver());   
           } catch (SQLException E) {   
              throw new RuntimeException("Can't register driver!");   
           }   
   }   
// ~ Constructors   
// -----------------------------------------------------------   
/**
   * Construct a new driver and register it with DriverManager
   *
   * @throws SQLException
   *              if a database error occurs.
   */  
public Driver() throws SQLException {   
     // Required for Class.forName().newInstance()   
}   
}  

里面的static代码块。而我们并不需要返回这个类的印象,因为我们只需要加载这个代码块就可以了,这个时候就可以使用Class.forName()。所以这里new对象无非就是获取这个类的class对象,就可以通过RTTI加载这个类从而常见里面的所有对象!

类字面常量(.class)

上面是类使用前的步骤,通过类名.class可以获取该类的Class对象,与Class.forName不同的是,创建Class对象引用的时候,并不会初始化。这样可以实现惰性,同时如果static final 修饰的常量,也是可以直接调用而不需要对类初始化就可以调用,但只限于常量。

泛化Class引用

照理来说是可以的,因为Interger继承Number,但是这里Interger Class不是Number Class的子类,但是如果<Number>换成<? extends Number>,就是在编译时检查类型,就可以防止在运行时才报错还要折返回来修改。同时当使用泛型的Class引用的时候像下面这样。

 Class<? super FancyToy> up = ftClass.getSuperclass();
  Object obj = up.newInstance();

因为这个通配符的含糊性,所以在使用newInstance返回的稚嫩是Object,这个时候就需要进行强转。

新的转型语法(cast)

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.
    }
} 

类型转换前先做检查

按照上面的,目前我们知道的RTTI形式:

1.交给默认RTTI,但是这样出问题会在运行时报错。

2.使用Class对象(一种是Class.forname,一种是类字面量),同时Class对象来预防运行时才报错。

下面介绍第三种,关键字instanceof,下图可以通过instanceof判断x是不是Dog的实例,也就是说x是不是跟Dog类一个类型。

通过判断才选择进入,这样做也是可以避免运行时报错。这个可以用作基类各实例的次数。具体可以看书,这里就不写出来了。

注册工厂

这里很绕,其实也就是把工厂模式改成注册工厂,具体意思看下面代码

class Part {
  public String toString() {
    return getClass().getSimpleName();
  }
  static List<Factory<? extends Part>> partFactories =
    new ArrayList<Factory<? extends Part>>();	
  static {
    // Collections.addAll() gives an "unchecked generic
    // array creation ... for varargs parameter" warning.
    partFactories.add(new FuelFilter.Factory());
    partFactories.add(new AirFilter.Factory());
    partFactories.add(new CabinAirFilter.Factory());
    partFactories.add(new OilFilter.Factory());
    partFactories.add(new FanBelt.Factory());
    partFactories.add(new PowerSteeringBelt.Factory());
    partFactories.add(new GeneratorBelt.Factory());
  }
  private static Random rand = new Random(47);
  public static Part createRandom() {
    int n = rand.nextInt(partFactories.size());
    return partFactories.get(n).create();
}
}	

其实也就是在基类里做好一个工厂模式注册在基类里。

instanceof与Class的等价性

instanceof其实就是判断比较一个类与另一个类是不是同一种类型,而使用Class对象比较使用equals与==就是比较两者只能是同一个类。

反射:运行时的类信息

首先,既然RTTI与反射是获取两种类型信息的方式,那么他们两个区别只在于,RTTI是需要在编译期知道所有的类型(需要所有的类),而假设有某些类需要通过网络或者另一台机器来进行获取,RTTI就无法在运行时去获取,因为在编译器就必须知道所有类型(尽管这在我们平常的编程中不常见,但是要知道)。反射就是可以在运行时去检查类型信息,这就是区别。

动态代理

在多态那边说过了,代理其实也就是一个中间人的作用,假设你并不希望别人对类或者接口的所有方法可见,只想把一个方法过滤出来,通过一个代理的形式去展现给别人看,这时候代理就可以为我们提供帮助。

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
*///:~

上面是书中的一个代理的例子,我这边直接做总结,通过代理,可以在调用方法的时候做一些操作。上面的例子中, consumer(new SimpleProxy(new RealObject()));在main中consumer通过SimpleProxy代理类传入RealObject对象调用doSomething和doSomethingElse方法,在doSomethingElse中多做了一步输出一段话的操作。这就是代理可以作为中间人进行的操作。然而,我们看到了上面的代理方法是已经写好了,调用那些方法都已经在代码中写好了。

而动态代理,比代理更进一步就是可以动态的去调用接口的方法,并传入相应参数。

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
*///:~

直接看输出结果,可以动态的去获取这些方法已经方法参数并进行代理操作。动态就是动态在一个自动获取类型信息。

空对象(先放一边)

其实这个我感觉就是一个特殊的对象,里面的信息是为了防止出现null而获取一个空对象,当对象为NULL时使用一个表示为空的对象信息去存放他,而书中还使用上了动态代理去动态获取,因为你每一个类型的null都是需要一个空对象去代替,所以每个类都需要去编写一个null信息,这就会显得代码上多余,而这个空对象往往我们在真正的编程上是不常用的,而花费时间去编写,感觉没有必要。而书中的例子其实就是一种思想的转变,把动态代理运用到这个空对象的思想上。这里就不做深究。

接口与类型信息

public interface A {
  void f();
}
class B implements A {
  public void f() {}
  public void g() {}
}

public class InterfaceViolation {
  public static void main(String[] args) {
    A a = new B();
    a.f();
    // a.g(); // Compile error
    System.out.println(a.getClass().getName());
    if(a instanceof B) {
      B b = (B)a;
      b.g();
    }
  }
} 
/* Output:
B
*///:~

从上面看,结果转换,到最后B可以去实现A的方法,但是这样会使得B过于依赖于A,这个时候最简单的就是使用访问权限,这样客户端程序员就没办法看到你的方法。但是使用反射的方式,即使是使用访问权限,或者是只发布编译之后的JAR(这个可以使用JAVAP -private 方法名就可以获取该方法的内部的信息),又或者是内部类,都是可以通过反射获取到类型信息。但是这样做的话,就意味着,如果去修改别人的设置的访问权限,你就需要去承担这样对你的程序产生的后果。这也给程序一个后路,可以去修改这个他。这就是反射的好处。

总结

多态是面向对象的特性,而我们在能使用多态时尽可能去使用它,但是这样对于基类的定义会影响到其导出类。使用RTTI,可以的好处,就拿书中的例子,假设乐器这个基类,而管乐器需要加上一个清理口水的方法,但是如果直接在基类上添加,那么他的其他导出类比如打击乐器、弦乐器、电子乐器都需要去重写这个方法,显得多余而没用。这个时候我们可以把这个清理口水的方法放在管乐器这个类中,通过instanceof的方式调用RTTI方式,再进行调用这个类的方法是可以的。

“反射允许更加动态的编程风格”,这个可以有更多的想法。这里后续看书再返回来补充。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值