将泛型添加到 Java™ 语言中增加了类型系统的复杂性,提高了许多变量和方法声明的冗长程度。因为没有提供 “typedef” 工具来定义类型的简短名称,所以有些开发人员转而把扩展当作 “穷人的 typedef”,结果收到了良好的效果。Java 专家 Brian Goetz 解释了这个 “反模式” 的限制。
对于 Java 5.0 中新增的泛型工具,一个常见的抱怨就是,它使代码变得太冗长。原来用一行就够的变量声明不再存在了,与声明参数化类型有关的重复非常讨厌,特别是还没有良好地支持自动补足的 IDE。例如,如果想声明一个 Map,它的键是 Socket,值是 Future<String>,那么老方法就是:
Map socketOwner = new HashMap();
比新方法紧凑得多:
Map<Socket, Future<String>> socketOwner
= new HashMap<Socket, Future<String>>();
当然,新方法内置了更多类型信息,减少了编程错误,提高了程序的可读性,但是确实带来了更多声明变量和方法签名方面的前期工作。类型参数在声明和初始化中的重复看起来尤其没有必要;Socket 和 Future<String> 需要输入两次,这迫使我们违犯了 “DRY” 原则(不要重复自己)。
合成类似于 typedef 的东西
添加泛型给类型系统增加了一些复杂性。在 Java 5.0 之前,“type” 和 “class” 几乎是同义的,而参数化类型,特别是那些绑定的通配类型,使子类型和子类的概念有了显著区别。类型 ArrayList<?>、ArrayList<? extends Number> 和 ArrayList<Integer> 是不同的类型,虽然它们是由同一个类 ArrayList 实现的。这些类型构成了一个层次结构;ArrayList<?> 是 ArrayList<? extends Number> 的超类型,而 ArrayList<? extends Number> 是 ArrayList<Integer> 的超类型。
对于原来的简单类型系统,像 C 的 typedef 这样的特性没有意义。但是对于更复杂的类型系统,typedef 工具可能会提供一些好处。不知是好还是坏,总之在泛型加入的时候,typedef 没有加入 Java 语言。
有些人用作 “穷人的 typedef” 的一个(坏的)做法是一个小小的扩展:创建一个类,扩展泛型类型,但是不添加功能,例如 SocketUserMap 类型,如清单 1 所示:
清单 1. 伪 typedef 反模式 —— 不要这么做
public class SocketUserMap extends HashMap<Socket<Future<String>> { }
SocketUserMap socketOwner = new SocketUserMap();
我将这个技巧称为伪 typedef 反模式,它实现了将 socketOwner 定义简化为一行的这一(有问题的)目标,但是有些副作用,最终成为重用和维护的障碍。(对于有明确的构造函数而不是无参构造函数的类来说,派生类也需要声明每个构造函数,因为构造函数没有被继承。)
伪类型的问题
在 C 中,用 typedef 定义一个新类型更像是宏,而不是类型声明。定义等价类型的 typedef,可以与原始类型自由地互换。清单 2 显示了一个定义回调函数的示例,其中在签名中使用了一个 typedef,但是调用者提供给回调的是一个等价类型,而编译器和运行时都可以接受它:
清单 2. C 语言的 typedef 示例
// Define a type called "callback" that is a function pointer
typedef void (*Callback)(int);
void doSomething(Callback callback) { }
// This function conforms to the type defined by Callback
void callbackFunction(int arg) { }
// So a caller can pass the address of callbackFunction to doSomething
void useCallback() {
doSomething(&callbackFunction);
}
扩展不是类型定义
用 Java 语言编写的试图使用伪 typedef 的等价程序就会出现麻烦。清单 3 的 StringList 和 UserList 类型都扩展了一个公共超类,但是它们不是等价的类型。这意味着任何想调用 lookupAll 的代码都必须传递一个 StringList,而不能是 List<String> 或 UserList。
清单 3. 伪类型如何把客户限定在只能使用伪类型
class StringList extends ArrayList<String> { }
class UserList extends ArrayList<String> { }
...
class SomeClass {
public void validateUsers(UserList users) { ... }
public UserList lookupAll(StringList names) { ... }
}
这个限制要比初看上去严格得多。在小程序中,可能不会有太大差异,但是当程序变大的时候,使用伪类型的需求就会不断地造成问题。如果变量类型是 StringList,就不能给它分配普通的 List<String>,因为 List<String> 是 StringList 的超类型,所以不是 StringList。就像不能把 Object 分配给类型为 String 的变量一样,也不能把 List<String> 分配给类型为 StringList 的变量(但是,可以反过来,例如,可以把 StringList 分配给类型为 List<String> 的变量,因为 List<String> 是 StringList 的超类型。)
同样的情况也适用于方法的参数;如果一个方法参数是 StringList 类型,那么就不能把普通的 List<String> 传递给它。这意味着,如果不要求这个方法的每次使用都使用伪类型,那么根本不能用伪类型作为方法参数,而这在实践当中就意味着在库 API 中根本就不能使用伪类型。而且大多数库 API 都源自本来没想成为库代码的那些代码,所以 “这个代码只是给我自己的,没有其他人会用它” 可不是个好借口(只要您的代码有一点儿用处,别人就有可能会使用它;如果您的代码臭得很,那您可能是对的)。
伪类型会传染
这种 “病毒” 性质是让 C 代码的重用有困难的因素之一。差不多每个 C 包都有头文件,定义工具宏和类型,像 int32、boolean、true、false,诸如此类。如果想在一个应用程序内使用几个包,而它们对于这些公共条目没有使用相同的定义,那么即使要编译一个只包含所有头文件的空程序,之前也要在 “头文件地狱” 问题上花好长时间。如果编写的 C 应用程序要使用许多来自不同作者的不同的包,那么几乎肯定要涉及一些这类痛苦。另一方面,对于 Java 应用程序来说,在没有这类痛苦的情况下使用许多甚至更多的包,是非常常见的事。如果包要在它们的 API 中使用伪类型,那么我们可能就要重新经历早已留在痛苦回忆中的问题。
作为示例,假设有两个不同的包,每个包都用伪类型反模式定义了 StringList,如清单 4 所示,而且每个包都定义了操作 StringList 的工具方法。两个包都定义了同样的标识符,这一事实已经是不方便的一个小源头了;客户程序必须选择导入一个定义,而另一个定义则要使用完全限定的名称。但是更大的问题是现在这些包的客户无法创建既能传递给 sortList 又能传递给 reverseList 的对象,因为两个不同的 StringList 类型是不同的类型,彼此互不兼容。客户现在必须在使用一个包还是使用另一个包之间进行选择,否则他们就必须做许多工作,在不同类型的 StringList 之间进行转换。对包的作者来说以为方便的东西,成为在所有地方使用这个包的突出障碍,除非在最受限的环境中。
清单 4. 伪类型的使用如何妨碍重用
package a;
class StringList extends ArrayList<String> { }
class ListUtilities {
public static void sortList(StringList list) { }
}
package b;
class StringList extends ArrayList<String> { }
class SomeOtherUtilityClass {
public static void reverseList(StringList list) { }
}
...
class Client {
public void someMethod() {
StringList list = ...;
// Can't do this
ListUtilities.sortList(list);
SomeOtherUtilityClass.reverseList(list);
}
}
伪类型通常太具体
伪类型反模式的进一步问题是,它会丧失使用接口定义变量类型和方法参数的好处。虽然可以把 StringList 定义成扩展 List<String> 的接口,再定义一个具体类型 StringArrayList 来扩展 ArrayList<String> 并实现 StringList,但多数伪 typedef 反模式的用户通常达不到这种水平,因为这项技术的目的主要是为了简化和缩短类型的名称。但结果是,API 的用处减少了并变得更脆弱,因为它们使用 ArrayList 这样的具体类型,而不是 List 这样的抽象类型。
更安全的技巧
一个更安全的减少声明泛型集合所需打字量的技巧是使用类型推导(type inference)。编译器可以非常聪明地使用程序中内嵌的类型信息来分配类型参数。如果定义了下面这样一个工具方法:
public static <K,V> Map<K,V> newHashMap() {
return new HashMap<K,V>();
}
那么可以安全地用它来避免录入两次参数:
Map<Socket, Future<String>> socketOwner = Util.newHashMap();
这种方法之所以能够奏效,在于编译器可以根据泛型方法 newHashMap() 被调用的位置推导出 K 和 V 的值。
结束语
伪 typedef 反模式的动机很简单 —— 开发人员想要一种方法可以定义更紧凑的类型标识符,特别是在泛型把类型标识符变得更冗长的时候。问题在于这个做法在使用它的代码和代码的客户之间形成了紧密的耦合,从而妨碍了重用。不喜欢泛型类型标识符的冗长是可以理解的,但这不是解决问题的办法。
转自http://www.ibm.com/developerworks/cn/java/j-jtp02216.html
分享到:
相关推荐
本文介绍了新方法内置了更多类型信息,减少了编程错误,提高了程序的可读性,但是确实带来了更多声明变量和方法签名方面的前期工作。类型参数在声明和初始化中的重复看起来尤其没有必要;Socket和Future
若说变量定义是为变量命名,而typedef(或称为类型定义)就是为类型命名。既然都是命名,那就会有很多类似的地方。而变量定义我想大家都会使用,因此类型定义也必然会使用。类型定义的语法可以归结为一句话:只要在...
通过`typedef`,程序员可以为已存在的数据类型或者复杂的类型定义一个新的名字,从而使得代码更加清晰、易于理解和维护。 #### 一、`typedef`的主要用途 ##### 1. 定义类型的别名 `typedef`最常见的用途之一就是...
`typedef`是C/C++语言中的一个重要关键字,用于给已存在的数据类型定义一个新的别名。通过`typedef`,可以使得程序更加清晰易懂,并有助于提高代码的可读性和可维护性。 #### 二、`typedef`的基本用法 ##### 1. ...
采用类似GPIO_TypeDef的方式,定义Led_TypeDef结构体类型,用结构体类型设置led参数,用固定占空比闪烁; 2. 使用说明: 通过修改宏定义可以修改开关时间; 通过查看头文件可以方便移植到不同硬件上; 3. 适合人群...
- **实现平台无关性**:可以定义特定于平台的类型,然后使用 `typedef` 给这些类型起一个通用的名字,这样编写的代码就可以在不同的平台上使用而不必关心具体的平台差异。 #### 三、`typedef` 的具体使用方法 1. ...
通过 `typedef`,我们可以为基本类型、结构体、联合体、枚举类型以及数组和指针定义新的类型名。以下是关于 `typedef` 的详细解释和应用: 1. **定义新的类型名**: `typedef` 关键字用于创建一个新类型名,它可以...
基于c语言的数据结构,一、使用typedef生成一个类型,并且使用该类型定义变量 定义步骤:(即类型替代变量,而后加typedef,所有的操作均在初始定义变量的形式上进行,即使用旧类型定义该变量的形式上进行)
在C语言中,typedef是一个关键字,其主要作用是为已经存在的数据类型定义一个新的别名。这种做法可以让代码更加清晰易懂,同时也方便了复杂类型的定义。typedef关键字不仅可以用于基本数据类型,还可以用于数组、...
在 C/C++ 中,struct 和 typedef struct 是两个常用的数据类型定义方式,它们之间有着微妙的区别。本文将详细介绍 struct 和 typedef struct 的用法,并通过实例代码演示其差异。 首先,让我们来了解一下 struct 的...
typedef 的主要作用是为一种数据类型定义一个新名字,这些数据类型包括内部数据类型(int、char 等)和自定义的数据类型(struct 等)。在编程中使用 typedef 的目的主要有两个:一是给变量一个易记且意义明确的新...
这里,`struct tagMyStruct`定义了一个新的结构体类型,而`typedef`则为这个结构体类型定义了一个新的名称`MyStruct`。之后,可以使用`MyStruct`来声明结构体变量: ```c MyStruct varName; ``` 这种方式不仅让...
`typedef` 最常见的用途之一就是定义易于记忆的类型名,这些类型名可以更好地反映变量的实际用途。例如: ```c typedef int size_t; // 定义一个整型的别名 size_t ``` 这样,当你需要表示某种“大小”时,可以...
typedef 是 C 和 C++ 语言中的一个关键字,用于为数据类型定义一个新的名称。这可以使得代码更加简洁和可读。通过使用 typedef,我们可以为基本数据类型、结构体、联合体等定义别名,这样在后续的代码中就可以使用这...
2. **`typedef`不适合用于定义变量**:虽然`typedef`可以用来定义新的类型名称,但它并不能直接用于定义变量。例如: ```cpp typedef int MyInt; MyInt a; // 正确 typedef MyInt b; // 错误 ``` 3. **`...
通过`typedef`,可以隐藏复杂的类型定义,使得代码更加清晰、简洁。 #### `typedef`的基本用法 `typedef`的基本语法结构如下: ```c typedef existing_type new_name; ``` 这里的`existing_type`是已经存在的...
使用`typedef`定义的新类型具有类型安全,这意味着如果你试图将一个`size`类型的变量赋值给一个`int`类型的变量,编译器会发出警告或错误。而使用`#define`定义的宏没有类型安全。 2. **表达式处理**: `#define...
`typedef` 在 C 语言中是一个非常有用的工具,它提供了定义类型别名的功能,从而让代码更加易读、可维护,特别是在处理复杂类型或者跨平台编程时。下面将详细阐述 `typedef` 的四个主要用途以及两个需要注意的陷阱。...