LoginSignup
2
2

More than 5 years have passed since last update.

Java と Groovy のアノテーションによる ToString の挙動まとめ

Last updated at Posted at 2017-07-01

これは?

仕事はsrc.mainJavasrc.testGroovyなんだけど、ちょっと別要件でsrc.mainGroovyで書いたら@ToStringまわりでハマったのでまとめメモ
果たしてどれくらい意味があるまとめかは不明

Groovyでクラス作って動かしてAOPでターミナルとログファイルに変数を出力しながら使うシステムなんだけど、全然ちゃんとToStringされてなかったので調べてみた

Java

Javaには@lombok.ToStringというアノテーションがある(要lombok

lombok.ToString
public @interface ToString {
    boolean includeFieldNames() default true;

    String[] exclude() default {};

    String[] of() default {};

    boolean callSuper() default false;

    boolean doNotUseGetters() default false;
}

ソースのjavadocの部分を除いた抜粋だけど、要点は上の設定項目

Class

普通はimportして使うけど、今回は明示したいのと掲載コード行を減らすために@lombok.ToStringみたいに書く

1 アノテーションなし

こんなのだれが見たいんだ

public class FooId1 {
    private final String value = "1234";
}
// to_string.j.c.FooId1@27c170f0

2 ただ付ける

楽だし、状況次第ではこれで十分

@lombok.ToString
public class FooId2 {
    private final String value = "1234";
}
// FooId2(value=1234))

3 フィールド名を無くす

後で例をだすけど、入れ子が多いクラス構造だとこっちの方が良い

@lombok.ToString(includeFieldNames = false)
public class FooId3 {
    private final String value = "1234";
}
// FooId3(1234)

4 アクセッサがあると使われる

手書きして変なアクセッサ書くことはまずないけど、事故りそうだし小ネタとして掲載

@lombok.ToString
public class FooId4 {
    private final String value = "1234";

    public String getValue() {
        return "5678";
    }
}
// FooId4(value=5678)

5 アクセッサを使わせない

一応こう書いておけば事故は防げる

@lombok.ToString(doNotUseGetters = true)
public class FooId5 {
    private final String value = "1234";

    public String getValue() {
        return "5678";
    }
}
// FooId5(value=1234)

2' 2の例の入れ子

ただ付けた場合、個人的には冗長

@lombok.ToString
public class Foo2 {
    private final FooId2 id = new FooId2();
    private final FooName2 name = new FooName2();
}
// Foo2(id=FooId2(value=1234), name=FooName2(value=John Doe))

3' 3の例の入れ子

Javaのクラスでは、これが個人的には一番良い

@lombok.ToString(includeFieldNames = false)
public class Foo3 {
    private final FooId3 id = new FooId3();
    private final FooName3 name = new FooName3();
}
// Foo3(FooId3(1234), FooName3(John Doe))

Enum

一応Enumも確認しておいた

1 アノテーションなし

Enumはこれで良い

public enum FooStatus1 {
    APPLYING, NOT_APPLYING
}
// NOT_APPLYING

2 ただ付ける

クラス名が出るのは良いんだけど、肝心な部分が含まれない

@lombok.ToString
public enum FooStatus2 {
    APPLYING, NOT_APPLYING
}
// FooStatus2()

Groovy

Groovyには@groovy.transform.ToStringというアノテーションがあり、こちらは言語が用意している

lombok.ToString
public @interface ToString {
    String[] excludes() default {};

    String[] includes() default {};

    boolean includeSuper() default false;

    boolean includeSuperProperties() default false;

    boolean includeNames() default false;

    boolean includeFields() default false;

    boolean ignoreNulls() default false;

    boolean includePackage() default true;

    boolean cache() default false;
}

同じく設定項目だけ抜粋

Class

Groovyは色々試すまでさっぱり分からなかった

1 アノテーションなし

同じく、これに需要はない

class FooId1 {
    private final String value = '1234'
}
// to_string.g.c.FooId1@3159c4b8

2 ただ付ける

値出てないんですけど!?
実質何も変わってないよね??

@groovy.transform.ToString
class FooId2 {
    private final String value = '1234'
}
// to_string.g.c.FooId2()

3 public にしてみる

privateが悪いかと思い、仕方なくpublicにしてみるが解決しない

@groovy.transform.ToString
class FooId3 {
    public final String value = '1234'
}
// to_string.g.c.FooId3()

4 アクセス修飾子を外す

なぜか解決した
が、privateとしたい

@groovy.transform.ToString
class FooId4 {
    final String value = '1234'
}
// to_string.g.c.FooId4(1234)

5 フィールドを含むと明示する

色々見ていたらフィールドはデフォルトだと含まないらしい

@groovy.transform.ToString(includeFields = true)
class FooId5 {
    private final String value = '1234'
}
// to_string.g.c.FooId5(1234)

6 フィールド名を含む

含むことも出来る

@groovy.transform.ToString(includeFields = true, includeNames = true)
class FooId6 {
    private final String value = '1234'
}
// to_string.g.c.FooId6(value:1234)

7 FQCN を含めない

ある程度の規模でちゃんとパッケージ構成を考えていると、FQCN があると長くなりすぎるので消したい

@groovy.transform.ToString(includeFields = true, includePackage = false)
class FooId7 {
    private final String value = '1234'
}
// FooId7(1234)

6' 6の例の入れ子

FQCNとフィールド名があると長すぎるかな

@groovy.transform.ToString(includeFields = true, includeNames = true)
class Foo6 {
    private final FooId6 id = new FooId6()
    private final FooName6 name = new FooName6()
}
// to_string.g.c.Foo6(id:to_string.g.c.FooId6(value:1234), name:to_string.g.c.FooName6(value:Jane Doe))

7' 7の例の入れ子

Groovyのクラスでは、これが個人的には一番良い

@groovy.transform.ToString(includeFields = true, includePackage = false)
class Foo7 {
    private final FooId7 id = new FooId7()
    private final FooName7 name = new FooName7()
}
// Foo7(FooId7(1234), FooName7(Jane Doe))

Enum

Enumに関してはJavaと全く同じ

1 アノテーションなし

Enumはこれで良い

public enum FooStatus1 {
    APPLYING, NOT_APPLYING
}
// NOT_APPLYING

2 ただ付ける

クラス名が出るのは良いんだけど、肝心な部分が含まれない

@lombok.ToString
public enum FooStatus2 {
    APPLYING, NOT_APPLYING
}
// FooStatus2()

まとめ

対応表

対応してそうな部分だけ整理

項目 Java Groovy 備考
フィールド名 includeFieldNames()
default true
includeNames()
default false
逆だ
フィールドを一部含む of()
default {}
includes()
default {}
この項目を指定する予定がないので
実例は省略した
フィールドを一部除く exclude()
default {}
excludes()
default {}
同上
継承時の挙動 callSuper()
default false
includeSuper()
default false
継承をしないので、実例は省略した
FQCN 出ない includePackage()
default true
ある意味逆

@groovy.transform.ToStringにはまだ項目があるが、対応する@lombok.ToStringの項目がないし今は興味も無いので省略する

Java 再掲

ClassFooと同じ指定方法、Enumにはアノテーションなしで統一

@lombok.ToString(includeFieldNames = false)
public class Foo {
    private final FooId id = new FooId();
    private final FooName name = new FooName();
    private final FooStatus fooStatus = FooStatus.NOT_APPLYING;
    private final BarStatus barStatus = BarStatus.NOT_APPLYING;
}
// Foo(FooId(1234), FooName(John Doe), NOT_APPLYING, NOT_APPLYING)

一番スッキリと要点だけが表示されると思う

Groovy 再掲

同じくClassFooと同じ指定方法、Enumにはアノテーションなしで統一

@groovy.transform.ToString(includeFields = true, includePackage = false)
class Foo {
    private final FooId id = new FooId()
    private final FooName name = new FooName()
    private final FooStatus fooStatus = FooStatus.NOT_APPLYING
    private final BarStatus barStatus = BarStatus.NOT_APPLYING
}

// Foo(FooId(1234), FooName(Jane Doe), NOT_APPLYING, NOT_APPLYING)

まったく同じに出来た!

おまけ

Groovy の Class に @lombok.ToString を使う

いつもはJavaでクラス作るんだけど、Groovyでクラス作ってアノテーションの部分だけjavaからコピーして持ってきたら混ざっちゃった

結論から言うと無意味

@lombok.ToString
class FooId1 {
    private final String value = '1234'
}

// to_string.r.g.FooId1@29ca901e

Java の Class に @groovy.transform.ToString を使う

逆は起き得ないと思うけど、一応やってみた

結論から言うとやはり無意味

@groovy.transform.ToString
public class FooId1 {
    private final String value = "1234";
}

// to_string.r.j.FooId1@27c170f0

Java のアノテーションの配列指定の仕方

これは@ToStringと言うよりはアノテーション全般の話なのかな?

Javaだと{x, y, ...}と指定する
これは知っていた

@lombok.ToString(includeFieldNames = false, of = {"spam", "egg"})
public class Python {
    private final String spam = "spam";
    private final String ham = "ham";
    private final String egg = "egg";
}
// Python(spam, egg)
  • 間違えがち(?)だけど、["spam", "ham"]ではない
    • これはInelliJなら赤線出るからすぐ気づける
  • {"spam"}"spam"と書ける
    • これは略記の話

Groovy のアノテーションの配列指定の仕方

これが最初わからなくて、groovy.transform.ToStringのアクセス修飾子の挙動も相まってとても混乱した

Groovyだと[x, y, ...]と指定する

@groovy.transform.ToString(includePackage = false, includes = ['spam', 'egg'])
class Python {
    private final String spam = 'spam'
    private final String ham = 'ham'
    private final String egg = 'egg'
}
// Python()

...!?
何も警告されてないのに、全然ダメな感じなんだけど何これ!

ってとてもハマったけど、今思うとincludeFields = trueが無いからだね
こんだけまとめたからすぐ理解できる様になってる自分がちょっと面白いw

includesを指定してもincludeFields立てないと意味ないのかよ!!分かりづらい!!!

ちなみに、includes = 'spam,egg'とかいう謎の記法もアリらしい
これって@groovy.transform.ToStringのルール?Groovyのルール?

Groovy の private フィールド

実はGroovyprivateはほぼ無意味
なのでprivateを外してただ@groovy.transform.ToStringを付けるだけでも実はまぁまぁ良かったりする

ちょっと一旦@groovy.transform.ToStringからは離れて、privateの挙動の整理

class Bar {
    String v1 = 'v1'
    public String v2 = 'v2'
    private String v3 = 'v3'
    static String v4 = 'v4'
    static public String v5 = 'v5'
    static private String v6 = 'v6'
}

こんなクラスを書いて

スクリーンショット 2017-07-01 21.25.57.png

こんな風に書くと、アクセス出来ちゃう

けど、IntelliJだと補完対象にされないし無理矢理書いても警告が出るので、一応privateと書いておきたい

スクリーンショット 2017-07-01 21.24.23.png
スクリーンショット 2017-07-01 21.25.21.png

v3v6は補完候補に出てこないし、書いてもv3v6はちょっと黄ばんでいるね!
ありがとうIntelliJ

参考

実は Enum の結果に不満

まとめのところで再掲した様に、同じ要素名を持つEnumが並ぶとやや分かりづらくなってしまう

// Foo(FooId(1234), FooName(John Doe), NOT_APPLYING, NOT_APPLYING)

もしAPPLYING, NOT_APPLYING, APPLYINGとか並ぶと「ん?2つめってなんだっけ?」ってなることがある

なので理想は下みたいになることなんだけど、どうもうまくいかなかった

// Foo(FooId(1234), FooName(John Doe), FooStatus.NOT_APPLYING, BarStatus.NOT_APPLYING)

こんなん書いたらできるけどねー
手書きはしたくないよねー

public enum BazStatus {
    APPLYING, NOT_APPLYING;

    @Override
    public String toString() {
        return getClass().getSimpleName() + "." + name();
    }
}
// BazStatus.NOT_APPLYING

@ToString以外でも色々と小さい不満があるのでアノテーション作りたいんだけど、こういうコードを生み出す系のアノテーションってどうやって作るんだろう
内容次第ではInelliJプラグインも必要になるし、オレオレならまだしも仕事でチームで使うのは難しいよなー

MetaClass が混じる

そういえばこのまとめ中見なかったので忘れていたけど、仕事中に対応した時はなんかそんなのが@groovy.transform.ToStringした結果に混じっていた
けどただのFQCN付きのハッシュで無駄に長いし自分で設定してないので中身に興味も無いので、邪魔だった

それでincludesの調査をしたんだったかな
アクセス修飾子、includeFieldsincludes、配列指定を一気に組み合わせちゃいながら確認したから混乱してしまった

おしまい

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2