Thinking in Java -- 类型信息
来源:互联网 发布:淘宝注册店铺单能做吗 编辑:程序博客网 时间:2024/06/01 10:10
运行时类型信息(RTTI:Run-Time Type Identification)使得你可以在程序运行时发现和使用类型信息
RTTI
为什么需要 RTTI
通常,我们希望大部分代码尽可能的少了解对象的具体类型,仅仅与对象家族中的一个通用表示打交道。这样的代码会更容易写,更容易读,且更容易维护;设计也更容易实现、理解和改变。所以“多态”是面向对象编程的基本目标。
来看书上的一个例子:
package typeinfo;import java.util.ArrayList;import java.util.List;/** * Created by wwh on 16-3-21. */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 = new ArrayList<Shape>(); shapeList.add(new Circle()); shapeList.add(new Triangle()); shapeList.add(new Square()); for(Shape shape : shapeList) { shape.draw(); } }}
很简单的一个例子,我们可以通过基类(抽象类)的引用控制派生类,这样基类(抽象类)就是此类的一个通用的方法,不同的需求放在派生类中来实现。
实际运用可参考这篇文章 重构:运用Java反射加多态 “干掉” switch
Class 对象
类型信息在运行时是通过 Class 对象来完成的,Class 对象保存着同名类的元信息,它用来创建类的实例对象。
元信息包含:类的所有方法代码,类的静态成员等。
每个类都有个同名的 Class 对象,在起初用命令行时,javac xx.java 编译后就会生成 .class 文件(保存着对应类的 Class 数据)。在实际使用中,JVM 通过类加载系统来生成此类对象。
所有的类都是在第一次使用时,动态加载到 JVM 中(惰性加载)。所谓第一次使用指的是:当程序创建第一个对类的静态成员的引用时。
package typeinfo;/** * Created by wwh on 16-3-21. */class Candy { static { System.out.println("Loading Candy"); }}class Gum { static { System.out.println("Loading Gum"); }}class Cookie { static { System.out.println("Loading Cookie"); }}public class SweetShop { public static void main(String[] args) throws ClassNotFoundException { /* 类的构造方法也是静态方法,第一次加载调用 static 代码块 */ new Candy(); try{ /* forName 是显示加载类 */ Class.forName("typeinfo.Gum"); }catch (ClassNotFoundException e) { System.out.println("Not found Gum"); } new Cookie(); /* 第二次加载没有调用 static 代码块*/ new Candy(); }}
类的静态代码块只有第一次使用该类时才会调用,从上面例子我们可以得知在第一次实例化类或者通过 Class.forName() 都可以加载类的 Class,一旦某个类的 Class 被载入内存,它就被用来创建这个类的所有对象。
所有 Class 对象都属于 Class 类,我们可以通过 Class 对象的 forName ()方法来获取不同类的 Class。
从上图得知我们可以通过类的 Class 对象在运行时获取很多相关信息。
类字面常量
Java 还提供了另一种方法来生成 Class 对象的引用,即类字面常量。如直接使用 Gum.class 而不用通过 forName() 方法。比较高效,而且更简单,安全。
注意:当使用 .class 创建对 Class 对象的引用时,不会自动初始化该 Class 对象。forName() 方法会立即初始化。
为了使用类而做的准备工作包含三个步骤:
1.加载:由类加载器执行嗯。查找字节码,并从字节码中创建一个 Class 对象。
2.链接:验证类中字节码,为静态域分配存储空间。
3.初始化:如果该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化块。
如果类中的一个值是编译期常量,那么不需要加载就可以读取。如 static final 修饰的变量。如果仅仅是 static 修饰的变量,对他访问时,要先进行链接和初始化。
泛化的 Class 引用
如果我们直接定义一个 Class 引用,这样会缺少类型检查,不会产生编译器警告。如下
public class WildcardClassReferences { public static void main(String []args){ Class intClass = int.class; intClass = double.class; }}
此时我们可以使用泛化的 Class
public class WildcardClassReferences { public static void main(String []args){ Class<?> intclass = int.class; intclass = double.class; }}
Class <?> 可以表明我们本身就是要选择一个非具体的类引用。除此之外,我们还可以为 Class 引用限定为某种类型,或该类型的任何子类,将通配符与 extends 关键字配合创建一个范围(这样可以提供编译器类型检查,不会到运行时才发现错误)。
Class<? extends Number> bounded = int.classbounded = double.class;bounded = Number.class
类型转换前先做检查
在 Java 中,编译器允许自由地做向上转型的赋值操作,而不需要任何显式的转型操作。但如果不使用显式的类型转换,编译器不允许你执行向下转型赋值。除非告知编译器额外的信息,以确定你是某种特定类型。我们通过 instanceof 来实现告知编译器相关信息。
/* 判断 triangle 是否是 shape 的一个实例,如果不使用 instanceof,则会抛出 ClassCastException 异常 */if(triangle instanceof Shape) { triangle.draw();}
注意:只能将 instanceof 与命名类型进行比较,不能将它与 Class 对象作比较。
instanceof 和 Class 的等价性
instanceof 表明“你是这个类吗,你是这个类的基类吗?”,而用 == 比较 Class 则不会考虑继承,仅考虑是这个类型或者不是。
class Base {}class Derived extends Base{}public class InstanceofAndClass { public static void main(String[] args) { Base b = new Base(); Derived d = new Derived(); System.out.println((b instanceof Base)); System.out.println((d instanceof Base)); System.out.println((b.getClass() == Base.class)); /* 编译错误 Java:不可比较的类型 */ //System.out.println((d.getClass() == Base.class)); }}
反射:运行时类信息
如果我们不确定某个对象的类型,RTTI 能够告诉我们。但有一个限制:该对象的类型必须编译时已知,这样才能用 RTTI 来识别它。但假设程序运行中我们从磁盘中或者网络上获取到一个类名字,需要创建该类的对象或者获取该类的相关信息该怎么做呢?在 Java 中,我们通过反射来实现。
PS:反射是什么
我的理解反射是程序在运行时可以动态识别并控制自己的一种机制。
Class 类与 java.lang.reflect 类库一起对 Java 反射的概念进行了支持,类库包含 Field、Method 和 Constructor 类。这些类型的对象由 JVM 在运行时创建,表示未知的类里对应的字段、方法和构造器。我们可以通过 get() 和 set() 方法可以读取和修改与 Field 对象关联的字段,用 invoke() 方法调用与 Method 对象关联的方法。使用 Constructor 创建新的对象。
RTTI 和 反射的区别:
RTTI:编译器在编译时打开和检查 .class 文件。
反射: .class 文件在编译时是不可获取的,所以在运行是打开和检查 .class 文件。
类方法提取器
来看一个例子,运行时通过命令行参数来获得未知对象的所有类方法,并调用第一个 HelloWorld 方法。
package typeinfo;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.regex.Pattern;/** * Created by wwh on 16-3-21. */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'"; private static Pattern p = Pattern.compile("\\w+\\."); public static void HelloWorld() { System.out.println("-------------------"); System.out.println("hello world"); System.out.println("-------------------"); } public static void main(String [] args){ if(args.length < 1) { System.out.println(usage); System.exit(1); } int lines = 0; try{ Class<?> c = Class.forName(args[0]); Method[] methods = c.getMethods(); /* 调用 hello world 方法*/ Method methodHello = methods[0]; methodHello.invoke(null); Constructor[] ctors = c.getConstructors(); 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; } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }}
命令行参数:> java ShowMethods typeinfo.ShowMethods
反射的威力
反射能做到许多平时我们做不到的事,来看看
假设我们现在有一个接口 A,B 来实现接口 A。通过 RTTI 发现 a 是被当作 B 来实现的。通过转型为 B,我们可以调用不在 A 中的方法。
public interface A { void f();}
package typeinfo;import typeinfo.interfacea.A;/** * Created by wwh on 16-3-21. */class B implements A { public void f() {} public void g() {}}public class InterfaceViolation { public static void main(String[] args) { A a = new B(); a.f(); if(a instanceof B) { B b = (B)a; b.g(); } }}
这样虽然可以访问 B 类的方法,但如果 A 和 B 是我们提供给别人使用(如上面的 IterfaceViolation)代码之间的耦合性就比较高了。
我们可以通过访问权限来限定其他人使用。
package typeinfo.packageaccess;import typeinfo.interfacea.A;/** * Created by wwh on 16-3-21. */class C implements A { 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 访问权限 */ protected void v() { System.out.println("protected C.v()"); } /* private 权限 */ private void w() { System.out.println("private C.w()"); }}public class HiddenC { public static A makeA() { return new C(); }}
使用:
package typeinfo;import typeinfo.interfacea.A;import typeinfo.packageaccess.HiddenC;/** * Created by wwh on 16-3-21. */public class HiddenImlementation { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { A a = HiddenC.makeA(); a.f(); System.out.println(a.getClass().getName()); //error:cannot find symbol 'C' //if(a instanceof C) { // C c = (C)a; // c.g(); //}}
通过反射来访问
package typeinfo;import typeinfo.interfacea.A;import typeinfo.packageaccess.HiddenC;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/** * Created by wwh on 16-3-21. */public class HiddenImlementation { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { A a = HiddenC.makeA(); a.f(); System.out.println(a.getClass().getName()); 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); /* 取消访问控制 * 值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。 * 值为 false 则指示反射的对象应该实施 Java 语言访问检查。 */ g.setAccessible(true); g.invoke(a); }}
从上图我们可以看出通过反射可以调用所有方法,包括 private 方法!
如果是接口是私有内部类呢?
package typeinfo;import typeinfo.interfacea.A;import java.lang.reflect.InvocationTargetException;/** * Created by wwh on 16-3-21. */class InnnerA { /* private 内部类 */ private static class C implements A { 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 C(); }}public class InnerImplementation { public static void main(String [] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { A a = InnnerA.makeA(); a.f(); System.out.println(a.getClass().getName()); HiddenImlementation.callHiddenMethod(a, "g"); HiddenImlementation.callHiddenMethod(a, "u"); HiddenImlementation.callHiddenMethod(a, "v"); HiddenImlementation.callHiddenMethod(a, "w"); }}
上图我们可以看出通过反射可以访问私有内部类的方法和成员。
如果是匿名类呢?
package typeinfo;import typeinfo.interfacea.A;import java.lang.reflect.InvocationTargetException;/** * Created by wwh on 16-3-21. */class AnonymousA { public static A makeA() { /* 匿名内部类 */ return new A() { public void f() { System.out.println("public C.f()"); } public void g() { System.out.println("public C.g()"); } void u() { System.out.println("void C.u()"); } protected void v() { System.out.println("protected C.v()"); } private void w() { System.out.println("private C.w()"); } }; }}public class AnonymousImplementation { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { A a = AnonymousA.makeA(); a.f(); System.out.println(a.getClass().getName()); HiddenImlementation.callHiddenMethod(a, "g"); HiddenImlementation.callHiddenMethod(a, "u"); HiddenImlementation.callHiddenMethod(a, "v"); HiddenImlementation.callHiddenMethod(a, "w"); }}
从上面的例子可以看出,无论是普通的类,还是 private 内部类,又或是匿名内部类,反射都可以访问其类的内部成员,包括 private 成员。
C++ RTTI
相对与 Java,C++对动态支持就弱多了,但也不一定是坏处,静态检查代码是值得的。
C++ 通过两种方式来支持 RTTI,如下
1.typeid:返回指针或引用所指对象的实际类型
2.dynamic_cast:将基类类型的指针或引用安全的转换为派生类的指针或引用。
对于带虚函数的类,在运行时执行RTTI操作符,返回动态类型信息;对于其他类型,在编译时执行RTTI,返回静态类型信息。
- Thinking in Java :类型信息
- Thinking in Java -- 类型信息
- Thinking in Java -- 类型信息RTTI
- Thinking in Java 整理笔记:类型信息
- thinking in java 第十四章 类型信息
- Thinking in java学习笔记-类型信息
- thinking in java 14章 类型信息
- thinking-in-java(14)类型信息
- thinking in java 14章 类型信息 学习笔记
- Thinking in Java 第14章 类型信息
- Thinking in Java——第十四章-类型信息
- 读thinking in java笔记(十三):类型信息
- Thinking in Java---类型信息和java反射机制学习笔记
- Thinking in java 基本类型方法重载
- thinking-in-java(19)枚举类型
- thingking in Java 类型信息
- Thinking in Java 第19章 枚举类型
- Thinking in Java——第十九章-枚举类型
- session的垃圾回收机制
- wamp完美支持asp
- 第11期《沈博绝丽》2016年3月刊
- 如何通过IIS设置301重定向
- \\r \\r\\n \\t的区别,是什么意思
- Thinking in Java -- 类型信息
- 跨浏览器设计–你必须得了解的浏览器默认样式(User Agent Stylesheet)
- PHP不用数据库怎么弄图片分页
- 78. Subsets
- 直接恢复DEDE(织梦系统)网站备份数据
- MSN、腾讯QQ、SKYPE、阿里旺旺网页在线客服源代码
- 淘宝IP地址库API接口 (PHP)通过ip获取地址信息
- 用PHP对文件夹进行访问权限设置(文件防止被下载设置)
- setTimeout设置为0的作用