网站首页 > java教程 正文
本章将讨论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<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,并且为其编写一段特别的代码以提高效率。然而必须要注意,不要太早的关注程序的效率问题,这是个诱人的陷阱。最后首先让程序运作起来,然后再考虑它的速度。
猜你喜欢
- 2024-09-10 感动哭了!《Java 编程思想》最新中文版开源
- 2024-09-10 Java编程思想---对象介绍---参数化类型(泛型)
- 2024-09-10 《JAVA编程思想》5分钟速成:第15章(泛型)
- 2024-09-10 Java基础学习带你了解面向对象的编程思想
- 2024-09-10 Java编程思想——对象介绍——抽象过程
- 2024-09-10 Java编程思想---对象介绍---并发编程
- 2024-09-10 Java编程思想——并发(java并发编程实践和并发编程实战)
- 2024-09-10 Java编程思想:抽象类和方法(java抽象方法是什么)
- 2024-09-10 Java编程思想---万物皆是对象---以引用来操作对象
- 2024-09-10 Java编程思想:我们的第一个 Java 程序
你 发表评论:
欢迎- 最近发表
-
- pyinstaller打包python程序高级技巧
- 将python打包成exe的方式(python打包成exe的方法)
- Python打包:如何将 Flask 项目打包成exe程序
- py2exe实现python文件打包为.exe可执行程序(上篇)
- 如何将 Python 项目打包成 exe,另带卸载功能!
- Python打包成 exe,太大了该怎么解决?
- 可视化 Python 打包 exe,这个神器绝了!
- 案例详解pyinstaller将python程序打包为可执行文件exe
- Cocos 3.x 菜鸟一起玩:打包window程序
- 怎么把 Python + Flet 开发的程序,打包为 exe ?这个方法很简单!
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)