JDK

KeyValueHolderクラス

キーと値を保持するだけのMap.Entry<K,V>インタフェースを実装したクラスであるが,パッケージプライベートなクラスであるため,ユーザは基本的に使用できない(リフレクションでやれなくはないが,そこまでやるなら自作した方が早い)。

インスタンス変数としては,keyとvalueがある。コンストラクタはKeyValueHolder(K k, V v)だけが定義されている。

setValue(V value)メソッドを呼び出すとUnsupportedOperationExceptionがスローされる。equals(Object o)メソッドは,KeyValueHolderクラスかそのサブクラスならば,キーと値が一致すればtrue,それ以外はfalseを返す。

hashCode()メソッドではkeyのハッシュコードとvalueのハッシュコードの排他的論理和を算出して返す。toString()hashCode()メソッドではkeyとvalueを”=”で連結して返している。

/*
 * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.util;
import jdk.internal.vm.annotation.Stable;
final class KeyValueHolder<K,V> implements Map.Entry<K,V> {
    @Stable
    final K key;
    @Stable
    final V value;
    KeyValueHolder(K k, V v) {
        key = Objects.requireNonNull(k);
        value = Objects.requireNonNull(v);
    }
    @Override
    public K getKey() {
        return key;
    }
    @Override
    public V getValue() {
        return value;
    }
    @Override
    public V setValue(V value) {
        throw new UnsupportedOperationException("not supported");
    }
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        return key.equals(e.getKey()) && value.equals(e.getValue());
    }
    @Override
    public int hashCode() {
        return key.hashCode() ^ value.hashCode();
    }
    @Override
    public String toString() {
        return key + "=" + value;
    }
}
JDK

java.util.Base64クラス

java.util.Base64はJava8になってようやくSDKに導入されたBase64エンコーダー/デコーダーである。バイナリーデータをasciiコードの範囲内に収めるために伸長する仕様で,それほど難しい処理ではない。

インナークラスとしてエンコードを担うEncoderクラス,デコードを行うDecoderクラスが定義されており,実質的な処理はこのクラスが担っている。

Encoderクラスの中では,Base64エンコードする際に用いる文字をchar[]型の定数で定義している。 Encoderクラスのコンストラクタはprivateであり, isURL, newline, linemax, doPaddingという4つの引数をとる。 isURL はURLか否かを表すboolean型の引数である。URLの場合は”/”が使えないため,エンコードする際に用いるascii文字セットが一部異なる。newlineはchar[]の配列で,改行コードである。linemaxは1行の文字数であり,初期値は76に設定されている。doPaddingは余った領域をパディングするか否かを表すbooleanであり,trueの場合は”=”でパディングする。

encodedOutLength(int srclen, boolean throwOOME)メソッドは,アウトプットのバイト数を計算するメソッドである。改行コードの分を考慮しているため複雑な計算になってしまっているが,改行を考慮しなければ元のバイト数の約4/3倍と考えてよい。

encode(byte[] src)メソッドがsrcで渡されたバイト配列をbase64エンコードするpublicなメソッドである。上述のencodedOutLength()メソッドを使ってアウトプットのバイト数を求め,そのサイズのbyte[]配列を確保する。そしてencode0()メソッドに渡してエンコードする。

encode0メソッドが実際にデータをbase64エンコードする処理ロジックが書かれたものである。ここは完全にbase64エンコードの仕様そのものである。

/*
 * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the “Classpath” exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.util;
import java.io.FilterOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import jdk.internal.HotSpotIntrinsicCandidate;
public class Base64 {
    private Base64() {}
    public static Encoder getEncoder() {
         return Encoder.RFC4648;
    }
    public static Encoder getUrlEncoder() {
         return Encoder.RFC4648_URLSAFE;
    }
    public static Encoder getMimeEncoder() {
        return Encoder.RFC2045;
    }
    public static Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) {
         Objects.requireNonNull(lineSeparator);
         int[] base64 = Decoder.fromBase64;
         for (byte b : lineSeparator) {
             if (base64[b & 0xff] != -1)
                 throw new IllegalArgumentException(
                     “Illegal base64 line separator character 0x” + Integer.toString(b, 16));
         }
         // round down to nearest multiple of 4
         lineLength &= ~0b11;
         if (lineLength <= 0) {
             return Encoder.RFC4648;
         }
         return new Encoder(false, lineSeparator, lineLength, true);
    }
    public static Decoder getDecoder() {
         return Decoder.RFC4648;
    }
    public static Decoder getUrlDecoder() {
         return Decoder.RFC4648_URLSAFE;
    }
    public static Decoder getMimeDecoder() {
         return Decoder.RFC2045;
    }
    public static class Encoder {
        private final byte[] newline;
        private final int linemax;
        private final boolean isURL;
        private final boolean doPadding;
        private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) {
            this.isURL = isURL;
            this.newline = newline;
            this.linemax = linemax;
            this.doPadding = doPadding;
        }
        private static final char[] toBase64 = {
            ‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’, ‘H’, ‘I’, ‘J’, ‘K’, ‘L’, ‘M’,
            ‘N’, ‘O’, ‘P’, ‘Q’, ‘R’, ‘S’, ‘T’, ‘U’, ‘V’, ‘W’, ‘X’, ‘Y’, ‘Z’,
            ‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’, ‘i’, ‘j’, ‘k’, ‘l’, ‘m’,
            ‘n’, ‘o’, ‘p’, ‘q’, ‘r’, ‘s’, ‘t’, ‘u’, ‘v’, ‘w’, ‘x’, ‘y’, ‘z’,
            ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘+’, ‘/’
        };
        private static final char[] toBase64URL = {
            ‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’, ‘H’, ‘I’, ‘J’, ‘K’, ‘L’, ‘M’,
            ‘N’, ‘O’, ‘P’, ‘Q’, ‘R’, ‘S’, ‘T’, ‘U’, ‘V’, ‘W’, ‘X’, ‘Y’, ‘Z’,
            ‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’, ‘i’, ‘j’, ‘k’, ‘l’, ‘m’,
            ‘n’, ‘o’, ‘p’, ‘q’, ‘r’, ‘s’, ‘t’, ‘u’, ‘v’, ‘w’, ‘x’, ‘y’, ‘z’,
            ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘-‘, ‘_’
        };
        private static final int MIMELINEMAX = 76;
        private static final byte[] CRLF = new byte[] {‘\r’, ‘\n’};
        static final Encoder RFC4648 = new Encoder(false, null, -1, true);
        static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true);
        static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true);
        private final int encodedOutLength(int srclen, boolean throwOOME) {
            int len = 0;
            try {
                if (doPadding) {
                    len = Math.multiplyExact(4, (Math.addExact(srclen, 2) / 3));
                } else {
                    int n = srclen % 3;
                    len = Math.addExact(Math.multiplyExact(4, (srclen / 3)), (n == 0 ? 0 : n + 1));
                }
                if (linemax > 0) {                             // line separators
                    len = Math.addExact(len, (len – 1) / linemax * newline.length);
                }
            } catch (ArithmeticException ex) {
                if (throwOOME) {
                    throw new OutOfMemoryError(“Encoded size is too large”);
                } else {
                    // let the caller know that encoded bytes length
                    // is too large
                    len = -1;
                }
            }
            return len;
        }
        public byte[] encode(byte[] src) {
            int len = encodedOutLength(src.length, true);          // dst array size
            byte[] dst = new byte[len];
            int ret = encode0(src, 0, src.length, dst);
            if (ret != dst.length)
                 return Arrays.copyOf(dst, ret);
            return dst;
        }
        public int encode(byte[] src, byte[] dst) {
            int len = encodedOutLength(src.length, false);         // dst array size
            if (dst.length < len || len == -1)
                throw new IllegalArgumentException(
                    “Output byte array is too small for encoding all input bytes”);
            return encode0(src, 0, src.length, dst);
        }
        @SuppressWarnings(“deprecation”)
        public String encodeToString(byte[] src) {
            byte[] encoded = encode(src);
            return new String(encoded, 0, 0, encoded.length);
        }
        public ByteBuffer encode(ByteBuffer buffer) {
            int len = encodedOutLength(buffer.remaining(), true);
            byte[] dst = new byte[len];
            int ret = 0;
            if (buffer.hasArray()) {
                ret = encode0(buffer.array(),
                              buffer.arrayOffset() + buffer.position(),
                              buffer.arrayOffset() + buffer.limit(),
                              dst);
                buffer.position(buffer.limit());
            } else {
                byte[] src = new byte[buffer.remaining()];
                buffer.get(src);
                ret = encode0(src, 0, src.length, dst);
            }
            if (ret != dst.length)
                 dst = Arrays.copyOf(dst, ret);
            return ByteBuffer.wrap(dst);
        }
        public OutputStream wrap(OutputStream os) {
            Objects.requireNonNull(os);
            return new EncOutputStream(os, isURL ? toBase64URL : toBase64,
                                       newline, linemax, doPadding);
        }
        public Encoder withoutPadding() {
            if (!doPadding)
                return this;
            return new Encoder(isURL, newline, linemax, false);
        }
        @HotSpotIntrinsicCandidate
        private void encodeBlock(byte[] src, int sp, int sl, byte[] dst, int dp, boolean isURL) {
            char[] base64 = isURL ? toBase64URL : toBase64;
            for (int sp0 = sp, dp0 = dp ; sp0 < sl; ) {
                int bits = (src[sp0++] & 0xff) << 16 |
                           (src[sp0++] & 0xff) <<  8 |
                           (src[sp0++] & 0xff);
                dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f];
                dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f];
                dst[dp0++] = (byte)base64[(bits >>> 6)  & 0x3f];
                dst[dp0++] = (byte)base64[bits & 0x3f];
            }
        }
        private int encode0(byte[] src, int off, int end, byte[] dst) {
            char[] base64 = isURL ? toBase64URL : toBase64;
            int sp = off;
            int slen = (end – off) / 3 * 3;
            int sl = off + slen;
            if (linemax > 0 && slen  > linemax / 4 * 3)
                slen = linemax / 4 * 3;
            int dp = 0;
            while (sp < sl) {
                int sl0 = Math.min(sp + slen, sl);
                encodeBlock(src, sp, sl0, dst, dp, isURL);
                int dlen = (sl0 – sp) / 3 * 4;
                dp += dlen;
                sp = sl0;
                if (dlen == linemax && sp < end) {
                    for (byte b : newline){
                        dst[dp++] = b;
                    }
                }
            }
            if (sp < end) {               // 1 or 2 leftover bytes
                int b0 = src[sp++] & 0xff;
                dst[dp++] = (byte)base64[b0 >> 2];
                if (sp == end) {
                    dst[dp++] = (byte)base64[(b0 << 4) & 0x3f];
                    if (doPadding) {
                        dst[dp++] = ‘=’;
                        dst[dp++] = ‘=’;
                    }
                } else {
                    int b1 = src[sp++] & 0xff;
                    dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];
                    dst[dp++] = (byte)base64[(b1 << 2) & 0x3f];
                    if (doPadding) {
                        dst[dp++] = ‘=’;
                    }
                }
            }
            return dp;
        }
    }
    public static class Decoder {
        private final boolean isURL;
        private final boolean isMIME;
        private Decoder(boolean isURL, boolean isMIME) {
            this.isURL = isURL;
            this.isMIME = isMIME;
        }
        private static final int[] fromBase64 = new int[256];
        static {
            Arrays.fill(fromBase64, -1);
            for (int i = 0; i < Encoder.toBase64.length; i++)
                fromBase64[Encoder.toBase64[i]] = i;
            fromBase64[‘=’] = -2;
        }
        private static final int[] fromBase64URL = new int[256];
        static {
            Arrays.fill(fromBase64URL, -1);
            for (int i = 0; i < Encoder.toBase64URL.length; i++)
                fromBase64URL[Encoder.toBase64URL[i]] = i;
            fromBase64URL[‘=’] = -2;
        }
        static final Decoder RFC4648         = new Decoder(false, false);
        static final Decoder RFC4648_URLSAFE = new Decoder(true, false);
        static final Decoder RFC2045         = new Decoder(false, true);
        public byte[] decode(byte[] src) {
            byte[] dst = new byte[decodedOutLength(src, 0, src.length)];
            int ret = decode0(src, 0, src.length, dst);
            if (ret != dst.length) {
                dst = Arrays.copyOf(dst, ret);
            }
            return dst;
        }
        public byte[] decode(String src) {
            return decode(src.getBytes(StandardCharsets.ISO_8859_1));
        }
        public int decode(byte[] src, byte[] dst) {
            int len = decodedOutLength(src, 0, src.length);
            if (dst.length < len || len == -1)
                throw new IllegalArgumentException(
                    “Output byte array is too small for decoding all input bytes”);
            return decode0(src, 0, src.length, dst);
        }
        public ByteBuffer decode(ByteBuffer buffer) {
            int pos0 = buffer.position();
            try {
                byte[] src;
                int sp, sl;
                if (buffer.hasArray()) {
                    src = buffer.array();
                    sp = buffer.arrayOffset() + buffer.position();
                    sl = buffer.arrayOffset() + buffer.limit();
                    buffer.position(buffer.limit());
                } else {
                    src = new byte[buffer.remaining()];
                    buffer.get(src);
                    sp = 0;
                    sl = src.length;
                }
                byte[] dst = new byte[decodedOutLength(src, sp, sl)];
                return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst));
            } catch (IllegalArgumentException iae) {
                buffer.position(pos0);
                throw iae;
            }
        }
        public InputStream wrap(InputStream is) {
            Objects.requireNonNull(is);
            return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME);
        }
        private int decodedOutLength(byte[] src, int sp, int sl) {
            int[] base64 = isURL ? fromBase64URL : fromBase64;
            int paddings = 0;
            int len = sl – sp;
            if (len == 0)
                return 0;
            if (len < 2) {
                if (isMIME && base64[0] == -1)
                    return 0;
                throw new IllegalArgumentException(
                    “Input byte[] should at least have 2 bytes for base64 bytes”);
            }
            if (isMIME) {
                // scan all bytes to fill out all non-alphabet. a performance
                // trade-off of pre-scan or Arrays.copyOf
                int n = 0;
                while (sp < sl) {
                    int b = src[sp++] & 0xff;
                    if (b == ‘=’) {
                        len -= (sl – sp + 1);
                        break;
                    }
                    if ((b = base64[b]) == -1)
                        n++;
                }
                len -= n;
            } else {
                if (src[sl – 1] == ‘=’) {
                    paddings++;
                    if (src[sl – 2] == ‘=’)
                        paddings++;
                }
            }
            if (paddings == 0 && (len & 0x3) !=  0)
                paddings = 4 – (len & 0x3);
            // If len is near to Integer.MAX_VALUE, (len + 3)
            // can possibly overflow, perform this operation as
            // long and cast it back to integer when the value comes under
            // integer limit. The final value will always be in integer
            // limits
            return 3 * (int) ((len + 3L) / 4) – paddings;
        }
        private int decode0(byte[] src, int sp, int sl, byte[] dst) {
            int[] base64 = isURL ? fromBase64URL : fromBase64;
            int dp = 0;
            int bits = 0;
            int shiftto = 18;       // pos of first byte of 4-byte atom
            while (sp < sl) {
                if (shiftto == 18 && sp + 4 < sl) {       // fast path
                    int sl0 = sp + ((sl – sp) & ~0b11);
                    while (sp < sl0) {
                        int b1 = base64[src[sp++] & 0xff];
                        int b2 = base64[src[sp++] & 0xff];
                        int b3 = base64[src[sp++] & 0xff];
                        int b4 = base64[src[sp++] & 0xff];
                        if ((b1 | b2 | b3 | b4) < 0) {    // non base64 byte
                            sp -= 4;
                            break;
                        }
                        int bits0 = b1 << 18 | b2 << 12 | b3 << 6 | b4;
                        dst[dp++] = (byte)(bits0 >> 16);
                        dst[dp++] = (byte)(bits0 >>  8);
                        dst[dp++] = (byte)(bits0);
                    }
                    if (sp >= sl)
                        break;
                }
                int b = src[sp++] & 0xff;
                if ((b = base64[b]) < 0) {
                    if (b == -2) {         // padding byte ‘=’
                        // =     shiftto==18 unnecessary padding
                        // x=    shiftto==12 a dangling single x
                        // x     to be handled together with non-padding case
                        // xx=   shiftto==6&&sp==sl missing last =
                        // xx=y  shiftto==6 last is not =
                        if (shiftto == 6 && (sp == sl || src[sp++] != ‘=’) ||
                            shiftto == 18) {
                            throw new IllegalArgumentException(
                                “Input byte array has wrong 4-byte ending unit”);
                        }
                        break;
                    }
                    if (isMIME)    // skip if for rfc2045
                        continue;
                    else
                        throw new IllegalArgumentException(
                            “Illegal base64 character ” +
                            Integer.toString(src[sp – 1], 16));
                }
                bits |= (b << shiftto);
                shiftto -= 6;
                if (shiftto < 0) {
                    dst[dp++] = (byte)(bits >> 16);
                    dst[dp++] = (byte)(bits >>  8);
                    dst[dp++] = (byte)(bits);
                    shiftto = 18;
                    bits = 0;
                }
            }
            // reached end of byte array or hit padding ‘=’ characters.
            if (shiftto == 6) {
                dst[dp++] = (byte)(bits >> 16);
            } else if (shiftto == 0) {
                dst[dp++] = (byte)(bits >> 16);
                dst[dp++] = (byte)(bits >>  8);
            } else if (shiftto == 12) {
                // dangling single “x”, incorrectly encoded.
                throw new IllegalArgumentException(
                    “Last unit does not have enough valid bits”);
            }
            // anything left is invalid, if is not MIME.
            // if MIME, ignore all non-base64 character
            while (sp < sl) {
                if (isMIME && base64[src[sp++] & 0xff] < 0)
                    continue;
                throw new IllegalArgumentException(
                    “Input byte array has incorrect ending byte at ” + sp);
            }
            return dp;
        }
    }
    /*
     * An output stream for encoding bytes into the Base64.
     */
    private static class EncOutputStream extends FilterOutputStream {
        private int leftover = 0;
        private int b0, b1, b2;
        private boolean closed = false;
        private final char[] base64;    // byte->base64 mapping
        private final byte[] newline;   // line separator, if needed
        private final int linemax;
        private final boolean doPadding;// whether or not to pad
        private int linepos = 0;
        private byte[] buf;
        EncOutputStream(OutputStream os, char[] base64,
                        byte[] newline, int linemax, boolean doPadding) {
            super(os);
            this.base64 = base64;
            this.newline = newline;
            this.linemax = linemax;
            this.doPadding = doPadding;
            this.buf = new byte[linemax <= 0 ? 8124 : linemax];
        }
        @Override
        public void write(int b) throws IOException {
            byte[] buf = new byte[1];
            buf[0] = (byte)(b & 0xff);
            write(buf, 0, 1);
        }
        private void checkNewline() throws IOException {
            if (linepos == linemax) {
                out.write(newline);
                linepos = 0;
            }
        }
        private void writeb4(char b1, char b2, char b3, char b4) throws IOException {
            buf[0] = (byte)b1;
            buf[1] = (byte)b2;
            buf[2] = (byte)b3;
            buf[3] = (byte)b4;
            out.write(buf, 0, 4);
        }
        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            if (closed)
                throw new IOException(“Stream is closed”);
            if (off < 0 || len < 0 || len > b.length – off)
                throw new ArrayIndexOutOfBoundsException();
            if (len == 0)
                return;
            if (leftover != 0) {
                if (leftover == 1) {
                    b1 = b[off++] & 0xff;
                    len–;
                    if (len == 0) {
                        leftover++;
                        return;
                    }
                }
                b2 = b[off++] & 0xff;
                len–;
                checkNewline();
                writeb4(base64[b0 >> 2],
                        base64[(b0 << 4) & 0x3f | (b1 >> 4)],
                        base64[(b1 << 2) & 0x3f | (b2 >> 6)],
                        base64[b2 & 0x3f]);
                linepos += 4;
            }
            int nBits24 = len / 3;
            leftover = len – (nBits24 * 3);
            while (nBits24 > 0) {
                checkNewline();
                int dl = linemax <= 0 ? buf.length : buf.length – linepos;
                int sl = off + Math.min(nBits24, dl / 4) * 3;
                int dp = 0;
                for (int sp = off; sp < sl; ) {
                    int bits = (b[sp++] & 0xff) << 16 |
                               (b[sp++] & 0xff) <<  8 |
                               (b[sp++] & 0xff);
                    buf[dp++] = (byte)base64[(bits >>> 18) & 0x3f];
                    buf[dp++] = (byte)base64[(bits >>> 12) & 0x3f];
                    buf[dp++] = (byte)base64[(bits >>> 6)  & 0x3f];
                    buf[dp++] = (byte)base64[bits & 0x3f];
                }
                out.write(buf, 0, dp);
                off = sl;
                linepos += dp;
                nBits24 -= dp / 4;
            }
            if (leftover == 1) {
                b0 = b[off++] & 0xff;
            } else if (leftover == 2) {
                b0 = b[off++] & 0xff;
                b1 = b[off++] & 0xff;
            }
        }
        @Override
        public void close() throws IOException {
            if (!closed) {
                closed = true;
                if (leftover == 1) {
                    checkNewline();
                    out.write(base64[b0 >> 2]);
                    out.write(base64[(b0 << 4) & 0x3f]);
                    if (doPadding) {
                        out.write(‘=’);
                        out.write(‘=’);
                    }
                } else if (leftover == 2) {
                    checkNewline();
                    out.write(base64[b0 >> 2]);
                    out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
                    out.write(base64[(b1 << 2) & 0x3f]);
                    if (doPadding) {
                       out.write(‘=’);
                    }
                }
                leftover = 0;
                out.close();
            }
        }
    }
    /*
     * An input stream for decoding Base64 bytes
     */
    private static class DecInputStream extends InputStream {
        private final InputStream is;
        private final boolean isMIME;
        private final int[] base64;      // base64 -> byte mapping
        private int bits = 0;            // 24-bit buffer for decoding
        private int nextin = 18;         // next available “off” in “bits” for input;
                                         // -> 18, 12, 6, 0
        private int nextout = -8;        // next available “off” in “bits” for output;
                                         // -> 8, 0, -8 (no byte for output)
        private boolean eof = false;
        private boolean closed = false;
        DecInputStream(InputStream is, int[] base64, boolean isMIME) {
            this.is = is;
            this.base64 = base64;
            this.isMIME = isMIME;
        }
        private byte[] sbBuf = new byte[1];
        @Override
        public int read() throws IOException {
            return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff;
        }
        private int eof(byte[] b, int off, int len, int oldOff)
            throws IOException
        {
            eof = true;
            if (nextin != 18) {
                if (nextin == 12)
                    throw new IOException(“Base64 stream has one un-decoded dangling byte.”);
                // treat ending xx/xxx without padding character legal.
                // same logic as v == ‘=’ below
                b[off++] = (byte)(bits >> (16));
                if (nextin == 0) {           // only one padding byte
                    if (len == 1) {          // no enough output space
                        bits >>= 8;          // shift to lowest byte
                        nextout = 0;
                    } else {
                        b[off++] = (byte) (bits >>  8);
                    }
                }
            }
            return off == oldOff ? -1 : off – oldOff;
        }
        private int padding(byte[] b, int off, int len, int oldOff)
            throws IOException
        {
            // =     shiftto==18 unnecessary padding
            // x=    shiftto==12 dangling x, invalid unit
            // xx=   shiftto==6 && missing last ‘=’
            // xx=y  or last is not ‘=’
            if (nextin == 18 || nextin == 12 ||
                nextin == 6 && is.read() != ‘=’) {
                throw new IOException(“Illegal base64 ending sequence:” + nextin);
            }
            b[off++] = (byte)(bits >> (16));
            if (nextin == 0) {           // only one padding byte
                if (len == 1) {          // no enough output space
                    bits >>= 8;          // shift to lowest byte
                    nextout = 0;
                } else {
                    b[off++] = (byte) (bits >>  8);
                }
            }
            eof = true;
            return off – oldOff;
        }
        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            if (closed)
                throw new IOException(“Stream is closed”);
            if (eof && nextout < 0)    // eof and no leftover
                return -1;
            if (off < 0 || len < 0 || len > b.length – off)
                throw new IndexOutOfBoundsException();
            int oldOff = off;
            while (nextout >= 0) {       // leftover output byte(s) in bits buf
                if (len == 0)
                    return off – oldOff;
                b[off++] = (byte)(bits >> nextout);
                len–;
                nextout -= 8;
            }
            bits = 0;
            while (len > 0) {
                int v = is.read();
                if (v == -1) {
                    return eof(b, off, len, oldOff);
                }
                if ((v = base64[v]) < 0) {
                    if (v == -2) {       // padding byte(s)
                        return padding(b, off, len, oldOff);
                    }
                    if (v == -1) {
                        if (!isMIME)
                            throw new IOException(“Illegal base64 character ” +
                                Integer.toString(v, 16));
                        continue;        // skip if for rfc2045
                    }
                    // neve be here
                }
                bits |= (v << nextin);
                if (nextin == 0) {
                    nextin = 18;         // clear for next in
                    b[off++] = (byte)(bits >> 16);
                    if (len == 1) {
                        nextout = 8;    // 2 bytes left in bits
                        break;
                    }
                    b[off++] = (byte)(bits >> 8);
                    if (len == 2) {
                        nextout = 0;    // 1 byte left in bits
                        break;
                    }
                    b[off++] = (byte)bits;
                    len -= 3;
                    bits = 0;
                } else {
                    nextin -= 6;
                }
            }
            return off – oldOff;
        }
        @Override
        public int available() throws IOException {
            if (closed)
                throw new IOException(“Stream is closed”);
            return is.available();   // TBD:
        }
        @Override
        public void close() throws IOException {
            if (!closed) {
                closed = true;
                is.close();
            }
        }
    }
}
JDK

java.util.Optional<T>クラス

java.util.Optional<T>クラスはオブジェクトをラップしてnullチェックを簡素化するためにJava8から導入された比較的新しいクラスである。オブジェクトを格納するためのvalueというインスタンス変数が宣言されている。またオブジェクトが空であることを表すOptional EMPTY = new Optional<>(null)という定数が定義されている。

コンストラクタはprivateになっており,直接new演算子を使って外部からインスタンスを作成することはできない。staticメソッドのof(T value)メソッド,あるいは,ofNullable(T value)を使ってインスタンスを作成する。このstaticメソッド内でprivateなコンストラクタが呼び出されている。

get()メソッドでは,インスタンス変数valueがnullでなければvalueを返す。valueがnullの場合はNoSuchElementExceptionをスローする。

isPresent()メソッドはvalueがnullか否かをチェックする。value != nullを返すだけの実装である。isEmpty()メソッドはその逆で,実装もvalue == nullを返している。

ifPresent(Consumer<? super T> action)メソッドは,同時に導入された関数型プログラミングに対応したものである。valueがnullでない場合に実行するアクションをConsumer型の引数として受け取り,それを実行する。

ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)メソッドはifPresent (Consumer action) メソッドに加えて,valueがnullの場合の動作も定義できる。valueがnullの場合の動作はRunnableインタフェースで渡す仕様である。

filter(Predicate<? super T> predicate)メソッドは与えられた条件式に合致するオブジェクトを返すものである。条件式のtest()メソッドがtrueを返したらvalueを返し,falseを返したらEMPTYを返す。

map(Function<? super T, ? extends U> mapper)メソッドは与えられた関数をオブジェクトに適用するものだ。引数で与えられた関数オブジェクトのapply()メソッドにラップしているオブジェクトvalueを渡して,返された値をstaticメソッドofNullable()に渡して新たなOptionalクラスのインスタンスを作成する。

flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)メソッドはmapメソッドと似ているが,戻り値がOptionalではない。

or(Supplier<? extends Optional<? extends T>> supplier)メソッドは,ラップしているオブジェクトvalueがnullでない場合はそのオブジェクトを返し,nullの場合は引数で与えられたオブジェクトを返す。valueがnullの場合の実装は,引数で与えられたSupplierのget()メソッドで取り出したオブジェクトをOptional<T>にキャストしているだけである。

stream()メソッドはStream API用のstreamを作成するメソッドであり,Streamクラスのstaticメソッドof()に移譲している。

orElse(T other)メソッドはとても単純なメソッドで,ラップしているオブジェクトvalueがnullでなければvalueを,valueがnullならば引数として受け取ったotherを返す。

orElseGet(Supplier<? extends T> supplier)メソッドもorElse()メソッドとほぼ同じ実装だが,引数の型がTではなく,Supplier<? extends T>となっている。

orElseThrow()メソッド, orElseThrow(Supplier<? extends X> exceptionSupplier)メソッドは,valueが非null値の場合はvalueを返し,valueがnullの場合は例外をスローする。違いは,スローされる例外クラスが,前者はNoSuchElementExceptionであるのに対して,後者は引数でクラスを指定できる点で,後者ではexceptionSupplier.get()の戻り値をスローする。

/*
 * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.util;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
public final class Optional<T> {
    private static final Optional<?> EMPTY = new Optional<>(null);
    private final T value;
    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }
    private Optional(T value) {
        this.value = value;
    }
    public static <T> Optional<T> of(T value) {
        return new Optional<>(Objects.requireNonNull(value));
    }
    @SuppressWarnings("unchecked")
    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? (Optional<T>) EMPTY
                             : new Optional<>(value);
    }
    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }
    public boolean isPresent() {
        return value != null;
    }
    public boolean isEmpty() {
        return value == null;
    }
    public void ifPresent(Consumer<? super T> action) {
        if (value != null) {
            action.accept(value);
        }
    }
    public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
        if (value != null) {
            action.accept(value);
        } else {
            emptyAction.run();
        }
    }
    public Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent()) {
            return this;
        } else {
            return predicate.test(value) ? this : empty();
        }
    }
    public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent()) {
            return empty();
        } else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }
    public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent()) {
            return empty();
        } else {
            @SuppressWarnings("unchecked")
            Optional<U> r = (Optional<U>) mapper.apply(value);
            return Objects.requireNonNull(r);
        }
    }
    public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) {
        Objects.requireNonNull(supplier);
        if (isPresent()) {
            return this;
        } else {
            @SuppressWarnings("unchecked")
            Optional<T> r = (Optional<T>) supplier.get();
            return Objects.requireNonNull(r);
        }
    }
    public Stream<T> stream() {
        if (!isPresent()) {
            return Stream.empty();
        } else {
            return Stream.of(value);
        }
    }
    public T orElse(T other) {
        return value != null ? value : other;
    }
    public T orElseGet(Supplier<? extends T> supplier) {
        return value != null ? value : supplier.get();
    }
    public T orElseThrow() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }
    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Optional)) {
            return false;
        }
        Optional<?> other = (Optional<?>) obj;
        return Objects.equals(value, other.value);
    }
    @Override
    public int hashCode() {
        return Objects.hashCode(value);
    }
    @Override
    public String toString() {
        return value != null
            ? String.format("Optional[%s]", value)
            : "Optional.empty";
    }
}

JDK

java.util.LinkedHashMap

LinkedHashMapはHashMapクラスを,要素間にリンクを持たせて順序を保持するように拡張したクラスである。実装面においても,「extends HashMap<K, V>という記述がある。内部で保持するキーと値のペアについてもHashMapクラスのものを継承してEntry<K, V>というインナークラスが定義されている。

リンクリストの特徴を顕著に表したのが要素の先頭と末尾を表したエントリーの参照を格納したインスタンス変数headとtailだ。このほかに昇順か降順かを示すboolean型のaccessOrderというインスタンス変数が定義されている。

コンストラクタは,次の5種類が定義されている。最初の3つはスーパークラスのコンストラクタの定義とほぼ同じである。同じ引数をとるスーパークラスのコンストラクタを呼び出した上で,accessOrderはいずれもfalseをセットしている。4つめのコンストラクタは,同じようにスーパークラスの同じ引数をとるコンストラクタを呼び出して,accessOrderにfalseを設定した上で,更に, スーパークラスのputMapEntries(Map, boolean)メソッドを実行する。最後のコンストラクタは,accessOrderも設定できるコンストラクタである。
  • LinkedHashMap(int initialCapacity, float loadFactor)
  • LinkedHashMap(int initialCapacity)
  • LinkedHashMap()
  • LinkedHashMap(Map<? extends K, ? extends V> m)
  • LinkedHashMap(int initialCapacity,float loadFactor, boolean accessOrder)
  • containsValue(Object value)メソッドは,エントリーのvalueに引数と同じものが含まれるかをチェックする。先頭要素を表すhead要素からforループで順次そのafterの要素をたどっていく。そして,引数のオブジェクトと同一または同値のオブジェクトが含まれていればtrueを返す。同値または同一のオブジェクトがみつからなければ,メソッド内の最後の行でfalseを返す。

    get(Object key)メソッドは引数のキーに対応する値を取得する。処理内容はスーパークラスのgetNode()メソッドにキーのハッシュ値とキーを渡してノードを取得する。ハッシュ値を使って検索した方が高速にアクセスできる。

    値を取り出した後,afterNodeAccess(Node e)メソッドを実行して,取得したノードをリンクリストの最後に移動させている。エントリーの後ろの要素を表すafterにnullを設定し,これより後ろには要素がないことを示している。次に現在のエントリーの部分からそのエントリーへのリンクを取り除いて,前後のリンクをつなぎかえる。見つかったエントリーの前の要素が保持している自身へのリンクを,自身の次の要素へのリンクへ変更する。またみつかったエントリーの後の要素が保持している自身へのリンクを自身の前の要素へのリンクへ変更する。そして現在の最後の要素を自身の前の要素を表すbeforeへセットする。

    clear()メソッドは,スーパークラスのclear()メソッドを実行した後,先頭要素を表すheadと末尾要素を表すtailにnullをセットする。

    /*
     * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     *
     * This code is free software; you can redistribute it and/or modify it
     * under the terms of the GNU General Public License version 2 only, as
     * published by the Free Software Foundation.  Oracle designates this
     * particular file as subject to the "Classpath" exception as provided
     * by Oracle in the LICENSE file that accompanied this code.
     *
     * This code is distributed in the hope that it will be useful, but WITHOUT
     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     * version 2 for more details (a copy is included in the LICENSE file that
     * accompanied this code).
     *
     * You should have received a copy of the GNU General Public License version
     * 2 along with this work; if not, write to the Free Software Foundation,
     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     *
     * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     * or visit www.oracle.com if you need additional information or have any
     * questions.
     */
    package java.util;
    import java.util.function.Consumer;
    import java.util.function.BiConsumer;
    import java.util.function.BiFunction;
    import java.io.IOException;
    public class LinkedHashMap<K,V>
        extends HashMap<K,V>
        implements Map<K,V>
    {
        static class Entry<K,V> extends HashMap.Node<K,V> {
            Entry<K,V> before, after;
            Entry(int hash, K key, V value, Node<K,V> next) {
                super(hash, key, value, next);
            }
        }
        private static final long serialVersionUID = 3801124242820219131L;
        transient LinkedHashMap.Entry<K,V> head;
        transient LinkedHashMap.Entry<K,V> tail;
        final boolean accessOrder;
        // internal utilities
        // link at the end of list
        private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
            LinkedHashMap.Entry<K,V> last = tail;
            tail = p;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
        }
        // apply src's links to dst
        private void transferLinks(LinkedHashMap.Entry<K,V> src,
                                   LinkedHashMap.Entry<K,V> dst) {
            LinkedHashMap.Entry<K,V> b = dst.before = src.before;
            LinkedHashMap.Entry<K,V> a = dst.after = src.after;
            if (b == null)
                head = dst;
            else
                b.after = dst;
            if (a == null)
                tail = dst;
            else
                a.before = dst;
        }
        // overrides of HashMap hook methods
        void reinitialize() {
            super.reinitialize();
            head = tail = null;
        }
        Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
            LinkedHashMap.Entry<K,V> p =
                new LinkedHashMap.Entry<>(hash, key, value, e);
            linkNodeLast(p);
            return p;
        }
        Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
            LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
            LinkedHashMap.Entry<K,V> t =
                new LinkedHashMap.Entry<>(q.hash, q.key, q.value, next);
            transferLinks(q, t);
            return t;
        }
        TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
            TreeNode<K,V> p = new TreeNode<>(hash, key, value, next);
            linkNodeLast(p);
            return p;
        }
        TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
            LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
            TreeNode<K,V> t = new TreeNode<>(q.hash, q.key, q.value, next);
            transferLinks(q, t);
            return t;
        }
        void afterNodeRemoval(Node<K,V> e) { // unlink
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.before = p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a == null)
                tail = b;
            else
                a.before = b;
        }
        void afterNodeInsertion(boolean evict) { // possibly remove eldest
            LinkedHashMap.Entry<K,V> first;
            if (evict && (first = head) != null && removeEldestEntry(first)) {
                K key = first.key;
                removeNode(hash(key), key, null, false, true);
            }
        }
        void afterNodeAccess(Node<K,V> e) { // move node to last
            LinkedHashMap.Entry<K,V> last;
            if (accessOrder && (last = tail) != e) {
                LinkedHashMap.Entry<K,V> p =
                    (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
                p.after = null;
                if (b == null)
                    head = a;
                else
                    b.after = a;
                if (a != null)
                    a.before = b;
                else
                    last = b;
                if (last == null)
                    head = p;
                else {
                    p.before = last;
                    last.after = p;
                }
                tail = p;
                ++modCount;
            }
        }
        void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
            for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
                s.writeObject(e.key);
                s.writeObject(e.value);
            }
        }
        public LinkedHashMap(int initialCapacity, float loadFactor) {
            super(initialCapacity, loadFactor);
            accessOrder = false;
        }
        public LinkedHashMap(int initialCapacity) {
            super(initialCapacity);
            accessOrder = false;
        }
        public LinkedHashMap() {
            super();
            accessOrder = false;
        }
        public LinkedHashMap(Map<? extends K, ? extends V> m) {
            super();
            accessOrder = false;
            putMapEntries(m, false);
        }
        public LinkedHashMap(int initialCapacity,
                             float loadFactor,
                             boolean accessOrder) {
            super(initialCapacity, loadFactor);
            this.accessOrder = accessOrder;
        }
        public boolean containsValue(Object value) {
            for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
                V v = e.value;
                if (v == value || (value != null && value.equals(v)))
                    return true;
            }
            return false;
        }
        public V get(Object key) {
            Node<K,V> e;
            if ((e = getNode(hash(key), key)) == null)
                return null;
            if (accessOrder)
                afterNodeAccess(e);
            return e.value;
        }
        public V getOrDefault(Object key, V defaultValue) {
           Node<K,V> e;
           if ((e = getNode(hash(key), key)) == null)
               return defaultValue;
           if (accessOrder)
               afterNodeAccess(e);
           return e.value;
       }
        public void clear() {
            super.clear();
            head = tail = null;
        }
        protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
            return false;
        }
        public Set<K> keySet() {
            Set<K> ks = keySet;
            if (ks == null) {
                ks = new LinkedKeySet();
                keySet = ks;
            }
            return ks;
        }
        final class LinkedKeySet extends AbstractSet<K> {
            // 省略
        }
        public Collection<V> values() {
            Collection<V> vs = values;
            if (vs == null) {
                vs = new LinkedValues();
                values = vs;
            }
            return vs;
        }
        final class LinkedValues extends AbstractCollection<V> {
            // 省略
        }
        public Set<Map.Entry<K,V>> entrySet() {
            Set<Map.Entry<K,V>> es;
            return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
        }
        final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
            // 省略
        }
        // Map overrides
        public void forEach(BiConsumer<? super K, ? super V> action) {
            if (action == null)
                throw new NullPointerException();
            int mc = modCount;
            for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
                action.accept(e.key, e.value);
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
        public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
            if (function == null)
                throw new NullPointerException();
            int mc = modCount;
            for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
                e.value = function.apply(e.key, e.value);
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
        // Iterators
        abstract class LinkedHashIterator {
            // 省略
        }
        final class LinkedKeyIterator extends LinkedHashIterator
            // 省略
        }
        final class LinkedValueIterator extends LinkedHashIterator
            // 省略
        }
        final class LinkedEntryIterator extends LinkedHashIterator
            // 省略
        }
    }

    JDK

    java.util.HashMapクラス

    java.util.HashMapクラスはキーと値のペアを保持し,キーを使って値を取り出せるため,非常に便利なクラスである。そのため非常によく使われている。内部の実装はかなり複雑なものとなっている。ソースコードも2500行近いサイズがある。ここでは,同じソースファイルに書かれているインナークラスは省略している。

    初期のサイズはDEFAULT_INITIAL_CAPACITYという定数に1 << 4,すなわち1を4bit左シフトさせた値である2^4 = 16である。最大のサイズはMAXIMUM_CAPACITYという定数で1 << 30と定義されており,1を30bit左シフトした2^30である。

    Map.Entryインタフェースを実装した静的クラスNode<K, V>が定義されている。hash(Object key)メソッドはkeyに対応するハッシュ値を計算するメソッドである。keyがnullの場合は0, 非null値の場合はkeyのhashCode()の値とそれを16bit右シフトした値をXORした値としている。

    キーと値のペアはNode[]型のインスタンス変数tableに保持される。また,格納されている要素の数はint型の変数sizeに保持されている。

    コンストラクタは次の4種類が定義されている。1つ目のコンストラクタが基本となるもので,初期のサイズと負荷係数を引数に取る。不可係数は内部で保持している配列を拡張する際の閾値となる格納率で,ディフォルト値は0.75となっている。2番目,3番目のコンストラクタはこれらの引数の一部または全部が省略されたもので,ディフォルト値を使ってインスタンスを作成する。4つ目のコンストラクタは,コピーコンストラクタであり,Mapインタフェースを実装したクラスのインスタンスをもとに,それに含まれている各要素を持ったインスタンスを作成する。
    • HashMap(int initialCapacity, float loadFactor)
    • HashMap(int initialCapacity)
    • HashMap()
    • HashMap(Map<? extends K, ? extends V> m)

    size()メソッドはインスタンス変数sizeをそのまま返す。isEmpty()メソッドはインスタンス変数sizeが0か否かを返す実装となっている。

    get(Object key)メソッドは,HashMapクラスの肝となるメソッドである。getNode(hash(key)メソッドに,keyのハッシュ値とkeyを渡して,keyに対応するNode<K,V>型のエントリeを取得する。eがnullであればnullを返し,非null値であれば,eのvalueを返す。

    getNode(int hash, Object key)メソッドは,ハッシュ値と元のObject型のキーを使って,Mapのエントリーを検索するメソッドである。内部配列の大きさ-1とハッシュ値のビット積を計算し,その値をインデックスとするエントリーを最初の候補とする。この候補のハッシュ値とキーの値が一致した場合はこの要素を返す。もし最初の候補で一致しなかった場合には,最初の要素のnextプロパティを使って次の候補を取得し,同様にキーのハッシュ値とキーの値が一致するかを検査する。これを一致するものがみつかるか,検査対象がなくなるまで繰り返す。

    containsKey(Object key)メソッドでも getNode(int hash, Object key) メソッドにキーのハッシュ値とキーを渡して,戻り値が非null値か否かで判定している。

    put(K key, V value)メソッドは,HashMapにキーと値を格納するメソッドである。putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) メソッドに,淳にキーのハッシュ値,キー,値,false, trueという実引数を渡している。

    putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) メソッド では,キーのハッシュ値と内部配列の最大インデックスとのbit積をインデックスとして,内部配列のそのインデックス位置に何も格納されていなければ,newNode()メソッドを使って,新たなノードを作成してそれを内部配列に格納する。もしこのインデックスの位置に既にエントリーが格納されており,キーが一致しない場合は,格納済のエントリーのnextプロパティを順次たどっていき,nextがnullになったところに,新たなノードを追加する。

    /*
     * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     *
     * This code is free software; you can redistribute it and/or modify it
     * under the terms of the GNU General Public License version 2 only, as
     * published by the Free Software Foundation.  Oracle designates this
     * particular file as subject to the "Classpath" exception as provided
     * by Oracle in the LICENSE file that accompanied this code.
     *
     * This code is distributed in the hope that it will be useful, but WITHOUT
     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     * version 2 for more details (a copy is included in the LICENSE file that
     * accompanied this code).
     *
     * You should have received a copy of the GNU General Public License version
     * 2 along with this work; if not, write to the Free Software Foundation,
     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     *
     * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     * or visit www.oracle.com if you need additional information or have any
     * questions.
     */
    package java.util;
    import java.io.IOException;
    import java.io.InvalidObjectException;
    import java.io.Serializable;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    import java.util.function.BiConsumer;
    import java.util.function.BiFunction;
    import java.util.function.Consumer;
    import java.util.function.Function;
    import jdk.internal.access.SharedSecrets;
    public class HashMap<K,V> extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable, Serializable {
        private static final long serialVersionUID = 362498820763181265L;
        static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
        static final int MAXIMUM_CAPACITY = 1 << 30;
        static final float DEFAULT_LOAD_FACTOR = 0.75f;
        static final int TREEIFY_THRESHOLD = 8;
        static final int UNTREEIFY_THRESHOLD = 6;
        static final int MIN_TREEIFY_CAPACITY = 64;
        static class Node<K,V> implements Map.Entry<K,V> {
            // 省略
        }
        /* —————- Static utilities ————– */
        static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }
        static Class<?> comparableClassFor(Object x) {
            if (x instanceof Comparable) {
                Class<?> c; Type[] ts, as; ParameterizedType p;
                if ((c = x.getClass()) == String.class) // bypass checks
                    return c;
                if ((ts = c.getGenericInterfaces()) != null) {
                    for (Type t : ts) {
                        if ((t instanceof ParameterizedType) &&
                            ((p = (ParameterizedType) t).getRawType() ==
                             Comparable.class) &&
                            (as = p.getActualTypeArguments()) != null &&
                            as.length == 1 && as[0] == c) // type arg is c
                            return c;
                    }
                }
            }
            return null;
        }
        @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable
        static int compareComparables(Class<?> kc, Object k, Object x) {
            return (x == null || x.getClass() != kc ? 0 :
                    ((Comparable)k).compareTo(x));
        }
        static final int tableSizeFor(int cap) {
            int n = -1 >>> Integer.numberOfLeadingZeros(cap – 1);
            return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
        }
        /* —————- Fields ————– */
        transient Node<K,V>[] table;
        transient Set<Map.Entry<K,V>> entrySet;
        transient int size;
        transient int modCount;
        // (The javadoc description is true upon serialization.
        // Additionally, if the table array has not been allocated, this
        // field holds the initial array capacity, or zero signifying
        // DEFAULT_INITIAL_CAPACITY.)
        int threshold;
        final float loadFactor;
        /* —————- Public operations ————– */
        public HashMap(int initialCapacity, float loadFactor) {
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal initial capacity: " +
                                                   initialCapacity);
            if (initialCapacity > MAXIMUM_CAPACITY)
                initialCapacity = MAXIMUM_CAPACITY;
            if (loadFactor <= 0 || Float.isNaN(loadFactor))
                throw new IllegalArgumentException("Illegal load factor: " +
                                                   loadFactor);
            this.loadFactor = loadFactor;
            this.threshold = tableSizeFor(initialCapacity);
        }
        public HashMap(int initialCapacity) {
            this(initialCapacity, DEFAULT_LOAD_FACTOR);
        }
        public HashMap() {
            this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
        }
        public HashMap(Map<? extends K, ? extends V> m) {
            this.loadFactor = DEFAULT_LOAD_FACTOR;
            putMapEntries(m, false);
        }
        final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
            int s = m.size();
            if (s > 0) {
                if (table == null) { // pre-size
                    float ft = ((float)s / loadFactor) + 1.0F;
                    int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                             (int)ft : MAXIMUM_CAPACITY);
                    if (t > threshold)
                        threshold = tableSizeFor(t);
                } else {
                    // Because of linked-list bucket constraints, we cannot
                    // expand all at once, but can reduce total resize
                    // effort by repeated doubling now vs later
                    while (s > threshold && table.length < MAXIMUM_CAPACITY)
                        resize();
                }
                for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                    K key = e.getKey();
                    V value = e.getValue();
                    putVal(hash(key), key, value, false, evict);
                }
            }
        }
        public int size() {
            return size;
        }
        public boolean isEmpty() {
            return size == 0;
        }
        public V get(Object key) {
            Node<K,V> e;
            return (e = getNode(hash(key), key)) == null ? null : e.value;
        }
        final Node<K,V> getNode(int hash, Object key) {
            Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n – 1) & hash]) != null) {
                if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                if ((e = first.next) != null) {
                    if (first instanceof TreeNode)
                        return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            return e;
                    } while ((e = e.next) != null);
                }
            }
            return null;
        }
        public boolean containsKey(Object key) {
            return getNode(hash(key), key) != null;
        }
        public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
        final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            if ((p = tab[i = (n – 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD – 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }
        final Node<K,V>[] resize() {
            Node<K,V>[] oldTab = table;
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            int oldThr = threshold;
            int newCap, newThr = 0;
            if (oldCap > 0) {
                if (oldCap >= MAXIMUM_CAPACITY) {
                    threshold = Integer.MAX_VALUE;
                    return oldTab;
                }
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
                    newThr = oldThr << 1; // double threshold
            }
            else if (oldThr > 0) // initial capacity was placed in threshold
                newCap = oldThr;
            else {               // zero initial threshold signifies using defaults
                newCap = DEFAULT_INITIAL_CAPACITY;
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            }
            if (newThr == 0) {
                float ft = (float)newCap * loadFactor;
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                          (int)ft : Integer.MAX_VALUE);
            }
            threshold = newThr;
            @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            table = newTab;
            if (oldTab != null) {
                for (int j = 0; j < oldCap; ++j) {
                    Node<K,V> e;
                    if ((e = oldTab[j]) != null) {
                        oldTab[j] = null;
                        if (e.next == null)
                            newTab[e.hash & (newCap – 1)] = e;
                        else if (e instanceof TreeNode)
                            ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                        else { // preserve order
                            Node<K,V> loHead = null, loTail = null;
                            Node<K,V> hiHead = null, hiTail = null;
                            Node<K,V> next;
                            do {
                                next = e.next;
                                if ((e.hash & oldCap) == 0) {
                                    if (loTail == null)
                                        loHead = e;
                                    else
                                        loTail.next = e;
                                    loTail = e;
                                }
                                else {
                                    if (hiTail == null)
                                        hiHead = e;
                                    else
                                        hiTail.next = e;
                                    hiTail = e;
                                }
                            } while ((e = next) != null);
                            if (loTail != null) {
                                loTail.next = null;
                                newTab[j] = loHead;
                            }
                            if (hiTail != null) {
                                hiTail.next = null;
                                newTab[j + oldCap] = hiHead;
                            }
                        }
                    }
                }
            }
            return newTab;
        }
        final void treeifyBin(Node<K,V>[] tab, int hash) {
            int n, index; Node<K,V> e;
            if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
                resize();
            else if ((e = tab[index = (n – 1) & hash]) != null) {
                TreeNode<K,V> hd = null, tl = null;
                do {
                    TreeNode<K,V> p = replacementTreeNode(e, null);
                    if (tl == null)
                        hd = p;
                    else {
                        p.prev = tl;
                        tl.next = p;
                    }
                    tl = p;
                } while ((e = e.next) != null);
                if ((tab[index] = hd) != null)
                    hd.treeify(tab);
            }
        }
        public void putAll(Map<? extends K, ? extends V> m) {
            putMapEntries(m, true);
        }
        public V remove(Object key) {
            Node<K,V> e;
            return (e = removeNode(hash(key), key, null, false, true)) == null ?
                null : e.value;
        }
        final Node<K,V> removeNode(int hash, Object key, Object value,
                                   boolean matchValue, boolean movable) {
            Node<K,V>[] tab; Node<K,V> p; int n, index;
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (p = tab[index = (n – 1) & hash]) != null) {
                Node<K,V> node = null, e; K k; V v;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    node = p;
                else if ((e = p.next) != null) {
                    if (p instanceof TreeNode)
                        node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                    else {
                        do {
                            if (e.hash == hash &&
                                ((k = e.key) == key ||
                                 (key != null && key.equals(k)))) {
                                node = e;
                                break;
                            }
                            p = e;
                        } while ((e = e.next) != null);
                    }
                }
                if (node != null && (!matchValue || (v = node.value) == value ||
                                     (value != null && value.equals(v)))) {
                    if (node instanceof TreeNode)
                        ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                    else if (node == p)
                        tab[index] = node.next;
                    else
                        p.next = node.next;
                    ++modCount;
                    –size;
                    afterNodeRemoval(node);
                    return node;
                }
            }
            return null;
        }
        public void clear() {
            Node<K,V>[] tab;
            modCount++;
            if ((tab = table) != null && size > 0) {
                size = 0;
                for (int i = 0; i < tab.length; ++i)
                    tab[i] = null;
            }
        }
        public boolean containsValue(Object value) {
            Node<K,V>[] tab; V v;
            if ((tab = table) != null && size > 0) {
                for (Node<K,V> e : tab) {
                    for (; e != null; e = e.next) {
                        if ((v = e.value) == value ||
                            (value != null && value.equals(v)))
                            return true;
                    }
                }
            }
            return false;
        }
        public Set<K> keySet() {
            Set<K> ks = keySet;
            if (ks == null) {
                ks = new KeySet();
                keySet = ks;
            }
            return ks;
        }
        final class KeySet extends AbstractSet<K> {
            // 省略
        }
        public Collection<V> values() {
            Collection<V> vs = values;
            if (vs == null) {
                vs = new Values();
                values = vs;
            }
            return vs;
        }
        final class Values extends AbstractCollection<V> {
            // 省略
        }
        public Set<Map.Entry<K,V>> entrySet() {
            Set<Map.Entry<K,V>> es;
            return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
        }
        final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
            // 省略
        }
        // Overrides of JDK8 Map extension methods
        @Override
        public V getOrDefault(Object key, V defaultValue) {
            Node<K,V> e;
            return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
        }
        @Override
        public V putIfAbsent(K key, V value) {
            return putVal(hash(key), key, value, true, true);
        }
        @Override
        public boolean remove(Object key, Object value) {
            return removeNode(hash(key), key, value, true, true) != null;
        }
        @Override
        public boolean replace(K key, V oldValue, V newValue) {
            Node<K,V> e; V v;
            if ((e = getNode(hash(key), key)) != null &&
                ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
                e.value = newValue;
                afterNodeAccess(e);
                return true;
            }
            return false;
        }
        @Override
        public V replace(K key, V value) {
            Node<K,V> e;
            if ((e = getNode(hash(key), key)) != null) {
                V oldValue = e.value;
                e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
            return null;
        }
        @Override
        public V computeIfAbsent(K key,
                                 Function<? super K, ? extends V> mappingFunction) {
            if (mappingFunction == null)
                throw new NullPointerException();
            int hash = hash(key);
            Node<K,V>[] tab; Node<K,V> first; int n, i;
            int binCount = 0;
            TreeNode<K,V> t = null;
            Node<K,V> old = null;
            if (size > threshold || (tab = table) == null ||
                (n = tab.length) == 0)
                n = (tab = resize()).length;
            if ((first = tab[i = (n – 1) & hash]) != null) {
                if (first instanceof TreeNode)
                    old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
                else {
                    Node<K,V> e = first; K k;
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k)))) {
                            old = e;
                            break;
                        }
                        ++binCount;
                    } while ((e = e.next) != null);
                }
                V oldValue;
                if (old != null && (oldValue = old.value) != null) {
                    afterNodeAccess(old);
                    return oldValue;
                }
            }
            int mc = modCount;
            V v = mappingFunction.apply(key);
            if (mc != modCount) { throw new ConcurrentModificationException(); }
            if (v == null) {
                return null;
            } else if (old != null) {
                old.value = v;
                afterNodeAccess(old);
                return v;
            }
            else if (t != null)
                t.putTreeVal(this, tab, hash, key, v);
            else {
                tab[i] = newNode(hash, key, v, first);
                if (binCount >= TREEIFY_THRESHOLD – 1)
                    treeifyBin(tab, hash);
            }
            modCount = mc + 1;
            ++size;
            afterNodeInsertion(true);
            return v;
        }
        @Override
        public V computeIfPresent(K key,
                                  BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            if (remappingFunction == null)
                throw new NullPointerException();
            Node<K,V> e; V oldValue;
            int hash = hash(key);
            if ((e = getNode(hash, key)) != null &&
                (oldValue = e.value) != null) {
                int mc = modCount;
                V v = remappingFunction.apply(key, oldValue);
                if (mc != modCount) { throw new ConcurrentModificationException(); }
                if (v != null) {
                    e.value = v;
                    afterNodeAccess(e);
                    return v;
                }
                else
                    removeNode(hash, key, null, false, true);
            }
            return null;
        }
        @Override
        public V compute(K key,
                         BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            if (remappingFunction == null)
                throw new NullPointerException();
            int hash = hash(key);
            Node<K,V>[] tab; Node<K,V> first; int n, i;
            int binCount = 0;
            TreeNode<K,V> t = null;
            Node<K,V> old = null;
            if (size > threshold || (tab = table) == null ||
                (n = tab.length) == 0)
                n = (tab = resize()).length;
            if ((first = tab[i = (n – 1) & hash]) != null) {
                if (first instanceof TreeNode)
                    old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
                else {
                    Node<K,V> e = first; K k;
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k)))) {
                            old = e;
                            break;
                        }
                        ++binCount;
                    } while ((e = e.next) != null);
                }
            }
            V oldValue = (old == null) ? null : old.value;
            int mc = modCount;
            V v = remappingFunction.apply(key, oldValue);
            if (mc != modCount) { throw new ConcurrentModificationException(); }
            if (old != null) {
                if (v != null) {
                    old.value = v;
                    afterNodeAccess(old);
                }
                else
                    removeNode(hash, key, null, false, true);
            }
            else if (v != null) {
                if (t != null)
                    t.putTreeVal(this, tab, hash, key, v);
                else {
                    tab[i] = newNode(hash, key, v, first);
                    if (binCount >= TREEIFY_THRESHOLD – 1)
                        treeifyBin(tab, hash);
                }
                modCount = mc + 1;
                ++size;
                afterNodeInsertion(true);
            }
            return v;
        }
        @Override
        public V merge(K key, V value,
                       BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
            if (value == null || remappingFunction == null)
                throw new NullPointerException();
            int hash = hash(key);
            Node<K,V>[] tab; Node<K,V> first; int n, i;
            int binCount = 0;
            TreeNode<K,V> t = null;
            Node<K,V> old = null;
            if (size > threshold || (tab = table) == null ||
                (n = tab.length) == 0)
                n = (tab = resize()).length;
            if ((first = tab[i = (n – 1) & hash]) != null) {
                if (first instanceof TreeNode)
                    old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
                else {
                    Node<K,V> e = first; K k;
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k)))) {
                            old = e;
                            break;
                        }
                        ++binCount;
                    } while ((e = e.next) != null);
                }
            }
            if (old != null) {
                V v;
                if (old.value != null) {
                    int mc = modCount;
                    v = remappingFunction.apply(old.value, value);
                    if (mc != modCount) {
                        throw new ConcurrentModificationException();
                    }
                } else {
                    v = value;
                }
                if (v != null) {
                    old.value = v;
                    afterNodeAccess(old);
                }
                else
                    removeNode(hash, key, null, false, true);
                return v;
            } else {
                if (t != null)
                    t.putTreeVal(this, tab, hash, key, value);
                else {
                    tab[i] = newNode(hash, key, value, first);
                    if (binCount >= TREEIFY_THRESHOLD – 1)
                        treeifyBin(tab, hash);
                }
                ++modCount;
                ++size;
                afterNodeInsertion(true);
                return value;
            }
        }
        @Override
        public void forEach(BiConsumer<? super K, ? super V> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (Node<K,V> e : tab) {
                    for (; e != null; e = e.next)
                        action.accept(e.key, e.value);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
        @Override
        public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
            Node<K,V>[] tab;
            if (function == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (Node<K,V> e : tab) {
                    for (; e != null; e = e.next) {
                        e.value = function.apply(e.key, e.value);
                    }
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
        /* ———————————————————— */
        // Cloning and serialization
        @SuppressWarnings("unchecked")
        @Override
        public Object clone() {
            HashMap<K,V> result;
            try {
                result = (HashMap<K,V>)super.clone();
            } catch (CloneNotSupportedException e) {
                // this shouldn't happen, since we are Cloneable
                throw new InternalError(e);
            }
            result.reinitialize();
            result.putMapEntries(this, false);
            return result;
        }
        // These methods are also used when serializing HashSets
        final float loadFactor() { return loadFactor; }
        final int capacity() {
            return (table != null) ? table.length :
                (threshold > 0) ? threshold :
                DEFAULT_INITIAL_CAPACITY;
        }
        private void writeObject(java.io.ObjectOutputStream s)
            throws IOException {
            int buckets = capacity();
            // Write out the threshold, loadfactor, and any hidden stuff
            s.defaultWriteObject();
            s.writeInt(buckets);
            s.writeInt(size);
            internalWriteEntries(s);
        }
        private void readObject(java.io.ObjectInputStream s)
            throws IOException, ClassNotFoundException {
            // Read in the threshold (ignored), loadfactor, and any hidden stuff
            s.defaultReadObject();
            reinitialize();
            if (loadFactor <= 0 || Float.isNaN(loadFactor))
                throw new InvalidObjectException("Illegal load factor: " +
                                                 loadFactor);
            s.readInt();                // Read and ignore number of buckets
            int mappings = s.readInt(); // Read number of mappings (size)
            if (mappings < 0)
                throw new InvalidObjectException("Illegal mappings count: " +
                                                 mappings);
            else if (mappings > 0) { // (if zero, use defaults)
                // Size the table using given load factor only if within
                // range of 0.25…4.0
                float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
                float fc = (float)mappings / lf + 1.0f;
                int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
                           DEFAULT_INITIAL_CAPACITY :
                           (fc >= MAXIMUM_CAPACITY) ?
                           MAXIMUM_CAPACITY :
                           tableSizeFor((int)fc));
                float ft = (float)cap * lf;
                threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
                             (int)ft : Integer.MAX_VALUE);
                // Check Map.Entry[].class since it's the nearest public type to
                // what we're actually creating.
                SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Map.Entry[].class, cap);
                @SuppressWarnings({"rawtypes","unchecked"})
                Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
                table = tab;
                // Read the keys and values, and put the mappings in the HashMap
                for (int i = 0; i < mappings; i++) {
                    @SuppressWarnings("unchecked")
                        K key = (K) s.readObject();
                    @SuppressWarnings("unchecked")
                        V value = (V) s.readObject();
                    putVal(hash(key), key, value, false, false);
                }
            }
        }
        /* ———————————————————— */
        // iterators
        abstract class HashIterator {
            // 省略
        }
        final class KeyIterator extends HashIterator
            // 省略
        }
        final class ValueIterator extends HashIterator
            // 省略
        }
        final class EntryIterator extends HashIterator
            // 省略
        }
        /* ———————————————————— */
        // spliterators
        static class HashMapSpliterator<K,V> {
            // 省略
        }
        static final class KeySpliterator<K,V>
            extends HashMapSpliterator<K,V>
            implements Spliterator<K> {
            // 省略
        }
        static final class ValueSpliterator<K,V>
            extends HashMapSpliterator<K,V>
            implements Spliterator<V> {
            // 省略
        }
        static final class EntrySpliterator<K,V>
            // 省略
        }
        /* ———————————————————— */
        // LinkedHashMap support
        /*
         * The following package-protected methods are designed to be
         * overridden by LinkedHashMap, but not by any other subclass.
         * Nearly all other internal methods are also package-protected
         * but are declared final, so can be used by LinkedHashMap, view
         * classes, and HashSet.
         */
        // Create a regular (non-tree) node
        Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
            return new Node<>(hash, key, value, next);
        }
        // For conversion from TreeNodes to plain nodes
        Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
            return new Node<>(p.hash, p.key, p.value, next);
        }
        // Create a tree bin node
        TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
            return new TreeNode<>(hash, key, value, next);
        }
        // For treeifyBin
        TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
            return new TreeNode<>(p.hash, p.key, p.value, next);
        }
        void reinitialize() {
            table = null;
            entrySet = null;
            keySet = null;
            values = null;
            modCount = 0;
            threshold = 0;
            size = 0;
        }
        // Callbacks to allow LinkedHashMap post-actions
        void afterNodeAccess(Node<K,V> p) { }
        void afterNodeInsertion(boolean evict) { }
        void afterNodeRemoval(Node<K,V> p) { }
        // Called only from writeObject, to ensure compatible ordering.
        void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
            Node<K,V>[] tab;
            if (size > 0 && (tab = table) != null) {
                for (Node<K,V> e : tab) {
                    for (; e != null; e = e.next) {
                        s.writeObject(e.key);
                        s.writeObject(e.value);
                    }
                }
            }
        }
        /* ———————————————————— */
        // Tree bins
        static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
            // 省略
        }
    }

    JDK

    java.util.AbstractMapクラス

    AbstractMapクラスはMapインタフェースを実装するクラスのスーパークラスとなる抽象クラスである。内部で定義されているインナークラスについては省略した。コンストラクタはprotectedとなっており,サブクラスからしか呼び出せない。put(K key, V value)メソッドはこのクラスではUnsupportedOperationExceptionをスローする実装となっており,サブクラスでオーバーライドする必要がある。内部の各要素(キーと値のペア)はjava.util.Map.Entry型のオブジェクトで管理されているようだ。

    size()メソッドは,entrySet()メソッドで各要素のセットEntrySetを取得し,そのsize()メソッドを呼び出している。さらにisEmpty()メソッドはsize()メソッドの戻り値が0か否かで判定している。

    containsValue(Object value)メソッドでは, entrySet()メソッドで各要素のセットEntrySetを取得し ,そのiterator()メソッドでイテレータを取得した上で,保持している要素を順次取り出して,値が一致するものがないかチェックしている。containsKey(Object key)メソッドもほぼ同じ実装であり,こちらはキーを使って検索している。

    get(Object key)メソッドでも同じようにentrySet()メソッドで各要素のセットを取得し,そのイテレータを取得した上で順次要素を取り出し,キーが一致するオブジェクトを取得している。 remove(Object key)メソッドでも同様に順次検索を行って,見つかった要素を削除するというロジックである。

    putAll(Map m)メソッドでは,引数のmapで与えられたキーと値のペア要素をすべて追加する。引数のマップのentrySet()ですべての要素を取り出し,拡張forループでループさせてすべてのキーと値をput(key, value)メソッドで追加している。

    clear()メソッドでは,entrySet()メソッドで保持している要素のセットを取得して,そのclear()メソッドを実行する。

    keySet()メソッドでは,メソッド内でAbstractSetを継承した無名インナークラスのインスタンスを作成して,それを返すようになっている。values()メソッドでも,メソッド内でAbstractCollectionを継承した無名インナークラスのインスタンスを作成して,それを返す実装である。なお,entrySet()メソッドは抽象メソッドになっている。

    /*
     * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     *
     * This code is free software; you can redistribute it and/or modify it
     * under the terms of the GNU General Public License version 2 only, as
     * published by the Free Software Foundation.  Oracle designates this
     * particular file as subject to the "Classpath" exception as provided
     * by Oracle in the LICENSE file that accompanied this code.
     *
     * This code is distributed in the hope that it will be useful, but WITHOUT
     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     * version 2 for more details (a copy is included in the LICENSE file that
     * accompanied this code).
     *
     * You should have received a copy of the GNU General Public License version
     * 2 along with this work; if not, write to the Free Software Foundation,
     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     *
     * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     * or visit www.oracle.com if you need additional information or have any
     * questions.
     */
    package java.util;
    import java.util.Map.Entry;
    public abstract class AbstractMap<K,V> implements Map<K,V> {
        protected AbstractMap() {
        }
        // Query Operations
        public int size() {
            return entrySet().size();
        }
        public boolean isEmpty() {
            return size() == 0;
        }
        public boolean containsValue(Object value) {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            if (value==null) {
                while (i.hasNext()) {
                    Entry<K,V> e = i.next();
                    if (e.getValue()==null)
                        return true;
                }
            } else {
                while (i.hasNext()) {
                    Entry<K,V> e = i.next();
                    if (value.equals(e.getValue()))
                        return true;
                }
            }
            return false;
        }
        public boolean containsKey(Object key) {
            Iterator<Map.Entry<K,V>> i = entrySet().iterator();
            if (key==null) {
                while (i.hasNext()) {
                    Entry<K,V> e = i.next();
                    if (e.getKey()==null)
                        return true;
                }
            } else {
                while (i.hasNext()) {
                    Entry<K,V> e = i.next();
                    if (key.equals(e.getKey()))
                        return true;
                }
            }
            return false;
        }
        public V get(Object key) {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            if (key==null) {
                while (i.hasNext()) {
                    Entry<K,V> e = i.next();
                    if (e.getKey()==null)
                        return e.getValue();
                }
            } else {
                while (i.hasNext()) {
                    Entry<K,V> e = i.next();
                    if (key.equals(e.getKey()))
                        return e.getValue();
                }
            }
            return null;
        }
        // Modification Operations
        public V put(K key, V value) {
            throw new UnsupportedOperationException();
        }
        public V remove(Object key) {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            Entry<K,V> correctEntry = null;
            if (key==null) {
                while (correctEntry==null && i.hasNext()) {
                    Entry<K,V> e = i.next();
                    if (e.getKey()==null)
                        correctEntry = e;
                }
            } else {
                while (correctEntry==null && i.hasNext()) {
                    Entry<K,V> e = i.next();
                    if (key.equals(e.getKey()))
                        correctEntry = e;
                }
            }
            V oldValue = null;
            if (correctEntry !=null) {
                oldValue = correctEntry.getValue();
                i.remove();
            }
            return oldValue;
        }
        // Bulk Operations
        public void putAll(Map<? extends K, ? extends V> m) {
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
                put(e.getKey(), e.getValue());
        }
        public void clear() {
            entrySet().clear();
        }
        // Views
        transient Set<K>        keySet;
        transient Collection<V> values;
        public Set<K> keySet() {
            Set<K> ks = keySet;
            if (ks == null) {
                ks = new AbstractSet<K>() {
                    public Iterator<K> iterator() {
                        return new Iterator<K>() {
                            private Iterator<Entry<K,V>> i = entrySet().iterator();
                            public boolean hasNext() {
                                return i.hasNext();
                            }
                            public K next() {
                                return i.next().getKey();
                            }
                            public void remove() {
                                i.remove();
                            }
                        };
                    }
                    public int size() {
                        return AbstractMap.this.size();
                    }
                    public boolean isEmpty() {
                        return AbstractMap.this.isEmpty();
                    }
                    public void clear() {
                        AbstractMap.this.clear();
                    }
                    public boolean contains(Object k) {
                        return AbstractMap.this.containsKey(k);
                    }
                };
                keySet = ks;
            }
            return ks;
        }
        public Collection<V> values() {
            Collection<V> vals = values;
            if (vals == null) {
                vals = new AbstractCollection<V>() {
                    public Iterator<V> iterator() {
                        return new Iterator<V>() {
                            private Iterator<Entry<K,V>> i = entrySet().iterator();
                            public boolean hasNext() {
                                return i.hasNext();
                            }
                            public V next() {
                                return i.next().getValue();
                            }
                            public void remove() {
                                i.remove();
                            }
                        };
                    }
                    public int size() {
                        return AbstractMap.this.size();
                    }
                    public boolean isEmpty() {
                        return AbstractMap.this.isEmpty();
                    }
                    public void clear() {
                        AbstractMap.this.clear();
                    }
                    public boolean contains(Object v) {
                        return AbstractMap.this.containsValue(v);
                    }
                };
                values = vals;
            }
            return vals;
        }
        public abstract Set<Entry<K,V>> entrySet();
        // Comparison and hashing
        public boolean equals(Object o) {
            if (o == this)
                return true;
            if (!(o instanceof Map))
                return false;
            Map<?,?> m = (Map<?,?>) o;
            if (m.size() != size())
                return false;
            try {
                for (Entry<K, V> e : entrySet()) {
                    K key = e.getKey();
                    V value = e.getValue();
                    if (value == null) {
                        if (!(m.get(key) == null && m.containsKey(key)))
                            return false;
                    } else {
                        if (!value.equals(m.get(key)))
                            return false;
                    }
                }
            } catch (ClassCastException unused) {
                return false;
            } catch (NullPointerException unused) {
                return false;
            }
            return true;
        }
        public int hashCode() {
            int h = 0;
            for (Entry<K, V> entry : entrySet())
                h += entry.hashCode();
            return h;
        }
        public String toString() {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            if (! i.hasNext())
                return "{}";
            StringBuilder sb = new StringBuilder();
            sb.append('{');
            for (;;) {
                Entry<K,V> e = i.next();
                K key = e.getKey();
                V value = e.getValue();
                sb.append(key   == this ? "(this Map)" : key);
                sb.append('=');
                sb.append(value == this ? "(this Map)" : value);
                if (! i.hasNext())
                    return sb.append('}').toString();
                sb.append(',').append(' ');
            }
        }
        protected Object clone() throws CloneNotSupportedException {
            AbstractMap<?,?> result = (AbstractMap<?,?>)super.clone();
            result.keySet = null;
            result.values = null;
            return result;
        }
        private static boolean eq(Object o1, Object o2) {
            return o1 == null ? o2 == null : o1.equals(o2);
        }
        // Implementation Note: SimpleEntry and SimpleImmutableEntry
        // are distinct unrelated classes, even though they share
        // some code. Since you can't add or subtract final-ness
        // of a field in a subclass, they can't share representations,
        // and the amount of duplicated code is too small to warrant
        // exposing a common abstract class.
        public static class SimpleEntry<K,V>
            implements Entry<K,V>, java.io.Serializable
        {
    省略
        }
        public static class SimpleImmutableEntry<K,V>
            implements Entry<K,V>, java.io.Serializable
        {
    省略
        }
    }
    JDK

    java.util.HashSetクラス

    HashSetは少々驚きの実装となっている。Set<E>の要素を保持するコレクションが,HashMap<E, Object>型のインスタンス変数mapになっている。すなわち,Setと言いつつも,中身はHashMapなのである。MapのKeyだけを利用してぶSetにみせかけているということだ。

    publicなコンストラクタは次の4種類が定義されている。一つ目のコンストラクタはmap = new HashMap<>()という実装である。2つめのコンストラクタは引数のコレクションに含まれるオブジェクトからSetを作成するものである。作成するHashMapの大きさをMath.max((int) (c.size()/.75f) + 1, 16)という式で計算している。0.75というのはdefault load factorとコメントされている。このload factorは,3つ目のコンストラクタの第2引数にも登場している。HashMapの実装を参照する必要がある。3つめと4つ目のコンストラクタでは,初期のHashMapのサイズを指定できる。
    • HashSet()
    • HashSet(Collection<? extends E> c)
    • HashSet(int initialCapacity, float loadFactor)
    • HashSet(int initialCapacity)

    この後のメソッドは,内部がHashMap<E, Object>であることを考えれば,容易に想像できるものとなっている。iterator()メソッドはHashMapからkeySet()メソッドでキーのセットを取得してそのiterator()メソッドを呼び出している。size()メソッド,isEmpty()メソッド,clear()メソッド,remove(Object o)メソッドはそのまま内部で要素を保持しているHashMapの同じメソッドを呼び出す。contains(Object o)メソッドもHashMapのcontainsKey(o)メソッドを呼び出しているだけである。

    add(E e)メソッドは,要素を保持しているHashMapのput(e, PRESENT)を実行する。PRESENTはダミーのオブジェクトで,final Object PRESENT = new Object()と定義されたprivateなクラス変数である。

    このほか,オブジェクトをシリアライズ,デシリアライズするためのメソッドとして,privateなwriteObject(ObjectOutputStream s)メソッドとreadObject(ObjectInputStream s)というメソッドが定義されている。

    /*
     * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     *
     * This code is free software; you can redistribute it and/or modify it
     * under the terms of the GNU General Public License version 2 only, as
     * published by the Free Software Foundation.  Oracle designates this
     * particular file as subject to the "Classpath" exception as provided
     * by Oracle in the LICENSE file that accompanied this code.
     *
     * This code is distributed in the hope that it will be useful, but WITHOUT
     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     * version 2 for more details (a copy is included in the LICENSE file that
     * accompanied this code).
     *
     * You should have received a copy of the GNU General Public License version
     * 2 along with this work; if not, write to the Free Software Foundation,
     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     *
     * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     * or visit www.oracle.com if you need additional information or have any
     * questions.
     */
    package java.util;
    import java.io.InvalidObjectException;
    import jdk.internal.access.SharedSecrets;
    public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable
    {
        static final long serialVersionUID = -5024744406713321676L;
        private transient HashMap<E,Object> map;
        // Dummy value to associate with an Object in the backing Map
        private static final Object PRESENT = new Object();
        public HashSet() {
            map = new HashMap<>();
        }
        public HashSet(Collection<? extends E> c) {
            map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
            addAll(c);
        }
        public HashSet(int initialCapacity, float loadFactor) {
            map = new HashMap<>(initialCapacity, loadFactor);
        }
        public HashSet(int initialCapacity) {
            map = new HashMap<>(initialCapacity);
        }
        HashSet(int initialCapacity, float loadFactor, boolean dummy) {
            map = new LinkedHashMap<>(initialCapacity, loadFactor);
        }
        public Iterator<E> iterator() {
            return map.keySet().iterator();
        }
        public int size() {
            return map.size();
        }
        public boolean isEmpty() {
            return map.isEmpty();
        }
        public boolean contains(Object o) {
            return map.containsKey(o);
        }
        public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }
        public boolean remove(Object o) {
            return map.remove(o)==PRESENT;
        }
        public void clear() {
            map.clear();
        }
        @SuppressWarnings("unchecked")
        public Object clone() {
            try {
                HashSet<E> newSet = (HashSet<E>) super.clone();
                newSet.map = (HashMap<E, Object>) map.clone();
                return newSet;
            } catch (CloneNotSupportedException e) {
                throw new InternalError(e);
            }
        }
        private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException {
            // Write out any hidden serialization magic
            s.defaultWriteObject();
            // Write out HashMap capacity and load factor
            s.writeInt(map.capacity());
            s.writeFloat(map.loadFactor());
            // Write out size
            s.writeInt(map.size());
            // Write out all elements in the proper order.
            for (E e : map.keySet())
                s.writeObject(e);
        }
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            // Read in any hidden serialization magic
            s.defaultReadObject();
            // Read capacity and verify non-negative.
            int capacity = s.readInt();
            if (capacity < 0) {
                throw new InvalidObjectException("Illegal capacity: " +
                                                 capacity);
            }
            // Read load factor and verify positive and non NaN.
            float loadFactor = s.readFloat();
            if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
                throw new InvalidObjectException("Illegal load factor: " +
                                                 loadFactor);
            }
            // Read size and verify non-negative.
            int size = s.readInt();
            if (size < 0) {
                throw new InvalidObjectException("Illegal size: " +
                                                 size);
            }
            // Set the capacity according to the size and load factor ensuring that
            // the HashMap is at least 25% full but clamping to maximum capacity.
            capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
                    HashMap.MAXIMUM_CAPACITY);
            // Constructing the backing map will lazily create an array when the first element is
            // added, so check it before construction. Call HashMap.tableSizeFor to compute the
            // actual allocation size. Check Map.Entry[].class since it's the nearest public type to
            // what is actually created.
            SharedSecrets.getJavaObjectInputStreamAccess()
                         .checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity));
            // Create backing HashMap
            map = (((HashSet<?>)this) instanceof LinkedHashSet ?
                   new LinkedHashMap<>(capacity, loadFactor) :
                   new HashMap<>(capacity, loadFactor));
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                @SuppressWarnings("unchecked")
                    E e = (E) s.readObject();
                map.put(e, PRESENT);
            }
        }
        public Spliterator<E> spliterator() {
            return new HashMap.KeySpliterator<>(map, 0, -1, 0, 0);
        }
    }
    JDK

    java.util.AbstractSetクラス

    AbstractSetクラスはSetインタフェースを実装するクラスの共通する処理を定義している。

    equals(Object o)メソッドでは,オブジェクトがSetではない場合と,セットの大きさが異なる場合は直ちにfalseを返すが,セットの大きさが同じ場合は,すべてのオブジェクトを比較対象が持っているかをチェックする。

    removeAll(Collection c)メソッドでは,引数のコレクションに含まれるすべてのオブジェクトを削除する。イテレータを使ってループを回し,一つひとつ削除していくというわかりやすい実装となっている。

    /*
     * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     *
     * This code is free software; you can redistribute it and/or modify it
     * under the terms of the GNU General Public License version 2 only, as
     * published by the Free Software Foundation.  Oracle designates this
     * particular file as subject to the "Classpath" exception as provided
     * by Oracle in the LICENSE file that accompanied this code.
     *
     * This code is distributed in the hope that it will be useful, but WITHOUT
     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     * version 2 for more details (a copy is included in the LICENSE file that
     * accompanied this code).
     *
     * You should have received a copy of the GNU General Public License version
     * 2 along with this work; if not, write to the Free Software Foundation,
     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     *
     * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     * or visit www.oracle.com if you need additional information or have any
     * questions.
     */
    package java.util;
    public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {
        protected AbstractSet() {
        }
        // Comparison and hashing
        public boolean equals(Object o) {
            if (o == this)
                return true;
            if (!(o instanceof Set))
                return false;
            Collection<?> c = (Collection<?>) o;
            if (c.size() != size())
                return false;
            try {
                return containsAll(c);
            } catch (ClassCastException | NullPointerException unused) {
                return false;
            }
        }
        public int hashCode() {
            int h = 0;
            Iterator<E> i = iterator();
            while (i.hasNext()) {
                E obj = i.next();
                if (obj != null)
                    h += obj.hashCode();
            }
            return h;
        }
        public boolean removeAll(Collection<?> c) {
            Objects.requireNonNull(c);
            boolean modified = false;
            if (size() > c.size()) {
                for (Object e : c)
                    modified |= remove(e);
            } else {
                for (Iterator<?> i = iterator(); i.hasNext(); ) {
                    if (c.contains(i.next())) {
                        i.remove();
                        modified = true;
                    }
                }
            }
            return modified;
        }
    }
    JDK

    java.util.ArrayListクラス

    ArrayListクラスはインスタンスを作成することのできるListである。ソースファイルには,インナークラスがいくつか定義されているが,これらは省略する。

     リストの各要素はelementDataというObject[]型のインスタンス変数 に格納されている。ジェネリクスにより型パラメータを与えたとしても,内部ではObject型になっている。transient修飾子がついているので,リストの各要素はシリアライズの対象ではないようだ。要素数はsizeというインスタンス変数に格納されている。

    コンストラクタは次の3種類が定義されている。1つ目のコンストラクタは初期のリストの長さを指定してインスタンスを作成する。2つめのコンストラクタは空のリストを作成する。3つ目のコンストラクタは他のコレクションからリストを作成するものである。引数で渡されたコレクションのtoArray()メソッドを実行して,配列として要素を取り出す。この配列がObject[]型ではない場合は,Arrays.copyOf()メソッドで,Object[]型の配列にコピーしている。” (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)”というコメントが入っているので,toArray()メソッドでObject%5B%5D以外の型を返されるバグがあったのであろうか。
    • ArrayList(int initialCapacity)
    • ArrayList()
    • ArrayList(Collection c)

    trimToSize()メソッドでは,内部で要素を保持している配列サイズが要素数より大きい場合に,Arrays.copyOf()メソッドで新たな配列にコピーしている。privateなメソッドであるが,grow(int minCapacity)メソッドでは,要素を保持している配列を新しい配列を作ってコピーする。新しい配列サイズはnewCapacity(int minCapacity)メソッドで計算している。grow()メソッドは現在の要素数に1を加えてgrow(int)を呼び出す。何度も拡張するのは効率が悪いだろう。要素数が見積もれる場合には,予め大きなサイズの配列を用意しておいた方が効率的だ。

    newCapacity(int minCapacity)メソッドでは,リストの各要素を保持している配列サイズとその1バイト右シフトした値の和(つまり現在の大きさの50%増)を新しい要素のサイズとしている。そのほかにはオーバーフローやインデックスの範囲のチェックなどのチェックが行われる。

    size()メソッドは,インスタンス変数sizeをそのまま返している。またisEmpty()size()メソッドは,size == 0の評価結果を返している。contains(Object o)メソッドでは,indexOf(o)で引数のオブジェクトを検索し,そのインデックスが0以上か否かを返す。

    indexOf(Object o)メソッドは引数で渡されたオブジェクトを検索する。内部では,indexOfRange(Object o, int start, int end)メソッドにstart=0, end = sizeを渡して,リストの先頭から順次ループしながら検索する。lastIndexOf(Object o)メソッドはリストの末尾から検索するもので,実装はindexOfとほぼ同じである。

    toArray()メソッドは,Arrays.copyOf()メソッドで,要素を保持している配列をコピーして返す。

    toArray(T[] a)メソッドは,(T[]) Arrays.copyOf(elementData, size, a.getClass())という実装であり,内部のObject[]をT[]にキャストしているだけであることがはっきりと読み取れる。

    get(int index)メソッドでも,elementData(index)を呼び出しており,その実装を見ると,return (E) elementData[index]となっている。ここでもObject型の要素をE型にキャストしている。一方,set(int index, E element)メソッドでは,格納する配列がObject型の配列であるので,キャストすることなく格納できる。

    add(E e)メソッドではE型の要素を追加する。内部では,privateなメソッドadd(e, elementData, size)メソッドを実行する。ここでは,要素を格納する配列に追加する余裕がなければgrow()メソッドで拡張し,インデックスがsizeの位置に要素を格納して,sizeをインクリメントする。

    remove(int index)メソッドは指定したインデックスの位置にある要素を削除する。内部ではprivateなメソッドfastRemove(Object[] es, int i)を呼び出している。この中では,要素数を表すsizeをデクリメントして,削除対象のオブジェクトのインデックスより大きな位置にあるオブジェクトをすべてつめてコピーする。そして,最後の要素にnullをセットしている。

    /*
     * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     *
     * This code is free software; you can redistribute it and/or modify it
     * under the terms of the GNU General Public License version 2 only, as
     * published by the Free Software Foundation.  Oracle designates this
     * particular file as subject to the "Classpath" exception as provided
     * by Oracle in the LICENSE file that accompanied this code.
     *
     * This code is distributed in the hope that it will be useful, but WITHOUT
     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     * version 2 for more details (a copy is included in the LICENSE file that
     * accompanied this code).
     *
     * You should have received a copy of the GNU General Public License version
     * 2 along with this work; if not, write to the Free Software Foundation,
     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     *
     * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     * or visit www.oracle.com if you need additional information or have any
     * questions.
     */
    package java.util;
    import java.util.function.Consumer;
    import java.util.function.Predicate;
    import java.util.function.UnaryOperator;
    import jdk.internal.access.SharedSecrets;
    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        private static final long serialVersionUID = 8683452581122892189L;
        private static final int DEFAULT_CAPACITY = 10;
        private static final Object[] EMPTY_ELEMENTDATA = {};
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
        transient Object[] elementData; // non-private to simplify nested class access
        private int size;
        public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
        public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();
            if ((size = elementData.length) != 0) {
                // defend against c.toArray (incorrectly) not returning Object[]
                // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                // replace with empty array.
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }
        public void trimToSize() {
            modCount++;
            if (size < elementData.length) {
                elementData = (size == 0)
                  ? EMPTY_ELEMENTDATA
                  : Arrays.copyOf(elementData, size);
            }
        }
        public void ensureCapacity(int minCapacity) {
            if (minCapacity > elementData.length
                && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
                     && minCapacity <= DEFAULT_CAPACITY)) {
                modCount++;
                grow(minCapacity);
            }
        }
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE – 8;
        private Object[] grow(int minCapacity) {
            return elementData = Arrays.copyOf(elementData,
                                               newCapacity(minCapacity));
        }
        private Object[] grow() {
            return grow(size + 1);
        }
        private int newCapacity(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity – minCapacity <= 0) {
                if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                    return Math.max(DEFAULT_CAPACITY, minCapacity);
                if (minCapacity < 0) // overflow
                    throw new OutOfMemoryError();
                return minCapacity;
            }
            return (newCapacity – MAX_ARRAY_SIZE <= 0)
                ? newCapacity
                : hugeCapacity(minCapacity);
        }
        private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE)
                ? Integer.MAX_VALUE
                : MAX_ARRAY_SIZE;
        }
        public int size() {
            return size;
        }
        public boolean isEmpty() {
            return size == 0;
        }
        public boolean contains(Object o) {
            return indexOf(o) >= 0;
        }
        public int indexOf(Object o) {
            return indexOfRange(o, 0, size);
        }
        int indexOfRange(Object o, int start, int end) {
            Object[] es = elementData;
            if (o == null) {
                for (int i = start; i < end; i++) {
                    if (es[i] == null) {
                        return i;
                    }
                }
            } else {
                for (int i = start; i < end; i++) {
                    if (o.equals(es[i])) {
                        return i;
                    }
                }
            }
            return -1;
        }
        public int lastIndexOf(Object o) {
            return lastIndexOfRange(o, 0, size);
        }
        int lastIndexOfRange(Object o, int start, int end) {
            Object[] es = elementData;
            if (o == null) {
                for (int i = end – 1; i >= start; i–) {
                    if (es[i] == null) {
                        return i;
                    }
                }
            } else {
                for (int i = end – 1; i >= start; i–) {
                    if (o.equals(es[i])) {
                        return i;
                    }
                }
            }
            return -1;
        }
        public Object clone() {
            try {
                ArrayList<?> v = (ArrayList<?>) super.clone();
                v.elementData = Arrays.copyOf(elementData, size);
                v.modCount = 0;
                return v;
            } catch (CloneNotSupportedException e) {
                // this shouldn't happen, since we are Cloneable
                throw new InternalError(e);
            }
        }
        public Object[] toArray() {
            return Arrays.copyOf(elementData, size);
        }
        @SuppressWarnings("unchecked")
        public <T> T[] toArray(T[] a) {
            if (a.length < size)
                // Make a new array of a's runtime type, but my contents:
                return (T[]) Arrays.copyOf(elementData, size, a.getClass());
            System.arraycopy(elementData, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }
        // Positional Access Operations
        @SuppressWarnings("unchecked")
        E elementData(int index) {
            return (E) elementData[index];
        }
        @SuppressWarnings("unchecked")
        static <E> E elementAt(Object[] es, int index) {
            return (E) es[index];
        }
        public E get(int index) {
            Objects.checkIndex(index, size);
            return elementData(index);
        }
        public E set(int index, E element) {
            Objects.checkIndex(index, size);
            E oldValue = elementData(index);
            elementData[index] = element;
            return oldValue;
        }
        private void add(E e, Object[] elementData, int s) {
            if (s == elementData.length)
                elementData = grow();
            elementData[s] = e;
            size = s + 1;
        }
        public boolean add(E e) {
            modCount++;
            add(e, elementData, size);
            return true;
        }
        public void add(int index, E element) {
            rangeCheckForAdd(index);
            modCount++;
            final int s;
            Object[] elementData;
            if ((s = size) == (elementData = this.elementData).length)
                elementData = grow();
            System.arraycopy(elementData, index,
                             elementData, index + 1,
                             s – index);
            elementData[index] = element;
            size = s + 1;
        }
        public E remove(int index) {
            Objects.checkIndex(index, size);
            final Object[] es = elementData;
            @SuppressWarnings("unchecked") E oldValue = (E) es[index];
            fastRemove(es, index);
            return oldValue;
        }
        public boolean equals(Object o) {
            if (o == this) {
                return true;
            }
            if (!(o instanceof List)) {
                return false;
            }
            final int expectedModCount = modCount;
            // ArrayList can be subclassed and given arbitrary behavior, but we can
            // still deal with the common case where o is ArrayList precisely
            boolean equal = (o.getClass() == ArrayList.class)
                ? equalsArrayList((ArrayList<?>) o)
                : equalsRange((List<?>) o, 0, size);
            checkForComodification(expectedModCount);
            return equal;
        }
        boolean equalsRange(List<?> other, int from, int to) {
            final Object[] es = elementData;
            if (to > es.length) {
                throw new ConcurrentModificationException();
            }
            var oit = other.iterator();
            for (; from < to; from++) {
                if (!oit.hasNext() || !Objects.equals(es[from], oit.next())) {
                    return false;
                }
            }
            return !oit.hasNext();
        }
        private boolean equalsArrayList(ArrayList<?> other) {
            final int otherModCount = other.modCount;
            final int s = size;
            boolean equal;
            if (equal = (s == other.size)) {
                final Object[] otherEs = other.elementData;
                final Object[] es = elementData;
                if (s > es.length || s > otherEs.length) {
                    throw new ConcurrentModificationException();
                }
                for (int i = 0; i < s; i++) {
                    if (!Objects.equals(es[i], otherEs[i])) {
                        equal = false;
                        break;
                    }
                }
            }
            other.checkForComodification(otherModCount);
            return equal;
        }
        private void checkForComodification(final int expectedModCount) {
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
        }
        public int hashCode() {
            int expectedModCount = modCount;
            int hash = hashCodeRange(0, size);
            checkForComodification(expectedModCount);
            return hash;
        }
        int hashCodeRange(int from, int to) {
            final Object[] es = elementData;
            if (to > es.length) {
                throw new ConcurrentModificationException();
            }
            int hashCode = 1;
            for (int i = from; i < to; i++) {
                Object e = es[i];
                hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());
            }
            return hashCode;
        }
        public boolean remove(Object o) {
            final Object[] es = elementData;
            final int size = this.size;
            int i = 0;
            found: {
                if (o == null) {
                    for (; i < size; i++)
                        if (es[i] == null)
                            break found;
                } else {
                    for (; i < size; i++)
                        if (o.equals(es[i]))
                            break found;
                }
                return false;
            }
            fastRemove(es, i);
            return true;
        }
        private void fastRemove(Object[] es, int i) {
            modCount++;
            final int newSize;
            if ((newSize = size – 1) > i)
                System.arraycopy(es, i + 1, es, i, newSize – i);
            es[size = newSize] = null;
        }
        public void clear() {
            modCount++;
            final Object[] es = elementData;
            for (int to = size, i = size = 0; i < to; i++)
                es[i] = null;
        }
        public boolean addAll(Collection<? extends E> c) {
            Object[] a = c.toArray();
            modCount++;
            int numNew = a.length;
            if (numNew == 0)
                return false;
            Object[] elementData;
            final int s;
            if (numNew > (elementData = this.elementData).length – (s = size))
                elementData = grow(s + numNew);
            System.arraycopy(a, 0, elementData, s, numNew);
            size = s + numNew;
            return true;
        }
        public boolean addAll(int index, Collection<? extends E> c) {
            rangeCheckForAdd(index);
            Object[] a = c.toArray();
            modCount++;
            int numNew = a.length;
            if (numNew == 0)
                return false;
            Object[] elementData;
            final int s;
            if (numNew > (elementData = this.elementData).length – (s = size))
                elementData = grow(s + numNew);
            int numMoved = s – index;
            if (numMoved > 0)
                System.arraycopy(elementData, index,
                                 elementData, index + numNew,
                                 numMoved);
            System.arraycopy(a, 0, elementData, index, numNew);
            size = s + numNew;
            return true;
        }
        protected void removeRange(int fromIndex, int toIndex) {
            if (fromIndex > toIndex) {
                throw new IndexOutOfBoundsException(
                        outOfBoundsMsg(fromIndex, toIndex));
            }
            modCount++;
            shiftTailOverGap(elementData, fromIndex, toIndex);
        }
        private void shiftTailOverGap(Object[] es, int lo, int hi) {
            System.arraycopy(es, hi, es, lo, size – hi);
            for (int to = size, i = (size -= hi – lo); i < to; i++)
                es[i] = null;
        }
        private void rangeCheckForAdd(int index) {
            if (index > size || index < 0)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
        private String outOfBoundsMsg(int index) {
            return "Index: "+index+", Size: "+size;
        }
        private static String outOfBoundsMsg(int fromIndex, int toIndex) {
            return "From Index: " + fromIndex + " > To Index: " + toIndex;
        }
        public boolean removeAll(Collection<?> c) {
            return batchRemove(c, false, 0, size);
        }
        public boolean retainAll(Collection<?> c) {
            return batchRemove(c, true, 0, size);
        }
        boolean batchRemove(Collection<?> c, boolean complement,
                            final int from, final int end) {
            Objects.requireNonNull(c);
            final Object[] es = elementData;
            int r;
            // Optimize for initial run of survivors
            for (r = from;; r++) {
                if (r == end)
                    return false;
                if (c.contains(es[r]) != complement)
                    break;
            }
            int w = r++;
            try {
                for (Object e; r < end; r++)
                    if (c.contains(e = es[r]) == complement)
                        es[w++] = e;
            } catch (Throwable ex) {
                // Preserve behavioral compatibility with AbstractCollection,
                // even if c.contains() throws.
                System.arraycopy(es, r, es, w, end – r);
                w += end – r;
                throw ex;
            } finally {
                modCount += end – w;
                shiftTailOverGap(es, w, end);
            }
            return true;
        }
        private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException {
            // Write out element count, and any hidden stuff
            int expectedModCount = modCount;
            s.defaultWriteObject();
            // Write out size as capacity for behavioral compatibility with clone()
            s.writeInt(size);
            // Write out all elements in the proper order.
            for (int i=0; i<size; i++) {
                s.writeObject(elementData[i]);
            }
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
        }
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            // Read in size, and any hidden stuff
            s.defaultReadObject();
            // Read in capacity
            s.readInt(); // ignored
            if (size > 0) {
                // like clone(), allocate array based upon size not capacity
                SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
                Object[] elements = new Object[size];
                // Read in all elements in the proper order.
                for (int i = 0; i < size; i++) {
                    elements[i] = s.readObject();
                }
                elementData = elements;
            } else if (size == 0) {
                elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new java.io.InvalidObjectException("Invalid size: " + size);
            }
        }
        public ListIterator<E> listIterator(int index) {
            rangeCheckForAdd(index);
            return new ListItr(index);
        }
        public ListIterator<E> listIterator() {
            return new ListItr(0);
        }
        public Iterator<E> iterator() {
            return new Itr();
        }
        private class Itr implements Iterator<E> {
        }
        private class ListItr extends Itr implements ListIterator<E> {
        }
        public List<E> subList(int fromIndex, int toIndex) {
            subListRangeCheck(fromIndex, toIndex, size);
            return new SubList<>(this, fromIndex, toIndex);
        }
        private static class SubList<E> extends AbstractList<E> implements RandomAccess {
        }
        @Override
        public void forEach(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            final int expectedModCount = modCount;
            final Object[] es = elementData;
            final int size = this.size;
            for (int i = 0; modCount == expectedModCount && i < size; i++)
                action.accept(elementAt(es, i));
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
        @Override
        public Spliterator<E> spliterator() {
            return new ArrayListSpliterator(0, -1, 0);
        }
        final class ArrayListSpliterator implements Spliterator<E> {
        }
        // A tiny bit set implementation
        private static long[] nBits(int n) {
            return new long[((n – 1) >> 6) + 1];
        }
        private static void setBit(long[] bits, int i) {
            bits[i >> 6] |= 1L << i;
        }
        private static boolean isClear(long[] bits, int i) {
            return (bits[i >> 6] & (1L << i)) == 0;
        }
        @Override
        public boolean removeIf(Predicate<? super E> filter) {
            return removeIf(filter, 0, size);
        }
        boolean removeIf(Predicate<? super E> filter, int i, final int end) {
            Objects.requireNonNull(filter);
            int expectedModCount = modCount;
            final Object[] es = elementData;
            // Optimize for initial run of survivors
            for (; i < end && !filter.test(elementAt(es, i)); i++)
                ;
            // Tolerate predicates that reentrantly access the collection for
            // read (but writers still get CME), so traverse once to find
            // elements to delete, a second pass to physically expunge.
            if (i < end) {
                final int beg = i;
                final long[] deathRow = nBits(end – beg);
                deathRow[0] = 1L;   // set bit 0
                for (i = beg + 1; i < end; i++)
                    if (filter.test(elementAt(es, i)))
                        setBit(deathRow, i – beg);
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
                modCount++;
                int w = beg;
                for (i = beg; i < end; i++)
                    if (isClear(deathRow, i – beg))
                        es[w++] = es[i];
                shiftTailOverGap(es, w, end);
                return true;
            } else {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
                return false;
            }
        }
        @Override
        public void replaceAll(UnaryOperator<E> operator) {
            replaceAllRange(operator, 0, size);
            modCount++;
        }
        private void replaceAllRange(UnaryOperator<E> operator, int i, int end) {
            Objects.requireNonNull(operator);
            final int expectedModCount = modCount;
            final Object[] es = elementData;
            for (; modCount == expectedModCount && i < end; i++)
                es[i] = operator.apply(elementAt(es, i));
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
        @Override
        @SuppressWarnings("unchecked")
        public void sort(Comparator<? super E> c) {
            final int expectedModCount = modCount;
            Arrays.sort((E[]) elementData, 0, size, c);
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            modCount++;
        }
        void checkInvariants() {
            // assert size >= 0;
            // assert size == elementData.length || elementData[size] == null;
        }
    }