类型信息
编译前 - 强制
运行时 - 反射
// 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
,那就说明你的设计可能存在瑕疵。
动态的instanceof函数
Class.isInstance()
方法提供了一种动态测试对象类型的方法。因此,所有这些繁琐的 instanceof
语句都可以从 PetCount.java
中删除:
// typeinfo/PetCount3.java // 使用 isInstance() 方法 import java.util.*; import java.util.stream.*; import onjava.*; import typeinfo.pets.*; public class PetCount3 { static class Counter extends LinkedHashMap<Class<? extends Pet>, Integer> { Counter() { super(LiteralPetCreator.ALL_TYPES.stream() .map(lpc -> Pair.make(lpc, 0)) .collect( Collectors.toMap(Pair::key, Pair::value))); } public void count(Pet pet) { // Class.isInstance() 替换 instanceof: entrySet().stream() .filter(pair -> pair.getKey().isInstance(pet)) .forEach(pair -> put(pair.getKey(), pair.getValue() + 1)); } @Override public String toString() { String result = entrySet().stream() .map(pair -> String.format("%s=%s", pair.getKey().getSimpleName(), pair.getValue())) .collect(Collectors.joining(", ")); return "{" + result + "}"; } } public static void main(String[] args) { Counter petCount = new Counter(); Pets.stream() .limit(20) .peek(petCount::count) .forEach(p -> System.out.print( p.getClass().getSimpleName() + " ")); System.out.println("n" + petCount); } }
输出
Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric {Rat=2, Pug=3, Mutt=3, Mouse=2, Cat=9, Dog=6, Cymric=5, EgyptianMau=2, Rodent=5, Hamster=1, Manx=7, Pet=20}
注册工厂
// typeinfo/RegisteredFactories.java // 注册工厂到基础类 import java.util.*; import java.util.function.*; import java.util.stream.*; class Part implements Supplier<Part> { @Override public String toString() { return getClass().getSimpleName(); } static List<Supplier<? extends Part>> prototypes = Arrays.asList( new FuelFilter(), new AirFilter(), new CabinAirFilter(), new OilFilter(), new FanBelt(), new PowerSteeringBelt(), new GeneratorBelt() ); private static Random rand = new Random(47); public Part get() { int n = rand.nextint(prototypes.size()); return prototypes.get(n).get(); } } class Filter extends Part {} class FuelFilter extends Filter { @Override public FuelFilter get() { return new FuelFilter(); } } class AirFilter extends Filter { @Override public AirFilter get() { return new AirFilter(); } } class CabinAirFilter extends Filter { @Override public CabinAirFilter get() { return new CabinAirFilter(); } } class OilFilter extends Filter { @Override public OilFilter get() { return new OilFilter(); } } class Belt extends Part {} class FanBelt extends Belt { @Override public FanBelt get() { return new FanBelt(); } } class GeneratorBelt extends Belt { @Override public GeneratorBelt get() { return new GeneratorBelt(); } } class PowerSteeringBelt extends Belt { @Override public PowerSteeringBelt get() { return new PowerSteeringBelt(); } } public class RegisteredFactories { public static void main(String[] args) { Stream.generate(new Part()) .limit(10) .forEach(System.out::println); } }
输出
GeneratorBelt
CabinAirFilter
GeneratorBelt
AirFilter
PowerSteeringBelt
CabinAirFilter
FuelFilter
PowerSteeringBelt
PowerSteeringBelt
FuelFilter
这里用到了协变。
// typeinfo/FamilyVsExactType.java // instanceof 与 class 的差别 // {java typeinfo.FamilyVsExactType} package typeinfo; class Base {} class Derived extends Base {} public class FamilyVsExactType { 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( "Derived.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(Base.class)) "+ (x.getClass().equals(Base.class))); System.out.println( "x.getClass().equals(Derived.class)) " + (x.getClass().equals(Derived.class))); } public static void main(String[] args) { test(new Base()); test(new Derived()); } }
输出
Testing x of type class typeinfo.Base x instanceof Base true x instanceof Derived false Base.isInstance(x) true Derived.isInstance(x) false x.getClass() == Base.class true x.getClass() == Derived.class false x.getClass().equals(Base.class)) true x.getClass().equals(Derived.class)) false Testing x of type class typeinfo.Derived x instanceof Base true x instanceof Derived true Base.isInstance(x) true Derived.isInstance(x) true x.getClass() == Base.class false x.getClass() == Derived.class true x.getClass().equals(Base.class)) false x.getClass().equals(Derived.class)) true
令人放心的是,instanceof
和 isInstance()
产生的结果相同, equals()
和 ==
产生的结果也相同。与类型的概念一致,instanceof
说的是“你是这个类,还是从这个类派生的类?”。而如果使用 ==
比较实际的Class
对象,则与继承无关 —— 它要么是确切的类型,要么不是。
类方法提取器
Class<?> c = Class.forName(args[0]); Method[] methods = c.getMethods(); Constructor[] ctors = c.getConstructors();
Class
方法 getmethods()
和 getconstructors()
分别返回 Method
数组和 Constructor
数组。这些类中的每一个都有进一步的方法来解析它们所表示的方法的名称、参数和返回值。但你也可以像这里所做的那样,使用 toString()
,生成带有整个方法签名的 String
。
动态代理
普通的代理模式
// typeinfo/SimpleProxyDemo.java interface Interface { void doSomething(); void somethingElse(String arg); } class RealObject implements Interface { @Override public void doSomething() { System.out.println("doSomething"); } @Override public void somethingElse(String arg) { System.out.println("somethingElse " + arg); } } class SimpleProxy implements Interface { private Interface proxied; SimpleProxy(Interface proxied) { this.proxied = proxied; } @Override public void doSomething() { System.out.println("SimpleProxy doSomething"); proxied.doSomething(); } @Override public void somethingElse(String arg) { System.out.println( "SimpleProxy somethingElse " + arg); proxied.somethingElse(arg); } } class SimpleProxyDemo { public static void consumer(Interface iface) { iface.doSomething(); iface.somethingElse("bonobo"); } public static void main(String[] args) { consumer(new RealObject()); consumer(new SimpleProxy(new RealObject())); } }
输出
doSomething
somethingElse bonobo
SimpleProxy doSomething
doSomething
SimpleProxy somethingElse bonobo
somethingElse bonobo
Proxy.newInstance()动态代理方式
// typeinfo/SimpleDynamicProxy.java import java.lang.reflect.*; class DynamicProxyHandler implements InvocationHandler { private Object proxied; DynamicProxyHandler(Object proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println( "**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args); if (args != null) for (Object arg : args) System.out.println(" " + arg); return method.invoke(proxied, args); } } class SimpleDynamicProxy { public static void consumer(Interface iface) { iface.doSomething(); iface.somethingElse("bonobo"); } public static void main(String[] args) { RealObject real = new RealObject(); consumer(real); // Insert a proxy and call again: Interface proxy = (Interface) Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynamicProxyHandler(real)); consumer(proxy); } }
输出
doSomething somethingElse bonobo **** proxy: class $Proxy0, method: public abstract void Interface.doSomething(), args: null doSomething **** proxy: class $Proxy0, method: public abstract void Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@6bc7c054 bonobo somethingElse bonobo
可以通过调用静态方法 Proxy.newProxyInstance()
来创建动态代理,该方法需要一个类加载器(通常可以从已加载的对象中获取),希望代理实现的接口列表(不是类或抽象类),以及接口 InvocationHandler
的一个实现。动态代理会将所有调用重定向到调用处理程序,因此通常为调用处理程序的构造函数提供对“真实”对象的引用,以便一旦执行中介任务便可以转发请求。
invoke()
方法被传递给代理对象,以防万一你必须区分请求的来源---但是在很多情况下都无需关心。但是,在 invoke()
内的代理上调用方法时要小心,因为接口的调用是通过代理重定向的。
// typeinfo/SelectingMethods.java // Looking for particular methods in a dynamic proxy import java.lang.reflect.*; class MethodSelector implements InvocationHandler { private Object proxied; MethodSelector(Object proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("interesting")) System.out.println( "Proxy detected the interesting method"); return method.invoke(proxied, args); } } interface SomeMethods { void boring1(); void boring2(); void interesting(String arg); void boring3(); } class Implementation implements SomeMethods { @Override public void boring1() { System.out.println("boring1"); } @Override public void boring2() { System.out.println("boring2"); } @Override public void interesting(String arg) { System.out.println("interesting " + arg); } @Override public void boring3() { System.out.println("boring3"); } } class SelectingMethods { public static void main(String[] args) { SomeMethods proxy = (SomeMethods) Proxy.newProxyInstance( SomeMethods.class.getClassLoader(), new Class[]{Interface.class}, new MethodSelector(new Implementation())); proxy.boring1(); proxy.boring2(); proxy.interesting("bonobo"); proxy.boring3(); } }
输出
boring1
boring2
Proxy detected the interesting method
interesting bonobo
boring3
Optional
// typeinfo/Person.java // Using Optional with regular classes import onjava.*; import java.util.*; class Person { public final Optional<String> first; public final Optional<String> last; public final Optional<String> address; // etc. public final Boolean empty; Person(String first, String last, String address) { this.first = Optional.ofNullable(first); this.last = Optional.ofNullable(last); this.address = Optional.ofNullable(address); empty = !this.first.isPresent() && !this.last.isPresent() && !this.address.isPresent(); } Person(String first, String last) { this(first, last, null); } Person(String last) { this(null, last, null); } Person() { this(null, null, null); } @Override public String toString() { if (empty) return "<Empty>"; return (first.orElse("") + " " + last.orElse("") + " " + address.orElse("")).trim(); } public static void main(String[] args) { System.out.println(new Person()); System.out.println(new Person("Smith")); System.out.println(new Person("Bob", "Smith")); System.out.println(new Person("Bob", "Smith", "11 Degree Lane, Frostbite Falls, MN")); } }
输出
<Empty>
Smith
Bob Smith
Bob Smith 11 Degree Lane, Frostbite Falls, MN
Person
的设计有时候又叫“数据传输对象(DTO,data-transfer object)”。注意,所有字段都是 public
和 final
的,所以没有 getter
和 setter
方法。也就是说,Person
是不可变的,你只能通过构造器给它赋值,之后就只能读而不能修改它的值(字符串本身就是不可变的,因此你无法修改字符串的内容,也无法给它的字段重新赋值)。如果你想修改一个 Person
,你只能用一个新的 Person
对象来替换它。empty
字段在对象创建的时候被赋值,用于快速判断这个 Person
对象是不是空对象。
如果想使用 Person
,就必须使用 Optional
接口才能访问它的 String
字段,这样就不会意外触发 NullPointException
了。
// typeinfo/Position.java import java.util.*; class EmptyTitleException extends RuntimeException { } class Position { private String title; private Person person; Position(String jobTitle, Person employee) { setTitle(jobTitle); setPerson(employee); } Position(String jobTitle) { this(jobTitle, null); } public String getTitle() { return title; } public void setTitle(String newTitle) { // Throws EmptyTitleException if newTitle is null: title = Optional.ofNullable(newTitle) .orElseThrow(EmptyTitleException::new); } public Person getPerson() { return person; } public void setPerson(Person newPerson) { // Uses empty Person if newPerson is null: person = Optional.ofNullable(newPerson) .orElse(new Person()); } @Override public String toString() { return "Position: " + title + ", Employee: " + person; } public static void main(String[] args) { System.out.println(new Position("CEO")); System.out.println(new Position("Programmer", new Person("Arthur", "Fonzarelli"))); try { new Position(null); } catch (Exception e) { System.out.println("caught " + e); } } }
输出
Position: CEO, Employee: <Empty>
Position: Programmer, Employee: Arthur Fonzarelli
caught EmptyTitleException
Mock和桩
Mock 对象和桩之间的的差别在于程度不同。Mock 对象往往是轻量级的,且用于自测试。通常,为了处理各种不同的测试场景,我们会创建出很多 Mock 对象。而桩只是返回桩数据,它通常是重量级的,并且经常在多个测试中被复用。桩可以根据它们被调用的方式,通过配置进行修改。因此,桩是一种复杂对象,它可以做很多事情。至于 Mock 对象,如果你要做很多事,通常会创建大量又小又简单的 Mock 对象。