これは?
仕事はsrc.mainがJava、src.testがGroovyなんだけど、ちょっと別要件でsrc.mainをGroovyで書いたら@ToStringまわりでハマったのでまとめメモ
果たしてどれくらい意味があるまとめかは不明
Groovyでクラス作って動かしてAOPでターミナルとログファイルに変数を出力しながら使うシステムなんだけど、全然ちゃんとToStringされてなかったので調べてみた
Java
Javaには@lombok.ToStringというアノテーションがある(要lombok)
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というアノテーションがあり、こちらは言語が用意している
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 再掲
ClassはFooと同じ指定方法、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 再掲
同じくClassはFooと同じ指定方法、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 フィールド
実はGroovyのprivateはほぼ無意味
なので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'
}
こんなクラスを書いて
こんな風に書くと、アクセス出来ちゃう
けど、IntelliJだと補完対象にされないし無理矢理書いても警告が出るので、一応privateと書いておきたい
v3とv6は補完候補に出てこないし、書いてもv3とv6はちょっと黄ばんでいるね!
ありがとう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の調査をしたんだったかな
アクセス修飾子、includeFields、includes、配列指定を一気に組み合わせちゃいながら確認したから混乱してしまった
おしまい


