Java编程思想创新阅读第14章 您所在的位置:网站首页 java中draw方法怎么用 Java编程思想创新阅读第14章

Java编程思想创新阅读第14章

#Java编程思想创新阅读第14章| 来源: 网络整理| 查看: 265

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

14.1

这是一个典型的类层次结构图,基类位于顶部,派生类向下扩展。面向对象编程中基本的目的是,让代码只操作对基类(这里是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 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类的对象。

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

在这个例子中,RTTI类型转换并不彻底:Object被转型为Shape,而不是转型为Circle,Square,Triangle。这是因为目前我们只知道这个List保存的都是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,并且为其编写一段特别的代码以提高效率。然而必须要注意,不要太早的关注程序的效率问题,这是个诱人的陷阱。最后首先让程序运作起来,然后再考虑它的速度。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有