第29条:优先考虑类型安全的异构容器

介绍了一种类型安全的异构容器实现方式,该容器能够存储不同类型的数据,并通过泛型确保类型安全。通过示例代码展示了如何创建和使用这种容器。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

术语 :




        有时会需要未限定固定数目的类型参数的容器,此时,可以将容器的键进行参数化而不是将容器参数化。然后将参数化的键交给容器来插入或者获得值。用泛型系统来确保值的类型和它的键相符。示例代码如下:

// Typesafe heterogeneous container pattern - implementation
public class Favorites {
	private Map<Class<?>, Object> favorites = 
			new HashMap<Class<?>, Object>();
	public <T> void putFavorite(Class<T> type, T instance) {
		if (type == null)
			throw new NullPointerException("Type is null");
		favorites.put(type, instance);
	}
	public <T> T getFavorite(Class<T> type) {
		return type.cast(favorites.get(type));
	}
}

// Demo: Typesafe heterogeneous container pattern - client
public static void main(String[] args) {
	Favorites f = new Favorites();
	f.putFavorite(String.class, "Java");
	f.putFavorite(Integer.class, 0xcafebale);
	f.putFavorite(Class.class, Favorites.class);
	String favoriteString = f.getFavorite(String.class);
	int favoriteInteger = f.getFavorite(Integer.class);
	Class<?> favoriteClass = f.getFavorite(Class.class);
	System.out.printf("%s %x %s%n", favoriteString, favoriteInteger,
			                        favoriteClass);
}
        上面的代码中,class方法返回的是一个class<T>的形式。Favorites是类型安全的,它总是按照键返回正确的值,同时它也是异构的,因为它的容器的键不是同一种类型,这有别于传统的Map,因此,将Favorites称作类型安全的异构容器。异构来自哪?答案是无限制通配符的键Class<?>,在这里它仅代表是某种class,因此允许将不同类的class放入同一个Map,这就是异构的原因。注意,因为我们使用的值类型是Object,因此Map并无法保证键一对能对应正确的值,它只知道值是一个Object就可以了,这种对应的关系是实现者自己来确保的。手动重新建立类型与值之间的关系是在getFavorite方法中进行的,利用Class的cast方法,将对象动态地转换成Class对象所表示的类型。cast只是检验它的参数是不是为Class对象所表示的类型的实例。如果是,就返回参数,如果不是就抛出ClassCastException。

public class Class<T> {
	T cast(Object obj);
}
        Favorites类有两种局限:一是恶意用户可以通过使用原生态形式的Class来破坏年Favorites实例的类型安全。这种方式可以通知在putFavorite中进行类型检查来确保实例对象进行检查。

	public <T> void putFavorite(Class<T> type, T instance) {
		if (type == null)
			throw new NullPointerException("Type is null");
		favorites.put(type, type.cast(instance);
	}
        第二个局限性在于它不能用在不可具体化的类型中。比如说可以存储喜爱的String,String[],但是不能存储List<String>。因为 List<String>.class是语法错误。因为在运行时他们的类型会被擦除,所在List<String>与List<Integer>实际上是共用一个Class。如果需要限制些可以传递给方法的类型,则可以使用有限制的通配符类型。
       
public <T extends Annotation>
      T getAnnotation(Class<T> annotationType);
        在这面这段代码里,如果想把一个Class<?>传递给getAnnotation方法,那么按照要求,可能想到可以将其转换成Class<? extends Annotation>,但是这种行为是非受检的,会收到编译器的警告,但是,可以利用Class类提供的一个安全且动态地执行这种转换的实例方法,asSubclass,它将调用它的Class对象转换成用其参数表示的类的一个子类型,如果转换成功,该方法就返回它的参数,如果失败则抛出ClassCastException。见如下例子:

public <T extends Annotation>
      T getAnnotation(Class<T> annotationType);

// Use of asSubclass to safely cast to a bounded type token
static Annotation getAnnocation(AnnocationElement element,
		                        String annotationTypeName) {
	Class<?> annotationType = null;// Unbounded type token
	try {
		annotationType = Class.forName(annotationTypeName);
	} catch (Exception ex) {
		throw new IllegalArgumentException(ex);
	}
	return element.getAnnotation {
		annotationType.asSubclass(Annotation.class);
	}
}

什么是ASCII码?ASCII码是美国信息交换标准代码的缩写,最初由美国国家标准学会(ANSI)在1960年代提出,旨在为计算机、通信设备之间的数据交换提供统一的字符编码标准。ASCII码使用7位二进制数来表示128个字符,覆盖了包括控制字符、标点符号、数字、字母和一些特殊符号。ASCII码的特点标准化:ASCII为数字、字母和特殊符号提供了统一的编码方案,方便不同系统之间的数据交换。简洁性:采用7位编码,共支持128个字符。扩展性:后续扩展了8位ASCII码(Extended ASCII),支持256个字符,以应对更多语言和符号需求。ASCII码的发展历史1. 早期的字符编码在ASCII码出现之前,不同的计算机厂商和电信系统使用各自的编码方案,如IBM的EBCDIC(扩展二进制编码十进制交换码)。这些编码方案往往只能在特定设备上使用,缺乏互通性,导致设备之间的数据交换困难。2. ASCII的诞生随着计算机和通信行业的迅速发展,亟需一个统一的字符编码标准来实现不同设备之间的数据交换。1963年,美国国家标准学会(ANSI)发布了ASCII码的早期版本,定义了包括26个英文字母(大小写)、10个数字、标点符号和一些控制字符的编码。3. ASCII码的推广与扩展随着时间的推移,ASCII码成为美国及国际上的广泛采用标准。到了1980年代,随着个人电脑的普及和多语言需求的增加,ASCII码扩展为8位(即Extended ASCII),支持256个字符,以便涵盖更多的符号和国际语言字符。4. 现代字符编码标准的发展虽然ASCII码至今仍然在许多场景下被使用,但随着全球化的发展和多语言支持需求的增加,Unicode等更为通用的编码标准得到了广泛应用。然而,ASCII码作为字符编码的基础,依然是其他编码标准的重要组成部分。ASCII码的基本结构ASCII码由7位二进制数表示,每个字符的二进制值可以从0到127。其结构大致分为以下几类:控制字符(0–31和127):控制字符用于管理数据流,例如回车(Carriage Return, CR)、换行(Line Feed, LF)等。可打印字符(32–126):这些字符包括空格、标点符号、数字、字母等,主要用于文本文字的表示。扩展字符(128–255, Extended ASCII):这部分字符在标准ASCII中并不存在,但在扩展ASCII中被定义,用于支持国际化字符集、图形符号等。控制字符示例0 (NUL):空字符,通常用于字符串的终止。9 (TAB):水平制表符,用于对齐文本。13 (CR):回车符,通常用于光标回到行首。可打印字符示例32:空格字符。65 (A):大写字母A。97 (a):小写字母a。48 (0):数字0。全量ASCII码表解析下面列出ASCII码表中每个字符的对应值及其二进制表示。十进制 二进制 字符 描述0 0000000 NUL 空字符1 0000001 SOH 标题开始2 0000010 STX 正文开始3 0000011 ETX 正文结束… … … …65 1000001 A 大写字母A66 1000010 B 大写字母B67 1000011 C 大写字母C… … … …97 1100001 a 小写字母a98 1100010 b 小写字母b99 1100011 c 小写字母c完整ASCII码表可以帮助我们快速查找特定字符的编码值,或者通过编码值反推出具体的字符。ASCII码的应用场景1. 文本文件的存储在文本文件中,每个字符都对应一个ASCII码。例如,文本文件中存储的字符串"Hello",实际上在底层被编码为72 101 108 108 111。2. 网络通信ASCII码广泛用于网络协议中的数据传输。例如,HTTP协议中的请求和响应头信息都采用ASCII编码。3. 编程语言的字符串处理在编程语言中,字符串的底层通常使用ASCII编码。例如,在C语言中,字符串实际上是一个以NUL字符结束的ASCII字符数组。4. 控制字符在终端和打印机中的应用ASCII码中的控制字符广泛应用于早期的终端和打印机控制,例如换行符(LF)和回车符(CR)用来控制输出的格式。ASCII码与其他编码标准的关系1. 与Unicode的关系Unicode是现代字符编码的主流标准,支持几乎所有语言的字符。ASCII码作为其子集,Unicode中前128个字符与ASCII码保持完全一致。也就是说,在支持Unicode的系统中,ASCII字符可以直接兼容使用。2. 与扩展ASCII的关系扩展ASCII(Extended ASCII)通过使用8位编码扩展了原有的ASCII码,新增了128个字符,用于表示更多的符号和国际化字符。然而,扩展ASCII并没有得到统一的标准,具体字符集可能因地区或操作系统而异。3. 与EBCDIC的区别EBCDIC(扩展二进制编码十进制交换码)是IBM在早期大型机系统中使用的一种编码方式,与ASCII完全不同。EBCDIC的字符顺序与ASCII不兼容,虽然它也使用7位或8位编码,但字符的排列和用途完全不同。结语ASCII码作为字符编码的基础标准,尽管在全球化和多语言支持的背景下逐渐被Unicode等更为广泛的编码标准取代,但它仍然在许多计算机系统中扮演着重要的角色。掌握ASCII码表及其应用,可以帮助开发者更好地理解计算机系统中的字符处理机制,提高编程效率。在现代计算机中,了解ASCII不仅是基础知识,还能帮助我们更好地理解其他复杂的字符编码标准,为更高效的系统设计和编码实践打下坚实的基础。希望通过这篇文章,大家能对ASCII码有更深入的认识,并能在实际开发中应用这些知识。
最新发布
04-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值