Javaのヒント99:toString()の作成を自動化する

大規模なプロジェクトに取り組んでいる開発者は、通常、便利なtoStringメソッドの作成に何時間も費やします。各クラスが独自のtoStringメソッドを取得していなくても、各データコンテナクラスは取得します。各開発者がtoString独自の方法で書くことを許可すると、混乱につながる可能性があります。各開発者は間違いなく独自のフォーマットを思い付くでしょう。その結果、デバッグ中に出力を使用することは必要以上に難しくなり、明らかな利点はありません。したがって、各プロジェクトはtoStringメソッドの単一の形式で標準化してから、メソッドの作成を自動化する必要があります。

toStringを自動化する

ここで、それを実行できるユーティリティについて説明します。このツールは、定期的で堅牢なものを自動的に生成します

toString

指定されたクラスのメソッド。メソッドの開発に費やされる時間をほぼ排除します。また、一元化されます

toString()

フォーマット。フォーマットを変更した場合は、を再生成する必要があります

toString

メソッド; ただし、これは、数百または数千のクラスを手動で変更するよりもはるかに簡単です。

生成されたコードの保守も簡単です。クラスに属性を追加する場合は、toStringメソッドにも変更を加える必要がある場合があります。toStringメソッドの生成は自動化されているため、変更を加えるには、クラスでユーティリティを再度実行するだけで済みます。これは、手動によるアプローチよりも簡単で、エラーが発生しにくくなります。

コード

この記事は、ReflectionAPIを説明するためのものではありません。次のコードは、Reflectionの背後にある概念を少なくとも理解していることを前提としています。あなたは訪問することができます

リソース

ReflectionAPIのドキュメントのセクション。ユーティリティは次のように記述されています。

パッケージfareed.publications.utilities; importjava.lang.reflect。*; public class ToStringGenerator {public static void main(String [] args){if(args.length == 0){System.out.println( "コマンドライン引数としてクラス名を指定してください"); System.exit(0); } try {Class targetClass = Class.forName(args [0]); if(!targetClass.isPrimitive()&& targetClass!= String.class){フィールドfields [] = targetClass.getDeclaredFields();クラスcSuper = targetClass.getSuperclass(); //スーパークラスを取得するoutput( "StringBuffer buffer = new StringBuffer(500);"); //バッファの構築if(cSuper!= null && cSuper!= Object.class){output( "buffer.append(super.toString());"); //スーパークラスのtoString()} for(int j = 0; j <fields.length; j ++){output( "buffer.append(\" "+ fields [j]。getName()+ "= \"); "); //フィールド名を追加if(fields [j] .getType()。isPrimitive()|| fields [j] .getType()== String.class)//プリミティブまたは文字列をチェックしますoutput( "buffer.append(this。" + fields [j] .getName()+ ");"); //プリミティブフィールドの値を追加しますelse {/ *プリミティブフィールドではないため、これには、集約されたオブジェクトのNULL値のチェックが必要です* / output( "if(this。" + fields [j] .getName()+ "!= null)"); output( "buffer.append(this。" + fields [j] .getName()+ ".toString());"); output( "else buffer.append(\" value is null \ ");");} // elseの終わり} // end of for loop output( "return buffer.toString();");}} catch(ClassNotFoundException e){System.out.println( "クラスがクラスパスに見つかりません"); System.exit(0);}} private static void output(String data){System.out.println(data); }}

コード出力チャネル

コードの形式は、プロジェクトツールの要件によっても異なります。一部の開発者は、ディスク上のユーザー定義ファイルにコードを含めることを好む場合があります。他の開発者は満足しています

system.out

コンソール。コードをコピーして実際のファイルに手動で埋め込むことができます。これらのオプションはあなたに任せて、最も簡単な方法を使用します。

system.out

ステートメント。

アプローチの制限

このアプローチには2つの重要な制限があります。1つ目は、サイクルを含むオブジェクトをサポートしていないことです。オブジェクトAにオブジェクトBへの参照が含まれ、オブジェクトBへの参照が含まれている場合、このツールは機能しません。ただし、そのようなケースは多くのプロジェクトではまれです。

2番目の制限は、メンバー変数を加算または減算するには、toStringメソッドを再生成する必要があることです。これはツールの有無にかかわらず行う必要があるため、このアプローチに固有の問題ではありません。

結論

この記事では、開発者の生産性を実際に向上させ、プロジェクト全体のタイムラインの短縮に小さいながらも重要な役割を果たすことができる小さな自動化ユーティリティについて説明しました。


フォローアップのヒント

このヒントが公開された後、コードを改善する方法について読者からいくつかの提案を受けました。このフォローアップでは、これらの提案と私自身の洞察に基づいてユーティリティを更新した方法について説明します。これらの改善のソースコードは、リソースにあります。

Sangeeta Varmaによって提案された改善#1

元のコードでは、オブジェクトの配列型とプリミティブデータ型を処理しませんでした。新しいコードが配列データを処理するようになりました。ただし、コードは1次元配列にしか適用されず、複数次元配列では機能しません。私の知る限り、Javaのデータ型の次元数に制限はありません(唯一の制限は使用可能なメモリです)ので、この問題の一般的な解決策を思い付くことができませんでした。解決策についてご意見をお寄せいただければ幸いです。

改善#2、ChrisSanscraintによる提案

もともと私は、ランタイム環境ではなく、開発時にユーティリティを提案しました。ユーティリティを実行時に実行できるようにすることは非常に便利ですが、CPUサイクルがさらに数回かかる場合があります。ただし、オブジェクトのダンプ/デバッグ(の基本的な使用toString())は通常、開発時に実行され、実稼働環境ではオフになります。一部のプロジェクトではtoString()ビジネスロジックの目的で使用される場合があるため、本番環境でのこのスイッチオフが適用できない場合があります。プロジェクトごとにその決定を行うことをお勧めします。

このユーティリティを開発する前に、私はすでにこのランタイムの柔軟性を念頭に置いていました。最初に、クライアントクラスがを生成するために使用する個別の委任クラスを開発しましたtoString()。クラスは、のようなメソッド呼び出しを使用してそれを生成しましたreturn ToStringGenerator.generateToString(this)。ここthisで、クライアントクラスの現在のインスタンスを指し、コードステートメントはtoString()メソッド実装で記述されます。しかし、Reflection APIには実行時にプライベートメンバーの値を取得する機能がないため、このアプローチは失敗しました。ですから、このクラスは私が望まなかった公のメンバーだけに役立ちました。

しかし、Sanscraint氏は、コードが同じ呼び出し元クラスのメソッド内に記述されている場合、同じReflectionAPIコードが実行時にプライベートメンバーの値を取得することを指摘しました。そのため、実行時に使用するユーティリティを更新しました。さらに、toString()ターゲットクラスの属性を減算または追加するために、メソッドを更新または編集する必要はありません。

Eric Yeによって提案された改善#3

もともとthis、生成されたコードでメンバー変数へのアクセスにプレフィックスを使用していましたが、Ye氏は、コードが静的メソッドで使用されたり、静的メンバーを出力したりすることもできると指摘しました。したがって、更新されたコードは、クラスメンバーとインスタンスメンバーの両方を処理できるようになりました。Ye氏はまた、このバージョンで修正されたバグを特定しました。このバグにより、クラスは属性のないクラスに対して役に立たないコードを生成していました。

コードの変更

ユーティリティをランタイム対応にした後、各クラスのメソッドをコピーして貼り付ける必要があることに不満を感じました。これは、新しいコードが複数のメソッドで構成されていたために困難になりました。

1つの解決策は、少なくともメソッドシグネチャの問題を解決するインターフェイス/抽象基本クラスを作成することですが、それでもコピー/貼り付けが必要になります。抽象基本クラスソリューションは、クライアントが別のクラスから派生することも制限します。

ただし、内部クラスには親クラスのプライベートメンバーにアクセスする機能があるため、そのメソッド内で実行されているリフレクションコードもプライベート値を取得できます。そこで、ユーティリティを、任意の親クライアントクラスに挿入できる内部クラスに変更することにしました。また、toString()メソッドを実装するための内部クラスとしてToStringGenerator.javaを使用するToStringGeneratorExample.javaも提供しました。

最後に、このアプローチを改善するための提案をしてくれた人々に感謝したいと思います。

Syed Fareed Ahmadは、パキスタンのラホールに住むJavaプログラマー、デザイナー、アーキテクトです。彼は、Java(サーブレット、JSP、EJB)、WebSphere、およびXMLベースのe-ビジネスソリューションの開発に携わっています。

このトピックの詳細

  • フォローアップソースコード用

    //images.techhive.com/downloads/idge/imported/article/jvw/2000/08/jw-javatip99.zip

  • SunのWebサイトにあるReflectionドキュメント

    //java.sun.com/products/jdk/1.1/docs/guide/reflection/index.html

  • 以前のJavaのヒントをすべて表示し、独自のヒントを送信します

    //www.javaworld.com/javatips/jw-javatips.index.html

このストーリー「JavaTip99:Automate toString()の作成」は、もともとJavaWorldによって公開されました。