Java 11 Silver 受験対策 & 今後の備忘録
型
Javaのデータ型にはプリミティブ型と参照型がある。
参照型には更にクラス型、配列型、列挙型などの分類がある。
- プリミティブ型
- boolean
- char
- byte
- short
- int
- long
- float
- double
- 参照型
- クラス型
- インターフェース型
- 配列型
- 列挙型
プリミティブ型
型 | サイズ | 説明 | 値 |
---|---|---|---|
boolean |
仕様無 | 真偽値 |
true , false
|
char |
16bit | Unicode文字 |
¥u0000 ~ ¥uFFFF
|
byte |
8bit | 整数 |
-128 ~ 127
|
short |
16bit | 整数 |
-32768 ~ 32767
|
int |
32bit | 整数 |
-2137383648 ~ 2147483647
|
long |
64bit | 整数 |
-9223372036854775808 ~ 9223372036854775807
|
float |
32bit | 単精度浮動小数点数 |
±3.40282347E+38 ~ 1.40239846E-45
|
double |
64bit | 倍精度浮動小数点数 |
±1.79769313486231570E+308 ~ ±4.94065645841246544E-324
|
String
型はプリミティブ型ではなく参照型。
参照型
クラス型、配列型、列挙型がある。
リテラル
整数リテラル
デフォルトではint
型として扱われる。
整数リテラルをlong
型として扱いたい場合、末尾にl
もしくはL
の接尾辞をつける。
long a = 100L;
また整数リテラルをshort
型やbyte
型として扱うための接尾辞は存在しない。その代わりにint
型とshort
型などは自動変換される。
// 10はint型として扱われる整数リテラルだが、short型に代入できる(short型に自動変換される)
short i = 10;
10進数、2進数、8進数、16進数
デフォルトでは10進数として扱われる。
2進数で表したい場合、0b
を接尾辞としてつける。
byte ichi = 0b0001; // 1
short ni = 0b0010; // 2
int san = 0b00011; // 3
System.out.println(ichi);
System.out.println(ni);
System.out.println(san);
// >> 1
// >> 2
// >> 3
8進数で表したい場合、0
を接尾辞としてつける。
byte nana = 07; // 7
short hachi = 010; // 8
int kyu = 011; // 9
System.out.println(nana);
System.out.println(hachi);
System.out.println(kyu);
// >> 7
// >> 8
// >> 9
16進数で表したい場合、0x
を接尾辞としてつける。
byte jugo = 0xF; // 15
short juroku = 0x10; // 16
int junana = 0x11; // 17
System.out.println(jugo);
System.out.println(juroku);
System.out.println(junana);
// >> 15
// >> 16
// >> 17
浮動小数点数リテラル
デフォルトではdouble
型として扱われる。
float
型として扱いたい場合、末尾にf
もしくはF
の接尾辞をつける。
float a = 100.0F;
真偽リテラル
boolean
型として扱われる。
文字リテラル
char
型として扱われる。
文字リテラルはシングルコーテーション('
)で囲う必要がある。
またJavaは Unicode を文字コードとして採用していて、Unicodeエスケープシーケンスを使って文字を表現する際にはバックスラッシュ(\
)を使用する。(Windows環境では¥
)
Unicode の文字は U+0000
~ U+FFFF
までの文字コードが存在するが、Javaでは\u0000
~ \uFFFF
で表現する。
char komoji = '\u0061';
char omoji = '\u0041';
System.out.println(komoji);
System.out.println(omoji);
// >> a
// >> A
文字リテラルは直接16進数や10進数でも表現できる。(0x0000
~ 0xFFFF
は10進数では 0
~ 65535
であるため、次のような表現も可能。
char komoji = 0x0061;
char komoji2 = 97; // 0x0061 = 97
char omoji = 0x0041;
char omoji2 = 65; // 0x0041 = 65
System.out.println(komoji);
System.out.println(komoji2);
// >> a
// >> a
System.out.println(omoji);
System.out.println(omoji2);
// >> A
// >> A
char
型の変数に文字列リテラルは代入できない。
char ok = 'a'; // 有効
char error = "a"; // コンパイルエラー
Javaはシングルコーテーションとダブルコーテーションに明確な違いがあるため注意。
文字列リテラル
String
型として扱われる。
文字列リテラルはダブルコーテーション("
)で囲う必要がある。
String
型
String
型はプリミティブ型ではなく参照型(java.lang.String
)に分類される。
String
クラスは内部で文字列をchar
型の配列として扱っている。
String
型はインターフェースCharSequence
の実装クラスでもある。
String
型のインスタンス化
参照型のデータ型はnew
演算子によってインスタンス化を行う。
MyClass a = new MyClass();
ただし、String
型だけは特別扱いで、文字列リテラルによってString
型のインスタンスが生成できるようになっている。
String a = "hello world"; // String型はプリミティブ型ではなく参照型
immutable
String
クラスは immutable なクラスであるため、一度生成したインスタンスは中身を変更することができない。
String
型変数への再代入は、インスタンスの中身変更ではなく実際にはインスタンス新規生成を意味する。
// インスタンスに変更を加えているように見えるが、実際にはインスタンスを変更している訳ではない
String a;
a = "hello world";
a = "ABCD";
// 実際は immutable なインスタンスを新規生成している
String a;
a = new String("hello world");
a = new String("ABCD");
このため、subString()
、concat()
、toUpperCase()
などのメソッドが返すインスタンスは、実行されたメソッドのインスタンスではなく、新しいインスタンスということになる。
コンスタントプール
前述の通り、「文字列リテラルの使用」は「java.lang.String
クラスのインスタンス生成」を意味する。
しかし実際の文字列リテラルは、同じものが頻繁に使用されることが多い。同じ文字列であるのにわざわざ別のインスタンスとして扱うメリットはなく、逆に新たなインスタンス生成の度にメモリ領域が必要になってしまい非常に非効率になる。
String a = "hello world";
String b = "hello world";
String c = "hello world";
String d = "hello world";
そのため文字列リテラルにはコンスタントプールという仕組みがある。
コンスタントプールは文字列リテラルから生成されたString
インスタンスを管理するためのメモリ領域のことを指す。
通常、文字列リテラルが使用されるとまず最初に定数用のメモリ空間(コンスタントプール)にその文字列リテラルから生成されたインスタンスが既に存在しないかがチェックされる。存在しない場合に限り、String
型インスタンスが生成され、生成されたインスタンスがコンスタントプールに配置される。
この仕組みにより、同じ文字列リテラルからString
型インスタンスを生成する際は、過去に生成されたString
インスタンスへの参照が再利用される。
// 同じインスタンスへの参照が再利用されていることがわかる
String a = "hello world"; // コンスタントプール生成
String b = "hello world"; // コンスタントプールに存在するインスタンスへの参照を再利用
String c = "hello world"; // コンスタントプールに存在するインスタンスへの参照を再利用
String d = "hello world"; // コンスタントプールに存在するインスタンスへの参照を再利用
if (a == b && b == c && c == d) {
System.out.println("各変数は同じインスタンスを参照している");
}
// >> 各変数は同じインスタンスを参照している
ただし、プログラムの中で動的に生成された文字列はコンスタントプールに配置されない。また明示的にnew
演算子によって生成されたインスタンスが生成された場合、String
インスタンスはコンスタントプールからの再利用は行われない。
String
クラスのintern()
メソッドは、コンスタントプール(を含むメモリ領域)内からString
インスタンスを検索してその参照を返すことができる。
String a = "hello world";
// 明示的にnewしているので、コンスタントプールからのインスタンスの再利用、またはコンスタントプールへの保存がされない
String b = new String("hello world");
if (a == b) {
System.out.println("同じインスタンスを参照している");
}else{
System.out.println("別のインスタンスを参照している");
}
// >> 別のインスタンスを参照している
if (a == b.intern()) {
System.out.println("同じインスタンスを参照している");
}else{
System.out.println("別のインスタンスを参照している");
}
// >> 同じインスタンスを参照している
初期値
クラスのフィールドは、デフォルトで初期化されるため、int
型のメンバ変数は0
、boolean
型はfalse
、参照型のメンバ変数はnull
に初期化される。
一方メソッド内のローカル変数は、明示的に初期化されない限り初期値が設定されない。
class MyClass {
int a; // 0に初期化される
boolean b; // falseに初期化される
String c; // nullに初期化される
void myMethod() {
int x; // 初期化されない
System.out.println(x); // コンパイルエラー
}
}
型推論
varによる型推論は、ローカル変数にしか利用できない。
型推論はコンパイル時に代入演算子の右辺の値を元に行われる。
宣言時に型を指定しなかった場合、Object
型を指定したものとして扱われる。
// 型を指定していない
ArrayList list = new ArrayList<>();
// Object型を指定したものとして扱われる
ArrayList<Object> list = new ArrayList<>();
また、配列の初期化時にも型推論が働く。
int[] array = {1, 2, 3};
初期化子(Initializer)
クラスの初期化時に特定のコードを実行するための特殊なブロックのこと。
static初期化子(static initializer)とインスタンス初期化子(instance initializer)がある。
static初期化子
クラスがロードされるときに一度だけ実行されるコードブロック。
class MyClass {
static {
System.out.println("hello world");
}
}
静的なフィールドを初期化する際に有効。
class MyClass {
static int a;
static {
a = 10;
}
}
インスタンス初期化子
インスタンス生成の度に実行されるコードブロック。
コンストラクタの前に実行される。
class MyClass {
{
System.out.println("hello world");
}
}
オーバーロードによるコンストラクタが複数あるような場合に、全てのコンストラクタに共通の処理をまとめたい時などに有効。
class MyClass {
{
// コンストラクタの前に実行される
System.out.println("共通処理");
}
MyClass(int a) {
System.out.println("コンストラクタ1");
}
MyClass(int a, int b) {
System.out.println("コンストラクタ2");
}
}
実行順
- スーパークラスのstatic初期化子
- サブクラスのstatic初期化子
- スーパークラスのインスタンス初期化子
- スーパークラスのコンストラクタ
- サブクラスのインスタンス初期化子
- サブクラスのコンストラクタ
class SuperClass {
static {
System.out.println("①static initializer of super class");
}
{
System.out.println("③instance initializer of super class");
}
SuperClass(){
System.out.println("④constructor of super class");
}
}
class SubClass extends SuperClass{
static {
System.out.println("②static initializer of sub class");
}
{
System.out.println("⑤instance initializer of sub class");
}
SubClass(){
System.out.println("⑥constructor of sub class");
}
}
SuperClass a = new SubClass();
// >> ①static initializer of super class
// >> ②static initializer of sub class
// >> ③instance initializer of super class
// >> ④constructor of super class
// >> ⑤instance initializer of sub class
// >> ⑥constructor of sub class
ワイルドカード
型パラメータを柔軟に指定するための機能。
ワイルドカードを使うことで、特定の型に依存しない汎用的なコードを記述することができる。
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
List<?> list = new ArrayList<>();
List<Dog> dogs = new ArrayList<>(){
{
add(new Dog());
add(new Dog());
add(new Dog());
}
};
List<? extends Animal> animals = dogs;
List<Animal> animals = new ArrayList<>(){
{
new Animal();
new Animal();
new Animal();
}
};
List<? super Dog> dogs = animals;
共変性・反変性
ジェネリクスや型パラメータにおいて、型の代入互換性に関する性質として共変性と反変性がある。
共変性
Covariance
型パラメータ(T
)がその型のサブタイプ(?
)に対して有効である性質。
? extends T
(上限境界ワイルドカード)で表現する。
主に読み取り専用の操作に使用される。リストからの要素の取得はできるが、要素の追加はできない。
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
List<Dog> dogs = new ArrayList<>(){
{
add(new Dog());
add(new Dog());
add(new Dog());
}
};
// 共変性が成り立つ
List<? extends Animal> animals = dogs;
// 要素の取得はできる
Animal animal = animals.get(1);
// 要素の追加ができない(コンパイルエラー)
animals.add(new Dog());
反変性
Contravariance
ある型パラメータ(T
)がその型のスーパータイプ(?
)に対しても有効である性質。スーパータイプにはインターフェースも含む。
? super T
(下限境界ワイルドカード)で表現する。
主に書き込み専用の操作に使用される。リストへの要素の追加はできるが、型安全が失われるため要素の取得はできない。
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
List<Animal> animals = new ArrayList<>(){
{
new Animal();
new Animal();
new Animal();
}
};
// 反変性が成り立つ
List<? super Dog> dogs = animals;
// 要素の追加はできる
dogs.add(new Dog());
// 要素の取得は型安全が保証されないためキャストが必要(コンパイルエラー)
Animal animal = dogs.get(1);
// Object型として扱うならOK
Ocject obj = dogs.get(1);
共変戻り値
サブクラスでオーバーライドされたメソッドの戻り値型が、親クラスで定義されたメソッドの戻り値型のサブタイプにすることができるという概念のこと。
class Animal {
// 戻り値型: Animal型
Animal getAnimal() {
return new Animal();
}
}
class Dog extends Animal {
// 戻り値型: Dog型(アニマルのサブクラス)
@Override
Dog getAnimal() {
return new Dog();
}
}
class SuperClass<T> {
// 戻り値型: T型
T getItem(T t) {
return t;
}
}
class SubClass extends SuperClass<String> {
// 戻り値型: String型(ジェネリクス T の具体型)
@Override
String getItem(String arg) {
return "hello";
}
}
ネストしたクラス
クラスの内部でさらにクラスを定義した場合、外側のクラスはEnclosingクラスと呼ばれ、内側のクラスはネストしたクラスと呼ばれる(enclose=囲う)。
いくつかの分類がある。
ネストしたクラス | public |
protected |
private |
abstract |
final |
stataic |
|
---|---|---|---|---|---|---|---|
インナークラス | クラス内で定義 | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ |
ローカルクラス | メソッド内で定義 | × | × | × | ◯ | ◯ | × |
匿名クラス | メソッド内で定義 | × | × | × | × | × | × |
インナークラス
クラス内で定義されたクラスをインナークラスと言う。
class OuterClass {
class InnerClass {
}
}
インナークラスはEnclosingクラスと同様のアクセス修飾子(public
, protected
, private
, abstract
, final
)を使用する事ができる。
またインターフェースを実装(implements
)することもできる。
インナークラスはEnclosingクラスのインスタンスに属しているため、Enclosingクラスのインスタンスを介してアクセスを行う。
OuterClass outer = new MyClass();
OuterClass.InnerClass inner = outer.new InnerClass();
以下のように1行にまとめて記述することもできる。
OuterClass.InnerClass inner = new OuterClass().new InnerClass();
またimport
を行えば、このような記述も可能。
import パッケージ名.OuterClass.InnerClass;
InnerClass inner = new OuterClass().new InnerClass();
特徴
インナークラスはEnclosingクラスで定義されたフィールドやメソッドと同様に、Enclosingクラスのインスタンス変数に直接アクセスする事ができる(ここが個人的に最初全然理解できていなかったところで、インナークラスを理解する上での肝となるところ)。
class OuterClass {
String outerField = "hello";
class InnerClass {
String innerFiled = outerField + " world";
}
}
この特徴からわかるように、インナークラスはEnclosingクラスのインスタンスに依存している。
インナークラスはEnclosingクラスのインスタンスに依存している
static
インナークラス
インナークラスをstatic
修飾したものを特にstaticインナークラスと呼ぶ。
class OuterClass {
static class InnerClass {
}
}
static
インナークラスは、インスタンス化にEnclosingクラスのインスタンスを必要としない。
OuterClass.InnerClass inner = new OuterClass.InnerClass();
import パッケージ名.OuterClass.InnerClass;
InnerClass inner = new InnerClass();
static
インナークラスはEnclosingクラスのインスタンスではなく、Enclosingクラス自体に属するため、static
インナークラスはEnclosingクラスのインスタンスに依存しない。
そのため、Enclosingクラスの非static
フィールドや、非static
メソッドにはアクセスする事ができない。
class OuterClass {
String outerField = "hello";
static class InnerClass {
// コンパイルエラー
String innerFiled = outerField + " world";
}
}
static
インナークラスはEnclosingクラスのインスタンスに依存しない。
Enclosingクラス自体に依存している。
インナークラスでstatic
フィールドは定義できない
インナークラス内でstatic
なフィールドを定義しようとした場合、コンパイルエラーが発生する。
class OuterClass {
class InnerClass {
// コンパイルエラー
static int staticField = 0;
// 定数は定義可能
static final int CONSTANT_FIELD = 10;
}
}
static
なフィールドは、インスタンスではなくクラス自体に依存するため、クラスをstatic
修飾しなければならない。
class OuterClass {
static class InnerClass {
static int staticField = 10;
}
}
ローカルクラス
メソッド内で定義されるクラスをローカルクラスと言う。
(メソッド内で定義される変数をローカル変数と言う)
class OuterClass {
void outerMethod() {
class LocalClass {
}
}
}
ローカルクラスは自身が定義されたスコープ内でのみ、インスタンス化を行う事ができる。
class OuterClass {
void outerMethod() {
class LocalClass {
}
LocalClass local = new LocalClass();
}
}
Factoryパターンを使用する際に、クラス定義をメソッド内に隠蔽させる使い方などがある。
Factoryパターンについてはこちら。
ローカルクラスのインスタンスは、メソッドの実行後もメモリ上に存在し続ける可能性がある。
interface MyInterface {
void myMethod();
}
class OuterClass {
MyInterface outerMethod() {
class LocalClass implements MyInterface {
@Override
public void myMethod() {
}
}
return new LocalClass();
}
}
MyInterface local = new OuterClass().outerMethod();
// outerMethod()実行後も、LocalClassのインスタンスはまだ生きている
local.myMethod();
一方で、ローカル変数のスコープはメソッドの終了とともに消滅する(メモリ上から削除される)ため、ローカルクラス内でローカル変数を使用した場合、再代入を行う事ができない実質的にfinal
(effectively final)な定数扱いになる(ラムダ式と同様)。
ローカルクラスに対しては、ローカル変数の参照ではなく値のコピーが渡っているため、ローカル変数のスコープを抜けた後も定数として使用することができる。
class OuterClass {
void outerMethod() {
String localVal = "hello";
class LocalClass {
void innerMethod(){
// コンパイルエラー
localVal = "world";
}
}
}
}
ちなみにJavaScriptではクロージャーと呼ばれる、外側のスコープ(レキシカルスコープ)を保持し続ける関数を定義する事ができる。
function outer() {
// レキシカルスコープ
let outerVar = "hello";
function inner() {
outerVar = "world";
console.log(outerVar);
}
return inner;
}
const innerFn = outer();
innerFn();
// >> "world"
匿名クラス
名前のないクラスのインスタンスを生成するための特殊な構文。
普通、Javaのクラスは名前とともに宣言され、宣言された名前とnew
キーワードを使ってインスタンス化が行われる。
MyClass obj = new MyClass();
匿名クラスは宣言と同時にインスタンスが生成される。
また、匿名クラスはインターフェースに使用した場合にはインターフェースを「実装(implements
)」したクラスになり、通常のクラスに使用した場合にはそのクラスをスーパークラスとして「継承(extends
)」したサブクラスになる。
インターフェースに対して使用する匿名クラス
public interface MyInterface {
void method();
}
public class Main {
public static void main(String[] args) {
MyInterface obj = new MyInterface() {
// ここが MyInterface を「実装」するクラス
@Override
public void method() {
System.out.println("ここでインターフェースを実装する");
}
};
}
}
スレッドの生成時にも利用される。
// java.lang.Thread、java.lang.Runnable を使用
// 匿名クラスを使ってRunnableインターフェースを実装する
var thread = new Thread(new Runnable(){
@Override
public void run() {
System.out.println("非同期的に処理を実行します。");
}
});
thread.start();
// >> 非同期的に処理を実行します。
クラスに対して使用する匿名クラス
class SuperClass {}
public class Main {
public static void main(String[] args) {
SuperClass obj = new SuperClass() {
// ここが SuperClass を「継承」するクラス
public void method() {
System.out.println("ここでスーパークラスを継承する");
}
};
}
}
匿名クラスは、クラス名やコンストラクタは定義する事ができないため、初期化を行いたい場合インスタンス初期化子を使用する。
List<String> list = new ArrayList<String>(){
// ここが匿名クラス部分
{
// ここが匿名クラスのインスタンス初期化子部分
add("Apple");
add("Orange");
add("Melon");
}
};
Enum
JavaのEnumはクラス。
定義したEnumはjava.lang.Enum
クラスのサブクラスとして扱われる。
定義した列挙子は、それぞれが列挙型から生成されたインスタンスとして扱われる。
public enum Fruits {
Apple, // Fruits型インスタンス
Banana, // Fruits型インスタンス
Orange, // Fruits型インスタンス
}
コンストラクタやフィールドを定義する事ができる。
public enum Fruits {
Apple(1), // Fruits型インスタンス
Banana(2), // Fruits型インスタンス
Orange(3); // Fruits型インスタンス
private int id;
Fruits(int id){
this.id = id;
}
}
例外
モジュールシステム
複数のパッケージをまとめる仕組みをモジュールシステムという。
モジュールの定義はmodule-info.java
に記述する。
module モジュール名 {
exports 公開するパッケージ名;
requires このモジュールが依存する別のモジュール名;
}
このmodule-info.java
を配置したフォルダがモジュールとして扱われる。
パッケージ内ではクラス単位でアクセス制御を行うのに対して、モジュールシステムではパッケージ単位でアクセス制御を行うことができる。
モジュール名はパッケージと同様に一意であることが必要。そのため他のモジュール名と被らない名前にする必要がある。
通常は、モジュールに含めるパッケージ名に共通の文字列があれば、その部分をモジュール名として使用する。共通部分がない場合は、モジュールの目的を表現する名前にする事が望ましい。
ちなみにモジュール名とパッケージ名は重複しても良い。
ただし、ドット「.
」で区切ったパッケージがフォルダ階層にマッピングされるのに対して、モジュール名にドット「.
」を含めてもフォルダ階層にマッピングされない。そのため、モジュールを配置するフォルダ名はキャメルケースなどで命名する、もしくはmods
などのプロジェクト毎に変わらない名前にする。
また、標準クラスライブラリをモジュールとして提供するjava.base
モジュールについては、requires
を記述しなくても使用することができる。このようなモジュールをプラットフォームモジュールと呼ぶ。
例
- モジュール名:
a.b.c
- モジュールに含めるパッケージ
-
x.y.z
パッケージ -
x.y.zz
パッケージ
-
(通常はパッケージの共通部分であるx.y
を使用してモジュール名はx.y.myModule
などとした方が良い。)
project
|
|--- src
| |
| |--- abc ◀️ モジュールのルートディレクトリ(階層にしない。キャメルケースが適切?)
| |
| |--- x ◀️ パッケージのルートディレクトリ
| | |
| | |--- y
| | |
| | |--- z
| | | |
| | | |--- Main.java
| | |
| | |--- zz
| | |
| | |--- MyClass.java
| |
| |--- module-info.java
|
|--- mods ◀️ コンパイルの出力先( ◀️ ◀️ ◀️ コンパイル後のモジュールのルートディレクトリ)
|
|--- abc ◀️ コンパイルしたモジュール用のフォルダ
コンパイル
コンパイル時には-d
オプションで出力先を指定することができる。
javac \
-d コンパイルしたクラスファイルの格納先 コンパイル対象のソースファイル
javac \
-d mods/abc \
src/abc/module-info.java \
src/abc/x/y/z/Main.java \
src/abc/x/y/zz/MyClass.java
実行
コンパイルしたクラスファイルを指定して実行する。
モジュールを実行したい場合、--module-path
(-p
)オプションでモジュールのルートディレクトリを指定し、-module
(-m
)オプションで実行したいモジュールのクラスを指定する。
モジュール名とパッケージはスラッシュ「/
」で区切る。
java \
--module-path コンパイル後のモジュールのルートディレクトリ \
-module モジュール名/FQCN(エントリーポイント)
java \
--module-path mods \
-module a.b.c/x.y.z.Main
JARファイル化
モジュールをJARファイルにまとめたいときには以下のようにする。
-
--create
: 新しくJARファイルを作成する -
--file-name
(-f
): JARファイルの名前 -
--main-class
: エントリポイントのFQCN -
-C
: JARファイル化するフォルダ(フォルダ名をドット「.
」にすることでフォルダ内の全てのファイルを対象にする)
jar --create \
--file-name JARファイル名 \
--main-class エントリポイント \
-C JARファイルにするフォルダパス
jar --create \
--file-name jars/a.b.c.jar \
--main-class x.y.z.Main \
-C mods/abc .
JMODファイル化
JMODファイルは、モジュールをバンドル化して配布するための形式の1つ。
バンドル化とは、ソフトウェアの複数の要素(例えば、クラスファイル、リソースファイル、ライブラリなど)を一つのアーカイブファイルにまとめることを指す。
JavaのモジュールシステムにおいてJMOD形式のファイルはこのバンドル化の一種であり、モジュールに関連するすべてのファイルを一つの単位として扱いやすくする。
jmod create \
--class-path クラスパス \
出力JMODファイル名 モジュール名
jmod create \
--class-path mods/abc \
abc.jmod a.b.c
クラスパスについては以下をにまとめています。
情報を確認する1
モジュールが公開するパッケージや依存するモジュールを調べる方法。
java \
--module-path コンパイル後のモジュールのルートディレクトリ \
--describe-module モジュール名
java \
-module-path mods \
--describe-module a.b.c
情報を確認する2
対象のモジュールがJMOD形式の時の使用できる、モジュールが公開するパッケージや依存するモジュールを調べる方法。
jmod describe モジュール名
jmod describe a.b.c
情報を確認する3(依存関係のみ調べる)
jdeps
コマンドを使うと、モジュールに限らずJARファイルやクラス同士の依存関係を調べることができる。
jdeps JARファイル名
jdeps --module-path コンパイル後のモジュールのルートディレクトリ -module モジュール名
jdeps --module-path mods -module a.b.c
モジュールグラフ
モジュール間の依存関係を表現した図をモジュールグラフと言う。
moduleA
が moduleB
に依存し、moduleB
が moduleC
に依存している場合、モジュールグラフは以下のようになる。
この状況で moduleB
が moduleC
への依存を moduleA
に伝播させることができる。
モジュールの依存を伝播させるためにはtransitive
を使用する。(transitive=推移的)
module moduleB {
requires transitive moduleC;
}
これによりモジュールAはrequires moduleC
を記述しなくても直接モジュールCが使用できるようになる。
この時、moduleA
と moduleC
には推移的な依存関係があると表現される。
試験対策としての早見表
文法的な暗記系知識。
実行
起動パラメータにスペースを含めたい場合、ダブルコーテーション「"
」で囲う
java Main.java "hello world"
起動パラメータにダブルコーテーション「"
」を使いたい場合、エスケープする
LinuxOSの場合はバックスラッシュ「\
」、Windowsの場合は「¥
」を使用する
java Main.java \"
上の例では「"」がパラメータとして渡される。
エスケープされないダブルコーテーション「"
」は無視される
java Main.java a"bc"
上の例では「abc」がパラメータとして渡される。
アクセス修飾子
protected
とデフォルトの違いに注意して覚える
修飾子 | 可視性 | |
---|---|---|
private | - | 同じクラス内からのみ |
なし(デフォルト) | ~ | 同じパッケージ内からのみ |
protected | # | 同じパッケージ内 + 継承関係にあるサブクラス からのみ |
public | + | 全て |
for文
初期化文と更新文はコンマ(,
)で区切って複数記述することができる
for (int i = 0, j = 0; i < 10; i++, j--) {
// 処理
}
ただし初期化では同じ型しか記述できない
// コンパイルエラー
for (int i = 0, long j = 0; i < 10; i++, j--) {
// 処理
}
条件式と更新式は省略できる
for (int i = 0; ; i++) {
// 処理
}
for (int i = 0; i < 10; ) {
// 処理
}
break
で抜けるのは直近のループだけ
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if(条件){
braek;
}
}
// ここに抜ける
}
// ここには抜けない
switch文
switch文の条件式にはboolean
型やlong
型、float
型、double
型を使用することができない。
参照型(String
型と列挙型を除く)についても使用することができない。
fianl
ではない変数はcase
に使用できない
int target = 100;
int a = 10;
final int B = 10;
switch (target) {
case B:
// 処理
break;
case a: // コンパイルエラー
// 処理
break;
}
配列
要素数は必ず右辺に書く
配列宣言時に左辺の型として使用する配列型(int[]
や int[][]
など)は配列オブジェクトへの参照を保持するためのものであり、配列の要素数については関与しない。
各要素については配列オブジェクトが管理を行う。
int[] array = new int[10];
// コンパイルエラー
int[10] array = new int[];
// コンパイルエラー
int[10] array = new int[10];
[]
は変数名の後ろにも置ける
int[] array = new int[10];
int array2[] = new int[10];
int array3[][] = new int[10][20];
多次元配列は[]
をデータ型の後ろと変数名の後ろに分散して置ける
int[] array2[] = new int[10][20]; // 2次元配列
int[][] array3[] = new int[10][20][30]; // 3次元配列
多次元配列の1次元目の要素数は省略できない
int[][] array = new int[10][20];
int[][] array2 = new int[10][];
// コンパイルエラー
int[][] array3 = new int[][20];
多次元配列の2次元目以降は要素数を揃える必要はない
int[][] array = new int[3][];
array[0] = new int[10];
array[1]= new int[20];
array[2] = new int[30];
配列の初期子{}
を使用する場合、要素数は指定できない
int[] array = {1, 2, 3, 4, 5};
int[] array2 = new int[]{1, 2, 3, 4, 5};
// コンパイルエラー
int[] array3 = new int[5]{ 1, 2, 3, 4, 5};
明示的に初期化しなかった配列の各要素は、型のデフォルト値になる
int[] a = new int[1];
System.out.println(a[0]);
// >> 0
double[] b = new double[1];
System.out.println(b[0]);
// >> 0.0
boolean[] c = new boolean[1];
System.out.println(c[0]);
// >> false
char[] d = new char[1];
System.out.println(d[0]); // \u0000
System.out.println((int)d[0]);
// >> �
// >> 0
String[] e = new String[1];
System.out.println(e[0]);
// >> null
Object[] f = new Object[1];
System.out.println(f[0]);
// >> null
コンストラクタ
コンストラクタに見えるメソッド
class MyClass {
// 戻り値を指定しているためコンストラクタ扱いにならず、メソッド扱いになる
void MyClass(){
System.out.println("hello world");
}
}
public class Main {
public static void main(String[] args) {
MyClass a = new MyClass();
a.MyClass();
// >> hello world
}
}
自身のコンストラクタはthis()
で呼び出せる
class MyClass {
MyClass() {
this(10);
}
MyClass(int a) {
}
}
自身のコンストラクタを呼び出す場合、thisより前に処理は実行できない
class MyClass {
MyClass() {
// thisは最初に実行しなければならない
System.out.println("hello world");
// コンパイルエラー
this(10);
}
MyClass(int a) {
}
}
インターフェース
final
かつstatic
なフィールドなら定義できる
interface MyInterface {
public static final int A_MAX = 10;
}
以下のように記述することも可能。
interface MuInterface {
// 内部的には public static final int A_MAX = 10; として扱われる
int A_MAX = 10;
}
static
なメソッドも定義できる
interface MyInterface {
public static void myMethod(){
};
}
private
なメソッドも定義できる
public interface MyInterface {
private void method() {
System.out.println("hello world");
}
}
複数のdefault
メソッドの共通処理をまとめる事ができる。
Object
クラスのメソッドをオーバーライドできない
インターフェースは通常、中身のない抽象メソッドのみが定義可能であり、中身のあるメソッドを定義する場合default
を使用する必要がある。
interface MyInterface {
public void myMethodA();
default public void myMethodB() {
System.out.println("hello world");
}
// コンパイルエラー
public void myMethodC() {
System.out.println("hello world");
}
}
このdefault
を使用してメソッドの定義をするとき、java.lang.Object
のメソッド(equals()
, hashCode()
, toString()
)をオーバーライドすることはできない。
interface MyInterface {
// コンパイルエラー
@Override
default public String toString() {
return "hello world";
}
}
インターフェースのdefault
メソッドを呼び出す
次の構文でインターフェースのdefault
メソッドを実装クラスから呼び出す事ができる。
インターフェース名.super.メソッド名()
interface MyInterface {
default public void a() {
System.out.println("hello world");
}
}
class ConcreteClass implements MyInterface {
@Override
public void a() {
// インターフェース名.super.メソッド名()
MyInterface.super.a();
System.out.println("hello world");
}
}
ただし、直接実装(implements
)していないスーパークラスのdefault
メソッドは呼び出せない(孫から祖父母のメソッドは呼べない)。
interface SuperInterface {
default public void a() {
System.out.println("hello world");
}
}
interface SubInterface extends SuperInterface {
}
class ConcreteClass implements SubInterface {
@Override
public void a() {
// コンパイルエラー
SuperInterface.super.a();
System.out.println("hello world");
}
}
「もしインターフェース名を指定しなくても良い」とすると、多重に実装(implements
)したインターフェースに同じ名前のdefault
メソッドが定義されていたらどちらのメソッドかが判別できなくなってしまう。
interface InterfaceA {
default public void hello() {
System.out.println("hello world");
}
}
interface InterfaceB {
default public void hello() {
System.out.println("hello world");
}
}
class ConcreteClass implements InterfaceA, InterfaceB {
@Override
public void hello() {
// コンパイルエラー(どっちのhello()?)
super.hello();
System.out.println("hello world");
}
}
default
メソッド > 通常メソッド
インタフェースの本来の目的は「型を提供すること」であり、default
メソッドはあくまでもオプション的な位置付けであるため、同じ名前のメソッドがインターフェースとスーパークラス両方で定義されている場合、スーパークラスのメソッドが優先される。
public interface MyInterface {
default void method() {
System.out.println("インターフェース");
}
}
public class SuperClass {
public void method() {
System.out.println("スーパークラス");
}
}
public class MyClass extends SuperClass implements MyInterface {}
MyClass obj1 = new MyClass();
obj1.method();
// >> スーパークラス
SuperClass obj2 = new MyClass();
obj2.method();
// >> スーパークラス
MyInterface obj3 = new MyClass();
obj3.method();
// >> スーパークラス
継承
サブクラスではスーパークラスのフィールド名と同じフィールド名を使える
サブクラスではスーパークラスのフィールド名と同じフィールド名を使える。(オーバーライドではなく、実際には新しいフィールドが追加される)
ただし、実行時どちらにアクセスされるかは変数の宣言型に依存する。スーパークラス型で宣言した変数を通してアクセスを行った場合にはスーパークラスで宣言したフィールドが使用される。
class SuperClass {
String a = "super";
}
class SubClass extends SuperClass {
String a = "sub";
}
SuperClass obj1 = new SubClass();
System.out.println(obj1.a);
// >> super
SubClass obj2 = new SubClass();
System.out.println(obj2.a);
// >> sub
メソッドからのアクセスの場合、以下のようになる。
class SuperClass {
String a = "super";
public void b() {
System.out.println(a);
}
}
class SubClass extends SuperClass {
String a = "sub";
}
SuperClass obj1 = new SubClass();
obj1.b();
// >> super
SubClass obj2 = new SubClass();
obj2.b();
// >> super
class SuperClass {
String a = "super";
public void b() {
System.out.println(a);
}
}
class SubClass extends SuperClass {
String a = "sub";
// オーバーライドしている
@Override
public void b() {
System.out.println(a);
}
}
SuperClass obj1 = new SubClass();
obj1.b(); // オーバーライドしているためSubClassのメソッドが使用される
// >> sub
SubClass obj2 = new SubClass();
obj2.b();
// >> sub
スーパークラスのコンストラクタはコンパイル時に自動で追加される
class SuperClass {
public SuperClass() {
System.out.println("super class constructor");
}
}
class SubClass extends SuperClass {
public SubClass() {
// コンパイル時にここに super(); が自動挿入される
System.out.println("sub class constructor");
}
}
SuperClass obj = new SubClass();
// >> super class constructor
// >> sub class constructor
継承させたくないクラスはfinal
で修飾する
final class myClass {
}
Object
のequals()
Object
のequals()
はオーバーライドして同値性を検証させるものだが、オーバーライドをしなかった場合には同一性(==
と同じ)を検証するようになっている。
static
メソッドはオーバーライドできない
public class SuperClass {
static void method() {
}
}
public class SubClass extends SuperClass {
// コンパイルエラー
@Override
static void method() {
}
}
ただし@Override
アノテーションをつけなかった場合、スーパークラスのstatic
メソッドを隠蔽したことになり、コンパイルエラーが起きない。
public class SubClass extends SuperClass {
// 親クラスのメソッドを隠蔽
static void method() {
}
}
ラムダ式
基本事項はこちら。
ラムダ式内で利用するローカル変数は実質的にfinal
でなければならない
ラムダ式は宣言場所と同じスコープを持つため、ラムダ式内でラムダ式を宣言したスコープからアクセス可能なローカル変数にアクセスすることができる。
一方で、ラムダ式が実行されるのは宣言場所と別のタイミングになるため、ラムダ式内でローカル変数へのアクセスを行うと、そのローカル変数は実質的にfinal
(effectively final)でなければならなくなる。
String a = "hello";
// ラムダ式内で a を参照
Runnable runnable = () -> System.out.println(a);
// aに再代入しようとするとコンパイルエラーが起きる
a = "Goodbye";
runnable.run();
コレクション
Arrays.asList()
が返すList
は固定サイズ
配列をList
に変換することができるArrays.asList()
が返すList
は、要素数が固定の特殊なList
になっている。
通常のList
と同じだと勘違いして要素を追加しようとすると、実行時例外が発生する。
List<String> list = Arrays.asList(new String[]{"aaa", "bbb", "ccc"});
list.add("ddd"); // 実行時例外が発生する
要素数が固定のList
は明示的に生成することも可能。
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
// 要素数が固定のListに変換
List<String> list2 = Collections.unmodifiableList(list);
list2.add("ddd"); // 実行時例外が発生する
動的サイズのArrayList
を初期化したい場合、ArrayList
のコンストラクタに固定サイズのList
を渡す。
List<String> list = new ArrayList<>(
Arrays.asList(new String[]{"aaa", "bbb", "ccc"})
);
list.add("ddd");
API
累乗はpow
、平方根はsqrt
pow : power(累乗)
// x の y 乗
Math.pow(x, y)
sqrt: square root(平方根)
// x の平方根
Math.sqrt(x)
try-catch / try-with-resources
catch
とfinally
ブロックでどちらも戻り値をreturn
したときはfinally
の戻り値が優先される
String a() {
try {
throw new Exception();
} catch (Exception e) {
return "catch";
} finally {
return "finally";
}
}
System.out.println(a());
// >> finally
try
ブロックで宣言した変数はfinally
ブロックからアクセスできない
try (FileInputStream is = new FileInputStream("sample.txt")) {
} catch (Exception e) {
} finally {
// コンパイルエラー
if (is != null) {
}
}
リソースのクローズ時の実行順
class MyResource implements AutoCloseable {
@Override
public void close() {
System.out.println("①close()が実行される");
}
}
try (MyResource r = new MyResource()) {
throw new Exception();
} catch (Exception e) {
System.out.println("②catchブロックが実行される");
} finally {
System.out.println("③finallyブロックが実行される");
}
// >> ①close()が実行される
// >> ②catchブロックが実行される
// >> ③finallyブロックが実行される