运行时类型信息使得你可以在程序运行时发现和使用类型信息。
在Java中主要有两种方法让我们在运行时识别对象和类的信息:第一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。
为什么需要RTTI?
面向对象编程中基本的目的是:让代码只操纵对基类的引用。这样,如果添加一个新类来扩展程序,就不会影响到原来的代码。
示例代码:
abstract class Shape {
void draw(){
System.out.println(this + ".draw()");
}
abstract public String toString();
}
public class Circle extends Shape {
public String toString() {
return "Circle";
}
}
public class Square extends Shape {
public String toString() {
return "Square";
}
}
public class Triangle extends Shape {
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();
}
}
}
/*
out:
Circle.draw()
Square.draw()
Triangle.draw()
*/
当从数组中取出元素时,这种容器——实际上它将所有的事物都当作Object持有——会自动将结果转型回数组指定的类型。这就是RTTI最基本的使用形式,因为在Java中,所有的类型转换都是在运行时进行正确性检查的。这也就是RTTI名字的含义:在运行时,识别一个对象的类型。
Class对象
Class对象就是用来创建类的所有“常规”对象的。Java使用CLass对象来执行其RTTI,即使你正在执行的是类似转型这样的操作。Class类还拥有大量的使用RTTI的其他方式。
类是程序的一部分,每一个类都有一个Class对象。换言之,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行这个程序的JVM将使用被称为“类加载器”的子系统。
类加载器实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加的是所谓的可信类,包括Java API类,它们通常是从本地加载的。在这条链中,通常不需要添加额外的类加载器,但是如果有特殊需求,那么有一种方式可以挂接额外的类加载器。
所有的类加载器都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,即使在构造器之前并没有使用static关键字。因此,使用new操作符创建类的新对象也会被当做对类的静态成员的引用。
因此,Java程序在它开始运行之前并非被完全加载,其各个部分是在必要时才加载的。
类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良Java代码。
一旦某个类的Class对象被载入内存,它就用来创建这个类的所有对象。
示例代码:
public class Candy {
static {
System.out.println("Loading Candy...");
}
}
public class Gum {
static {
System.out.println("Loading Gum...");
}
}
public class Cookie {
static {
System.out.println("Loading Cookie...");
}
}
public class SweetShop {
public static void main(String[] args) {
System.out.println("inside main");
new Candy();
System.out.println("after creating Candy");
try {
//Class.forName()要填写全路径,不然找不到
Class.forName("TypeInfo.ClassObject.Gum");
} catch (ClassNotFoundException e) {
System.out.println("cannt find Gum");
}
System.out.println("after class.forName(Gum)");
new Cookie();
System.out.println("after creating Cookie");
}
}
/*
out:
inside main
Loading Candy...
after creating Candy
Loading Gum...
after class.forName(Gum)
Loading Cookie...
after creating Cookie
*/
无论何时,只要你想在运行时使用类型信息,就必须首先获得恰当的Class对象的引用。Class.forName()就是实现此功能的便捷途径,因为你不需要为了获得Class引用而持有该类型的对象。但是,如果你已经拥有了一个感兴趣的类型的对象,那就可以通过调用getClass()方法来获取Class引用了,这个方法属于根类Object的一部分,它将返回表示该对象的实际类型的Class引用。
示例代码:
public class ToyTest {
static void printInfo(Class cc){
System.out.println("Class Name:" + cc.getName()
+ ",is interface?[" + cc.isInterface() + "]"
);
System.out.println("Simple Name:" + cc.getSimpleName());
System.out.println("Canonical Name:" + cc.getCanonicalName());
}
public static void main(String[] args) {
Class c = null;
try {
c = Class.forName("TypeInfo.ClassObject.FancyToy");
} catch (ClassNotFoundException e) {
System.out.println("cannt find FancyToy!");
System.exit(1);
}
printInfo(c);
for (Class face : c.getInterfaces()){
printInfo(face);
}
Class up = c.getSuperclass();
Object obj = null;
try {
//已经废弃
//obj = up.newInstance();
//用这个
obj = up.getDeclaredConstructor().newInstance();
} catch (InstantiationException e) {
System.exit(1);
} catch (InvocationTargetException e) {
System.exit(1);
} catch (NoSuchMethodException e) {
System.exit(1);
} catch (IllegalAccessException e) {
System.exit(1);
}
printInfo(up.getClass());
}
}
/*
out:
Class Name:TypeInfo.ClassObject.FancyToy,is interface?[false]
Simple Name:FancyToy
Canonical Name:TypeInfo.ClassObject.FancyToy
Class Name:TypeInfo.ClassObject.HasBatteries,is interface?[true]
Simple Name:HasBatteries
Canonical Name:TypeInfo.ClassObject.HasBatteries
Class Name:TypeInfo.ClassObject.Shoots,is interface?[true]
Simple Name:Shoots
Canonical NameTypeInfo.ClassObject.Shoots
Class Name:TypeInfo.ClassObject.Waterproof,is interface?[true]
Simple Name:Waterproof
Canonical Name:TypeInfo.ClassObject.Waterproof
Class Name:java.lang.Class,is interface?[false]
Simple Name:Class
Canonical Name:java.lang.Class
*/
类字面常量
Java还提供了另一种方法来生成对Class对象的引用,即使用类字面常量。
形如,XXX.class。
这样做不仅仅更简单,而且更安全,因为它在编译时就会受到检查(因此不需要至于try语句块中)。并且根除了forName()方法的调用,所以更加高效。
类字面常量不仅可以应用于普通的类,也可以用于接口、数组以及基本类型数据。另外,对于基本类型的包装器类,还有一个标准字段Type。Type字段是一个引用,指向对应的基本数据类型的Class对象。
对应表:
类字面常量 | 标准字段TYPE |
---|---|
boolean.class | Boolean.TYPE |
byte.class | Byte.TYPE |
short.class | Short.TYPE |
int.class | Integer.TYPE |
long.class | Long.TYPE |
float.class | Float.TYPE |
double.class | Double.TYPE |
void.class | Void.TYPE |
char.class | Character.TYPE |
为了使用类而做的准备工作实际包含三个步骤:
-
加载,这是由类加载器执行的。该步骤将查找字节码(通常在classpath所指定的路径中查找,但这并非是必须的),并从这些字节码中创建一个Class对象;
-
链接。在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用;
-
初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。
初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行;
示例代码:
public class Initable {
static final int staticFinal = 47;
static final int staticFinal2 = ClassInit.rand.nextInt(1000);
static {
System.out.println("Init Initable...");
}
}
public class Initable2 {
static int staticNonFinal = 147;
static {
System.out.println("Init Initable2...");
}
}
public class Initable3 {
static int staticNonFinal = 74;
static {
System.out.println("Init Initable3...");
}
}
public class ClassInit {
public static Random rand = new Random(47);
public static void main(String[] args) throws ClassNotFoundException {
//Class对象不会触发初始化
Class initable = Initable.class;
System.out.println("After creating Initable ref");
System.out.println(Initable.staticFinal);
System.out.println(Initable.staticFinal2);
System.out.println(Initable2.staticNonFinal);
//forName会触发初始化
Class initable3 = Class.forName("TypeInfo.Classliterals.Initable3");
System.out.println("After creating Initable3 ref");
System.out.println(Initable3.staticNonFinal);
}
}
/*
out:
After creating Initable ref
47
Init Initable...
258
Init Initable2...
147
Init Initable3...
After creating Initable3 ref
74
*/
在输出中可以看到,在获取Initable的Class引用时,通过.class的方式并没有触发类的初始化,而相对的Class.forName()则会触发类的初始化;
如果一个static域不是final的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间)。
泛化的Class引用
普通的类引用不会产生警告信息,尽管泛型类引用只能赋值为指向其声明的类型,但是普通的类引用可以被重新赋值为指向任何其他的Class对象。通过使用泛型语法,可以让编译器强制执行额外的类型检查。
示例代码:
public class GenericClassReferences {
public static void main(String[] args) {
Class intClass = int.class;
Class<Integer> genericIntClass = int.class;
genericIntClass = Integer.class;
intClass = double.class;
//报错
//genericIntClass = double.class;
}
}
为了在使用泛化的Class引用时放松限制,可以使用通配符,它是Java泛型的一部分。通配符就是“?”,表示“任何事物”。
示例代码:
public class WildcardClassREferences {
public static void main(String[] args) {
Class<?> intClass = int.class;
intClass = double.class;
intClass = void.class;
}
}
为了创建一个Class引用,它被限定为某种类型,或该类型的任何子类,需要将通配符与extends关键字相结合,创建一个范围。
示例代码:
public class BoundedClassReference {
public static void main(String[] args) {
Class<? extends Number> bounded = int.class;
bounded = double.class;
bounded = Number.class;
}
}
向Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查,因此如果操作有误,会马上发现错误。在使用普通的Class引用,不会误入歧途,但是如果确实犯了错,那么有可能到运行时才会发现错误,这就显得非常不方便。
示例代码:
public class CountedInteger {
private static long counter;
private final long id = counter++;
@Override
public String toString() {
return "CountedInteger{" +
"id=" + id +
'}';
}
}
public class FilledList<T> {
private Class<T> type;
public FilledList(Class<T> type){
this.type = type;
}
public List<T> craete(int nElements){
List<T> result = new ArrayList<>();
try {
for (int i = 0; i < nElements; i++) {
result.add(type.getDeclaredConstructor().newInstance());
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
public static void main(String[] args) {
FilledList<CountedInteger> fl = new FilledList<>(CountedInteger.class);
System.out.println(fl.craete(5));
}
}
/*
out:
[CountedInteger{id=0},
CountedInteger{id=1},
CountedInteger{id=2},
CountedInteger{id=3},
CountedInteger{id=4}]
*/
注意,这个类必须假设与它一同工作的任何类型都具有一个默认的构造器(无参构造器),并且如果不符合该条件,你将得到一个异常。
类型转换前先做检查
几个常见的RTTI形式包括:
- 传统的类型转换,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换就会抛出一个ClassCastException;
- 代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息;
- 关键字instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例;
对instanceof有比较严格的限制:只可将其与命名类型进行比较,而不能与Class对象作比较。
动态的instanceof
Class.isInstance方法提供了一种动态地测试对象的途径。
注册工厂
为方便调用者创建自己想要获取的对象,此时可以使用工厂模式,对象创建者只需要将创建某些对象的方法准备好,保存至注册工厂中,而使用者只需要调用即可。
示例代码:
public class Part {
@Override
public String toString() {
return getClass().getSimpleName();
}
public static List<Factory<? extends Part>> partFactories = new ArrayList<>();
static {
partFactories.add(new FuelFilter.Factory());
partFactories.add(new AirFilter.Factory());
partFactories.add(new CabinAirFilter.Factory());
partFactories.add(new OilFilter.Factory());
partFactories.add(new FanBelt.Factory());
partFactories.add(new PowerSteeringBelt.Factory());
partFactories.add(new GeneratorBelt.Factory());
}
private static Random random = new Random(47);
public static Part createRandom(){
int n = random.nextInt(partFactories.size());
return partFactories.get(n).create();
}
}
public class Filter extends Part {
}
public class FuelFilter extends Filter {
public static class Factory implements TypeInfo.RegisterFactory.Factory<FuelFilter>{
@Override
public FuelFilter create() {
return new FuelFilter();
}
}
}
public class OilFilter extends Filter {
public static class Factory implements TypeInfo.RegisterFactory.Factory<OilFilter>{
@Override
public OilFilter create() {
return new OilFilter();
}
}
}
public class AirFilter extends Filter {
public static class Factory implements TypeInfo.RegisterFactory.Factory<AirFilter>{
@Override
public AirFilter create() {
return new AirFilter();
}
}
}
public class CabinAirFilter extends Filter {
public static class Factory implements TypeInfo.RegisterFactory.Factory<CabinAirFilter>{
@Override
public CabinAirFilter create() {
return new CabinAirFilter();
}
}
}
public class Belt extends Part {
}
public class FanBelt extends Belt {
public static class Factory implements TypeInfo.RegisterFactory.Factory<FanBelt>{
@Override
public FanBelt create() {
return new FanBelt();
}
}
}
public class GeneratorBelt extends Belt {
public static class Factory implements TypeInfo.RegisterFactory.Factory<GeneratorBelt>{
@Override
public GeneratorBelt create() {
return new GeneratorBelt();
}
}
}
public class PowerSteeringBelt extends Belt {
public static class Factory implements TypeInfo.RegisterFactory.Factory<PowerSteeringBelt>{
@Override
public PowerSteeringBelt create() {
return new PowerSteeringBelt();
}
}
}
public interface Factory<T> {
T create();
}
public class RegisterFactories {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(Part.createRandom());
}
//使用者不需要知道怎么创建一个对象,只需要调用即可
Part part = Part.partFactories.get(2).create();
}
}
instanceof与Class的等价性
instanceof和isInstance()产生的结果完全一样,equals()和==也一样。instanceof保持了类型的概念,它指的是“你是这个类吗,或者你是这个类的派生类吗?”,而如果使用==比较时间的Class对象,就没有考虑继承——它是这个确切的类型或者不是。
示例代码:
public class FamilyVsExactType {
public static void test(Object x){
System.out.println("Testing x of type " + x.getClass());
System.out.println("x instanceof 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());
System.out.println("*****************************");
test(new Derived());
}
}
/*
out:
Testing x of type class TypeInfo.InstanceofAndClass.Base
x instanceof 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 TypeInfo.InstanceofAndClass.Derived
x instanceof 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
*/
反射:运行时的类信息
如果不知道某个对象的确切类型,RTTI可以告诉你。但是有个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,并利用这些信息做一些有用的事,换句话说,在编译时,编译器必须知道所有要通过RTTI来处理的类。实际上,在编译时程序根本无法获知这个对象所属的类。
Class类和java.lang.reflect类库一起对反射的概念进行了支持,该类库包括了Field、Method以及Constructor类(每一个类都实现了Member接口)。这些类型的对象都是由JVM在运行时创建的,用以表示未知类里对应的成员。这样就可以使用Constructor创建新的对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外还可以调用getField()、getMethods()和getConstructors()等很便利的方法,以返回表示字段、方法以及构造器的对象的数组。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。
值的注意的是,反射机制并没有什么神奇之处。当通过反射与一个位置类型的对象打交道时,JVM只是简单地检查这个对象没,看它属于哪个特定的类。在用它做其他事情之前必须先加载那个类的Class对象。因此,那个类的.class文件对于JVM来说必须是可获取的:要么在本地,要么可以通过网络获取。所以RTTI和反射之间真正的区别只在于,对于RTTI来说,编译器在编译时打开和检查.class文件。而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。(反射相对RTTI来说显得相对灵活)
类方法提取器
这里给出获取某个对象中getter方法并调用的例子(自己编写非TIJ示例):
public class Frame implements Comparable<Frame>{
Integer allFrameNum;
Integer currFrameNum;
byte[] data;
Integer prmCode;
Integer fnCode;
MessageDirection dir;
Date tp;
String tpStr;
Integer clientId;
String firAndFin;
Integer pfc;
Integer Afn;
Integer secretData;
public Integer isSecretData() {
return secretData;
}
public void setSecretData(Integer secretData) {
this.secretData = secretData;
}
public Integer getAfn() {
return Afn;
}
public void setAfn(Integer afn) {
Afn = afn;
}
public Integer getAllFrameNum() {
return allFrameNum;
}
public void setAllFrameNum(Integer allFrameNum) {
this.allFrameNum = allFrameNum;
}
public Integer getCurrFrameNum() {
return currFrameNum;
}
public void setCurrFrameNum(Integer currFrameNum) {
this.currFrameNum = currFrameNum;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
public Integer getPrmCode() {
return prmCode;
}
public void setPrmCode(Integer prmCode) {
this.prmCode = prmCode;
}
public Integer getFnCode() {
return fnCode;
}
public void setFnCode(Integer fnCode) {
this.fnCode = fnCode;
}
public MessageDirection getDir() {
return dir;
}
public void setDir(MessageDirection dir) {
this.dir = dir;
}
public Date getTp() {
return tp;
}
public void setTp(Date tp) {
this.tp = tp;
}
public String getTpStr() {
return tpStr;
}
public void setTpStr(String tpStr) {
this.tpStr = tpStr;
}
public Integer getClientId() {
return clientId;
}
public void setClientId(Integer clientId) {
this.clientId = clientId;
}
public String getFirAndFin() {
return firAndFin;
}
public void setFirAndFin(String firAndFin) {
this.firAndFin = firAndFin;
}
public Integer getPfc() {
return pfc;
}
public void setPfc(Integer pfc) {
this.pfc = pfc;
}
@Override
public String toString() {
return "Frame{" +
"allFrameNum=" + allFrameNum +
", currFrameNum=" + currFrameNum +
", data=" + Arrays.toString(data) +
", prmCode=" + prmCode +
", fnCode=" + fnCode +
", dir=" + dir +
", tp=" + tp +
", tpStr='" + tpStr + '\'' +
", clientId=" + clientId +
", firAndFin='" + firAndFin + '\'' +
", pfc=" + pfc +
", Afn=" + Afn +
", secretData=" + secretData +
'}';
}
@Override
public int compareTo(Frame frame){
return Long.compare(this.getCurrFrameNum(),frame.getCurrFrameNum());
}
@Override
public boolean equals(Object obj) {
if (obj == null){
return false;
}
//自己判断自己
if (this == obj){
return true;
}
//判断类型
if (obj instanceof Frame){
Frame other = (Frame)obj;
Method[] methods = Frame.class.getMethods();
for (Method cursor : methods) {
//只获取get方法
if (!cursor.getName().contains("get")
||cursor.getName().contains("set")){
continue;
}
//对get方法获取的结果进行比较
try {
Method otherMothod = Frame.class.getMethod(cursor.getName());
//如果双方该字段都为null则一定相同
if (cursor.invoke(this) == null
&& otherMothod.invoke(other) == null){
continue;
}
//如果双方该字段一个有一个没有则一定不同
if (cursor.invoke(this) == null
&& otherMothod.invoke(other) != null){
return false;
}
if (cursor.invoke(this) != null
&& otherMothod.invoke(other) == null){
return false;
}
//如果该字段双方均有则需要判断
if (cursor.getReturnType().getCanonicalName().contains("[]")){
//如果是数组
byte[] myData = (byte[]) cursor.invoke(this);
byte[] otherData = (byte[]) cursor.invoke(other);
if (!Arrays.equals(myData, otherData)){
return false;
}
}else if (!cursor.invoke(this).equals(otherMothod.invoke(other))){
//如果不是集合
return false;
}
} catch (Exception e) {
System.out.println("error method:" + cursor.getName());
e.printStackTrace();
return false;
}
}
}else {
return false;
}
return true;
}
}
示例是比较两个自定的对象,如果挨个调用getter,一是显得代码十分臃肿,二是如果以后扩展这里需要挨个修改容易出错不易扩展。
动态代理
代理是基本的设计模式之一,它是为了提供额外的或者不同的操作,而插入的用来代替“实际”对象的对象。这些操作通常涉及与“实际”对象的通信,因此代理通常充当着中间人的角色。
示例代码:
public interface Interface {
void doSomething();
void doSomethingElse(String arg);
}
public class RealObject implements Interface {
@Override
public void doSomething() {
System.out.println(
this.getClass().getSimpleName() + ":" +
new Exception().getStackTrace()[0].getMethodName()
);
}
@Override
public void doSomethingElse(String arg) {
System.out.println(
this.getClass().getSimpleName() + ":" +
new Exception().getStackTrace()[0].getMethodName() +
":" + arg
);
}
}
public class SimpleProxy implements Interface {
private Interface proxied;
public SimpleProxy(Interface proxied){
this.proxied = proxied;
}
@Override
public void doSomething() {
System.out.println(
this.getClass().getSimpleName() + ":" +
new Exception().getStackTrace()[0].getMethodName()
);
proxied.doSomething();
}
@Override
public void doSomethingElse(String arg) {
System.out.println(
this.getClass().getSimpleName() + ":" +
new Exception().getStackTrace()[0].getMethodName() +
":" + arg
);
proxied.doSomethingElse(arg);
}
}
public class SPDemo {
public static void consumer(Interface iface){
iface.doSomething();
iface.doSomethingElse("ABCDEFG");
}
public static void main(String[] args) {
consumer(new RealObject());
System.out.println("*********************");
consumer(new SimpleProxy(new RealObject()));
}
}
/*
out:
RealObject:doSomething
RealObject:doSomethingElse:ABCDEFG
*********************
SimpleProxy:doSomething
RealObject:doSomething
SimpleProxy:doSomethingElse:ABCDEFG
RealObject:doSomethingElse:ABCDEFG
*/
关于Exception.getStackTrace()
JDK中的注释:
/**
* Provides programmatic access to the stack trace information printed by
* {@link #printStackTrace()}. Returns an array of stack trace elements,
* each representing one stack frame. The zeroth element of the array
* (assuming the array's length is non-zero) represents the top of the
* stack, which is the last method invocation in the sequence. Typically,
* this is the point at which this throwable was created and thrown.
* The last element of the array (assuming the array's length is non-zero)
* represents the bottom of the stack, which is the first method invocation
* in the sequence.
*
* <p>Some virtual machines may, under some circumstances, omit one
* or more stack frames from the stack trace. In the extreme case,
* a virtual machine that has no stack trace information concerning
* this throwable is permitted to return a zero-length array from this
* method. Generally speaking, the array returned by this method will
* contain one element for every frame that would be printed by
* {@code printStackTrace}. Writes to the returned array do not
* affect future calls to this method.
*
* @return an array of stack trace elements representing the stack trace
* pertaining to this throwable.
* @since 1.4
*/
机翻:
/**
*提供对由打印的堆栈跟踪信息的编程访问
*{@link#printStackTrace()}。返回堆栈跟踪元素数组,
*每个代表一个堆栈帧。数组的第0个元素
*(假设数组的长度为非零)表示
*堆栈,这是序列中最后一个方法调用。通常情况下,
*这就是这个投掷物被创造和投掷的点。
*数组的最后一个元素(假设数组的长度为非零)
*表示堆栈的底部,这是第一个方法调用
*按顺序。
*
*<p>在某些情况下,某些虚拟机可能会忽略一个
*或堆栈跟踪中的更多堆栈帧。在极端情况下,
*没有堆栈跟踪信息的虚拟机
*允许此throwable从中返回零长度数组
*方法。一般来说,此方法返回的数组将
*为每个要打印的帧包含一个元素
*{@code printStackTrace}。对返回数组的写入不
*影响将来对此方法的调用。
*
*@返回表示堆栈跟踪的堆栈跟踪元素数组
*关于这个投掷物的。
*@从1.4开始
*/
在任何时候,只要想将额外的操作从“实际”对象中分离到不同的地方,特别是希望能够很容易地做出修改,从没有使用额外操作的地方转为使用这些操作,或者反过来时,代理就显得十分有用(设计模式的关键就是对封装修改——因此需要修改事务以证明这种模式的正确性)。
Java的动态代理比代理的思想更向前迈进了一些,因为它可以动态地创建代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。
示例代码:
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 + ",arg:" + args);
if (args != null){
for (Object arg :
args) {
System.out.print(" " + arg);
}
}
return method.invoke(proxied, args);
}
}
public class SimpleDynamicProxyDemo {
public static void consumer(Interface iface){
iface.doSomething();
iface.doSomethingElse("ABCDEFG");
}
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);
}
}
/*
out:
RealObject:doSomething
RealObject:doSomethingElse:ABCDEFG
**** Proxy :class com.sun.proxy.$Proxy0.method:public abstract void TypeInfo.ProxyDemo.SimpleProxyDemo.Interface.doSomething(),arg:null
RealObject:doSomething
**** Proxy :class com.sun.proxy.$Proxy0.method:public abstract void TypeInfo.ProxyDemo.SimpleProxyDemo.Interface.doSomethingElse(java.lang.String),arg:[Ljava.lang.Object;@433c675d
ABCDEFGRealObject:doSomethingElse:ABCDEFG
*/
通过调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要得到一个加载器,一个你希望该代理实现的接口列表(不是类或抽象类),以及InvocationHandler接口的实现。动态代理可以将所有调用重定向到调用处理器,因此通常会调用处理器的构造器传递给一个“实际”对象的引用,从而使得调用处理器在执行其中介任务时,可以将请求转发。
invoke()方法中传递进来了代理对象,以防你需要区分请求的来源,但是在许多情况下,并不关心这一点。然而,在invoke()内部,在代理上调用方法时需要格外小心,因为接口的调用将被重定向为对代理的调用。
通常,你会执行被代理的操作,然后使用Method.invoke()将请求转发给被代理对象,并传入必须参数。这样虽然看起来有些受限,就像只能执行泛化操作一样。但是,可以通过传递其它的参数,来过滤某些方法的调用。
示例代码:
public interface SomeMethods {
void boringOne();
void interesting(String arg);
void boringTwo();
void boringThree();
}
public class Implementation implements SomeMethods {
@Override
public void boringOne() {
System.out.println("boringOne");
}
@Override
public void interesting(String arg) {
System.out.println("interesting:" + arg);
}
@Override
public void boringTwo() {
System.out.println("boringTwo");
}
@Override
public void boringThree() {
System.out.println("boringThree");
}
}
public class MethodSelector implements InvocationHandler {
private Object proxied;
public MethodSelector(Object proxied) {
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("interesting")){
System.out.println("Proxy detected the interesting method");
}
return method.invoke(proxied, args);
}
}
public class SelectingMethods {
public static void main(String[] args) {
SomeMethods proxy = (SomeMethods) Proxy.newProxyInstance(
SomeMethods.class.getClassLoader(),
new Class[]{SomeMethods.class},
new MethodSelector(new Implementation())
);
proxy.boringOne();
proxy.boringTwo();
proxy.boringThree();
proxy.interesting("ABCEDFG");
}
}
关于代理模式
为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用。