Java类型信息

RTTI

RTTI: 在运行时,识别一个对象的类型。
RTTI (Run-time type information)
在编译时,将由容器和Java的泛型系统来强制确保这一点;而在运行时,由类型转换操作来确保这一点。

import java.util.*;

abstract class Shape{
	void draw() {
		System.out.println(this);
	}
}
class Circle extends Shape{
	public String toString(){
		return "Circel";
	}
}
class Square extends Shape{
	
}
public class Shapes {
	public static void main(String[] args) {
		List<Shape> shapeList = Arrays.asList(new Circle(),new Square());
		for(Shape shape : shapeList)
			shape.draw();
	}
}
/*
Circel
Square@54bedef2
*/

class对象

RTTI在Java中的工作是由称为Class对象的特殊对象完成的,它包含了与类相关的信息。
类是程序的一部分,每个类都有一个Class对象。每当编写并且编译了一个新类,就会产生一个Class对象(更确切地说,是被保存在一个同名的.class文件中)。
为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用被称为“类加载器”的子系统。

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) {
		System.out.println("inside main");
		new Candy();
		System.out.println("after creating Candy");
		try {
			Class.forName("Gum");
		}catch(ClassNotFoundException e) {
			System.out.println("Couldn't find Gum");
		}
		System.out.println("after Class.forName(\"Gum\")");
		new Cookie();
		System.out.println("After creating Cookie");
	}
}
/*
inside main
Loading Candy
after creating Candy
Loading Gum
after Class.forName("Gum")
Loading Cookie
After creating Cookie
*/

从输出中可以看到,Class对象仅在需要的时候才被加载,static初始化是在类加载时进行的。
Class包含很多有用的方法,下面是其中一部分示例:

package typeinfo.toys;
interface HasBatteries{}
interface Waterproof{}
interface Shoots{}

class Toy{
	Toy(){}
	Toy(int i){}
}

class FancyToy extends Toy implements HasBatteries,Waterproof,Shoots{
	FancyToy(){super(1);}
}

public class ToyTest {
	static void printInfo(Class cc) {
		System.out.println("Class name:"+cc.getName()+"Is interface?"+cc.isInterface());
		System.out.println("simple name:"+cc.getSimpleName());
		System.out.println("Canonical name: "+cc.getCanonicalName());
	}
	public static void main(String[] args) {
		Class c = null;
		try {
			c = Class.forName("typeinfo.toys.FancyToy");	//字符串必须使用全限定名(包含包名)
		}catch(ClassNotFoundException e){
			System.out.println("Can't find FancyToy");
			System.exit(1);
		}
		printInfo(c);
		for(Class face:c.getInterfaces()) {
			printInfo(face);
		}
		Class up = c.getSuperclass();
		Object obj = null;
		try {
			obj = up.newInstance();		//从Java9开始被弃用
		}catch(InstantiationException e) {
			System.out.println("Cannot instantiate");
			System.exit(1);
		}catch(IllegalAccessException e) {
			System.out.println("Cannot access");
			System.exit(1);
		}
		System.out.println(obj.getClass());
	}
}
/*
Class name:typeinfo.toys.FancyToyIs interface?false
simple name:FancyToy
Canonical name: typeinfo.toys.FancyToy
Class name:typeinfo.toys.HasBatteriesIs interface?true
simple name:HasBatteries
Canonical name: typeinfo.toys.HasBatteries
Class name:typeinfo.toys.WaterproofIs interface?true
simple name:Waterproof
Canonical name: typeinfo.toys.Waterproof
Class name:typeinfo.toys.ShootsIs interface?true
simple name:Shoots
Canonical name: typeinfo.toys.Shoots
class typeinfo.toys.Toy
*/
  • newInstance()方法从Java9开始被弃用

forName()方法在适当的try块中创建Class引用,并将其初始化指向FancyToyClass.在传递给forName()方法的字符串中必须使用全限定名(包含包名)。
getName()和getCanonicalName()方法用来产生全限定类名。
getName()方法用来产生不含包名的类名。
isInterface()方法用来判断这个Class对象是否表示某个接口。
getInterfaces()方法返回的是Class对象,它们表示在感兴趣的Class对象中所包含的接口。
getSuperclass()方法用来查询直接基类。

类字面常量

类字面常量是Java提供的生成Class对象引用的另一种方法。
例如:FancyToy.class;
这样做更简单、更安全,因为它在编译器就会收到检查(因此不需要置于try块语句中);并且不用对forName()方法进行调用,更加高效
类字面常量不仅可以应用于普通的类,也可以应用于接口、数组、以及基本数据类型;另外,对于基本数据类型的包装器类,还有一个标准字段TYPE。TYPE字段是一个引用,指向对应的基本数据类型的Class对象。如下所示:

等价于
boolean.classBoolean.TYPE
char.classCharacter.TYPE
byte.classByte.TYPE
short.classShort.TYPE
int.classInteger.TYPE
long.classLong.TYPE
float.classFloat.TYPE
double.classDouble.TYPE
void.classVoid.TYPE

建议使用“.class”的形式,以保持与普通类的一致性。

当使用.class来创建Class对象的引用的时候,不会自动地初始化该Class对象。为了使用类而做的准备工作实际包含三个步骤:

  1. 加载;由类加载器执行。该步骤将查找字节码(通常在classpath所指定的路径中查找,但这并非是必需的),并从这些字节码中创建一个Class对象。
  2. 链接;在链接阶段将验证类中的字节码,为静态域 分配存储空间,并且如果必需的话,将解析 这个类创建的对其他类的所有引用。
  3. 初始化;如果该类具有 超类,则对其进行初始化,执行静态初始化器和静态初始化块。
  • 初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行。
import java.util.*;

class A{
	static final int a = 47;
	static final int b = ClassInfoTest.rand.nextInt(1000);
	static {
		System.out.println("initializing A");
	}
}

class B{
	static final int a = 147;
	static {
		System.out.println("initializing B");
	}
}
public class ClassInfoTest {
	public static Random rand = new Random(47);
	public static void main(String[] args) throws Exception{
		Class a1 = A.class;
		System.out.println("After creating A ref");
		System.out.println(A.a);
		System.out.println(A.b);
		System.out.println();
		Class b1 = Class.forName("B");
		System.out.println("After creating B ref");
		System.out.println(B.a);
	}
}
/*
After creating A ref
47
initializing A
258

initializing B
After creating B ref
147
*/

仅使用.class语法来获得对类的引用不会引发初始化,但是使用Class.forName()方法的话就会立即对类进行初始化。

  • 如果一个值是“编译期常量”,那么这个值不需要对类进行初始化就可以被读取。
  • 如果不是,在读取这个值的时候就会强制进行类的初始化。
泛化的Class引用。

Class引用总是指向某个 Class对象,它可以制造类的实例,并包含可作用于这些实例的所有方法代码。
它还包含该类的静态成员,因此,Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。

public class GenericClass {
	public static void main(String[] args) {
		Class intClass = int.class;
		Class<Integer> genericIntClass = int.class;
		genericIntClass = Integer.class;
		//	genericIntClass = double.class;
		intClass = double.class;
	}
}

泛型类引用只能赋值为指向其声明的类型,但是普通的类引用可以被重新赋值为指向任何其他的Class对象。
通过泛型语法,可以让编译器强制执行额外的类型检查。

为了在使用泛化的Class对象引用时放松限制,可以使用通配符“?”。表示任何事物

	Class<?> intClass = int.class;
	intClass = double.class;

想要限定一个Class引用为某种类型,或该类型的任何子类型,就需要将通配符与extends关键字相结合创建一个范围。

		Class<? extends Number> bounded = int.class;
		bounded = double.class;
		bounded = Number.class;

向Class引用添加泛型语法的原因仅仅是为了提供编译器类型检查。

instanceof与Class的等价性

package typeinfo.toys;
class Base{}
class Derived extends Base{
	
}
public class Type {
	static void test(Object x) {
		System.out.println("Testing x of type"+x.getClass());
		System.out.println("x instanceof Base"+(x instanceof Base));
		System.out.println("x instanceof Derived" + (x instanceof Derived));
		System.out.println("Base.isInstance(x)" + Base.class.isInstance(x));
		System.out.println("Derives.isInstance(x)" + Derived.class.isInstance(x));
		System.out.println("x.getClass() == Base.class"+ (x.getClass() == Base.class));
		System.out.println("x.getClass() == Derived.class" + (x.getClass() == Derived.class));
		System.out.println("x.getClass().equals(Derived.class)" + x.getClass().equals(Derived.class));
		System.out.println("x.getClass().equals(Base.class)" + x.getClass().equals(Base.class));
	}
	public static void main(String[] args) {
		test(new Base());
		test(new Derived());
	}
}
/*
Testing x of typeclass typeinfo.toys.Base
x instanceof Basetrue
x instanceof Derivedfalse
Base.isInstance(x)true
Derives.isInstance(x)false
x.getClass() == Base.classtrue
x.getClass() == Derived.classfalse
x.getClass().equals(Derived.class)false
x.getClass().equals(Base.class)true
Testing x of typeclass typeinfo.toys.Derived
x instanceof Basetrue
x instanceof Derivedtrue
Base.isInstance(x)true
Derives.isInstance(x)true
x.getClass() == Base.classfalse
x.getClass() == Derived.classtrue
x.getClass().equals(Derived.class)true
x.getClass().equals(Base.class)false
*/

test()方法使用了两种形式的instanceof作为参数来执行类型检查 。然后获取Class引用,并用==和equals()来检查Class对象是否相等。
instanceof和isInstance()生成的结果完全一样,equals()和==也一样。
但是这两组测试所得出的结论却不同。instanceof保持了类型的概念,它指的是“你是这个类吗,或者你是这个类的派生类吗?”
而如果用==比较实际的Class对象,就没有考虑继承——它或者是这个确切的类型,或者不是。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值