人生、気合いと具合 - blog

この記事は Java Puzzlers Advent Calendar の8日目です。

・問題
最近は問題なくマルチバイト文字を含んだファイルを読み込めるようになった java.util.Properties ですが、昔はnative2ascii使ったりで面倒でした。
以下は、仕事で実際に遭遇したコードを元にしたものです。仕様的にはダメなわけですが、さて、実際に実行したら結果はどうなるでしょうか。

●プロパティファイル(Shift_JIS)

programmer=十把一絡げ

●プログラム

package com.bugworm.advent2016.puzzlers;

import java.io.InputStream;
import java.util.Properties;

public class MultiByteCharacterProperties{

    public static void main(String... args)throws Exception{
        Properties properties = new Properties();
        try(InputStream in = 
                ClassLoader.getSystemResourceAsStream("test.properties")){
            properties.load(in);
        }

        String value = properties.getProperty("programmer");
        String shift_jis = new String(value.getBytes("ISO8859-1"), "Shift_JIS");
        System.out.println("programmer は " + shift_jis);
    }
}

(1) 「programmer は 十把一絡げ」と出力される
(2) 実行時例外
(3) 「十把一絡げ」の部分が文字化けする






解答

(3) 「十把一絡げ」の部分が文字化けする

解説

この場合は文字化けするのですが、実は、たいていのケースではちゃんとマルチバイト文字を読み込めてしまいます。
しかし、上記の「十把一絡げ」はダメなのです。

「十把一絡げ」は、Shift_JISだと 「8F 5C 94 63 88 EA 97 8D 82 B0」となります。 
2バイト目の「5C」がくせものです。
単独で見るとこれは「\」になります。最初にPropertiesクラスが読み込む際にはShift_JISとして意識していませんので、これは実際に「\」として読みこまれます。
プロパティファイルでは「\」はエスケープ文字なので、「5C 94」部分がエスケープとみなされます。ただし「94」はエスケープとして特に意味がないのでそのまま「94」として解釈されます。
したがって読み込んだ結果を getBytes("ISO8859-1") すると「8F 94 63 88 EA 97 8D 82 B0」です。(5Cがなくなる)
なので、これをShift_JISとして文字列にすると、「8F 5C」が「諸」、3バイト目(元は4バイト目)の「63」は半角「c」なので、実行結果は「programmer は 諸c一絡げ」と表示されます。

ちなみに、 「十\把一絡げ」として、5Cを重ねてやれば、「8F 5C 5C 94 63 88 EA 97 8D 82 B0」となり、読み込み時に5Cが残る(5C 5Cをエスケープとみなして、5Cとして読み込まれる)ので、 結果的に「十把一絡げ」になります。
 

今日、ジョギングしている最中に、ちょっと自分の考え方が変わったというか、いままでの考え方にプラスアルファが発生したので書いておこうと思う。

・いままでの考え方(つまり、「その1」)
35歳あたりを境に、どうしても体力が低下していく。
勉強してより良い技術を学ぼう、という姿勢が無い、すなわち体力に任せてなんとかタスクをこなす、というようなやり方に頼っているプログラマは、この辺で限界が来る。(こういう輩をプログラマと呼んでも良いのか、というのは別の問題としてあるが)
だから、学んでいく気持ちを持っていれば、そんな俗説は関係ないだろ!

・追加で思いついた仮説
そうは言っても、仕事以外で学びを得るためには時間が必要である。体力の低下は自分が使える時間の減少を引き起こす可能性が高い。
つまり、一晩寝ても疲労が全回復しないとか、それに伴って集中力が低下して時間を有効に使えないとか。
そうなると結局、数年遅れてプログラマとしてやっていけなくなることになる。

・結論
プログラマは体力。

このエントリは、JavaFX Advent Calendar 2015 の記事です。
昨日分は、aoeさんの、「javapackagerの紹介」でした。

JavaFXでは、ListViewやTreeViewの表示をカスタマイズできます。
ここでは例として、ListViewの場合を考えてみましょう。

表示方法を決めるために、ListCellのサブクラスを作成します。
以下、タイトルと著者を持つ、Bookクラスを表示するListViewのためのListCell<Book>です。

package samplej;

public class BookListCell extends ListCell<Book>{

    private final Label titleLabel = new Label();
    private final Label authorLabel = new Label();
    private final HBox container = new HBox(8.0, titleLabel, authorLabel);

    @Override
    protected void updateItem(Book book, boolean empty){
        super.updateItem(book, empty);
        if(!empty){
            titleLabel.setText(book.titlePropertey().get());
            authorLabel.setText(book.authorProperty().get());
            setGraphic(container);
        }
    }
}
※インポートなどは省略しています。以下同様。

そして、上記クラスをインスタンス化する、CellFactoryをListViewに設定します。

ListView<Book> list = new ListView<>();
list.setCellFactory(listView -> new BookListCell());

Callbackクラスなので、ラムダで書けますね。
こうすることで、ListViewを描画するときに、BookListCellクラスのupdateItemメソッドが実行されるわけです。

さて本題です。
これ、そのままScalaFXでやるとどうなるでしょうか?

package samples

class BookListCell extends ListCell[Book]{

  val titleLabel = new Label()
  val authorLabel = new Label()
  val container = new HBox(8.0, titleLabel, authorLabel)

  override def updateItem(book : Book, empty : Boolean) : Unit = {
  //この時点でコンパイルエラー
  }
}

はい、updateItemメソッドをoverrideで書くとコンパイルエラーになります。
おかしいなー、と思ってソースを追ってみると、Cell.scalaにこんなコメントがあります。(一番最後の方)
// TODO: implement updateItem(T item, boolean empty)
// might be difficult since updateItem is a protected method which needs to be defined in the delegate's class

まあ、確かにそうなんだよ...
ScalaFXの基本方針は、JavaFXの各クラスのラッパー的なクラスを用意して、各メソッドの実行を内部で持つJavaFXのインスタンスに委譲する感じになっています。
でも、CellクラスのupdateItemメソッドはprotectedなのでそういう方針では実装できませんよね。

とはいえ、そもそもupdateItemメソッドは外部から実行されるわけじゃないし(protectedなんだから)、やろうと思えば、Javaのソースと合わせて以下のようにできます。
ScalaFX側ではBookListCellを作成せず、

val list = new ListView[Book]()
list.cellFactory = (listView : ListView[Book]) => new samplej.BookListCell()

とします。
ListViewはScalaFXのクラスですが、samplej.BookListCellは先程作成したJavaのクラスの方です。
ScalaFXのListCellでオーバーライドはできませんが、ListCellの委譲先のインスタンスに、必要な処理をオーバーライドしたJavaFXのインスタンスを持たせる感じですね。
なお上記のソースは、ScalaFXで用意された良いように計らってくれるimplicit conversionを使った記述なので、ちゃんと書くと次のような感じになります。

list.cellFactory = (listView : ListView[Book]) => new ListCell[Book](new samplej.BookListCell())

JavaFXのサンプルで作った方のBookListCellで必要な処理をオーバーライドしているので、ScalaFXの方では委譲先を指定するコンストラクタを使用して、ListCellを作成しています。
※ちなみに、通常(引数のないコンストラクタ)は、内部でJavaFXのインスタンスが生成されています

JavaFXとScalaFX、両方のクラスが同じソースに出てくるといろいろ面倒というかややこしくなるのであまり好ましくないと思うのですが、やっぱり細かいところで必要になってしまいますね...


明日は、opengl-8080さんの、「Spring BootとJavaFXを組み合わせてみる」です。

このページのトップヘ