- 投稿日:2019-10-02T23:58:19+09:00
【Java8】拡張for文、forEach文のインデックスをimmutableにする
普通に宣言した場合
拡張for文、forEach文を普通に宣言すると以下のようになります。
final
宣言していないため、再代入可能です(for
文は原理的に再代入が必須なので飛ばします)。List<Integer> intList = Arrays.asList(1, 2, 3, 4); for(int i : intList) { i = 0; // 書き換えられる } intList.forEach(it -> { it = 0; // 書き換えられる });この記事では、これらの内容を再代入不能な形に書き換えます。
インデックスをimmutableにする
拡張for文の場合
final
で宣言することでimmutable
にできます。for(final int i : intList) { i = 0; // final宣言しているため再代入不可でコンパイルエラー }forEach文の場合
以下のように、カッコで括って型を指定することで引数を
final
にできます。intList.forEach((final int it) -> it = 0; // final宣言しているため再代入不可でコンパイルエラー );別解
一応ですが、無名クラスとして実装する場合も引数を
final
にできます。Consumer<Integer> finalConsumer = new Consumer<Integer>() { @Override public void accept(final Integer it) { it = 0; // final宣言しているため再代入不可でコンパイルエラー } };お詫び
この記事は当初公開した際「インデックスをimmutableにできるのはfor文、拡張for文、forEach文の内拡張for文だけ」というタイトル・内容でしたが、@saka1029 様のご指摘の通り、これは誤りでしたので、タイトル・内容共に全面的に書き直しを行いました。
@saka1029 様、ご指摘ありがとうございました。
- 投稿日:2019-10-02T23:58:19+09:00
【Java8】インデックスをimmutableにできるのはfor文、拡張for文、forEach文の内拡張for文だけ
内容は(補足の補足に書いた通りちょっと語弊が有りますが)タイトルの通りです。
具体的には以下のようになります。List<Integer> intList = Arrays.asList(1, 2, 3, 4); // final int iで宣言するとi++がエラーになる for (int i = 0; i < intList.size(); i++) { i = 0; // 当然書き換えられる } for(final int i : intList) { i = 1; // final宣言できるので再代入不可 } intList.forEach(it -> { it = 0; // 書き換えられる }); intList.stream().forEach(it -> { it = 0; // 当然streamでも書き換えられる }); // こう書くとコンパイルエラー intList.forEach(final Integer it -> { it = 0; });補足
forEach
文でインデックスが書き換えられるのは、これが引数に取る関数型インターフェースConsumer
の引数がfinal
で定義されていない(というか多分できない)からです。例えばインターフェースで引数を
final
にしようと思っても以下のようになります。関数型インターフェース@FunctionalInterface public interface FinalConsumer<T> { void accept(final T t); }実際に使うと上書きできてしまうFinalConsumer<Integer> finalConsumer = it -> it = 0; finalConsumer.accept(1); // 実行時エラーにもならない補足の補足
一応ですが、無名クラスとして実装する場合は引数を
final
にできます。Consumer<Integer> finalConsumer = new Consumer<Integer>() { @Override public void accept(final Integer it) { it = 0; // finalで引数を宣言しているため、これはコンパイルエラー } };
- 投稿日:2019-10-02T22:14:36+09:00
メイン・クラスが存在するにもかかわらず「エラー: メイン・クラスXXXが見つからなかったかロードできませんでした」が発生する
事象
まず以下のような
Child
クラスが存在するとします。Child.javapublic class Child extends Parent { public static void main(String[] args) { System.out.println("This class is `Child`"); } }これを
child.jar
に格納し、Java8で実行した結果が次の通りになります。C:\>java -version java version "1.8.0_202" Java(TM) SE Runtime Environment (build 1.8.0_202-b08) Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode) C:\>java -cp child.jar Child エラー: メイン・クラスChildが見つからなかったかロードできませんでした
Child
クラスは確かに存在するにも関わらず「メイン・クラスChildが見つからなかったかロードできませんでした」というエラーによりJavaの実行が失敗しました。原因
真因は
Child
の親クラスParent
が読み込めていないためになります。実はParent
はchild.jar
とは別のparent.jar
に格納されています。しかし上記のjava
コマンド実行方法では、parent.jar
が読み込めていません。そこで
parent.jar
もクラスパスに加えて、java
を実行してやると、想定通り動作しました。C:\>java -version java version "1.8.0_202" Java(TM) SE Runtime Environment (build 1.8.0_202-b08) Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode) C:\>java -cp child.jar;parent.jar Child This class is `Child`「Childが見つからない」という部分だけを見ると、
Child
が正しくchild.jar
に格納できていないように思えますし、実際筆者はこの思いこみのせいでずいぶんと時間を無駄にしました……。エラーメッセージが分かりにくいのが悪いんや(逆ギレ)
- 投稿日:2019-10-02T20:37:13+09:00
Java 単体テスト結果報告書の自動作成
目次 ⇒ Java単体テストライブラリ-Artery-サンプル
Javaの単体テストライブラリArteryでは、単体テストの結果をタブ区切りで出力することにより、単体テスト結果報告書とすることができます。
package jp.avaj.lib.test; /** Java 単体テスト結果報告書の自動作成 単体テストの結果をエクセルで読込むと、テスト結果報告書の形式にすることができる。 この形式でよければ、テスト結果報告書の作成の手間を省くことができる。 */ public class Q06_00 { public static void main(String[] args) { // 単体テスト結果報告書の作成 // この出力をエクセルで読込むとテスト結果報告書となる { // エクセル出力の指定 ArTest.testOutLevel = ArTestOutLevel.EXCEL; // テストの名称⇒先頭に表示される ArTestExcelEnv.systemName = "QIITAシステム単体テスト結果報告書"; // テスト実施者⇒指定しなければテスト実施者は表示されない ArTestExcelEnv.testerName = "闇雲"; // テスト実施日⇒指定しなければ yy/mm/dd となる ArTestExcelEnv.testDate = "12/31"; // テスト番号の開始⇒指定しなければ 0 となる ArTestExcelEnv.startTestNo = 1; } // ログ出力ファイルの指定 ArTest.startUnitTest("unittest","c:/tmp"); ArTest.startTestCase("うるう年判定"); boolean result; // 2020年はうるう年 ⇒ resultはtrueが正しい result = LeapYear.isLeapYear(2020); // 結果をチェックする. ArTest.isTrue("2020年","result",result); // 2100年は平年 ⇒ resultはfalseが正しい result = LeapYear.isLeapYear(2100); // 結果をチェックする. ArTest.isFalse("2100年","result",result); // テストケースを終了する ArTest.endTestCase(); ArTest.startTestCase("キム・ベイシンガーの役名⇒映画名の変換"); // ヴィッキー・ヴェイル ⇒ 正解はバットマン String movie = Kim.getMovieTitle("ヴィッキー・ヴェイル"); // 結果をチェックする ArTest.equals("ヴィッキー・ヴェイル","expected","バットマン","movie",movie); // キャロル・マッコイ ⇒ 正解はゲッタウェイ movie = Kim.getMovieTitle("キャロル・マッコイ"); // 結果をチェックする ArTest.equals("キャロル・マッコイ","expected","ゲッタウェイ","movie",movie); // テストケースを終了する ArTest.endTestCase(); // すべてのテストケースの集計の表示 ArTest.reportTotalSummary(); } //////////// 以下はテスト対象のクラス /** うるう年判定クラス(もしかしたらバグがあるかも). */ static class LeapYear { public static boolean isLeapYear(int year) { return ((year % 4) == 0); } } /** キム・ベイシンガーの役名⇒映画名の変換クラス(もしかしたらバグがあるかも). */ static class Kim { public static String getMovieTitle(String name) { if ("ヴィッキー・ヴェイル".equals(name)) { return "バットマン"; } if ("キャロル・マッコイ".equals(name)) { return "ブロンディ"; } // その他は省略 return null; } } }エクセルの出力指定をしない場合の結果は次のとおり
result.txt**** うるう年判定 start **** OK 2020年:result=true NG 2100年:result=true jp.avaj.lib.test.Q06_00.main(Q06_00.java:44) **** うるう年判定 summary **** test count = 2 success = 1 **** キム・ベイシンガーの役名⇒映画名の変換 start **** OK ヴィッキー・ヴェイル:expected=バットマン:movie=バットマン NG キャロル・マッコイ:expected=ゲッタウェイ:movie=ブロンディ jp.avaj.lib.test.Q06_00.main(Q06_00.java:60) **** キム・ベイシンガーの役名⇒映画名の変換 summary **** test count = 2 success = 1 **** total **** total test count = 4 total success = 2エクセルの出力指定をした場合の結果は次のとおり
result.txtQIITAシステム単体テスト結果報告書 **** うるう年判定 start **** TestNo 日付 担当 結果 テスト内容 期待値 実際値 001-001 12/31 闇雲 OK 2020年 TRUE true 001-002 12/31 闇雲 NG 2100年 FALSE true **** うるう年判定 summary **** test count = 2 success = 1 **** キム・ベイシンガーの役名⇒映画名の変換 start **** TestNo 日付 担当 結果 テスト内容 期待値 実際値 002-001 12/31 闇雲 OK ヴィッキー・ヴェイル バットマン バットマン 002-002 12/31 闇雲 NG キャロル・マッコイ ゲッタウェイ ブロンディ **** キム・ベイシンガーの役名⇒映画名の変換 summary **** test count = 2 success = 1 **** total **** total test count = 4 total success = 2エクセルで読込むと次のようになる。
- 投稿日:2019-10-02T19:45:20+09:00
Java 8 (OpenJDK: Zulu Community) を Homebrew で macOS にインストールする
概要
- macOS に Homebrew で Java 8 (OpenJDK をビルドした Zulu Community) をインストールする方法を示す
Zulu Community とは
Zulu Community は Azul Systems が配布している OpenJDK ディストリビューション。
Zulu Community Java OpenJDK無料ダウンロード TCKテスト済
Azulは、2023年7月までOpenJDK7のZulu Communityビルドのアップデートを毎四半期に提供し、OpenJDK 8のビルドは2026年3月までアップデートされ、OpenJDK 11のビルドは2027年9月までアップデートされます。Java 9、10、12、15、16のような中間リリースは、最初のリリースから少なくとも6か月はアップデートされます。
Azulはまた、2014年以降のOpenJDK Zulu Communityビルドのアーカイブを保持します。Zulu Community 8 は2026年3月までサポートされるとのこと。
Zulu Community 8 をインストールする
Zulu Community 8 は homebrew/cask-versions にあるのでこれを使用してインストールする。
$ brew cask install zulu8java_home コマンドでインストールされたディレクトリの場所を確認する
$ /usr/libexec/java_home -v 1.8 /Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home環境変数 JAVA_HOME と PATH を設定する
必要に応じて .bash_profile や .bashrc などに記述する。
$ export JAVA_HOME=`/usr/libexec/java_home -v 1.8` $ PATH=${JAVA_HOME}/bin:${PATH}インストールされた Zulu Community を確認する
$ java -version openjdk version "1.8.0_222" OpenJDK Runtime Environment (Zulu 8.40.0.25-CA-macosx) (build 1.8.0_222-b10) OpenJDK 64-Bit Server VM (Zulu 8.40.0.25-CA-macosx) (build 25.222-b10, mixed mode) $ javac -version javac 1.8.0_222 $ which java /Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/bin/java $ which javac /Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/bin/javac不要になったらアンインストールする
$ brew cask uninstall zulu8参考資料
- 投稿日:2019-10-02T19:32:08+09:00
Java 8 (OpenJDK: Amazon Corretto) を Homebrew で macOS にインストールする
概要
- macOS に Homebrew で Java 8 (OpenJDK をビルドした Amazon Corretto) をインストールする方法を示す
Amazon Corretto とは
Amazon Corretto は Amazon が配布している OpenJDK ディストリビューション。
Amazon Corretto(本番環境に対応したOpenJDKディストリビューション)| AWS
Amazon Corretto は、マルチプラットフォームで本番環境に対応した、無料の Open Java Development Kit (OpenJDK) ディストリビューションです。Corretto は、パフォーマンスの向上とセキュリティの修正などの長期サポートを用意しています。Amazon は、社内において何千もの本番サービスで Corretto を実行しており、Corretto は Java SE 標準と互換性があると認定されています。Corretto を使用することで、Linux、Windows、macOS などの一般的なオペレーティングシステムで Java アプリケーションを開発し、実行できます。
Amazon Corretto 8 は少なくとも2023年6月まではサポートされるとのこと。
よくある質問 - Amazon Corretto | AWS
Amazon Corretto は、マルチプラットフォームで本番環境に対応した、長期サポート (LTS) を含む無料の Open Java Development Kit (OpenJDK) ディストリビューションです。LTS には、最短でも関連するリリースバージョンの指定日 (例: Corretto 8 の場合は 2023 年 6 月) までは、パフォーマンスの向上とセキュリティの更新を無償で提供するという Amazon のコミットメントが含まれています。アップデートは四半期ごとにリリースされる予定です。Amazon では、通常の四半期サイクルの範囲外で、利用可能になった際に緊急で対応できる修正 (セキュリティを含む) を適用することも計画しています。
Amazon Corretto 8 をインストールする
Amazon Corretto 8 は homebrew/cask-versions にあるのでこれを使用してインストールする。
$ brew tap homebrew/cask-versions $ brew cask install corretto8java_home コマンドでインストールされたディレクトリの場所を確認する
$ /usr/libexec/java_home -v 1.8 /Library/Java/JavaVirtualMachines/amazon-corretto-8.jdk/Contents/Home環境変数 JAVA_HOME と PATH を設定する
必要に応じて .bash_profile や .bashrc などに記述する。
export JAVA_HOME=`/usr/libexec/java_home -v 1.8` PATH=${JAVA_HOME}/bin:${PATH}インストールされた Amazon Corretto を確認する
$ java -version openjdk version "1.8.0_222" OpenJDK Runtime Environment Corretto-8.222.10.1 (build 1.8.0_222-b10) OpenJDK 64-Bit Server VM Corretto-8.222.10.1 (build 25.222-b10, mixed mode) $ javac -version javac 1.8.0_222 $ which java /Library/Java/JavaVirtualMachines/amazon-corretto-8.jdk/Contents/Home/bin/java $ which javac /Library/Java/JavaVirtualMachines/amazon-corretto-8.jdk/Contents/Home/bin/javac不要になったらアンインストールする
$ brew cask uninstall corretto8参考資料
- 投稿日:2019-10-02T19:20:14+09:00
Java 8 (OpenJDK: AdoptOpenJDK) を Homebrew で macOS にインストールする
概要
- macOS に Homebrew で Java 8 (OpenJDK をビルドした AdoptOpenJDK) をインストールする方法を示す
AdoptOpenJDK とは
AdoptOpenJDK は Amazon, Azul Systems, IBM, Microsoft, Pivotal, Red Hat などがスポンサーになっていて配布されている OpenJDK ディストリビューション。
AdoptOpenJDK - Open source, prebuilt OpenJDK binaries
Java™ is the world's leading programming language and platform. AdoptOpenJDK uses infrastructure, build and test scripts to produce prebuilt binaries from OpenJDK™ class libraries and a choice of either the OpenJDK HotSpot or Eclipse OpenJ9 VM.
AdoptOpenJDK 8 は少なくとも2023年9月まではサポートされるとのこと。
Support | AdoptOpenJDK - Open source, prebuilt OpenJDK binaries
At Least Sep 2023
AdoptOpenJDK 8 をインストールする
インストール方法が公式リポジトリの README に載っている。
AdoptOpenJDK/homebrew-openjdk: AdoptOpenJDK HomeBrew Tap
brew tap AdoptOpenJDK/openjdk brew cask install <version>README には AdoptOpenJDK/openjdk を tap して追加するように書いてあるが、 AdoptOpenJDK は homebrew/cask-versions にも存在する。
今回は homebrew/cask-versions を使用してインストールする。
$ brew tap homebrew/cask-versions $ brew cask install adoptopenjdk8java_home コマンドでインストールされたディレクトリの場所を確認する
$ /usr/libexec/java_home -v 1.8 /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home環境変数 JAVA_HOME と PATH を設定する
必要に応じて .bash_profile や .bashrc などに記述する。
export JAVA_HOME=`/usr/libexec/java_home -v 1.8` PATH=${JAVA_HOME}/bin:${PATH}インストールされた AdoptOpenJDK を確認する
$ java -version openjdk version "1.8.0_222" OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_222-b10) OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.222-b10, mixed mode) $ javac -version javac 1.8.0_222 $ which java /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/bin/java $ which javac /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/bin/javac不要になったらアンインストールする
$ brew cask uninstall adoptopenjdk8参考資料
- 投稿日:2019-10-02T14:24:26+09:00
デザインパターン:(23分の1)
過去に教えて頂いたデザインパターンの一つ
テンプレートメソッドパターンをアウトプット
(間違ってるかも)デザインパターン:TemplateMethod
を参考にJAVAをC#に変えて記述にしております。Class1.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Class1 { public abstract class AbstractDisplay { public abstract void Open(); public abstract void Print(); public abstract void Close(); public void Display() { Open(); for (int i = 0; i < 3; i++) { Print(); } Close(); } } public class CharDisplay : AbstractDisplay { char ch; public CharDisplay(char ch) { this.ch = ch; } public override void Open() { Console.Write("***"); } public override void Print() { Console.Write(ch); } public override void Close() { Console.WriteLine("***"); } } public class StringDisplay : AbstractDisplay { private string str; private int width; public StringDisplay(string str) { this.str = str; this.width = str.Length; } void PrintLine() { Console.Write("+"); for (int i = 0; i < width; i++) { Console.Write("-"); } Console.WriteLine("+"); } public override void Open() { PrintLine(); } public override void Print() { Console.WriteLine("|" + str + "|"); } public override void Close() { PrintLine(); } } }Program.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp1 { class Program { static void Main(string[] args) { AbstractDisplay cd = new CharDisplay('T'); cd.Display(); AbstractDisplay sd = new StringDisplay("Design Pattern"); sd.Display(); } } }23分の1個目
- 投稿日:2019-10-02T14:24:26+09:00
デザインパターンC#:TemplateMethod
過去に教えて頂いたデザインパターンの一つ
テンプレートメソッドパターンをアウトプット
(間違ってるかも)デザインパターン:TemplateMethod
を参考にJAVAをC#に変えて記述にしております。Class1.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Class1 { public abstract class AbstractDisplay { public abstract void Open(); public abstract void Print(); public abstract void Close(); public void Display() { Open(); for (int i = 0; i < 3; i++) { Print(); } Close(); } } public class CharDisplay : AbstractDisplay { char ch; public CharDisplay(char ch) { this.ch = ch; } public override void Open() { Console.Write("***"); } public override void Print() { Console.Write(ch); } public override void Close() { Console.WriteLine("***"); } } public class StringDisplay : AbstractDisplay { private string str; private int width; public StringDisplay(string str) { this.str = str; this.width = str.Length; } void PrintLine() { Console.Write("+"); for (int i = 0; i < width; i++) { Console.Write("-"); } Console.WriteLine("+"); } public override void Open() { PrintLine(); } public override void Print() { Console.WriteLine("|" + str + "|"); } public override void Close() { PrintLine(); } } }Program.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp1 { class Program { static void Main(string[] args) { AbstractDisplay cd = new CharDisplay('T'); cd.Display(); AbstractDisplay sd = new StringDisplay("Design Pattern"); sd.Display(); } } }23/1個目
- 投稿日:2019-10-02T14:24:26+09:00
デザインパターン:(23/1)
過去に教えて頂いたデザインパターンの一つ
テンプレートメソッドパターンをアウトプット
(間違ってるかも)デザインパターン:TemplateMethod
を参考にJAVAをC#に変えて記述にしております。Class1.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Class1 { public abstract class AbstractDisplay { public abstract void Open(); public abstract void Print(); public abstract void Close(); public void Display() { Open(); for (int i = 0; i < 3; i++) { Print(); } Close(); } } public class CharDisplay : AbstractDisplay { char ch; public CharDisplay(char ch) { this.ch = ch; } public override void Open() { Console.Write("***"); } public override void Print() { Console.Write(ch); } public override void Close() { Console.WriteLine("***"); } } public class StringDisplay : AbstractDisplay { private string str; private int width; public StringDisplay(string str) { this.str = str; this.width = str.Length; } void PrintLine() { Console.Write("+"); for (int i = 0; i < width; i++) { Console.Write("-"); } Console.WriteLine("+"); } public override void Open() { PrintLine(); } public override void Print() { Console.WriteLine("|" + str + "|"); } public override void Close() { PrintLine(); } } }Program.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp1 { class Program { static void Main(string[] args) { AbstractDisplay cd = new CharDisplay('T'); cd.Display(); AbstractDisplay sd = new StringDisplay("Design Pattern"); sd.Display(); } } }23/1個目
- 投稿日:2019-10-02T14:14:50+09:00
今更だけどぬるぽ
ぬるぽってなんですぞい
Javaと付き合うのであれば一生ついてくるであろうNullPointerExceptionという例外で
海外ではNPE等と略されてる
参照型で参照する値がないときに参照しようとするとおこるやつぬるぽがわかれば参照型がわかる
参照型
参照型とは変数の内部に、実際の値が入っているメモリのアドレスを持っている型で、
例として
String x = "あ"
と定義したとすると、このときにxに実際に格納されているものは "あ" のメモリ上のアドレスとなっている。
つまり
x = 0001
0001 = "あ”
みたいな感じになっている。住所不定無職
さて、いよいよぬるぽはどうしておこるのかですが
nullという状態はさっきの変数xのアドレスが未定義の状態のことで
x = null
とすると
x = 未定義
0001 = "あ"
みたいにメモリにデータがあってもなくても実際のデータがある場所がわからない状態に、
このときに x を使おうとすると 「ふえぇ、住所わっかんないよぉ〜><」 と言う感じでぬるぽになります。
つまりNullPointerExceptionは住所不定無職の人に会おうとしても住所がなくて会えないって感じです。
- 投稿日:2019-10-02T10:00:51+09:00
Effective Java 項目25 具象化不可能型(nonreifiable type)について
E、
List<E>
、そして、List<String>
は、技術的には具象化不可能型(nonreifiable type)として知られています。
直感的に言えば、具象化不可能型は、その実行時の表現がコンパイル時の表現よりも情報が少ない型です。
具象化可能な唯一のパラメータ化された型は、List<?>
やMap<?, ?>
などの非境界ワイルドカード型です。非境界ワイルドカード型の配列の生成は許されていますが、稀にしか有用ではありません。
ジェネリック配列生成の禁止は、煩わしいかもしれません。例えば、一般にジェネリック型は、その要素型の配列を返すことが不可能です。
可変長引数のメソッドをジェネリック型と組み合わせて使用する場合、困惑する警告が出る可能性も意味しています。それは、可変長引数のメソッドを呼び出すと、必ず可変長パラメータを保持するために配列が生成されるからです。
(下記のコードでいうと、argsがそれに該当する)static int sum(int... args) { int sum = 0; for (int arg : args) { sum += arg; } return sum; }この配列の要素型が具象化可能でなければ、警告が出ます。その警告を抑制することとAPIでジェネリックスと可変長引数を混在させないようにすること以外に、警告に対してできることはほとんどありません。
ジェネリック配列生成エラーとなる場合には、最善の解決策は、配列型
E[]
よりはコレクション型List<E>
を大抵は使用することです。
何らかのパフォーマンスや簡潔さを犠牲にするかもしれませんが、代わりに、より良い型安全性と相互運用性を得ます。
- 投稿日:2019-10-02T10:00:51+09:00
Effective Java 項目25 配列よりリストを選ぶ 前半
配列よりリストを選ぶ
配列とジェネリック型の違いとして、
- 配列は共変であり、ジェネリック型は不変である
- 配列は具象化(実行時にその要素型を知っていて、強制すること)だが、ジェネリックスはイレイジャ(コンパイル時にのみ型制約を強制し、実行時に型情報を廃棄すること)
E、
List<E>
、そして、List<String>
は、技術的には具象化不可能型(nonreifiable type)として知られています。
直感的に言えば、具象化不可能型は、その実行時の表現がコンパイル時の表現よりも情報が少ない型です。
具象化可能な唯一のパラメータ化された型は、List<?>
やMap<?, ?>
などの非境界ワイルドカード型です。非境界ワイルドカード型の配列の生成は許されていますが、稀にしか有用ではありません。
ジェネリック配列生成の禁止は、煩わしいかもしれません。例えば、一般にジェネリック型は、その要素型の配列を返すことが不可能です。
可変長引数のメソッドをジェネリック型と組み合わせて使用する場合、困惑する警告が出る可能性も意味しています。それは、可変長引数のメソッドを呼び出すと、必ず可変長パラメータを保持するために配列が生成されるからです。
(下記のコードでいうと、argsがそれに該当する)static int sum(int... args) { int sum = 0; for (int arg : args) { sum += arg; } return sum; }この配列の要素型が具象化可能でなければ、警告が出ます。その警告を抑制することとAPIでジェネリックスと可変長引数を混在させないようにすること以外に、警告に対してできることはほとんどありません。
ジェネリック配列生成エラーとなる場合には、最善の解決策は、配列型
E[]
よりはコレクション型List<E>
を大抵は使用することです。
何らかのパフォーマンスや簡潔さを犠牲にするかもしれませんが、代わりに、より良い型安全性と相互運用性を得ます。
- 投稿日:2019-10-02T09:46:39+09:00
AtCoder Programming Guide for beginners (APG4b)のEX25をjavaで書いてみた。
はじめに
この記事は、AtCoder内コンテンツの
AtCoder Programming Guide for beginners (APG4b)
で掲載されている課題EX25 - 集合の操作をJavaで記述してみたので、備忘録としてコードを公開するものです。ご一読いただけたら幸いです。
読みづらい点、間違い、感想等ありましたらコメントにてお伝えいただけると嬉しいです。
尚、bit演算子を用いよう!という気概無く、整数値を直接扱っています...(C++でいうところのbitSetが実装されていればよかったのですが。ありますかね…?)
コード
Main.javaimport java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Scanner; import java.util.Set; import java.util.TreeSet; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int N = sc.nextInt(); Integer[] A = new Integer[N]; for(int i = 0;i < N;i++) { A[i] = sc.nextInt(); } int M = sc.nextInt(); Integer[] B = new Integer[M]; for(int i = 0;i < M;i++) { B[i] = sc.nextInt(); } String command = sc.next(); int x = 0; if(command.equals("subtract")) { x = sc.nextInt(); } sc.close(); switch(command) { case "intersection": printResult(intersection(A, B)); break; case "union_set": printResult(unionset(A, B)); break; case "symmetric_diff": printResult(symmetricdiff(A, B)); break; case "subtract": printResult(subtract(A, x)); break; case "increment": printResult(increment(A)); break; case "decrement": printResult(decrement(A)); break; default: break; } } /** * AとBに共通して含まれる要素からなる集合を返す * @param A * @param B * @return 共通要素の配列 */ private static Integer[] intersection(Integer[] A,Integer[] B) { List<Integer> Alist = Arrays.asList(A); List<Integer> Blist = Arrays.asList(B); List<Integer> result = new ArrayList<>(); for(Integer i : Alist) { if(Blist.contains(i)) { result.add(i); } } if(result.size()==0) return null; Collections.sort(result); return result.toArray(new Integer[result.size()]); } /** * AとBのうち少なくとも一方に含まれる要素からなる集合を返す * @param A * @param B * @return 少なくとも一方に含まれる要素の配列 */ private static Integer[] unionset(Integer[] A,Integer[] B) { Set<Integer> result = new TreeSet<>(Comparator.naturalOrder()); for(Integer i : A) result.add(i); for(Integer i : B) result.add(i); return result.size()==0 ? null : result.toArray(new Integer[result.size()]); } /** * AとBのうちどちらか一方にだけ含まれる要素からなる集合を返す * @param A * @param B * @return 一方にのみ含まれる要素の配列 */ private static Integer[] symmetricdiff(Integer[] A,Integer[] B) { List<Integer> intersection = Arrays.asList(intersection(A, B)); List<Integer> union = Arrays.asList(unionset(A, B)); List<Integer> result = new ArrayList<>(); for(Integer i : union) { if(union.contains(i) && !intersection.contains(i)) { result.add(i); } } if(result.size()==0) return null; Collections.sort(result); return result.toArray(new Integer[result.size()]); } /** * 集合Aから値xを除く * @param A * @param x * @return */ private static Integer[] subtract(Integer[] A, int x) { List<Integer> list = Arrays.asList(A); List<Integer> result = new ArrayList<>(); for(Integer i : list) { if(i != x) { result.add(i); } } if(result.size()==0) return null; Collections.sort(result); return result.toArray(new Integer[result.size()]); } /** * Aの要素すべてに1を加える。 */ private static Integer[] increment(Integer[] A) { List<Integer> result = new ArrayList<>(); for(Integer i : A) { i++; if(i.equals(50)) { i = 0; } result.add(i); } if(result.size()==0) return null; Collections.sort(result); return result.toArray(new Integer[result.size()]); } /** * Aの要素すべてから1を引く。 */ private static Integer[] decrement(Integer[] A) { List<Integer> result = new ArrayList<>(); for(Integer i : A) { i--; if(i.equals(-1)) { i = 49; } result.add(i); } if(result.size()==0) return null; Collections.sort(result); return result.toArray(new Integer[result.size()]); } /** * 結果出力 */ private static void printResult(Integer[] result) { if(result==null) { System.out.println(""); return; } if(result.length > 1) { for(int i = 0;i < result.length-1;i++) { System.out.print(result[i] + " "); } } System.out.println(result[result.length-1]); } }