第十四章 类型信息

运行时类型信息使得你可以在程序运行时发现和使用类型信息。

1、运行时发现和使用类型信息

  • 传统RTTI(运行时识别一个对象的类型)
  • 反射
  • instanceof关键字

1.1 为什么需要RTTI

基本目的:让代码只操纵对基类的引用;

//: typeinfo/Shapes.java
import java.util.*;

abstract class Shape {
  void draw() { System.out.println(this + ".draw()"); }
  abstract public String toString();
}

class Circle extends Shape {
  public String toString() { return "Circle"; }
}

class Square extends Shape {
  public String toString() { return "Square"; }
}

class Triangle extends Shape {
  public String toString() { return "Triangle"; }
}	

public class Shapes {
  public static void main(String[] args) {
    List<Shape> shapeList = Arrays.asList(
      new Circle(), new Square(), new Triangle()
    );
    for(Shape shape : shapeList)
      shape.draw();
  }
} /* Output:
Circle.draw()
Square.draw()
Triangle.draw()
*///:~

当放入list中时,所有形状都被转型成为shape,而当从list取出元素时,这种容器——实际都被当做object持有,然后自动转换成shape;

2、Class对象

  • Class对象就是用来创建所有“常规”对象的;
  • class还拥有大量使用RTTI的其他方式;
  • 每个类都有Class对象,即编译了一个新类就会产生一个该类的Class对象,并且保存在.class文件中。Class对象就是用来产生“常规”对象的;
  • 只要创建了对一个类的静态成员的引用就会加载该类。new 的时候加载类说明 类的构造器虽然没写static但也是静态方法;
  •   一个类的Class对象载入内存后,他就会用来创建该类的对象;
//: typeinfo/SweetShop.java
// Examination of the way the class loader works.
import static net.mindview.util.Print.*;

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

2.1 获得Class对象

  • Class类的静态方法forName()可以通过传入一个全限定类名(包含包名)返回一个该类的Class类对象引用,此时该类会被加载到内存:Class.forName("thinking14class.Test");
  • 运行时要获得一个类的信息(类型信息)或引用可以通过一个该类的Class对象获得,使用Class.forName()就可以做到,而不必持有该类型的对象通过该对象获得。
  • 如果有了一个实例对象可以调用getClass()方法来获得Class对象。

2.2 Class类的一些方法

  • getName() 获得全限定类名, getCanonicalName()也是获得全限定类名。对于普通类来说,二者没什么区别,只是对于特殊的类型上有点表示差异。
  • getSimpleName()只获得类名,没有包名。
  • isInterface()判断是否为接口。
  • getInterfaces() 返回一个Class对象数组,数组元素是该类实现的接口,元素顺序和实现顺序一致。
  • getSuperclass() 返回直接基类(不是接口)的Class对象,可以用来发现对象完整的类继续结构。
  • newInstance()创建该类实例对象并返回,但该类必须要有默认构造器,这个方法相当于一个虚拟构造器。

2.3.类字面常量

java中还提供了另一种方法来生成class对象的引用,即类字面常量;

                                                     …等价于…

boolean.class

Boolean.TYPE

char.class

Character.TYPE

byte.class

Byte.TYPE

short.class

Short.TYPE

int.class

Integer.TYPE

long.class

Long.TYPE

float.class

Float.TYPE

double.class

Double.TYPE

void.class

Void.TYPE

.class来创建Class对象时,不会自动初始化该Class对象,实际包含三个动作:

  1. 加载
  2. 链接
  3. 初始化,如果具有超类,则对其初始化,执行静态初始化器和静态初始化块;
//: typeinfo/ClassInitialization.java
import java.util.*;

class Initable {
  static final int staticFinal = 47;
  static final int staticFinal2 =
    ClassInitialization.rand.nextInt(1000);
  static {
    System.out.println("Initializing Initable");
  }
}

class Initable2 {
  static int staticNonFinal = 147;
  static {
    System.out.println("Initializing Initable2");
  }
}

class Initable3 {
  static int staticNonFinal = 74;
  static {
    System.out.println("Initializing Initable3");
  }
}

public class ClassInitialization {
  public static Random rand = new Random(47);
  public static void main(String[] args) throws Exception {
    Class initable = Initable.class;
    System.out.println("After creating Initable ref");
    // Does not trigger initialization:
    System.out.println(Initable.staticFinal);
    // Does trigger initialization:
    System.out.println(Initable.staticFinal2);
    // Does trigger initialization:
    System.out.println(Initable2.staticNonFinal);
    Class initable3 = Class.forName("Initable3");
    System.out.println("After creating Initable3 ref");
    System.out.println(Initable3.staticNonFinal);
  }
} /* Output:
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74
*///:~

从对initable引用的创建中可以看到,仅使用.class语法来获取对类的引用不会引发初始化。

2.4 泛化的Class引用

//: typeinfo/GenericClassReferences.java

public class GenericClassReferences {
  public static void main(String[] args) {
    Class intClass = int.class;
    Class<Integer> genericIntClass = int.class;
    genericIntClass = Integer.class; // Same thing
    intClass = double.class;
    // genericIntClass = double.class; // Illegal
  }
} ///:~

如果要放松一些限制,则使用通配符:

//: typeinfo/WildcardClassReferences.java

public class WildcardClassReferences {
  public static void main(String[] args) {
    Class<?> intClass = int.class;
    intClass = double.class;
  }
} ///:~

为了创建一个class引用,被限定为某种类型,与extends结合,创建一个范围:

//: typeinfo/BoundedClassReferences.java

public class BoundedClassReferences {
  public static void main(String[] args) {
    Class<? extends Number> bounded = int.class;
    bounded = double.class;
    bounded = Number.class;
    // Or anything else derived from Number.
  }
} ///:~

向class引用添加泛型语法的原因仅仅是为了提供编译期类型检查,Class<?> 优于Class 因为Class在编译期不会产生警告,而Class<?>当指向一个非具体的类引用时会产生警告。

2.4.1 newInstance()返回类型

  • a.newInstance()如果Class引用a不是泛型引用,在编译期就不知道它会返回什么类型那么只能返回Object。
 Class a = A.class;
        Object t = a.newInstance();
        //A t =  (A) a.newInstance();//无法确定类型需要强制转换
        System.out.println(t.getClass().getName());//thinking14class.A
  • a.newInstance()  a 是泛型引用并且能确定类型则会返回确切的类型。
Class <A> a= A.class; 
        Class <? extends C> c= C.class; //上界
        Class <? super A> d= A.class; 
        A ta =  a.newInstance(); // 可以确定
        A tc =  c.newInstance(); // 上界至少它是一个C可以确定
  •  a.newInstance()  a 是泛型引用但不能确定类型则只能返回Object。
Class <A> a= A.class; 
        Class <? extends C> c= C.class; //上界
        Class <? super A> d= A.class; //下界
        //A ta =  a.newInstance(); // 通配符无法确定
        
        A tc =  c.newInstance(); // 上界至少它是一个C可以确定
        
        //A td =  d.newInstance();//  下界无法确定

2.5 新的转型语法

//: 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);
//把父类类型的对象存放到子类类型对象里,且b之前必须是该子类所转型成的父类
    h = (House)b; // ... or just do this.
  }
} ///:~

3、类型转换前先做检查

3.1 RTTI的形式:

  1. 传统的类型转换
  2. 代表对象的类型的Class对象
  3. 关键字instanceof:检查对象是否从属于该类型
  4. instanceof 不能比较Class对象,对于Class对象使用isAssignableFrom()判断
    if (as.isAssignableFrom(cs))// Class对象cs所在类是不是属于Class对象as所在类或者派生类 
  5. 动态的instanceof :Class对象的isInstance(Object o)方法判断该Class对象是不是o类的(如果o是class对象所在类则返回true,否则返回false哪怕o是所在类的父类)。                                                                                                        if (cs.isInstance(c)) //如果c是class对象所在类则返回true,否则返回false,哪怕c是所在类的父类

3.2 Instanceof与class的等价性

//: typeinfo/FamilyVsExactType.java
// The difference between instanceof and class
package typeinfo;
import static net.mindview.util.Print.*;

class Base {}
class Derived extends Base {}	

public class FamilyVsExactType {
  static void test(Object x) {
    print("Testing x of type " + x.getClass());
    print("x instanceof Base " + (x instanceof Base));
    print("x instanceof Derived "+ (x instanceof Derived));
    print("Base.isInstance(x) "+ Base.class.isInstance(x));
    print("Derived.isInstance(x) " +
      Derived.class.isInstance(x));
    print("x.getClass() == Base.class " +
      (x.getClass() == Base.class));
    print("x.getClass() == Derived.class " +
      (x.getClass() == Derived.class));
    print("x.getClass().equals(Base.class)) "+
      (x.getClass().equals(Base.class)));
    print("x.getClass().equals(Derived.class)) " +
      (x.getClass().equals(Derived.class)));
  }
  public static void main(String[] args) {
    test(new Base());
    test(new Derived());
  }	
} /* Output:
Testing x of type class typeinfo.Base
x instanceof Base true
x instanceof Derived false
Base.isInstance(x) true
Derived.isInstance(x) false
x.getClass() == Base.class true
x.getClass() == Derived.class false
x.getClass().equals(Base.class)) true
x.getClass().equals(Derived.class)) false
Testing x of type class typeinfo.Derived
x instanceof Base true
x instanceof Derived true
Base.isInstance(x) true
Derived.isInstance(x) true
x.getClass() == Base.class false
x.getClass() == Derived.class true
x.getClass().equals(Base.class)) false
x.getClass().equals(Derived.class)) true
*///:~

4、反射

反射机制:用来检查可用方法,并返回方法名。

Class类和java.lang.reflect类库对反射提供了支持 点击查看

  • reflect包中有Field类,Method类,Constructor类,这些类对象由jvm在运行时创建,用来表示未知类里的字段,方法,构造器。
  • 使用Constructor创建新对象,Filed的get() set()方法修改Filed对象关联的字段,Class类的invoke()调用Method关联的方法。
  • 调用Class类的getFileds()返回表示字段的Filed数组,getMethods()返回表示方法的Method数组,getConstructor()返回表示构造器的Constructor数组。
  • 通过以上方法一个匿名对象的类信息便可在运行时被确定下来,再在运行时通过.class文件获得相关信息构造该类。
  • 没有任何方法可以阻止反射调用那些非公共访问权限的方法,哪怕是private方法或者private域。

5、动态代理

代理是一本基本的设计模式;代理通常充当着中间人的角色,静态代理:

interface Interface {
    void doSomething();
    void somethingElse(String arg);
}

class RealObject implements Interface {
    public void doSomething() {
        System.out.println("doSomething");
    }
    public void somethingElse(String arg) {
    System.out.println("somethingElse " + arg);
    }
}

class SimpleProxy implements Interface {
    private Interface profixed;
    public SimpleProxy(Interface profixed) {
    this.profixed = profixed;
    }
    public void doSomething() {
    System.out.println("SimpleProxy doSomething");
    profixed.doSomething();
    }
    public void somethingElse(String arg) {
    System.out.println("SimpleProxy somethingElse " + arg);
    profixed.somethingElse(arg);
    }
}

public class SimpleProxyDemo {
    public static void consumer(Interface iface) {
    iface.doSomething();
    iface.somethingElse("bonobo");
    }
    public static void main(String[] args) {
    consumer(new RealObject());//直接操作RealObject对象
        //通过SimpleProxy代理类对象间接操作RealObject对象
    consumer(new SimpleProxy(new RealObject()));
    }
}

在动态代理上,所有的调用都会被重新定向到单一的调用处理器上,他的工作是解释调用的类型并确定相应的对策;

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

这里的invoke方法把请求转发给被代理的对象,args是被代理对象所接受的参数;

6、空对象

他可以接受传递给它的所有代表对象的消息,但是将返回表示为实际上并不存在任何真实对象的值;

空对象的一种变体称为空迭代器模式,逻辑变体是模拟对象和桩

6.1 为什么要用空对象呢?

有时候我们的代码中为避免 NullPointerException 会出现很多的对Null的判断语句,而这些语句一旦多起来,我们的代码就会变的惨不忍睹,因此我们引入了空对象模式(null object pattern)以此来使我们的代码变的更优雅一点。

下面来看一下空对象模式的大概视图

为了方便我这里就直接用手写的了,大家谅解一下:) ,凑合着看吧

假如我们需要查询某个学生的信息,我们输入学号来进行查询,如果没有这个学生的话,我们就返回一个空对象,以此来实现空对象模式。

下面是我的代码实现。

import java.util.Scanner;



//首先我定义一个为查询和空对象的类定义一个共同的接口
interface AbstractCustomer{
    String query();
}

//查询学号的具体实现
class RealObject implements AbstractCustomer{
    @Override
    public String query() {
        //这里你也可以返回一些别的有用的东西,我这里为了演示就返回一个字符串
        return "successful";
    }
}

//空对象类的实现
class NullObject implements AbstractCustomer{
    @Override
    public String query() {
        return "can not find this student";
    }
}

//建造工厂
class CustomerFactory{
    //我们假定这是我们储存学生学号的数据库,现在我们获取到了,把它装到我们的数组里
    int[] arr = {1,2,3,4,5};
    
    public AbstractCustomer query(){
        //模拟查询学号
        Scanner scanner = new Scanner(System.in);
        System.out.println("please input student_id:\n");
        int index = scanner.nextInt();
        for(int i : arr){
            if(i == index){
                return new RealObject();
            }
        }
        //如果没有查找到的话我们直接返回一个空对象
        return new NullObject();
    }
}

//接下来是客户端方面,我们直接操作工厂即可
public class Main {
    static void customer(CustomerFactory cf){
        //先调用工厂的query返回一个对象,然后再用这个对象调用自己的query
        String s = cf.query().query();
        System.out.println(s);
    }
    public static void main(String[] args){
        customer(new CustomerFactory());
    }
}

上面则是描述的空对象模式,当我们需要较多的判断是否为null时,可以用到这个模式,以上代码还可以给接口再添加一个isNull方法来确切的判断是否为Null。

我们来执行一下这个程序

接下来我们输入一个数组里没有的数据

 7、接口与类型信息

interface关键字一种重要目标就是允许程序员隔离构件,进而降低耦合性;

Public interface A {
    void f();
}

//: typeinfo/InterfaceViolation.java
// Sneaking around an interface.
import typeinfo.interfacea.*;

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

通过RTTI发现,a是被当作B来实现的,解决方案1:直接声明,使用包访问权限;

//: typeinfo/packageaccess/HiddenC.java
package typeinfo.packageaccess;
import typeinfo.interfacea.*;
import static net.mindview.util.Print.*;

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 class HiddenC {
  public static A makeA() { return new C(); }
} ///:~

但如果试图向下转型为C,则被禁止,方案二:反射机制;

//: typeinfo/HiddenImplementation.java
// Sneaking around package access.
import typeinfo.interfacea.*;
import typeinfo.packageaccess.*;
import java.lang.reflect.*;

public class HiddenImplementation {
  public static void main(String[] args) throws Exception {
    A a = HiddenC.makeA();
    a.f();
    System.out.println(a.getClass().getName());
    // Compile error: cannot find symbol 'C':
    /* if(a instanceof C) {
      C c = (C)a;
      c.g();
    } */
    // Oops! Reflection still allows us to call g():
    callHiddenMethod(a, "g");
    // And even methods that are less accessible!
    callHiddenMethod(a, "u");
    callHiddenMethod(a, "v");
    callHiddenMethod(a, "w");
  }
  static void callHiddenMethod(Object a, String methodName)
  throws Exception {
    Method g = a.getClass().getDeclaredMethod(methodName);
    g.setAccessible(true);
    g.invoke(a);
  }
} /* Output:
public C.f()
typeinfo.packageaccess.C
public C.g()
package C.u()
protected C.v()
private C.w()
*///:~

当接口实现为私有类时情况如何?

//: typeinfo/InnerImplementation.java
// Private inner classes can't hide from reflection.
import typeinfo.interfacea.*;
import static net.mindview.util.Print.*;

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

这里反射依旧没有隐藏任何东西,如果是匿部类会如何?

//: typeinfo/AnonymousImplementation.java
// Anonymous inner classes can't hide from reflection.
import typeinfo.interfacea.*;
import static net.mindview.util.Print.*;

class AnonymousA {
  public static A makeA() {
    return new 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 class AnonymousImplementation {
  public static void main(String[] args) throws Exception {
    A a = AnonymousA.makeA();
    a.f();
    System.out.println(a.getClass().getName());
    // Reflection still gets into the anonymous class:
    HiddenImplementation.callHiddenMethod(a, "g");
    HiddenImplementation.callHiddenMethod(a, "u");
    HiddenImplementation.callHiddenMethod(a, "v");
    HiddenImplementation.callHiddenMethod(a, "w");
  }
} /* Output:
public C.f()
AnonymousA$1
public C.g()
package C.u()
protected C.v()
private C.w()
*///:~

对域来说又会如何,即便域是私有的:

//: typeinfo/ModifyingPrivateFields.java
import java.lang.reflect.*;

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

final域实际上在遭遇修改时是安全的,运行时在不抛出异常的情况下接受任何修改尝试,但实际不会发生任何修改;

 8、总结

面向对象编程语言的目的是让我们在凡是可以使用的地方都使用多态机制,只在必须的时候使用RTTI,动态代码将是java与其他诸如C++这样的语言区分开的重要工具之一。

参考博客: https://www.cnblogs.com/haodawang/p/5967181.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值