十九、类型信息(3)

2023-10-31 16:30
文章标签 十九 类型信息

本文主要是介绍十九、类型信息(3),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本章概要

  • 类型转换检测
    • 使用类字面量
    • 一个动态 instanceof 函数
    • 递归计数

类型转换检测

直到现在,我们已知的 RTTI 类型包括:

  1. 传统的类型转换,如 “(Shape)”,由 RTTI 确保转换的正确性,如果执行了一个错误的类型转换,就会抛出一个 ClassCastException 异常。
  2. 代表对象类型的 Class 对象. 通过查询 Class 对象可以获取运行时所需的信息.

在 C++ 中,经典的类型转换 “(Shape)” 并不使用 RTTI。它只是简单地告诉编译器将这个对象作为新的类型对待. 而 Java 会进行类型检查,这种类型转换一般被称作“类型安全的向下转型”。之所以称作“向下转型”,是因为传统上类继承图是这么画的。将 Circle 转换为 Shape 是一次向上转型, 将 Shape 转换为 Circle 是一次向下转型。但是, 因为我们知道 Circle 肯定是一个 Shape,所以编译器允许我们自由地做向上转型的赋值操作,且不需要任何显式的转型操作。

当你给编译器一个 Shape 的时候,编译器并不知道它到底是什么类型的 Shape——它可能是 Shape,也可能是 Shape 的子类型,例如 CircleSquareTriangle 或某种其他的类型。在编译期,编译器只能知道它是 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() 不需要转型就可以产生 Petget() 随机的选取出一个 Class 对象,然后可以通过 Class.newInstance() 来生成该类的新实例。

在调用 newInstance() 时,可能会出现两种异常。在紧跟 try 语句块后面的 catch 子句中可以看到对它们的处理。异常的名字再次成为了一种对错误类型相对比较有用的解释(IllegalAccessException 违反了 Java 安全机制,在本例中,表示默认构造器为 private 的情况)。

当你创建 PetCreator 的子类时,你需要为 get() 方法提供 Pet 类型的 Listtypes() 方法会简单地返回一个静态 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);}
}

输出结果:

在这里插入图片描述

为了计算所有不同类型的 PetCounter Map 预先加载了来自 LiteralPetCreator.ALL_TYPES 的类型。如果不预先加载 Map,将只计数随机生成的类型,而不是像 PetCat 这样的基本类型。

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)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/316649

相关文章

【重学 MySQL】十九、位运算符的使用

【重学 MySQL】十九、位运算符的使用 示例检查权限添加权限移除权限 在 MySQL 中,位运算符允许你直接在整数类型的列或表达式上进行位级操作。这些操作对于处理那些需要在二进制表示上进行直接修改或比较的场景特别有用,比如权限管理、状态标记等。 &(位与) 对两个数的二进制表示进行位与操作。只有两个相应的二进制位都为 1 时,结果的该位才为 1,否则为 0。 |(位

类型信息:反射-Class

在说反射前提一个概念:RTTI(在运行时,识别一个对象的类型) public class Shapes {public static void main(String[] args) {List<Shape> shapes = Arrays.asList(new Circle(), new Square(), new Triangle());for (Shape shape : shapes

Flink实例(十九):Flink 异步IO (四)实例 (二) MySQL

业务如下: 接收kafka数据,转为user对象,调用async,使用user.id 查询对应的phone,放回user对象,输出  主类: import com.alibaba.fastjson.JSON;import com.venn.common.Common;import org.apache.flink.formats.json.JsonNodeDeserializatio

【硬刚ES】ES基础(十九) Query Filtering 与多字符串多字段查询

本文是对《【硬刚大数据之学习路线篇】从零到大数据专家的学习指南(全面升级版)》的ES部分补充。

OCI编程高级篇(十九) 创建和使用OCI连接池

上一节介绍了连接池的概念和使用连接池的步骤,这一节看看具体的操作是怎样的,先看一下用到的函数原型和参数。 创建连接池函数OCIConnectionPoolCreate(),原型和参数如下。 sword OCIConnectionPoolCreate ( OCIEnv *envhp,     OCIError          *errhp,     OCICPool        *poolh

【Unity 3D】学习笔记十九:实例:游戏人物移动

结合学习笔记十八,来学习游戏中人物的基本移动(真的感觉好基础啊)。不多说,直接上代码。 例: //动画数组private var animUp: Object[] ;private var animDown: Object[] ;private var animLeft: Object[] ;private var animRight: Object[] ;//地图贴图priv

斗破C++编程入门系列之十九:C++程序设计必知:多文件结构和编译预处理命令(九星斗者)

斗破C++目录: 斗破C++编程入门系列之前言(斗之气三段) 斗破C++编程入门系列之二:Qt的使用介绍(斗之气三段) 斗破C++编程入门系列之三:数据结构(斗之气三段) 斗破C++编程入门系列之四:运算符和表达式(斗之气五段) 斗破C++编程入门系列之五:算法的基本控制结构之选择结构(斗之气八段) 斗破C++编程入门系列之六:算法的基本控制结构之循环结构(斗之气八段) 斗破C++编程入门系列之

Django REST Framework(十九)权限

Django REST framework (DRF) 的权限认证涉及以下几个方面:全局权限配置、局部权限配置、自定义权限类、以及自定义认证类。以下是关于这些方面的详细说明: 1. 全局权限配置 在 Django 项目的配置文件 settings.py 中,可以全局配置 DRF 的权限管理类。这种设置适用于整个项目中的所有视图。默认情况下,如果不做任何配置,DRF 会允许所有用户访问视图(Al

【从问题中去学习k8s】k8s中的常见面试题(夯实理论基础)(十九)

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》:python零基础入门学习 《python运维脚本》: python运维脚本实践 《shell》:shell学习 《terraform》持续更新中:terraform_Aws学习零基础入门到最佳实战 《k8》从问题中去学习k8s 《docker学习》暂未更新 《ceph学习》ceph日常问题解决分享 《日志收集》ELK

代谢组数据分析(十九):随机森林构建代谢组预后模型

介绍 建立胃癌(GC)预后模型时,从队列3中的181名患者中,使用右删失结果数据进行了随机分层抽样,分为训练数据集(n = 121)和测试数据集(n = 60)。训练了一个包含1000棵树的随机生存森林(RSF)模型,根据它们基于排列的特征重要性来选择突出的特征。通过再次训练随机生存模型,并选取28种代谢物,建立了显示出色预测能力(AUROC = 0.832,95% CI:0.697-0.9