Пример внутреннего класса

В классе AbstractList есть внутренний класс Itr. Это реализация интерфейса Iterator, который позволяет поочередно получать элементы коллекций:


private class Itr implements Iterator<E> {
	int cursor = 0;
	int lastRet = -1;
	int expectedModCount = modCount;
 
	public boolean hasNext() {
    		return cursor != size();
	}
 
	public E next() {
    	checkForComodification();
    	try {
        	int i = cursor;
        	E next = get(i);
        	lastRet = i;
        	cursor = i + 1;
        	return next;
    	} catch (IndexOutOfBoundsException e) {
        	checkForComodification();
        	throw new NoSuchElementException(e);
    	}
	}
 
	public void remove() {
    	if (lastRet < 0)
        	throw new IllegalStateException();
    	checkForComodification();
 
    	try {
        	AbstractList.this.remove(lastRet);
        	if (lastRet < cursor)
            	cursor--;
        	lastRet = -1;
        	expectedModCount = modCount;
    	} catch (IndexOutOfBoundsException e) {
   	     throw new ConcurrentModificationException();
    	}
	}
 
	final void checkForComodification() {
    	if (modCount != expectedModCount)
        	throw new ConcurrentModificationException();
	}
}

Используется он в методе iterator:


public Iterator<E> iterator() {
	return new Itr();
}

Таким образом любой класс-наследник AbstractList получает готовый рабочий итератор. А если нужно все-таки кастомизировать итератор, можно реализовать свой класс, унаследовав его от Iterator или Itr, и переопределить метод iterator. Например, как это сделано в классе ArrayList.

Класс Itr — нестатический. Благодаря этому у экземпляра Itr есть ссылка на экземпляр AbstractList, и он может обращаться к его методам (size, get, remove).

Пример внутреннего статического класса

В классе Integer есть внутренний класс IntegerCache:


private static class IntegerCache {
	static final int low = -128;
	static final int high;
	static final Integer[] cache;
	static Integer[] archivedCache;
 
	static {
    	int h = 127;
    	String integerCacheHighPropValue =
        	VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
    	if (integerCacheHighPropValue != null) {
        	try {
            	h = Math.max(parseInt(integerCacheHighPropValue), 127);
            	h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
        	} catch( NumberFormatException nfe) {
        	}
    	}
    	high = h;
 
    	VM.initializeFromArchive(IntegerCache.class);
    	int size = (high - low) + 1;
 
    	if (archivedCache == null || size > archivedCache.length) {
        	Integer[] c = new Integer[size];
        	int j = low;
        	for(int i = 0; i < c.length; i++) {
            	c[i] = new Integer(j++);
        	}
        	archivedCache = c;
    	}
    	cache = archivedCache;
    	assert IntegerCache.high >= 127;
}
 
	private IntegerCache() {}
}

IntegerCache инкапсулирует в себе генерацию кеша, хранит диапазона кеша и сами закешированные значения. Таким образом все, что касается кеша, вынесено в отдельный класс. Это упрощает чтение кода и внесение в него изменений. Код использования класса:


public static Integer valueOf(int i) {
	if (i >= IntegerCache.low && i <= IntegerCache.high)
    		return IntegerCache.cache[i + (-IntegerCache.low)];
	return new Integer(i);
}

Класс IntegerCache не обращается к нестатическим полям и методам класса Integer, а обращение к нему происходит только в статическом методе valueOf. То есть можно сказать, что он привязан ко всему классу Integer, а не к отдельным его экземплярам, — и поэтому IntegerCache статический.

Пример внутреннего анонимного класса

В качестве примера анонимного класса возьмем класс InputStream и его статический метод nullInputStream:


public static InputStream nullInputStream() {
    return new InputStream() {
    	private volatile boolean closed;
 
    	private void ensureOpen() throws IOException {
        	if (closed) {
            		throw new IOException("Stream closed");
        	}
    	}
 
    	@Override
    	public int available () throws IOException {
        	ensureOpen();
        	return 0;
    	}
 
    	@Override
    	public int read() throws IOException {
        	ensureOpen();
        	return -1;
    	}
 
    	@Override
    	public int read(byte[] b, int off, int len) throws IOException {
        	Objects.checkFromIndexSize(off, len, b.length);
        	if (len == 0) {
            		return 0;
        	}
        	ensureOpen();
        	return -1;
    	}
 
    	@Override
    	public byte[] readAllBytes() throws IOException {
        	ensureOpen();
        	return new byte[0];
    	}
 
    	@Override
    	public int readNBytes(byte[] b, int off, int len)throws IOException {
        	Objects.checkFromIndexSize(off, len, b.length);
        	ensureOpen();
        	return 0;
    	}
 
    	@Override
   	 public byte[] readNBytes(int len) throws IOException {
        	if (len < 0) {
            		throw new IllegalArgumentException("len < 0");
        	}
        	ensureOpen();
        	return new byte[0];
    	}
 
    	@Override
    	public long skip(long n) throws IOException {
        	ensureOpen();
        	return 0L;
    	}
 
    	@Override
    	public void skipNBytes(long n) throws IOException {
        	ensureOpen();
        	if (n > 0) {
            		throw new EOFException();
        	}
    	}
 
    	@Override
    	public long transferTo(OutputStream out) throws IOException {
        	Objects.requireNonNull(out);
        	ensureOpen();
        	return 0L;
    	}
 
    	@Override
    	public void close() throws IOException {
        	closed = true;
    	}
    };
}

Метод возвращает пустой InputStream, а для его реализации как раз используется анонимный класс. Так как у класса не предполагались наследники, его сделали анонимным.

С добавлением в Java Stream API анонимные классы стали использовать повсеместно: все лямбда-выражения — это анонимные классы, реализующие тот или иной функциональный интерфейс. Рассмотрим примеры.

В классе AbstractStringBuilder — родитель знаменитых StringBuilder и StringBuffer:


@Override
public IntStream chars() {
	return StreamSupport.intStream(
        	() -> {
            	byte[] val = this.value;
            	int count = this.count;
            	byte coder = this.coder;
            	return coder == LATIN1
                   	? new StringLatin1.CharsSpliterator(val, 0, count, 0)
                   	: new StringUTF16.CharsSpliterator(val, 0, count, 0);
        	},
        	Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED,
        	false);
}

В классе Files — метод для конвертации Closeable в Runnable:


private static Runnable asUncheckedRunnable(Closeable c) {
	return () -> {
    	try {
        	c.close();
    	} catch (IOException e) {
        	throw new UncheckedIOException(e);
    	}
	};
}

В классе Class — метод для строкового отображения метода:


private String methodToString(String name, Class<?>[] argTypes) {
	return getName() + '.' + name +
        	((argTypes == null || argTypes.length == 0) ?
        	"()" :
        	Arrays.stream(argTypes)
        	        .map(c -> c == null ? "null" : c.getName())
                	.collect(Collectors.joining(",", "(", ")")));
}