年/月/日 の一部が省略された日付をパースして LocalDateTime を取得する方法

年が省略されている日付(月/日)の場合

まずは、DateTimeFormatter を用意します。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd");

次に、日付を MonthDay クラスでパースします。

MonthDay monthDay = MonthDay.parse("07/18", formatter)
// ==> --07-18

それに、 現在の年を付け加えます。

LocalDate date = Year.now().atMonthDay(monthDay);
// ==> 2022-07-18

最後に、LocalDate#atStartOfDay() で日付を 00:00:00 に設定すれば、LocalDateTime クラス が取得できます。

LocalDateTime dateTime = date.atStartOfDay();
// ==> 2022-07-18T00:00

日が省略されている日付(年/月)の場合

YearMonth クラスを使うぐらいで、あとは月日の場合と大体同じです。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu/MM")

YearMonth yearMonth = YearMonth.parse("2022/07", FORMATTER);
// ==> 2022-07

LocalDate date = yearMonth.atDay(1);
// ==> 2022-07-01

LocalDateTime dateTime = date.atStartOfDay();
// ==> 2022-07-01T00:00

左腕のハードウェア障害についてのご報告

6月6日(土)に自転車でお散歩していた際に転倒し、左腕がクラッシュしました。 それにより、しばらくの間は右腕のみの片系運用となっておりました。

数日が経過しても左腕からのアラートが治まらない状態が続いたことから、病院に詳細なデバッグを依頼したところ、 ハードウェア障害(左肘頭骨折)が発覚しました。

対応

意識を運用から切り離しての修復作業全身麻酔下での手術)が行われました。
これにより、現在はハードウェア内部で障害個所をワイヤーで固定した仮復旧状態となっております。
(負荷をかけなければ、基本的な動作に支障はありません)

なお、完全な修復までには、もう2ヶ月ほど時間がかかる見込みです。

フォロワーの皆様には、ご心配をおかけいたしました。

今後について

自転車でお散歩する際、何らかの原因による転倒は避けられないものと考えております。 そのため、もし今後転倒した際にはしっかりと受け身の姿勢で無難にやり過ごすように対応するつもりです。

以上

JJUG CCC 2022 Spring ( #jjug_ccc ) - セッション資料の一覧

JJUG CCC 2022 Spring に参加しました!

いつも以上におもしろいセッション盛りだくさんで、参加してとても楽しかったです。
LINEのトラブルシューティングのような現場での経験をもとにした話から、バイトコードのようなJavaのコアな話まで、とてもバランスよくセッションが採択されていたからかなと思います。
特に前者はなかなか普段の JJUG ナイトセミナーなどでは聞けないので、いつもとても楽しみにしています。

あと、動画セッションならではですが、今回は休憩時間中に時間がかぶって見れなかったセッションを2倍速で追っかけて見るということをやってみて、より密度の高い時間になりました。
授業は動画配信で見たいという大学生の気持ちがちょっとわかりましたw

次回の JJUG CCC 2022 Fall も楽しみです。
もしかすると、オフラインも併用になるんでしょうか。ただ、飲食を伴う懇親会はまだ厳しそう…?


さて、最後にいつものを。

今回、残念ながら時間がかぶってしまって参加できなかったセッションがいっぱいあったので、あとで読むために現時点で発表者の方が公開されている資料一覧をまとめしました。1
(あとで JJUG CCC 2022 Spring のページにもリンクが載るかもしれませんが、とりあえず自分の方で調べました)

10:00

11:00

12:30

13:30

15:00

16:00

17:00

過去記事


  1. 発表者のお名前は敬称略とさせていただきました。

Java のラムダ式のクラスファイルを調べてみた!

Javaラムダ式が、どのようにコンパイルされ実行されているか気になりますよね。 そこで、クラスファイルを分析して、その中身を調べてみました。

なお、今回は OpenJDK 17.0.1 を使って調べています。 バージョンによって挙動が異なる場合があるので、ご注意ください。

外側のローカル変数を参照しない場合

おさらい:匿名クラスのコンパイル結果

本題のラムダ式を調べてみる前に、匿名クラスの場合にどのようにコンパイルされているかを確認しておきましょう。 (ご存じの方は、読み飛ばしてもらって大丈夫です)

今回は、Runnable インタフェースを匿名クラスとして実装しました。

public class Main {
  public static void main(String[] args) throws InterruptedException {
    var thread = new Thread(new Runnable() {
      @Override
      public void run() {
        System.out.println("Hello world!");
      }
    });

    thread.run();
    thread.join();
  }
}

このクラスをコンパイルすると、2つのクラスファイルが出来上がりました。

  • Main.class
  • Main$1.class

後者が匿名クラスのコンパイル結果です。 これを、OpenJDK 付属の javap でリバースアセンブルしてみました。

Classfile /C:/Users/YujiSoftware/Desktop/java/Main$1.class
  Last modified 2021/12/18; size 534 bytes
  SHA-256 checksum f6f8ec5118c480c54b31ee06db110837e3459fa50f8d052a7d157b32885fd22d
  Compiled from "Main.java"
class Main$1 implements java.lang.Runnable
  minor version: 0
  major version: 61
  flags: (0x0020) ACC_SUPER
  this_class: #21                         // Main$1
  super_class: #2                         // java/lang/Object
  interfaces: 1, fields: 0, methods: 2, attributes: 4
{
  Main$1();
    descriptor: ()V
    flags: (0x0000)
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public void run();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #13                 // String Hello world!
         5: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8
}

この javap の結果を、@YujiSoftware を使って Javaソースコードに変換してみました。 1

class Main$1 implements java.lang.Runnable {
  Main$1(){
    super();
  }
  
  public void run() {
    System.out.println("Hello world!");
  }
}

「外側のクラス名 + $ + 連番」という名前のクラスです。 クラスの中身は、普通の(匿名ではない)クラスとして実装したのと同じです。

なお、Java 言語仕様に基づいて、匿名コンストラクタが追加されています。

:::note info Java言語仕様:15.9.5.1 匿名コンストラクタ 】 匿名クラスは、明示的にコンストラクタを宣言することができない。その代わりに、コンパイラは匿名クラスに対して匿名コンストラクタ(anonymous constructor)を自動的に提供しなければならない。 :::

ラムダ式コンパイル結果

続いて、ラムダ式の場合を見ていきましょう。 先ほどの匿名クラスを使って書いた部分を、ラムダ式に置き換えました。

public class Main {
  public static void main(String[] args) throws InterruptedException {
    var thread = new Thread(() -> System.out.println("Hello world!"));

    thread.run();
    thread.join();
  }
}

このクラスをコンパイルすると、1つのクラスファイルが出来上がりました。

  • Main.class

匿名クラスの時のように、コンパイル時にはクラスファイルは作られませんでした。 代わりに、 実行時(より詳しく言えば、ラムダ式を含んだメソッドを初めて実行する際) に、メモリ上にクラスファイルが作られました。

メモリ上…、調べるのがとてもめんどくさいですね。

でも、簡単な方法があります。 システムプロパティ jdk.internal.lambda.dumpProxyClassesディレクトリを指定しておくと、その作られたクラスファイルを出力してくれるのです。

今回は、以下のように Java コマンドの引数でシステムプロパティを指定しました。

java -Djdk.internal.lambda.dumpProxyClasses=output Main

こうして実行すると、output ディレクトリにクラスファイルが出来上がりました。

  • Main$$Lambda$1.class

このクラスファイルを javap して、Javaソースコードに変換してみました。

Main$$Lambda$1.class の javap 結果

Classfile /C:/Users/YujiSoftware/Desktop/java/Main$$Lambda$1.class
  Last modified 2021/12/25; size 227 bytes
  SHA-256 checksum b1184aa81051852c052de612ab4b9f272f87cfd655717abb39dfa8ee74d8c5f4
final class Main$$Lambda$1 implements java.lang.Runnable
  minor version: 0
  major version: 59
  flags: (0x1030) ACC_FINAL, ACC_SUPER, ACC_SYNTHETIC
  this_class: #2                          // Main$$Lambda$1
  super_class: #4                         // java/lang/Object
  interfaces: 1, fields: 0, methods: 2, attributes: 0
{
  private Main$$Lambda$1();
    descriptor: ()V
    flags: (0x0002) ACC_PRIVATE
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #10                 // Method java/lang/Object."<init>":()V
         4: return

  public void run();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: invokestatic  #16                 // Method Main.lambda$main$0:()V
         3: return
}

final class Main$$Lambda$1 implements java.lang.Runnable {
  private Main$$Lambda$1() {
  }

  public void run() {
    Main.lambda$main$0();
  }
}

ラムダ式の中身である System.out.println("Hello world!"); がありません。 代わりに、 Main クラスの lambda$main$0() という static メソッドを呼び出しています。

このメソッドは何者でしょうか。 それを確認するために、Main.class を、 javap してみました。

すると、このクラス内に lambda$main$0() というメソッドが含まれていることが分かりました。

  private static void lambda$main$0();
    descriptor: ()V
    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #21                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #27                 // String Hello world!
         5: invokevirtual #29                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 3: 0
private static void lambda$main$0() {
  System.out.println("Hello world!");
}

どういうことかというと…

  • コンパイル時に、 ラムダ式の中身が static メソッドに変換され、 コンパイルされる
  • 実行時に、 インタフェース(今回は Runnable )の実装クラスが作られ、 コンパイルされる
    • このクラスは、事前に作られた static メソッドを呼び出すだけ

匿名クラスとは、処理が大きく異なりますね。

外側のローカル変数を参照する場合

続いて、匿名クラスやラムダ式その外側にあるローカル変数を参照した場合、 どのようなクラスファイルが出来上がるのかを見ていきましょう。

おさらい:匿名クラスのコンパイル結果

今回は、 main メソッド内にあるmessage というローカル変数を匿名クラス内で参照した場合に、どのようなクラスファイルになるかを見ていきます。

public class Main {
  public static void main(String[] args) throws InterruptedException {
    var message = "Hello world!";
    var thread = new Thread(new Runnable() {
      @Override
      public void run() {
        System.out.println(message);
      }      
    });

    thread.run();
    thread.join();
  }
}

このクラスをコンパイルすると、先ほどと同様に2つのクラスファイルが出来上がりました。

  • Main.class
  • Main$1.class

後者のクラスファイルを、javap して、Javaソースコードに変換してみました。

Main$1.class の javap 結果

Classfile /C:/Users/YujiSoftware/Desktop/java/Main$1.class
  Last modified 2021/12/25; size 603 bytes
  SHA-256 checksum 4b78e2e34c543f25184362a99d8b506dbe2fd4c46e7a662e245e6b96b8908c95
  Compiled from "Main.java"
class Main$1 implements java.lang.Runnable
  minor version: 0
  major version: 61
  flags: (0x0020) ACC_SUPER
  this_class: #2                          // Main$1
  super_class: #8                         // java/lang/Object
  interfaces: 1, fields: 1, methods: 2, attributes: 4
{
  final java.lang.String val$message;
    descriptor: Ljava/lang/String;
    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC

  Main$1();
    descriptor: (Ljava/lang/String;)V
    flags: (0x0000)
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #1                  // Field val$message:Ljava/lang/String;
         5: aload_0
         6: invokespecial #7                  // Method java/lang/Object."<init>":()V
         9: return
      LineNumberTable:
        line 4: 0
    Signature: #12                          // ()V

  public void run();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: getfield      #1                  // Field val$message:Ljava/lang/String;
         7: invokevirtual #19                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        10: return
      LineNumberTable:
        line 7: 0
        line 8: 10
}
SourceFile: "Main.java"
EnclosingMethod: #34.#36                // Main.main
NestHost: class Main
InnerClasses:
  #2;                                     // class Main$1

class Main$1 implements java.lang.Runnable {
  final String val$message;

  Main$1(String arg1) {
    this.val$message = arg1;
    super();
  }

  public void run() {
    System.out.println(this.val$message);
  }
}

なんと、コンストラクタに引数が追加されています。 そして、その引数の値をフィールド変数として保持しています。

run() メソッド内では、そのフィールド変数の値を参照しています。 直接、ローカル変数の値を参照しているわけではないんですね…。

よく見ると、コンストラクタでフィールド変数に代入してから、super() を呼び出しています。 Java 言語仕様(8.8.7 コンストラクタの本体)としては、最初に this または super のコンストラクタを明示的または暗黙的に呼び出さなくてはいけないので、あれ?という感じがしますね。 でも、大丈夫です。この点は、Javaのクラスファイルとしては問題ありません。

:::note Java仮想マシン仕様:4.8.2 構造上の制約 】 クラス Object のコンストラクタから導出されたインスタンス初期化メソッドを除いた各インスタンス初期化メソッドは、該当インスタンス・メンバに対するアクセスの前に、this に対する別のインスタンス初期化メソッドか、その直接のスーパークラス super に対するインスタンス初期化メソッドのいずれかを呼び出さなければならない。しかし、インスタンス初期化メソッドの呼び出しに先立って、カレント・クラスで宣言されている this に対するインスタンスフィールドへの代入を行うことができる。 :::

ラムダ式コンパイル結果

続いて、ラムダ式の場合を見ていきましょう。 先ほどの匿名クラスを使って書いた部分を、ラムダ式に置き換えました。

public class Main {
  public static void main(String[] args) throws InterruptedException {
    var message = "Hello world!";
    var thread = new Thread(() -> System.out.println(message));

    thread.run();
    thread.join();
  }
}

このクラスをコンパイルすると、1つのクラスファイルが出来上がりました。

  • Main.class

そして、これをシステムプロパティ jdk.internal.lambda.dumpProxyClasses=output を付けて実行した結果、output ディレクトリにクラスファイルが出来上がりました。

  • Main$$Lambda$1.class

このクラスファイルを javap して、Javaソースコードに変換してみました。

Main$$Lambda$1.class の javap 結果

Classfile /C:/Users/YujiSoftware/Desktop/java/Main$$Lambda$1.class
  Last modified 2021/12/25; size 307 bytes
  SHA-256 checksum 9ba2c22eca9a152b636e2a0b00a4a12cb0039ed659914bcc48103ec6718a0d17
final class Main$$Lambda$1 implements java.lang.Runnable
  minor version: 0
  major version: 59
  flags: (0x1030) ACC_FINAL, ACC_SUPER, ACC_SYNTHETIC
  this_class: #2                          // Main$$Lambda$1
  super_class: #4                         // java/lang/Object
  interfaces: 1, fields: 1, methods: 2, attributes: 0
{
  private final java.lang.String arg$1;
    descriptor: Ljava/lang/String;
    flags: (0x0012) ACC_PRIVATE, ACC_FINAL

  private Main$$Lambda$1(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: (0x0002) ACC_PRIVATE
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: invokespecial #13                 // Method java/lang/Object."<init>":()V
         4: aload_0
         5: aload_1
         6: putfield      #15                 // Field arg$1:Ljava/lang/String;
         9: return

  public void run();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #15                 // Field arg$1:Ljava/lang/String;
         4: invokestatic  #21                 // Method Main.lambda$main$0:(Ljava/lang/String;)V
         7: return
}

final class Main$$Lambda$1 implements java.lang.Runnable {
  private final String arg$1;

  private Main$$Lambda$1(String arg1) {
    super();
    this.arg$1 = ar$1;
  }

  public void run() {
    Main.lambda$main$0(this.arg$1);
  }
}

匿名クラスと同様に、コンストラクタに引数が追加されています。 そして、その引数の値をフィールド変数として保持しています。

run() メソッド内では、そのフィールド変数の値を参照しています。 あとは、「外側のローカル変数を参照しない場合」と同じですね。

外側のローカル変数をいっぱい参照する場合

ローカル変数は、1メソッド内で 65,535 個まで宣言できます。 また、メソッドの引数には int 型などでは 255 個まで宣言できます。2 (コンストラクタも同様)

ということは、ラムダ式の内部から、外側のローカル変数を256個以上参照した場合はどうなるのでしょうか。 ここまで見てきたように、ローカル変数はコンストラクタの引数で受け渡していますが、メソッドの引数は 255 個までなので 256 個以上は渡せません。

ローカル変数を256個参照した場合

外側のローカル変数を256個参照するラムダ式を、実際にコンパイルしてみました。

public class Main {
  public static void main(String[] args) throws InterruptedException {
    var number1 = 1;
    var number2 = 2;
    (中略)
    var number255 = 255;
    var number256 = 256;

    var thread = new Thread(() -> {
      System.out.println(number1);
      System.out.println(number2);
      (中略)
      System.out.println(number255);
      System.out.println(number256);
    });

    thread.run();
    thread.join();
  }
}

その結果、 コンパイルエラーになりました。

C:\Users\YujiSoftware\Desktop\java>javac *.java 
Main.java:1: エラー: パラメータが多すぎます
エラー1個

特に対処することなく、256個の引数を持つ static メソッドを作ろうとしてエラーになるみたいです。

ちなみに、匿名クラスの場合はコンパイルに成功しました。 ただし、無効なクラスファイルになったため、実行時に ClassFormatError になりました。

C:\Users\YujiSoftware\Desktop\java>java -Djdk.internal.lambda.dumpProxyClasses=. Main 
Exception in thread "main" java.lang.ClassFormatError: Too many arguments in method signature in class file Main$1
        at java.base/java.lang.ClassLoader.defineClass1(Native Method)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1012)
        at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
        at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862)
        at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760)
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681)
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
        at Main.main(Main.java:260)

ローカル変数を255個参照した場合

ラムダ式で参照する外側のローカル変数を255個に減らしてみました。 すると、コンパイルに成功しました。

しかし、実行してみると BootstrapMethodError になりました。

C:\Users\YujiSoftware\Desktop\java>java -Djdk.internal.lambda.dumpProxyClasses=. Main 
Exception in thread "main" java.lang.BootstrapMethodError: java.lang.IllegalArgumentException: bad parameter count 256
        at Main.main(Main.java:260)
Caused by: java.lang.IllegalArgumentException: bad parameter count 256
        at java.base/java.lang.invoke.MethodHandleStatics.newIllegalArgumentException(MethodHandleStatics.java:167)
        at java.base/java.lang.invoke.MethodType.checkSlotCount(MethodType.java:223)
        at java.base/java.lang.invoke.MethodType.insertParameterTypes(MethodType.java:437)
        at java.base/java.lang.invoke.MethodType.appendParameterTypes(MethodType.java:461)
        at java.base/java.lang.invoke.DirectMethodHandle.makePreparedLambdaForm(DirectMethodHandle.java:256)
        at java.base/java.lang.invoke.DirectMethodHandle.preparedLambdaForm(DirectMethodHandle.java:233)
        at java.base/java.lang.invoke.DirectMethodHandle.preparedLambdaForm(DirectMethodHandle.java:218)
        at java.base/java.lang.invoke.DirectMethodHandle.preparedLambdaForm(DirectMethodHandle.java:227)
        at java.base/java.lang.invoke.DirectMethodHandle.make(DirectMethodHandle.java:108)
        at java.base/java.lang.invoke.MethodHandles$Lookup.getDirectMethodCommon(MethodHandles.java:4004)
        at java.base/java.lang.invoke.MethodHandles$Lookup.getDirectMethodNoSecurityManager(MethodHandles.java:3960)
        at java.base/java.lang.invoke.MethodHandles$Lookup.getDirectMethodForConstant(MethodHandles.java:4204)
        at java.base/java.lang.invoke.MethodHandles$Lookup.linkMethodHandleConstant(MethodHandles.java:4152)
        at java.base/java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(MethodHandleNatives.java:615)
        ... 1 more

java.lang.IllegalArgumentException: bad parameter count 256 とのことです。 これは、コンストラクタの最初の引数として暗黙的に自身のオブジェクトが追加されているため、引数の数が +1 されて256個になってしまったのが原因のようです。

ローカル変数を254個参照した場合

もう一個減らして、ラムダ式で参照する外側のローカル変数を254個にしてみました。

しかし、これも実行してみると BootstrapMethodError になりました。 (255個の時と微妙にスタックトレースが違います)

C:\Users\YujiSoftware\Desktop\java>java -Djdk.internal.lambda.dumpProxyClasses=. Main 
Exception in thread "main" java.lang.BootstrapMethodError: bootstrap method initialization exception
        at java.base/java.lang.invoke.BootstrapMethodInvoker.invoke(BootstrapMethodInvoker.java:188)
        at java.base/java.lang.invoke.CallSite.makeSite(CallSite.java:315)
        at java.base/java.lang.invoke.MethodHandleNatives.linkCallSiteImpl(MethodHandleNatives.java:281)
        at java.base/java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:271)
        at Main.main(Main.java:259)
Caused by: java.lang.IllegalArgumentException: bad parameter count 256
        at java.base/java.lang.invoke.MethodHandleStatics.newIllegalArgumentException(MethodHandleStatics.java:167)
        at java.base/java.lang.invoke.MethodType.checkSlotCount(MethodType.java:223)
        at java.base/java.lang.invoke.MethodType.insertParameterTypes(MethodType.java:437)
        at java.base/java.lang.invoke.DirectMethodHandle.makePreparedLambdaForm(DirectMethodHandle.java:259)
        at java.base/java.lang.invoke.DirectMethodHandle.preparedLambdaForm(DirectMethodHandle.java:233)
        at java.base/java.lang.invoke.DirectMethodHandle.preparedLambdaForm(DirectMethodHandle.java:218)
        at java.base/java.lang.invoke.DirectMethodHandle.preparedLambdaForm(DirectMethodHandle.java:227)
        at java.base/java.lang.invoke.DirectMethodHandle.makeAllocator(DirectMethodHandle.java:142)
        at java.base/java.lang.invoke.DirectMethodHandle.make(DirectMethodHandle.java:133)
        at java.base/java.lang.invoke.MethodHandles$Lookup.getDirectConstructorCommon(MethodHandles.java:4122)
        at java.base/java.lang.invoke.MethodHandles$Lookup.getDirectConstructor(MethodHandles.java:4106)
        at java.base/java.lang.invoke.MethodHandles$Lookup.findConstructor(MethodHandles.java:2751)
        at java.base/java.lang.invoke.InnerClassLambdaMetafactory.buildCallSite(InnerClassLambdaMetafactory.java:269)
        at java.base/java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:341)
        at java.base/java.lang.invoke.BootstrapMethodInvoker.invoke(BootstrapMethodInvoker.java:134)
        ... 4 more

これは、原因が分かりませんでした。 ラムダ式ではなく、匿名クラスだと参照するローカル変数が254個でも大丈夫なんですが…。

ちなみに、参照するローカル変数を253個にまで減らせば、ラムダ式でもエラーにならずに実行できました。

最後に

Java Advent Calendar 2021 も、これにて終了です!
みなさん、お疲れ様でした。

メリークリスマス!!!

SantaDuke.png


  1. これ以降も、同様に javap の結果を見ながら @YujiSoftware が気合で Javaソースコードに変換しているので、間違えているところがあるかも…。

  2. long や double は2個としてカウントします。「arg1 が long 型や double 型である場合、ローカル変数 1 と 2 が用いられる(Java仮想マシン仕様:6 Java仮想マシンの命令セット, invokespecial より)」

JJUG CCC 2021 Fall ( #jjug_ccc ) - セッション資料の一覧

JJUG CCC 2021 Fall に参加しました!

JJUG CCC オンラインも3回目ということで、JJUG運営の方も参加者の方もお互い慣れてきた感じがします。 自分も、今回は夕方から用事があったので、時間差でセッションを見ながら電車で移動ということをやりました。ラジオの生放送と Radiko のオンデマンドの組み合わせみたいな感じで、リアルタイムでみんなで一緒にわいわいできるのも、時間と場所にとらわれずに見れるのもどっちもいいなーと思うようになりました。

だから、オフラインで集まりつつ1、今回のように配信でも見れるといいのかな、と。
コストが不安になりますがw

セッションは、最近のお仕事に関連しそうな「エキサイトブログ刷新に向けて序章」など、パフォーマンス改善やリファクタリングの話を中心に聞いていました。 教科書的な話だけでなく、なぜか歴史的経緯で不思議な構成になっているという話に「あるあるw」と共感したり、現状をもとにした工夫を聞いて何か自分の仕事にも取り入れられないかと考えたりと、いつものことながら楽しく役に立つ時間でした。


さて、最後にいつものを。

今回、残念ながら時間がかぶってしまって参加できなかったセッションがいっぱいあったので、あとで読むために現時点で発表者の方が公開されている資料一覧をまとめしました。2
(あとで JJUG CCC 2021 Fall のページにもリンクが載るかもしれませんが、とりあえず自分の方で調べました)

10:00

11:00

12:30

13:30

15:00

16:00

17:00

  • A: Getting the Most from Modern Javaf:id:chiheisen:20210523185503p:plain:w16:h16 動画) / Simon Ritter
  • B: Building for Resiliency, Fault-Tolerance, Scalability and Your User's Expectation(f:id:chiheisen:20210523185503p:plain:w16:h16 動画) / Chris Engelbert
  • D: Javaとコミュニティの歩み / 日本Javaユーザーグループ 鈴木雄介/杉山貴章

過去記事


  1. オンラインカンファレンス会場 reBako.io があったものの、うまく使いこなせず…。

  2. 発表者のお名前は敬称略とさせていただきました。

Outlook のメールファイル(.msg ファイル)をテキストに展開するツールを用意しました

たまに、Outlook のメールファイル(.msg ファイル)が添付で送られてくるのですが、手元に Windows 版の Outlook がなくて1内容を確認できなくて困ることが多々…。

そこで、.msg ファイルをテキストファイルに展開するツールを用意しました。
(添付ファイルも展開できます)

YujiSoftware/msg2txt: Extract .msg file (Outlook mail file) to a text file.

インストーラ版の方には Java ランタイムが含まれていますので、どの環境でもすぐに使えます。
自分のお気に入りの Java ランタイムを使いたいという場合は、スタンドアローン版をご利用ください! 2

実装について

Apache POI-HSMFJava API To Access Microsoft Outlook MSG Files)のサンプルプログラムをビルドしただけです。

頑張ったところ

それだけだと面白みがないので、Java 15 で正式リリースされた Packaging Tool を使って、インストーラ版も作ってみました。
… といっても、適切な引数を指定して jpackage コマンドを実行するだけでいい感じのインストーラができあがるので、とても簡単でした。

なお、手元に macOS のビルド環境はないので、インストーラの作成には GitHub Actions を使いました。3
runs-on: macOS-latest と指定するだけでいい感じの環境でビルドしてくれるので、マニュアル通りに書いていくだけでサクッと使えました。
これ、便利ですね(いまさら感)。

まとめ

というわけで、ぜひご利用ください!


  1. macOS 版の Outlook だと、.msg ファイルを開けないです。

  2. スタンドアローン版は Java 8 以上に対応しています。

  3. 作れはしたけど、動作確認ができていないです…。どなたか、サンプルファイルを展開できるかどうか確認していただけると嬉しいです。

いつから touch コマンドでファイルを作れるようになったのか

答え: 最初から

最初の touch コマンド

FreeBSD の touch のマニュアル曰く、touch コマンドは Version 7 の AT&T UNIX で登場したそうです。

HISTORY A touch utility appeared in Version 7 AT&T UNIX. touch(1) - FreeBSD Manual Pages

このときの man を読むと、すでに「ファイルが存在しなければ作成する」ということが記載されています。

DESCRIPTION

Touch attempts to set the modified date of each file. This is done by reading a character from the file and writing it back. If a file does not exist, an attempt will be made to create it unless the -c option is specified.

Touchは、それぞれのファイルの修正日時の設定を試みます。 これは、ファイルから文字を読み取り、それを書き戻すことによって行われます。 ファイルが存在しない場合、-c オプションが指定されていない限り、ファイルの作成を試みます。

unix-history-repo/touch.1 at Research-V7 · dspinellis/unix-history-repo · GitHub

ソースコード1を読むと、たまたまファイルがなければ作るようになったのではなく、あえてそのように実装していたことが分かりました。
具体的には、まず stat(3) でファイルの存在を確認して、なければ create(3) でファイルを作るという実装をしていました。 unix-history-repo/touch.c at Research-V7 · dspinellis/unix-history-repo · GitHub

#include <sys/types.h>
#include <sys/stat.h>

touch(force, name)
int force;
char *name;
{
struct stat stbuff;
char junk[1];
int fd;

if( stat(name,&stbuff) < 0)
    if(force)
        goto create;
    else
        {
        fprintf(stderr, "touch: file %s does not exist.\n", name);
        return;
        }

if(stbuff.st_size == 0)
    goto create;

if( (fd = open(name, 2)) < 0)
    goto bad;

if( read(fd, junk, 1) < 1)
    {
    close(fd);
    goto bad;
    }
lseek(fd, 0L, 0);
if( write(fd, junk, 1) < 1 )
    {
    close(fd);
    goto bad;
    }
close(fd);
return;

bad:
    fprintf(stderr, "Cannot touch %s\n", name);
    return;

create:
    if( (fd = creat(name, 0666)) < 0)
        goto bad;
    close(fd);
}

では、なぜファイルが存在しない場合にエラーにするのではなく、ファイルを作るようにしたのでしょうか。

それは、わかりませんでした(ぇ

まとめ

いかがでしたでしょうかw

ソースコードにコメントはなく、なぜこのような仕様にしたのかはこのコードを書いたDennis M. Ritchie2しか知る由がなさそうです。ただ、2011年に亡くなられたので、もはや聞くことはできません…。

おそらく、その方が便利だからだとは思うのですが、確証がありません。
もしかしたら本か何かに、その意図が残されているかもしれません。
ご存じの方がいましたら、情報をお寄せいただければと思います。


  1. たったの70行しかありません。

  2. C言語の生みの親。当時はベル研究所に所属していた。