有经验的 Java 开发人员都知道写好 toString 方法很重要对可用人类理解的形式查看的对象进行记录和调试都相当容易当处理分布式应用程序时尤其是这样不幸的是为许多类实现 toString 方法可能相当耗时 尤其是对于那些具有许多属性的类因为 toString 的行为相当规则所以最好使创建该方法的任务能够自动完成本文提供的这个实用程序可以帮助您实现这一点同时也减轻了您在开发时的负担 参与大项目的开发人员通常要花数个小时编写有用的 toString 方法即便不为每个类都提供属于它们自己的 toString 方法但每个数据容器都必须有自己的 toString 方法让每个开发人员按他们自己的方法编写 toString 方法可能会造成混乱每个开发人员无疑都会提出一种唯一的格式结果在调试过程中使用这样的输出将增添不必要的麻烦而且也没有什么好处因此每个项目都应该为 toString 方法规定一种单一的格式并使它们的创建自动化 使 toString 的创建自动化 我下面将演示一个实用程序您可用它来实现 toString 的自动创建这个工具会自动为指定的类生成一个规则的强健的 toString 方法几乎消除了用于开发该方法的时间它还对 toString() 的格式进行集中管理如果您更改了格式则必须重新生成 toString 方法但是这仍然比手动更改成百上千个类要容易得多 对生成的代码进行维护也很容易如果您在类中添加了更多的属性则您也可能需要对 toString 方法作一些修改因为 toString 方法是自动生成的所以您只须再次对该类运行这个实用程序来完成更改这比手动方法更简单而且犯错误的可能性也较小 代码 本文无意解释 Reflection API下面的代码假定您已理解 Reflection 的基本概念要查看 Reflection API 的文档您可以访问参考资源部分实用程序的源代码如下所示 package fareedpublicationsutilities; import javalangreflect*; public class ToStringGenerator { public static void main(String[] args) { if (argslength == ) { Systemoutprintln(Provide the class name as the command line argument); Systemexit(); } try { Class targetClass = ClassforName(args[]); if (!targetClassisPrimitive() && targetClass != Stringclass) { Field fields[] = targetClassgetDeclaredFields(); Class cSuper = targetClassgetSuperclass(); // 检索超类 output(StringBuffer buffer = new StringBuffer();); // 构造缓沖区 if (cSuper != null && cSuper != Object for (int j = ; j < fieldslength; j++) { output(bufferappend(\ + fields[j]getName() + = \);); // 附加域名称 if (fields[j]getType()isPrimitive() || fields[j]getType() == Stringclass) // 检查基本数据类型或字符串类型 output(bufferappend(this + fields[j]getName() + );); // 附加基本数据类型域的值 else { /* 它不是基本数据类型域所以需要检查聚集对象的 NULL 值 */ output(if ( this + fields[j]getName() + != null ) ); output(bufferappend(this + fields[j]getName() + toString());); output(else bufferappend(\value is null\); ); } // else 结束 } // 循环结束 output(return buffertoString();); } } catch (ClassNotFoundException e) { Systemoutprintln(Class not found in the class path); Systemexit(); } } private static void output(String data) { Systemoutprintln(data); } } 代码输出通道 代码的格式还取决于您的项目工具需求某些开发人员可能喜欢将这些代码存入磁盘上用户定义的文件中而另一些开发人员对 systemout 控制台就很满意他们可以利用控制台手动将这些代码复制或嵌入实际的文件中我将这些选择权留给您本文只使用最简单的方法systemout 语句 这种方法的局限性 这种方法有两个明显的局限性第一个局限性是它不支持对象的循环包含如果对象 A 包含对象 B 的一个引用对象 B 又包含对象 A 的一个引用则这个工具无法处理但是对于许多项目而言这种情况很少出现 第二个局限性是添加或减少成员变量之后要求重新生成 toString 方法因为不管用不用这个工具都需要完成这一步所以这不是工具特有的问题 小结 在本文中我说明了一个小型的自动实用程序它可以真正提高开发人员的效率就整个项目的工期而言它起着很小但很重要的作用 补充技巧 这篇技巧发表以后我收到读者关于改进这段代码的几点建议在这部分补充内容中我将说明如何根据这些建议以及我自己的见解改进这个实用程序在参考资源部分可找到这些改进的源代码 改进 在最初的代码中我没有处理包含对象和基本数据的数组类型现在新代码可以处理数组数据但是这段代码只能处理一维数组而不能处理多维数组我现在还无法提供这个问题的一般解决方案因为就我所知Java 对数据类型的维数没有限制(只受可用内存的限制)我欢迎您提供解决方案 改进 最初我建议将这个工具用于开发时环境而不是运行时环境允许这个工具在运行时运行很方便但可能会占用更多的 CPU 周期但是对象转储/调试(toString() 的基本用途)通常是在开发时进行而在生产环境中要将其关闭在某些情况下如果某些项目可能将 toString() 用于业务逻辑则在生产环境中就可能不能关闭转储/调试我建议您根据具体项目决定是否将其关闭 在开发这个实用程序之前我已经想到了这种运行时灵活性首先我开发了一个单独的授权类客户端的类用它来生成 toString()这个类利用类似 return ToStringGeneratorgenerateToString(this) 的一个方法调用生成 toString其中 this 指向客户端类的当前实例这条语句放在 toString() 方法实现中但这个方法失败了因为 Reflection API 在运行时无法获取私有成员的值因此这个类只能用于公用成员这不是我所希望的 但后来 Sanscraint 先生指出如果将这段代码放在同一个调用类的一个方法中则同样的 Reflection API 代码就可以获取私有成员的值因此我修改了这个实用程序以便在运行时使用另外即使在目标类中新增或减少了任何属性您也不必更新或编辑 toString() 方法 改进 最初我用 this 前缀来访问生成代码中的成员变量但 Ye 先生指出这段代码也可用于静态方法甚至可用来输出静态成员这样更新后的代码现在既可处理类成员也可处理实例成员Ye 先生还指出一个错误(在此版本中已修正)这个错误导致为无属性的类生成毫无用处的代码 代码修改 在使这个实用程序可用于运行时以后我不得不在每个类中复制/粘贴这些方法因为新代码由多个方法组成所以这变得很困难我对此感到很沮丧 一种解决方案是创建一个接口/抽象基类这至少解决了方法签名问题但仍然需要进行复制/粘贴抽象基类还使客户类无法从另一个类继承出 但是内部类可以访问父类的私有成员这样在内部类的方法中运行的 Reflection 代码也可以获取私有值因此我决定将这个实用程序改为内部类它可被插入客户的任何父类中我还提供了 ToStringGeneratorExamplejava它将 ToStringGeneratorjava 用作内部类来实现 toString() 方法 |