专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java编程思想创新阅读第14章-类型信息

temp10 2024-09-10 20:48:01 java教程 12 ℃ 0 评论

本章将讨论Java是如何让我们在运行时识别对象和类的信息的。主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另外一种是“反射”机制,它允许我们在运行时发现和使用类的信息。

14.1

Java编程思想创新阅读第14章-类型信息

这是一个典型的类层次结构图,基类位于顶部,派生类向下扩展。面向对象编程中基本的目的是,让代码只操作对基类(这里是Shape)的引用。这样,如果要添加一个新类(比如从Shape派生的子类Rhomboid)来拓展程序,就不会影响到原来的代码。在这个例子的Shape接口中动态绑定了draw()方法,目的就是让客户端程序员使用泛化的Shape引用来调用draw()。draw()在所有派生类里都会被覆盖,并且由于它是被动态绑定的,所以即使是通过泛化的Shape引用来调用,也能产生正确行为。这就是多肽。

因此,通常会创建一个具体对象(Circle,Square,或者Triangle),把它向上转型成Shape(忽略对象的具体类型),并在后面的程序中使用匿名(译注:即不知道具体类型)的Shape引用。你可以像下面这样对Shape层次结构编码:

package com.exam.cn;

import java.util.Arrays;
import java.util.List;

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

	abstract public String toString();
}

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

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

class Triangle extends Shape {
	@Override
	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();
		}
	}
}
输出结果:
Circle.draw()
Square.draw()
Triangle.draw()

基类中包含draw()方法,它通过传递this参数给System.out.println(),间接地使用toString()打印类标识符(注意。toString()被声明为abstract,以此强制继承者覆写该方法,并可以防止对无格式的Shape的实例化)。如果某个对象出现在字符串表达式中(涉及“+”和字符串对象的表达式),toString()方法就会被自动调用,以生成表示该对象的String。每个派生类都要覆盖(从Object继承来的)toString()方法,这样draw()在不同情况下就打印处不同的消息。(多态)。

在这个例子中,当把Shape对象放入List<Shape>的数组时会向上转型。但在向上转型为Shape的时候也丢失了Shape对象的具体类型。对于数组而言,他们只是Shape类的对象。

当从数组中取出元素时,这种容器——实际上它将所有的事物都当作Object持有——会自动将结果转型回Shape。这是RTTI最基本的使用形式,因为在Java中,所有的类型转换都是在运行时进行正确性检查的。这也是RTTI名字的含义:在运行时,识别一个对象的类型。

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

接下来就是多态机制的事情了,Shape对象实际上执行什么样的代码,是由引起所指向的具体对象Circle,Square,或者Triangle而决定的。通常也正是这样要求的;通常也正是这样要求的;你希望大部分代码尽可能少地了解对象地具体类型,而是只与对象家族中地一个通用表示打交道(在这个例子中是Shape)。这样代码会更容易写,更容易读,且便于维护;设计也更容易实现,理解和改变。所以“多态”是面向对象编程地基本目标。

在查询类型信息时,,以instanceof的形式(即以instanceof的形式或isInstance()的形式,他们产生相同的结果)与直接比较Class对象有一个很重要的差别。下面的例子展示了这种差别:

package com.exam.cn;
class Base{
	
}
class Derived extends Base{
	
}
public class FamilyVsExactType {
static void test(Object x){
	System.out.println("Testing x of type "+x.getClass());
	System.out.println("x instance of Base "+(x instanceof Base));
	System.out.println("x instanceof Derived "+(x instanceof Derived));
	System.out.println("Base.isInstance(x) "+Base.class.isInstance(x));
	System.out.println("Derived.isInstance(x) "+Derived.class.isInstance(x));
	System.out.println("x.getClass()==Base.class "+(x.getClass()==Base.class));
	System.out.println("x.getClass()==Derived.class "+(x.getClass()==Derived.class));
	System.out.println("x.getClass.equals(Base.class) "+x.getClass().equals(Base.class));
	System.out.println("x.getClass.equals(Derived.class) "+x.getClass().equals(Derived.class));
}
public static void main(String[] args) {
	test(new Base());
	test(new Derived());
}
}
输出结果:
Testing x of type class com.exam.cn.Base
x instance of 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 com.exam.cn.Derived
x instance of 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

test()方法使用了两种形式的instanceof作为参数来执行类型检查。然后获取Class引用,并用==和equals来检查Class对象是否相等。使人放心的是,instanceof和isInstance()生成的结果完全一样,equals()和==也一样,但是这两组测试得出的结论却不相同。instanceof保持了类型的概念,它指的是“你是这个类吗,或者你是这个类的派生类吗?”,而如果用==比较实际的Class对象,就没有考虑继承——它或者是这个确切的类型,或者不是。

代理:

package com.exam.cn;

public 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");
		proxied.somethingElse(arg);
	}

}
package com.exam.cn;

public 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()));
	}
}

因为consumer()接受的Interface,所以它无法知道正在获得的到底是RealObject还是SimpleProxy,因为这二者都实现了Interface。

任何时刻,只要你想要将额外的操作从实际对象中分离到不同的地方,特别是当你希望能够容易的作出修改,从没有使用额外操作转为使用这些操作,或者反过来时,代理就显得很有用(设计模式的关键就是封装修改——因此你需要修改事物以证明这种模式的正确性)。例如,如果你希望跟踪对RealObject中的方法的调用,或者希望度量这些方法的开销,那么你应该怎样做呢?这些代码肯定是你不希望将其合并到应用中的代码,因此代理使得你可以很容易地添加或移除它们。

Java动态代理比代理的思想更向前迈进了一步,因为它可以动态的创建代理并动态的处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,他的工作是揭示调用的类型并确定相应的对策。下面是用动态代理重写的Demo:

package com.exam.cn;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicProxyHandler implements InvocationHandler {
	private Object proxied;
	public DynamicProxyHandler(Object proxied) {
		this.proxied = proxied;
	}
	@Override
	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(" 1 "+arg); 
		}
		return method.invoke(proxied, args);
	}

}
package com.exam.cn;

import java.lang.reflect.Proxy;

public 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);
		Interface proxy=(Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(),new Class[]{Interface.class}, new DynamicProxyHandler(real));
		consumer(proxy);
	}
}
输出结果:
doSomething
somethingElse bonobo
SimpleProxy doSomething
doSomething
SimpleProxy somethingElse
somethingElse bonobo


通过调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要得到一个类加载器(你通常可以从已经被加载的对象中获得其类加载器,然后传递给它),一个你希望该代理实现的接口列表(不是类或抽象类),以及InbocationHandler接口的一个实现。动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造器传递给一个“实际”对象的引用,从而使得调用处理器在执行其中介任务时,可以将请求转发。

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

通过使用反射,仍旧可以到达并调用所有方法,甚至是private方法!

最后一点,RTTI有时能解决效率问题。也许你的程序漂亮的运用了多态,但其中某个对象是以极端缺乏效率的方式达到这个目的的。你可以挑出这个类,使用RTTI,并且为其编写一段特别的代码以提高效率。然而必须要注意,不要太早的关注程序的效率问题,这是个诱人的陷阱。最后首先让程序运作起来,然后再考虑它的速度。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表