1. JAVAの基礎基本
パッケージ
クラスなどの名称が重複して、予期せぬ不具合が発生することを防ぐための仕組み。使用目的は3つ
- 名前空間を提供して、名前が重複することを防ぐ
- アクセス修飾子と組み合わせてアクセス制御機能を提供する
- クラスを分類する
package sample; // package宣言はソースコードの先頭行に書く
import aaa.*
public class Test { }
javaコマンド
javaコマンドで実行できるのは次の3つ
- mainメソッドをもつクラスファイル(.class)
- jarファイル(.jar)内のメインクラス
- モジュールに含まれるメインクラス
$ java Main
ソースファイルモード
javacコマンドでのコンパイルなしでソースファイルを直接実行できる。(Java SE 11以降)
※メモリ上でコンパイル後のコードを扱っており、コンパイル自体を行っていないわけではない。
$ java Sample.java
その他のコマンド
# クラスの依存関係を調べる
$ jdeps
# モジュールの依存関係を調べる
$ java --show-module-resolution
# モジュールの情報を確認する
$ jmod describe
# クラスファイルの出力先を指定してコンパイル
$ javac -d
# クラスパスを指定して実行
$ java -cp
アクセス修飾子
-
public
: 『すべてのクラス』からアクセス可 -
protected
: 『同じパッケージに属するクラス』か『継承しているサブクラス』からのみアクセス可 - なし(デフォルト): 『同じパッケージに属するクラス』からのみアクセス可
-
private
: 『クラス内』からのみアクセス可
2. データ型と文字列操作
リテラル
真偽値
- boolean
文字
- char
char a = 'a'; // 文字は''
String b = "a"; // 文字列は""
char c = '\u30A2'; // Unicode(『\u』+『16進数4桁』)
char d = 65; // 0 ~ 65535の数値
int e = 10;
char f = (char) e; // 変数を介して整数を代入する場合はキャストが必要
整数
- byte … 8ビット
- short … 16ビット
- int … 32ビット
- long … 64ビット
int a = 0b10010; // 2進数
int b = 0320560; // 8進数
int c = 0x10b2c; // 16進数
int d = 123_456_789; // 桁区切りの_(アンダースコア)は、先頭・末尾・記号の前後以外で使用できる
小数
- float … 32ビット
- double … 64ビット
ラッパークラスとの演算
プリミティブとそのラッパークラスで演算が行われる際、ラッパークラス側がアンボクシングされる。
int a = 10;
Integer b = 10;
System.out.println(a == b); // true
識別子
変数やメソッド、クラスなどの名前
- 予約語は識別子に使えない
- 使える記号は『_』(アンダースコア)と通過記号のみ
- 数字からは始められない
暗黙の型変換
キャスト式が不要な型変換のこと。
『データ型を変換してもデータが欠損しない』ことが条件
short a = 10
int b = a; // 『小』を『大』に代入してもデータは欠損しないのでOK
double c = 12.3;
int d = c; // 小数を整数に代入すると小数部分が欠損するのでNG
型推論
var
を使った変数宣言。コンパイル時に型が推論される。
ローカル変数の宣言にのみ使用できる。フィールドやメソッドの引数などには使用できない
。
var a = 10; // コンパイル時にint型と推論される。
var b = sample(); // メソッドの戻り値からも推論できる。
var c = () -> {}; // ラムダ式からは推論できない。
var d = {1, 2, 3}; // 配列の初期化式からも推論できない。
var e = new ArrayList<>(); // ダイヤモンド演算子で推論できないときは、Object型と推論される。
var f; // 初期化されていなかったり、
var g = null; // nullで初期化していると推論できない。
immutableなクラス
- 『mutable(可変)なオブジェクト』... フィールドの値を変更できる
- 『immutable(不変)なオブジェクト』... フィールドの値を変更できない(String, File, etc...)
実装するには以下のようにする。
- setter を提供しない。
- 参照型のフィールドについて getter では値そのものではなくコピーを返却する。
- フィールドの値を変更するメソッドでは、新しくインスタンスを生成して返却する。
- 全フィールドを final かつ private にする。
- メソッドのオーバーライドを許可しない。
3. 演算子・判定構造
数値リテラルの型変換
大きな範囲の値を小さな変数に代入するときは、明示的なキャストが必要。
(数値リテラルはデフォルトで特定の型をもっており、整数はint
型、小数はdouble
型が使用される。)
int i = 10;
short a = (short)i; //『大』を『小』に代入
long l = 10L;
int b = (int)l; //『大』を『小』に代入
double d = 10.0;
float c = (float)d; //『大』を『小』に代入
long d = 10; //『小』を『大』に代入するのは問題ない
ただし、byte
型やshort
型に整数リテラルを代入する場合、
その値が型の範囲に収まるのであればエラーは発生しない。
byte a = 127; //『大』を『小』に代入しているが、127はbyte型の範囲内なのでOK
byte b = 128; // 128はbyte型の範囲外なのでNG(コンパイルエラー)
byte c = (byte)128; // 型の範囲外でも、キャストしていればエラーにはならない
int d = 10L; //←※NG(これができるのはbyteとshortのみ。intではできない)
同一性と同値性
- 同一性:同じインスタンスであること(参照先が同じであること)。
==
で比較する。 - 同値性:同じ値であること。
equals
メソッドで比較する。
public class Sample {
private int num;
public Sample(int num) {
this.num = num;
}
public equals(Object obj) {
if (obj == null) return false; // 比較対象がnullのときはfalseを返却
if (obj instanceof Sample) { // Sampleクラスのインスタンスであるかどうか
Sample s = (Sample) obj;
return s.num = this.num; // 【クラスごとに比較処理を実装】
}
return false; // Sampleクラスのインスタンスでない場合はfalseを返却
}
}
class Main {
public static void main(String[] args) {
Sample s1 = new Sample(10);
Sample s2 = new Sample(10);
System.out.println(s1 == s2); // 同一性はFALSE
System.out.println(s1.equals(s2)); // 同値性はTRUE
}
}
ちなみに、Objectクラスに実装されているequalsメソッドは同一性を返却する。
public boolean equals(Object obj) {
return (this == obj);
}
Stringクラスのコンスタントプール
String a = "sample";
String b = "sample";
System.out.println(a == b); // TRUE
変数aとbに異なる参照を代入しているが、☝のコードはTRUEを返却する。
これは、String s = [文字列]
の形で変数を宣言した際、同じ文字列が使われている場合には
同じ参照先が使用されるという仕組みになっているからである。
この仕組みをコンスタントプール
という。
String a = "sample";
String b = new String("sample");
System.out.println(a == b); // FALSE:`new String()`で変数宣言するとコンスタントプールは使用されない
System.out.println(a.intern() == b.intern()); // TRUE:intern()メソッドを使用すると、メモリ内にある文字列への参照を戻し、その結果は必ず同じになる。
4. 制御構造
for文に関するtips
for (int i = 0, j = 0; ; ) { } // 型が同じなら複数の変数を宣言できる
for (int i = 0; ; i++, System.out.println("JAVA")) { } // 更新文で複数の処理を実行できる
拡張for文内の変数
拡張for文で要素を格納する変数は一時的なものなので、変数の参照先を変更しても集合には影響しない。
String[] ary = {"A", "B", "C"};
for (String str: ary) {
str = "X";
}
for (String str: ary) {
System.out.println(str); // -> ABC
}
switch の default
- どの
case
にも当てはまらなかったときに実行される。 - 記述する順番は最後でなくてもいい。
int i = 0;
switch (i) {
default: System.out.print("A");
case 1: System.out.print("B");
case 2: System.out.print("C"); // 『ABC』と出力される。
}
5. 配列の操作
配列型変数の宣言
[]
を使って宣言する。
int[] ary01; // 型の後でも
int ary02[]; // 変数名の後でもOK
int[][] ary03; // 重ねると多重配列になる。
int[][] ary04[]; // どちらも3重配列
int[] ary05[][];
配列型変数には配列インスタンスを代入するだけであり、
参照先の配列がいくつの要素を扱えるかは関係ないため、
宣言時に要素数を指定することはできない。
int[2] ary; // <- ×: 要素数の指定はできない。
配列インスタンスの生成
必ず要素数を指定する必要がある。
int[] ary01 = new int[2]; // OK
int[] ary02 = new int[]; // NG
int[][][] ary03 = new int[2][3][4]; // 多重配列は各次元の要素数を一度に指定できる
int[][] ary04 = new int[3][]; // 1次元目の要素数の指定は省略できないので注意
初期化子
配列インスタンスの生成と要素の代入を一度に行える。
int[] ary01 = new int[2];
ary[0] = 2;
ary[1] = 3;
// ☝と☟のコードは同じ処理内容
int[] ary02 = {2, 3};
new
と合わせて使用するときは、要素数を指定できないことに注意
int ary01 = new int[]{2, 3}; // OK
int ary02 = new int[2]{2, 3}; // NG: 初期化子を使うと要素数が自動で計算される
int ary03 = new int[2]{}; // NG: {} は int[0] と同じ意味になる
多次元配列の場合は、次元数を合わせる必要があることに注意する。
{}
を重ねて次元数を表現するか、new
を使用する方法がある。
int[][] ary01 = {{1, 2}, {2, 3}}; // OK
int[][] ary02 = int[][]{}; // OK
int[][] ary03 = new int[]{}; // NG
int[][] ary04 = {}; // NG
初期化子は、
『変数宣言と同じとき』か、
『[]
を使って次元数を明示しているとき』にしか使用できない。
int[] ary01 = {}; // OK: 変数宣言と同じとき
int[][] ary02;
ary02 = new int[][]{}; // OK: 次元数を明示しているとき
int[] ary03;
ary03 = {}; // NG
要素の初期値
各要素の明示的な初期化が行われなかった場合、デフォルト値で初期化される。
int[] a = new int[3];
for (int i: a) {
System.out.print(i); // 『000』
}
cloneメソッド
cloneメソッドは、新しい配列を作り、その配列に同じ要素への参照をコピーする。
大元の変数の参照先は新しく生成されるが、要素の参照先は共有される。
int[][] a = {{1, 2}, {3, 4}};
int[][] b = a.clone();
int[] c = a[0].clone();
System.out.prinln(a[0] == b[0] && a[1] == b[1]); // true: 要素の参照先は共有される
System.out.prinln(a == b); // false: 配列自体は新しく生成される
System.out.prinln(c == a[0]); // false: 配列自体は新しく生成される
6. インスタンスとメソッド
ガベージコレクション
インスタンスはメモリ上に生成されるため、メモリ空間を使い切ってしまいかねない。
JAVAではガベージコレクタ
が使わなくなったインスタンスをメモリ上から探して破棄し、メモリを解放する。
これをガベージコレクション
と呼ぶ。
GCはプログラマが制御することはできず、JVMがタイミングを判断し実行する。
gcメソッドというGCを促すメソッドもあるが、必ずGCが起こるものではない。
GCが起こる代表的なタイミングは『nullが代入されたとき』。
Object a = new Object(); // ①
Object b = new Object(); // ②
Object c = b; // ②
a = null; // メモリ①への参照がなくなるのでGC対象になる。
b = null; // メモリ②はまだ`c`から参照されているのでGC対象にならない。
Staticフィールド
クラスファイルのロード後、
staticフィールド・staticメソッドは『static領域』に、
それ以外の部分は『ヒープ領域』に保存される。
『static領域』のフィールドやメソッドはインスタンスを生成しなくても使用できる
。
インスタンスが生成されるときに使用される情報は『ヒープ領域』のものが使用される。
※ インスタンスから呼び出すか直接呼び出すかにかかわらず、
staticフィールドは同一の値が使用される(☟)
public class Sample {
static int num = 0;
}
class Main {
public static void main(String[] args) {
System.out.println(Sample.num); // <- 0
Sample.num = 10;
System.out.println(Sample.num); // <- 10
Sample s = new Sample();
s.num += 10;
System.out.println(Sample.num); // <- 20
}
}
可変長引数
引数の数を自由に変更できる引数のこと。
- 引数の型の直後に
...
をつける。 - シグニチャの最後に配置する。
- 渡された引数は配列として扱われる。
void sample(String value, int... num) {
for (int i = 0; i < num.length; i++) {
System.out.println(num[i]);
}
}
オーバーロード
メソッドの多重定義
。
JAVAはメソッドをメソッド名とシグニチャ(引数の数・型・順番)で判断している。
メソッドのシグニチャを変えることで、メソッドの多重定義を行える。
※ 戻り値の型はメソッドの判断に使用していない。
コンストラクタ
- メソッド名をクラス名と同じにすること。
- 戻り値型は記述できない。
- newと一緒にしか使えない。
※ コンストラクタが明示的に宣言されていない場合、コンパイラによってデフォルトコンストラクタが自動生成される。
※ コンストラクタが明示的に宣言されている場合、デフォルトコンストラクタの自動生成は行われない。
※ ほかのコンストラクタをコンストラクタ内から呼び出す場合、this()
を用いて、処理内の最初の行で呼び出す。
(super()
を呼び出した後はthis()
でほかのコンストラクタを呼び出すことはできない。)
※ アクセス修飾子に制限はない。
※ クラス名と同名のコンストラクタ以外のメソッドは問題なく宣言できる。
※ 初期化されていないfinalフィールドがある場合、コンストラクタ内で初期化する必要がある。
初期化子・static初期化子
クラスのロードからコード実行までの流れ
- クラスファイルのロード
- static領域に情報を格納
- static初期化子呼び出し
- (インスタンス生成 → )初期化子呼び出し
- コンストラクタ呼び出し( → コード実行)
public class Sample {
static int num = 0;
static {
num += 1;
}
{
num += 1;
}
Sample() {
}
Sample(int num) {
this.num += num;
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Sample.num); // 1 (クラスのロード時にstatic初期化子で1インクリメント)
Sample s1 = new Sample();
System.out.println(Sample.num); // 2 (インスタンス生成時に初期化子で1インクリメント)
Sample s2 = new Sample(1);
System.out.println(Sample.num); // 4 (インスタンス生成時に初期化子とコンストラクタで1ずつインクリメント)
}
}
変数の初期化
- 初期化されていない
ローカル変数
を参照するとコンパイルエラーとなる。 -
フィールド
は初期化されていなくてもデフォルト値で初期化されているので参照しても問題ない。
7. 継承・インタフェース・抽象クラス
継承で引き継がれないもの
- コンストラクタ
- privateフィールドおよびprivateメソッド
※ final
で宣言されたクラスは継承できない。
インタフェースについて
- クラスから『型』だけを取り出したもの。『扱い方』を定義する。
- 抽象メソッドはすべて
public
,abstract
で修飾される(private
,protected
は付けられない)。 - 多重実現が可能
- フィールドは
static final
のみ定義可能 -
default
修飾子を付与することでデフォルトメソッドの定義が可能。(※)java.lang.Object
内のメソッドのオーバーライドは不可 - static, privateメソッドも実装可能
- デフォルトメソッドをオーバーライドしたメソッドから元のデフォルトメソッドを呼び出す場合は『インタフェース名.super.メソッド名』として呼び出す。
- 同じシグニチャのデフォルトメソッドが複数存在する場合、どのデフォルトメソッドをオーバーライドするかを宣言する必要がある。
オーバーライドのルール
- シグニチャ(引数の数・型・順番)が同じであること。
- 戻り値は同じかそのサブクラス(共変戻り値)であること。
- アクセス修飾子は同じか、緩いものであること。
- throwする例外クラスは同じかそのサブクラスであること。
class Sample {
Sample init(int i, String s) throws RuntimeException {
// some code
}
}
class SubSample extends Sample {
@Override
public SubSample init(int i, String s) throws NullPointerException{
// 戻り値の型、アクセス修飾子、シグニチャ、例外 いずれもOK
}
@Override
private Sample init(int i, String s) throws NullPointerException{
// アクセス修飾子をより厳しいものにはできない。
}
@Override
public SubSample init(int i, String s) throws Exception{
// 元のメソッドがスローしない例外を、オーバーライドしたメソッドからスローすることはできない。
}
}
- ジェネリクスの型パラメータは完全一致しなければオーバーライドとみなされない。
class Sample {
List<Number> test(Set<Number> s) {
// some code
}
}
class SubSample extends Sample {
@Override
List<Number> test(Set<Number> s) {
// 引数、戻り値とも、ジェネリクスの型パラメータは完全一致させる。
}
@Override
List<Integer> test(Set<Integer> s) {
// ERROR: ジェネリクスの型パラメータは変更できない。
}
@Override
ArrayList<Number> test(Set<Number> s) {
// 型パラメータが一致していれば共変戻り値の仕組みは利用できる。
}
@Override
List<Number> test(TreeSet<Number> s) {
// Error: `@Override`を外せばオーバーロードとみなされコンパイルエラーにはならない。
}
}
親子クラスに存在する同名フィールドの呼び出し
- フィールドを参照した場合:変数の型に従う
- メソッドを呼び出した場合:メソッドの指示に従う
『どの変数を使うか』はコンパイル時に決定し、
『どのメソッドを使うか』は実行時に決定する。
8. 関数型インタフェース、ラムダ式
関数型インタフェース
抽象メソッドを1つだけ持つインタフェース
ラムダ式
関数型インタフェースの実装を簡易に行う記述方法
public interface Sample {
void sample(String val);
}
public class Main {
public static void main(String[] args) {
Sample s = (val) -> {
System.out.println("Hello, " + val);
};
s.sample("Qiita"); // "Hello, Qiita"
}
}
ラムダ式の記述省略
Function f = (int a, int b) -> { return a * b; }; // 基本形
Function f = (a, b) -> { return a * b; }; // 引数の型を省略可
Function f = a -> { return a * b; }; // 引数が1つだけのとき()を省略可
Function f = (a, b) -> a * b; // 処理が1つだけのとき{}とreturnを省略可(※)
(※){}
とreturn
はどちらも省略すること。どちらか一方だけを省略することはできない。
ラムダ式からアクセスできる変数
ラムダ式からアクセスする変数は、final
で宣言されているか、実質的にfinal
である必要がある。
int i = 0;
Supplier<Integer> s = () -> i; // `i`が`final`でないのでエラー
同じブロック内のローカル変数と同じ変数名は使用できない。
int x = 0;
Consumer<Integer> c = (x) -> System.out.println(x); // xはローカル変数で使用されているのでコンパイルエラー
メソッド参照
関数型インタフェースの実装に、既存のメソッドを使用するための記述方法。
クラスメソッドを使用する場合 ⇒ クラス名::メソッド名
インスタンスメソッドを使用する場合 ⇒ インスタンス名::メソッド名
※ メソッド名の後の()
は付けないこと
public class Main {
public static void main(String[] args) {
Consumer<String> cClass = Main::printClass; // クラスメソッドを使用
Main main = new Main();
Consumer<String> cInstance = main::printInstance; // インスタンスメソッドを使用
}
public static void printClass(String str) {
System.out.println(str);
}
public void printInstance(String str) {
System.out.println(str);
}
}
9. API
java.lang.Math
System.out.println(Math.pow(4, 2)); // 16.0(累乗)
System.out.println(Math.sqrt(3)); // 1.7320508075688772(平方根)
java.util.Comparator, java.lang.Comparable
- 並べ替え対象のクラスがjava.lang.Comparable.compareTo()を実装する。
- java.util.Comparator.compareを実装するクラスを定義する。
- 第一引数を前にするときは
-1
を、第二引数を前にするときは1
を、変えないときは0を返す。
java.time.LocalDate
- immutableなオブジェクト
- 『月』が0からではなく1から始まる
- 日付操作の便利なメソッドが追加された
ジェネリクス
- AtrrayListで扱う型を制限するための機能
- 型推論が使用できる
List a = new ArrayList(); // Object型を扱える
List b = new ArrayList<>(); // 型推論が行われる
List c = new ArrayList<String>(); // String型を扱える
List<String> d = new ArrayList(); // String型を扱える
List<String> e = new ArrayList<>(); // String型を扱える
List<String> f = new ArrayList<String>(); // String型を扱える
固定長のListの生成
var a = List.of(1, 2, 3);
var b = Arrays.asList(new Integer[] {1, 2, 3});
11.モジュールシステム
相互依存
コンパイラは、requires
で指定されたモジュールのmodule-info.javaを探し、さらにそのモジュールが利用する他のモジュールを探す。
下のように、複数のモジュールが相互に利用しあう相互依存の状態になると、設定ファイルの確認でループが発生するため、コンパイルエラーとなる。
module A {
requires B;
}
module B {
requires A;
}