Приведение CharBuffer и StringBuilder к суперинтерфейсу
И StringBuilder, и CharBuffer реализуют интерфейсы CharSequence и Appendable. При объявлении суперинтерфейса
public interface IAppendableCharSequence extends CharSequence, Appendable{}
тогда я могу привести CharBuffer к IAppendableCharSequence, но не к StringBuilder:
private IAppendableCharSequence m_buffer;
// ...
m_buffer = (IAppendableCharSequence) CharBuffer.allocate(512); // ok
m_buffer = (IAppendableCharSequence) new StringBuilder(512); // Cannot cast from StringBuilder to IAppendableCharSequence
Это почему? Спасибо!
3 ответа
Я могу привести CharBuffer к IAppendableCharSequence
На самом деле вы не можете. Вы можете привести только экземпляр класса к типу, который класс намеренно реализует. Тот факт, что CharBuffer реализует Appendable и CharSequence, не означает, что он реализует интерфейс IAppendableCharSequence.
Компилятор разрешает приведение, потому что он не может сказать, что именно будет возвращено CharBuffer.allocate(512)
, Насколько известно компилятору, он может вернуть подкласс CharBuffer, который явно реализует IAppendableCharSequence. Но если объект на самом деле не реализует этот интерфейс, приведение вызовет исключение ClassCastException во время выполнения.
В то время как, new StringBuilder(512)
гарантирует, что новый объект является StringBuilder, а не подклассом этого, поэтому компилятор может видеть во время компиляции, что приведение не будет работать.
Одним из решений вашей проблемы является создание универсальной оболочки, которая реализует ваш интерфейс:
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;
}
};
}
(Заявленный csq
а также a
там переменные не обязательны, так как можно вызывать одни и те же методы t
напрямую, но дополнительные переменные делают возвращаемый объект IAppendableCharSequence немного быстрее, избегая необходимости выполнять приведение при каждом вызове одного из его методов. Объявление этих переменных также выполняет предварительную проверку безопасности на предмет того, что вызывающая сторона не обошла непатентованные значения, что в противном случае могло бы вызвать сбой только при попытке использовать возвращенный IAppendableCharSequence.)
Когда у вас есть этот метод, вы можете сделать оба из них:
m_buffer = wrap(CharBuffer.allocate(512));
m_buffer = wrap(new StringBuilder(512));
Вы также можете вызывать его с помощью чего-либо еще, реализующего как CharSequence, так и Appendable.
Ни один из них на самом деле не будет работать во время выполнения, но причина, по которой компилятор разрешает одно, а не другое, состоит в том, что StringBuilder
является final
а также CharBuffer
нет.
Компилятор точно знает, что ничего такого instanceof StringBuilder
может когда-либо быть действительной реализацией IAppendableCharSequence
так как StringBuilder
сам не реализует этот интерфейс, и будучи final
у него не может быть подклассов. Таким образом, нет обстоятельств, при которых такое приведение может быть законным, и компилятор отклоняет его.
В случае CharBuffer
компилятор не имеет такой гарантии, потому что вы можете создать собственный подкласс CharBuffer
что реализует IAppendableCharSequence
,
Правила, для которых приведение типов допустимо компилятором, а какие нет в Спецификации языка Java, в данном случае это раздел 5.1.6 (сужающее ссылочное преобразование), который, среди прочего, разрешает преобразование
Из любого типа класса
C
к любому параметризованному типу интерфейсаK
, при условии, чтоC
не являетсяfinal
и не реализуетK
Т.е. приведение из любого типа класса к любому типу интерфейса, не реализованному этим классом, допустимо при условии, что класс не является окончательным.
@ Ян Робертс прав.
Другая часть головоломки состоит в том, что система типов Java обрабатывает IAppendableCharSequence
как нечто большее, чем просто CharSequence
а также Appendable
, Это на самом деле сам по себе тип, который может иметь связанную семантику... такую, что "любой старый класс", который является одновременно CharSequence
а также Appendable
не соответствует требованиям.
Это означает, что нет CharBuffer
или же StringBuilder
это IAppendableCharSequence
, хотя они оба реализуют CharSequence
а также Appendable
интерфейсы.
Java-интерфейсы - это больше, чем псевдонимы C или C++, даже если они расширяют только другие интерфейсы...