発生元情報を保持するnullっぽい何か

Informative Null Pointer (1) - d.y.dで仰ってるのは、こんな感じでしょうか。確かに、あったら便利そう。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class Nil {
    /**
     * 指定したインタフェースクラスのNilオブジェクトを生成、返却する。
     * 生成したNilオブジェクトは、全てのメソッド呼び出しに対して、
     * Nilオブジェクト生成時のスタックトレースを保持した{@link NullPointerException}をスローする。
     * 指定したクラスがインタフェースではない場合、{@link IllegalArgumentException}をスローする。
     * @param <T> 指定したNilオブジェクトのクラスと同一のクラス
     * @param clazz 作成するNilオブジェクトのインタフェースクラス
     * @return 指定したインタフェースクラスのNullオブジェクト
     * @throws IllegalArgumentException 指定したクラスがインタフェースではない場合
     */
    @SuppressWarnings("unchecked")
    public static <T> T nil(final Class<T> clazz) {
        if (!clazz.isInterface()) throw new IllegalArgumentException("class must be interface.");
        Class<?> proxyClass = Proxy.getProxyClass(clazz.getClassLoader(), clazz);
        try {
            return (T)proxyClass.getConstructor(InvocationHandler.class).newInstance(
                    new InvocationHandler() {
                        private final NilPointer e = removeInternalStackTraceElements(
                                new NilPointer());
                        @Override
                        public Object invoke(final Object proxy, final Method method,
                                final Object[] args) throws Throwable {
                            NullPointerException nulpo = removeInternalStackTraceElements(
                                    new NullPointerException());
                            nulpo.initCause(e);
                            throw nulpo;
                        }
                    }
                );
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * スタックトレースの内、内部情報として持つ先頭2要素を削除して返却する。
     */
    private static <T extends Throwable> T removeInternalStackTraceElements(T t) {
        t.setStackTrace(
                Arrays.asList(t.getStackTrace())
                    .subList(2, t.getStackTrace().length)
                        .toArray(new StackTraceElement[0]));
        return t;
    }
    
    /**
     * Nilオブジェクト生成時のスタック情報を管理するクラス。
     */
    private static class NilPointer extends NullPointerException {}
    
}
public class NilTest {
    public static Runnable usingNil() {
        return Nil.nil(Runnable.class);
    }
    public static Runnable usingNull() {
        return null;
    }
    public static void main(String[] args) {
        try {
            Runnable r = NilTest.usingNull();
            r.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        try {
            Runnable r = NilTest.usingNil();
            r.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
usingNull:
java.lang.NullPointerException
	at NilTest.main(NilTest.java:12)

usingNil:
java.lang.NullPointerException
	at NilTest.main(NilTest.java:19)
Caused by: Nil$NilPointer
	at NilTest.usingNil(NilTest.java:4)
	at NilTest.main(NilTest.java:18)

他、ダミーオブジェクト生成のライブラリとかもあるから、見様見真似で作れば、「生成するのはインタフェースのみ」なんて嫌な制約も減らせるはず。

とは言え、これってJavaVMとか言語レベルで解決してくれない限り、結局APIのほとんどをラップしなきゃいけないことには変わりないわけで、結局「ぬるぽ可能性を緩やかに許容する」のが現実的でしょうね。