第十四章 类型信息

前言

在开始介绍类型信息之前一定要区分一些概念:

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);
    }

}
View Code

 

输出结果:

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);
        }
    }

}
View Code

 

输出结果:

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());
    }

}
View Code

 输出结果:

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));
    }

}
View Code

输出结果:

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();
        }
        
    }
}
View Code

 输出结果:

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();
        }
        
    }

}
View Code

 输出结果:

[1, test]

结果分析:可以看到利用反射,能够在编译器毫无防备的情况下狸猫换太子。

getMethod(): 可以获得Class对象的指定方法所代表的Method对象。

Method.invoke():可以调用该方法表示的对象。

 

posted @ 2019-09-08 18:58  卑微芒果  Views(243)  Comments(0Edit  收藏  举报