第十四章 类型信息
前言
在开始介绍类型信息之前一定要区分一些概念:
1、静态类型语言与动态类型语言
静态类型语言:在编译期进行类型检查的语言(例如,Java)。(例如 int i = 3;)
动态类型语言:在运行期进行类型检查的语言(例如,JavaScript)。最明显的特征为变量没有类型值有类型(如,var=3;)
2、类型检查与类型转换(在Java中的区别)
类型检查:发生在编译期,检查方法是不是接收到了合适类型的参数,赋值是不是有合适类型的右值。
类型转换正确性检查:发生在运行期,检查一个类型变换到另外一个类型是不是正确合法的。举个栗子:
class A {} class B {} public class Test { public static void main(String[] args) { Object o = new A(); B b = (B)o; } }
像这种明显偷梁换柱的不安全转换,但是,编译器却发现不了。因为编译器不知道对象o的实际类型,只知道o的编译时静态类型Object,只有当运行期A.class文件载入虚拟机,虚拟机进行类型转换正确性检查才会知道o的实际类型是A,将A赋值给B类型不正确,抛出运行时异常。
3、清楚Java的语言性质
Java是静态类型的语言,因为其在编译期接受静态的类型检查。Java是动态类型安全(动态 类型安全 这样断句)的语言,因为其在运行期进行类型转换正确性检查。
4、RTTI与反射的区别
RTTI:运行时类型识别,可以看出它只是用来查询Class对象的一些简单信息。
反射:功能强大,可以绕过编译器调用方法。
最本质的区别是RTTI在编译时打开和检查class文件,反射是在运行时打开和检查class文件。这和虚拟机加载class文件没有冲突。
一、代表类型信息的Class对象
简述:每一个类经过编译都会生成一个ClassName.class文件,这个.Class文件就是表示类信息的二进制字节码文件。运行时,虚拟机的类加载系统会加载这个class文件,并生成Class对象,将class文件中的常量池放入方法区的运行时常量池,Class对象就代表着类型信息,并提供访问运行时常量池的途径。
1、示例一:类的加载行为
class Candy { static int i = 11; static{ System.out.println("Loading Candy!"); } } class Cookie { static { System.out.println("Loading Cookie!"); } } class Gum { static final int i = 15; static { System.out.println("Loading Gum!"); } } public class Demo1 { public static void main(String[] args) { new Cookie(); System.out.println("Candy.i = " + Candy.i); System.out.println("Cookie.i = " + Gum.i); } }
输出结果:
Loading Cookie!
Loading Candy!
Candy.i = 11
Cookie.i = 15
结果分析:只有两种行为可以引起类的加载,一种创建此类及其子类的对象,另一种是引用此类的静态成员。有一种情况特殊,编译期常量不会引起类的加载行为(这样说不准确,只不过看起来像),例如 static final int i = 3;。类加载过程分为:加载,验证、准备、初始化。编译期常量在类加载过程的准备环节就已经有值可用,其他类变量要到类加载的初始化环节才会被赋予值,所以static{}不会执行。
2、示例二:Class类的基本接口
interface Pet {} interface Color {} class Dog implements Pet, Color {} class Mutt extends Dog {} public class Demo2 { static void printInfo(Class<?> c) { System.out.println("Class name: " + c.getName() + " is interface? [" + c.isInterface() + "]"); System.out.println("Canonical name: " + c.getCanonicalName()); System.out.println("Simple name: " + c.getSimpleName()); System.out.println("------------------------------------------"); } public static void main(String[] args) { Class<?> c = null; try { c = Class.forName("rtti.Mutt"); }catch(ClassNotFoundException e) { e.printStackTrace(); System.out.println("Can't find Mutt"); System.exit(1);//非零状态码表示异常终止 } printInfo(c); Class<?> up = c.getSuperclass(); Object o = null; try { o = up.newInstance(); }catch(InstantiationException|IllegalAccessException e) { } printInfo(o.getClass()); for(Class<?> face : up.getInterfaces()) { printInfo(face); } } }
输出结果:
Class name: rtti.Mutt is interface? [false] Canonical name: rtti.Mutt Simple name: Mutt ------------------------------------------ Class name: rtti.Dog is interface? [false] Canonical name: rtti.Dog Simple name: Dog ------------------------------------------ Class name: rtti.Pet is interface? [true] Canonical name: rtti.Pet Simple name: Pet ------------------------------------------ Class name: rtti.Color is interface? [true] Canonical name: rtti.Color Simple name: Color ------------------------------------------
结果分析:
Class.forName(String ClassName): 使用类的全限定类名来获得该类Class对象的引用,若该类尚未加载,则加载该类。
getClass(): 如果已经拥有该类对象,可使用继承自Object类的getClass()方法获得该类Class对象。
getName(): 返回此Class对象所表示类的名称。
getCanonicalName(): 返回此Class对象所表示类的全限定类名。
getSimple(): 返回简称。
getInterfaces(): 返回Class对象数组,代表此类实现了的接口。
getSuperclass(): 返回class对象数组,代表此类的基类。
newInstance(): 创建此类的实例,此类必须有默认构造方法,此方法抛出异常。
基本上所有返回class对象的方法,其返回值都是Class<?>,因为编译器是不知道这类方法返回的Class对象的具体类型的。
3、示例三:类字面常量
class B { static { System.out.println("Initializing B"); } } public class Demo3 { class A { { System.out.println("Initializing A"); } } public static void main(String[] args) { Class<A> c1 = A.class; System.out.println(c1.getName()); Class<?> c2 = null; try { c2 = Class.forName("rtti.B"); }catch(ClassNotFoundException e) { e.printStackTrace(); System.exit(1); } System.out.println(c2.getSimpleName()); } }
输出结果:
rtti.Demo3$A
Initializing B
B
结果分析:
可以明显的看出“.class”形式,比forName()更加高效。前者编译时,右值已知可以接受编译期检查;后者编译时,右值未知,还抛出异常。不过,".class"创建Class对象时不会进行类的初始化(前文说过,类加载过程中的一个阶段),forName()会进行类的初始化。
二、RTTI运行时类型识别
运行时类型识别的三种表现形式:(看一下就行了,个人觉得无关紧要)
(1)RTTI确保类型转换的正确性
(2)代表对象类型的Class对象
(3)instanceof关键字
1、示例一:instanceof与“==”
class Base {} class Derived extends Base {} public class Demo4 { public static void main(String[] args) { Derived d = new Derived(); System.out.println("d instanceof Base " + (d instanceof Base)); System.out.println("d instanceof Derived " + (d instanceof Derived)); System.out.println("Base.class.isInstance(d) " + Base.class.isInstance(d)); System.out.println("Derived.class.isInstance(d) " + Derived.class.isInstance(d)); System.out.println("d.getClass() == Base.class " + (d.getClass() == (Class<?>)Base.class)); System.out.println("d.getClass() == Derived.class " + (d.getClass() == (Class<?>)Derived.class)); } }
输出结果:
d instanceof Base true d instanceof Derived true Base.class.isInstance(d) true Derived.class.isInstance(d) true d.getClass() == Base.class false d.getClass() == Derived.class true
结果分析:instanceof与isInsatance()保持了类型的概念,x为子类示例结果也为true,即只要是同意类型即可。
三、功能强大的反射
1、示例一:反射基本接口的用法
import java.lang.reflect.*; import java.util.Scanner; import java.util.regex.*;; public class ShowMembers { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String s = scanner.next(); Pattern p = Pattern.compile("\\w+\\."); try { Class<?> c = Class.forName(s); System.out.println("Field:---------------------------------"); for(Field f : c.getDeclaredFields()) { System.out.println(p.matcher(f.toString()).replaceAll("")); } System.out.println("Constructors:----------------------------"); for(Constructor<?> con : c.getDeclaredConstructors()) { System.out.println(p.matcher(con.toString()).replaceAll("")); } System.out.println("Methods:------------------------------------------"); for(Method m : c.getDeclaredMethods()) { System.out.println(p.matcher(m.toString()).replaceAll("")); } }catch(Exception e) { e.printStackTrace(); }finally { scanner.close(); } } }
输出结果:
java.io.FilterOutputStream Field:--------------------------------- protected OutputStream out Constructors:---------------------------- public FilterOutputStream(OutputStream) Methods:------------------------------------------ public void write(byte[],int,int) throws IOException public void write(byte[]) throws IOException public void write(int) throws IOException public void close() throws IOException public void flush() throws IOException
第一行是输入
结果分析:
这是一个可以读取指定类中的字段、构造器以及方法的小程序。
getDeclaredFields():获取类中已经声明的字段,返回Field数组。
getDeclaredConstructors():获取类中已经声明的构造方法,返回Constructor<?>数组。
c.getDeclaredMethods():获取类中已经声明的方法,返回Method的数组。
Field类代表域、Constructor类代表构造方法、Method类代表方法。
这些基本的方法都属于Class类,是Class类对反射提供的支持。
2、使用反射越过编译器检查
import java.lang.reflect.*; import java.util.*; public class Reflect { public static void main(String[] args) { // TODO Auto-generated method stub List<Integer> l = new ArrayList<Integer>(); l.add(1); Class<?> c = l.getClass(); try { Method m = c.getMethod("add", Object.class); m.invoke(l, "test"); System.out.println(l); }catch(Exception e) { e.printStackTrace(); } } }
输出结果:
[1, test]
结果分析:可以看到利用反射,能够在编译器毫无防备的情况下狸猫换太子。
getMethod(): 可以获得Class对象的指定方法所代表的Method对象。
Method.invoke():可以调用该方法表示的对象。