Javaソースコード解析(1) 整数

    Java 言語をある程度習得した場合、または Java の共通クラスや API をすでにスムーズに使用できるようになった場合。何か考えはありますか?たとえば、このクラスはどのように設計されていますか? このメソッドはどのように実装されているのでしょうか? 次の一連の記事では、Java のいくつかの一般的なクラスのソース コードを学びましょう。今回はIntegerのソースコードを一緒に解析してみましょう。

目次

1 つまたは 2 つの整数の質問

2、整数クラス図

3. 文字列を int に変換する    

1、Integer.parseInt

2、整数の値

4. まとめ 


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 つの疑問にも答えました。

おすすめ

転載: blog.csdn.net/qq_21154101/article/details/132380640