JAVA编程思想第四版笔记 十四、 类型信息

十四、 类型信息

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

如何让我们在运行时识别对象和类的信息的

1.RTTI,假定我们在编译时已经知道所有的类型

2.“反射”机制,允许我们在运行时发现和使用类的信息

14.1 为什么需要RTTI

RTTI运行时类型判定

可以用来查询某个父类引用所指向的对象的确切类型,然后选择或者剔除特例。

类型转换。

14.2 Class对象

java使用Class对象来执行其RTTI。每个类都有一个Class对象,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当的说,是被保存在一个同名的.class文件中)。为生成这个类的对象,运行这个程序的jvm将使用被称为“类加载器”的子系统。

所有的类都是在对其第一次使用,动态加载到jvm中。

java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载的。

类加载器的工作流程

1.类加载器首先检查这个类的Class对象是否已加载。若未加载,默认的类加载器就会根据类名查找.class文件。

2.在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良java代码。

3.一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。

14.2.1 类字面常量

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

类字面常量不仅可以应用于普通的类,也可应用于接口,数组以及基本数据类型。对于基本数据类型的包装器类,还有标准字段TYPE,TYPE字段是个引用,指向对应的基本数据类型的Class对象

boolean.class 等价于 Boolean.TYPE

当使用“.class”来创建对Class对象的引用不会自动初始化该Class对象

使用类的过程

1.加载。类加载器执行,将查找字节码(通常在classpath所指定的路径中查找,但这并非是必需的),并从这些字节码中创建一个Class对象。

2.链接。验证类中的字节码,为静态域分配存储空间,若必需的话,将解析这个类创建的对其他类的所有引用。

3.初始化。若该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。初始化延迟到对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行。

14.2.2 泛化的Class引用

通配符“?”,表示任何事物

Class<?> intClass = int.class;
intClass = double.class;

为创建一个Class引用,它被限定为某种类型,或该类型的任何子类型,你需要将通配符与extends关键字相结合,创建一个范围。super则表示是其自己或其超类;向Class引用添加泛型语法仅仅是为了提供编译器类型检查

 Class<? extends Number> bounded = int.class;
 bounded = double.class;
 bounded = Number.class;

newInstance():弱类型,效率低,只能调用无参构造

new():强类型,高效率,能调用任何public构造器

public class GenericToyTest {
  public static void main(String[] args) throws Exception {
    Class<FancyToy> ftClass = FancyToy.class;
    // Produces exact type:
    FancyToy fancyToy = ftClass.newInstance();
    Class<? super FancyToy> up = ftClass.getSuperclass();
    // This won't compile:
     //Class<Toy> up2 = ftClass.getSuperclass();//报错
    // Only produces Object:
    Object obj = up.newInstance();
  }
} ///:~

14.2.3 新的转型语法

Class引用的转型语法,即cast()方法;cast()方法接受参数对象,并将其转型为Class引用的类型。

Class Building{}
Class House extends Building{}
main{
    Building b = new Building();
    Class<House> houseType = House.class;
	House h = houseType.cast(b);
	h = (House)b;
}

14.3 类型转换前先做检查

RTTI形式

1.传统类型转换,就抛出ClassCastException异常

2.代表对象的类型的Class对象。通过查询Class对象可获取运行时所需的信息

3.关键字instanceof。返回一个布尔值,告诉我们对象是不是某个特定类型的实例

@SuppressWarnings(“unchecked”)告诉编译器忽略 unchecked 警告信息,如使用List,ArrayList等未进行参数化产生的警告信息。

//用法:
  boolean result = object instanceof class
//参数:
  //result :boolean类型。
  //object :必选项。任意对象表达式。
  //class:必选项。任意已定义的对象类。
//(如果该x是该class的一个实例,那么返回true。如果该object 不是该class的一个实例,或者object是null,则返回false)
//一般用于向下转型
if(x instanceof Dog){
    ((Dog)x).bark();
}

14.3.1 使用类字面常量

14.3.2 动态的instanceof

Class.isInstanceof方法提供了一种动态地测试对象的途径。完美代替了instanceof语句

14.3.3 递归计数

14.4 注册工厂

将对象的创建工作交给类自己去完成。工厂方法可被动态调用,从而为你创建恰当类型的对象。

public class Filter extends Part{
	
}
public class FuelFilter extends Filter {
	public static class Factory implements com.agree.ShiSi.Factory<FuelFilter>{

		@Override
		public FuelFilter create() {
			// TODO Auto-generated method stub
			return new FuelFilter();
		}
		
	}
}
public class AirFilter extends Filter {
	public static class Factory implements com.agree.ShiSi.Factory<AirFilter>{

		@Override
		public AirFilter create() {
			// TODO Auto-generated method stub
			return new AirFilter();
		}
		
	}
}
public class Belt extends Part {
	
}
public class FanBelt extends Belt {
	public static class Factory implements com.agree.ShiSi.Factory<FanBelt>{

		@Override
		public FanBelt create() {
			// TODO Auto-generated method stub
			return new FanBelt();
		}
		
	}
}
public class GeneratorBelt extends Belt{
	public static class Factory implements com.agree.ShiSi.Factory<GeneratorBelt>{

		@Override
		public GeneratorBelt create() {
			// TODO Auto-generated method stub
			return new GeneratorBelt();
		}
		
	}
}
public class Part {
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return getClass().getSimpleName();
	}
	static List<Factory<? extends Part>> partFactories = new ArrayList<Factory<? extends Part>>();
	static {
		partFactories.add(new FuelFilter.Factory());
		partFactories.add(new AirFilter.Factory());
		partFactories.add(new FanBelt.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();
	}
	
}
public class RegisteredFactories {
	public static void main(String[] args) {
		for(int i=0;i<10;i++) {
			System.out.println(Part.createRandom());
		}
	}
}
//output
FanBelt
AirFilter
FanBelt
FuelFilter
FuelFilter
FanBelt
FuelFilter
AirFilter
FanBelt
FanBelt

14.5 instanceof与Class的等价性

instanceof和isInstance()生成结果一样,equals()和==也一样

x instanceof Base
Base.class.isInstance(x)
x.getClass == Base.class
x.grtClass.equals(Base)

14.6 反射:运行时类的信息

概念:运行时获取类的信息。

快速应用开发(RAD);集成开发环境(IDE);图形化用户界面(GUI)

人们想要在运行时获取类的信息的另一个动机,便是希望提供在跨网络的远程平台上创建和运行对象的能力,称为远程方法调用(RMI)

Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样就可以使用Constructor创建新的对象,用get()和set()方法读取和修改与Filed对象管理的字段,用invoke()方法调用与Method对象关联的方法。还可以调用getFields()、getMethods()和getConstructors()等方法,以返回表示字段、方法以及构造器的对象的数组。Class.forName()生成的结果在编译时是不可知的,因此所有方法的特征签名信息都是在执行时被提取出来的。

RTTI和反射的区别在于

RTTI编译器在编译时打开和检查.class文件。反射是说.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件

14.6.1 类方法提取器

反射的作用:反射机制提供了足够的支持,使得能够创建一个在编译时完全未知的对象,并调用此对象的方法。反射在java中是用来支持其他特性的,例如对象序列化和JavaBean。

 Class<?> c = Class.forName(args[0]);
 Method[] methods = c.getMethods();
 Constructor[] ctors = c.getConstructors();

14.7 动态代理

为了提供额外的或不同操作,而插入的用来代表“实际”对象的对象。这些操作通常涉及与“实际”对象的通信,因此通常充当中间人的角色。

只要是你想将额外的操作从“实际”对象中分离到不同的地方,特别是当你希望能够很容易地做出修改,从没有使用额外操作转为使用这些操作,或者反过来,代理就显得很有用。

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

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

通过调用了Proxy.newProxyInstance()可创建动态代理,这是这个方法所要传递的参数,动态代理可将所有调用重定向到调用处理器,通常会向调用处理器的构造器传递给一个“实际”对象的引用,从而使得调用处理器在执行其中介任务时,可请求转发。

 public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, 		    InvocationHandler h)
 throws IllegalArgumentException

invoke()方法传递进来代理对象,以防你需要区分的请求的来源。在invoke()内部,对接口的调用将被重定向为对代理的调用。

至于它是怎么自动执行invoke()的方法,需要看产生的内部类的源码。

代理对象调接口中的方法—代理对象的真身是$proxy0 调用了对应的方法—此方法内部调用其父类的成员h调用h的invoke方法—就是调用传入了InvocationHandler的invoke方法,至于返回值,那就看我们的InvocationHandler的实现类怎么写了。

动态代理之代理工厂实现

public class ProxyFactory {

    private Object obj;//目标对象
    private BeforeAdvice before;
    private AfterAdvice after;

    public Object newProxyInstance() {

        Object object = Proxy.newProxyInstance(
                this.getClass().getClassLoader(), obj.getClass()
                        .getInterfaces(), new InvocationHandler() {

                    @Override
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {

                        if (before != null) {
                            before.before();
                        }
                        //result即为接口的真实实现类的返回值
                        Object result = method.invoke(obj, args);
                        
                        if (after != null) {
                            after.after();
                        }

                        return result;
                    }
                });
        //object即为代理对象
        return object;
    }

    public ProxyFactory() {
        super();
        // TODO Auto-generated constructor stub
    }

    public ProxyFactory(Object obj, BeforeAdvice before, AfterAdvice after) {
        super();
        this.obj = obj;
        this.before = before;
        this.after = after;
    }

    public Object getObj() {
        return obj;
    }

    public void setObj(Object obj) {
        this.obj = obj;
    }

    public BeforeAdvice getBefore() {
        return before;
    }

    public void setBefore(BeforeAdvice before) {
        this.before = before;
    }

    public AfterAdvice getAfter() {
        return after;
    }

    public void setAfter(AfterAdvice after) {
        this.after = after;
    }

}
//前置增强
interface BeforeAdvice {
    public void before();
}
//后置增强
interface AfterAdvice {
    public void after();
}

14.8 空对象

空迭代器模式,它使得在组合层次结构中遍历各个节点的操作对客户端透明(客户端可使用相同的逻辑来遍历组合和叶子节点)。

需要测试一对象是否为空可创建一个空对象接口(r instanceof Null),或者用“==”和对象名.equals()

14.8.1 模拟对象与桩

空对象的逻辑变体是模拟对象和桩。

模拟对象往往是轻量级和自测试的,通常是为了处理各种不同的测试。

桩值返回桩数据,通常为重量级的,经常在测试间被复用。

14.9 接口与类型信息

public interface A {
	void f();
}
public class B implements A {
	public void f(){}
    
	public void g() {}
	public static void main(String[] args) {
		A a = new B();
		a.f();
		//a.g();报错
		System.out.println(a.getClass().getName());
		if(a instanceof B) {
			B b = (B)a;
			b.g();
		}
	}
}
//output B
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孙嵓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值