- Published on
Java基礎
- Authors
- Name
- Kikusan
- 基礎構文
- 変数・定数宣言
- 演算子
- if
- switch
- while dowhile
- for
- クラス構造
- 継承関係
- 返り値とオーバーロード
- 可変長引数メソッド
- ネストしたクラス
- 抽象クラスとインターフェース
- enum型
- アクセス修飾子とstatic
- アクセス修飾子
- static
- コレクション
- List
- Set
- Map
- Queue
- Deque
- 拡張for文
- 例外処理
- マルチキャッチ
- チェック例外
- assert
- try-with-resource
- Format
- Date
- Number
- Optional
- 関数型インターフェースとラムダ式、StreamAPI
- 関数型インタフェース
- StreamAPI
- 中間操作
- 終端処理
- プリミティブ型のStream
- 並列ストリーム
- 副作用のある処理(ステートフル)
- Collectors
- 使用例
- ラムダ式の省略
- マルチスレッド
- Thread
- Runnable
- Executorフレームワーク
- ExecutorService
- ScheduledExecutorService
- Future
- Callable
- CyclicBarrier
- 複数スレッドの同期化処理
- 排他制御
- java.util.concurrentパッケージ
- ファイルとストリーム
- Serializable
- Comparable
- nioパッケージ
- Path
- Files
- jdbc
- アノテーション
- 総称型とワイルドカード
- 総称型
- ワイルドカード
- ローカライズ
- Locale
- Properties
- ResourceBundle
- モジュールシステム
- SPI
- ServiceLoader
- アプリケーション側
- ライブラリ側
- セキュリティ対策
基礎構文
変数・定数宣言
String str = null; //変数
final String STR = "msg"; //定数
long l = 1111111111L; //long型は最後にLで宣言
float f = 1111.111F; //float型は最後にFで宣言
double d = 1111.111; //小数点はdoubleになる
char c = 'c'; //char型は''で
str = "a\na\ta"; //\n:改行 \t:タブ
String[] ss = {"a", "b"}; // 配列
//参照型としてintを宣言する
Integer I = new Integer(32);
int n = I.intValue();
Integer I2 = 2; // auto boxing
int i = 1; // auto unboxing
型 | 初期値 |
---|---|
参照型 | null |
boolean | false |
char | \u0000 |
byte,short,int,long | 0 |
float,double | 0.0 |
演算子
演算子には優先順位があり、表の上から優先順位が高い。
演算子 | 式の結合始点(右は右辺から実行) |
---|---|
++(後) --(後) (キャスト) | 左 |
! ++(前) --(前) | 右 |
new | 右 |
* / % | 左 |
+ - | 左 |
< > <= >= instanceof | 左 |
== != | 左 |
& ^ | | 左 |
&& || | 左 |
?: | 右 |
*= /= %= | 右 |
if
if (条件式) {
//処理
} else if (条件式) {
//処理
} else {
//処理
}
ちなみに を省略することもできて、
その場合は if句と else句の下一行が実行されます。
switch
String singou = "red";
switch (singou) {
case "red":
//処理
break;
case "yellow":
//処理
break;
case "blue":
//処理
break;
default:
//処理
}
defaultはどのケースでもないとき実行される。
breakがないとその下がすべて実行される。
使える型は、byte, short, int, char, enum, String
while dowhile
while (条件式) {
//処理
}
do {
//処理
} while (条件式);
条件式が真の間処理が実行される。
処理の中で条件式に使われる変数をインクリメントするのが鉄板。
while と dowhile の違いは判定の場所。
for
for (int i=0; i<5; i++){
if (i < 2) {
continue; //次のループ
} else {
break; //ループ終了
}
}
for(①カウンター宣言; ②条件式; ③カウンタ変数の増減) と書く。
①、②,③はそれぞれ省略ができる。 for(;;); も間違いではない。
※java8ではfor文は実は非推奨なのだそうな、、、
ちなみに、labelを貼ると、その階層まで一気にbreakできる。
class Nest {
public static void main(String[] args) {
int num[][] = {
{0, 1, 2},
{3, 4, 5},
{6, 7, 8}
};
int i = 0;
label:
while(true) {
int j = 0;
while(j < 3) {
System.out.print(num[i][j]);
j++;
if(i == 1) break label;
}
i++;
}
}
}
クラス構造
継承関係
public class Animal {
//コンストラクタ
Animal(){
System.out.println("new Animal created");
}
//引数付きコンストラクタ
Animal(String species){
System.out.println("new " + species + " created");
}
void animalMethod() {
System.out.println("this is animal method!");
}
void characteristic() {
System.out.println("I'm alive!");
}
}
//インターフェース宣言
interface Breath {
//メンバ変数は勝手にstatic finalになる
String breath = "Breathing!";
//インターフェースのメソッドは本文なし
void breath(String breath);
}
次にAnimalクラスとインターフェースを実装した子クラス。
//継承とインターフェース実装
public class Dog extends Animal implements Breath{
//コンストラクタは暗黙的に super() されるが、
//明示することで親の引数付きコンストラクタを指定できる。
Dog(){
super("Dog");
}
//オーバーライド
//インターフェースの抽象メソッドはオーバーライドしないとエラー。
@Override
public void breath(String breath) {
System.out.println(breath);
}
//オーバーライド
@Override
public void characteristic() {
System.out.println("I have 4 legs!");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog(); //①new Dog created
dog.animalMethod(); //②this is animal method!
//インターフェースのメンバ変数はすべてstatic final
dog.breath(Breath.breath); //③Breathing!
dog.characteristic(); //④I have 4 legs!
Animal dogA = new Dog(); //⑤new Dog created
//dogA.breath(); //⑥
dogA.characteristic(); //⑦I have 4 legs!
}
}
- ①はDogインスタンスの生成で、Dogクラスのコンストラクタが呼ばれている。
- ②は継承されたメソッド。
- ③はインターフェースのDogクラスでオーバーライドしたメソッド。
- ④はAnimalクラスのDogクラスでオーバーライドしたメソッド。
- ⑤はDogインスタンスをAnimal型変数に入れている。(逆はできない)
- ⑥はAnimal型なので、breathは実装されておらず使用できないの意。
- ⑦はAnimal型だがnewしたのはDogで、アップキャストされてもDogのcharacteristicになる。
返り値とオーバーロード
返り値を返すメソッドは宣言に返す型を頭につけ、return句を必ず書く。
オーバーロードは引数の数または型が違う、同じ名前のメソッドを作ること。
class Calc {
int sum(int a, int b) {
return a + b;
}
//オーバーロード
int sum(int a, int b, int c) {
return a + b + c;
}
}
可変長引数メソッド
void variableLength(String... colors){
for (String c : colors) {
System.out.println(c);
}
}
ネストしたクラス
インナークラスやローカルクラスといった、情報隠蔽のためのクラス。
public | protected | private | abstract | final | static | |
---|---|---|---|---|---|---|
インナークラス | ○ | ○ | ○ | ○ | ○ | ✖️ |
staticインナークラス | ○ | ○ | ○ | ○ | ○ | ○ |
ローカルクラス | ✖️ | ✖️ | ✖️ | ○ | ○ | ✖️ |
匿名クラス | ✖️ | ✖️ | ✖️ | ✖️ | ✖️ | ✖️ |
/**
* アウタークラス
* インナークラスとは相互にprivateなメンバでもアクセスできる
*/
public class Outer {
private String msg = "hello from outer!";
private static String stMsg = "hello static from outer!";
public static void main(String[] args) {
Outer out = new Outer();
// インナークラスのコンストラクタはアウタークラスのインスタンスが必要
Inner in = out.new Inner();
// staticインナークラスのコンストラクタ Comparatorなどの実装クラスを定義しておくときとかに使える
StaticInner sIn = new StaticInner();
in.inner(); // hello from outer! this is inner
sIn.staticInner(); // hello static from outer! this is static inner
StaticInner.staticInStatic(); // this is static in static
out.test(); // hello from outer! this is no name inner
// this is no name
// this is local
}
/**
* インナークラス
* アウタークラスのメンバにはアクセスできる
*/
public class Inner {
private String inMsg = " this is inner";
void inner() {
System.out.println(msg + inMsg);
}
}
/**
* static インナークラス
* インナークラスにはstaticをつけることができ、普通にコンストラクタを呼べる。
*/
static class StaticInner {
void staticInner() {
// アウターのクラス変数なら使える
System.out.println(stMsg + " this is static inner");
}
static void staticInStatic() {
System.out.println("this is static in static");
}
}
/**
* 関数内のクラスをローカルクラス、無名クラスという
*/
void test() {
/*
* 無名クラス
* インターフェイスでもクラスでも、その型の実装インスタンスを生成する。
*/
Inner i = new Inner() {
void noName() {
System.out.println("this is no name");
}
@Override
void inner() {
System.out.println(msg + " this is no name inner");
noName(); // 定義したものは宣言の中でだけ使える(Innerの定義にはnoNameは含まれないため)
}
};
i.inner();
/**
* ローカルクラス
*/
class Local {
void local() {
System.out.println("this is local");
}
}
Local l = new Local();
l.local();
}
}
抽象クラスとインターフェース
抽象クラスは子クラスと、is-a関係である。
インターフェースは子クラスと、can-do関係である。
抽象クラス | インターフェース | |
---|---|---|
アクセス修飾子 | public, protected | public |
変数の定義 | インスタンス変数,ローカル変数,クラス変数 | public static finalのクラス変数 |
継承 | 多重継承不可 | 多重継承可 |
メソッド定義 | 具体的なメソッド,抽象メソッドで子に実装を強制 | メソッドの型のみ,defaultメソッドには処理を書ける。,staticメソッドもかける。 |
コンストラクタ,イニシャライザ | 実装可 | 実装不可 |
abstract class Animal {
String name;
Animal(String name){
this.name = name;
}
//具体的な処理を書ける。
void sleep(){
System.out.println("sleeping!");
}
//abstractメソッドは子クラスに実装を強制
abstract void speak();
}
public interface MyInterface {
/**
* java8から、interface でもstatic関数は実装できる
* ただし呼び出すには必ず、Static.staticFunc()とし、
* 実装クラス.staticFunc()では呼び出せないし、オーバーライドもできない。
* (インスタンスの挙動を決めるのがオーバーライドだから当たり前)
*/
static void staticFunc() {
System.out.println("this is static func");
}
/**
* default句で実装可能
* なお、スーパークラスを持つクラスにdefault句を持つインターフェイスを継承し、
* 同じシグニチャを多重継承してしまった場合、スーパークラスが優先される
*/
default void defaultFunc() {
System.out.println(add("this is", " default func"));
}
/**
* privateなinterface関数は実装が可能(default関数で使用するため)
*/
private String add(String a, String b) {
return a + b;
}
}
enum型
//列挙型 プログラム上では一つのクラスとして扱われる
enum Output {
OK, NG,
}
public class Enum {
public static void main(String[] args) {
Output out;
out = Output.NG;
switch (out) {
case OK:
System.out.println("OK!");
System.out.println(out.ordinal()); // 0
break;
case NG:
System.out.println("NG!");
System.out.println(out.ordinal()); // 1
break;
}
}
}
アクセス修飾子とstatic
アクセス修飾子
javaのアクセス修飾子は4つ。
アクセス修飾子 | 同一クラス | 同一パッケージ | サブクラス | すべて |
---|---|---|---|---|
public | 〇 | 〇 | 〇 | 〇 |
protected | 〇 | 〇 | 〇 | ー |
デフォルト | 〇 | 〇 | ー | ー |
private | 〇 | ー | ー | ー |
これらを組み合わせることで データ隠蔽・カプセル化 ができる。
- データ隠蔽:メンバ(属性や操作)を公開と非公開に分けて、外部から属性への直接アクセスを避ける。
- カプセル化:オブジェクト内に属性とその属性にアクセスする操作を一つにまとめて持たせること。
public class Parser {
private String str = null;
public String getStr() {
return str;
}
private void setStr(String param) {
str = param;
}
}
static
変数やメソッドに static を付けることで、クラスフィールド、クラスメソッドの実装ができる。
class StaticClass {
//staticメンバ
static String staticStr;
static String getStaticStr(){
return staticStr;
}
//インスタンスメンバ
String instanceStr;
String getInstatnceStr() {
return instanceStr;
}
//staticイニシャライザ
static {
StaticClass.staticStr = "staticStr";
}
//インスタンスイニシャライザ
{
instanceStr = "instanceStr";
}
}
class Main {
public static void main(String[] args) {
//static参照
StaticClass.staticStr = StaticClass.staticStr + "Main";
System.out.println(StaticClass.getStaticStr()); //staticStrMain
//インスタンス参照
StaticClass stsCls = new StaticClass();
stsCls.instanceStr = stsCls.instanceStr + "Main";
System.out.println(stsCls.instanceStr); //instanceStrMain
}
}
イニシャライザは変数の初期化ができる。
ちなみに、イニシャライザはインスタンス生成の前、コンストラクタはインスタンス生成の後に実行される。
静的要素(staticメンバ)はアプリケーションが停止するまで保持されるのに対し、
インスタンスメンバはインスタンスが殺されるまでしか保持されない。
コレクション
コレクションは型を決めて、その型の値を連続して持つクラス。
List、Map、Set, Queue, Dequeがあり、それぞれ特徴がある。
(クラスはこの3つの下にあり、List,Map,Setはインターフェース)
List
順番がある。
ArrayListとLinkedListがある。
ArrayList: 検索がはやい。
LinkedList: 追加削除がはやい。
List<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);
System.out.println(list.get(1)); //20
Set
値の重複を許さない。
HashSetとTreeSet、LinkedHashSetがある。
HashSet: 順番がない。
TreeSet: 値順にソートされる。
LinkedHashSet: 追加された順にソートされる。
Set<Integer> hashSet = new HashSet<Integer>();
hashSet.add(10);
hashSet.add(20);
hashSet.add(10);
System.out.println(hashSet.size()); //2
Map
keyと値を持つ。
HashMapとTreeMap、LinkedHashMapがある。
HashMap: 順番なし。
TreeMap: keyの順番にソート。
LinkedHashMap: 追加順にソート。
Map <String, Integer> hashMap = new HashMap<>();
hashMap.put("sato", 50);
hashMap.put("ito", 60);
System.out.println(hashMap.get("sato")); //50
Queue
FIFOでデータを扱う。
Queue<String> queue = new ArrayDeque<>();
queue.add("A");
queue.add("B");
System.out.println(queue.poll()); // A
Deque
FIFOに加えてLIFOでもデータを扱える。
Deque<String> deque = new ArrayDeque<>();
deque.addFirst("A");
deque.addLast("B");
System.out.println(deque.pollLast()); // B
拡張for文
for(中身の型 中身の変数名 : 配列及びコレクション)
配列及びコレクションにすべての中身に処理を行うことができる。
上のコレクションを使ってみるとこうなる。
for (Integer data : list) {
System.out.println(data); //10, 20
}
//Map.Entry<K,V>はMapの中身の型(インターフェース)
//hashMap.entrySet()の戻り値の型はSet<Map.Entry<K,V>>
for (Map.Entry<String, Integer> score : hashMap.entrySet()) {
System.out.println(score.getKey() + " : " + score.getValue());
//ito : 60
//sato : 50
}
例外処理
例外処理はExceptionを使う。
例外を発生させ、catchし、加えて独自例外を発生させてみる。
public class ExceptionSample {
public static void main(String[] args) {
try {
//ここでNumberFormatException発生
String s = "No.1";
System.out.println(Integer.parseInt(s));
} catch (ArithmeticException e) {
System.err.println("ArithmeticException: " + e.getMessage());
//NumberFormatExceptionはArithmeticExceptionのサブクラスではないので
//Exceptionにcatchされる
} catch (Exception e) {
//Messageを得るときはgetMessage
System.err.println("Exception: " + e.getMessage());
} finally {
try {
//MyExceptionをthrowsさせる
getErr();
//MyExceptionをcatch
}catch(MyException e){
System.err.println(e.getMessage());
}
}
}
//自分でcatchしないときは呼び出しもとにExceptionを返す。
static void getErr() throws MyException {
throw new MyException("MyException!");
}
}
//独自Exceptionの定義はextendsで
//コンストラクタで設定できる。
class MyException extends Exception {
public MyException(String s) {
super(s);
}
}
・出力
Exception: For input string: "No.1"
MyException!
マルチキャッチ
try {
// 通常処理
} catch (AException | BException e) {
// 例外処理
}
チェック例外
- チェック例外: ソースに誤りがなくても発生する例外。catchの必要あり。
- 非チェック例外: ソースの誤り、致命的エラーによる例外。catchの必要なし。
(ソースの書き方で事前に例外を回避できるためor致命的なものはソースの問題ではないため)
クラス | 説明 |
---|---|
Throwable | 全てのエラーと例外のスーパークラス。throwができるようになる |
Error | アプリケーションがキャッチすべきでない重大な問題。そのため非チェック例外となる |
Exception | アプリケーションでキャッチされる可能性のある例外 |
RuntimeException | 非チェック例外。この他の例外はチェック例外 |
assert
java -ea Main
のように-eaをつけると仕込んでおいたassert文を実行することができる。
// 条件式がtrueならば、AssertionErrorを投げず、
// 条件式がfalseならば、与えられたメッセージを持つAssertionErrorが投げられる。
assert name != null : "name is null";
public class Main {
public static void main(String[] args) {
Item a = new Item("a", 100);
Item b = new Item("b", 120);
Item c = new Item("c", 80);
List<Item> list = Arrays.asList(a, b, c);
Collections.sort(list);
list.forEach(System.out::println);
// c $80
// a $100
// b $120
}
}
try-with-resource
closeが求められていたオブジェクトを勝手に閉じてくれるようになった。
package modern.java;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
public class With {
public static void main(String[] args) {
// try-with-resources構文を使うことでclose省略
try(
FileInputStream fi = new FileInputStream("file.csv");
InputStreamReader is = new InputStreamReader(fi);
BufferedReader br = new BufferedReader(is);
) {
String line;
int i = 0;
while ((line = br.readLine()) != null) {
if (i == 0) {
System.out.println(line);
} else {
Person p = new Person();
String[] row = line.split(",");
p.setId(row[0]);
p.setName(row[1]);
// 処理
System.out.println(p.getId() + " " + p.getName());
System.out.print("\n");
i++;
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
AutoCloseable
もしくはCloseable
の実装がされたものに使える。
発生するExceptionは、
AutoCloseable → Exceptionなのに対し、
Closeable → IOException。
Format
Date
LocalDateTimeはJava8から導入。
DateクラスやCalendarクラスは標準API。
public static void main(String[] args) {
//LocalDateTimeの基礎
LocalDateTime d = LocalDateTime.now();
System.out.println(d.getYear());
System.out.println(d.getMonth());
System.out.println(d.getDayOfMonth());
System.out.println(d.getHour());
System.out.println(d.getMinute());
System.out.println(d.getSecond());
System.out.println(d.getNano());
//特定日時を指定
d = LocalDateTime.of(2015, 12, 15, 23, 30, 59);
System.out.println(d.plusDays(20)); //2016-01-04T23:30:59
System.out.println(d.minusDays(20)); //2015-11-25T23:30:59
System.out.println(d.withDayOfMonth(20)); //2015-12-20T23:30:59
//時刻の切り捨て
LocalDateTime.of(2015, 12, 15, 23, 30, 59).truncatedTo(ChronoUnit.HOURS); // 2015-12-15T23:00
//来月1日の12時
d =
LocalDateTime.now()
.plusMonths(1)
.withDayOfMonth(1)
.withHour(12)
.truncatedTo(ChronoUnit.HOURS);
//文字列へ変換
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
d = LocalDateTime.parse("2015/12/15 23:30:59", f);
System.out.println(d.format(f)); //2015/12/15 23:30:59
f = DateTimeFormatter.ofPattern("yyyy/MM/dd");
System.out.println(d.format(f)); //2015/12/15
}
Number
ロケールに従って、通貨記号などをフォーマットできる。
NumberFormat a = NumberFormat.getInstance();
NumberFormat b = NumberFormat.getIntegerInstance();
NumberFormat c = NumberFormat.getCurrencyInstance();
NumberFormat d = NumberFormat.getPercentInstance();
double num = 1000.1;
System.out.println(a.format(num)); // 1,000.1
System.out.println(b.format(num)); // 1,000
System.out.println(c.format(num)); // ¥1,000
System.out.println(d.format(num)); // 100,010%
Optional
Java8からnullチェックを簡潔に、可読性高く行えるようになった。
package modern.java;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class Option {
public static void main(String[] args) {
Optional<Integer> optionalValue = IntegerList.get(2);
//optionalをorElseでnullのときの代替値にできる
Integer i = optionalValue.orElse(-1) + 100;
System.out.println(i);
}
}
/**
* OptionalはNullになりえる値をラップして返すのに使える
* Nullチェックを簡潔にし、Nullがあり得ることを明示できる
*/
class IntegerList {
private final static List<Integer> INTEGER_LIST = Arrays.asList(new Integer[]{1,2,null});
public static Optional<Integer> get(int index) {
//ラップして返す
return Optional.ofNullable(INTEGER_LIST.get(index));
}
}
関数型インターフェースとラムダ式、StreamAPI
関数型インタフェース
インタフェース | 戻り値型 | メソッド |
---|---|---|
Supplier<T> | T | get() |
Consumer<T> | void | accept(T) |
BiConsumer<T,U> | void | accept(T,U) |
Predicate<T> | boolean | test(T) |
BiPredicate<T,U> | boolean | test(T,U) |
Function<T,R> | R | apply(T) |
BiFunction<T,U,R> | R | apply(T,U) |
UnaryOperator<T> | T | apply(T) |
BinaryOperator<T> | T | apply(T,T) |
Runnable | void | run() |
Callable<V> | V | call() |
StreamAPI
中間操作
メソッド | 内容 |
---|---|
distinct | 重複を除く(equalsで判定) |
filter | T -> booleanに一致する要素だけのStreamを返す |
limit | n個に切り詰める |
map | 各要素にT -> Uを反映したStreamを返す |
peek | そのままStreamを返しつつ、forEachができる |
skip | 最初のn個を破棄したStreamを返す |
sorted | Comparatorの条件で並び替えができる |
終端処理
メソッド | 内容 |
---|---|
allMatch | 全ての要素がT -> booleanに一致するかを返す |
anyMatch | いずれかの要素がT -> booleanに一致するかを返す |
collect | コレクションにまとめる |
count | 要素数を返す |
findAny | 最初に処理した要素を返す(Optionalの中身は基本先頭だが、並列時にランダムになる) |
findFirst | 先頭の要素を返す(ない場合空のOptional) |
forEach | 全ての要素にT -> void を行う |
max | Comparatorの条件で最大の要素を返す |
min | Comparatorの条件で最小の要素を返す |
noneMatch | 全ての要素がT -> booleanを満たさないかを返す |
reduce | (T, T) -> Tを受け取り、逐次結合していく |
toArray | 配列にまとめる |
プリミティブ型のStream
プリミティブ型のStreamはIntStream
のように独自のものを使用する。
IntStreamのスーパーインタフェースはBaseStreamであり、Streamではない。
並列ストリーム
Collection.parallelStream
を使用することで、並列処理を使用できる。
要素の並びは実行順に関係ない。並びに準じることもできるけど、パフォーマンス的に意味ない。
副作用がある場合は、synchronizedで同期処理が必要。
副作用のある処理(ステートフル)
ラムダ式の中では、finalな外部オブジェクトのみが使える。
なお、外部オブジェクトが持っている値は変更してもコンパイルエラーにはならない。
↑のような操作を副作用のある処理という。
これは基本避けるべきで、理由は
- 関数型インタフェースは遅延実行なので、渡した後いつ実行されるかわからないから。
- 関数型インタフェースに切り出して再利用ができないから
Collectors
https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/stream/Collectors.html
// Accumulate names into a List
List<String> list = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
// Accumulate names into a TreeSet
Set<String> set = people.stream()
.map(Person::getName)
.collect(Collectors.toCollection(TreeSet::new));
// Convert elements to strings and concatenate them, separated by commas
String joined = things.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
// Compute sum of salaries of employee
int total = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary));
// Group employees by department
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
// Compute sum of salaries by department
Map<Department, Integer> totalByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.summingInt(Employee::getSalary)));
// Partition students into passing and failing
Map<Boolean, List<Student>> passingFailing = students.stream()
.collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
使用例
package modern.java;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* StreamAPIとラムダ式を理解する
* ラムダとは概念的に関数をJavaが表現できるようにしてくれたもの
* (関数=object)
* Stream操作
* 1. Stream生成
* 2. 中間操作 Streamを返す。
* 3. 終端操作 コレクションや値を返す。
* ラムダ式はすべて終端操作が実行される時点で実行される(遅延評価)
* 終端操作メソッドを行うとStreamオブジェクトはクローズされる(使えなくなる)
*/
public class StreamAPI {
public static void main(String[] args) {
System.out.println("*無名クラスDEMO*********");
anonymousClassDEMO();
System.out.println("*ラムダDEMO*************");
lambdaDEMO();
System.out.println("*ConsumerDEMO***********");
consumerDEMO();
System.out.println("*SupplierDEMO***********");
supplierDEMO();
System.out.println("*PredicateDEMO**********");
predicateDEMO();
System.out.println("*FunctionDEMO***********");
functionDEMO();
System.out.println("*終端処理DEMO***********");
streamTerminal();
System.out.println("*中間処理DEMO***********");
streamIntermediate();
}
/**
* Funcインターフェースを実装した無名クラスをローカルクラスとして宣言
*/
static void anonymousClassDEMO() {
Func f = new Func() {
public int calc(int x, int y) {
return x + y;
}
};
System.out.println(f.calc(1, 2));
}
/**
* 無名クラスをラムダ式で書く
* 左辺から型推論で型を判定、関数型インターフェースなので式まで決定。
*/
static void lambdaDEMO() {
Func f = (x, y) -> x + y;
System.out.println(f.calc(1, 2));
}
/**
* Streamオブジェクトの生成
* データの集合をラムダ式で処理するためのオブジェクト
*/
static Stream<String> streamCreate1() {
List<String> list = Arrays.asList("A", "B", "C", "D");
Stream<String> stream = list.stream();
//Filesクラスから行を持つStreamもツクレル
//直接作るには Stream<String> stream = Stream.of("A", "B");
return stream;
}
static IntStream streamCreate2() {
int[] arr1 = { 1, 2, 3, 4, 5, 1 };
IntStream stream = Arrays.stream(arr1);
//LongStream DoubleStreamもある
return stream;
}
/**
* 終端操作
*/
static void streamTerminal() {
List<String> list = Arrays.asList("A","BC","DEF","GH","I", "A");
// countはlongで個数を返す
System.out.println(list.stream().count());
// min, maxはComparatorを受け取り、次要素を比較し、与えられた式でmax, minを返す
// 文字数の小さいものは小さいという式
String max1 = list.stream().max(
(o1,o2) -> o1.length() - o2.length()
).orElse("最大値なし");
String min1 = list.stream().min(
(o1,o2) -> o1.length() - o2.length()
).orElse("最小値なし");
System.out.println(max1);
System.out.println(min1);
// 戻り型のOptionalはほしい戻り値をラップした型
// orElseメソッドで中身をとりだせ、nullである場合の戻り値を書ける。(orElseは空のOptionalかを判定している)
List<String> list2 = Arrays.asList();
String max2 = list2.stream().max(
(o1,o2) -> o1.length() - o2.length()
).orElse("最大値なし");
System.out.println(max2);
int[] arr = {1,2,3,4,5};
// sumは合計
System.out.println(Arrays.stream(arr).sum());
// averageは平均(戻り値OptionalDouble)
System.out.println(Arrays.stream(arr).average().orElse(0.0));
// reduceは全てに処理を行う
//引数1つで戻り値Optional
Optional<String> optional = list.stream().reduce((x, y) -> x + y);
System.out.println(optional.orElse("null"));
//引数2つで戻り値<T> 第一引数は初期値
String str = list.stream().reduce("Z", (x, y) -> x + y);
System.out.println(str);
//<R> r collect(Supplier<R> s, BiConsumer<R,? super T>, a, BiCumsumer<R,R> c)
// 可変オブジェクトにStream内の要素を集める
ArrayList<String> list3 =
list.stream()
.collect(
// Supplierを生成
() -> new ArrayList<String>(),
// 個々のデータをどう集めるか
(l, s) -> l.add(s),
// 複数の可変コンテナをどう結合するか(並列処理のため)
(l1, l2) -> l1.addAll(l2)
);
list3.forEach(System.out::println);
// <R,A> R collect(Collector<<? super T, A, R> c) Collectorクラスで簡潔に書ける
Set<String> set = list.stream().collect(Collectors.toSet());
set.stream().forEach(System.out::println);
}
/**
* 中間操作
*/
static void streamIntermediate() {
List<String> list = Arrays.asList("KLMN", "ABc", "dE", "FGH", "iJ");
//filterはPredicateを引数にとり、式がTrueのものを集めたStreamを返す。
list.stream()
.filter(s -> s.length() >= 3)
.forEach(System.out::println);
//mapはFunctionを引数にとり、データを加工してStreamを返す。
list.stream()
.map(s -> s.toUpperCase())
.forEach(System.out::println);
//mapで型変換
IntStream iStream =
list.stream()
.mapToInt(s -> s.length());
iStream.forEach(System.out::println);
//sortedは<T>がComparableなら自然順序でソートしたStreamを返す。
list.stream()
.sorted()
.forEach(System.out::println);
//Conparatorを引数に入れて、次の要素と比較するソート順もかける
list.stream()
.sorted((s1,s2) -> s1.length() - s2.length())
.forEach(System.out::println);
Stream<Person> stream = Stream.of(
new Person("1", "taro"),
new Person("2", "jiro"),
new Person("3", "saburo")
);
//Comparatorを引数に入れることで<T>のなにを基準にするかを書ける
stream.sorted(
Comparator.comparing(Person::getName) //.reversed() で逆順
).forEach(System.out::println);
}
/**
* メソッド参照 class::method
* 引数なしで書ける
*/
static void methodRef() {
List<Integer> l = Arrays.asList(1, 2, 3);
//l.forEach(i -> System.out.print(i));
l.forEach(System.out::print);
}
/**
* コンストラクタ参照 class::new
* オブジェクトを関数型インターフェースに渡せる
*/
static void constructorRef() {
Supplier<ArrayList<Integer>> s = ArrayList::new;
List<Integer> l = s.get();
}
/*********************************************
* java.util.functionパッケージ
********************************************/
/**
* Consumer<T>.accept(T t)
* T型を受け取り何かを行う(戻り値なし)
*/
static void consumerDEMO() {
List<String> list = Arrays.asList("A", "B", "C");
//forEachはストリームオブジェクトが持つ要素すべてになんらかの処理を行う
list.stream().forEach(System.out::println);
//Tが決まっている IntConsumer, LongConsumer, DoubleConsumerもある
//2つ引数を受け取れるBiConsumerもある
//参照型をプリミティブ型を受け取る ObjIntConsumer, ObjLongConsumer, ObjDoubleConsumerもある
}
/**
* Supplier<T>.get()
* 引数なしでT型の値を返す。
*/
static void supplierDEMO() {
Stream
// generate()がSupplierを求めているので、ラムダ式にできる
.generate(() -> Math.round(Math.random()))
//generateは無限にT型(ここではroundの戻り値)の値を生成して保持するので、limitで終わらせる
.limit(10)
.forEach(System.out::print);
System.out.print("\n");
//Tが決まっている BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplierもある
}
/**
* Predicate<T>.test(T t)
* T型を受け取りbooleanで返す
*/
static void predicateDEMO() {
List<String> list = Arrays.asList("ABC", "DE", "EGHI");
// TはString 文字が3文字かを判定する
Predicate<String> p = s -> s.length() >= 3;
// Streamすべての要素でtest
boolean all = list.stream().allMatch(p);
boolean any = list.stream().anyMatch(p);
boolean none = list.stream().noneMatch(p);
System.out.println(all);
System.out.println(any);
System.out.println(none);
// Tが決まっている IntPredicate, LongPredicate, DoublePredicateもある
// 2つの引数を受け取る BiPredicateもある
}
/**
* Function<T,R>.apply(T t)
* T型で受け取った引数を処理し、R型の値を返す
*/
static void functionDEMO() {
/**
* 〇T型のみ決まっている
* IntFunction<R>, LongFunction<R>, DoubleFunction<R>
* 〇R型のみ決まっている
* ToIntFunction<T>, ToLongFunction<T>, ToDoubleFunction<T>
* 〇T・R型が決まっている
* IntToLongFucntion, IntToDoubleFunction, LongToIntFunction, DoubleToIntFunction, DoubleToLongFunction
* 〇二つの引数を受け取る Rは戻り型
* BiFunction<T,U,R>, ToIntBiFunction<T,U>, ToLongBiFunction<T,U>, ToDoubleBiFunction<T,U>
* 〇受け取りと戻り値の型が同じとき
* UnaryOperator<T>
* 〇二つの引数を受け取り同じ型を返す <T,T,T>
* BinaryOperator<T>, IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator
*/
// iterateはUnaryOperatorを第二引数にとり,第一引数を初期値として繰り返したStreamを返す
Stream.iterate(1, x -> x * 10)
.limit(5)
.forEach(System.out::println);
}
}
/**
* Functionパッケージはこうなっている
* FunctionalInterfaceは1メソッドのインターフェースを厳密に判定
*/
@FunctionalInterface
interface Func {
int calc(int x, int y);
}
ラムダ式の省略
一定条件を満たすと、いろいろと省略ができる。
//基本形
Predicate javaChecker = (String s) -> { return s.equals("Java"); };
//引数一つのとき、型省略
Predicate javaChecker = ( s) -> { return s.equals("Java"); };
//型省略したとき、()も省略
Predicate javaChecker = s -> { return s.equals("Java"); };
//一行かつreturnがないとき、{}省略
Consumer buyer = goods -> System.out.println(goods + "を購入しました");
//returnも{}と同時省略
Predicate javaChecker = s -> s.equals("Java");
マルチスレッド
- 並行処理: シングルコアで並列化。計算自体が速くなるわけではない。
- 並列処理: マルチコアで並列化。コアが多いので計算自体が速くなる。
- スレッドセーフ: 並列処理でも競合・デッドロック・ライブロックが起きないクラス
Thread
スレッドを作成できる。
上のソースでmainが先か、subが先かはスレッド作成処理のスピード次第
public class SampleThread extends Thread {
@Override
public void run() {
System.out.println("sub");
}
}
public class Main {
public static void main(String[] args) {
Thread t = new SampleThread();
t.start(); // 新スレッドを作成
System.out.println("main");
}
}
Runnable
ThreadにはコンストラクタでRunnableを渡すことでrun()の実装をすることができる。
public class Main {
public static void main(String[] args) {
/*
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("sub");
}
});
以上の実装をRunnableが関数型インタフェースなのを利用して↓で書ける
*/
Thread t = new Thread(() -> {
System.out.println("sub");
});
t.start();
System.out.println("main");
}
}
Executorフレームワーク
スレッドプールを使用して効率的に並行処理を実現するインタフェースとクラス群
ExecutorServiceもしくはScheduledExecutorServiceを実装する。
ExecutorService
public class Main {
public static void main(String[] args) {
// シングルスレッドを生成
// 固定数の場合,Executors.newFixedThreadPool(3)など
ExecutorService exec = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
exec.submit(() -> {
System.out.println(Thread.currentThread().getId());
});
}
System.out.println(Thread.currentThread().getId());
}
}
// 1
// 13
// 13
// 13
// 13
// 13
// ↑execのスレッドプールには1つしかスレッドがないからIDは統一
public class Main {
public static void main(String[] args) {
// 必要に応じてスレッドを増減
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
exec.submit(() -> {
System.out.println(Thread.currentThread().getId());
});
}
exec.shutdown();
System.out.println(Thread.currentThread().getId());
}
}
// 13
// 15
// 14
// 16
// 17
// 1
// 必要に応じてスレッドを増減させている
// 60秒未満であればスレッドは再利用され、それ以上は破棄される新しくスレッドが作成される。
ScheduledExecutorService
- 遅延実行
public class Main {
public static void main(String[] args) {
ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
System.out.println(Thread.currentThread().getId());
// 3秒後に別スレッドで発火
// 繰り返し実行させる場合はscheduledAtFixedRate
// ↑起動時間タイミング毎のインターバルを指定できるが、前の処理が長引くとその分遅延する
// 処理時間に関係なくインターバルを一定にしたい場合はscheduleWithFixedDelay
exec.schedule(
() -> {
System.out.println(Thread.currentThread().getId());
System.out.println("finish");
exec.shutdown();
}, // 実行処理
3L, // 遅延させる時間
TimeUnit.SECONDS); // TimeUnit単位
}
}
Future
(Scheduled)ExecutorServiceにタスクを登録した戻り値として受け取り、
スレッドの結果を知ることができる。
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService exec = Executors.newSingleThreadExecutor();
// Future<戻り値の型>で結果を受け取る
Future<String> future = exec.submit(() -> {
try {
System.out.println("start");
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "finish"); // 第二引数で固定の戻り値を設定できる
exec.shutdown();
Thread.sleep(5000);
System.out.println("main start");
// .get()で結果を受け取る
// 実行自体はsubmitの段階で別スレッドで開始しているが、結果が受け取れるまでmainスレッドで待つ
String result = future.get();
System.out.println("main finish");
System.out.println(result);
// start
// main start
// end
// main finish
// finish
}
}
Callable
処理結果を返したり例外をスローするマルチスレッドタスク定義のための関数型インタフェース
Runnableでは戻り値を返すことができないので、そのためのクラス。
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(2);
List<Future<Boolean>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
// Callableをsubmitに渡す
futures.add(exec.submit(() -> Thread.currentThread().getId() % 2 == 0));
}
exec.shutdown();
System.out.println("start");
futures.forEach(future -> {
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
}
public class Main {
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(2);
List<Future<Boolean>> futures = new ArrayList<>();
for (int i = 0; i < 2; i++) {
// Callableをsubmitに渡す
futures.add(exec.submit(() -> {
if (Thread.currentThread().getId() % 2 == 0) {
throw new Exception("My Exception");
}
return true;
}));
}
exec.shutdown();
System.out.println("start");
futures.forEach(future -> {
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
// 別スレッドのExceptionはExecutionExceptionとして投げられる
} catch (ExecutionException e) {
var e2 = e.getCause();
System.out.println(e2.getClass().toString()); // class java.lang.Exception
System.out.println(e.getMessage()); // java.lang.Exception: My Exception
}
});
}
}
CyclicBarrier
複数スレッドの同期化処理
/**
* CyclicBarrierを受け取れるRunnable実装
*/
public class Task implements Runnable {
private CyclicBarrier barrier;
public Task(CyclicBarrier barrier) {
super();
this.barrier = barrier;
}
@Override
public void run() {
long id = Thread.currentThread().getId();
System.out.println("START:" + id);
int r = new Random().nextInt(10);
try {
Thread.sleep(r * 100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("END:" + id);
try {
this.barrier.await(); // barrierによって他の処理を待つ。
} catch (BrokenBarrierException | InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public class Main {
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(5);
CyclicBarrier barrier = new CyclicBarrier(
5, // awaitが5つ揃うまで待つ
() -> {
System.out.println("all done"); // Runnableの最終処理
});
for (int i = 0; i < 5; i++) {
exec.submit(new Task(barrier));
}
exec.shutdown();
//START:16
//START:14
//START:15
//START:17
//START:13
//END:16
//END:17
//END:13
//END:14
//END:15
//all done
}
}
排他制御
同時に変数を更新しないようにブロックできる。
デッドロックに注意!!!
public class ExclusionControl implements Runnable {
private int num = 100;
public int getNum() {
return num;
}
/**
* synchronizedメソッド
* このクラスのインスタンスについて、同時にこの関数が実行されずに、処理を待たせることができる
*/
public synchronized void add(int amount) {
this.num = this.num + amount;
}
@Override
public void run() {
/*
synchronizedブロック
引数で指定したインスタンスについて、synchronizedブロックの処理を同時に進めることはできない。
メソッド、ブロック両方同じインスタンスについて同期される。
*/
synchronized (this) {
add(100);
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
ExclusionControl ex = new ExclusionControl();
ExecutorService exec = Executors.newFixedThreadPool(2);
for (int i = 0; i < 10; i++) {
exec.submit(ex);
}
Thread.sleep(200);
System.out.println(ex.getNum());
exec.shutdown();
}
}
java.util.concurrentパッケージ
並行プログラミングのユーティリティ・パッケージ
単純な変数の増減であれば、java.util.concurrent.atomicのクラスを使うと原子性を保てる。
これは、読み取りから書き込みまでを同時に行うようにできているため(他の処理が割り込むラグがない)
public class AtomicValue {
private AtomicInteger num = new AtomicInteger(0);
public void add(int num) {
this.num.addAndGet(num);
}
}
他にも、CopyOnWriteArrayList
のようなスレッドセーフなクラスがある。
ReentrantLock
クラスを使用すると、ロックを取得できる。
同じインスタンスに対して同時にロックすることはできない。
public class ExclusionControl implements Runnable {
private static final ReentrantLock lock = new ReentrantLock();
private String s;
public ExclusionControl(String s) {
super();
this.s = s;
}
@Override
public void run() {
// lockとunlock 必ずfinallyでunlockをすること。
try {
lock.lock();
System.out.println("START:" + s);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("END:" + s);
} finally {
lock.unlock();
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(2);
exec.submit(new ExclusionControl("1"));
exec.submit(new ExclusionControl("2"));
exec.submit(new ExclusionControl("3"));
exec.shutdown();
}
}
// START:2
// END:2
// START:1
// END:1
// START:3
// END:3
ファイルとストリーム
ストリームはデータの入出力のこと。
ストリームを使えばディスク、デバイス、プログラム、メモリなどの入力元と出力元を扱える。
- ストリームの基底クラス
文字ストリーム | バイトストリーム | |
---|---|---|
入力 | java.io.Reader | java.io.InputStream |
出力 | java.io.Writer | java.io.OutputStream |
ストリームはfinallyブロックで必ずcloseメソッドを呼び出す。(物理リソースを解放するため)
try-with-resourceを使うといい。
- 文字入力ストリーム
クラス | 処理 |
---|---|
FileReader | ファイルからReader定義のread()で文字を一文字ずつ処理する |
BufferedReader | 行ごとや指定文字数ごとに処理する(バッファを使用する) |
- 文字出力ストリーム
クラス | 処理 |
---|---|
FileWriter | ファイルに一文字ずつ書き込む |
BufferedReader | バッファに書き込みを行い、それをまとめてディスクに書き込む |
public class Main {
public static void main(String[] args) {
try (FileWriter out = new FileWriter("output.txt", true);// trueで追記モード
BufferedWriter writer = new BufferedWriter(out);) {
writer.newLine(); // システム定義の改行文字を入れる
writer.write("buffering write");
writer.flush(); // バッファに残っている(かも)しれない書き込みをディスクにうつす。
} catch (IOException e) {
e.printStackTrace();
}
}
}
- バイト入力ストリーム
クラス | 処理 |
---|---|
FileInputStream | ファイルから指定のByteを読み込む |
BufferedInputStream | バッファを使用して読み込む |
- バイト出力ストリーム
クラス | 処理 |
---|---|
FileOutputStream | ファイルへ指定のByteを書き込む |
BufferedOutputStream | バッファを使用して書き込む |
public class Main {
// コピーでinputoutput両方使う
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("sample.jpg");
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("sample_bk.jpg", false); // falseなら指定する必要もないが、上書きモード
BufferedOutputStream bos = new BufferedOutputStream(fos);
) {
byte[] data = null;
while ((data = bis.readNBytes(1024)).length != 0) { // 1024Byteごと書き込み
bos.write(data);
}
// bos.write(bis.readAllBytes()); これでもいい
bos.flush(); // バッファを使用した書き込みは最後にflush
} catch (IOException e) {
e.printStackTrace();
}
}
}
Serializable
メモリ上にあるインスタンスをストリームとして出力することをシリアライズ、
逆にファイルからインスタンスを作り直すことをデシリアライズという。
シリアライズはオブジェクトの参照先まですべてプリミティブ型/Serializableオブジェクトでなければならない。
/**
* Serializableクラス
*/
public class Item implements Serializable {
private String name;
private int price;
public Item(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public int getPrice() {
return price;
}
@Override
public String toString() {
return name + " $" + price;
}
}
/**
* シリアライズサンプル
*/
public class Main {
public static void main(String[] args) {
try (FileOutputStream fos = new FileOutputStream("sample.ser", false);
ObjectOutputStream oos = new ObjectOutputStream(fos);
) {
Item item = new Item("banana", 10000);
oos.writeObject(item); // シリアライズ
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* デシリアライズサンプル
*/
public class Main {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("sample.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
) {
Item item = (Item) ois.readObject(); // デシリアライズ
System.out.println(item); // banana $10000
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
なお、transient
やstatic修飾子がついたフィールドは、シリアライズ対象にならない。
SerializableオブジェクトはwriteObject
,readObject
をオーバーライドすることでカスタムシリアライズ処理を作成できる。
Comparable
同じクラスのインスタンスが比較できるようになる。
/**
* Comparableサンプル
*/
public class Item implements Comparable<Item> {
private String name;
private int price;
public Item(String name, int price) {
super();
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public int getPrice() {
return price;
}
@Override
public String toString() {
return name + " $" + price;
}
/**
* compareToを実装する
* 相手より前に並べる -> 負の整数
* 相手より後に並べる -> 正の整数
* 並びを変える必要なし -> 0
*/
@Override
public int compareTo(Item other) {
if (this.price < other.price) {
return -1;
} else if (this.price > other.price) {
return 1;
} else {
return 0;
}
}
}
nioパッケージ
Path
パスを扱えるインタフェース
Path path = Paths.get("dir", "sample.txt"); // OS間の区切り文字違いを吸収してくれる
path = new File("sample.txt").toPath(); // Fileとも変換可能
path = Paths.toAbsolutePath(); // 絶対パスもとれる
Files
Fileクラスの、ファイル操作部分を担うクラス
if (Files.exists(path)) {
Files.createFile(path);
}
System.out.println(Files.getPosixFilePermissions(path));
Path base = Paths.get(".");
Files.walk(base) // walkは再帰的にフォルダ内のパスをStreamにしてくれる
.forEach(System.out::println);
jdbc
クラス | 概要 | close時 |
---|---|---|
Connection | DBコネクションを管理する | jdbcリソースとコネクションを破棄する |
PreparedStatement | SQL命令を管理する | jdbcリソースとResultSetを解放する |
ResultSet | SELECT結果を管理する | jdbcリソースとカーソル情報を解放する |
アノテーション
アノテーション | 意味 |
---|---|
Override | メソッドのスーパークラスにオーバーライド対象がないとコンパイルエラーを出す |
Deprecated | 下位互換性を保つために残したメソッドを、非推奨であることを示す |
SuppressWarnings | コンパイラの警告をオフにする |
//アノテーションの読み込まれるタイミング
@Retention(RetentionPolicy.RUNTIME) // 実行時にデータを取得できる
// SOURCE: コンパイル時破棄される情報
// CLASS: デフォルト. classファイルには残るが実行時データ取得できない
@Target({
ElementType.TYPE, // クラスにアノテーションする
ElementType.METHOD, // メソッドにアノテーションする
ElementType.CONSTRUCTOR, // コンストラクタにアノテーションする
ElementType.FIELD, // 変数にアノテーションする
})
public @interface AnnotationClass {
String version = "0.0.1"; // interfaceと同じく定数を持つ
String value(); // valueがデフォルト アノテーションの単一引数のときセットされる
int num(); // アノテーションからデータをセットできる
}
@AnnotationClass(value = "Sample class", num = 0)
class Sample{
@AnnotationClass(value = "field", num = 1)
public String field;
@AnnotationClass(value = "constructor", num = 2)
public Sample(String field) {
this.field = field;
}
@AnnotationClass(value = "method", num = 3)
public String getField() {
return field;
};
}
public static void main(String[] args)
throws ClassNotFoundException, NoSuchMethodException, SecurityException, NoSuchFieldException {
Class<Sample> cls = Sample.class; // 対象クラス
// クラスアノテーション
AnnotationClass aClass = cls.getAnnotation(AnnotationClass.class);
System.out.println(aClass.value()); // Sample class
System.out.println(aClass.num()); // 0
// フィールドアノテーション
Field field = cls.getField("field");
AnnotationClass aField = field.getAnnotation(AnnotationClass.class);
System.out.println(aField.value()); // field
System.out.println(aField.num()); // 1
// コンストラクタアノテーション
Constructor<?> constructor = cls.getConstructor(String.class);
AnnotationClass aConstructor = constructor.getAnnotation(AnnotationClass.class);
System.out.println(aConstructor.value()); // constructor
System.out.println(aConstructor.num()); // 2
// メソッドアノテーション
Method method = cls.getMethod("getField");
AnnotationClass aMethod = method.getAnnotation(AnnotationClass.class);
System.out.println(aMethod.value()); // method
System.out.println(aMethod.num()); // 3
}
総称型とワイルドカード
総称型
使うまでなんの型が入るかわからないとき、総称型が使える。 まずはクラスの総称型。
// 型を汎用化することでいろいろな型で使えるようになる
// クラス宣言で<> の中に適当なクラス名を宣言する
// <T extends Number> のようにクラス範囲を指定できる※wildcard参照
// T は慣習(Type)。何でも構わない
class GenericSample<T> {
private T value;
public void setValue(T val) {
value = val;
}
public T getValue() {
return value;
}
}
public class Generics {
public static void main(String[] args) {
GenericSample i = new GenericSample<>();
i.setValue(10);
System.out.println(i.getValue()); //10
GenericSample s = new GenericSample<>();
s.setValue("Hello");
System.out.println(s.getValue()); //Hello
}
}
次にコンストラクタ、メソッドの総称型。
class MethodConstractorSample {
// コンストラクタ前に仮型引数を置く
<T> MethodConstractorSample(T arg) {
System.out.println(arg);
}
// メソッド戻り値の前に仮型引数を置く
// (戻り値にTも使える)
public <T> boolean genericSample(T arg) {
T t = arg;
if (t != null) {
return true;
} else {
return false;
}
}
}
ワイルドカード
ワイルドカード: 総称型のクラスやメソッドを使う際、実行(呼び出される)するまで型が分からないとき使う。
class WildCard {
// List<? extends Number> でNumber型及びNumberを継承するクラスを指定
// implementsされているクラスもextendsで指定できる 違和感
// List<? super Number> でNumber型及びNumberのスーパークラスを指定
// 上2つは違うとコンパイルすらできない
// List<?>もしくはList で何クラスでも良し
public List<?> createList(boolean s) {
if (s) {
return new ArrayList<Number>();
} else {
return new ArrayList<String>();
}
}
}
// WildCard w = new WildCard();
// List l = w.createList(false);
// l.add("String");
// System.out.println(l.get(0)); // String
ローカライズ
Locale
Locale
クラスを使用するとi18n
に対応したロケール情報を処理できる。
Locale locale = Locale.getDefault(); // OSのロケール情報
locale = new Locale("ja", "JP"); // 言語, 国を指定
locale = Locale.JAPAN; // 日本のロケール情報を使用
locale = new Locale.Builder()
.setLanguage("jp")
.setRegion("JP")
.build(); // Builderを使用
locale = Locale.forLanguageTag("ja-JP"); // IETF言語タグで生成
System.out.println(locale.toLanguageTag()); // IETF言語タグを生成 ja-JP
Properties
Map
を実装してあるProperties
クラスを使用することで、propertiesファイルに記載した設定を読み込むことができる。
JavaSE9からプロパティファイルはUTF-8を指定して読み込めるようになった。
Properties prop = new Properties();
try (FileReader fr = new FileReader("settings.properties", Charset.forName("UTF-8"))) {
prop.load(fr);
prop.forEach((k, v) -> System.out.println(k + "=" + v));
} catch (IOException e) {
e.printStackTrace();
}
ResourceBundle
ロケールとファイル名を使用し、クラスパスに置いておいたプロパティファイルを取得する。基底名_言語コード_国_地域コード_バリアントコード.properties
ファイルを用意しておくと、自動でそのファイルを取得してくれる。
右の情報から順に省略可能。
# settings.properties
hello=こんにちは
# settings_en_US.properties
hello=hello
public static void main(String[] args) {
// 基底名を、クラスパスが通っているフォルダの、packagename.filenameで指定する。
ResourceBundle resource = ResourceBundle.getBundle("settings");
// デフォルトロケールのja_JPファイルは用意していないため、settings.propertiesが読み込まれる
System.out.println(resource.getString("hello")); // こんにちは
resource = ResourceBundle.getBundle("settings", Locale.US);
System.out.println(resource.getString("hello")); // hello
}
モジュールシステム
JavaSE9から導入されたパッケージ分離システム。
ソースフォルダのトップにmodule-info.java
を作成することで、モジュールシステムを利用することになる。
モジュールシステムを利用すると、外部に使わせるクラスを制限できる(public未満protected以上ができる)
また、リフレクションによるアクセスも、制限することができる。
逆に、外部ライブラリ(java.baseモジュール以外)を利用する際はmodule-info.java
で宣言が必要。
モジュールシステムでは、クラスパスの他にモジュールパスを利用する。(java --module-path)
/**
* module-info.java
*/
module module.name { // モジュール名を宣言
exports package.name; // 外部に公開するパッケージを指定
requires java.net.http; // java.base以外を内部で使う場合、requiresが必要になる
opens package.name; // リフレクションを許可するパッケージを指定
uses work.sehippocampus.Hello; // SPIでアプリケーション側で使用する ↓SPIを参照
provides work.sehippocampus.lib.HelloImpl; // SPIでライブラリ側で使用する ↓SPIを参照
}
- 自動モジュール: モジュールパスに存在するモジュールシステムで作られていないjar
自動でモジュールとして認識され、すべてのクラスが使えるようになる。 - 無名モジュール: クラスパスに存在するjar
モジュールパスに存在するjarの中身は無名モジュールから使用することができるが、反対はできない
ボトムアップ移行: jdepsなどを使用して依存されている側から順に無名モジュールを名前付きモジュールに変更すること
トップダウン移行: 無名モジュールをモジュールパスに移動させ、自動モジュールにすること
SPI
Service Provider Interface
JDBCのようにAPI(インタフェース)だけがあり、第三者が実装をすること。
依存性逆転の原則を守るためのインタフェースの利用を行っている。
ServiceLoader
SPIをさせるためのクラスで、インタフェースを実装したクラスを検索できる。
アプリケーション側
package work.sehippocampus;
public interface Hello {
public String hello();
}
public class Main {
public static void main(String[] args) {
// ServiceLoaderによって実装クラスを検索し、インスタンスを生成する
for ( Hello hello : ServiceLoader.load(Hello.class)) {
System.out.println(hello.hello()); // Hello!
}
}
}
ライブラリ側
package work.sehippocampus.lib;
import work.sehippocampus.Hello;
public class HelloImpl implements Hello {
@Override
public String hello() {
return "Hello!";
}
}
# META-INF/services/work.sehippocampus.Hello
work.sehippocampus.lib.HelloImpl # ファイル名をインタフェース名、中身に実装クラスを列挙する
セキュリティ対策
セキュアコーディングについては、SEI CERT Oracle Cording Standard for Javaにまとまっており(日本語版)、
中でも重要なトップ10がTop 10 Secure Coding Practicesにまとまっている
- 整数オーバーフロー: 事前条件テスト or アップキャスト or BigIntegerを使用する
- 情報漏洩
- 必ず例外処理する(Dos攻撃やメモリリークなどでスタックトレースから攻撃に必要な情報が漏れる)
- コアダンプやログを必要以上に長く持たない
- インスタンスをガベージコレクションに処理されるように設計する
- Dos対策
- 同時接続数を制限する
- ファイル読み込みサイズを制限する(
Files.size()
を利用) - try-with-resourceでリソースを解放
- SecurityManager: JVMにSecurityManagerを登録することでセキュリティポリシーを適用させることができる(java -Dオプションでもできる)
- SQLインジェクション: PreparedStatementを使用する
- シリアライズオブジェクト: 機密情報を扱うシリアライズオブジェクトは、
transient
を使用して書き出しできないようにする
(バイトデータ解析で中身がわかるため)