Java中对于多个返回参数的选项是有限制的一种方法只能返回一个对象数组或原始函数和其他语言不同的是它不会提供一种简易方式来消耗方法调用中的参数实际上我们的选择是返回一个对象数组一个集合仅为返回的参数创建一个类或者最终将其发送到你打算替换的对象中所有这些方法都存在缺陷
使用对象数组
如果我们能够幸运地获取一套同类的返回参数那么对象的数组就会是一个带有例外的选项当我们打开这些对象时需要能分辨出每个参数从另一方面来说如果我们正在返回多个参数的不同类型我们需要使用所有超类对象的数组最有可能的就是对象本身然后我们要抛出每一个打开的参数我们已经丢失了类型安全性且返回参数命令出错的机会也可能增加
使用集合
与使用数组的方法类似我们或许也能创造一个集合来实现返回在集合之上使用数组主要是因为创建集合所需要代码的数量要比这段代码短
List< Object> retVal = new ArrayList< Object>();
retValadd(string);
retValadd(num);
retValadd(object);
return retVal;
而创建集合的代码要高于使用数组初始化设置
return new Object[] {string num object}
事实上在数组之上使用集合没有什么真正的优势除非我们要使用映射以便通过名称或其他要素来返回值
首次创建Java时其简单程度是对日趋复杂的c++的一种颠覆指针和内存管理被简化了包括去除参数间接常量函数指针以及其他功能强大但容易混淆的性能在c++中我们可以用值或参照传递参数这样可以对方法中的参照进行重新分配并为你提供参数或返回更多值的方式
使用JavaBeans
C++也支持的structs允许简单结构化的数据包当然Java类可以简单完成双倍于structs的任务但通常习惯以大量模板来扩大源码
使用类和JavaBeans惯例的时候还存在一个问题即对象本质上是易变的这些对象有可能被方法的调用者和方法调用的类共享这样就会导致易变状态的共享而这中情况无益于多线程系统
在Java中你能用值传递的只剩下方法参数了而且还不能是outparams同时方法只能返回一个参数看一下任意代码库就会发现大量的实例不过却不是十分有效
改进Java Beans方式
那么应该怎样做呢?Java类选项事实上才是解决方案的关键以及对其方式进行改进这些类可以成为structs更好的替代物
让我们为返回的类确定两个参数名称和出生日期
public class PersonNameDOB {
private String name;
private Date dob;
public Date getDob() {
return dob;
}
public void setDob(Date dob) {
thisdob = dob;
}
public String getName() {
return name;
}
public void setName(String name) {
thisname = name;
}
}
显然这是一个人为的例子我们有机会拥有一个已经定义的Person类大家肯定也会有类似的例子需要从方法中返回两个不同的对象但是却没有已经为其定义的类或者是返回的类夹带多余的信息或许是比这更糟的情况例如如果有人调用了你的方法来使用或修改返回对象中的值
上述情况所需代码更多因此我们可以做一些简单的修改
public class PersonNameDOB {
public final String name;
public final Date dob;
public PersonNameDOB(String name Date dob) {
thisname = name;
thisdob = dob;
}
}
其结果要短一些且更适合这项任务值已经返回了因此不需要setters了我们只要在返回对象建成后创建值就可以了它们不需要更改由于它们位于构造器中因此具有决定性作用现在任务已经完成将类的属性设为公开也没有风险了同理可以处理getters了其结果更短更易于使用
PersonNameDOB personNameDOB = SSNLookuplookupBySSN();
Systemoutprintln(personNameDOBname);
Systemoutprintln(personNameDOBdob);
lookupBySSN方法
public PersonNameDOB lookupBySSN(String ssn) {
Find the person record in the DB etc
return new PersonNameDOB(persongetName() persongetDOB());
}
如果这些太显而易见请耐心看下去我喜欢用这个方法来简化对象的返回这种类型是安全的因此在返回后不需要将对象抛出数组而最后属性的修改意味着这些返回的对象不会被滥用它们只是为了传输数据
为了安全起见建议你复制对象或使用不可改变的对象以应付值的意外修改在我们的例子中字符串是不可改变的但是日期可以复制
public PersonNameDOB lookupBySSN(String ssn) {
Find the person record in the DB etc
return new PersonNameDOB(persongetName() new Date(persongetDOB()getTime()));
}
这可以阻止调用者做接下来的操作
PersonNameDOB personNameDOB = SSNLookuplookupBySSN();
personNameDOBdobsetTime();
成对的需求
以上的模式是笔者经常在Java应用程序接口调用中用来替代structs的方法但是如果我们只是想返回两个类型对象这些就还不够看上去是唾手可得的东西其实仍然从JavaSE标准分配中神秘失蹤而这就是被原始化的Pair类看看我们如何从上述模式来建立Pair类
首先值要比名称和出生日期都普遍最普遍的是在将域名定为first和second
public class Pair {
public final String first;
public final Date second;
public Pair(String first Date second) {
thisfirst = first;
thissecond = second;
}
}
现在拥有了一个可以返回Strings和Dates对的通用类但还不包括其他类型将其扩展为通用类型
public class Pair< A B> {
public final A first;
public final B second;
public Pair(A first B second) {
thisfirst = first;
thissecond = second;
}
}
这样就好多了没必要担心代表了一对返回类型快捷方式的通配符这个类现在可以作为通用类型对来使用如
public static Pair< String Date> lookupBySSN(String ssn) {
// find the person in the DB
return new Pair(persongetName() new Date(persongetDOB()getTime()));
}
开始使用
Pair< String Date> personNameDOB = SSNLookuplookupBySSN();
Systemoutprintln(personNameDOBfirst);
Systemoutprintln(personNameDOBsecond);
Pair类还未完成
我们还要考虑类型是否真具有普遍性
我们不想其他人扩展或更改Pair类因为这可能破坏类的最初意图
新的new Pair()是可以的但是看上去有些不雅我们可以改进一下
Pair对于返回值是很有效的但是如果要作为映射中的关键要素就有些勉为其难
拥有用于调试或其他toString()用途的Pair字符串代表形式是非常好的
最后允许Pair和包含的对象可被序列化其内容也被假定为可以序列化了
因此让我们看看它是如何改变Pair执行的
public final class Pair< AB> implements Serializable {
private static final long serialVersionUID = L; // shouldnt
// need to change
public final A first;
public final B second;
private Pair (A first B second) {
thisfirst = first;
thissecond = second;
}
public static < AB> Pair< AB> of (A first B second) {
return new Pair< AB>(firstsecond);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != objgetClass()) {
return false;
}
final Pair other = (Pair) obj;
if (thisfirst != otherfirst &&
(thisfirst == null || !thisfirstequals(otherfirst))) {
return false;
}
if (thissecond != othersecond &&
(thissecond == null || !thissecondequals(othersecond))) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = ;
hash = * hash + (thisfirst != null ?
thisfirsthashCode() : );
hash = * hash + (thissecond != null ? thissecondhashCode() : );
return hash;
}
@Override
public String toString () {
return Stringformat(Pair[%s%s] firstsecond);
}
}
这样就已经将构造器私有化
但是提供了一个看起来更适合的静态of()方法
return Pairof(persongetName() new Date(persongetDOB()getTime()));
Pair类已经完成因此没有人可以取代它或改变类的原始意图它看上去很严格但却可以用于广泛的使用如果有人想让Pair以不同方式运作就应该写出适合自己的执行代码并对其进行求证
equals()和hash()方法意味着这个类不止可以用来返回值它们还可以成为映射的关键此处对使用该模式返回对象给出的建议是让IDE为我们创建equals和hashCode方法IDE往往都有可插入的模板其中包含已定义的最佳方法因此我们只需让NetBeans创建就可以了
toString()给出了pair的合适形式如 Pair[FredJonesSun Mar :: PDT ]这对于调试弹出很有用
这一个类现在执行Serializable相信此处我们的选择令人怀疑但是集合是可序列化的Pair应该也可以如果Pair中的类不能序列化那么就如果集合中的类不能序列化一样糟糕而Pair不应该阻止类中间的序列化
完成了吗?
既是又不是这个类现在在Navigenics中使用毫无疑问在其他Java应用程序中也存在类似的执行那我们完成了没有呢?从最具预见性的角度来讲我们完成了从任何时候都需要的角度来说恐怕还没完成
例如这类Pairs不能做比较增加一个compareTo()方法使之可比但是要注意应对复杂的通用类设计通常比较第一个值很容易如果它们相等就要比较第二个值这可能是最合适的行为但是Pair的每次使用都正确吗?我们要检查一下作比较的类是否具有可比性如果不具备该怎么办?
结论
为Java创建一个Pair类是有些难的而且在简单通用的类库执行中类库要求在少量功能前提下不改变Java语言通用性是很麻烦的一件事不过本人相信本文描述的Pair类至少是SE库中标准化Pair执行的良好开端但愿在Java 中会增加这一项尽管它对于其他语言中的Tuple来说是显得有些贫乏但是加入标准的Pair有望为我们带来大量实用性这种可读性的改进以及模板代码的删除都旨在减少Coin项目的语言变化