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 (Streams = 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
// 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# より範囲が広いように感じました。
- try-with-resources 文 - Oracle
AutoCloseable の既知のすべての実装クラス を見ていると、 try-with-resources が必要になる処理自体は C# と大きな違いがなさそうです。
ということで結論としては、使用目的としては大体同じ、ただし詳細や Close(Dispose) の動作には異なる点がある、というくらいでしょうか。
おわりに
同じような目的で作られ、同じように使われる機能であっても、詳細を調べてみると違っているところやこれまで気づかなかった特性を見つけることができたりして楽しいものですね。
一旦この話はここで締めますが、また特筆すべき違いなどがあればその3として書こうかなと思います。
参照
try-with-resources
AutoCloseable
Stream API
- Stream (Java Platform SE 8 ) - Oracle
- Java Streamメモ - Hishidama's Java8 Stream Memo
- StreamはAutoCloseableであると認識していないとアレな件 - mike-neckのブログ
IDisposable
- IDisposable インターフェイス (System) - msdn.aspx)
- IDisposable を実装するオブジェクトの使用 - Microsoft Docs
- Dispose メソッドの実装 - Microsoft Docs
- C# Language Specification 5.0 - Microsoft ダウンロード センター
- IEnumerator
を実装していれば必ず IDisposable である理由 - NyaRuRuが地球にいたころ
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なインスタンスの場合、 Java、C# ともに下記のように書くことができます (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"); } }
実行順序
- try (using) ブロック
- close() (Dispose()) メソッド
- 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
- The try-with-resources Statement (The Java™ Tutorials > Essential Classes > Exceptions)
- try-with-resources 文 - Oracle
- More concise try-with-resources statements in JDK 9 - Oracle Joseph D. Darcy's Oracle Blog
- try-with-resourcesでリソース解放されないパターン - Qiita
- Java文メモ - Hishidama's Java Statement Memo
- The C# using statement and suppressed exceptions compared to Java 7 try…with - Info Support Blog
- Java7 の try-with-resources で複数のリソースを扱うときの書き方が、C# の using と全然違う - 地平線に行く
- Java本格入門
using
【Ubuntu】Mavenのインストール → コンパイル
はじめに
タイトル通り、Ubuntu に Maven をインストールしてコンパイルしたときのお話。
後述する通りインストールは特に問題ありませんでしたが、プロジェクト作成→コンパイルでエラーが発生したのでメモっておくことにします。
インストール
インストール手順に従ってインストールしていきます。
まず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
ターミナルで「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つ問題が発生しました。
- 「curl -s "https://get.sdkman.io" | bash」と入力しても動作せず、「curl -s get.sdkman.io | bash」だと動作した
- インストール完了のメッセージが出ているのに、「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, inshortcut = 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 始めました。
とは言いつつも、さらっと読み流しつつ、へ~、ほ~と思っているだけではあるのですが。
その中で気になったこととして、 Slice があります。
ほかの言語にある機能とは少し違っているのか、 Slice について書かれた記事は結構あったりするのですが、
その辺も踏まえて、自分がわからなかったり気になった部分をまとめたいと思います。
Tour of Go,iOS版Firefoxでも普通に動いて凄い😀
— var 🍅 = new 増井将則(); (@masanori_msl) 2018年2月19日
それにしても、3D数学に挑戦してたはずが何でGoに手を出そうとしてるんだ僕は?
😇
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 をうまく設定してくれるなど便利な面も多いですが、
どういう動きになるのかをちゃんと理解していないと容易にバグも引き起こしそう、ということで怖さもあるなぁ、と感じました。
(こなみかん)
参照
【Unity】DOTweenで画像を点滅させるメモ
はじめに
必要になったのでメモ。
やりたいこと
- 何かの操作をトリガーに、一定時間で Image を Alpha 0 -> Image のデフォルトの Alpha 値に戻すアニメーションを実行
- 何かの操作をトリガーに、一定時間で Image の Scale を大きくするアニメーションを実行
今回は DOTween を使用しました。
http://dotween.demigiant.com/index.php
コード
using DG.Tweening; using UnityEngine; using UnityEngine.UI; public class BlinkController : MonoBehaviour { // 点滅対象. public Image Limited; // アニメーション開始のトリガー. public Button StartButton; private const float AnimeTimeSec = 1.0f; private const int AnimeRepeatCount = 3; private int count; private float defaultAlpha; private Vector3 defaultScale; private Vector3 largeScale; private void Start () { defaultAlpha = Limited.color.a; defaultScale = Limited.rectTransform.localScale; largeScale = new Vector3( defaultScale.x * 1.2f, defaultScale.y * 1.2f, defaultScale.z * 1.2f); StartButton.onClick.AddListener(() => { // 実行中のアニメーションをリセット. count = 0; DOTween.Clear(); // 点滅アニメーション. StartBlinking(); }); } private void StartBlinking() { var startColor = Limited.color; startColor.a = 0f; Limited.color = startColor; Limited.rectTransform.localScale = defaultScale; // 引数1: アニメーション完了後の Scale 引数2: アニメーションの実行時間. Limited.rectTransform.DOScale(largeScale, AnimeTimeSec) .SetEase(Ease.Linear); // 引数1: Alpha 値変更対象の Color(Getter) 引数2: Setter. // 引数3: アニメーション完了後の Alpha 引数4: アニメーションの実行時間. DOTween.ToAlpha(() => Limited.color, value => Limited.color = value, defaultAlpha, AnimeTimeSec) .SetEase(Ease.Linear) .OnComplete(() => { count += 1; if (count < AnimeRepeatCount) { // 一定回数繰り返し. StartBlinking(); } else { // 完了後の処理が不要なら Else ごと削除しても OK. Debug.Log("finished"); } }); } }
非常にシンプルにできてよいですね。
タイマーを追加する
ついでに、ボタンが押されたらタイマーを起動して、一定時間ごとにアニメーションを繰り返すようにしてみます。
using System.Diagnostics; using DG.Tweening; using UnityEngine; using UnityEngine.UI; using Debug = UnityEngine.Debug; public class BlinkController : MonoBehaviour { public Image Limited; public Button StartButton; // アニメーション、タイマーをリセット. public Button StopAllButton; private const long RepeatAnimeTimeMillisec = 5000L; private const float AnimeTimeSec = 1.0f; private const int AnimeRepeatCount = 3; private int count; private float defaultAlpha; private Vector3 defaultScale; private Vector3 largeScale; private bool timerSet; private readonly Stopwatch watch = new Stopwatch(); private void Start () { defaultAlpha = Limited.color.a; defaultScale = Limited.rectTransform.localScale; largeScale = new Vector3( defaultScale.x * 1.2f, defaultScale.y * 1.2f, defaultScale.z * 1.2f); StartButton.onClick.AddListener(Play); StopAllButton.onClick.AddListener(StopAll); } private void Update() { if (timerSet) { if (watch.ElapsedMilliseconds > RepeatAnimeTimeMillisec) { // 一定時間が経過したらアニメーション開始. Play(); } } } // タイマーの開始は最初のアニメーション実行後とする. private void Play() { timerSet = false; watch.Stop(); count = 0; DOTween.Clear(); StartBlinking(); } // アニメーション、タイマーをリセット. private void StopAll() { timerSet = false; watch.Stop(); count = 0; DOTween.Clear(); } private void StartBlinking() { var startColor = Limited.color; startColor.a = 0f; Limited.color = startColor; Limited.rectTransform.localScale = defaultScale; Limited.rectTransform.DOScale(largeScale, AnimeTimeSec) .SetEase(Ease.Linear); DOTween.ToAlpha(() => Limited.color, value => Limited.color = value, defaultAlpha, AnimeTimeSec) .SetEase(Ease.Linear) .OnComplete(() => { count += 1; if (count < AnimeRepeatCount) { StartBlinking(); } else { // アニメーションが終わったらタイマー起動. timerSet = true; watch.Reset(); watch.Start(); } }); } }
参照
Xubuntu17.10 に OpenCV3.2.0を入れようとしたときのメモ
はじめに
え~、諸事情とちょっとした興味から、 Xubuntu に OpenCV をインストールしてみることにしました。
なんかはるか昔に同じようなことをやった気がするのですが、 やってみるとバージョンの違いからかエラー出しまくりといった感じであったので、メモっておきます。
なお 2/6 時点では CMake 、 make が完了した(っぽい)というだけで、サンプルを動かしたりはできていない状態です。
また後述しますが、ググったときに見つかった情報からインストールしたものが含まれており、 今回インストールしたものすべてが必要かどうかは不明です。
この辺りは、サンプルのビルドやインストールのし直しなどを行う予定なので、
間違いなどを見つけたら順次修正していく予定です。
ということで懺悔おしまい。
インストールしたソフトウェア
とりあえずテキストエディタをインストールします。
Vim とか Visual Studio Code とか。
CMakeのインストール
まずは CMake をインストールすることにします。
- CMake のソース(cmake-3.10.2.tar.gz)を https://cmake.org/download/ からダウンロードして任意の場所に展開します。
- ビルドに必要なソフトウェアをインストールします。
sudo apt install gcc g++ build-essential git libboost1.63 make libeigen3-dev
- ターミナルで Step1. のフォルダに移動して、ビルドします。
./bootstrap && make && sudo make install
OpenCVのビルドに必要そうなファイルをインストール
以前の記事などを参考に、OpenCV のビルドに必要そうなファイルをインストールします。
ただしバージョンの違いのためか、見つからなかったものは除外しています。
この内容はもう少しちゃんと確認が必要ですね(;'∀')。
sudo apt install ffmpeg libopencv-dev libgtk-3-dev python-numpy python3-numpy libdc1394-22 libdc1394-22-dev libjpeg-dev libtiff5-dev libavcodec-dev libavformat-dev libswscale-dev libxine2-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libv4l-dev libtbb-dev qtbase5-dev libfaac-dev libmp3lame-dev libopencore-amrnb-dev libopencore-amrwb-dev libtheora-dev libvorbis-dev libxvidcore-dev x264 v4l-utils unzip
何はともあれ、OpenCV 3.2.0 のソースをダウンロードして、任意の場所に展開します。
https://github.com/opencv/opencv/releases
ググって得た情報によると、展開したフォルダ内に build というフォルダを作成し、その中で cmake . を実行しているものがありました。
実際に実行してみると、 build フォルダ内に依存ファイルなどが複製されていたため、 元のファイルをそのまま置いておくためなのかなぁ、と勝手に思っています。
cmake ../
VTKのインストール
で、 cmake を実行してみると VTK が見つからないとエラーになりました。
そのため、 https://www.vtk.org/download/ から VTK(8.1.0)をダウンロードして展開し、 cmake . を実行したところ、エラーになりました。
どうも必要なソフトウェアが足りていなかったようです。
sudo apt install libxt-dev
【参考】 https://stackoverflow.com/questions/23528248/how-to-install-x11-xt-lib-when-configure-vtk
これでビルドはできたのですが、古い EeePC で実行したためか、完了まで 3, 4 時間はかかりました。。。
ICV
今度こそ!と思うもまたエラーが発生しました。
今度は ippicv_linux_20151201.tgz のダウンロード?に失敗しているようです。
ググってみたところ、下記のような情報が見つかりました。
CMake から https に接続できてない。。。?
対処法としては、 OpenCV のフォルダ内にある、 3rdParty > ippicv フォルダの中身を手動でダウンロードしてきた ippicv_linux_20151201.tgz に差し替える、 というものがあるようです。
一応 CMake はこれで完了し、 make を実行すると warning が途中途中で出はしたものの完了できたようです。
色々雑すぎるのでもう少し確認も必要なのですが、まずは本当に正しくインストールできているかの確認からかなぁ。