盒子
盒子
文章目录
  1. Class对象
    1. 类字面常量
    2. 泛化的Class引用
    3. 新的转型语法

类型信息

运行时类型信息使得你可以在程序运行时发现和使用类型信息

方法包括:

  • 传统的RTTI(Run-Time Type Identification)
  • 在运行时,识别一个对象的类型。

  • 反射机制
  • 可以获取和使用所有的方法和属性信息

    获取所有方法,构造器和属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    cl = Class.forName("Reflect.BeReflect");
    Constructor constructor=cl.getConstructor();

    System.out.println(constructor.toString());

    Object obj = constructor.newInstance();

    Method[] methods=cl.getMethods();
    //包括私有方法
    Method[] priMethods=cl.getDeclaredMethods();
    for(Method method:methods){

    System.out.println(method.toString());

    }
    for(Method method:priMethods){

    System.out.println(method.toString());

    }

    Field[] fields=cl.getDeclaredFields();
    for(Field f:fields){

    //param:被调用类的实例
    System.out.println(f.get(obj));

    }

    调用方法和属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //param:方法名,参数类型
    Method m=cl.getDeclaredMethod("setA",int.class);
    Object[] args=new Object[]{1};
    //设置为可操作私有方法
    method.setAccessible(true);
    //param:被调用类实例,要传的参数值
    method.invoke(obj,args);

    //param:属性名
    Field f=cl.getDeclaredField("a");
    f.setAccessible(true);
    //param:被调用类的实例,要设置的属性值
    f.set(obj,2);


    面向对象的最基本目的是:
    让代码只操纵对基类的引用,多态也是面向对象编程的基本目标

    Class对象

    Java使用Class对象来执行其RTTI

    每当编写并且编译一个新类就会产生一个Class对象,而为了生成这个类的对象,Java虚拟机(JVM)将会使用被称为类加载器的子系统。

    类加载器子系统包含一条类加载器链,但只有一个原生类加载器即可信类,包括java API类,

    类从加载到虚拟机到卸载的生命周期为:
    类从加载到虚拟机到卸载的生命周期

    解析阶段也可能会在初始化后才执行,以为动态绑定提供可能。

  • 动态绑定
  • 在程序运行时根据具体对象的类型进行绑定,动态绑定的过程为:

    1. JVM提供对象的实际类型的方法表
    2. 虚拟机搜索方法签名
    3. 调用方法

  • 静态绑定

  • 在程序执行前方法就被绑定,Java方法中只有static,final,private和构造方法是属于静态绑定。

    类的初始化

    静态变量先于静态代码块初始化

    类初始化的条件为:

    1. 创建类的实例
    2. 访问类的静态变量
    3. 访问类的静态方法
    4. 反射

    当初始化的子类有父类时先初始化其父类,调用类常量不会初始化该类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class ClassInfo {
    public ClassInfo() {
    }
    public static void main(String...args){
    Test.str="main";
    System.out.println("执行完Test.str=\"main\"后");
    Test test=new Test();
    }
    }
    class Test{
    public static String str="str";
    public Test() {
    System.out.println("Test");
    }
    static{
    System.out.println("static");
    }
    {
    System.out.println("normal");
    }
    }

    static
    执行完Test.str=”main”后
    normal
    Test

    由此可见当访问Test类的静态成员时即完成对Test类的加载,并在加载期间执行了静态代码块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class SingleTon {
    private static SingleTon singleTon = new SingleTon();
    public static int count1;
    public static int count2 = 0;

    private SingleTon() {
    count1++;
    count2++;
    }

    public static SingleTon getInstance() {
    return singleTon;
    }
    }

    public class Test {
    public static void main(String[] args) {
    SingleTon singleTon = SingleTon.getInstance();
    System.out.println("count1=" + singleTon.count1);
    System.out.println("count2=" + singleTon.count2);
    }
    }

    count1=1
    count2=0

    当调用getInstance方法获取对象的引用时,会初始化count1,count2为0,实例化SingleTon静态对象,然后调用构造方法,将count1和count2自加1,最后在main方法中访问静态变量

    加载

    1. 通过一个类的全限定名来获取定义此类的二进制字节流。
    2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    3. 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

    验证
    文件格式验证、元数据验证、字节码验证和符号引用验证。

    准备
    是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。

    解析
    解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

    符号引用: 以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。

    直接引用: 直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,如果有了直接引用,那么引用的目标必定已经在内存中存在。

    类字面常量

    类字面常量是对class对象引用的另一种方法,如FancyToy.Class

    由于其在编译阶段就会受到检查,所以更安全。

    对于基本数据类型的包装器类还有一个标准字段TYPE:

    boolean.class Boolean.TYPE
    short.Class Short.TYPE

    当使用.class来创建Class对象的引用时,该Class对象不会被初始化。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class Class1{
    public static final String str="Class1_str";
    public static String str_static="Class1.str_static";
    static{
    System.out.println("Class1_static");
    }
    }
    class Class2{
    public static final String str="Class2_str";
    static{
    System.out.println("Class2_static");
    }
    }
    public class Classer {
    public static void main(String...args) {
    Class class1=Class1.class;
    System.out.println(Class1.str);
    System.out.println(Class1.str_static);
    System.out.println();
    try {
    Class class2=Class.forName("thinkinjava.Class2");
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    }
    System.out.println(Class2.str);
    }
    }

    Class1_str
    Class1_static
    Class1.str_static

    Class2_static
    Class2_str

    static final作为编译器常量,不需要初始化后便能读取,但访问static域不是final值时会强制实现初始化。

    为了使用类的准备工作要经历三个步骤:

    1. 加载,由类加载器执行,该步骤将查找字节码(通常在classpath所指定的路径中查找),并从这些字节码中创建一个Class对象。
    2. 链接,验证类中的字节码,为静态域分配内存空间,并且如果必需的话将解析这个类创建的其它类的所以引用。
    3. 初始化,如果该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化块。

    泛化的Class引用

    如:

    1
    Class<Integer> intClass=int.class

    通过使用泛型语法可以强制编译器执行额外的类型检查

    创建一个Class引用,限定为Number类型的任何子类型:

    1
    2
    Class<? extends Number> numberClass=int.class;
    numberClass=double.class;

    新的转型语法

    1
    2
    3
    4
    5
    Building b=new House();
    Class<House> hs=House.class;
    House h=hs.cast(b);
    //or:
    h=(House)b;

    #

    支持一下
    扫一扫,支持Grooter
    • 微信扫一扫
    • 支付宝扫一扫