虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

【目指せ、脱初心者】Java入門書に載っていない「Java 次の一歩」

皆さんこんにちは、虎の穴ラボのH.Kです。
今回はJava入門書を一通り熟したけれど、もう一歩先に進みたい人に向けて、Javaの基礎的な文法の中でもプロジェクトで出てきそう かつ 入門書に載っていない内容をピックアップして少しまとめてみました。

概要

Java入門書を一通り読んでも、「このあと何を勉強したらいいかわからない」、「プロジェクトにジョインしたけど知らない文法がたくさん出てくる」というようなことはよくあります。
入門書に載っていない文法などを取り上げることで、Javaの世界の広さと便利な書き方を知っていっていただけると嬉しいです。
Java入門書は『スッキリわかるJava入門(第3版)』を参考にしていますので、もし「私の持っている入門書には載っていたよ!」などがあってもご容赦いただきますよう、よろしくお願いいたします。
また、上記の入門書の対応状況に合わせ、本記事はJava11ベース(とは言ってもJava8以降であればソースは動くはずです)で書いています。

紹介する内容

  • privateなコンストラクタ
  • for文の書き方
  • StreamAPI(ちょっとだけ)

privateなコンストラクタ

用途

  • インスタンスを作ってほしくないクラス
    • staticなフィールドのみの定数クラス
    • staticなメソッドのみのユーティリティクラス
  • ファクトリメソッドを使ってほしいクラス
    • シングルトンパターンなど

インスタンスを作ってほしくないクラス

例えば定数のみが列挙されているConstantクラスがあったとしましょう。
このクラスは定数のみなので、インスタンスを作る必要はありませんが、コンストラクタを定義しなくてもデフォルトコンストラクタがあるので、newできてしまいます。
そこで、あえてprivateでコンストラクタを定義することによってnewをできなくすることができます。

Constant.java

package main.example;

public class Constant {
  public static final String PRODUCT_NAME = "EXAMPLE";
}

Main.java

package main;

import main.example.Constant;

public class Main {
  public static void main(String[] args) {
    Constant constant = new Constant(); //new できる
  }
}

次にprivateコンストラクタを定義します。 Constant.java

package main.example;

public class Constant {
  private Constant() { //privateなコンストラクタ
  }

  public static final String PRODUCT_NAME = "EXAMPLE";
}

Main.java

package main;

import main.example.Constant;

public class Main {
  public static void main(String[] args) {
    Constant constant = new Constant(); //コンパイルエラー('Constant()' has private access in 'main.example.Constant')
    System.out.println(Constant.PRODUCT_NAME) //EXAMPLE
  }
}

ファクトリメソッドを使ってほしいクラス

クラスを設計していく中で、このインスタンスは1つしか存在してほしくないものが出てくることもあるかもしれません。
そういった場合、シングルトンパターンというデザインパターンを使って実装するのですが、その際にもprivateコンストラクタが使われます。
デザインパターンとは「こういう実装がしたいんだけど」に対して「こうすれば良いよ」というパターンをまとめたものです。
newしてしまうと複数インスタンスが生成されてしまうため、getInstanceというstaticメソッドを経由してインスタンスを取得するように実装します。

Singleton.java

package main.example;

public final class Singleton {
  private int counter;

  public int getCounter() {
    return counter;
  }

  public void addCount() {
    this.counter++;
  }

  private Singleton() { //privateなコンストラクタ
  }

  private static class SingletonHolder {
    private static final Singleton instance = new Singleton();
  }

  public static Singleton getInstance() {
    return SingletonHolder.instance;
  }
}

これでcounterの数値は実行しているアプリケーション内で一つになり、どこで数値を増やしても同じインスタンスを見るようになります。 呼び出し方法は以下のようになります。
Main.java

package main;

import main.example.Singleton;

public class Main {
  public static void main(String[] args) {
    Singleton counter1 = Singleton.getInstance();
    counter1.addCount();
    Singleton counter2 = Singleton.getInstance();
    counter2.addCount();
    System.out.println(counter1.getCounter()); //2になる(counter1であっても同じインスタンスなのでcounter2の加算分も含まれる)
  }
}

for文の書き方

入門書に載っている書き方は以下のような形です。(1~10までカウントアップ)
Main.java

package main;

public class Main {
  public static void main(String[] args) {
    for (int i = 1; i <= 10; i++) {
      System.out.println(i);
    }
  }
}

入門書の内容をまとめるとfor(初期化処理;繰り返し条件;繰り返し時処理){処理}というように書いてあります。 実はこの初期化処理、繰り返し条件、繰り返し時処理について、必須なものは一つもありません。 以下のような形でもコンパイルエラーにならず実行されます。
ただし無限ループになるため実行した場合はCtrl+Cなどで処理を抜けてください。
Main.java

package main;

public class Main {
  public static void main(String[] args) {
    for (;;) {// while(true){}と同じ処理
    }
  }
}

なお、某所では以下のように無限ループを書くと顔文字に見えるみたいな話もありましたので紹介します。

boolean v = true;
for (;v;) {
  // 無限に繰り返す
}

初期化処理などを書かずに1~10までカウントアップを実施することもできます。
Main.java

package main;

public class Main {
  public static void main(String[] args) {
    int i = 1;
    for (;;) {
      if (!(i <= 10))// もとの繰り返し条件と同様にするためにちょっとわかりにくい書き方です
          break;
      System.out.println(i);
      i++;
    }
  }
}

こう書いたときの利点ですが、あまり多くありません。
初期化がブロックの外で行われているため、for文の外でもiが使えることくらいだと思います。
例えば繰り返しをやめる条件がもう少し複雑で、やめたときのiが知りたい場合などには有効です。

StreamAPI

最後に紹介するのはJava8で導入されたStreamAPIです。
詳しく紹介するとそれだけで1記事になってしまうので、少しだけ紹介します。
先程の1~10までカウントアップする処理ですが、StreamAPIを使うと以下のように書くことができます。
Main.java

package main;

import java.util.stream.IntStream;

public class Main {
  public static void main(String[] args) {
    IntStream.rangeClosed(1,10).forEach(System.out::println);
  }
}

分解して処理を見ていきます。

IntStream

整数型の集約操作を行うためのクラス(インターフェイス)です。
通常はStreamクラス(インターフェイス)を使うのですが、カウントアップ処理にはStreamの中でもintに特化した(intのみ扱える)IntStreamのほうが都合が良いため、IntStreamを使っています。

IntStream#rangeClosed

rangeClosed(int startInclusive, int endInclusive)startInclusiveからendInclusiveまでのIntStreamを作成します。
同じようなメソッドにIntStream#rangeがありますが、終わりの数値を含むかどうかが違います。
IntStream#rangeを使った場合、10までのカウントアップは以下のようになります。 Main.java

package main;

import java.util.stream.IntStream;

public class Main {
  public static void main(String[] args) {
    IntStream.rangeClosed(1,11).forEach(System.out::println);// 終わりの数値を含まないので第2引数は11にする
  }
}

IntStream#forEach

forEachは順次処理を実行します。
例ではメソッド参照という機能を使っていますが、使わない例だと以下の通りです。
Main.java

package main;

import java.util.stream.IntStream;

public class Main {
  public static void main(String[] args) {
    IntStream.rangeClosed(1,10).forEach(i -> System.out.println(i));
  }
}

Streamの処理は大きくわけて3つの処理で構成されています。
- 初期化(Streamを作る) - 中間操作(Streamを加工する) - 終端操作(中身を処理する=集計したり結果を出力したり)

カウントアップ時の初期化はIntStream.rangeClosed(1,10)、終端操作はforEach(System.out::println)になります。
今回はカウントアップという単純な処理だったので、中間操作は行っていませんが、例えば「偶数だけを出力する」のような処理だと中間操作で偶数だけを抜き出します。
Main.java

package main;

import java.util.stream.IntStream;

public class Main {
  public static void main(String[] args) {
    IntStream.rangeClosed(1,10).filter(i -> i % 2 == 0).forEach(System.out::println);
  }
}

filterメソッドが中間操作になります。
また、平然と->みたいな記号を使って見慣れない操作を行っていますが、こちらは「ラムダ式」と呼ばれるものです。
こちらも解説していくと1記事書けてしまうので、興味があるかたは調べて使ってみてください。

まとめ

Javaには他にも入門書では説明しきれていない文法や機能がたくさんあります。
例えば、

  • Optional
  • Reflection
  • ジェネリクス
  • var(ローカル変数の型推論)
  • staticフィールド(静的初期化ブロック、初期化ブロック)
  • アノテーション(Overloadなどなど)
  • clone  などなど・・・・

長く使われている言語なので、多様な書き方がありますし、最近リリースサイクルが短くなり、多くの機能が比較的短いスパンで追加されるようになりました。(一年後には次のLTSであるJava17もリリースされます!)
既存のソースコードを見ていくと入門書に載っていなかった書き方もたくさん出てくると思います。
いろいろな機能を知って、よりスマートにバグなくソースが書けるように入門書の内容で満足せずに、継続して勉強していきましょう!

P.S.

虎の穴ラボ主催LT会開催!

虎の穴ラボ主催のオンラインLTイベントを 9/30(水)19:30〜 開催します!
今回もフリーテーマとなっており、一般参加者はまだまだ募集しておりますので、是非ご参加ください。
connpassにて参加受付中です!

yumenosora.connpass.com

カジュアル面談

弊社エンジニアと1on1で話せます、カジュアル面談も現在受付中です!こちらも是非ご検討ください。 yumenosora.connpass.com

その他採用情

虎の穴ラボでの開発に少しでも興味を持っていただけた方は、採用説明会やカジュアル面談という場でもっと深くお話しすることもできます。ぜひお気軽に申し込みいただければ幸いです。
カジュアル面談では虎の穴ラボのエンジニアが、開発プロセスの内容であったり、「今期何見ました?」といったオタクトークから業務の話まで何でもお応えします。

カジュアル面談や採用情報はこちらをご確認ください。
yumenosora.co.jp