kなんとかの日記 このページをアンテナに追加

2008-03-05

Java が使いにくいのは静的だからではない

| 03:53 |  Java が使いにくいのは静的だからではないを含むブックマーク

Java が使いにくい言語であるというのは、世界中の LL ファンが皆思っていることだろうから改めていうことでもないけど、使いにくいのは静的言語だからというのは間違っている。Java が使いにくいのは単に Java の設計者のセンスが悪かっただけであり、静的言語のせいではない。

たとえばこんなコード。

public Map<String, List<String>> example() {
  List<String> list = new ArrayList<String>();
  list.add("foo");
  list.add("bar");
  list.add("baz");
  Map<String, List<String>> map = new HashMap<String, List<String>>();
  map.put("names", list);
  return map;
}

まあなんといいますか。Map や List を表すリテラルがないせいで、記述が冗長になってしかたない。どうせ List と Map の 99 パーセントは ArrayList と HashMap なんだから、もうそろそろリテラルを用意してもいいと思う。型指定のせいでリテラルが難しいのであれば、「new ArrayList<String> {"foo", "bar", "baz"};」のような記述を用意するだけでもずいぶん違う。

追記(2008-03-07):「リテラル」という言葉に過剰に反応している人もいるが、ここでの趣旨は「本質的ではない記述を減らして Java を使いやすくする」ということなので、ライブラリで済むのであればそれで結構 (コメントを参照のこと)。また『リテラルを用意してもいいと思う』『リテラル難しいのであれば』と書いたように、絶対にリテラルを導入すべきだという主張ではない。あくまで、Java を使いやすくするための方法のひとつとして提案したまでであり、他の方法を否定してはいない。趣旨を勘違いしないでいただきたい。)


また <String> や <String, List<String>> が 1 行に 2 回出てくるのがうっとうしい。List#add() や List#put() は戻り値void だが、これは return this とするべき (StringBuffer#append() はそうなっているのにね)。せめてこう書けるようにしてほしかった。

public Map<String, List<String>> example() {
  var list = new ArrayList<String>();
  list.add("foo").add("bar").add("baz");
  var map = new HashMap<String, List<String>>();
  map.put("names", list);
  return map;
}

「var」という予約語が増やしているから互換性が問題になるけど、assert と同じでコンパイルオプションで on/off すればいいだろう。

あと、アクセッサね。いちいち getter と setter を定義しなきゃいけないのがバカバカしい。よく「IDE で自動生成すれば問題ない」という人がいるけど、そんなのが必要な時点で間違ってるとは思わないのだろうか。だいたい、本質的でないコードが何十行もある時点で、ソースコードの readability が下がってしまう。あんなもの、コード行数を水増しするための仕様だ。

世の Java 屋はこのコードを見ても、何も疑問に感じないそうだ。ありえん。

private int id;
private String name;
private Date updated;

public int getId() {
  return this.id;
}
public void setId(int id) {
  this.id = id;
}
public String getName() {
  return this.name;
}
public void setName(String name) {
  this.name = name;
}
public Date getUpdated() {
  return this.date;
}
public void setUpdate(Date date) {
  this.date = date;
}

これが次のように簡単になったら、なにか問題でもあるのだろうか。こっちのほうが書くのも簡単だし、なにより本質的でないコードがなくなるためたいへん読みやすい。

@accessor private int id;
@accessor private String name;
@accessor private Date updated;

こういった Java の使いにくさは、静的言語であることとは関係がない。Java の言語設計者は、仕様をコンパクトにすることには注意を払っていたけど (それも 1.5 で覆ったが)、使いやすさについてはあまり関心がなかったようだ。というか、Java に限らず Struts とか EJB とか見る限り、SUN や IBM には使いやすさに関するセンスがない。使いやすい product はほとんどが個人で作った OSS だ。

keisuke_nkeisuke_n 2008/03/06 16:54 私はJavaは静的言語である点も使いにくいと思ってます.
指摘にもあるように,
List<Foo> foos = new ArrayList<Foo>();
というのは型があるから冗長になってます.
もちろん大抵の場合は定義と生成した型は同じでよいので,
var list = new ArrayList<Foo>();
とかければいいんですけどね.
いや何が言いたいかと言うと,ただでさえ文法(特にリテラル)で使いにくいのにあわせて型があるせいでもっと使いにくくなってると.
そういう私はScalaに逃げてるわけですが(^^;
しかし,いろいろ解決方法はあって,例えば
class Tuple<A,B> {
A a; B b;
public Tuple(A a, B b) {
this.a = a; this.b = b;
}
}
class FooMap<A,B> extends HashMap<A,B> {
public FooMap(Tuple... values) {
...
}
public static Tulple<A.B> $(A a, B b) {
return new Tulple(a, b);
}
}
new FooMap<Foo,Boo>($(foo, boo), $(...))
こんなふうにするとか.

みずしまみずしま 2008/03/06 17:57 Javaの仕様が記述をコンパクトにすることに
感心が払われてなくて、その結果使い
にくくなってるというのはその通りだと
思います。ただ、ListとMapのリテラルに
関しては以下のようなライブラリを標準で
用意してくれれば済む話であって、言語仕様
を別に変える必要は無いと感じます(
keisukenさんのと似ていますが)。

import java.util.*;

public class CollectionUtil {
public static class Pair<A, B> {
public final A _1;
public final B _2;
public Pair(A _1, B _2) {
this._1 = _1;
this._2 = _2;
}
}
public static <T> List<T> $(T... elements) {
List<T> list = new ArrayList<T>();
for(T e : elements) list.add(e);
return list;
}
public static <K, V> Map<K, V> m(Pair<K, V>... entries) {
Map<K, V> map = new HashMap<K, V>();
for(Pair<K, V> e : entries) map.put(e._1, e._2);
return map;
}
public static <K, V> Pair<K, V> e(K key, V value) {
return new Pair<K, V>(key, value);
}
}

記述量を比較してみると、
リストの場合は

リテラル(Ruby風) : [”A”, ”B”, ”C”]
ライブラリ : $(”A”, ”B”, ”C”)

マップの場合
リテラル(Ruby風) : {”k1” => ”v1”, ”k2” => ”v2”, ”k3” => ”v3”}
ライブラリ : m(e(”k1”, ”v1”), e(”k2”, ”v2”), e(”k3”, ”v3”))

となり、両者ともに記述量は大差無いことが
わかります。というわけで、リスト/マップ
リテラルに関しては標準ライブラリとして
上記のようなクラスを提供してくれれば良くて、リテラルは特に入れなくても良いと
思います。あと、標準ライブラリの設計
と言語仕様の設計に関する批判は別であって
(無関係ではないですが)、いっしょくたに
するのはどうかなと思います。ライブラリ
の設計で回避できる問題を何でも言語仕様に
持ち込むと無意味に言語仕様が肥大化する
だけですし。getter/setterの記述が
冗長とかはライブラリではどうしようも
無い問題なので、言語仕様で解決するしか
無いと思いますが。

とまあ長々と書きましたが、色々とJavaの
仕様がビミョーなのは同意する
ところで、自分はこれからはScalaで色々
書いていこうと考えているところです。

no-nameno-name 2008/03/06 19:38 初めてコメントさせていただきます。
リストのリテラルはありませんが、
Arrays.<T>asList(a, b, c...) があって、そこそこ便利だと思います。

型指定については、型推論があれば良いのでしょうね。Haskellは便利でした。

kwatchkwatch 2008/03/07 08:21 keisuke_n さん:

>私はJavaは静的言語である点も使いにくいと思ってます.

静的な言語はメリットも多いので、自分としてはJavaが静的であることは評価しています。
ただご指摘にあるように、静的であっても工夫次第で使いやすくすることは可能なので、
Javaはもっとそのことに気づいてほしいと思います。
closure導入よりもほかにやるべきことがあるだろ、といつも思います。

>もちろん大抵の場合は定義と生成した型は同じでよいので,
>var list = new ArrayList<Foo>();
>とかければいいんですけどね.

寿命の長いインスタンス変数やクラス変数はこう書くわけにはいかないでしょうが、
寿命の短いローカル変数であればこの書き方でもあまり問題にならないと思います。
つまりこの書き方が ArrayList<Foo> list = new ArrayList<Foo>(); と解釈
されても、list がローカル変数であれば、ローカル変数は局所的なので、これでも
問題はないのではないかと思います。
ただ本当に問題ないと言い切るには考察不足なんですけど。

kwatchkwatch 2008/03/07 08:23 みずしまさん:

>ただ、ListとMapのリテラルに
>関しては以下のようなライブラリを標準で
>用意してくれれば済む話であって、言語仕様
>を別に変える必要は無いと感じます

ライブラリで済むのであれば、別にそれで構わないと思います。
このエントリの趣旨は、「Javaはもっと使いやすさに気を使うべき」ということなので、
それが解消されるなら、言語仕様で解決してもいいし、ライブラリで解決してもいいでしょう。

>両者ともに記述量は大差無いことが
>わかります

このエントリは、短く書くことが趣旨ではないので、そこは勘違いしないでください。
紹介いただいた方法は、確かに短く書けますが、個人的には不自然さを感じるので、
より自然に記述できるほうが嬉しいです。

>あと、標準ライブラリの設計
>と言語仕様の設計に関する批判は別であって
>(無関係ではないですが)、いっしょくたに
>するのはどうかなと思います。

繰り返しますが、このエントリの趣旨は「Javaはもっと使いやすさに気を使うべき」
ということです。Javaの言語仕様もライブラリも使いにくいのであれば、両方を
とりあげるのはおかしな話とは思いません。それを「いっしょくた」といわれるのは
理解しかねます。

kwatchkwatch 2008/03/07 08:26 no-name さん:

>Arrays.<T>asList(a, b, c...) があって、そこそこ便利だと思います。

これが返すArrayListは変更不可ではなかったですっけ。
もしそうなら、あまり便利とは思えません。

>型指定については、型推論があれば良いのでしょうね。Haskellは便利でした。

型推論は便利なのですが、使う人の敷居を上げてしまうと思います。
(今でも十分高いといえばそうですが。)
あと、classだけでなくinterfaceがあるJavaで、本当に型推論ができるのかどうかは興味あります。
ただ本格的な型推論じゃなくても、代入する値の型によってローカル変数の型を決めるくらいの機能でも十分使いやすくなると思っています。

みずしまみずしま 2008/03/07 11:50 いっしょくた、というのは口が滑りました。
失礼しました。ただ、ライブラリの導入で
改善できるならそれで良いわけで、それの
検討無しにリテラルを導入すべきという
結論はどうなのかなと思った次第です。

あと、記述の自然さという尺度は人によって
大きく異なりえるわけで、そのような尺度を
持ち出すならば、どういうものが自然である
かについて根拠が必要なのではないでしょう
か?少なくとも、個人的に不自然に見える
から十分でないというのはリテラル導入の
根拠としては弱いです。ちなみに、自分も
Mapの方はやや不自然だと感じますが、List
を作るための表記$(”A”, ”B”, ...)は特に
不自然だとは感じません。あと、追記しておくなら、別に僕は
リスト/マップリテラルを「導入すべきで
ない」とまでは思いません。

kwatchkwatch 2008/03/07 21:24 みずしまさん:
本文に追記しましたのでお読みください。

>ちなみに、自分も
>Mapの方はやや不自然だと感じます

どういうものが自然であるかについて根拠が必要なのではないでしょうか?

みずしまみずしま 2008/03/07 23:01 > 本文に追記しましたのでお読みください。

追記読みました。過剰反応と言われるのは心外ではありますが、kwatchさんの書かれた事の趣旨を誤解していたとは思います。

>> ちなみに、自分も
>> Mapの方はやや不自然だと感じます
> どういうものが自然であるかについて根拠が
> 必要なのではないでしょうか?

これはどういう意図なのでしょうか?
kwatchさんが

> 紹介いただいた方法は、確かに短く書け
> ますが、個人的には不自然さを感じる
> ので、より自然に記述できるほうが
> 嬉しいです。

のように書かれたので、自然と感じるかどうかは人によって異なるという端的な例として書いただけであって、この場合に、どういうものが自然であるかについて根拠を示す必要は無いと思いますが。

kfkf 2008/03/10 19:59 > Javaの言語仕様もライブラリも(中略)両方をとりあげるのはおかしな話とは思いません。
> それを「いっしょくた」といわれるのは理解しかねます。
理解はできますよ。(あなたのプライドが)受け入れてないか,理解不足であって。
私はみずしま氏の意見はなかなか良いと思う。所詮,自然かどうかなど主観の問題である。どうもkwatch氏は他人の意見が素直に聞けない人のようだ。
度量が狭いなら,コメント欄なんかつけなければよいのに。

NagiseNagise 2008/05/03 22:45 「List#add() や List#put() は戻り値が void だが、これは return this とするべき」
との記述がありますが、List#add()の戻り値はbooleanですし、List#put()に至っては、そのようなメソッドがそもそもありません。
ご報告まで。

kami_objectkami_object 2009/10/24 13:38 どうせ List と Map の 99 パーセントは ArrayList と HashMap なんだから

というのであれば、エビデンスを示してください。少なくとも私のソフトウェアではListに関しては99%がArrayListかも知れませんが、Mapに関してはそうでもありません。私はまず、LinkedHashMapを使うことから考えます。

議論が乱暴すぎます。

SampoSampo 2010/05/01 10:51 人これをC#とよぶ(笑)
デリゲートというかクロージャ&ラムダ式のことも書いてくれればよかったのに。

JVMコード吐くC#コンパイラあるといいですよね。

white-azaleawhite-azalea 2011/11/30 00:46 scala の List/Map と case class 使うと幸せになれそうです。