类型信息主要讲述的是:在运行时类型信息使得你可以在程序运行时发现和使用类型信息
也就是 RTTI(运行时类型识别)
如何在运行时识别对象和类的信息?主要有两种方式:一种是传统的RTTI,它假定我们在编译时已经知道了所有的类型;另一种是反射机制,它允许我们在运行时发现和使用类的信息。
为什么需要RTTI?
答:java中需要在运行时知道对象的具体类型,所以需要用到RTTI
类型信息在运行时如何表示?
由class对象的特殊对象完成,它包含类的相关信息。这里是怎么存呢?①Class.forName("对象名"),这样会返回一个Class对象,②还有一种方法就是:对象.class,具有同样的效果,这种方法叫:类的字面常量。③然而实际java存储类相关信息是使用了一个叫泛化的Class引用(Class<Integer> intclass=int.class) 限定Class引用必须是Integer类型。
类型转换前要先做检查
目前RTTI形式有:传统的类型装换(会抛类型转换异常)、代表对象类型的Class对象(用Class存储对象信息)
RTTI在java中第三中形式是instanceof,告诉我们对象是不是某个特定类型的实例,并返回boolean值
InstanceOf与Class的等价性
查询类型信息时,可以用InstanceOf与Class,那么两者有什么区别?
(x InstanceOf 类A)(x.getClass==类A.class)
InstanceOf比较的是你是不是这个类A或是不是这个类A的派生类,而Class比较则是确切的问你是不是这个类A。
什么是反射?
能够分析类能力的程序(java.lang.reflect)
通过反射我们能对一个类知道哪些内容?如下表所示
类名 | 作用 |
Field | 代表类的成员变量(成员变量也称为类的属性)。 get() set()方法读取和修改与Field对象关联的字段 |
Method | 代表类的方法。 用invoke()方法调用与此类关联的方法 |
Constructor | 代表类的构造方法。 |
反射用法例子:
public static void main(String[] args) { try { String name = "com.reflect.Employee"; Class c = Class.forName(name); /** * c.getModifiers():返回此类或接口以整数编码的 Java 语言修饰符。 * Modifier.toString将整数编码转换成字符串 例如 public class Employee{} * 返回class往前部分public public final class Employee{} 返回class往前部分public * final */ String modifier = Modifier.toString(c.getModifiers()); /** * 返回构造器数组,该对象反映此 Class 对象所表示的类或接口的指定构造方法。 */ Constructor[] constructor = c.getDeclaredConstructors(); for (Constructor c1 : constructor) { System.out.println(c1.getName());// 构造器方法名:com.reflect.Employee for (int i = 0; i < c1.getParameterTypes().length; i++) { System.out.println(c1.getParameterTypes()[i]);// 构造器参数类型 } System.out.println(); } /** * 返回方法数组,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。 */ Method[] method = c.getDeclaredMethods(); for (Method m1 : method) { System.out.println(m1.getReturnType());// 方法返回值类型 System.out.println(m1.getName());// 方法名 System.out.println(Modifier.toString(m1.getModifiers()));// 方法修饰符 // m1.getParameterTypes():返回方法参数数组 for (int i = 0; i < m1.getParameterTypes().length; i++) { System.out.println(m1.getParameterTypes()[i]); } } /** * 返回域对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段 */ Field[] field = c.getDeclaredFields(); for (Field f1 : field) { System.out.println(f1.getType());// 变量类型 System.out.println(f1.getName());// 变量名 System.out.println(Modifier.toString(f1.getModifiers()));// 变量修饰符 } } catch (ClassNotFoundException e) { e.printStackTrace(); } } |
运行时使用反射分析对象
public static void main(String[] args) throws Exception { String str = "hello"; // 实例化一个String类对象 String s = str; // 用于后面的比较测试 // 打印字符串和hashCode编码 System.out.println(str + "::" + str.hashCode());// hello::99162322 //开始反射动作 Class<?> cls = String.class; Field value = cls.getDeclaredField("value");//获取value这个域 value.setAccessible(true);//开启运行时动态反射, // 反射取得str对象的字符数组 char[] arr = (char[]) value.get(str); // 修改字符数组的内容 arr[0] = 's'; // 打印字符串和hashCode编码 System.out.println(str + "::" + str.hashCode());// sello::99162322 // 比较两次是否相同 System.out.println(s == str);// true } |
反射:运行时的类信息
通过反射能在运行时确定匿名对象的类信息,开头有说过RTTI可以识别对象和类的信息,传统RTTI就是默认运行前已经知道了所有类型,但是假设你获取一个指向某个并不存在你程序空间的对象的引用,或者在网络链接上获得一串字节,并告诉你这个字节是一个类,那怎样才能使用这些类型的类呢?
这时反射就起了作用,Class类和reflect类库一起对反射概念进行了支持,反射可以在运行时获取该类的域、方法、构造器,这样,匿名对象的类信息就能在运行时被确定下来。
RTTI与反射的区别?
RTTI编译时类型必须已知。编译器在编译时打开和检查 .class文件
对于反射,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。
java代理
代理是基本设计模式之一,用来提供额外或不同的操作,
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"+arg); proxied.somethingElse(arg); } } public class ProxyDemo { //一、consumer参数是一个Interface,它不知道具体是RealObject还是SimpleProxy,因为两者都实现Interface public static void consumer(Interface iface){ iface.doSomething(); iface.somethingElse("aaa"); } public static void main(String[] args) { //二、这里consumer接收的是一个RealObject对象,所以运行RealObject的doSomething和somethingElse方法 consumer(new RealObject()); //三、这里consumer接收的是一个SimpleProxy对象, // 但是SimpleProxy对象中的方法用到了RealObject对象的的doSomething和somethingElse方法 consumer(new SimpleProxy(new RealObject())); } } |
上面这个例子主旨目的是想要通过SimpleProxy对象去调用RealObject两个实际方法,目的是提供额外的或不同的操作,比如代码中打印的两句话,灵活一点的话,这时我们可以在SimpleProxy对象的方法中做一些额外的操作,这种想法就叫代理。代理通常充当一个中间人的角色,好比我想买个包,但是我不太熟悉包的质量,所以想找个代购帮我买,代购帮我买的时候会帮我查看包的质量,并帮我把包买好。
动态代理
class DynmicProxyHandler implements InvocationHandler{ private Object proxied; public DynmicProxyHandler(Object proxied) { this.proxied = proxied; } @Override /** * 动态代理可以将所有调用重定向到调用处理器(下面的invoke方法), * 因此通常会向调用处理器的构造器传递一个实际对象的引用(上面的proxied), * 从而使得调用处理器在执行其中介任务时,将请求转发 */ 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 o:args) System.out.println(" "+o); //请求转发,调用proxied对象的method方法 return method.invoke(proxied,args); } }public class SimpleDynamicProxy { public static void consumer(Interface iface){ iface.doSomething(); iface.somethingElse(" aaa "); } public static void main(String[] args) { RealObject real=new RealObject(); consumer(real); //创建动态代理。需要一个类加载器、该代理实现的接口、以及InvocationHandler接口的实现 Interface proxy= (Interface) Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynmicProxyHandler(real)); consumer(proxy); } } |
空对象
当你使用内置对象为null表示缺少对象时,必须在每次引用时都得测试是不是null,不然就有可能报空指针异常,但是有时候空对象思想会很有用,比如我有一个空对象(这里说的空对象不是指A a=null,后面会讲如何实现空对象),他可以接受任何传递给他对象消息,但是这个空对象并不作为,通过这种方式我可以假设所有对象都是有效的,所以就不需要去对他进行判空。
空对象例子:
//创建一个标记接口,所有空对象都要实现这个接口 public interface Null { } |
interface Operation {//操作 String description();//描述 void command();//指令 } |
interface Robot{//机器人 String name();//名字 String model();//模型 List<Operation> operations();//描述机器人行为能力 class Test{ public static void test(Robot robot){ //判空 if (robot instanceof Null) System.out.println("【NULL ROBOT】"); System.out.println("robot name: "+robot.name()); System.out.println("robot model: "+robot.model()); for (Operation operation:robot.operations()){ System.out.println(operation.description()); operation.command(); } } } } |
class NullRobotProxyHandler implements InvocationHandler{//定义一个空的机器人的代理 private String nullName; private Robot proxied =new NullRobot(); //构造器,传入一个继承Robot对象的引用 public NullRobotProxyHandler(Class<? extends Robot> type) { nullName=type.getSimpleName()+" NULL Robot"; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(proxied,args); } class NullRobot implements Null,Robot{//定义一个空的机器人 public String name() { return nullName; } public String model() { return nullName; } public List<Operation> operations() { return Collections.emptyList(); } } } |
class SnowRemovalRobot implements Robot{//定义一个扫雪的机器人 private String name; public SnowRemovalRobot(String name) {this.name = name;} public String name() {return name;} public String model() {return "扫雪机器人一号";} public List<Operation> operations() { return Arrays.asList(new Operation() { public String description() { return name+" 会扫雪"; } public void command() { System.out.println(name+" 正在扫雪"); } },new Operation() { public String description() { return name+" 会除冰"; } public void command() { System.out.println(name+" 正在除冰"); } }); } } |
class Test{ public static Robot newNullRobot(Class<? extends Robot> type){ return (Robot) Proxy.newProxyInstance(NullRobotProxyHandler.NullRobot.class.getClassLoader(), new Class[]{Null.class,Robot.class}, new NullRobotProxyHandler(type)); } public static void main(String[] args) { Robot robot1=new SnowRemovalRobot("扫雪号"); Robot.Test.test(robot1); Robot robot2=newNullRobot(SnowRemovalRobot.class); Robot.Test.test(robot2); } } |
输出: robot name: 扫雪号 robot model: 扫雪机器人一号 扫雪号 会扫雪 扫雪号 正在扫雪 扫雪号 会除冰 扫雪号 正在除冰 【NULL ROBOT】 robot name: SnowRemovalRobot NULL Robot robot model: SnowRemovalRobot NULL Robot |
总结:
RTTI允许通过匿名基类的引用来发现类型信息,但不可以滥用RTTI,面向对象的目的是让我们凡是可以使用的地方都是用多态,但要在必需的时候使用RTTI。