RTTI(Run-Time Type Identification):运行时类型识别。
从数组中取出元素是RTTI最基本的使用形式。
Class对象包含了与类有关的信息。
每一个类都有一个Class对象,在编写并编译后,被保存在同名的.class文件中。
而需要生成该类的对象时,则是由JVM使用”类加载器”的子系统来加载。所有的类是在其第一次使用(第一次创建对象或使用相应的静态成员或方法)时,动态加载到JVM当中。
类加载器首先检查该类的Class对象是否已经加载,如尚未加载,则默认的类加载器会根据类名查找.class文件,在经验证后载入内存,以用于创建该类的所有对象。
package com.mzm.chapter14;
/**
* 构造器实际上是类的静态方法
* new操作符创建对象可看作是对类的静态成员的引用
*
*/
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("com.mzm.chapter14.Gum");
} catch (ClassNotFoundException e) {
System.out.println("Couldn't find Gum");
}
System.out.println("After Class.forName(\"Gum\")");
new Cookie();
System.out.println("After creating Cookie");
}
}
class Candy{
static {
System.out.println("Loading Candy");
}
}
class Gum{
static {
System.out.println("Loading Gum");
}
}
class Cookie{
static {
System.out.println("Loading Cookie");
}
}
Class.forName()方法是根据这个类的类全名(包名+类名)获取Class对象。这一可以不必通过创建该类的对象的方式来获取相应的Class对象。如果根据给定的类全名找不到该类,则会抛出ClassNotFoundException异常。
如果在已经持有一个类的对象的情况下,可以通过这一对象的getClass()获取所属类的Class对象。
Class对象有一系列方法
getName():获取该类的类全名(包名 + 类名);
getSimpleName():获取该类的类名;
getCanonicalName():获取该类的类全名;
getInstances():获取该类实现的所有接口对应的Class对象;
getSuperClass():获取该类的父类的Class对象;
newInstance():创建该类的对象,使用该法创建该类对象时,该类必须有默认的构造器。
还可以根据类字面常量来获取一个类的Class对象。如:
ClassName.class
类字面常量可用于普通类、接口、数组以及基本数据类型。此外,基本数据类型的包装类,还有一个标准字段TYPE,是对应基本数据类型的Class对象的引用。
基本数据类型类字面常量 | 对应包装类的TYPE字段 |
---|---|
boolean.class | Boolean.TYPE |
char.class | Character.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 |
与使用Class.forName()方法不同,使用类字面常量获取Class对象,不会自动初始化Class对象。
package com.mzm.chapter14;
import java.util.Random;
/**
*
*/
public class ClassInitialization {
public static Random rand = new Random(47);
public static void main(String[] args) throws ClassNotFoundException {
//不会立即执行初始化
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);
//立即执行初始化
Class initable3 = Class.forName("com.mzm.chapter14.Initable3");
System.out.println("After creating Initable3 ref");
System.out.println(Initable3.staticNonFinal);
}
}
class Initable{
static final int staticFinal = 47;
static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
static {
System.out.println("Initializing Initable");
}
}
class Initable2{
static int staticNonFinal = 147;
static {
System.out.println("Initializing Initable2");
}
}
class Initable3{
static int staticNonFinal = 74;
static {
System.out.println("Initializing Initable3");
}
}
泛化的Class引用,为其所指向的类进行了限定。而普通的Class引用则没有此限定。如:
Class<Integer> genericIntClass = Integer.class;
// genericIntClass = Double.class;
让泛化的Class引用放松限制:
Class> intClass = int.class;
intClass = double.class;
限定范围
Class extends Number> bounded = int.class;
可以指向任何继承自Number类的子类的Class对象。利用这一Class对象的newInstance()方法创建的对象的类型是确定的,即Integer类型。
Class super Integer> up = Number.class;
可以指向任何衍生此类的父类的Class对象。但是利用这一Class对象的newInstance()方法创建的对象是Object类型。
cast()方法接受参数对象,并将其转型为Class引用的类型。
package com.mzm.chapter14;
/**
*
*/
public class ClassCasts {
public static void main(String[] args){
Building b = new House();
Class houseType = House.class;
House h = houseType.cast(b);
h = (House) b;
}
}
class Building{
}
class House extends Building{
}
RTTI的三种形式:
1) 传统的类型转化,由RTTI确保类型转换的正确性,如果错误,则抛出ClassCastException;
2) 代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。
3) 关键字instanceof,返回一个boolean值,用于判断对象是不是某一类的实例。一般要查找某种类型,可以使用instanceof来遍历所有对象。
Class.isInstance()方法提供了一种动态测试对象的途径。
Class<Type> type = Type.class;
boolean b = type.isInstance(obj);
Class.isAssignableFrom(),接受一个Class对象作为参数,判断传入的Class对象的类型是否是其后代。
工厂方法设计模式,将对象的创建工作交给类自己去完成。
package com.mzm.chapter14;
/**
*
*/
public interface Factory {
T create();
}
任何需要使用工厂来创建对象的类,可在其内部包含一个静态的Factory类,并实现这一接口,然后返回该类对象即可。
在查询类型信息时,使用instanceof与Class对象的isInstance()方法所得结果一致。
反射主要是希望获取不在自身程序空间中的类的信息。
Class类与java.lang.reflect类库一起对反射提供了支持。java.lang.reflect类库包括Field、Method以及Constructor类,每个类都实现了Member接口。
RTTI与反射的区别:RTTI在编译时打开和检查.class文件,反射是在运行时打开和检查.class文件。
package com.mzm.chapter14;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
/**
*
* Created by 蒙卓明 on 2017/11/12.
*/
public class ShowMethods {
private static String usage = "usage:\n" +
"ShowMethods qualified.class.name\n" +
"To show all methods in class or:\n" +
"ShowMethods qualified.class.name word\n" +
"To search for methods involving 'word'";
//使用正则表达式匹配方法中的命名修饰词,如"java.reflect."
private static Pattern p = Pattern.compile("\\w+\\.");
public static void main(String[] args) {
if (args.length < 1) {
System.out.println(usage);
System.exit(0);
}
int lines = 0;
try {
Class> c = Class.forName(args[0]);
Method[] methods = c.getMethods();
Constructor[] ctors = c.getConstructors();
if (args.length == 1) {
for (Method method : methods) {
//删除命名修饰词 System.out.println(p.matcher(method.toString()).replaceAll(""));
}
for (Constructor ctor : ctors) {
System.out.println(p.matcher(ctor.toString()).replaceAll(""));
}
lines = methods.length + ctors.length;
} else {
for (Method method : methods) {
if(method.toString().indexOf(args[1]) != -1){
System.out.println(p.matcher(method.toString()).replaceAll(""));
lines++;
}
}
for (Constructor ctor : ctors) {
if(ctor.toString().indexOf(args[1]) != -1){
System.out.println(p.matcher(ctor.toString()).replaceAll(""));
lines++;
}
}
}
} catch (ClassNotFoundException e) {
System.out.println("No such class: " + e);
}
}
}
代理提供了额外的或不同操作,而插入的用来代替”实际”对象的对象。其充当中间人的角色。
package com.mzm.chapter14;
/**
* 代理Demo
*
*/
public class SimpleProxyDemo {
public static void consumer(Interface iface){
iface.doSomething();
iface.somrthingElse("bonobo");
}
public static void main(String[] args){
consumer(new RealObject());
consumer(new SimpleProxy(new RealObject()));
}
}
interface Interface {
void doSomething();
void somrthingElse(String arg);
}
class RealObject implements Interface {
@Override
public void doSomething() {
System.out.println("doSomething");
}
@Override
public void somrthingElse(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 somrthingElse(String arg) {
System.out.println("SimpleProxy somethingElse " + arg);
proxied.somrthingElse(arg);
}
}
首先是必须存在一个接口,真实对象和代理对象实现同一接口,代理对象包含一个给定的接口成员,在构造方法中将真实对象传入,这是静态代理。
动态代理,可以动态地创建代理并动态地处理对所代理方法的调用。在动态代理上所有调用都会重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。
package com.mzm.chapter14;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 动态代理Demo
*
*/
public class SimpleDynamicProxy {
public static void consumer(Interface iface){
iface.doSomething();
iface.somrthingElse("bonobo");
}
public static void main(String[] args){
//真实对象
RealObject real = new RealObject();
consumer(real);
//代理对象
//使用Proxy的newProxyInstance()方法创建,需要三个参数:
//类加载器,可从已加载对象获取
//代理实现的接口列表
//调用处理器,即InvocationHandler接口的一个实现
Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(),
new Class[]{Interface.class}, new DynamicProxyHandler(real));
consumer(proxy);
}
}
/**
* 调用处理器
*/
class DynamicProxyHandler implements InvocationHandler {
//真实对象
private Object proxied;
public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
/**
* 揭示调用的类型并确定相应的对策
* @param proxy 真实对象
* @param method 代理方法
* @param args 方法参数
* @return 调用真实对象的对应方法
* @throws Throwable
*/
@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(" " + arg);
}
}
return method.invoke(proxied, args);
}
}
package com.mzm.chapter14;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 使用动态代理探查指定方法
* Created by 蒙卓明 on 2017/11/12.
*/
public class SelectingMethods {
public static void main(String[] args){
SomeMethods proxy = (SomeMethods) Proxy.newProxyInstance(SomeMethods.class.getClassLoader(),
new Class[]{SomeMethods.class}, new MethodSelector(new Implemetation()));
proxy.boring1();
proxy.boring2();
proxy.interesting("bonobo");
proxy.boring3();
}
}
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);
}
}
interface SomeMethods {
void boring1();
void boring2();
void interesting(String arg);
void boring3();
}
class Implemetation implements SomeMethods {
@Override
public void boring1() {
System.out.println("boring1");
}
@Override
public void boring2() {
System.out.println("boring2");
}
@Override
public void interesting(String arg) {
System.out.println("interesting " + arg);
}
@Override
public void boring3() {
System.out.println("boring3");
}
}
空对象是策略模式的一种特例。它与null的区别在于,它是一个实际的对象,只是其相应的字段取默认值而已。
区别空对象与其他实际对象,可以定义一个标志接口,比如Null:
package com.mzm.chapter14;
/**
*
*/
public interface Null {
}
package com.mzm.chapter14;
/**
* Created by 蒙卓明 on 2017/11/14.
*/
public class Human {
public final String first;
public final String last;
public final String address;
public Human(String first, String last, String address){
this.first = first;
this.last = last;
this.address = address;
}
public String toString() {
return "Human: " + first + " " + last + " " + address;
}
/**
* 人的空对象
*/
public static class NullHuman extends Human implements Null {
public NullHuman() {
super("None", "None", "None");
}
public String toString() {
return "NullHuman";
}
}
/**
* 人的空对象只有一个
*/
public static final Human Null = new NullHuman();
}
判断该类的对象是不是一个空对象,只需要查看它的类型是否是Null类型即可(instanceof)。
空对象还可以交由动态代理来创建。首先必须要有具体对象和空对象抽象出来的接口:
package com.mzm.chapter14;
import java.util.List;
/**
* 具体对象和空对象的抽象接口
*
*/
public interface Robot {
String name();
String model();
List operations();
class Test {
public static void test(Robot r) {
if(r instanceof Null){
System.out.println("[Null Robot]");
}
System.out.println("Robot name: " + r.name());
System.out.println("Robot model: " + r.model());
for(Operation operation : r.operations()){
System.out.println(operation.description());
operation.command();
}
}
}
}
package com.mzm.chapter14;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.List;
/**
* 通过动态代理创建按空对象
* Created by 蒙卓明 on 2017/11/16.
*/
public class NullRobot {
public static Robot newNullRobot(Class extends Robot> type) {
return (Robot) Proxy.newProxyInstance(NullRobot.class.getClassLoader(), new Class[]{Null.class, Robot.class},
new NullRobotProxyHandler(type));
}
public static void main(String[] args){
Robot[] robots = {new SnowRemovalRobot("SnowBee"), newNullRobot(SnowRemovalRobot.class)};
for(Robot robot : robots){
Robot.Test.test(robot);
}
}
}
class NullRobotProxyHandler implements InvocationHandler {
private String nullName;
private Robot proxied = new NRobot();
NullRobotProxyHandler(Class extends Robot> type) {
nullName = type.getSimpleName() + " NullRobot";
}
private class NRobot implements Null, Robot {
@Override
public String name() {
return nullName;
}
@Override
public String model() {
return nullName;
}
@Override
public List operations() {
return Collections.emptyList();
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(proxied, args);
}
}
空对象的判断还是一样查看其是否是标志接口Null的对象。
接口并非是对解耦的一种无懈可击的保障。
假定有一个A接口,一个类B实现了这个接口,但是同时还有另外的方法。
package com.mzm.chapter14;
/**
*
*/
public interface A {
void f();
}
package com.mzm.chapter14;
/**
* Created by 蒙卓明 on 2017/11/16.
*/
public class InterfaceViolation {
public static void main(String[] args){
A a = new B();
a.f();
//a.g();
System.out.println(a.getClass().getSimpleName());
if(a instanceof B){
B b = (B) a;
b.g();
}
}
}
class B implements A {
@Override
public void f() {
}
public void g() {
}
}
显然可以通过判断类型的方法,来调用B类对象中,除A接口定义方法之外的方法(g()方法)。
要想实现这一目的,可以使用包访问权限。
package com.mzm.chapter14;
import com.mzm.chapter14.packageaccess.HiddenC;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
*
*/
public class HiddenImplementation {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException,
InvocationTargetException {
A a = HiddenC.makeA();
a.f();
System.out.println(a.getClass().getSimpleName());
//设定包访问权限后,正常方式已经无法访问
/*if(a instanceof C) {
C c = (C) a;
c.g();
}*/
//但是通过反射,可以访问所有方法
callHiddenMethod(a, "g");
callHiddenMethod(a, "u");
callHiddenMethod(a, "v");
callHiddenMethod(a, "w");
}
static void callHiddenMethod(Object a, String methodName) throws NoSuchMethodException,
InvocationTargetException, IllegalAccessException {
Method g = a.getClass().getDeclaredMethod(methodName);
g.setAccessible(true);
g.invoke(a);
}
}
但是如果知道方法名,通过反射可以调用所有方法。
即便是发布编译后的代码,仍然可以通过反编译的手段获取方法名,然后通过反射的方式调用所有方法。
即便是将接口实现为私有内部类也是一样,同理匿名类也是一样。
package com.mzm.chapter14;
import java.lang.reflect.InvocationTargetException;
/**
*
*/
public class InnerImplementation {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
A a = InnerA.makeA();
HiddenImplementation.callHiddenMethod(a, "g");
HiddenImplementation.callHiddenMethod(a, "u");
HiddenImplementation.callHiddenMethod(a, "v");
HiddenImplementation.callHiddenMethod(a, "w");
}
}
class InnerA {
private static class Cc implements A {
@Override
public void f() {
System.out.println("public C.f()");
}
public void g() {
System.out.println("public C.g()");
}
void u() {
System.out.println("package C.u()");
}
protected void v() {
System.out.println("protected C.v()");
}
private void w() {
System.out.println("private C.w()");
}
}
public static A makeA() {
return new Cc();
}
}
利用反射可以获取和修改类的私有字段,当然final修饰的无法修改。
package com.mzm.chapter14;
import java.lang.reflect.Field;
/**
*
*/
public class ModifyingPrivateFields {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
WithPrivateFinalField pf = new WithPrivateFinalField();
System.out.println(pf);
Field f = pf.getClass().getDeclaredField("i");
f.setAccessible(true);
System.out.println("f.getInt(pf): " + f.getInt(pf));
f.setInt(pf, 47);
System.out.println(pf);
//final修饰的字段无法修改
f = pf.getClass().getDeclaredField("s");
f.setAccessible(true);
System.out.println("f.get(pf): " + f.get(pf));
f.set(pf, "No, you're not!");
System.out.println(pf);
f = pf.getClass().getDeclaredField("s2");
f.setAccessible(true);
System.out.println("f.get(pf): " + f.get(pf));
f.set(pf, "No, you're not!");
System.out.println(pf);
}
}
class WithPrivateFinalField {
private int i = 1;
private final String s = "I'm totally safe";
private String s2 = "Am I safe?";
public String toString(){
return "i = " + i + ", " + s + ", " + s2;
}
}