编译前 - 强制
运行时 - 反射
// typeinfo/Shapes.java import java.util.stream.*; abstract class Shape { void draw() { System.out.println(this + ".draw()"); } @Override public abstract String toString(); } class Circle extends Shape { @Override public String toString() { return "Circle"; } } class Square extends Shape { @Override public String toString() { return "Square"; } } class Triangle extends Shape { @Override public String toString() { return "Triangle"; } } public class Shapes { public static void main(String[] args) { Stream.of( new Circle(), new Square(), new Triangle()) .forEach(Shape::draw); } }
输出
Circle.draw() Square.draw() Triangle.draw()
- 编译期,
stream
和 Java 泛型系统确保放入stream
的都是Shape
对象(Shape
子类的对象也可视为Shape
的对象),否则编译器会报错; - 运行时,自动类型转换确保了从
stream
中取出的对象都是Shape
类型。
其实构造器也是类的静态方法,虽然构造器前面并没有 static
关键字。所以,使用 new
操作符创建类的新对象,这个操作也算作对类的静态成员引用。
class是用到再加载,static
初始化是在类加载时进行的。
// typeinfo/SweetShop.java // 检查类加载器工作方式 class Cookie { static { System.out.println("Loading Cookie"); } } class Gum { static { System.out.println("Loading Gum"); } } class Candy { static { System.out.println("Loading Candy"); } } 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引用
// typeinfo/toys/ToyTest.java // 测试 Class 类 // {java typeinfo.toys.ToyTest} package typeinfo.toys; interface HasBatteries {} interface Waterproof {} interface Shoots {} class Toy { // 注释下面的无参数构造器会引起 NoSuchMethodError 错误 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 { // Requires no-arg 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: typeinfo.toys.FancyToy is interface? [false] Simple name: FancyToy Canonical name : typeinfo.toys.FancyToy Class name: typeinfo.toys.HasBatteries is interface? [true] Simple name: HasBatteries Canonical name : typeinfo.toys.HasBatteries Class name: typeinfo.toys.Waterproof is interface? [true] Simple name: Waterproof Canonical name : typeinfo.toys.Waterproof Class name: typeinfo.toys.Shoots is interface? [true] Simple name: Shoots Canonical name : typeinfo.toys.Shoots Class name: typeinfo.toys.Toy is interface? [false] Simple name: Toy Canonical name : typeinfo.toys.Toy
FancyToy
继承自 Toy
并实现了 HasBatteries
、Waterproof
和 Shoots
接口。在 main
方法中,我们创建了一个 Class
引用,然后在 try
语句里边用 forName()
方法创建了一个 FancyToy
的类对象并赋值给该引用。需要注意的是,传递给 forName()
的字符串必须使用类的全限定名(包含包名)。
另外,你还可以调用 getSuperclass()
方法来得到父类的 Class
对象,再用父类的 Class
对象调用该方法,重复多次,你就可以得到一个对象完整的类继承结构。
Class
对象的 newInstance()
方法是实现“虚拟构造器”的一种途径,虚拟构造器可以让你在不知道一个类的确切类型的时候,创建这个类的对象。在前面的例子中,up
只是一个 Class
对象的引用,在编译期并不知道这个引用会指向哪个类的 Class
对象。当你创建新实例时,会得到一个 Object
引用,但是这个引用指向的是 Toy
对象。当然,由于得到的是 Object
引用,目前你只能给它发送 Object
对象能够接受的调用。而如果你想请求具体对象才有的调用,你就得先获取该对象更多的类型信息,并执行某种转型。另外,使用 newInstance()
来创建的类,必须带有无参数的构造器。在本章稍后部分,你将会看到如何通过 Java 的反射 API,用任意的构造器来动态地创建类的对象。
类字面常量
例子
// typeinfo/ClassInitialization.java import java.util.*; class Initable { static final int STATIC_FINAL = 47; static final int STATIC_FINAL2 = 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"); // Does not trigger initialization: System.out.println(Initable.STATIC_FINAL); // Does trigger initialization: System.out.println(Initable.STATIC_FINAL2); // Does trigger initialization: System.out.println(Initable2.staticNonFinal); Class initable3 = Class.forName("Initable3"); System.out.println("After creating Initable3 ref"); System.out.println(Initable3.staticNonFinal); } }
输出
After creating Initable ref 47 Initializing Initable 258 Initializing Initable2 147 Initializing Initable3 After creating Initable3 ref 74
初始化有效地实现了尽可能的“惰性”,从对 initable
引用的创建中可以看到,仅使用 .class
语法来获得对类对象的引用不会引发初始化。但与此相反,使用 Class.forName()
来产生 Class
引用会立即就进行初始化,如 initable3
。
如果一个 static final
值是“编译期常量”(如 Initable.staticFinal
),那么这个值不需要对 Initable
类进行初始化就可以被读取。但是,如果只是将一个字段设置成为 static
和 final
,还不足以确保这种行为。例如,对 Initable.staticFinal2
的访问将强制进行类的初始化,因为它不是一个编译期常量。
如果一个 static
字段不是 final
的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个字段分配存储空间)和初始化(初始化该存储空间),就像在对 Initable2.staticNonFinal
的访问中所看到的那样。
泛化class
// typeinfo/WildcardClassReferences.java public class WildcardClassReferences { public static void main(String[] args) { Class<?> intClass = int.class; intClass = double.class; } }
这边有个奇怪的地方,类和class并非一样的概念。这看起来似乎是起作用的,因为 Integer
继承自 Number
。但事实却是不行,因为 Integer
的 Class
对象并不是 Number
的 Class
对象的子类
Class<Number> geenericNumberClass = int.class; (错误)
cast()方法
// typeinfo/ClassCasts.java 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
引用的类型。
instanceof
// typeinfo/pets/ForNameCreator.java package typeinfo.pets; import java.util.*; public class ForNameCreator extends PetCreator { private static List<Class<? extends Pet>> types = new ArrayList<>(); // 需要随机生成的类型名: private static String[] typeNames = { "typeinfo.pets.Mutt", "typeinfo.pets.Pug", "typeinfo.pets.EgyptianMau", "typeinfo.pets.Manx", "typeinfo.pets.Cymric", "typeinfo.pets.Rat", "typeinfo.pets.Mouse", "typeinfo.pets.Hamster" }; @SuppressWarnings("unchecked") private static void loader() { try { for (String name : typeNames) types.add( (Class<? extends Pet>) Class.forName(name)); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } static { loader(); } @Override public List<Class<? extends Pet>> types() { return types; } }
这边还是要提一句,生产extends 消费super
// typeinfo/PetCount.java // 使用 instanceof import typeinfo.pets.*; import java.util.*; public class PetCount { static class Counter extends HashMap<String, Integer> { public void count(String type) { Integer quantity = get(type); if (quantity == null) put(type, 1); else put(type, quantity + 1); } } public static void countPets(PetCreator creator) { Counter counter = new Counter(); for (Pet pet : Pets.array(20)) { // List each individual pet: System.out.print( pet.getClass().getSimpleName() + " "); if (pet instanceof Pet) counter.count("Pet"); if (pet instanceof Dog) counter.count("Dog"); if (pet instanceof Mutt) counter.count("Mutt"); if (pet instanceof Pug) counter.count("Pug"); if (pet instanceof Cat) counter.count("Cat"); if (pet instanceof EgyptianMau) counter.count("EgyptianMau"); if (pet instanceof Manx) counter.count("Manx"); if (pet instanceof Cymric) counter.count("Cymric"); if (pet instanceof Rodent) counter.count("Rodent"); if (pet instanceof Rat) counter.count("Rat"); if (pet instanceof Mouse) counter.count("Mouse"); if (pet instanceof Hamster) counter.count("Hamster"); } // Show the counts: System.out.println(); System.out.println(counter); } public static void main(String[] args) { countPets(new ForNameCreator()); } }
输出
Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric {EgyptianMau=2, Pug=3, Rat=2, Cymric=5, Mouse=2, Cat=9, Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1}
instanceof
有一个严格的限制:只可以将它与命名类型进行比较,而不能与 Class
对象作比较。在前面的例子中,你可能会觉得写出一大堆 instanceof
表达式很乏味,事实也是如此。但是,也没有办法让 instanceof
聪明起来,让它能够自动地创建一个 Class
对象的数组,然后将目标与这个数组中的对象逐一进行比较(稍后会看到一种替代方案)。其实这并不是那么大的限制,如果你在程序中写了大量的 instanceof
,那就说明你的设计可能存在瑕疵。
今天状态很差,本来不想写的,但是又不想中断,23:00起来写的,实在是很困,浪费时间,明日把。