1 简介
类是程序的一部分,每个类都有一个 Class 对象。换言之,每当编写并且编译了一个新类,就会产生一个 Class 对象(更恰当地说,是被保存在一个同名的 .class 文件中)。
所有的类都是在对其第一次使用时,动态加载到 JVM 中的。
当程序创建第一个对类的静态成员的引用时,就会加载这个类。
使用 new 操作符创建类的新对象也会被当做对类的静态成员的引用。
一旦某个类的 Class 对象被载入内存,它就被用来创建这个类的所有实例。
2 Class 类的基本应用
package com.test.class_obj;
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");
System.out.println("------------------");
try {
Class.forName("Gum");
} catch (ClassNotFoundException e) {
System.out.println("Couldn't find Gum");
}
System.out.println("------------------");
System.out.println("After Class.forName(\"Gum\")");
new Cookie();
System.out.println("After creating Cookie");
}
}
Class 对象仅在需要的时候才被加载,static 初始化使在类加载时进行的。
Class.forName() 返回的是一个 class 对象的引用,作用的要求 JVM 查找并加载指定的类
package com.test.class_obj;
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());
System.out.println("-----------------------------------");
}
public static void main(String[] args) {
Class c = null;
try {
c = Class.forName("com.test.class_obj.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 {
// Requires default constructor:
obj = up.newInstance();
} catch (InstantiationException e) {
System.out.println("Cannot instantiate");
System.exit(1);
} catch (IllegalAccessException e) {
System.out.println("Cannot access");
System.exit(1);
}
printInfo(obj.getClass());
}
}
Class name: com.test.class_obj.FancyToy is interface? [false]
Simple name: FancyToy
Canonical name : com.test.class_obj.FancyToy
-----------------------------------
Class name: com.test.class_obj.HasBatteries is interface? [true]
Simple name: HasBatteries
Canonical name : com.test.class_obj.HasBatteries
-----------------------------------
Class name: com.test.class_obj.Waterproof is interface? [true]
Simple name: Waterproof
Canonical name : com.test.class_obj.Waterproof
-----------------------------------
Class name: com.test.class_obj.Shoots is interface? [true]
Simple name: Shoots
Canonical name : com.test.class_obj.Shoots
-----------------------------------
Class name: com.test.class_obj.Toy is interface? [false]
Simple name: Toy
Canonical name : com.test.class_obj.Toy
-----------------------------------
Process finished with exit code 0
Class.forName() 查找并加载指定的 class,返回 class 对象的引用
注意:在传递给 forName() 的字符串中,必须使用全限定名(包名)
- classObj.getName() 产生全限定的类名
- classObj.getSimpleName() 产生不包含包名的类名
- classObj.getCanonicalName() 产生全限定的类名
- classObj.isInterface() 判断这个 class 对象是否是接口
- classObj.getSuperclass() 查询其直接基类
- classObj.newInstance() 是实现“虚拟构造器”的一种途径,允许声明:“我不知道你的确切类型,但是无路如何你要正确创建你自己的实例。”
注意:使用 newInstance() 来创建的类,必须带有默认的构造器。否则会得到一个异常
2 类字面常量
使用类字面常量生成对 Class 对象的引用
FancyToy.class
在编译时就会受到检查(不需要置于 try 块中)
类字面常量不仅可以用于普通的类,也可以应用于接口、数组、以及基本数据类型。
注意:当使用
.class
来创建对 Class 对象的引用时,不会自动地初始化该 Class 对象
package com.test.class_obj;
import java.util.*;
class Initable {
static final int staticFinal = 47;
static final int staticFinal2 =
ClassInitialization.rand.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 = 74;
static {
System.out.println("Initializing Initable3");
}
}
public class ClassInitialization {
public static Random rand = new Random(47);
public static void main(String[] args) throws Exception {
Class initable = Initable.class;
System.out.println("After creating Initable ref");
System.out.println("---------------------------");
// staticFinal 是编译期常量,不需要对 Initable 类进行初始化就可以被读取
System.out.println(Initable.staticFinal);
System.out.println("---------------------------");
// staticFinal2 是非编译期常量,访问它将会强制进行类的初始化
System.out.println(Initable.staticFinal2);
System.out.println("---------------------------");
// staticNonFinal 只是 static 域,在被读取之前,要先进行链接和初始化
System.out.println(Initable2.staticNonFinal);
System.out.println("---------------------------");
// Class.forName 会对类进行初始化
Class.forName("com.test.class_obj.Initable3");
System.out.println("After creating Initable3 ref");
System.out.println(Initable3.staticNonFinal);
System.out.println("---------------------------");
}
}
- 仅使用
.class
来获得对类的引用不会引发初始化;而Class.forName()
立即就进行了初始化(Initable3) - static final 值是“编译期常量”,不需要对类进行初始化就可以读取(Initable.staticFinal)
- 如果将一个域设置为 static 和 final 的,则可能会强制进行类的初始化(Initable.staticFinal2)
- 如果一个 static 域不是 final 的,那么在对他访问之前,要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间)(Initable2.staticNonFinal)
3 泛化的 Class 引用
Class 引用指向某个 Class 对象,可以制造类的实例,并包含课作用域这些实例的所有方法代码,包含该类的静态成员。
通过使用泛型语法,可以让编译器强制执行额外的类型检查。
package com.test.class_obj;
public class GenericClassReferences {
public static void main(String[] args) {
Class intClass = int.class;
System.out.println(intClass.getSimpleName());
intClass = double.class;
System.out.println(intClass.getSimpleName());
System.out.println("----------------------");
Class<Integer> genericIntClass = int.class;
System.out.println(genericIntClass.getSimpleName());
genericIntClass = Integer.class; // Same thing
System.out.println(genericIntClass.getSimpleName());
System.out.println(int.class.equals(Integer.class));
System.out.println("----------------------");
genericIntClass = double.class; // Illegal
Class<Number> genericNumberClass = int.class; // Illegal
Class<? extends Number> classExtendNumber = int.class;
System.out.println(classExtendNumber.getSimpleName());
}
}
通配符可以使泛化的 Class 引用放松限制,通配符 ?
表示“任何事物”。
Class<?>
优于 平凡的Class
,Class<?>
的好处是,它表示使用了一个非具体的类引用,选择了非具体的版本。
将通配符与 extends 关键字结合,可以创建一个范围,它被先定位某种类型,或该类型的任何子类型。
Class<? extends Number> classExtendNumber = int.class;
向 Class 引用添加泛型语法的原因仅仅是为了提供编译期类型检查
package com.test.class_obj;
public class GenericToyTest {
public static void main(String[] args) throws Exception {
Class<FancyToy> ftClass = FancyToy.class;
// newInstance() 返回确切类型
FancyToy fancyToy = ftClass.newInstance();
System.out.println(ftClass.newInstance().getClass());
System.out.println("----------------------------------");
// 某个类,它是 FancyToy 的超类
Class<? super FancyToy> up = ftClass.getSuperclass();
Class<Toy> up2 = ftClass.getSuperclass(); // This won't compile:
// newInstance() 无法返回精确的类型
Toy obj_1 = up.newInstance();
Object obj = up.newInstance();
System.out.println(up.newInstance().getClass());
}
}
4 参考资料
1、《Java编程思想》