【Java】ライブラリとは -自作クラスとの違いと分類-

Java

ライブラリとは

プログラミングにおけるライブラリとは、再利用可能な部品のことです。特にJavaの文脈では一般に、JARファイルの形式で配布されるプログラム(クラスファイル)の集合体を指します。

しかし必ずしもJavaの「ライブラリ」とは、自作クラス(.java)に対してクラスパスを通した共通処理(.jar)、あるいは、複数のプログラムをJARファイルにアーカイブしたもの、を指すわけではありません。ライブラリとはファイル形式やディレクトリ構成によって一概にそれと識別できるものではなく、特に自作ライブラリの場合は、どのように利用され得るかによってそれが自作クラスであるかライブラリであるかが定まります。ライブラリとは「再利用可能な部品」のこと、という抽象的な表現をしているのはこのためです。

ライブラリとは
再利用可能な部品

例えばJavaの場合、.javaファイルなら自作クラス、.jarファイルならライブラリ、とは一概に言えず、どちらも .classファイルの形式である場合があります。さらには同じ.classファイルであっても、状況によって自作クラスと呼ぶべき物であったりライブラリと呼ぶべき物であったりということが起こり得ます。実務的には多くの場合ライブラリと言えばJARファイルを指しますし、通常のJavaソースファイルが自作クラスに相当しますが、ライブラリという概念を考える時に拡張子は重要ではありません。

またファイル形式が関係ないのと同様、プログラムの集合体である必要もありません。一般にライブラリを説明する際、再利用できるように複数のプログラムをまとめた集合体、といった表現が用いられます。しかし仮に単一プログラム、Javaで言えば単体のクラスファイル(あるいは一つだけのメソッド)であっても、再利用可能な部品として扱われていれば、それはライブラリと呼べるものになります。実際にはJavaの場合JARやZIPのように複数のプログラムを一つにまとめたアーカイブファイルであることがほとんどですが、ここでは敢えて再利用可能な部品の集合体とはせず、再利用可能な部品と説明しています。

このように「ライブラリ」という用語は非常に概念的なものです。そのためここではJavaにおけるライブラリについて、まずは 自作クラスとの違い について比較しながら確認していきます。それを踏まえてライブラリについて把握できたところで、提供元が異なるだけで実体としては同じである、Java標準API自作ライブラリ外部ライブラリ の分類について最後に整理していきます。

自作クラスと自作ライブラリの違い

まずはライブラリの概念を理解するため、自作クラスと自作ライブラリとを比較しながら、部品を再利用するということを考えてみます。

例えば次のような Main.javaPrintLogic.java から構成される「NewProject」という新しいプロジェクトでプログラムの作成を始めたとします。

Main.java
package com.example;

import com.example.logic.PrintLogic;

public class Main {
    public static void main(String[] args) {

        var printLogic = new PrintLogic();

        printLogic.greet();
    }
}
PrintLogic.java
package com.example.logic;

public class PrintLogic {

    public void greet() {
        System.out.println("Hello World");
    }
}

(これら2ファイルは こちら でもサンプルとして使用していたものです。)

NewProject は次のようなディレクトリ構成になっており、対応するディレクトリの起点である bin にはクラスパスが通されています。

この時、MainクラスとPrintLogicクラスはいずれも自作したクラスであり開発対象として扱われているため、Main.javaPrintLogic.javaMain.classPrintLogic.class は全て自作クラスであると言えます。

① 自作クラスに処理を追加する

この NewProject において、PrintLogicprintSum というメソッドを追加する必要が出てきました。そこでまずはメソッドを定義し、受け取った引数を計算してその結果を出力する処理を記述します。

PrintLogic.java
package com.example.logic;

public class PrintLogic {

    public void greet() {
        System.out.println("Hello World");
    }

    // メソッドを追加
    public void printSum(int a, int b) {

        int sum = a + b;         // 引数を計算

        System.out.println(sum); // 結果を出力
    }
}
Main.java
package com.example;

import com.example.logic.PrintLogic;

public class Main {
    public static void main(String[] args) {

        var printLogic = new PrintLogic();

        printLogic.greet();
        printLogic.printSum(2, 3); // 呼び出し処理を追加
    }
}

② 過去に作成したプログラムを確認する

ここで、以前に別のプロジェクト「OldProject」で次のような Calculator を作成していたことを思い出しました。ここに定義されている add メソッドは、今回 PrintLogicprintSum で行いたい処理の一部と同じことをしています。

Calculator.java
package com.example.math;

public final class Calculator {

    private Calculator() { }

    // PrintLogic の printSum で引数を計算する処理と同じ
    public static int add(int a, int b) {
        return a + b;
    }

    public static int subtract(int a, int b) {
        return a - b;
    }

    // 以降も複数のメソッド定義あり...
}

③ 過去に作成したプログラムを再利用する

そこで今回はこの Calculator を再利用することにしました。具体的には printSumint sum = a + b; という部分を int sum = Calculator.add(a, b); に置き換えます。これが「プログラムを再利用する」ということです。

この例では単純な計算処理のため分かりづらいですが、例えば a + b の部分が複雑で大きな処理の場合、printSum に全て記述しなくても、既に定義されている Calculator のメソッドを呼び出すだけで同じことができるようになります。

さて今回の NewProject では Calculator 自体を修正することはなく、単に呼び出したいだけです。こちら でも触れている通り、Javaプログラムの実行に必要なのはクラスファイル(.class)のみで、ソースファイル(.java)は無くても問題ありません。従ってこのようにプログラムを再利用する際、クラスファイルさえあれば足ります。

そこで NewProject に新しく lib というディレクトリを作成し、Calculator.class ファイルだけを OldProject からコピーして配置しました。(パッケージ階層に合わせる必要があるため、com 以下は全て元のディレクトリ構成を保持したままコピーします。)Calculator.java ファイルは不要のため NewProject にはコピーしません。

この状態で、lib ディレクトリにクラスパスを通します。こうすることにより、PrintLogicCalculator を呼び出して利用できるようになります。この時、Calculator は再利用できる部品として扱われているため、ライブラリであると言えます。

自作クラスである MainPrintLogic が、開発用にソースファイルと併せて配置されているのに対し、ライブラリである Calculator は、自作クラスから呼び出すされるためだけにクラスファイルのみが配置されている状態です。

PrintLogic.java
package com.example.logic;

import com.example.math.Calculator; // クラスパス追加によりインポート可能になる

public class PrintLogic {

    public void greet() {
        System.out.println("Hello World");
    }

    public void printSum(int a, int b) {

     // int sum = a + b;
        int sum = Calculator.add(a, b); // ライブラリのメソッド呼び出しに変更

        System.out.println(sum);
    }
}

ソースファイルがなく、クラスファイルだけを配置して呼び出しているというのはつまり、NewProject の開発者は、Calculator の内部構造(addメソッドがどのように実装されているか)を知らなくても良いということです。メソッドの使い方(どのような引数を渡せばどのような戻り値を得られるか)さえ分かっていれば、内部の仕組みを意識せずに利用できるのがライブラリの良いところです。

ライブラリの利点
機能や使い方が分かっていれば内部構造を意識せずに利用可能

④ 過去に作成したプログラムのソースをコピーする

ここで注意が必要なのは、例えば Calculator のソースファイルをコピーして math パッケージに配置した場合、Calculator はライブラリとは呼び難いものになってしまうという点です。仮に Calculator を一切編集しなかったとしても、src 下に配置した Calculator.java をコンパイルして Calculator.class を作成している時点で、改修が想定される自作クラスの意味合いが強くなってしまいます。ライブラリとして部品を再利用しているのではなく、単なるコピー&ペーストによるソースコードの使い回しになってしまっている状態です。

あるいは次のように、OldProject の bin に直接クラスパスを通すことでも PrintLogicCalculator を呼び出せるようになります。この場合、NewProject 側では Calculator を修正せず部品として利用しているという側面が強いため、Calculator はライブラリであると言えそうです。

このように、何がライブラリで何がライブラリでないかは、どのような利用が想定されるかが重要であって、ファイル形式や配置構成によって決まるものではないことが分かります。

言い換えれば、プログラムの部品であるライブラリとは、プログラムが依存する先であると捉えることが可能です。従って、開発対象である自作クラスに対し、ライブラリは依存対象であると言えます。

自作クラス:開発対象
ライブラリ:依存対象

⑤ 過去に作成したプログラムの依存関係を解決する

ここまで単一のクラスに絞って自作クラスと自作ライブラリを比較してきました。しかし多くの場合、ライブラリと呼ばれるような複数の処理を担うプログラムは、単一クラスでは完結しません。

例えば Calculator の場合、add は引数が異なるメソッドがいくつも定義されていますが、その中では ArrayUtilsStringUtils のような別のクラスを利用しています。さらに StringUtilsCalculatorNumberFormatException に依存しており、従って PrintLogicCalculator を呼び出せるようにする際、単に Calculator.class をコピーするだけでは足りず、依存するクラス全て(ここでは math 下のすべてのクラス)をまとめてコピーする必要があります。

Calculator.java
package com.example.math;

import java.util.Arrays;

// Calculator は ArrayUtils と StringUtils に依存している
import com.example.math.util.ArrayUtils;
import com.example.math.util.StringUtils;

public final class Calculator {

    private Calculator() { }

    public static int add(int a, int b) {
        return a + b;
    }

    public static int subtract(int a, int b) {
        return a - b;
    }

    public static int add(int... numbers) {

        if (ArrayUtils.isEmpty(numbers)) {
            return 0;
        }

        return Arrays.stream(numbers)
                     .reduce(Integer::sum)
                     .getAsInt();
    }

    public static int add(String... numbers) {

        if (ArrayUtils.isEmpty(numbers)) {
            return 0;
        }

        int[] intNumbers = Arrays.stream(numbers)
                                 .mapToInt(StringUtils::parseInt)
                                 .toArray();
        return add(intNumbers);
    }
}
ArrayUtils.java
package com.example.math.util;

public final class ArrayUtils {

    private ArrayUtils() { }

    public static boolean isEmpty(int[] array) {
        return array == null || array.length == 0;
    }

    public static boolean isEmpty(String[] array) {
        return array == null || array.length == 0;
    }
}
StringUtils.java
package com.example.math.util;

// StringUtils は CalculatorNumberFormatException に依存している
import com.example.math.exception.CalculatorNumberFormatException;

public final class StringUtils {

    private StringUtils() { }

    public static boolean isNotNumeric(String str) {
        try {
            Integer.parseInt(str);
            return false;
        } catch (NumberFormatException e) {
            return true;
        }
    }

    public static int parseInt(String str) {
        if (isNotNumeric(str)) {
            throw new CalculatorNumberFormatException();
        }
        return Integer.parseInt(str);
    }
}
CalculatorNumberFormatException.java
package com.example.math.exception;

public class CalculatorNumberFormatException extends NumberFormatException {

    private static final long serialVersionUID = 1L;

}

これがライブラリが単一ファイルであることが少ない理由です。仮に利用したいものが一つだけであっても、それが正常に動作するためには依存するクラスが全て含まれている必要があります。

実務上はほとんどの場合このようになるため、ライブラリとは「再利用可能な部品の集合」と表現されることも少なくありません。またJavaであれば、基本的にライブラリといえばクラスパスを通すべきディレクトリ全体をJARファイルにまとめて配布されるのが通常です。

単一クラスでなく複数クラスの集合であることが多い
複数クラスをJARファイルにまとめて配布されることが多い

⑥ 再利用可能な部品としてライブラリを作成する

この例の NewProject と OldProject にはもう一つ、Mainクラスの有無という大きな違いがあります。これは OldProject が初めからライブラリとして提供することを目的として作成されているためです。

ここまでの自作クラスと自作ライブラリの比較で、他のプログラムから利用されるという位置づけになっているものがライブラリと呼ばれ得るということが分かりました。従ってライブラリはそれ単体で動作することは想定されておらず、そのためMainクラスの main メソッドのようなエントリポイントを持たないことが多いという点も特徴として挙げられます。

その他、ライブラリとして作成されたプロジェクトの場合、クラスファイルだけでなくソースファイルも併せて提供されることがあります。前述 の説明を踏まえると、ライブラリはソースファイルを除きクラスファイルだけであることが重要かのように思われるかもしれませんが、例えばEclipseで ソースファイルの関連付け を行う場合など、開発を補助する情報としてライブラリのソースファイルをプロジェクト内のディレクトリに配置することもあります。自作クラスがソースファイルを編集して開発されるのに対し、ソースファイルは必須ではないというのがライブラリの特徴です。

依存対象となる部品のためソースファイルは必須ではない
開発のためにソースファイルが提供されることもある

ライブラリの分類

ここまで自作クラスと自作ライブラリを比較しながら、ファイル形式や配置構成によらず、どのようなものがライブラリと呼ばれ得るかを確認してきました。続いて、Javaにおけるライブラリがどのように分類されるかを整理していきます。

内部ライブラリ

自作したライブラリや、同じ組織内の別チームが作成したライブラリ等、内製のライブラリです。システム固有の仕様に合わせた独自の共通ロジック等を部品化することを目的として主に作成されます。

基本的に公開されることはなく、内部のリポジトリや共有ファイルシステム等、チーム内で直接ライブラリの配布が行われます。

外部ライブラリ

サードパーティ製のライブラリです。Apache Commons Lang のようなOSSを指すことも多いですが、外部の第三者によって作成され配布されているものであれば、ソースコードが公開されているか否か、有償か無償かを問わず、外部ライブラリと言えます。

例えばJDBCドライバの場合、MySQLの「Connector/J」であれば GitHubリポジトリ でソースコードが公開されていますが、Oracleの「Oracle JDBC driver」は非公開です。またどちらもライブラリ(mysql-connector-j-0.0.0.jarojdbc00.jar)のダウンロード自体は無償で可能ですが、商用利用の際は場合によって有償のライセンス契約が必要となります。

ソースコードの公開/非公開、ライブラリ利用の有償/無償はそれぞれ異なりますが、いずれも外部ライブラリと呼べるものです。

標準ライブラリ

Javaの標準APIです。内部ライブラリや外部ライブラリの場合はクラスパスに追加してから利用する必要がありますが、標準ライブラリは初めから利用できるようになっています。

例えば外部ライブラリである Apache Commons Lang の場合、Eclipse設定手順の例 のように、クラスパス設定なしでは依存解決できずインポート文でエラーになってしまいます。一方で標準ライブラリの場合はデフォルトで使用可能なクラス群であるため、そのままインポート文を記述あるいは完全修飾クラス名を指定する(java.langパッケージの場合は不要)だけで利用が可能です。

標準ライブラリのクラスファイルの実体は、Java8以前は JREまたはJDKlib ディレクトリ下へ、rt.jarRunTime)というJARファイルにアーカイブされて格納されていました。
jre1.8.0/lib/rt.jarjdk1.8.0/jre/lib/rt.jar

Javaプラットフォームのクラスは、rt.jarに格納されています。

クラス・パスの設定 > 説明

最新のJavaでは、Java9から導入された モジュールシステム により jmods ディレクトリ下へ複数の .jmod にアーカイブされた状態で格納されています。
jdk-21/jmods/java.base.jmod, java.compiler.jmod, ..

ライブラリの要点

  • ライブラリとは
    • 再利用可能な部品
    • 開発対象ではなく依存対象として扱われるクラス
    • 形態は問わない
      • 単一のクラスファイル
      • ディレクトリ自体
      • JARやZIP等のアーカイブファイル
  • ライブラリの利点
    • 部品化されているためプログラムの再利用が可能
    • 機能や使い方が分かっていれば内部構造を意識せずに利用が可能
  • その他の指標(本質ではないものの参考となる情報)
    • 開発対象でないためソースファイルは必須ではない
      • 開発補助のためソースファイルが提供されることもある
    • 部品のためエントリポイントとなる main メソッドを持たないことが多い
    • 複数のクラスファイルの集合であることが多い
      • JARファイル形式で配布されることが多い
  • 自作クラス
    • 開発者によって定義されたクラス
    • 依存対象ではなく開発対象として扱われるクラス
  • 内部ライブラリ
    • 内製のライブラリ
    • 自作したライブラリや同じ組織内の別チームが作成したライブラリ等
  • 外部ライブラリ
    • サードパーティ製のライブラリ
    • OSSのように外部の第三者によって作成され配布されているもの
    • ソースコードの公開/非公開は問わない
    • ライブラリ利用の有償/無償は問わない
  • 標準ライブラリ
    • Java標準API
    • Java8以前:jre/lib/rt.jar
    • Java9以降:jdk/jmods/*.jmod