Java 类型信息知识点

来源:互联网 发布:数据恢复哪个软件好用 编辑:程序博客网 时间:2024/05/22 14:03

1、运行时类型信息使得你可以在程序运行时发现和使用类型信息,主要有两种方式:“传统的”RTTI(它假定我们在编译时已经知道了所有的类型)和“反射”机制,它允许我们在运行时发现和使用类的信息。

2、在Java中所有的类型转换操作都是在运行时进行正确性检查的,这也是RTTI名字的含义:在运行时,识别一个对象的类型。示例:

List<Shape> list = Array.asList(new Circle(),new Rectangle(),new Square());

在这个例子中,RTTI类型转换并不彻底:Object被转型为Shape,而不是转型为Circle、Square、 Rectangle。这是因为目前我们只知道这个List<Shape>保存的是Shape。在编译时和Java的泛型系统来强制确保了这一点;而在运行时,由类型转换操作来确保这一点。

3、类型信息在运行时是有Class对象完成的,Java使用Class对象来执行其RTTI,Class类还拥有大量的使用RTTI的其他方式。所有的类都是在对其第一次使用时,动态加载到JVM中。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,即使在构造器之前并没有使用static关键字。因此,使用new操作符创建类的新对象也会被当作对类的静态成员的引用。因此,Java程序在它开始运行之前并非是被完全加载,其各个部分是在必须时才加载的。一旦某个类的Class对象被载入到内存中,它就被用来创建这个类的所有对象。示例:

class Candy{

static{System.out.println("loading Candy");}

}

class Gum{

static{System.out.println("loading Gum");}

}

class Cookie{

static{System.out.println("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){

println("Could't find Gum");

}

print("After Class.forName(\"Gum\")");

new Cookie();

print("After creating Cookie");

}

}

-->inside main、loading Candy、After creating Candy、loading Gum、After Class.forName("Gum")、loading Cookie、After creating Cookie

这里的每个类Candy、Gum、Cookie,都有一个static子句,该子句在类的第一次被加载时执行。获得Class对象的引用,Class.forName是一个便捷的途径,除此之外,如果你已经拥有了一个对象,可以通过getClass方法获得Class引用。Class有很多方法,newInstance就是其中一个方法,只不过该方法要求类必须具有默认的构造器。

4、类字面常量

Java还提供了另外一种获取Class对象引用的方法:类名.class;这样做更简单、安全,因为它在编译时就会受到检查,并且它根除了对forName方法的调用,所以也更高效。类字面量不仅可以应用于普通类,还可以应用于接口、数组以及基本数据类型。

有一点需要注意:使用“类名.class”来创建对Class对象的引用时,不会自动初始化该Class对象。为了使用类而作的准备工作实际包含三个步骤:加载(类加载器执行)、链接(该阶段将验证类中的字节码,为静态域分配存储空间,并且如果必要的话,将解析这个类创建的对其他类的所有引用)、初始化(如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块)。

初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行引用时才执行。

class Initable{

static final int staticFinal = 47;

static final int staticFinal2 = ClassInitialization.rand.next(1000);

static{

System.out.println("Initialzie 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");

//没有触发初始化

System.out,println(Initable.staticFinal);

//触发了初始化

System.out,println(Initable.staticFina2l);

//触发了初始化

System.out.println(Initable2.staticNonFinal);

Class initable3 = Class.forName("Initable3");

System.out.println("After creating Initable3 ref");

System.out.println(Initable3.staticNonFinal);

}

}

-->After creating Initable ref、47、Initialzie Initable、258、Initialzie Initable2、147、Initialzie Initable3、After creating Initable3 ref、74

如果一个static final值是“编译期常量”,就像Initable.staticFinal那样,那么这个值不需要对Initable类进行初始化就可以被读取到。但是,如果只是将一个域设置为static和final的,还不足以确保这种行为,例如Initable.staticFinal2,因为它不是一个编译期常量。

如果一个域是static不是final的,那么在对它的访问时,总会要求在它被读取之前,要先进行链接和初始化,就像Initable2.staicNonFinal的访问中看到的那样。

5、RTTI在Java中还有第三种形式,就是关键字instanceof。在调用newInstance()时,可能会得到两种异常 -- IllegalAccessException(表示违反了Java安全机制)、InstantiationException。

6、Instanceof与Class的等价性

instanceof保持了类型的概念,它指的是“你是这个类吗,或者你是这个类的派生类吗?”而如果使用==比较实际的Class对象,就没有考虑继承 -- 它或者是这个确切的类型,或者不是。

7、动态代理

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

interface Interface{

void doSomething();

void somethingElse(String arg);

}

class RealObject implements Interface{

public void doSomething(){

......}

public void somethingElse(String arg){

......}

}

class SimpleProxy implements Interface{

private Interface proxied;

public SimpleProxy(Interface proxied){

this.proxied = proxied;

}

public void doSomething(){

......//一些操作

proxied.doSomething();

}

public void somethingElse(String arg){

......//一些操作

proxied.somethingElse(arg);

}

}

Java的动态代理比代理的思想更进一步,因为它可以动态的创建代理并动态地处理对所代理方法的调用。在动态代理上所做的所有的调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。实现方式是实现 InvocationHandler即可;

public interface Subject   {     public void doSomething();   }   public class RealSubject implements Subject   {     public void doSomething()     {       System.out.println( "call doSomething()" );     }   }   public class ProxyHandler implements InvocationHandler   {     private Object proxied;          public ProxyHandler( Object proxied )     {       this.proxied = proxied;     }          public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable     {       //在转调具体目标对象之前,可以执行一些功能处理    //转调具体目标对象的方法    return method.invoke( proxied, args);          //在转调具体目标对象之后,可以执行一些功能处理  }    } 
调用:
RealSubject real = new RealSubject();   Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[]{Subject.class}, new ProxyHandler(real));proxySubject.doSomething();
通常调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要得到一个类的加载器(通常可以从已经被加载的对象中获取其类加载器),一个你希望该代理实现的接口列表(不是类或抽象类),以及InvocationHandler接口的一个实现。动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造器传递一个“实际”对象的引用,从而使得调用处理器在执行其中的任务时,可以将请求转发。

invoke()方法中传递进来了代理对象,以防你需要区分请求的来源,但在很多情况下,你并不关心这一点。然而,在invoke()内部,在代理上调用方法时需要格外当心,因为对接口的调用将被重定向到对代理的调用。

通常,你会执行被代理的操作,然后使用Method.invoke()将请求转发给代理对象,并传入必须的参数。

原创粉丝点击