はじめに
Javaの標準API(日付・時刻、BigDecimal)を勉強したときに生まれた「なぜこうなっているの?」という疑問と、そこから得た新たな発見をまとめました。
1. staticメソッドとインスタンスメソッド
日付・時刻APIを使ううちに「なぜあるメソッドはクラスに対して呼び出し、別のメソッドは値に対して呼び出すのか?」という疑問が生まれました。
| 種類 | 構文 | 例 |
|---|---|---|
| staticメソッド | クラス名.メソッド名(引数) |
LocalDate.now()、LocalDate.of(2021, 9, 14)
|
| インスタンスメソッド | 値.メソッド名(引数) |
today.plusDays(3)、"abc".toUpperCase()
|
- static → 「作る・共通処理」。インスタンスがなくても使える
- インスタンス → 「使う・加工する」。手持ちの値に対して操作する
var today = LocalDateTime.now(); // staticで「今」を作る
today.plusDays(3) // インスタンスメソッドで加工する
today.plusWeeks(2)
today.plusHours(3)
💡 発見:使う人ではなく、作る人が意識する話
staticかinstanceかは、APIを設計する側が決める話です。使う側は「クラスに対して呼ぶのか、値に対して呼ぶのか」を覚えるだけです。
2. formattedとformatの違い
日付の整形メソッドは2種類ありました。
// インスタンスメソッド(Java 15〜)
"%tY年%<tm月%<td日".formatted(today) // => "2021年06月25日"
// staticメソッド(昔からある書き方)
String.format("%tY年%<tm月%<td日", today)
| メソッド | 種類 | 構文 |
|---|---|---|
formatted |
インスタンスメソッド | 書式.formatted(値) |
format |
staticメソッド | String.format(書式, 値) |
⚠️ 注意:
formatは静かな罠がある
"%sですよ".format("みかん")は"みかん"を返します。
formatはStringクラスのstaticメソッドなので、前の"%sですよ"という文字列は無視されています。String.format(...)と書くのが本来の使い方です。
3. BigDecimal
なぜ普通のdoubleじゃダメなのか
579 * 0.05 // => 28.950000000000003 ← 誤差が出る!
double は内部で2進数(IEEE 754規格) で数値を表現するため、10進数の小数を正確に表せないことがあります。
import java.math.BigDecimal;
var b579 = BigDecimal.valueOf(579);
var b005 = BigDecimal.valueOf(0.05);
b579.multiply(b005) // => 28.95 ← 正確!
💡 発見:BigDecimalは10進数のまま保持する
doubleは「2進数に変換 → 計算 → 2進数から変換」という流れで誤差が生まれます。BigDecimalは10進数で保持するので変換の誤差がありません。
ただし、円周率のような無限小数はBigDecimalでも誤差が出ます。誤差がゼロではなく「10進数に変換できる数値なら正確」というイメージです。
コンストラクタの使い方に要注意
// ❌ これはダメ:double の誤差をそのまま引き継ぐ
new BigDecimal(0.05)
// => 0.05000000000000000277555756156289135105907917022705078125
// ✅ 文字列を渡す
new BigDecimal("0.05") // => 0.05
// ✅ または valueOf を使う(精度は15桁程度まで)
BigDecimal.valueOf(0.05)
💡 発見:なぜ文字列を渡すのか?
double型は2進数で表現されるので、0.05という値をJVMに渡した瞬間にすでに誤差が乗っています。文字列で渡すことで、BigDecimalが10進数のまま受け取れる、
4. なぜ新しいAPIはコンストラクタを使わせないのか
LocalDate は new では作れません。
LocalDate.of(2021, 9, 14) // ✅ これはOK
new LocalDate(...) // ❌ コンパイルエラー
💡 発見:ファクトリメソッドパターンという設計
新しいJavaのAPIには「コンストラクタを隠して、staticメソッド(ファクトリメソッド)でオブジェクトを作らせる」という傾向があります。
理由のひとつはメモリ効率です。同じ意味を持つオブジェクトが既にメモリにあれば、新たに生成せずそれを返せます。newを直接呼ぶとかならず新しいオブジェクトが生成されてしまって、参照できない(もともとあるものをリサイクルできない)ので非効率となります。
5. APIという言葉の多義性
💡 発見:「API」は文脈によって意味が変わる
Java標準ライブラリの文脈では「クラスやメソッド」のことをAPIと呼びますが、実際には大きく3種類あります。
- 同じプロセス内で呼び出す(Javaの標準ライブラリはこれ)
- 同じ機器内の別プロセスから呼び出す(UDS・共有メモリ経由)
- 異なる機器間でHTTPやRPCで呼び出す(いわゆるWeb API)
Web開発をすると「APIを叩く」という言葉が出てきますが、それは主に3番目の意味です。呼び出し元の言語も関係なく、呼び出し先がどう実装されているかも意識しなくていい、「共通の入り口」としてのAPIです。
おわりに
Java標準APIの学習を通じて気づいたことをまとめました。
API一つとっても色々な概念が登場して、面白いなと思いました。