Java之类型信息

  • Post author:
  • Post category:java




Java之类型信息



Class对象

  • Class对象是用来创建类的所有的常规对象的,它同样是一个对象。
  • 在Java中,每个类都有一个对应的Class对象,这个对象提供了一些方法来操作这个类,比如实例化对象。因此,在创建一个类的对象之前,需要先创建这个类的Class对象。这个Class对象就像是这个类的代表,通过它我们可以了解这个类的属性和方法,并且可以实例化出这个类的对象。
  • 更通俗一点:“由于Java中一切皆对象,当你需要创建一个类的对象时,类当前还只是在文件中,它也需要一个对象来表示它,所以先得创建类的Class对象,等于说我们有了一个操作这个类的工具,通过这个工具我们就可以创建对象了。”
  • Class对象有4种方法可以获得:

    • ①使用类的类字面常量(class literal):使用.class语法可以在编译时获取Class对象。

      •  Class<?> clazz = String.class;
        
    • ②调用对象的getClass()方法:通过对象的getClass()方法可以获取对象所属类的Class对象。

      •  Class<?> clazz = "Hello".getClass();
        
    • ③使用Class.forName()方法:通过Class.forName()方法可以在运行时获取指定类的Class对象。

      •  Class<?> clazz = Class.forName("java.lang.String");
        
    • ④使用ClassLoader类的loadClass()方法:通过ClassLoader类的loadClass()方法可以在运行时根据类的二进制名称获取Class对象。

      •  Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
        
  • Class对象是第一次被使用时,被类加载加载到JVM的。第一次被使用包括:

    • ①通过new创建类的实例时:

      •  Dog dog = new Dog();
        
    • ②使用类的静态变量或静态方法:

      •  String name = Dog.name;
         Dog.run();
        
    • ③使用反射创建类的实例时。
    • ④当主动调用Class.forName()方法时:

      •  Class.forName("Dog");
        
    • 注:在加载一个类之前,会检查是否已经被加载,如果没有被加载过,才会进行加载。
  • Class类的一些常用的方法:

    • forName():根据类的全限定名生成一个Class对象。
    • getName():获取全限定名。
    • getSimpleName():获取全限定名。
    • getCanonicalName():获取简称。
    • isInterface():判断其是否是一个接口。
    • getInterfaces():获取类的接口。
    • getSuperclass():获取类的直接父类。
    • getTypeParameters():用于获取泛型类或接口的泛型声明所声明的类型参数。

      •  public class Example<T, U> {
         	public static void main(String[] args) {
         		TypeVariable[] typeParams = Example.class.getTypeParameters();
         		for (TypeVariable typeParam : typeParams) {
         		System.out.println("Type parameter: " + typeParam.getName());
         		Type[] bounds = typeParam.getBounds();
         		if (bounds.length > 0) {
         			System.out.println("  Bounds:");
         			for (Type bound : bounds) {
         				System.out.println("    " + bound.getTypeName());
         			} else {
         				System.out.println("  No bounds.");
         			}
         		}
         	}
         }
         
         输出:
         Type parameter: T
         No bounds.
         Type parameter: U
         No bounds.
        
    • isInstance():检查当前Class对象的泛型类型是不是指定对象的类型。

      •  Class c = Dog.class;
         Dog d = new Dog("lala");
         if (c.isInstance(d)) {
         	System.out.println("c 的类型是 Dog");
         } else {
         	System.out.println("c 的类型不是 Dog");
         }
         
         输出:
         c 的类型是 Dog
        
    • isAssignableFrom():判断传入的类的对象是否可以转换成当前类的对象。

      •  class Animal {}
        
         class Dog extends Animal {}
        
         public class Main {
         	public static void main(String[] args) {
         		// 判断Dog是否可以转换成为Animal的实例
         		boolean result1 = Animal.class.isAssignableFrom(Dog.class);
         		System.out.println("Dog是否可以转换成为Animal的实例:" + result1);
        
         		// 判断Animal是否可以转换成为Dog的实例
         		boolean result2 = Dog.class.isAssignableFrom(Animal.class);
         		System.out.println("Animal是否可以转换成为Dog的实例:" + result2);
         	}
         }
         
         输出:
         Dog是否可以转换成为Animal的实例:true
         Animal是否可以转换成为Dog的实例:false
        
    • getFields():用于获取该类及其父类中所有的public属性,包括静态和非静态属性。
    • getDeclaredFields():用于获取该类自身声明的所有属性,包括public、protected、default和private属性,但不包括父类的属性。
    • getMethods():用于获取该类及其父类类中所有的public方法。
    • getConstructors():用于获取该类自身定义的public构造方法。
    • getDeclaredMethods():用于获取该类自身定义的所有方法,包括public、protected、default和private方法。

      • 注:要想获取该类及其父类所有的方法,可以通过递归先获取到父类的Class对象,再通过该对象获取到对应的所有方法。
    • getDeclaredConstructors():用于获取该类自身定义的所有构造方法,包括public、protected、default和private构造方法。

      •  try {
         	Pattern p = Pattern.compile("\\w+\\.");
         	Class<?> c = Class.forName("java.lang.Object");
         	Method[] methods = c.getMethods();
         	Constructor[] ctors = c.getConstructors();
         	System.out.println("方法:");
         	for (Method method : methods) {
             	System.out.println(p.matcher(method.toString()).replaceAll(""));
         	}
         	System.out.println("构造器:");
         	for (Constructor ctor : ctors) {
             	System.out.println(p.matcher(ctor.toString()).replaceAll(""));
         	}
         } catch (ClassNotFoundException e) {
         	System.out.print("No such class: " + e);
         }
         
         输出:
         方法:
         public final native void wait(long) throws InterruptedException
         public final void wait(long,int) throws InterruptedException
         public final void wait() throws InterruptedException
         public boolean equals(Object)
         public String toString()
         public native int hashCode()
         public final native Class getClass()
         public final native void notify()
         public final native void notifyAll()
         构造器:
         public Object()
        
  •  class People {}
    
     interface MusicBehavior {}
     interface SportsBehavior {}
     interface DanceBehavior {}
    
     class Student extends People implements MusicBehavior, SportsBehavior,DanceBehavior {}
    
     	public class JavaLearnClass {
     		public static void main(String[] args) {
    
         	try {
         		//这里必须使用类的全限定名(包含包名)
             	Class<Student> studentClass = (Class<Student>) Class.forName("com.eos.javalearn.Student");
             	//也可以类字面常量类生成Class对象,这样在编译时就能知道这个类存不存在,而不用放在try块中。
             	Class studentClass = Student.class;
             	
             	String name = studentClass.getName();
             	String simpleName = studentClass.getSimpleName();
             	String canonicalName = studentClass.getCanonicalName();
             	System.out.println("类名 = " + name + ", 简称 = " + simpleName + ", 全称 = " + canonicalName + ", 是接口吗:" + studentClass.isInterface());
             	
             	//获取该类实现的接口
             	for (Class clazz : studentClass.getInterfaces()) {
                 	String name1 = clazz.getName();
                 	String simpleName1 = clazz.getSimpleName();
                 	String canonicalName1 = clazz.getCanonicalName();
                 	System.out.println("接口名 = " + name1 + ", 简称 = " + simpleName1 + ", 全称 = " + canonicalName1 + ", 是接口吗:" + clazz.isInterface());
             	}
             	
             	//获取该类的直接父类
             	Class superClass = studentClass.getSuperclass();
             	String name1 = superClass.getName();
             	String simpleName1 = superClass.getSimpleName();
             	String canonicalName1 = superClass.getCanonicalName();
             	System.out.println("类名 = " + name1 + ", 简称 = " + simpleName1 + ", 全称 = " + canonicalName1 + ", 是接口吗:" + superClass.isInterface());
             	
             	//获取该类的实例对象,该方法由于强制目标类要有一个无参构造方法,因此不实用
             	Object o = studentClass.newInstance();
             	//下面是推荐的构造类的实例的方法
             	Constructor<?> constructor = studentClass.getConstructor();
             	Student student = (Student) constructor.newInstance();
         	} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e | NoSuchMethodException | InvocationTargetException e) {
             	e.printStackTrace();
         	}
         }
     }
     
     输出:
     类名 = com.eos.javalearn.Student, 简称 = Student, 全称 = com.eos.javalearn.Student, 是接口吗:false
     接口名 = com.eos.javalearn.MusicBehavior, 简称 = MusicBehavior, 全称 = com.eos.javalearn.MusicBehavior, 是接口吗:true
     接口名 = com.eos.javalearn.SportsBehavior, 简称 = SportsBehavior, 全称 = com.eos.javalearn.SportsBehavior, 是接口吗:true
     接口名 = com.eos.javalearn.DanceBehavior, 简称 = DanceBehavior, 全称 = com.eos.javalearn.DanceBehavior, 是接口吗:true
     类名 = com.eos.javalearn.People, 简称 = People, 全称 = com.eos.javalearn.People, 是接口吗:false
    
  • 类字面量:

    • 用于生成类的Class对象。

    • Student.class

    • 类字面量不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。

      • 基本类型的包装器类的类字面量还有一个标准字段TYPE与之对应。
      •  Class<Integer> integerClass1 = int.class;
         Class<Integer> integerClass2 = Integer.class;
         Class<Integer> integerClass3 = Integer.TYPE;
        
      • class TYPE
        boolean.class Boolean.TYPE
        char.class Char.TYPE
        byte.class Byte.TYPE
        short.class Short.TYPE
        int.class Integer.TYPE
        long.class Long.TYPE
        float.class Float.TYPE
        double.class Double.TYPE
        void.class Void.TYPE
      • 注:建议还是使用class。
    • 使用类字面量创建Class对象时,是不会初始化该Class对象的。

      • 类的准备工作分3个步骤:

        • ①加载:由类加载器执行。查找字节码,并从这些字节码中创建一个Class对象;
        • ②链接:会验证类中的字节码,为静态字段分配存储空间。如有必要的话,会解析这个类创建的对其他类的引用;
        • ③初始化:如果该类有超类,则对其初始化,执行静态初始化器和初始化块。
      • 这种方式创建Class对象会将初始化延迟到对静态方法或者非静态常量进行首次引用的时候进行。

        • 验证:

          •  class Test1 {
             	static final int A = 1;
             	static final int B = JavaLearnClass.random.nextInt(100);
             	static {
             		System.out.println("Init Test1");
             	}
             }
            
             class Test2 {
             	static int A = 1;
             	static {
             		System.out.println("Init Test2");
             	}
             }
            
             class Test3 {
             	static int A = 1;
             	static {
             		System.out.println("Init Test3");
             	}
             }
             
             public class JavaLearnClass {
            
             	public static Random random = new Random(47);
            
             	public static void main(String[] args) throws ClassNotFoundException {
             		Class test1 = Test1.class;
             		System.out.println("调用Test1.class完成");
             		int a = Test1.A;
             		System.out.println("调用Test1.A完成, a = " + a);
             		int b = Test1.B;
             		System.out.println("调用Test1.B完成, b = " + b);
            
             		int c = Test2.A;
             		System.out.println("调用Test2.A完成, c = " + c);
            
             		Class test3 = Class.forName("com.eos.javalearn.Test3");
             		System.out.println("调用Class.forName Test3完成");
             	}
             }
             
             输出:
             调用Test1.class完成
             调用Test1.A完成, a = 1
             Init Test1
             调用Test1.B完成, b = 58
             Init Test2
             调用Test2.A完成, c = 1
             Init Test3
             调用Class.forName Test3完成
            
          • Test1对比Test3发现:

            • 类字面量不会引起对Class对象的初始化,而Class.forName()会。
          • Test1对比Test2发现:

            • 静态常量不会引起对Class对象的初始化,而静态变量会。
          • 通过Test1.B发现:

            • 静态变量被读取之前,要求先进行链接(为这个字段分配存储空间)和初始化(初始化该存储空间)。
  • 泛化的Class引用:

    • 对于Class类型,可以指定泛化的类型。

      •  //这里虽然Integer是Number的子类,但是Class<Integer>不是Class<Number>的子类,所以不能像如下使用
         //Class<Number> numberClass = int.class;
         //可以使用通配符?,它表示任何事物
         Class<?> numberClass = int.class;
         numberClass = double.class;
         numberClass = Dog.class;
        
         //或者使用通配符和extends关键字来创建一个范围,
         Class<? extends Number> numberClass1 = int.class;
         //这里已经限定了numberClass1的泛型类型为继承自Number的类型
         //numberClass1 = Dog.class;
        
  • instanceof:

    • 使用该关键字来测试对象是不是某个特定的类型。

      •  Dog dog = new Dog("lala");
         if (dog instanceof Dog) {
         	System.out.println("dog 的类型是 Dog");
         } else {
         	System.out.println("dog 的类型不是 Dog");
         }
         
         输出:
         dog 的类型是 Dog
        
    • 不管是instanceof关键字还是isInstance()方法,它们都指的是“你是这个类吗,或者你是这个类的子类吗”。而通过equals()或==比较Class对象时,并不考虑继承,它只能是这个类型或要么不是。



反射

  • Class类与java.lang.reflect类库一起对反射进行了支持。

    • reflect类库包含了Filed、Method以及Constructor类。每个类都实现了Member接口,这些类型的对象是由JVM在运行时创建的,用来表示未知类里对应的成员。

      • Constructor用来创建新的对象,使用get()或set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。
    • Constructor常用的方法:

      • newInstance()方法:创建该类的新实例,等价于调用new关键字创建对象。
      • getParameterTypes()方法:获取构造方法的参数类型数组。
      • getName()方法:获取构造方法的名称。
      • getModifiers()方法:获取构造方法的可见性修饰符。
      • isVarArgs()方法:判断构造方法是否支持可变参数。
      • getExceptionTypes()方法:获取构造方法可能抛出的异常类型数组。
      • isSynthetic()方法:判断构造方法是否是由编译器自动生成的合成方法。
    • Field常用的方法:

      • get()方法:获取该属性在指定对象中的值。
      • set()方法:设置该属性在指定对象中的值。
      • getName()方法:获取该属性的名称。
      • getType()方法:获取该属性的类型。
      • getModifiers()方法:获取该属性的可见性修饰符。
      • isAccessible()方法:判断该属性是否可访问。
      • setAccessible()方法:设置该属性是否可访问。
    • Method常用的方法:

      • invoke()方法:调用该方法并获取返回值。
      • getName()方法:获取该方法的名称。
      • getParameterTypes()方法:获取该方法的参数类型数组。
      • getReturnType()方法:获取该方法的返回值类型。
      • getModifiers()方法:获取该方法的可见性修饰符。
      • isAccessible()方法:判断该方法是否可访问。
      • setAccessible()方法:设置该方法是否可访问。



版权声明:本文为weixin_41092496原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。