【Java6から】古いJavaのサンプルコードを用意してモダン化してみる【Java10まで】

勉強用にやってみた時のメモ。アップデートと共に書き方が変わるのは Java に限った話ではありませんが、Java は特に昔と今で書き方を大きく変えられる言語な印象があります。Java の勉強中にネット記事を眺めていても、バージョンの異なる色々な書き方が混在していたりして結構混乱しました。どれが昔の書き方でどれが今風の書き方なのかをざっくりと確認するために、古めかしい Java サンプルコードを用意してステップを踏んで今風に書き換えてみます。

サンプルコード

public static String method() {
    BufferedReader reader = null;
    try{
        reader = new BufferedReader(new InputStreamReader(new FileInputStream(
                new File("test.txt")), "UTF-8"));

        Set<String> set = new HashSet<String>();
        String line;
        while((line = reader.readLine()) != null){
            if(line.length() >= 7) {
                set.add(line.toLowerCase());
            }
        }
        List<String> list = new ArrayList<String>(set);
        Collections.sort(list);

        StringBuilder builder = new StringBuilder();
        for(String s : list){
            builder.append(s);
        }
        String str = validate(builder.toString());
        if(str != null){
            str = str.replace("a","b");
        }
        return str;
    }catch (IOException e){
        e.printStackTrace();
    }finally{
        if(reader != null){
            try{
                reader.close();
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
    return null;
}

public static String validate(String str){
    return str.length() >= 30 ? "【" + str + "】" : null;
}

こいつを最終的に以下のようにします。

public static Optional<String> method() {
    try (Stream<String> lines = Files.lines(Paths.get("test.txt"))){
        var list = lines.distinct()
                .filter(s -> s.length() >= 7)
                .map(s -> s.toLowerCase())
                .sorted()
                .collect(Collectors.toList());

        var str = validate(String.join("", list));
        return str.map(s -> s.replace("a", "b"));
    } catch(IOException e){
        e.printStackTrace();
    }
    return Optional.empty();
}

public static Optional<String> validate(String str){
    if(str.length() < 30){
        str = null;
    }else{
        str = "【" + str + "】";
    }
    return Optional.ofNullable(str);
}

便宜上、サンプルコードではロジックとして冗長だったり無意味な記述がありますがあしからず。大した変更はしていないのですが、それでもコードの見通しが良くなっていると思います。が、今風に書くのが必ず正しいとは限らないという前提は置いておきます。

method() はファイルから文字列を読み込んで、それを加工して返しているだけです。validate() は引数にとった文字が30文字以上なら両端に【】を付けたものを返し、30文字以下なら null を返すメソッドです。

以下、本題。

try-with-resources , NIO2 を使う

上から順に書き換えていきます。まずは以下の部分。

BufferedReader reader = null;
    try{ 
        reader = new BufferedReader(new InputStreamReader(new FileInputStream(
                new File("test.txt")), "UTF-8"));
        ...
    }catch(IOException e){
        ...
    }finally{
        if(reader != null){
            try{
                reader.close();
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }

Java7 から使える try-with-resources を使えば、この冗長な記述をスッキリできます。try-with-resources は、IO等のリソースの変数を try の () 内に書くことで、コード側でリソースの close 処理を意識しなくてよくなる仕組みです。また、これがないと上記のように finally 部分で reader の close 処理をするためにまた try 文を書くというやたら面倒くさい記述になります。

ちなみに、Java9 までは try の () 内で変数を宣言しなければなりませんでしたが、9からは予め宣言しておいたリソース用の変数を try の () 内に書くだけでもOKになったみたいです。宣言時に右辺で例外が投げられる可能性がある場合には () 内で宣言したほうがスッキリそうですが。

try-with-resources を使うと以下のようになります。

try(BufferedReader reader = new BufferedReader(
    new InputStreamReader(new FileInputStream(new File("test.txt")), "UTF-8"))){
    ...
}catch (IOException e){
    ...
}

これで reader の閉め忘れの心配も減ります。以下は try-with-resources の解説記事。

The try-with-resources Statement

また、Java7 からは NIO2(New I/O 2) と呼ばれる新たなファイル操作系のライブラリが追加されました。これにより、上のコードは次のように書き換えられます。

try(BufferedReader reader = Files.newBufferedReader(Paths.get("test.txt"))){
    ...
}catch (IOException e){
    ...
}

従来のコードでは、File クラスを使ってファイルの指定や操作をしていました。NIO2 ではファイルの存在場所を表す Path , Paths と、ファイル操作をするための Files に役割が分割されています。上のコードでは、Files の newBufferedReader() というファクトリメソッドに Path オブジェクトを渡して BufferedReader を生成しています。

従来の File クラスは今のところは非推奨になっていませんが、今後は NIO2 が主流になり File がサポートされなくなることが無きにしも非ずと思うので、積極的に使っていきたいところです。ちなみに NIO2 では、File では難しいメタデータへのアクセスやファイルの監視といったことが容易にできるようになっています。以下は NIO2 の解説記事。

The Java NIO.2 File System in JDK 7

StreamAPI を使う

次に、以下の部分について、StreamAPI を使って書き換えてみます。ここでやっているのは、ファイルから行を読み出して、7文字以上であれば小文字化してリストに追加し、ソートするというものです。リストは要素の重複を許さないという架空の要件を設定しているので、一旦 Set に入れてから ArrayList に変換しています。

try (BufferedReader reader = Files.newBufferedReader(Paths.get("test.txt"))){
    String line;
    Set<String> set = new HashSet<String>();
    while((line = reader.readLine()) != null){
        if(line.length() >= 7) {
            set.add(line.toLowerCase());
        }
    }
    List<String> list = new ArrayList<String>(set);
    Collections.sort(list);
    ...
} 

StreamAPI は Java8 で追加された機能で、これを使うと要素集合に対する計算操作をイイ感じに書けるようになります。今さっき書き換えたばかりの try の () 内も、StreamAPI で書くためにまた書き換えます。書き換えると次のようになります。

try (Stream<String> lines = Files.lines(Paths.get("test.txt"))){
    List<String> list = lines.distinct()
         .filter(s -> s.length() >= 7)
         .map(s -> s.toLowerCase())
         .sorted()
         .collect(Collectors.toList());
    ...
}

もはや先のコードの跡形もありません。1行目では、Files.lines() でファイルの内容(行の集合)をストリームに格納しています。そして次の行以降で加工操作をしています。distinct() で重複を排除し、filter() で7文字未満の行を排除し、map() で小文字に変換し、sorted() でソートし、最後に collect() で List 化しています。これは、先のコードよりも直感的に理解できるようになっている印象です。filter() や map() の引数で使われているのはラムダ式と呼ばれるもので、メソッドに処理内容を渡しているイメージになります。コールバック処理に近い感じでしょうか。これも Java8 で導入されました。

このように、StreamAPI とラムダ式を組み合わせると、従来の Java らしからぬコードが書けるようになります。かといって、何でもかんでもこれらの書き方をするのが正義かというと、難しいところです。上のコードぐらいなら StreamAPI 等を使うことで可読性を上げられそうですが、複雑な処理を無理やりこの書き方で書くと結構しんどいことになりそうです。なので、特にチーム開発などでは用法用量を定めて(守って)適当に使うのが良さそうです。

ちなみに、単純な配列をストリーム化した場合などはストリームの close について意識する必要はないのですが、上記のように IO リソースを扱うようなストリームを生成した場合には close 処理が必要なので、try-with-resources で囲んでいます。

以下は Stream についての解説記事。

Processing Data with Java SE 8 Streams, Part 1

細かいアップデートも使う

ちょっと余談ですが、StreamAPI や try-with-resources などの構文レベルでの変更以外にも、色々細かいアップデートがありました。例えば Java9 では、これまで結構不便に感じていたコレクションの生成が楽に書けるようになったりしています(List.of() とか Map.of() とかで宣言・生成できる)。

Java8 で追加された String.join() を使うと、以下の部分を書き換えられます。

StringBuilder builder = new StringBuilder();
for(String s : list){
    builder.append(s);
}
String str = validate(builder.toString());

String str = validate(String.join("", list));

になります。文字列の結合方法が幾つか追加されていて、String.join() もその1つです。これでいちいち StringBuilder を使って連結しなくてよくなります。

Optionalを使う

次に、以下の部分を書き換えます。

public static String method(){
    ...
        String str = validate(String.join("", list));
        if(str != null){
            str = str.replace("a","b");
        }
        return str;
    ...
    return null;
}

public static String validate(String str){
    return str.length() >= 30 ? "【" + str + "】" : null;
}

method() と validate() の両方を書き換えます。ここでは、Java8 で追加された Optional という仕組みを使います。書き換え後は下のようになります。

public static Optional<String> method(){
    ...
        Optional<String> str = validate(String.join("", list));
        return str.map(s -> s.replace("a", "b"));
    ...
    return Optional.empty();
}

public static Optional<String> validate(String str){
    if(str.length() < 30){
        str = null;
    }else{
        str = "【" + str + "】";
    }
    return Optional.ofNullable(str);
}

Optional の基本的な使い方は、「null かもしれない値」を Optional クラスでラップすることで、その値が「nullかもしれない」ということを明示的に表現することです。上のコードでは validate() が null もしくは文字列を返します。すなわち、もしかしたら戻り値が null かもしれないわけで、これは結構怖いことです。なぜなら、呼び出し側で戻り値の null チェックをし忘れるとヌルポ(NullPointerException)が発生するからです。書き換え前のコードでは if 文でちゃんと null チェックをしていますが、これを書き忘れると(大体忘れる) str.replace() のタイミングでヌルポが発生する可能性があります。

書き換え後の validate() では Optional.ofNullable(str) で str を Optional でラップしたオブジェクトを返しています。したがって、method() 側では戻り値を String で受け取ることはできません。Optional で受け取ることになります。このため、method() 側に「戻り値がnullかもしれないよ」ということを明示し、null チェックを強制できます。

method() 側は Optional を受け取ると、Optioanl の map() を使って文字列を replace() した値を return します。map() 内では Optional がラップした String オブジェクトが null かどうかをチェックしていて、null なら空の Optional オブジェクトを、値が入っていれば map() に渡したラムダ式を適用した値を返します。すなわち、null なら str.replace() は実行されず、null じゃなければ str.replace() が実行されます。併せて method() 側の戻り値も Optional 化して、null を返さないようにします。

ここでは map() を使ってヌルポを回避していますが、最も基本的な使い方は ifPresent() を使うことだと思います。こいつを使うと、値があれば◯◯◯する、なければ何もしないという分岐ができます。

これで少し安全なコードになりました。以下は Optional についての解説記事。

Tired of Null Pointer Exceptions? Consider Using Java SE 8's Optional!

var を使う

最後に、Java10 で導入されたローカル変数の型推論を使ってみます。以下の変数宣言部分の型を var に置き換えるだけです。

...
List<String> list = lines.distinct()
                    .filter(s -> s.length() >= 7)
                    .map(s -> s.toLowerCase())
                    .sorted()
                    .collect(Collectors.toList());

Optional<String> str = validate(String.join("", list));
...

var list = lines.distinct()
                    .filter(s -> s.length() >= 7)
                    .map(s -> s.toLowerCase())
                    .sorted()
                    .collect(Collectors.toList());

var str = validate(String.join("", list));

こうなります。変数宣言時にいちいち型を書く、抽象型で受け取るというのが Java らしさだった印象ですが、こんな書き方ができるようになりました。代入時の右辺から型が推測できる場合には、var で宣言することで、記述を簡易化できます。これを読みやすいと感じるか否かは人それぞれだと思います。個人的には型が全て書かれていたほうが読みやすい気が。

上の例だと特に、validate() の戻り値を var で受け取るのは賛否両論ありそうです。というのも、右辺を見ただけでは何の型が返ってくるのか判断できないためです。これを判断するには validate() の戻り値が何型なのかを調べなければなりません。IDEが使える環境で読んでいるのであればあまり手間でもないですが、ブラウザ上でコードを読んでいたりするとちょっと面倒かも。StreamAPI やラムダ式と同様に、良くも悪くも書き方の幅が広がったので、複数人での開発時にはある程度規約を設けた方がよさそうな感じです。

以下はローカル変数型推論についての解説記事。

JEP 286: Local-Variable Type Inference

おわり

ということで、最終的に以下の形になりました。

public static Optional<String> method() {
    try (Stream<String> lines = Files.lines(Paths.get("test.txt"))){
        var list = lines.distinct()
                .filter(s -> s.length() >= 7)
                .map(s -> s.toLowerCase())
                .sorted()
                .collect(Collectors.toList());

        var str = validate(String.join("", list));
        return str.map(s -> s.replace("a", "b"));
    } catch(IOException e){
        e.printStackTrace();
    }
    return Optional.empty();
}

public static Optional<String> validate(String str){
    if(str.length() < 30){
        str = null;
    }else{
        str = "【" + str + "】";
    }
    return Optional.ofNullable(str);
}

サンプルコードではやや冗長なロジックが目立ちますが、なんとなーく(そしてざっくりと) Java コーディングの変遷が掴めた気がします。var や ラムダ式はともかく、try-with-resources や Optional は安全性を高められるイイ感じの仕組みだと思うので、積極的に使っていこうかなあと。おわり。

なぜJavaの文字列(String)はイミュータブルなのか

Java における String は不変(イミュータブル)であるというのはよく知られていることだと思います.が,なぜそうなっているのかということについては考えたことがありませんでした.今回はその理由についてメモ.

理由① String Pool のため

Java では String は少々特別扱いされていて,Java ヒープには String Pool と呼ばれる文字列を格納しておく領域があります.プログラム中で使われる文字列をここで重複無く管理しておくことで,いくつも同じ値の文字列オブジェクトが生成されることを無くすための仕組みです.所謂 Flyweight パターンです.

例えば以下のコードがあった時,String Pool には "Cat" と "Dog" が格納されます.

String str1 = "Cat";
String str2 = "Dog";
String str3 = "Cat";
str1 == str3; // true

まず,1行目で String Pool に "Cat" が格納されます.そして,3行目が実行されるタイミングでは既に String Pool に "Cat" が存在します.この場合,新たなオブジェクトは作成されず,String Pool にある既存のオブジェクトへの参照を str3 に代入します.すなわち,str1 と str3 が同じオブジェクトへの参照を持つことになります.これにより,プログラムを省リソース化できます.

さて,この時 str1 と str3 を == で比較すると当然 true になるわけです.ここで,String が可変だったとすると,str1 の値を変更すると str3 の値まで変わってしまいます(例えば破壊的な str1.toLowerCase() とか.String が可変の世界ではあり得る).一方で,String が不変だとこのような問題が起きないのは自明です.

可変であったとしても,str1 の値の変更を検出した時にその値が str3 からも参照されていることを判定できれば,str3 には新しく生成した "Cat" への参照を代入するというやり方も無くはないかもしれません.もしくは逆に,2箇所以上から参照されている文字列を変更する時だけは新たにオブジェクトを生成するとか(参照カウンタを使ったりして).ただ,こんなことをするよりかは文字列を不変オブジェクトにしてしまう方が容易でしょう.

ちなみに,String Pool が使われるのは上記のコードのようにダブルクォートで文字列を生成した場合です(所謂リテラル).で,new String() を使うと String Pool が考慮されずに,String Pool ではないヒープ上に文字列オブジェクトが新規生成されます.ということで,省リソースのためにも文字列はダブルクォートで作った方が良さそうです.

String str1 = "Cat";
String str2 = "Cat";
String str3 = new String("Cat");
str1 == str2; // true
str1 == str3; // false
str1 == str3.intern(); // true intern()はStringPoolの同一文字列を検索してヒットしたらその参照を返す

余談ですが最新の IntelliJ では new String(); したらちゃんと注意されるようになってました.

f:id:norikone:20180515165258p:plain

また,処理時間を測ってみてもだいぶ差があることがわかります.なお計測方法が正しいかどうかは不明.

String s1 = "Cat";
long startTime;
long endTime;

startTime = System.nanoTime();
String s2 = "Cat";
endTime = System.nanoTime();
System.out.println("処理時間1:" + (endTime - startTime) + " ns");

startTime = System.nanoTime();
String s3 = "Dog";
endTime = System.nanoTime();
System.out.println("処理時間2:" + (endTime - startTime) + " ns");

startTime = System.nanoTime();
String s4 = new String("Cat");
endTime = System.nanoTime();
 System.out.println("処理時間3:" + (endTime - startTime) + " ns");

結果

処理時間1:319 ns    // String Pool ヒット
処理時間2:3314 ns   // String Pool ノーヒット
処理時間3:10007 ns  // new で生成

処理時間1は endTime の計算が処理時間の90%以上を占めている感じなので,実際の処理時間はもっと小さそうです.

理由② キャッシングのため

不変であるということは,一度オブジェクトが生成されるとその状態が将来的に変更されないということです.これは,ハッシュコードが変更されないことを保証します.なぜなら,ハッシュコードはそのオブジェクトの状態を基に生成されるからです.なので,オブジェクトが不変なおかげで,一度ハッシュコードを計算したらそれをキャッシュして使い回しできるのです.

String クラスには以下のように hash という int 型の private フィールドがあり,hashCode() 実行時には hash に値があればそれを返します.なければハッシュコードの計算をして,hash に入れます.

...
private int hash; // Default to 0
...
public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}
...

このキャッシングにより,パフォーマンスの向上が期待できます.例えば,String は HashMap のキーなどでよく利用されますが,そのようなハッシュコードを頻繁に使うケースでより効率的に処理できるようになります.Hash コレクション系では値を追加する時や値を取得する時にハッシュコードが使われるので,それを高速化できる感じですね.

理由③ セキュリティのため

String は引数として使われることが非常に多いです.それも,データベース接続用のユーザ名であったり,ファイルを開くためのパスであったりと,外部リソースを扱う時によく使われたりします.このような状況下で String が可変であると,セキュリティ的によろしくありません.

例えば,引数として渡された文字列に対して何かしらのセキュリティ処理をした後に,文字列を変更されてしまうケースが想定できます.

public void secureOpen(String path){
    if(!isSecure(path)){ // 権限等のチェック
        throw new SecurityException();
    }

    // Stringが可変だと,ここで他の参照を使ってpathの値を書き換えることが可能
    open(path);   
}

isSecure() でのセキュリティチェックを通過した後で,他の参照から文字列が書き換えられてしまうと,予期せぬ問題が発生する可能性があります.他の参照というのは,path が指しているのと同じオブジェクトへの参照を持つ他の変数のことです.String を不変にしておくことで,呼び出し先に渡した文字列が途中で外部から書き換えられることを防げます.このように,String が不変な理由としてはセキュリティ的な観点もあります.

理由④ スレッドセーフティ

String が不変であるおかげで,マルチスレッドプログラミングで文字列を扱う際の面倒事を減らせます.String が可変である場合には,スレッド間で文字列を共有する際の同期制御について考える必要があります.

例えば String が可変であると,理由③に書いたコードのように文字列を参照するフェーズと使用するフェーズに分かれている場合,参照時と使用時で,スレッド間で共有している文字列の値が変わっている可能性があります.あるスレッドで isSecure() を実行した際にこのチェックをパスできる値になっていたとしても,次の open(path) が呼び出されるまでの間に他のスレッドが path の値を書き換えてしまうかもしれません.これを防ぐためにはロックなどの仕組みを使わなければならず,実装がやや複雑化します.

String が不変だとスレッド間での共有が簡単で,考え事やコードが少なくなります.つまり,正しく作成された不変オブジェクトは暗黙にスレッドセーフなので,文字列みたいな頻繁に使われるクラスは不変化したほうが捗るよね,ということでしょうか.String が可変だと,常に外部から変更される危険を孕むので,例えスレッド間で文字列を意図的に共有しない場合だったとしても面倒事が増えそうです.

おわり

これらは十分に String を不変とする理由になるかと思います.大きく分けると,「パフォーマンス」と「安全性」のため,になるでしょうか.最近のコンピューティング環境の発展に鑑みれば,パフォーマンスについてはあまり深刻に考える必要もないかもしれんが.といっても String は非常に使用頻度の高いオブジェクトなので,不変化による恩恵はそれなり受けられそうです.同じような理由で,Integer や Long などのクラスも不変クラスになっている模様です.
上記から分かる不変の強みは以下.

  • 不変オブジェクトには flyweight パターンが適用しやすい
  • 不変オブジェクトはキャッシュによるパフォーマンス向上がしやすい
  • 不変オブジェクトはセキュリティを向上できる
  • 不変オブジェクトはスレッドセーフ

参考

http://www.java67.com/2014/01/why-string-class-has-made-immutable-or-final-java.html

【図解】値渡し、アドレス渡し、参照渡しをメモリマップ的な視点でまとめる

結論

  • 値渡し

    • 仮引数が確保したメモリの値に引数の値をコピーする方法
  • アドレス渡し

    • 仮引数が確保したメモリの値に引数のアドレスをコピーする方法
  • 参照渡し

    • 仮引数に引数と同じメモリ領域を指させる方法

背景

プログラムを書いていると「◯◯渡し」という言葉をよく耳にしますが、その定義はよく理解できていませんでした。そこで、ググって解説記事を幾つか読んでみたのですが、記事によって書いてあることが違ったり、言語によっても呼び方が違ったりと、なかなか混沌としていることが判明。
例えば Java では、インスタンスの場所を指し示すをモノを「参照」と呼びます。なので、この参照の値を関数に渡すことを参照渡しと呼ぶことに違和感はありませんが、内部的には C++ などの言語で言うところの参照渡しとは少し違ったりします。このような状況から、どのような動作を「◯◯渡し」と呼ぶのかは、いくつかの派に分かれているのが現状っぽいです。

とはいえ、一応自分の中での呼び方を定めておきたかったので、少しまとめてみました。汎用的な定義にするために、言語に依存しない考え方を軸にします。このため、コード視点だけではなく、どちらかと言うとメモリ上での動作イメージをベースに考えています。
機械語などの更に低レイヤーな視点から考えることもできるかもしれません。しかしそれだと、究極的には全てが値渡しになるとかいう話になったりしそうですし、主な関心はソースコード上での変数(メモリ領域)の扱い方にあると思うので、そこまで低レイヤーな視点では考えません。(あくまでもコード上で変数を渡しているように見えるから「◯◯渡し」と呼ぶ)

サンプルとして、以下のオブジェクト志向的な擬似コードで考えていきますが、プログラミングパラダイムは今回の定義に影響しません。func の引数に hoge を指定して仮引数 arg に渡すコードにおいて、メモリ上でどのような動作になるかが主題です。

main() {
    Object hoge = new Object();
    func(hoge);
}

func(Object arg) {
    ...
}
  • new Object(); によって、hoge にはメモリ上のどこかに作成された Object インスタンスのメモリアドレスが値として格納される(Java 的な動作。C++ では Object *hoge = new Object; の動作。)
  • スコープはブロックで区切られている


値渡し

f:id:norikone:20180420200145j:plain

仮引数 arg の値に hoge の値をコピーことです。そのままですね。値にアドレスが入っていたとしてもメモリ上の "値" であることには変わりないので、値渡しです。

アドレス渡し(ポインタ渡し)

f:id:norikone:20180420201715j:plain

仮引数 arg の値に hoge のアドレスをコピーすることです。この渡し方はポインタ渡しと呼ばれているケースが多いですが、"渡す" のは呼び出し側であり、渡しているのはアドレスであるので、アドレス渡しと呼びたいところです。

参照渡し

f:id:norikone:20180420202117j:plain

仮引数 arg に hoge と同じアドレスを指させることです。メモリイメージの視点ではこれを "渡し" 呼ぶのは少し違和感がありますが、コード上では arg に渡しているように見えるので参照渡しと呼びます。


C++で動作確認

冗長な記述がありますが、C++で動作確認した時のコードをそのまま載っけておきます。

#include <iostream>

class MyClass{
};

void val(MyClass* arg) {
  printf("==============begin val=============\n");
  printf("argの確認\n");
  printf("  arg = %p\n", arg);
  printf(" &arg = %p\n", &arg);
  MyClass *piyo = new MyClass;
  printf("piyoの作成\n");
  printf(" piyo = %p\n", piyo);
  arg = piyo;
  printf("argにpiyoを代入\n");
  printf("  arg = %p\n", arg);
  printf(" &arg = %p\n", &arg);
  printf("==============end val=============\n");
}

void adrs(MyClass** arg) {
  printf("==============begin adrs=============\n");

  printf("argの確認\n");
  printf("  arg = %p\n", arg);
  printf(" &arg = %p\n", &arg);
  printf(" *arg = %p\n", *arg);
  MyClass *piyo = new MyClass;
  printf("piyoの作成\n");
  printf(" piyo = %p\n", piyo);
  *arg = piyo;
  printf("*argにpiyoを代入\n");
  printf("  arg = %p\n", arg);
  printf(" &arg = %p\n", &arg);
  printf(" *arg = %p\n", *arg);

  printf("hugaの作成\n");
  MyClass *huga = new MyClass;
  printf(" huga = %p\n", huga);
  arg = &huga;
  printf("arg に&hugaを代入\n");
  printf("  arg = %p\n", arg);
  printf(" &arg = %p\n", &arg);
  printf(" *arg = %p\n", *arg);

  printf("==============end adrs=============\n");
}

void ref(MyClass*& arg) {
  printf("==============begin ref=============\n");

  printf("argの確認\n");
  printf("  arg = %p\n", arg);
  printf(" &arg = %p\n", &arg);
  MyClass *piyo = new MyClass;
  printf("piyoの作成\n");
  printf(" piyo = %p\n", piyo);
  arg = piyo;
  printf("argにpiyoを代入\n");
  printf("  arg = %p\n", arg);
  printf(" &arg = %p\n", &arg);

  printf("==============end ref=============\n");
}

int main() {
  MyClass *hoge = new MyClass;

  printf("hogeの作成\n");
  printf(" hoge = %p\n", hoge);
  printf("&hoge = %p\n", &hoge);

  val(hoge);

  printf("mainでhogeの確認\n");
  printf(" hoge = %p\n", hoge);
  printf("&hoge = %p\n", &hoge);

  adrs(&hoge);

  printf("mainでhogeの確認\n");
  printf(" hoge = %p\n", hoge);
  printf("&hoge = %p\n", &hoge);

  ref(hoge);

  printf("mainでhogeの確認\n");
  printf(" hoge = %p\n", hoge);
  printf("&hoge = %p\n", &hoge);
}

実行結果は以下

hogeの作成
 hoge = 0x7f9d00402850   # hogeの値
&hoge = 0x7ffee0a24778   # hogeのアドレス
==============begin val=============
argの確認
  arg = 0x7f9d00402850
 &arg = 0x7ffee0a24728   # hogeのアドレスと異なる(新しくメモリが確保され値がコピーされている)
piyoの作成
 piyo = 0x7f9d00402860
argにpiyoを代入
  arg = 0x7f9d00402860   # argの値が書き換えられた
 &arg = 0x7ffee0a24728
==============end val=============
mainでhogeの確認
 hoge = 0x7f9d00402850   # val内でのarg値の書き換えはhogeに影響していない
&hoge = 0x7ffee0a24778
==============begin adrs=============
argの確認
  arg = 0x7ffee0a24778   # hogeのアドレスと等しい
 &arg = 0x7ffee0a24728   # hogeのアドレスと異なる(新しくメモリが確保され値がコピーされている)
 *arg = 0x7f9d00402850   # *argが示す値はhogeの値と等しい(argを1つ辿るとhogeに着く)
piyoの作成
 piyo = 0x7f9d00402870
*argにpiyoを代入
  arg = 0x7ffee0a24778
 &arg = 0x7ffee0a24728
 *arg = 0x7f9d00402870   # *argがpiyoの値を持つ(*argはhogeを示すのでhogeの値が変わる)
hugaの作成
 huga = 0x7f9d00402880
arg に&hugaを代入
  arg = 0x7ffee0a24718   # argの値が新しく作成されたhugaのアドレスに変わる(*argはhugaを示すようになり、hogeとの関係が切れる)
 &arg = 0x7ffee0a24728
 *arg = 0x7f9d00402880   # *argがhugaの値を持つ
==============end adrs=============
mainでhogeの確認
 hoge = 0x7f9d00402870   # adrs内でもpiyoの値に変わっている(hugaの値にはなっていない)
&hoge = 0x7ffee0a24778
==============begin ref=============
argの確認
  arg = 0x7f9d00402870   # hogeの値と等しい
 &arg = 0x7ffee0a24778   # hogeのアドレスとも等しい(arg用のメモリは用意されていない)
piyoの作成
 piyo = 0x7f9d00402890
argにpiyoを代入
  arg = 0x7f9d00402890   # argの値がpiyoの値に書き換えられる
 &arg = 0x7ffee0a24778
==============end ref=============
mainでhogeの確認
 hoge = 0x7f9d00402890   # ref内でのargの値の書き換えが反映されている
&hoge = 0x7ffee0a24778

おわり

ぶっちゃけ具体的なコンパイラの動作とかを詳しく知らないので、その辺を理解していけばまた考えが変わるかもしれませんが、とりあえず今現在の考えを書きました。おわり。

JetBrains IDE でカーソルの位置にある単語を削除するショートカットを作る

普段 IntelliJ や RubyMine などの JetBrains IDE を使っているのですが,環境設定の Keymap を見ても,カーソルが置いてある場所にある単語を単語ごと削除するショートカットが見当たりませんでした.
今回は Macro を使ってこのショートカットを作る方法を紹介します.メモ.

※カーソルというのはマウスカーソルではなくて文字カーソルです

マクロの作成

『カーソルの位置にある単語を削除』は,以下の2つのコマンドを組み合わせて作ります.

  1. カーソルの位置にある単語を選択 『Extend Selection』
  2. 削除 『Delete』

IDE のバージョンによって上記キーが現在どこに割り当てられているかが異なります.なので,自分の環境でそれぞれがどこに割り当てられているのかを確認します.[Preferences] → [Keymap] → [ウィンドウ右上の検索欄に上記『』内の単語を入力] という手順で確認できます.

次に IDE のメインメニューから [Edit] → [Macros] → [Start Macro Recording] を選択します.そうすると,IDE の右下にレコーディングマークが表示されます.この状態でキーを入力するとマクロを記録できます.なので,記録中に『Extended Selection』→『削除』の順番で確認しておいたキーを入力します.入力が終わったらレコーディングマークの赤い停止ボタンを押して,マクロ名を入力すれば完了です.作成したマクロは [Edit] → [Macros] から確認・編集可能です.

マクロをショートカットに登録

作成したマクロを簡単に使えるようにショートカットに登録します.[Preferences] → [Keymap] → [ウィンドウ右上の検索欄に『macros』と入力すると,作成したマクロ名が表示されます.あとはそれをダブルクリックして [Add Keyboard Shortcut] → [割り当てるキーの入力] をすれば完了です.

Mansory で表示した時に要素が重なる件

Webページを作っていて,画像などの大きさの異なる要素を繰り返しで表示する場合にはタイル状(レンガ状?)に敷き詰めて配置できると綺麗ですね.Masonry という JavaScript ライブラリを使うとこれを非常に簡単にやってくれるので便利.なのですが,配置した要素が重なり合ってしまうという問題が発生しました.この問題の解決策を書きます.

問題の原因

Masonry では,要素の配置場所の決定をそれぞれの要素のサイズをもとに行っています.このため,当該要素が読み込まれる前に Masonry の計算処理が実行されてしまうと,要素の配置場所がおかしなことになります.今回は画像をリストする要素としていたのですが,その画像が読み込まれる前に Masonry による処理が走ってしまっていたことが原因でした.

問題が発生したコードは以下.

$(function () {
    $('#container').masonry({
        ...
    });
});

$(function)() で初期化していますが,$(function)() での初期化は DOM ツリー(HTML要素)の読み込みが完了した時点で実行されます.そして,このタイミングではまだ画像や動画は読み込みが完了してない可能性があります.このように読み込みが完了していない時に Masonry の処理が走ると上記の問題が発生してしまいます.

解決策① JS 実行のタイミングを変える

以下のコードのように,$(window).load() を使います.

$(window).load(function(){
    $('#container').masonry({
         ...
    });
});

$(window).load() は,画像やフォントなどの関連データを読み込んだ後に実行されるので,Masonry がちゃんと画像サイズを取得できます.
$(function)() と $(window).load() の違いは以下.

$(function(){}) と $(window).on('load',function(){}) の違い - Qiita

解決策② ImagesLoaded を使う

今回初めて知ったのですが,ImagesLoaded という画像の読み込みが完了した後に処理を実行するためのライブラリがありました.これでも上記の問題を解決できます.
まずは以下からダウンロード.

https://imagesloaded.desandro.com/

後は js ファイルを読み込んで,以下のようなコードに変更.

$(function () {
    var container = $('#container');
    container.imagesLoaded(function () {
        container.masonry({
            ...
        });
    });
});

これで #container 配下の画像を読み込んだ後に Masonry が実行されることが保証されます.

画像読み込み後の処理による表示時のガタツキが気になる

fakeLoader などでローディング処理を入れることで綺麗になります.

Masonry 公式

masonry.desandro.com

github.com

おわり

このような問題にハマらないためにも,フロント周りのライブラリを導入する際には,読み込みや処理の実行順序をある程度意識しておきたいですね.おわり.

ブロックチェーンとは何なのか、ややこしいところを中心に概要をざっくりと

ここ数年でブロックチェーンという単語をよく耳にするようになりました。が、イマイチ理解できていなかったので、自分なりにまとめ。備忘メモ。
具体的な動作や理論については書きません、概要をざっくりと書きます。

ブロックチェーン is 何

思想などは後回しにして、とりあえず実体の部分から書いていきます。ブロックチェーンとは何なのかという話ですが、大枠としては「情報の管理手法の一つ」と捉えています。ブロックチェーンの定義を聞かれるとなかなか明確な回答を出すのは難しいと思いますが、JRA日本ブロックチェーン協会)は以下で次のように定義しています。

1)「ビザンチン障害を含む不特定多数のノードを用い、時間の経過とともにその時点の合意が覆る確率が0へ収束するプロトコル、またはその実装をブロックチェーンと呼ぶ。」

2)「電子署名とハッシュポインタを使用し改竄検出が容易なデータ構造を持ち、且つ、当該データをネットワーク上に分散する多数のノードに保持させることで、高可用性及びデータ同一性等を実現する技術を広義のブロックチェーンと呼ぶ。」

この定義を見ても分かるように、ブロックチェーンを一言でズバッと表現するのは難しいかもしれません。未だにfuzzyな印象です。

個人的に「ブロックチェーンとはこういうものだ」ということを理解し辛かった理由の1つとして、ブロックチェーンは大きく2つの特徴的な仕組みを持っていて、それを組み合わせることで成立する技術であったことが挙げられます。その1つは、情報を管理するための「データ構造」です。もう1つは、情報を管理するための「管理手法」です。

まず「データ構造」についてですが、ブロックチェーンはその名の通りブロック(情報の集まり)をチェーン状に繋げていくようなデータ構造で情報を管理します。詳細は割愛しますが、この構造の利点として改竄の検出が容易であることが挙げられます。で、このような構造を持っているからブロックチェーンと名付けられているのか〜、という感じですが、巷で使われているブロックチェーンという言葉はこのデータ構造そのものを差しているわけではないというのがややこしいところです。この「データ構造」を前述の「管理手法」で運用することで、ブロックチェーンという仕組み(概念?)が成立する、という形になっています。

「管理手法」はざっくり言うと、「データ構造」を誰がどうやって管理するかを決めたルールのようなものです。これはブロックチェーン界隈の用語では「合意形成(コンセンサスアルゴリズム)」と呼ばれています。ブロックチェーンという名前が付けられているのでどうしても「データ構造」への意識が強くなってしまいますが、この「管理手法」部分も非常に重要な要素です。むしろ、ブロックチェーンの革新的なところはここにあると理解している人も多いと思います。

まとめると、ブロックチェーンの実体は、特徴的な「データ構造」と「管理手法」を持った「情報管理機構」という認識でいます。それぞれの詳細は別の記事ででも書こうと思います。

ブロックチェーンを使うと何が嬉しいのか

ブロックチェーンは「情報の管理手法の1つ」でしかないので、当然他にも情報の管理手法はあるわけです。代表的な比較対象として、データベースがしばしば挙げられます。ではデータベースなどの従来の情報管理手法とブロックチェーンの差異はなんだろうという疑問が湧いてきますが、これを考える上でも少しややこしいことがあります。それは、ブロックチェーンには「プライベート」なものと「パブリック」なものが存在することです。

サトシナカモト氏が提案した本来のブロックチェーン技術(ペーパーではブロックチェーンという言葉は使われていません)は、信用された第三者による仲介なしに電子通貨の取引をよしなに成立させることが目的となっています。つまり、非中央集権が根本的な設計思想です。これを実現するために前述の「管理手法」、すなわち、中央管理者の存在無しにどうやって参加者間で合意を形成するのか、何をもって正しいとするのか、が提案されました。この仕組みによって、非中央集権的・自律分散的な情報管理の場を作ることが可能になったのです。これが、ブロックチェーンを使って嬉しいことの1つです。そして、従来 ”集中的な管理機構” が必要とされてきたケースをこのブロックチェーンで置き換えられるのではないかということで、色々な分野に適用していくことが期待されているわけですね。このように非中央集権を意識し、誰でもブロックチェーンの運営に携われて、みんなでそこで発生するやり取りの正しさを証明していこうというのがブロックチェーン元々の姿であるように思います。このようなブロックチェーンはその特性から、パブリックブロックチェーンと呼ばれています。

パブリックなブロックチェーンが誕生し注目される一方で、プライベートなブロックチェーンという考え方も生まれてきました。プライベートブロックチェーンは、中央的な管理機能を備えたブロックチェーンです。中央管理機能を導入することで、パブリックブロックチェーンの課題であった取引の高速化、プライバシーの保護などにアプローチできます。しかしこれは、非中央集権という本来のブロックチェーンの思想に反するものです。それでは本末転倒ではないかと思ってしまうのですが、ブロックチェーンが持つ「中央管理機能の排除」以外の凄味に注目し、そこを活用しようというのがプライベートブロックチェーンです。主に、企業などの組織内での閉鎖的な使用が想定されています。例えば、ブロックチェーンが持つ改竄検出が容易なデータ構造を他の何かに適用できないか、ブロックチェーンを使って企業内の中央管理システムを排除して低コスト化できないか、というような仮説のもと、実証実験が行われている段階です。その効果については未知数なところが多いと思いますが、ブロックチェーンの特性を見ていると確かに何か色々なことに利用できそうだという感覚になります。

つまり何が言いたいかというと、パブリックブロックチェーンとプライベートブロックチェーンとでは、見ているもの、実現したいことが違うということです。なので、ブロックチェーンを使うと何が嬉しいのか、ということを考える際には、それがパブリックなものなのか、それともプライベートなものなのかという前提が必要なわけです。どちらが優れているという話ではなく、別の技術としてそれぞれ議論するほうがいいかなと思います。

ブロックチェーンが管理するもの

僕はブロックチェーンを知ったばかりの頃、ブロックチェーンは仮想通貨の送金処理の管理のためだけに使うものだと理解していました。しかし、ブロックチェーンにはそれ以外の様々な用途があります(ブロックチェーンの種類によっても異なるかもしれませんが)。

興味深いのは、ブロックチェーン上に任意のソースコードを配備できる点です(これもチェーンによるかもしれませんが)。このコードは一般にスマートコントラクトなんて呼ばれていたりします。ブロックチェーン上にソースコードを置いてみんなが利用できるようにすることで、中央的なサーバ無しでのコンピューティングプラットフォームを実現できます。コードに書かれた処理を実行するのは、所謂マイナー達です。コンピュータやネットワークの世界では「集中 vs 分散」という構図で色々な議論がなされてきましたが、このブロックチェーンでの分散アプローチはその視点でみても非常に面白いものになっていると感じます。

このようにブロックチェーン上にソースコードを配備することで提供されているアプリケーションは Dapps (Decentralized Applications) と呼ばれていて、今後も様々な分野で提供されていきそうです。というような視点から、ブロックチェーンを「分散型コンピューティング基盤」と捉えることもできそうです。

仮想通貨とブロックチェーンの関係

ここまでの話から、ブロックチェーン(パブリックブロックチェーン)と仮想通貨はあまり関係が無いのではないかと思うかもしれません。しかし、これらは密接な関係にあります(プライベートブロックチェーンとは関係無いこともあります)。

先に書いた通り、パブリックブロックチェーンには中央的な管理者が存在しません。従って、ブロックチェーンネットワークの参加者みんなでそれを維持します。具体的には、各参加者は自身がもつコンピューティングリソースをブロックチェーンネットワークに提供します。所謂マイニングというやつです。マイニングがされなくなってしまうと、ブロックチェーンは機能しなくなります。しかし、マイニングをする参加者(=マイナー)には電力やその他もろもろのコストが課されることになります。それでもなおマイナーが存在し続けている理由は、リソースの提供によりインセンティブが発生するためです。

このインセンティブに、仮想通貨が使われています。ブロックチェーン維持に貢献した者には仮想通貨が与えられる仕組みになっており、これがなくなると恐らくブロックチェーンは機能しなくなってしまいます。このインセンティブが貨幣である必要はないと思いますが、その有効性(流通性)から貨幣以外のインセンティブにより成り立つブロックチェーンというのは想像し辛いですね。結局のところ、ブロックチェーンは貨幣(仮想通貨)によって支えられており、仮想通貨はブロックチェーンによって支えられているのです。

ビットコインも初めは全く無価値のデータに過ぎなかったと思いますが、それがインセンティブとして成立し、ここまで成長しています。このエコシステムこそが仮想通貨で一番面白いところだ、という人もいるかもしれませんね。

おわり

まだまだ理解できていないところだらけですが、ざっくりとブロックチェーンについて現状の理解を書きました。今後情報を取り入れていく中で修正していくかもしれません。以上。

参考(ペーパーへのリンク)

原著:https://bitcoin.org/bitcoin.pdf

日本語訳版:http://www.kk-kernel.co.jp/qgis/HALTAK/FEBupload/nakamotosatoshi-paper.pdf

gulpとかGruntとか

gulp を知った時のメモ。

Rails で開発していた時に scss(sass) に触れて、便利だなーと思ってました。で、最近静的な Web サイトを作る機会があって、そこでも scss で書きたくて書いたのはいいのですが、いちいち手動でコンパイルや圧縮、自動ベンダープレフィックス付けするのが面倒くさい。Rails はその辺りをよしなにやってくれるので今まではあまり意識していませんでしたが、フレームワークを使わない場合でもなんとかならないかと思っていたところ、gulp を見つけました。

gulp is 何

一言で言えばビルドツール(タスクランナー)です。ファイルのコンパイルや圧縮で使われるケースが多いみたいですね。scss(css) や pug(html) などは、あくまでも効率的な開発のために提案されたレイヤーであり、Web ブラウザが理解できる形式にはなっていません。なので、修正の都度コンパイルして普通の css や hml 形式にしてあげる必要があるのですが、手動でやるのも骨が折れます。gulp はそのようなビルドタスクを自動化してくれるツールです。

使い方としては、gulpfile.js というファイルに実行するタスクを javascript で定義して、gulp コマンドでそのファイルを実行する形です。gulpfile の記述形式は、普段から javascript に触れていれば直ぐに理解できると思います。

Gruntやnpm scriptでのビルド

gulp と似たツールとして、Grunt があります。関係的には gulp が後発ですね。前まではプラグインの充実度合いから Grunt が選ばれることが多かったみたいですが、gulp のプラグインが充実してきてからは Grunt よりは gulp、という人が多いみたいです。

Grunt は処理毎に成果物をファイルに書き出して次の処理に受け渡しているのに対して、gulp の方は処理間の成果物のやり取りを主にメモリ内で済ませているため、高速であるというのがその理由の一つです。所謂ストリーム処理というやつですね。あとは gulp では Grunt と比べるとコード量を抑えられたり、並列処理が得意であるといった強みがあります。というような感じで、新規でタスクランナーを導入する際には gulp が選ばれることが多そう。

しかしここ数年で、この gulp や Grunt に批判的な意見がいくつか挙がってきている模様です。

[意訳]私がGulpとGruntを手放した理由 - Qiita

Grunt/Gulpで憔悴したおっさんの話 - MOL

【Node.js7.5対応】gulp.js/Gruntを卒業すべき3つの理由とgulp非依存の本格タスクランナーの作り方 | 株式会社LIG

gulp 等で開発時のレイヤーを増やしてしまうと、その分問題が発生し得る箇所が増え、複雑性も増すので危険。また、プラグイン等への理解も要するため、学習コストも増える。であれば、gulpやGruntの一つ下のレイヤーでその土台となっている npm script を使えばいい。npm script は十分に高機能であり、そもそも gulp 等を使う必要はない。

ということですかね。gulp/Grunt 離れしようと。ただ、npm script は Shell に依存しているため、クロスプラットフォーム対応はされていないという問題があります。これは適切な npm パッケージを使って OS 間のコマンドの差異を吸収してあげればなんとかなるみたいですが、少なくともそのあたりを意識する必要があるということはデメリットとして挙げられそうです。あとは、gulp 等に比べると少々可読性に劣るということもありそうです(好みではありますが)。

まとめ

gulp 周りについて書いてみました。gulp は使い易いツールだと思いますが、先に挙げたような問題もあります。大規模でデバッグや依存関係が複雑になりそうなプロジェクトでの使用は少し怖いですが、小規模で簡易的なモノをサクッと作るくらいであれば、gulp でもいいかなという印象です。おわり。