Java 言語をある程度習得した場合、または Java の共通クラスや API をすでにスムーズに使用できるようになった場合。何か考えはありますか?たとえば、このクラスはどのように設計されていますか? このメソッドはどのように実装されているのでしょうか? 次の一連の記事では、Java のいくつかの一般的なクラスのソース コードを学びましょう。今回はIntegerのソースコードを一緒に解析してみましょう。
目次
1 つまたは 2 つの整数の質問
学生の中には、比較的高いレベルの Java を持っているか、Integer のソース コードを読む必要がないと考えている人もいるかもしれません。次に、次のいくつかの質問を見て、すべてに正しく答えられるかどうかを確認してください。
1. 次のコードは何を出力しますか?
public class IntegerTest1 {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1 == i2);
System.out.println(i3 == i4);
}
}
答えは次のとおりです: true false
2. 次のコードは何を出力しますか?
public class IntegerTest {
public static void main(String[] args) {
String s = "10";
System.out.println(Integer.getInteger(s));
}
}
答えは次のとおりです: null (ねえ、なぜ 10 ではないのですか?)
両方の質問に正しく答えられた場合は、時間を無駄にしたくないので、この記事をフォークしていただいても構いません。間違った答えが出た場合は、Integer のソースコードを一緒に学習してみてはいかがでしょうか。
2、整数クラス図
それでは次に、Integer のソースコードを見てみましょう。整数のソース コードは短くなく、1800 行以上のコードがあります。一般的に使用されない API が多数あるため、一般的に使用される API の一部のみを取り上げて、その実装を確認します。以下は、Integer とそれに関連するクラス/インターフェイスのクラス図です。
Integer クラスのクラス図を通じて、その特性を要約します。
- Integer クラスは抽象クラス Number を継承します。
- Integer クラスは Comparable インターフェイスを実装します。
- Integer クラスは最終変更を使用するため、サブクラスを持つことができません (継承できません)。
3. 文字列を int に変換する
日常業務では、数値を表す String を int に変換することが多いため、Java には 2 つのメソッドが用意されています。
1、Integer.parseInt
public static int parseInt(String s) throws NumberFormatException {
return parseInt(s,10);
}
このメソッドを使用する場合は、trycatch NumberFormatException に注意する必要があります。そうしないと、入力文字列が数値でない場合、または整数の範囲を超える場合に例外が生成されます。
さらに、このメソッドのデフォルトは基数 10 です。 もちろん、2 つのパラメーターのメソッドを呼び出して基数を渡し、他の基数の int に変換することもできますが、これは一般的には使用されません。
public static int parseInt(String s, int radix)
throws NumberFormatException
{...}
次に、このメソッドの実装を詳しく見てみましょう。
まず、受信した文字列が null ではないかどうかを判断し、受信したバイナリ パラメータが [2, 36] の範囲内にあることを確認し、文字列の長さが 0 より大きいかどうかを判断します。それ以外の場合は、NumberFormatException がスローされます。
if (s == null) {
throw new NumberFormatException("null");
}
if (radix < Character.MIN_RADIX) {
throw new NumberFormatException("radix " + radix +
" less than Character.MIN_RADIX");
}
if (radix > Character.MAX_RADIX) {
throw new NumberFormatException("radix " + radix +
" greater than Character.MAX_RADIX");
}
if (len > 0) {
...
} else {
throw NumberFormatException.forInputString(s);
}
string が int に変換する条件を満たす場合、String を int に変換するコード (上記の ... で省略されたコード) が実行されます。また、これを 2 つのステップで見ていきます。
まず、文字列の最初の文字によって正、負、不正を判断します。
char firstChar = s.charAt(0);
if (firstChar < '0') { // Possible leading "+" or "-"
if (firstChar == '-') {
negative = true;
limit = Integer.MIN_VALUE;
} else if (firstChar != '+') {
throw NumberFormatException.forInputString(s);
}
if (len == 1) { // Cannot have lone "+" or "-"
throw NumberFormatException.forInputString(s);
}
i++;
}
(1) 最初の文字が「-」の場合、正と負の数値を表す負の値が true に設定され、その制限は整数の最小値に設定されます。
(2) 「-」ではなく、「+」でもない場合は、他の文字を意味し、例外がスローされます。
(3) 最初の文字が「+」または「-」であっても、長さが 1 の場合、それは「+」または「-」を意味し、これも数値ではないため、例外がスローされます。
(4) 最初の文字が有効な場合は、i++ を実行し、非記号文字を調べます。
もちろん、最初の文字が記号ではなく数字の場合は、次のコードに直接進みます。
int multmin = limit / radix;
int result = 0;
while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUE
int digit = Character.digit(s.charAt(i++), radix);
if (digit < 0 || result < multmin) {
throw NumberFormatException.forInputString(s);
}
result *= radix;
if (result < limit + digit) {
throw NumberFormatException.forInputString(s);
}
result -= digit;
}
return negative ? result : -result;
(1) 文字列の文字を走査し、数値に変換できない場合に -1 を返す digit 関数を呼び出します。
(2) 文字が数字でない場合 (数字<0)、例外がスローされます。
(3) 問題がなければ、段階的に計算して int に変換し、範囲を超えるかどうかを判断し、範囲を超えた場合は例外をスローします
(4) 最後に、負の場合は result を返し、正の場合は -result を返します。
ここで、負の数が result を返し、正の数が -result を返す理由を理解できない生徒もいるかもしれません。「1234」を 10 進数に変換するなど、文字列数値を 10 進数に変換する方法は、実際には次のようになります。
((((1 ✖️10)+ 2)✖️10) + 3)✖️10 + 4
= ((12 ✖️10) + 3) ✖️10 + 4
=(120 + 3)✖️10 + 4
= 123✖️10 + 4
= 1230 + 4
= 1234
このメソッドは実際には逆に実装されます。
((((-1 ✖️10)- 2)✖️10) - 3)✖️10 - 4
= ((-12 ✖️10) - 3) ✖️10 - 4
=(-120 - 3)✖️10 - 4
= -123✖️10 - 4
= -1230 - 4
= -1234
したがって、正の累積方法ではなく、負の累積方法が使用されます。実際、コードには次のコメントがあります (最大値付近での事故を避けるための負の累積)。
// Accumulating negatively avoids surprises near MAX_VALUE
2、整数の値
2 番目のメソッドは Integer.valueOf で、最後に parseInt と呼ばれます。戻り値は int ではなく Integer であることに注意してください。ただし、ボックス化を解除する必要はなくなり、int を直接使用して戻り値を受け取ることができます。
public static Integer valueOf(String s, int radix) throws NumberFormatException {
return Integer.valueOf(parseInt(s,radix));
}
次に、valueOf の実装を見てみましょう。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
ここで驚くべきことに、Integer.valueOf(int i) メソッド、内部クラス IntegerCache が表示され、キャッシュ内のオブジェクトまたは新しい Integer オブジェクトが返されます。では、IntegerCache とは何でしょうか?
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer[] cache;
static Integer[] archivedCache;
private IntegerCache() {
}
static {
int h = 127;
String integerCacheHighPropValue = VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
int size;
if (integerCacheHighPropValue != null) {
try {
size = Integer.parseInt(integerCacheHighPropValue);
size = Math.max(size, 127);
h = Math.min(size, 2147483518);
} catch (NumberFormatException var6) {
}
}
high = h;
VM.initializeFromArchive(Integer.IntegerCache.class);
size = high - -128 + 1;
if (archivedCache == null || size > archivedCache.length) {
Integer[] c = new Integer[size];
int j = -128;
for(int k = 0; k < c.length; ++k) {
c[k] = new Integer(j++);
}
archivedCache = c;
}
cache = archivedCache;
assert high >= 127;
}
}
IntegerCache が Integer 内に保持されており、範囲は [-128,127] であることがわかります。valueOf メソッドは、値が [-128,127] の間にある場合、IntegerCache.cache に既に存在するオブジェクトへの参照を返し、それ以外の場合は新しい Integer オブジェクトを作成します。
これは実際に最初の質問の答えになります。ちなみに、ここでの 2 番目の質問、Integer.getInteger("10") が 10 ではなく null を返す理由も見てみましょう。実際、System.getProperty が呼び出されますが、これは String を int に変換することとは何の関係もありません。
public static Integer getInteger(String nm, Integer val) {
String v = null;
try {
v = System.getProperty(nm);
} catch (IllegalArgumentException | NullPointerException e) {
}
if (v != null) {
try {
return Integer.decode(v);
} catch (NumberFormatException e) {
}
}
return val;
}
4. まとめ
実際、Integer で一般的に使用されるのは、String を int に変換するこれら 2 つのメソッドにすぎません。Integer には、max、min、sum などの簡単な計算メソッドもいくつか用意されていますが、実際には使用しません。intValueメソッドもありますが、java1.6からはアンボックス化(アンボックス化)が必要ないので使えません。
この記事は、Integer の一般的に使用される 2 つのメソッドに基づいており、その主要なコードの実装を読むと、Integer が内部でキャッシュを維持していることもわかります。ソース コードの実装を分析することで、最初に挙げた 2 つの疑問にも答えました。