前提
- 投稿者は普段、RubyやJSを書いているのでC++やJava等、ルールがきっちりした静的型付け言語に触れていない。
- その視点でJavaの印象的だった点について感想を述べていく記事になります
文字列の比較方法
こう書きたいが
if (str == "hogehoge")...
こうらしい
if (str.equals("hogehoge"))...
理由
equals()
はアドレスが異なっても同じ内容であるかどうか(等価)を判定するメソッド。
String型は参照型であり、二つのString型の変数は同じアドレスを指していないから。
キャスト演算子
- ()をつけると強制的な型変換を行える
public class Main {
public static void main(String[] args) {
int age = (int)3.12
System.out.prinln(age) // 3
}
}
配列の作成
配列変数を作成してから配列要素を作成
int[] scores;
scores = new int[5];
// 配列変数の作成と初期値の代入をまとめて行う省略記法
int[] scores = new int[5] {10, 20, 30, 40, 50};
int[] scores = {10, 20, 30, 40, 50};
拡張for文
配列要素を一つずつ取り出すループを簡単に書くための特殊なfor文
ループが一周するたびに「任意の変数名」に指定した変数に配列の要素の内容が格納される
int[] scores = { 10, 20, 30, 40, 50 };
for(int value : scores) {
System.out.println(value);
};
ガベージコレクション
- 本来ならば、使用したメモリの解放をプログラムで明示的に指示しなければならないが、実行中のどの変数からも参照されなくなったメモリ領域を探し出して開放してくれる
値渡しと参照渡し
変数の種類:
- 値型変数
- 値そのものを保持する
- 参照型変数
- 値(オブジェクト)そのものではなくオブジェクトへの参照を変数の値として保持する
関数引数の種類:
- 実引数
- 呼出し元の引数
- 直値を渡すこともできるし、変数を渡すこともできる
- 仮引数
- 呼出し先関数の引数(変数)
関数引数への変数の渡し方:
- 値渡し
- 変数が保持している値を渡す
- 仮引数に新たな値を代入しても呼び出し元変数に影響しない
- 参照の値渡し
- 参照型変数の値である参照を渡してオブジェクトを共有する
- 呼び出し先でオブジェクトに加えた影響が呼出し元オブジェクトにも影響する
- 仮引数に新たなオブジェクトを代入しても呼出し元変数に影響しない
- 参照渡し
- 変数そのものを渡して変数を共有する(実引数と仮引数が同一変数)
- 呼び出し先でオブジェクトに加えた影響が呼出し元オブジェクトにも影響する
- 仮引数に新たなオブジェクトを代入すると呼出し元変数に代入される
- Javaではできない (C++, C#, PHPなどではできる)
APIで提供されるパッケージ
- java.langパッケージに属するクラスは頻繁に利用される者が多いので、自動的にインポートされる。
System.out.println("hogehoge")
// 正確には以下の通り
java.lang.System.out.println("hogehoge")
クラス型変数を用いる理由
- 仮想世界に存在しうる同名インスタンスの中から、特定の一つのインスタンスをプログラムとして識別するため
javaの仮想世界(JVM)の正体
- 結論:CPUのメモリ領域
- Javaプログラムを実行する際、大量にメモリ領域を使って準備する領域で、heapと呼ばれる
- インスタンスとはheapの中に確保されたメモリ領域
参照型
- クラス型と配列型の別名
- どちらも宣言した変数にインスタンス、配列の先頭番地を示すアドレスが格納されている
- 実はString型もクラス型
- java.lang.Stringのため定義せずとも使えるようになっている
int[] scores = new int[5] {10, 20, 30, 40, 50};
Hero h = new Hero();
// 上記はarray, h共に配列、インスタンスの先頭番地を示すアドレスが入っていて、それを参照している
参照イメージ
コンストラクタ
オーバーロード可能
- new するときに、引数が一致したコンストラクタが動く
public class Hero {
String name;
int hp;
...
public Hero(String name) {
this.hp = 100;
this.name = name;
}
public Hero() {
this.hp = 100;
this.name = "ダミー";
}
}
特例
- クラスに一つもコンストラクタが定義されていない場合に限って、引数なし、処理内容なしのデフォルトコンストラクタが、コンパイル時に自動的に追加される
同一クラス内の別コンストラクタ呼び出し
public class Hero {
String name;
int hp;
...
public Hero(String name) {
this.hp = 100;
this.name = name;
}
public Hero() {
this("ダミー"); //上のコンストラクタを呼び出すよう、JVMに呼び出している
}
}
継承できないクラス
- finalがついているclassは継承できない
public final class Main {
...
}
- 継承は許可するが、メソッドのオーバーライドを禁止したい場合は、メソッドにfinalをつける
public class Hero {
public final void run() {
...
}
}
super
- 今より一つ内側のインスタンス(親インスタンス部分のメソッドやフィールド等)にアクセスすることができる
public class SuperHero extends Hero {
public void attack (Matango m) {
super.attack(m);
}
...
}
全てのコンストラクタは、先頭で親クラスのコンストラクタを呼び出さなければならない
public SuperHero() {
super(); // もし書かれてない場合は自動で挿入される
...
}
is-aの原則
- 子クラスは親クラスの一種である。
抽象メソッド abstract
- 「宣言すべきであるが、具体的にどう動くか、内容がどうなるかまでは現時点では確定できないので、メソッド内部の処理はここでは記載しない」という表明ができる
- 抽象メソッドを1つでも含むクラスは必ず抽象クラスにしなければならない
- 抽象メソッドの未定内容を確定させることを実装すると言う
public abstract class Character {
...
public abstract void attack(Matango m);
}
利点
- 「現時点で処理内容を確定できないメソッド」なのか、「何もしないメソッド」なのか、区別がつかないと言うことを解決する
抽象クラス
- newによるインスタンス化を禁止する
public abstract class Character {
...
}
利点
- 継承元の親クラスとして作ったのに、間違えてインスタンス作成されることがない
Interface
- 抽象クラスの親玉みたいなクラス
- 全てのメソッドが抽象メソッドになる
- 基本的にフィールドを一つも持たない
- 定数だけは宣言が許される。
- フィールド宣言した場合自動的に定数になる
- 定数だけは宣言が許される。
public interface Creature {
void run(); // public abstractを省略しても大丈夫
double Pi = 3.141592 // 自動的にpublic static finalが付与される
}
- インターフェースを継承したクラスの定義は、
implements
をつける
public interface CleaningService {
Shirt washShirt(Shirt s);
...
}
public class KyotoCleaningShop implements CleaningService {
String ownerName;
String address;
String phone;
}
- インターフェースを親として多重継承させることができる
// Hero, princess, Characterをinterfaceとして定義している場合
public class PrincessHero
implements Hero, Princess, Character {
...
}
- インターフェースは継承できるが、その場合実装ではなく拡張する
java.lang.Objectクラス
- クラス定義時、extendsで親クラスを定義しなければ、デフォルトでjava.lang.Objectクラスを親クラスとして継承したとみなされる。
staticキーワード
フィールド
- 同じクラスから生成されたインスタンスでフィールドを共有したい場合につける
- インスタンスを一つも生み出さずに、箱が利用可能になることから、クラス変数とも言われる
メソッド
- メソッド自体がクラスに属するようになる
API活用
日付、時刻を扱う方法
エポック(1970年01/01 00:00:00)から何ミリ秒経過したかをlong型で表現する方法
- jvmが扱いやすい
- long型の情報が日付を表すのか、一目で分かりにくい場合がある
java.util.Dateクラスを使う方法
- long型を内部で保持しているだけのクラスだが、インスタンスを作成すれば一目でDate関連の値だと分かる
- 最初は取り上げるつもりはなかったが、上記の背景を鑑みて、ピックアップした
ジェネリクス
- <>記号を使って、格納するインスタンスの型を指定する
ArrayList<String> names = new ArrayList<String>();
ラッパークラス
- String以外のデータ型をインスタンス化できるようにしたもの
オートボクシング
- Javaにある機能で、int型等のプリミティブ型をインスタンス化して、ArrayListクラスで使用できるようにする機能
オートアンボクシング
- オートボクシングとは反対に、ラッパークラスからプリミティブ型への変換を行う機能
イテレータ
- リストの中身を一つずつ取り出す方法
- java.util.Iteratorクラスからインスタンス化して使う
- イテレータインスタンスはnewしない
- iteratorメソッドを呼び出すことで、リストの先頭を指したイテレータインスタンスを得ることができる
package study_java;
import java.util.ArrayList;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<String>();
names.add("a");
names.add("b");
names.add("c");
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String e = it.next();
System.out.println(e);
};
}
}
// a
// b
// c
LinkedList
- AllayListのように、配列を操作できるクラス
AllayListtの違い
- 要素の挿入・削除が速い
- get()による取得が遅い
ArrayListもLinkedListもjava.util.Listインターフェースを実装している
- 以下のような実装も可能
List<String> list = new ArrayList<String>();
- listを実装するときは、以下の構成にするのが「通」らしい
- 右辺を具体化されたクラス型
- 左辺を曖昧なインターフェース名
- ざっくりListとして扱うと、どんなリストでも受け付けることができる
hashSetクラス
- Setインターフェースを実装するコレクションで最も一般的なクラス
- 他にもSet関連クラスはあるが、いずれも複数の情報を重複なく格納する**集合(セット)**というデータ構造を実現するためのもの
package study_java;
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
// TODO 自動生成されたメソッド・スタブ
Set<String> colors = new HashSet<String>();
colors.add("red");
colors.add("blue");
colors.add("yellow");
colors.add("red"); // 重複しているが、追加されない(例外を出さない)
System.out.println(colors.size()); // 3
}
}
順序が保証されるSetバリエーション
- TreeSet
- 値を格納した順序に整列
- LinkedHashSet
- 自然順序づけで整列(辞書順)
例外
- 開発者がいちいち例外処理を細部まで書かなくて良いように、以下のクラスを継承した様々な例外クラスがある
Error系
- 回復見込みがない致命的な状況
Exception系
- 想定するべき例外的状況
- この例外が発生しそうな処理を書く場合、try-catch文を書かないとコンパイルエラーになる
import java.io.*
public class Main {
public static void main(String[] args) {
FileWriter fw = new FileWriter("data.txt"); //try-catch文を書く必要がある
...
}
}
RuntimeException系
- 必ずしも発生を想定するべきでない例外的状況
- 変数null, 添え字不正など、対処していてはキリがないもの
try-catch構文
- 後片付け処理には必ずfinnalyを実行
try-with-resource文
- ここで開かれたファイルやDB接続は、自動的にfinallyなしでcloseメソッドが呼び出される
import java.io.*
public class Main {
public static void main(String[] args) {
try (FileWriter fw = new FileWriter("data.txt");) {
fw.write("hello");
} catch(Exeption e) {
System.out.println("何らかの例外発生");
}
}
}
スロー宣言
-
例外の伝播(呼び出したメソッドで例外が発生するが、例外処理をしていないため、呼び出し元に例外が投げられること)を防ぐために、呼び出されるメソッドに
throws
をつける方法がある。- throwをつけたメソッドはException系のエラーでもtry-catch文を書かなくて良いというエラーがある。
- 「私は例外を発生させるかも知れませんが、呼び出し元が例外処理を行います」という宣言を行う。
public class Main {
public static void sub() throws IOException {
try (FileWriter fw = new FileWriter("data.txt");) {
fw.write("hello");
} catch(Exeption e) {
System.out.println("何らかの例外発生");
}
}
}
例外を意図的に発生させる
- 監視中のjvmに例外を報告する
- 例外を投げる、送出するとも言う
public class Person {
int age;
public static void setAge(int Age) {
if (age < 0) {
throw new IlligalArgumentException
("年齢は0以上です")
}
}
}
オリジナル例外の定義
- 開発者独自の例外を作成することもできる
public class UnsupportedMusicFileException extends Exception {
// エラーメッセージを受け取るコンストラクタ
public void UnsupportedMusicFileException(String msg) {
super(msg);
}
}
ストリーム
- ファイルを少しずつ読んだり、書いたりするための機能
- 一度にファイルを一気に読み込んで、メモリ不足になるという事態を防ぐことができる
まとめ
- 実務で使ったことはないが、「メソッドに例外処理が必要だったり、型が必要だったり」と、
- Ruby、JSよりも考えることが多いが、
- 処理の流れや型を意識せざるを得ないのでプログラマとしてJavaの実務経験を積むとスキルアップが見込めそうだと思った。
- 感想あればお気軽にコメント下さい。