Search code examples
javastringbuildercharsequence

Casting CharBuffer and StringBuilder to superinterface


Both StringBuilder and CharBuffer implement CharSequence and Appendable interfaces. When declaring the superinterface

 public  interface IAppendableCharSequence extends CharSequence, Appendable{}

then I can cast CharBuffer to IAppendableCharSequence, but not StringBuilder:

 private IAppendableCharSequence m_buffer;

 // ...

 m_buffer = (IAppendableCharSequence) CharBuffer.allocate(512); // ok


 m_buffer = (IAppendableCharSequence) new StringBuilder(512); // Cannot cast from StringBuilder to IAppendableCharSequence

Why is that? Thanks!


Solution

  • I can cast CharBuffer to IAppendableCharSequence

    Actually you can't. You can only cast an instance of a class to a type that the class deliberately implements. The fact that a CharBuffer implements Appendable and CharSequence does not mean it implements the IAppendableCharSequence interface.

    The compiler allows the cast because it cannot tell what exactly will be returned by CharBuffer.allocate(512). As far as the compiler knows, it could return a subclass of CharBuffer that does explicitly implement IAppendableCharSequence. But if the object doesn't really implement that interface, the cast will throw a ClassCastException at run time.

    Whereas, new StringBuilder(512) guarantees that the new object is a StringBuilder and not a subclass of that, so the compiler can see at compile time that the cast won't work.

    One solution to your problem is to make a generic wrapper that does implement your interface:

    public static <T extends CharSequence & Appendable> IAppendableCharSequence wrap(T t) {
        if (t == null) throw new NullPointerException();
        final CharSequence csq = t;
        final Appendable a = t;
        return new IAppendableCharSequence() {
            @Override
            public int length() {
                return csq.length();
            }
    
            @Override
            public char charAt(int index) {
                return csq.charAt(index);
            }
    
            @Override
            public CharSequence subSequence(int start, int end) {
                return csq.subSequence(start, end);
            }
    
            @Override
            public Appendable append(CharSequence s) throws IOException {
                a.append(s);
                return this;
            }
    
            @Override
            public Appendable append(CharSequence s, int start, int end) throws IOException {
                a.append(s, start, end);
                return this;
            }
    
            @Override
            public Appendable append(char c) throws IOException {
                a.append(c);
                return this;
            }
        };
    }
    

    (The declared csq and a variables are not strictly necessary there, since one can call the same methods on t directly, but the extra variables make the returned IAppendableCharSequence object a little faster by avoiding it needing to do a cast every time one of its methods is called. Declaring those variables also does an early safety check that the caller hasn't bypassed the generics, which would otherwise cause failure only when trying to use the returned IAppendableCharSequence.)

    Once you have that method, you can do both of these:

    m_buffer = wrap(CharBuffer.allocate(512));
    m_buffer = wrap(new StringBuilder(512));
    

    You can also call it with anything else that implements both CharSequence and Appendable.