vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

英語でLightning Talk

はじめに

5/14に行われた Oracle Dev Tour Japan in Osaka で LT をさせていただきました。

kanjava.connpass.com

speakerdeck.com

初の英語での LT ということで、日本語とは違うところがあったので準備の話などとともに書き残しておくことにします。

なお一応書きますが、英語で LT やるコツとか出てきません。

僕が知りたいくらいです。

発表について

まぁ完全に準備不足であったと思います。

大きく2点あって、原稿は作ったもののそれを声に出して読む、という練習が足りなかったと思います。

原稿を忘れてしまっても、その場で作りながら話をする、ということが理想ではあるのですが、まだそこまで消化できていなかったのだと思います。

家だとどうしても子どもたちが寝ていたり横やりが入ったりと難しいので、広い公園やカラオケボックスなど練習できる場所の確保が必要ですね。

もう一つがスライドで、内容自体は自分なりにわかりやすくなるよう作ったつもりですが、発表のときに(ディスプレイが複製モードになっていなかったため)自分のPCのモニタから見えず、カンペ代わりにできなかったという問題がありました。

Office というかパワポについては、やっぱり MS Office 良いなぁ、という感じがします。
(スクリーン側に全画面でスライドを表示して、PC側でメモや経過時間などを見ることができるため)

家のディスプレイを繋いでの確認もやっておかないといけませんね。

なお Ubuntu でディスプレイの設定を変更するには、 Settings > Devices > Displays からできる。。。ハズ。

ディスプレイは Mac Mini 用に一台あるのですが、ケーブルが HDMI と DVI ケーブルしかなくて VGA が必要な PC につなげませんでしたorz

明日か明後日に買おっと。

英語で原稿を書くことについて

日本語で書くようにスラスラと英語が出てきてくれるのが理想ですが、これも現状では難しいので、 Google 先生に頼ることにしました。

前職で韓国語に翻訳する必要があったりしたときに使っていた方法ですが、日本語を韓国語に翻訳 -> 翻訳結果を日本語に再翻訳して意味が通るかを確認してました。

英語は多少は書ける(気がする)ので、単語は調べつつ英文を作る -> 日本語に翻訳して、おかしな日本語になっていたらやり直ししたりしていました。

あと発音がわからないときに、これまた Google 翻訳の発声機能を使って確認できたのも助かりました。

この方法で、英語のブログに挑戦してみるのも良いかもしれませんね。

原稿

せっかくなので用意していた原稿も載せておきます。


Start

Ok, Let's start.

I'm Masui Masanori.

Today I'll talk about "var".

Have you already used it?

about Java's 'var'

  • The official name is 'Local-Variable Type Inference'.
  • It infers the type from the right side value.
  • If the type can't be inferred, the compiling error is occured.
  • You should take care what type is inferred. Because the type sometimes differ from your expected.
  • It can declare the variable of anonymous class.

about C#'s 'var'

  • C# also has 'var'.
  • Most of the features are as same as Java's.

Question

  • I felt C# programmers are more actively seeking to use it than Java programmers.
  • Where does the difference come from? and should I use it?

Merit of using 'var'

First, I talk about the merits of 'var'.

  • It improve readability. Because it make the code simpler.
  • It induces better naming. Because the type name is omitted from left side, so the programmer will seek to compensate the infomations with other parts.
  • If the precise type isn't so important, You can treat the type ambiguous. For example in this code. Even if the returning value type is changed to array of string, you don't need any modifications.

Demerit of using 'var'

Next, I talk about the demerits. * It reduces readability. For example in this code, I don't know what type will be returned. And even if I can understand the type, you shouldn't name like this.

  • It risks type mismatch. The biggest problem is I may not be able to know the type mismatch because the compiling error isn't occured.

Where does the difference come from?

  • This situation is not much different between Java and C#.
  • One of the big difference is that C#'s 'var' was implemented since over 10 years ago, so the programmers have been used to using it.
  • I think maybe this is the reason.
  • I don’t know if the situation of Java also will be as same as C#'s.

Should we use ‘var’ ?

I move another question.

I think we should use ‘var’ if there is no special reason.Like the examples what I talked.

Because keeping code simple makes us to think other important parts of code.

And the most important reason of my opinion is my favorit IDEs of C# suggest using it.

Summary

  • Because ‘var’ has some merits and demerits, so please use it properly.​
  • If the variable needs the precise type, please write the type explicitly.​
  • If your team has coding rules, please follow them or update them first.​

Rerences

End

Thank you for listening.


おわりに

正直なところもっと練習しておけば。。。と思い頭をかきむしりたくなる気持ちを抑えつつこの記事を書いているわけなのですが、挑戦したこと自体はとても良かったし、貴重な経験だったと思っています。

ということで、そんな貴重な場を提供していただいた関ジャバの方々と Oracle Dev Tour Japan の方々、何より温かく見守ってくださった皆様、本当にありがとうございました(..)_

いつかどこかでリベンジも狙っていきます(๑•̀ㅂ•́)و✧

Java と C# の var をぼんやり比較

はじめに

みなさん Java10 使ってますか〜?(何)

Java10 で特に注目された(多分)新機能に、 var の導入があります。

var については後述しますが C♯ では ver.3.0 から導入されていたりします。

ただ C♯ では使えるところでは使う、位の扱いになっている(個人の感想です)のに対し、Javaではもう少し控えめに使おう、 という感じを受けました。

まぁJavaでは登場したばっかりだったり、別の型推論があったりと状況が違うところはあるためあくまで現状、 ということにはなりますが。

とはいえ、この違いはどこから来るのだろう?と思ったので調べてみることにしました。

var について

まずは var についてですが、ローカル変数の定義において、左辺に型を明示しなくても右辺の値から型を推論してくれる、というものです。

var id = 0; // int 型として扱われる.

int や string くらいならともかく、10 文字 20 文字と長くなってくると見る方も辛くなってきますね。

そのような場合でもスッキリ書ける、という利点があります。

  • JavaScript などとは異なり、 var を使うからといって定義後に別の型に変更することはできません。
  • コンパイル時に推論された型に置き換わります。
  • 右辺の型から推論するため、「var e;」 や 「var n = null;」 のようにするとエラーになります。

匿名クラス

また var が必須となる場合として、匿名クラスがあります。

Java

var person = new Object() {
    private String name;
    private int id;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public int getId(){
        return id;
    }
    public void setId(int id){
        this.id = id;
    }
};
person.setName("masanori");
person.setId(0);

System.out.println("name: " + person.getName() + " id: " + person.getId());

C♯

var person = new
{
    Name = "masanori", Id = 0,
};
Console.WriteLine("name: " + person.Name + " id: " + person.Id);

Java ではメソッドの定義が可能だったり、定義後の変数の変更が可能だったりと、言語仕様上の違いはありますが、いずれも var を使わずに定義しようとするとエラーになります。

Java は 「new Object」 となっているので一見 Object 型かな?とも思ってしまいますが。

var が使えない場合

ローカル変数であれば常に var が使えるかというと、そうではありません。
いくつかの状況ではコンパイルエラーになります。

ラムダ式 (Java, C♯)

下記のようなラムダ式では右辺の方が確定しないので var を使うことはできません。

// Compile error.
var lambdasample = () => {
   // Do something.
};

Java

var lambdasample = (Runnable)(() -> {
   // Do something   
});
Runnable lambdasample2 = ()->{
   // Do something
};

C♯

// OK
var lambdasample = (Action)(() => {
    // Do something   
});

// OK
Action lambdasample = () => {
   // Do something
};

配列の定義 (Java, C♯)

配列を定義する場合、下記のように書くとコンパイルエラーとなります。

// Compile error.
var arraySample = {0, 2};

理由は型が int[] に限定できないため、ということのようです。

Java

// OK.
var arraySample1 = new int[]{0, 2};
// OK.
int[] arraySample2 = {0, 2};

C♯

// OK. "int"は書かなくてもOK.
var arraySample1 = new []{0, 2};
// OK.
int[] arraySample2 = {0, 2};

C♯の arraySample2 で int が推論されるなら、 arraySample も良さそうな気がしないでもないですが。

継承しているクラス・インターフェースとして定義する (C♯)

例えば定義しようとしている変数が、このようなインターフェースを継承したクラスであった場合。

ISample.java

public interface ISample {
    String getName();
}

SampleClass.java

public class SampleClass implements ISample{
    private String name;

    SampleClass(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
}

var を使うと、インターフェースではなくそれを実装している SampleClass として定義されます。

var sample = new SampleClass("hello");

ISample i = sample;
SampleClass s = sample;

// SampleClassとして定義される.
var s2 = sample;

この例で見るとそんなに大きな問題はないのですが、(C♯の) foreach で var を使った場合に思ったメソッドが出てこなくて???となる、といったことがまれによくありますね。

また C♯ で、(おそらく)上記の理由により、下記で SampleClass 、AnotherSampleClass がともに同じインターフェース (ISample) を継承していても下記のように書くことはできません。

// CompileError.
var samples = new[] {
    new SampleClass(1, "1"),
    new AnotherSampleClass(2, "2"),
};

// OK
ISample[] samples2 = {
    new SampleClass(1, "1"),
    new AnotherSampleClass(2, "2"),
};

// OK
var samples3 = new ISample[]{
    new SampleClass(1, "1"),
    new AnotherSampleClass(2, "2"),
};

ダイアモンド演算子 (Java)

Java7 で導入されたダイアモンド演算子と var を一緒に使うことはできません。

正確には使うこと自体は可能ですが、下記の場合だと型が ArrayList< Object> になってしまいます。

// ArrayList< Object>
var sampleList = new ArrayList< >();

// List< String>
List< String> sampleList2 = new ArrayList< >();

まぁダイアモンド演算子が左辺から型を推論するものである以上当然とも言えますが。

var をいつ使うべきか

では、使えるところではすべて var を使うべきでしょうか。
これは C♯ でも議論が行われていたようですし、今 Java で行われていたりもします。

で、先に自分の考えを書いてしまうと、使えるところではどんどん var を使って良いと思っています。

var を使うメリット・デメリット

とはいえまず var を使うメリット・デメリットから。

varを使うメリット

  1. 特に型名が長い場合も簡潔に書ける
  2. 変数の型を変えたい場合の修正量が少なくなる
  3. メソッドの戻り値が分かっていなくても書ける
  4. コードを書く量が少なくなる

varを使うデメリット

  1. 型の情報が失われる (特に IDE ではなく Web 上でコードを見た場合に調べづらい)
  2. 呼び出しているメソッドの戻り値が変更された場合に気づきにくい

メリット 3. は特にあまり使ったことのないメソッドなどを調べたりするのに便利ですね。

メリット 4. は他の方の意見通り、 IDE を使っているなら旨味は少ないかもしれません。
ただ、メソッド名まで型を気にせずに書ける、という点は利点かもしれません。

ドキュメントでの扱い

ドキュメントでは var をどのように使うべきと書かれているかを見てみます。

Microsoft Docs では下記のように書かれています。

  • 変数の型が割り当ての右側から明らかである場合、または厳密な型が重要でない場合は、ローカル変数の暗黙の型指定を使用します。
  • 割り当ての右側から型が明らかではない場合、var を使用しないでください。
  • 変数の型を指定するときに変数名に頼らないでください。 変数名が正しくない場合があります。
  • dynamic の代わりに var を使用しないようにしてください。
  • for ループおよび foreach ループでループ変数の型を決定するときは、暗黙の型指定を使用します。

引用元: C# のコーディング規則 (C# プログラミング ガイド) - Microsoft Docs

一方の Java についてですが、 OpenJDK の Style Guidelines を見てみます。

なんちゃって翻訳のため、詳しい内容や原文などは下記をご覧ください(..)_

変数名

var を使うことで、その変数についての左辺の情報は減少します。

それだけに適切な変数名をつける、ということがより重要になってきます。
前述のドキュメントでも触れられていますし、 var を使うべきか、という話でほぼ必ず登場します。

良い変数名を付けることを強制できることがメリット、という意見もあれば

変数名などで変なルールを作るべきでない、という話もあります。

これについては後者に賛成しますが、 var を使う・使わないに限らずわかりやすい名前を付けることは大事ですね。

型の詳細

var を使う・使わないを考える上でもう一つ重要なことが、その変数を使うにあたって型の詳細が必要かどうか、ということだと思います。

例えば C♯ でメソッドを呼び出して、その戻り値を foreach で使いたい場合、それが IEnumerable でも List でも配列でも特に問題はないと思います。

public void DoSomething()
{
    // samplesがListや配列でもOK.
        var samples = GetSamples();
    foreach (var s in samples)
    {
        // 何かの処理.
    }
}
private IEnumerable GetSamples()
{
    return Enumerable.Range(0, 10)
        .Select(n => new SampleClass(n, "masanori " + n));
}

このような場合、 var を使ったとしても可読性は下がらないのではないでしょうか。
(もしくは型を明示したとしてもあまり可読性が向上しないように思います)

一方、 float だと思っていたものが int だった場合など、型が異なると問題が発生する場合もあります。

とりわけ Effective C♯ で指摘されている、メソッドの戻り値が変更されてしまう危険性がある場合は、型名を明示しておいた方が良いかもしれません。

ということでこの項の最初の話に戻りますが、私の考えとしては、基本的には var を使い、型が正確に一致していないと問題が発生する場合や、インターフェース型として扱いたいなど必要がある場合のみ型を明示する、という感じです。

こうすることで、厳密な型が必要な箇所とそうでもない箇所のメリハリもつく気もしますし。

あと、var を使う・使わないにかかわらずその変数がどのように振る舞うのかは正しく認識しておく必要がありますね。

おわりに

結局 C♯ と Java でなぜ捉え方が違っている(ように感じる)のか、という話については、仕様として導入されてからの時間によっているような気がします。

2011年とか昔の記事では、 C♯ でも var の使用に否定的な意見も見られたので。
(C♯ では ver.3.0 に導入された話なので、今更話題にもなりにくいというのはあると思いますが)

また個人的には、 JetBrains の ReSharper や Rider がデフォルトで積極的に var をおすすめしてくるのも大きな要素だとは思いますw

今後 IntelliJ IDEA がどうなるのか、 Java が C♯ と同じような状況になるかはわかりませんが。

途中にも出てきましたが、 Java にせよ C♯ にせよ、 var を使うことが必ずしも最良、というわけではありません。

ただ、シンプルにできるところはできるだけ簡素化し、重要なところに力を注げると良いなとは思います。

参照

C♯

Java

Javaのtry-with-resourcesとC#のusingを比較する その2

はじめに

前回の続きです。Java の try-with-resources と C# の using の共通点、相違点をダラダラと見ていきますよ。

どう展開されるか

try-with-resources と using は、それぞれ内部的にはどのように展開されるのでしょうか。

まずは Java の class ファイルを見てみます。

App.java

public class App{
    public static void main(String[] args){

        try (BaseSampleClass s1 = new BaseSampleClass("class1-1")){
            System.out.println("try1-1");
        }
        try (BaseSampleClass s1 = new BaseSampleClass("class1-2")){
            System.out.println("try1-2");
        }catch (Exception e){
            System.out.println("catch1-2");
        }
    }
}

App.class

public class App {
    public App() {
    }
    public static void main(String[] args) {
        BaseSampleClass s1 = new BaseSampleClass("class1-1");
        Throwable var2 = null;

        try {
            System.out.println("try1-1");
        } catch (Throwable var19) {
            var2 = var19;
            throw var19;
        } finally {
            $closeResource(var2, s1);
        }

        try {
            s1 = new BaseSampleClass("class1-2");
            var2 = null;

            try {
                System.out.println("try1-2");
            } catch (Throwable var16) {
                var2 = var16;
                throw var16;
            } finally {
                $closeResource(var2, s1);
            }
        } catch (Exception var18) {
            System.out.println("catch1-2");
        }
    }
}

try のみを書いた場合、 try-catch を書いた場合とで結果が異なっていますね。

C# では、 using のみを使用した場合 (try を使わなかった場合)、 try-finally が使用されています。

参照

ラムダ式における AutoCloseable(IDisposable)

次のコードは Java では問題ありませんが、 C# ではコンパイルエラーになります。

Java

// OK
try(IntStream nums = IntStream.range(0, 100)
        .map(n -> n)
        .filter(n -> n % 2 == 0)){
    // 何かの処理.
}

C♯

// CompileError
using (var nums = Enumerable.Range(0, 100)
    .Select(n => n)
    .Where(n => n % 2 == 0)){
    // 何かの処理.
}

これは、 IntStream が継承している BaseStream では AutoCloseable を継承しているのに対し、 Enumerable は IDisposable を継承していないためです。

では Stream API を使うときは、いつも try-with-resouces が必要なのか?というと、そうではないようです。

ドキュメントによると、基本的には close の必要はなく、下記のファイル操作のような、Stream API でなくてもCloseが必要なリソースを扱う場合のみ必要になるようです。

Path p = new File("test.txt").toPath();

try (Stream s = Files.lines(p, StandardCharsets.UTF_8)){
    s.forEach(System.out::println);
}
catch (IOException e){
    System.out.println(e.getLocalizedMessage());
}

なお上記のコードは C# だと以下のようになります。

File.ReadAllLines(@"test.txt", Encoding.UTF8)
    .ToList()
    .ForEach(Console.WriteLine);

あれ? using は?となりそうですが、 ReadAllLines を見てみると using が使われており、
自分では書かなくて良いよ〜、ということのようです。

2018.4.15 8:00更新

ReadAllLines は Java にもあるそうで、C# と同様に呼び出し先のメソッド内で try-with-resources が使われていました。
教えていただいたうらがみさん、ありがとうございましたm(__)m

try {
    Files.readAllLines(Paths.get("test.txt"))
               .forEach(System.out::println);
}
catch (IOException e){
    System.out.println(e.getLocalizedMessage());
}

File.cs

// Decompiled with JetBrains decompiler
// Type: System.IO.File
// Assembly: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// MVID: 65984520-5776-46EC-9044-386EC4A7B3DD
// Assembly location: /usr/lib/mono/4.5/mscorlib.dll

~省略~
public static string[] ReadAllLines(string path, Encoding encoding){
    using (StreamReader reader = new StreamReader(path, encoding))
        return File.ReadAllLines(reader);
}
~省略~

Linq で using が使えないためにこのような処理を行っているのか、このようにしているから Linq で using を使う必要がない、と判断したのかは追えていません。
(にわとりたまご的な)

また C# では IEnumerator が IDisposable を継承しているため、(必要かどうかはともかく)下記のようなことができます。

using (var n = Enumerable.Range(0, 10).GetEnumerator()){
    // 何かの処理            
}

こちらは後にも触れますが、 foreach のクリーンナップに使われるようです。

AutoCloseableとIDisposableは同じもの?

さてここまで見てきたとおり、 try-with-resources と using には共通点がかなり多いというか、ほとんど同じと言っても良さそうな気がします。

というところで気になったのが、AutoCloseable と IDisposable との違いです。
これらが対象とするクラスやその役割などは本当に同じなのでしょうか。

C♯

ドキュメントを見ると Dispose は、アンマネージ リソースに割り当てられたメモリを解放するためのものとなっています。

アンマネージ リソースというのは諸説あるようですが、 CLR が管理していないため、ガベージコレクションの対象にならないデータ、というくらいの認識で良さそう?です。

具体的にはファイル操作、HTTP通信などが該当します。

しかし、先程登場した Enumerable.Range では見た感じアンマネージ リソースは登場しないような気がします。

ということで Enumerable.Range を辿っていくと、System.Linq.Enumerable.Iterator で Disposeが定義されているようでした。

// Decompiled with JetBrains decompiler
// Type: System.Linq.Enumerable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// MVID: FB28F097-6905-4726-B681-ECB6DF11A20B
// Assembly location: /usr/lib/mono/4.5/System.Core.dll

~省略~
      public virtual void Dispose()
      {
        this._current = default (TSource);
        this._state = -1;
      }
      ~省略~

また C# の言語仕様を見てみると、foreach などで利用される Enumerator では Dispose が呼ばれたときに以下のように扱われるようです。

Enumerator の状態と Dispose が呼ばれたときの動作

  • before(処理前): 状態を「after」にする
  • after(処理後): 何もしない
  • running(処理中): unspecified
  • suspended( yield return で宣言して実行するまでの間?): Disposeを実行して状態をデフォルトに戻す

ここから、 IDisposable 及び Dispose の役割は、アンマネージ リソースを開放するだけではなく、データのクリーンナップという範囲でもう少し広い用途に使われているように思います。

Java

ドキュメントを見ると、 try-with-resources の対象になるデータは「プログラムでの使用が終わったら閉じられなければいけないオブジェクト」「リソースとして知られるローカル変数」のようになっており、 C# より範囲が広いように感じました。

ということで結論としては、使用目的としては大体同じ、ただし詳細や Close(Dispose) の動作には異なる点がある、というくらいでしょうか。

おわりに

同じような目的で作られ、同じように使われる機能であっても、詳細を調べてみると違っているところやこれまで気づかなかった特性を見つけることができたりして楽しいものですね。

一旦この話はここで締めますが、また特筆すべき違いなどがあればその3として書こうかなと思います。

参照

try-with-resources

AutoCloseable

Stream API

IDisposable

using

アンマネージドリソース

Javaのtry-with-resourcesとC#のusingを比較する その1

はじめに

テキスト入出力で用いられる BufferedReader を始め、使用後に明示的に Close する必要があるクラスがあります。
Java7 以降から、try-with-resources 文を使うことで、使用後に自動で Close してくれるようになります。

ふ〜ん。分かるような分からないようなと思っていたのですが、ある方の発言で一気に理解ができるようになりました。

C# の using 句と同じ」

言われてみればなんで気づかなかったのか、というくらいですが。

とはいえ、本当に両者は同じなのでしょうか。

この謎を探るため急遽我々はry

両者を比較してみることにしました。

[Java]基本のクラス

確認用にこのようなクラスを用意します。

BaseSampleClass.java

public class BaseSampleClass implements AutoCloseable{
    private final String message;
    public BaseSampleClass(String message){
        this.message = message;
    }
    public String getMessage() throws Exception{
        System.out.println("getMessage " + message);
        return message;
    }
    public void close() throws Exception{
        System.out.println("closed " + message);
    }
}
  • try-with-resources を使用するためには、 AutoCloseable または Closeable を継承している必要があります。
  • 各メソッドの throws Exception は現状では不要ですが、あとでそれぞれのメソッドでエラーが出た場合の検証に使います。
  • close メソッドは今回は呼ばれたタイミングが知りたいだけであるため、ログ出力のみ行っています。

呼び出すコードはこちら。

App.java

public class App{
    public static void main( String[] args){
        System.out.println("start");

        try(BaseSampleClass class0 = new BaseSampleClass("class0")){
            System.out.println(class0.getMessage());
        }
        catch (Exception e){
            System.out.println(e.getMessage());
        }
        System.out.println("end");
    }
}

[C#]基本のクラス

C# でも同様にクラスを作っていきます。

BaseSampleClass.cs

using System;

namespace ConsoleApplication1 {
    public class BaseSampleClass: IDisposable {
        private readonly string _message;

        public BaseSampleClass(string message) {
            _message = message;
        }

        public string GetMessage() {
            Console.WriteLine("getMessage " + _message);
            return _message;
        }
        public void Dispose() {
            Console.WriteLine("disposed " + _message);
        }
    }
}
  • using 句で使用するためには、 IDisposable を継承している必要があります。

Program.cs

using System;

namespace ConsoleApplication1{
    internal class Program{
        public static void Main(string[] args){            
            Console.WriteLine("start");

            try{
                using (BaseSampleClass class0 = new BaseSampleClass("class0")){
                    Console.WriteLine(class0.GetMessage());
                }
            }
            catch (Exception e){
                Console.WriteLine(e.Message);
            }
            Console.WriteLine("end");
        }
    }
}

複数宣言

複数のインスタンスを try-with-resources で宣言するには、入れ子にすることももちろん可能ですが、下記のように書くことができます。

App.java

try(HasInnerClass class0 = new HasInnerClass();
    HasInnerClass.InnerClass class1 = class0.new InnerClass()){
      // 何か処理.
}

Java の場合はそれぞれのインスタンスの型が同じでも違っても同じように書くことができます。

しかし、 C# では若干違いがあります。

生成したいインスタンスの型が同じ場合

using (BaseSampleClass class0 = new BaseSampleClass("class0"),
    class1 = new BaseSampleClass("class1")){
      // 何か処理.
}

生成したいインスタンスの型が異なる場合

using (BaseSampleClass class0 = new BaseSampleClass("class0"))
using (HasInnerClass class1 = new HasInnerClass()){
      // 何か処理.
}

それぞれ別々に書く必要があるわけです。

また、 using の場合は if などと同じように {} 無しでも書くことができるため、上記のような書き方になります。

staticなインスタンス

staticなインスタンスの場合、 JavaC# ともに下記のように書くことができます (Java は ver.9 から)。

App.java

public class App{
    private final static BaseSampleClass S = new BaseSampleClass("final");
    public static void main( String[] args){

        try(S){
          
            // 何か処理.
        }
        catch (Exception e){
            System.out.println(e.getMessage());
        }
        System.out.println("end");
    }
}

実行順序

Java, C# ともに実行順序は下記の通りです。

  1. try (using) ブロック
  2. close() (Dispose()) メソッド
  3. catch ブロック

InnerClass

InnerClass を持っていて、両方が AutoCloseable(IDisposable) を継承している場合の順序はどのようになるでしょうか。

HasInnerClass.java

public class HasInnerClass implements AutoCloseable{
  public HasInnerClass(){
      System.out.println("constructor HasInnerClass");
  }
  public void close(){
      System.out.println("closed HasInnerClass");
  }
  public class InnerClass implements AutoCloseable {
      public InnerClass(){
          System.out.println("constructor InnerClass");
      }
      public void close(){
          System.out.println("closed InnerClass");
      }
  }
}

App.java

public class App{
    public static void main( String[] args){
      System.out.println("start");

      try(HasInnerClass class0 = new HasInnerClass();
          HasInnerClass.InnerClass class1 = class0.new InnerClass()){
            // 何か処理.
      }
      catch (Exception e) {
          System.out.println(e);
      }
      System.out.println("end");
    }
}

実行結果は以下の通りです。

start
constructor HasInnerClass
constructor InnerClass
closed InnerClass
closed HasInnerClass
end

InnerClass -> EnclosingClass の順番に close されています。

ただし、 C# の場合は InnerClass のインスタンスを生成する場合は「HasInnerClass.InnerClass class1 = new HasInnerClass.InnerClass();」となり HasInnerClass のインスタンスが不要なため、 class1 と class0 を逆に宣言できます。

この場合、EnclosingClass -> InnerClass の順に dispose(close) されます。

つまり、後から宣言されたインスタンスが先に close(dispose) されるわけですね。

例外の抑制

これも Java, C# 共通ですが、1.の try ブロックまたは 2.の close() メソッドのどちらかで例外は発生した場合、3.の catch ブロックが実行されるわけですが、 1.と2.の両方で例外が発生した場合、2.の例外は抑制されます。

BaseSampleClass.java

public class BaseSampleClass implements AutoCloseable{
    private final String message;
    public String getMessage() throws Exception{
      
        throw new Exception("exception from getMessage()");
        
    }
    public BaseSampleClass(String message){
        this.message = message;
    }
    public void close() throws Exception{
        System.out.println("closed " + message);
        
        throw new Exception("exception from close()");
        
    }
}
  • getMessage()、 close() で例外を投げるよう変更しています。

これを実行すると、以下がログに出力されます。

start
closed class0
exception from getMessage()
end

close() で投げているはずの例外が抑制されています。

抑制された例外の取得

Java では抑制された例外を取得することができます。

App.java

public class App{
    public static void main( String[] args){
        System.out.println("start");

        try(BaseSampleClass class0 = new BaseSampleClass("class0")){
            System.out.println(class0.getMessage());
        }
        catch (Exception e){
            System.out.println(e.getMessage());

            Throwable[] throwables = e.getSuppressed();
            if(throwables != null){
                for(Throwable t: throwables){
                    System.out.println(t.getMessage());
                }
            }
            
        }
        System.out.println("end");
    }
}

これを実行すると、以下がログに出力されます。

start
closed class0
exception from getMessage()
exception from close()
end

close() で投げた例外も受け取ることができていますね。

注意点としては getSuppressed() で取得できるのは「抑制された例外」のみであることです。
つまり、最初に投げた getMessage() の例外は取得できません。

そのため、 e.getMessage() と合わせて確認する必要があります。

次へ

長くなってきたので一旦切ります。

参照

try-with-resources

using

【Ubuntu】Mavenのインストール → コンパイル

はじめに

タイトル通り、UbuntuMaven をインストールしてコンパイルしたときのお話。

後述する通りインストールは特に問題ありませんでしたが、プロジェクト作成→コンパイルでエラーが発生したのでメモっておくことにします。

インストール

インストール手順に従ってインストールしていきます。

まずMaven のダウンロードページからバイナリ版をダウンロードして、任意の場所に展開します。
(今回は Documents フォルダに置きました)

http://maven.apache.org/download.cgi

.bashrc にパスを追加します。

export PATH=/home/ユーザー名/Documents/apache-maven-3.5.3/bin:$PATH
export JAVA_HOME=/home/ユーザー名/Documents/jdk-10
export PATH=$PATH:$JAVA_HOME/bin
  • 下二行は Java のパスです。 Maven で使用される Java は、この JAVA_HOME の設定を自動で見るようです。

ターミナルで「mvn -version」と打ってバージョンや使用する Java のバージョンなどの情報が出たら OK です。

プロジェクトを作成する

Getting started に従って、プロジェクトを作成します。

mvn -B archetype:generate \
  -DarchetypeGroupId=org.apache.maven.archetypes \
  -DgroupId=com.mycompany.app \
  -DartifactId=my-app
  • プロジェクトの Archetype(テンプレート) として、「org.apache.maven.archetypes」を指定しています。
  • 「BUILD SUCCESS」とメッセージがでれば OK です。

コンパイル(失敗)

じゃあ、ということで次の手順であるコンパイルをやってみます。

。。。

失敗しました/(^o^)\

[INFO] Scanning for projects...
[INFO] 
[INFO] -------------------------< com.mycompany.app:my-app >-------------------------
[INFO] Building my-app 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ my-app ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /home/ユーザー名/Documents/workspace/my-app/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ my-app ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /home/ユーザー名/Documents/workspace/my-app/target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR : 
[INFO] -------------------------------------------------------------
[ERROR] Source option 5 is no longer supported. Use 6 or later.
[ERROR] Target option 1.5 is no longer supported. Use 1.6 or later.
[INFO] 2 errors 
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.286 s
[INFO] Finished at: 2018-03-27T07:12:06+09:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project my-app: Compilation failure: Compilation failure: 
[ERROR] Source option 5 is no longer supported. Use 6 or later.
[ERROR] Target option 1.5 is no longer supported. Use 1.6 or later.
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

エラーの内容を見ると、何かのバージョンが合っていないようです。
また、エンコーディングの警告も出ていますね。

あれこれググった結果、「何か」とは Mavenコンパイラーのバージョンのことで、エンコーディングの指定とともに、 pom.xml(プロジェクトのフォルダ直下に生成される) に記述が必要らしいとわかりました。

Before

< project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  < modelVersion>4.0.0< /modelVersion>
  < groupId>com.mycompany.app< /groupId>
  < artifactId>my-app< /artifactId>
  < packaging>jar< /packaging>
  < version>1.0-SNAPSHOT< /version>
  < name>my-app< /name>
  < url>http://maven.apache.org< /url>
  < dependencies>
    < dependency>
      < groupId>junit< /groupId>
      < artifactId>junit< /artifactId>
      < version>3.8.1< /version>
      < scope>test< /scope>
    < /dependency>
  < /dependencies>
< /project>

After

< project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  < modelVersion>4.0.0< /modelVersion>
  < groupId>com.mycompany.app< /groupId>
  < artifactId>my-app< /artifactId>
  < packaging>jar< /packaging>
  < version>1.0-SNAPSHOT< /version>
  < name>my-app< /name>
  < url>http://maven.apache.org< /url>
  

  < properties>
    < maven.compiler.source>1.6< /maven.compiler.source>
    < maven.compiler.target>1.6< /maven.compiler.target>
    < project.build.sourceEncoding>UTF-8< /project.build.sourceEncoding>
  < /properties>
  

  < dependencies>
    < dependency>
      < groupId>junit< /groupId>
      < artifactId>junit< /artifactId>
      < version>3.8.1< /version>
      < scope>test< /scope>
    < /dependency>
  < /dependencies>
< /project>

これで「mvn compile」も正しく実行できるようになりました。

my-app > target > classes > com > mycompany > app に App.class というクラスファイルが出力されます。

実行方法は下記の2つがあるようです。

1

下記コマンドを実行する

mvn exec:java -Dexec.mainClass="mvn exec:java -Dexec.mainClass="com.mycompany.app.App""

2

Jar ファイルを作成

mvn package

Jar ファイルを実行

java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.

参照

UbuntuでSDKMAN をインストールしようとして失敗したときのメモ

はじめに

相変わらず使用言語が定まりません。どうもこんにちは。

Java10 もリリースされたということで、さっそく OpenJDK10 をインストールしてみよう。SDKMAN を使って。
と思ったらエラーが出てうまくいかなかったので、対処法を探したときのメモです。

原因からするとあまり他の方の役にはたたないような気もしますが。。。

問題

下記の方法に従って SDKMAN をインストールしようとしたところ、2つ問題が発生しました。

  1. curl -s "https://get.sdkman.io" | bash」と入力しても動作せず、「curl -s get.sdkman.io | bash」だと動作した
  2. インストール完了のメッセージが出ているのに、「source "/home/ユーザー名/.sdkman/bin/sdkman-init.sh"」が見つからないとエラーが出る

2.を更に調べると、途中で zip ファイルがダウンロードできていないことがわかりました。

下記に似たような問題が発生しているようでしたが、別にどこかの IP Address をブロックしているわけでもないしなぁ。。と手が止まってしまいました。

https://github.com/sdkman/sdkman-cli/issues/536

原因

一旦 SDKMAN を諦めて、 apt-add-repository で PPA を追加しようとしたときに原因が判明しました。

「sudo add-apt-repository ppa:webupd8team/java」などを実行すると、下記のようなエラーが発生したのです。

Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/softwareproperties/ppa.py", line 358, in get_ppa_info
    ret = get_ppa_info_from_lp(user, ppa)
  File "/usr/lib/python3/dist-packages/softwareproperties/ppa.py", line 94, in get_ppa_info_from_lp
    return get_info_from_lp(lp_url)
  File "/usr/lib/python3/dist-packages/softwareproperties/ppa.py", line 88, in get_info_from_lp
    return _get_https_content_py3(lp_url)
  File "/usr/lib/python3/dist-packages/softwareproperties/ppa.py", line 112, in _get_https_content_py3
    lp_page = urllib.request.urlopen(request, cafile=LAUNCHPAD_PPA_CERT)
  File "/usr/lib/python3.6/urllib/request.py", line 213, in urlopen
    capath=capath)
  File "/usr/lib/python3.6/ssl.py", line 512, in create_default_context
    context.load_verify_locations(cafile, capath, cadata)
ssl.SSLError: unknown error (_ssl.c:3566)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/bin/add-apt-repository", line 122, in 
    shortcut = shortcut_handler(line)
  File "/usr/lib/python3/dist-packages/softwareproperties/SoftwareProperties.py", line 864, in shortcut_handler
    ret = factory(shortcut)
  File "/usr/lib/python3/dist-packages/softwareproperties/ppa.py", line 430, in shortcut_handler
    return PPAShortcutHandler(shortcut)
  File "/usr/lib/python3/dist-packages/softwareproperties/ppa.py", line 387, in __init__
    info = get_ppa_info(self.shortcut)
  File "/usr/lib/python3/dist-packages/softwareproperties/ppa.py", line 370, in get_ppa_info
    _get_suggested_ppa_message(user, ppa))
  File "/usr/lib/python3/dist-packages/softwareproperties/ppa.py", line 327, in _get_suggested_ppa_message
    lp_user = get_info_from_lp(LAUNCHPAD_USER_API % user)
  File "/usr/lib/python3/dist-packages/softwareproperties/ppa.py", line 88, in get_info_from_lp
    return _get_https_content_py3(lp_url)
  File "/usr/lib/python3/dist-packages/softwareproperties/ppa.py", line 112, in _get_https_content_py3
    lp_page = urllib.request.urlopen(request, cafile=LAUNCHPAD_PPA_CERT)
  File "/usr/lib/python3.6/urllib/request.py", line 213, in urlopen
    capath=capath)
  File "/usr/lib/python3.6/ssl.py", line 512, in create_default_context
    context.load_verify_locations(cafile, capath, cadata)
ssl.SSLError: unknown error (_ssl.c:3566)

おや?

どうも、 SDKMAN をインストールする前に「sudo add-apt-repository --remove ppa:webupd8team/java」で追加していた PPA を削除していたのですが、これがまずかったようです。

上記エラーでググったときに出てきた、「ca-certificates」はインストール済みでしたが、再インストールしたことでエラーがでなくなりました。

sudo apt install ca-certificates

PPA 追加と合わせて SDKMAN も問題なくインストールできるようになりました。

あ〜よかったよかった。

参照

GolangのSliceあれこれ

はじめに

突然ですが A Tour of Go 始めました。

https://tour.golang.org/list

とは言いつつも、さらっと読み流しつつ、へ~、ほ~と思っているだけではあるのですが。

その中で気になったこととして、 Slice があります。

ほかの言語にある機能とは少し違っているのか、 Slice について書かれた記事は結構あったりするのですが、
その辺も踏まえて、自分がわからなかったり気になった部分をまとめたいと思います。

Sliceについて

まずは Slice について簡単におさらい。

他の言語と同じように Golang にも配列があります。

Slice はこの配列に対する参照(ポインタ)を持つものです。

特徴として、 Slice はポインタと要素数 (Length) の他に容量 (Capacity) を持っており、
Capacity を超えない範囲であれば要素数を任意に変更できることが挙げられます。

Sliceを作る

Sliceを作る方法は三つあります。
(s1 は配列 originalArray に対する参照を持っていたりと中のデータはそれぞれ異なります)

package main

import "fmt"

func main() {
    // 元の配列
    originalArray := [5]int{0, 1, 2, 3, 4}

    // Sliceを作る1.
    s1 := originalArray[:]
    printSlice(s1)                      // Length: 5 Capacity: 5 Value: [0 1 2 3 4]

    // Sliceを作る1-2. [low:high]で配列の一部を持つSliceを作ることも可能.
    s1b := originalArray[2:5]
    printSlice(s1b)                     // Length: 3 Capacity: 3 Value: [2 3 4]

    // Sliceを作る2.
    s2 := []int{1, 2, 3, 4, 5}
    printSlice(s2)                      // Length: 5 Capacity: 5 Value: [1 2 3 4 5]

    // Sliceを作る3.
    s3 := make([]int, 5, 5)
    printSlice(s3)                      // Length: 5 Capacity: 5 Value: [0 0 0 0 0]
}
func printSlice(v []int){
    fmt.Printf("Length: %d Capacity: %d Value: %v \n", len(v), cap(v), v)
}
  • s2, s3 ではそれぞれ Slice とは別に、その参照元となる配列が作成されます。
    この配列は、自分に対する参照がすべて無くなったら破棄されます。

LengthとCapacity

さて、 Slice で最初に引っかかったのが A Tour of Go > More types: structs, slices, and maps. > Slice length and capacity のところです。

ここでは Slice を 配列[low:high] として生成し、そこから Slice を再生成しています。

import "fmt"

func main() {
    // 1. 配列からSliceを生成.
    s1 := []int{0, 1, 2, 3, 4}
    printSlice(s1)                      // Length: 5 Capacity: 5 Value: [0 1 2 3 4]

    // 2. Lengthを0にする
    s1 = s1[:0]
    printSlice(s1)                      // Length: 0 Capacity: 5 Value: []

    // 3. Lengthを4にする
    s1 = s1[:4]                         // Length: 4 Capacity: 5 Value: [0 1 2 3]
    printSlice(s1)

    // 4. Capacityが3になる
    s1 = s1[2:]
    printSlice(s1)                      // Length: 2 Capacity: 3 Value: [2 3]
}
~省略~

2.で Length を 0 にした後、 3. を実行すると Length が 4 になります。

ところが、 3.をコメントアウトして 4. を実行すると例外が発生します。

また、 high 側を設定している 2.、3. では Capacity が 5 のまま変化していないのに対し、
4.では 3 に変化しています。

ここから、 low 側の指定によって Capacity は変化するが Length は変化しないこと、
high 側は Length が変化するが Capacity が変化しないことがわかります。

なお後述しますが、 Capacity は減らすことはできても増やすことはできません。

copyについて

Slice を複製したい場合。

同じポインタを持ったSliceを複製する

前述のとおり、 Slice は配列に対するポインタを保持しています。

下記のように同じ配列から Slice を生成した場合、インデックスは違っても対応する値が変更されます。

~省略~
func main() {
    // 配列からSliceを生成.
    originalArray := [5]int{0, 1, 2, 3, 4}
    printArray(originalArray)           // Length: 5 Value: [0 1 2 3 4]

    // 元の配列とLength、Capacityが同じSlice
    s1 := originalArray[:]
    printSlice(s1)                      // Length: 5 Capacity: 5 Value: [0 1 2 3 4]

    // 配列の2~5番目でSliceを生成.
    s2 := originalArray[2:5]
    printSlice(s2)                      // Length: 3 Capacity: 3 Value: [2 3 4]

    // s1を複製(元の配列のポインタを持ったSliceをs1と同条件で生成)
    s3 := s1
    printSlice(s3)                      // Length: 5 Capacity: 5 Value: [0 1 2 3 4]

    // Sliceから値を変更すると元の配列の値が変更される.
    s1[2] = 22
    printArray(originalArray)           // Length: 5 Value: [0 1 22 3 4]

    // インデックスは違っても対応する値が変更される
    s2[0] = 33
    printArray(originalArray)           // Length: 5 Value: [0 1 33 3 4]
}
~省略~
func printArray(v [5]int){
    fmt.Printf("Length: %d Value: %v \n", len(v), v)
}

なお Slice を複製する場合も、 Slice そのものが複製されるわけではなく、
元の Slice と同じポインタ、 Length 、Capacity を持った Slice が生成されるようです。

内容は同じで別のポインタを持ったSliceを生成する

今度は値は同じ、でも別の配列に対するポインタを持つ Slice を作りたい場合。

コピー元と Length が同じ Slice を作成し、 copy を使って値をコピーします。

~省略~
func main() {
    // 配列からSliceを生成.
    originalArray := [5]int{0, 1, 2, 3, 4}
    printArray(originalArray)           // Length: 5 Value: [0 1 2 3 4]

    // 元の配列とLength、Capacityが同じSlice
    s1 := originalArray[:]
    printSlice(s1)                      // Length: 5 Capacity: 5 Value: [0 1 2 3 4]

    // s1 と同じLengthを持ったSliceを新規作成
    s2 := make([]int, len(s1))
    // 生成時点ではすべてデフォルト値
    printSlice(s2)                      // Length: 5 Capacity: 5 Value: [0 0 0 0 0]
    // s1の値をコピーする
    copy(s2, s1)                        // Length: 5 Capacity: 5 Value: [0 1 2 3 4]
    printSlice(s2)

    s1[0] = -1
    // s1の値を変更してもs2に影響はない
    printSlice(s2)                      // Length: 5 Capacity: 5 Value: [0 1 2 3 4]
}
~省略~

明示的に生成しなおさないと同じポインタが使われてしまう、というのは注意が必要そうです。

appendについて

Slice の Capacity は増やせないと言ったな。あれは嘘だ。

Slice に値を追加したい場合、 append を使うことができます。

~省略~
func main() {
    originalArray := [5]int{0, 1, 2, 3, 4}
    printArray(originalArray)           // Length: 5 Value: [0 1 2 3 4]

    s1 := originalArray[0:4]
    printSlice(s1)                      // Length: 5 Capacity: 5 Value: [0 1 2 3]

    // 値を追加する1:  追加後のLengthがCapacityを超えない場合
    s1 = append(s1, 6)
    printSlice(s1)                      // Length: 5 Capacity: 5 Value: [0 1 2 3 6]

    // 値を追加する2: 追加後のLengthがCapacityを超える場合
    s1 = append(s1, 7)
    printSlice(s1)                      // Length: 6 Capacity: 10 Value: [0 1 2 3 6 7]
}
~省略~

注目しどころとしては、 append で値を追加すると Slice の最後に値が追加され、
Length が +1 されますが Capacity はそのままです。

ところが、 Length と Capacity の数が同じ状態で append すると、
Capacity は append 実行前の二倍になっています。

これは、 Capacity が Length の数より小さくなる場合に自動で Slice が作り直されるのですが、
一つ追加するごとに作り直すのは効率が悪いため、ということのようです。

この append したときの Length と Capacity の数によって Slice が作り直される、
という仕様でもう一つ驚いたことがありました。

それは、 Slice が作り直されると、元の配列へのポインタが保持されない、ということです。

~省略~
func main() {
    originalArray := [5]int{0, 1, 2, 3, 4}
    printArray(originalArray)           // Length: 5 Value: [0 1 2 3 4]

    s1 := originalArray[0:4]
    printSlice(s1)                      // Length: 5 Capacity: 5 Value: [0 1 2 3]

    // 値を追加する1:  追加後のLengthがCapacityを超えない場合
    s1 = append(s1, 6)
    printArray(originalArray)   // Length: 5 Value: [0 1 2 3 6]
    printSlice(s1)                      // Length: 5 Capacity: 5 Value: [0 1 2 3 6]

    // 値を追加する2: 追加後のLengthがCapacityを超える場合
    s1 = append(s1, 7)
    printArray(originalArray)   // Length: 5 Value: [0 1 2 3 6]
    printSlice(s1)                      // Length: 6 Capacity: 10 Value: [0 1 2 3 6 7] 
}
~省略~

値を追加する2 の後、元の配列 (originalArray) には 7 が追加されていません。

そのため、メソッドに Slice を渡して値を変更 -> 戻す というようなことがしたい場合には注意が必要そうです。

まぁそんな方々で同じ配列を変更しようとするな、ということかもしれませんが。

おわりに

Slice は自動で Length や Capacity をうまく設定してくれるなど便利な面も多いですが、
どういう動きになるのかをちゃんと理解していないと容易にバグも引き起こしそう、ということで怖さもあるなぁ、と感じました。
(こなみかん)

参照