类型信息(2):Class对象(下)

一、类字面常量

    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.Random;

class Initable {
	static final int staticFinal = 47;
	static final int staticFinal2 = ClassInitialization.r.nextInt(1000);

	static {
		System.out.println("Initializing Initable");
	}
}

class Initable2 {
	static int staticNonFinal = 147;

	static {
		System.out.println("Initializing Initable2");
	}
}

class Initable3 {
	static int staticNonFinal = 47;

	static {
		System.out.println("Initializing Initable3");
	}
}

public class ClassInitialization {
	public static Random r = new Random();

	public static void main(String[] args) throws ClassNotFoundException {
		Class initable = Initable.class;
		System.out.println("After creating Initable ref");
		System.out.println(Initable.staticFinal);
		System.out.println(Initable.staticFinal2);
		System.out.println(Initable2.staticNonFinal);
		Class initable3 = Class.forName("Initable3");
		System.out.println("After creating Initable3 ref");
		System.out.println(Initable3.staticNonFinal);
	}
}

    初始化有效地实现了尽可能的“惰性”。从对initable引用的创建中可以看到,仅使用.class语法来获得对类的引用不会引发初始化。但是,为了产生Class引用,Class.forName()立即就进行了初始化,就像在对initable3引用的创建中所看到的。

    如果一个static final值是“编译期常量”,就像Initable.staticFinal那样,那么这个值不需要对Initable类进行初始化就可以被读取。但是,如果只是将一个域设置为static和final的,还不足以确保这种行为,例如,对Initable.staticFinal2的访问将强制进行类的初始化,因为它不是一个编译期常量。

    如果一个static域不是final的,那么对它访问时,总是要求在它被读取之前,要先进行链接(为这个域分配空间)和初始化(初始化该存储空间),就像在对Initable2.staticNonFinal的访问中所看到的那样。

二、泛化的Class引用

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

    但是,java SE5的设计者们看准机会,将它的类型变得更具体了一些,而这是通过允许你对Class引用所指向的Class对象的类型进行限定而实现的,这里用到了泛型语法。在下面的实例中,两种语法都是正确的:

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

    普通的类引用不会产生警告信息,你可以看到,尽管泛型类引用只能赋值为所指向其声明的类型,但是普通的类引用可以被重新赋值为指向任何其他的Class对象。通过使用泛型语法,可以让编译器强制执行额外的类型检查。

    如果你希望稍微放松一些这种限制,应该怎么办呢?乍一看,好像你应该能够执行类似下面这样的操作:

Class<Number> genericNumberClass = int.class;

    这看起来似乎是起作用的,因为Integer继承自Number。但是它无法工作,因为Integer Class对象不是Number Class对象的子类。为了在使用泛化的Class引用时放松限制,我使用了通配符,它是java泛型的一部分。通配符就是“?”,表示“任何事物”。因此,我们可以在上例的普通Class引用中添加通配符,并产生相同的结果:

public class WildcardClassReferences {
	public static void main(String[] args) {
		Class<?> intClass = int.class;
		intClass = double.class;
	}
}

    在java SE5中,Class<?>优于平凡的Class,即便它们是等价的,并且平凡的Class如你所见,不会产生编译器警告信息。Class<?>的好处是它表示你并非是碰巧或者由于疏忽,而使用了一个非具体的类引用,你就是选择了非具体的版本。

    为了创建一个Class引用,它被限定为某种类型,或该类型的任何子类型,你需要将通配符与extends关键字相结合,创建一个范围。因此,与仅仅声明Class<Number>不同,现在做如下声明:

public class BoundedClassRenferences {
	public static void main(String[] args) {
		Class<? extends Number> bounded = int.class;
		bounded = double.class;
		bounded = Number.class;
	}
}

    向Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查,因此如果你操作有误,稍后立即就会发现这一点。在使用普通Class引用,你不会误入歧途,但是如果你确实犯了错误,那么直到运行时你才会发现它,而这显得很不方便。

    下面的示例使用了泛型类语法,它存储了一个类引用,稍后又产生了一个List,填充这个List的对象是使用newInstance()方法,通过该引用生成的:

import java.util.ArrayList;
import java.util.List;

class CountedInteger {
	private static long counter;
	private final long id = counter++;

	public String toString() {
		return Long.toString(id);
	}
}

public class FilledList<T> {
	private Class<T> type;

	public FilledList(Class<T> type) {
		this.type = type;
	}

	public List<T> create(int nElements) {
		List<T> result = new ArrayList<>();
		for (int i = 0; i < nElements; i++)
			try {
				result.add(type.newInstance());
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		return result;
	}

	public static void main(String[] args) {
		FilledList<CountedInteger> fl = new FilledList<CountedInteger>(CountedInteger.class);
		System.out.println(fl.create(15));
	}
}

    注意,这个类必须假设与它一同工作的任何类型都具有一个默认的构造器(无参构造器),并且如果不符合该条件,你将得到一个异常。编译器对该程序不会产生任何警告信息。

    当你将泛型语法用于Class对象时,会发生一件很有趣的事情:newInstance()将返回该对象的确切类型,而不仅仅只是在ToyTest.java中看到的基本的Object。这种某种程度上有些限制:

public class GenericToyTest {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException {
		Class<FancyToy> ftClass = FancyToy.class;
		FancyToy fancyToy = ftClass.newInstance();
		Class<? super FancyToy> up = ftClass.getSuperclass();
		// Class<Toy> up2 = ftClass.getSuperclass();
		Object obj = up.newInstance();
	}
}

    如果你手头的是超类,那编译器将只允许你声明超类引用是“某个类,它是FancyToy超类”,就像在表达式Class<? Super FancyToy>中所看到的,而不会接受Class<Toy>这样的声明。这看上去显得有些怪,因为getSuperClass()方法返回的是基类(不是接口),并且编译器在编译期就知道它是什么类型了——在本例中就是Toy.class——而不仅仅只是“某个类,它是FancyToy超类”。不管怎样,正是由于这种含糊性,up.newInstance()的返回值不是精确类型,而只是Object。

三、新的转型语法

    java SE5还添加了用于Class引用的转型语法,即cast()方法:

class Building {
}

class House extends Building {
}

public class ClassCasts {
	public static void main(String[] args) {
		Building b = new House();
		Class<House> houseType = House.class;
		House h = houseType.cast(b);
		h = (House) b;
	}
}

    cast()方法接受参数对象,并将其转型为Class引用的类型。当然,如果你观察上面的代码,则会发现,与实现了相同功能的main()中最后一行相比,这种转型好像做了很多额外的工作。型的转型语法对于无法使用普通转型的情况显得非常有用,在你编写泛型代码时,如果你存储了Class引用,并希望以后通过这个引用来执行转型,这种情况就会时有发生。这被证明是一种罕见的情况。

    在java SE5中另一个没有任何用处的新特性就是Class.asSubclass(),该方法允许你将一个类对象转型为更加具体的类型。

如果本文对您有很大的帮助,还请点赞关注一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

游王子og

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值