本文主要是介绍十九、类型信息(3),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
本章概要
- 类型转换检测
- 使用类字面量
- 一个动态 instanceof 函数
- 递归计数
类型转换检测
直到现在,我们已知的 RTTI 类型包括:
- 传统的类型转换,如 “
(Shape)
”,由 RTTI 确保转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException
异常。 - 代表对象类型的
Class
对象. 通过查询Class
对象可以获取运行时所需的信息.
在 C++ 中,经典的类型转换 “(Shape)
” 并不使用 RTTI。它只是简单地告诉编译器将这个对象作为新的类型对待. 而 Java 会进行类型检查,这种类型转换一般被称作“类型安全的向下转型”。之所以称作“向下转型”,是因为传统上类继承图是这么画的。将 Circle
转换为 Shape
是一次向上转型, 将 Shape
转换为 Circle
是一次向下转型。但是, 因为我们知道 Circle
肯定是一个 Shape
,所以编译器允许我们自由地做向上转型的赋值操作,且不需要任何显式的转型操作。
当你给编译器一个 Shape
的时候,编译器并不知道它到底是什么类型的 Shape
——它可能是 Shape
,也可能是 Shape
的子类型,例如 Circle
、Square
、Triangle
或某种其他的类型。在编译期,编译器只能知道它是 Shape
。
因此,你需要使用显式地进行类型转换,以告知编译器你想转换的特定类型,否则编译器就不允许你执行向下转型赋值。 (编译器将会检查向下转型是否合理,因此它不允许向下转型到实际不是待转型类型的子类类型上)。
RTTI 在 Java 中还有第三种形式,那就是关键字 instanceof
。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例,可以用提问的方式使用它,就像这个样子:
if(x instanceof Dog){((Dog)x).bark();
}
在将 x
的类型转换为 Dog
之前,if
语句会先检查 x
是否是 Dog
类型的对象。进行向下转型前,如果没有其他信息可以告诉你这个对象是什么类型,那么使用 instanceof
是非常重要的,否则会得到一个 ClassCastException
异常。
一般,可能想要查找某种类型(比如要找三角形,并填充为紫色),这时可以轻松地使用 instanceof
来度量所有对象。举个例子,假如你有一个类的继承体系,描述了 Pet
(以及它们的主人,在后面一个例子中会用到这个特性)。
在这个继承体系中的每个 Individual
都有一个 id
和一个可选的名字。尽管下面的类都继承自 Individual
,但是 Individual
类复杂性较高。正如你所看到的,此处并不需要去了解 Individual
的代码——你只需了解你可以创建其具名或不具名的对象,并且每个 Individual
都有一个 id()
方法,如果你没有为 Individual
提供名字,toString()
方法只产生类型名。
下面是继承自 Individual
的类的继承体系:
Individual.java
import java.util.*;public class Individual implements Comparable<Individual> {private static long counter = 0;private final long id = counter++;private String name;public Individual(String name) {this.name = name;}// 'name' is optional:public Individual() {}@Overridepublic String toString() {return getClass().getSimpleName() +(name == null ? "" : " " + name);}public long id() {return id;}@Overridepublic boolean equals(Object o) {return o instanceof Individual &&Objects.equals(id, ((Individual) o).id);}@Overridepublic int hashCode() {return Objects.hash(name, id);}@Overridepublic int compareTo(Individual arg) {// Compare by class name first:String first = getClass().getSimpleName();String argFirst = arg.getClass().getSimpleName();int firstCompare = first.compareTo(argFirst);if (firstCompare != 0) {return firstCompare;}if (name != null && arg.name != null) {int secondCompare = name.compareTo(arg.name);if (secondCompare != 0) {return secondCompare;}}return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));}
}
public class Person extends Individual {public Person(String name) {super(name);}
}
public class Pet extends Individual {public Pet(String name) {super(name);}public Pet() {super();}
}
public class Dog extends Pet {public Dog(String name) {super(name);}public Dog() {super();}
}
public class Mutt extends Dog {public Mutt(String name) {super(name);}public Mutt() {super();}
}
public class Pug extends Dog {public Pug(String name) { super(name); }public Pug() { super(); }
}
public class Cat extends Pet {public Cat(String name) {super(name);}public Cat() {super();}
}
public class EgyptianMau extends Cat {public EgyptianMau(String name) {super(name);}public EgyptianMau() {super();}
}
public class Manx extends Cat {public Manx(String name) {super(name);}public Manx() {super();}
}
public class Cymric extends Manx {public Cymric(String name) {super(name);}public Cymric() {super();}
}
public class Rodent extends Pet {public Rodent(String name) {super(name);}public Rodent() {super();}
}
public class Rat extends Rodent {public Rat(String name) {super(name);}public Rat() {super();}
}
public class Mouse extends Rodent {public Mouse(String name) {super(name);}public Mouse() {super();}
}
public class Hamster extends Rodent {public Hamster(String name) {super(name);}public Hamster() {super();}
}
我们必须显式地为每一个子类编写无参构造器。因为我们有一个带一个参数的构造器,所以编译器不会自动地为我们加上无参构造器。
接下来,我们需要一个类,它可以随机地创建不同类型的宠物,同时,它还可以创建宠物数组和持有宠物的 List
。为了使这个类更加普遍适用,我们将其定义为抽象类:
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;public abstract class PetCreator implements Supplier<Pet> {private Random rand = new Random(47);// The List of the different types of Pet to create:public abstract List<Class<? extends Pet>> types();@Overridepublic Pet get() { // Create one random Petint n = rand.nextInt(types().size());try {return types().get(n).getConstructor().newInstance();} catch (InstantiationException |NoSuchMethodException |InvocationTargetException |IllegalAccessException e) {throw new RuntimeException(e);}}public Stream<Pet> stream() {return Stream.generate(this);}public Pet[] array(int size) {return stream().limit(size).toArray(Pet[]::new);}public List<Pet> list(int size) {return stream().limit(size).collect(Collectors.toCollection(ArrayList::new));}
}
抽象的 types()
方法需要子类来实现,以此来获取 Class
对象构成的 List
(这是模板方法设计模式的一种变体)。注意,其中类的类型被定义为“任何从 Pet
导出的类型”,因此 newInstance()
不需要转型就可以产生 Pet
。get()
随机的选取出一个 Class
对象,然后可以通过 Class.newInstance()
来生成该类的新实例。
在调用 newInstance()
时,可能会出现两种异常。在紧跟 try
语句块后面的 catch
子句中可以看到对它们的处理。异常的名字再次成为了一种对错误类型相对比较有用的解释(IllegalAccessException
违反了 Java 安全机制,在本例中,表示默认构造器为 private
的情况)。
当你创建 PetCreator
的子类时,你需要为 get()
方法提供 Pet
类型的 List
。types()
方法会简单地返回一个静态 List
的引用。下面是使用 forName()
的一个具体实现:
import java.util.*;public class ForNameCreator extends PetCreator {private static List<Class<? extends Pet>> types = new ArrayList<>();// 需要随机生成的类型名:private static String[] typeNames = {"com.example.test.Mutt","com.example.test.Pug","com.example.test.EgyptianMau","com.example.test.Manx","com.example.test.Cymric","com.example.test.Rat","com.example.test.Mouse","com.example.test.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();}@Overridepublic List<Class<? extends Pet>> types() {return types;}
}
loader()
方法使用 Class.forName()
创建了 Class
对象的 List
。这可能会导致 ClassNotFoundException
异常,因为你传入的是一个 String
类型的参数,它不能在编译期间被确认是否合理。由于 Pet
相关的文件在 typeinfo
包里面,所以使用它们的时候需要填写完整的包名。
为了使得 List
装入的是具体的 Class
对象,类型转换是必须的,它会产生一个编译时警告。loader()
方法是分开编写的,然后它被放入到一个静态代码块里,因为 @SuppressWarning
注解不能够直接放置在静态代码块之上。
为了对 Pet
进行计数,我们需要一个能跟踪不同类型的 Pet
的工具。Map
是这个需求的首选,我们将 Pet
类型名作为键,将保存 Pet
数量的 Integer
作为值。通过这种方式,你就可以询问:“有多少个 Hamster
对象?”我们可以使用 instanceof
来对 Pet
进行计数:
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 : creator.array(20)) {// List each individual pet:System.out.print(pet.getClass().getSimpleName() + " ");if (pet instanceof Pet) {counter.count("com.example.test.Pet");}if (pet instanceof Dog) {counter.count("com.example.test.Dog");}if (pet instanceof Mutt) {counter.count("com.example.test.Mutt");}if (pet instanceof Pug) {counter.count("com.example.test.Pug");}if (pet instanceof Cat) {counter.count("com.example.test.Cat");}if (pet instanceof EgyptianMau) {counter.count("com.example.test.EgyptianMau");}if (pet instanceof Manx) {counter.count("com.example.test.Manx");}if (pet instanceof Cymric) {counter.count("com.example.test.Cymric");}if (pet instanceof Rodent) {counter.count("com.example.test.Rodent");}if (pet instanceof Rat) {counter.count("com.example.test.Rat");}if (pet instanceof Mouse) {counter.count("com.example.test.Mouse");}if (pet instanceof Hamster) {counter.count("com.example.test.Hamster");}}// Show the counts:System.out.println();System.out.println(counter);}public static void main(String[] args) {countPets(new ForNameCreator());}
}
输出结果:
在 countPets()
中,一个简短的静态方法 Pets.array()
生产出了一个随机动物的集合。每个 Pet
都被 instanceof
检测到并计算了一遍。
instanceof
有一个严格的限制:只可以将它与命名类型进行比较,而不能与 Class
对象作比较。在前面的例子中,你可能会觉得写出一大堆 instanceof
表达式很乏味,事实也是如此。但是,也没有办法让 instanceof
聪明起来,让它能够自动地创建一个 Class
对象的数组,然后将目标与这个数组中的对象逐一进行比较(稍后会看到一种替代方案)。其实这并不是那么大的限制,如果你在程序中写了大量的 instanceof
,那就说明你的设计可能存在瑕疵。
使用类字面量
如果我们使用类字面量重新实现 PetCreator
类的话,其结果在很多方面都会更清晰:
import java.util.*;public class LiteralPetCreator extends PetCreator {// try 代码块不再需要@SuppressWarnings("unchecked")public static final List<Class<? extends Pet>> ALL_TYPES =Collections.unmodifiableList(Arrays.asList(Pet.class, Dog.class, Cat.class, Rodent.class,Mutt.class, Pug.class, EgyptianMau.class,Manx.class, Cymric.class, Rat.class,Mouse.class, Hamster.class));// 用于随机创建的类型:private static final List<Class<? extends Pet>> TYPES =ALL_TYPES.subList(ALL_TYPES.indexOf(Mutt.class),ALL_TYPES.size());@Overridepublic List<Class<? extends Pet>> types() {return TYPES;}public static void main(String[] args) {System.out.println(TYPES);}
}
输出结果:
在即将到来的 PetCount3.java
示例中,我们用所有 Pet
类型预先加载一个 Map
(不仅仅是随机生成的),因此 ALL_TYPES
类型的列表是必要的。types
列表是 ALL_TYPES
类型(使用 List.subList()
创建)的一部分,它包含精确的宠物类型,因此用于随机生成 Pet
。
这次,types
的创建没有被 try
块包围,因为它是在编译时计算的,因此不会引发任何异常,不像 Class.forName()
。
我们现在在 typeinfo.pets
库中有两个 PetCreator
的实现。为了提供第二个作为默认实现,我们可以创建一个使用 LiteralPetCreator
的 外观模式:
import java.util.*;
import java.util.stream.*;public class Pets {public static final PetCreator CREATOR = new LiteralPetCreator();public static Pet get() {return CREATOR.get();}public static Pet[] array(int size) {Pet[] result = new Pet[size];for (int i = 0; i < size; i++) {result[i] = CREATOR.get();}return result;}public static List<Pet> list(int size) {List<Pet> result = new ArrayList<>();Collections.addAll(result, array(size));return result;}public static Stream<Pet> stream() {return Stream.generate(CREATOR);}
}
这还提供了对 get()
、array()
和 list()
的间接调用,以及生成 Stream<Pet>
的新方法。
因为 PetCount.countPets()
采用了 PetCreator
参数,所以我们可以很容易地测试 LiteralPetCreator
(通过上面的外观模式):
// typeinfo/PetCount2.java
import typeinfo.pets.*;public class PetCount2 {public static void main(String[] args) {PetCount.countPets(Pets.CREATOR);}
}
输出结果:
输出与 PetCount.java
的输出相同。
一个动态 instanceof
函数
Class.isInstance()
方法提供了一种动态测试对象类型的方法。因此,所有这些繁琐的 instanceof
语句都可以从 PetCount.java
中删除:
Pair.java
public class Pair<K, V> {public final K key;public final V value;public Pair(K k, V v) {key = k;value = v;}public K key() {return key;}public V value() {return value;}public static <K, V> Pair<K, V> make(K k, V v) {return new Pair<K, V>(k, v);}
}
PetCount3.java
import java.util.*;
import java.util.stream.*;public class PetCount3 {static class Counter extendsLinkedHashMap<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));}@Overridepublic 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);}
}
输出结果:
为了计算所有不同类型的 Pet
,Counter Map
预先加载了来自 LiteralPetCreator.ALL_TYPES
的类型。如果不预先加载 Map
,将只计数随机生成的类型,而不是像 Pet
和 Cat
这样的基本类型。
isInstance()
方法消除了对 instanceof
表达式的需要。此外,这意味着你可以通过更改 LiteralPetCreator.types
数组来添加新类型的 Pet
;程序的其余部分不需要修改(就像使用 instanceof
表达式时那样)。
toString()
方法被重载,以便更容易读取输出,该输出仍与打印 Map
时看到的典型输出匹配。
递归计数
PetCount3.Counter
中的 Map
预先加载了所有不同的 Pet
类。我们可以使用 Class.isAssignableFrom()
而不是预加载 Map
,并创建一个不限于计数 Pet
的通用工具:
import java.util.*;
import java.util.stream.*;public class TypeCounter extends HashMap<Class<?>, Integer> {private Class<?> baseType;public TypeCounter(Class<?> baseType) {this.baseType = baseType;}public void count(Object obj) {Class<?> type = obj.getClass();if (!baseType.isAssignableFrom(type)) {throw new RuntimeException(obj + " incorrect type: " + type + ", should be type or subtype of " + baseType);}countClass(type);}private void countClass(Class<?> type) {Integer quantity = get(type);put(type, quantity == null ? 1 : quantity + 1);Class<?> superClass = type.getSuperclass();if (superClass != null && baseType.isAssignableFrom(superClass)) {countClass(superClass);}}@Overridepublic String toString() {String result = entrySet().stream().map(pair -> String.format("%s=%s",pair.getKey().getSimpleName(),pair.getValue())).collect(Collectors.joining(", "));return "{" + result + "}";}
}
count()
方法获取其参数的 Class
,并使用 isAssignableFrom()
进行运行时检查,以验证传递的对象实际上属于感兴趣的层次结构。countClass()
首先计算类的确切类型。然后,如果 baseType
可以从超类赋值,则在超类上递归调用 countClass()
。
public class PetCount4 {public static void main(String[] args) {TypeCounter counter = new TypeCounter(Pet.class);Pets.stream().limit(20).peek(counter::count).forEach(p -> System.out.print(p.getClass().getSimpleName() + " "));System.out.println("n" + counter);}
}
输出结果:
输出表明两个基类型以及精确类型都被计数了。
这篇关于十九、类型信息(3)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!