Типичным объявлением переменной является
extern int x;
. Поскольку это только объявление, требуется одно определение. Соответствующим определением будет:
int x;
Например, следующее генерирует ошибку:
extern int x;
int main()
{
x = 0;
}
//int x; // uncomment this line for successful definition
Аналогичные замечания относятся к функциям. Объявление функции без ее определения приводит к ошибке:
void foo(); // declaration only
int main()
{
foo();
}
//void foo() {} //uncomment this line for successful definition
Будьте осторожны, чтобы выполняемая вами функция точно соответствовала той, которую вы объявили. Например, у вас могут быть несогласованные cv-квалификаторы:
void foo(int& x);
int main()
{
int x;
foo(x);
}
void foo(const int& x) {} //different function, doesn't provide a definition
//for void foo(int& x)
Другие примеры несоответствий включают
Сообщение об ошибке из компилятора часто дает вам полное объявление переменной или функции, которая была объявлена, но не определена. Сравните его с определением, которое вы указали. Убедитесь, что каждая деталь соответствует.
super
для привязки параметра именованного типа (например, <S super T>
) в отличие от подстановочного знака (например, <? super T>
) НЕЗАКОННО просто потому, что даже если это разрешено, оно не будет делать то, что вы надеялись do, потому что поскольку Object
является конечной super
всех ссылочных типов, и все является Object
, , в действительности нет привязки .
В вашем конкретном например, поскольку любой массив ссылочного типа является Object[]
(по ковариации Java-массива), поэтому он может использоваться как аргумент <S super T> S[] toArray(S[] a)
(если такая привязка является законной) во время компиляции , и это не помешало бы ArrayStoreException
во время выполнения.
То, что вы пытаетесь предложить, заключается в том, что данный:
List<Integer> integerList;
и с учетом этого гипотетического super
, связанный с toArray
:
<S super T> S[] toArray(S[] a) // hypothetical! currently illegal in Java
, компилятор должен разрешать следующее:
integerList.toArray(new Integer[0]) // works fine!
integerList.toArray(new Number[0]) // works fine!
integerList.toArray(new Object[0]) // works fine!
и другие аргументы типа массива ( поскольку Integer
имеет только эти 3 типа в качестве super
). То есть вы пытаетесь предотвратить это от компиляции:
integerList.toArray(new String[0]) // trying to prevent this from compiling
, потому что по вашему аргументу String
не является super
в Integer
. Однако , Object
является super
в Integer
, а String[]
является Object[]
, поэтому компилятор все еще позволил бы скомпилировать выше, даже если гипотетически вы можете сделать <S super T>
!
Таким образом, следующий все еще будет компилировать (так же, как и сейчас), а ArrayStoreException
во время выполнения не препятствовать какой-либо проверке времени компиляции с использованием ограничений общего типа:
integerList.toArray(new String[0]) // compiles fine!
// throws ArrayStoreException at run-time
Дженерики и массивы не смешиваются, и это одно из многих мест, где оно показано.
Опять же, скажем, что у вас есть это объявление универсального метода:
<T super Integer> void add(T number) // hypothetical! currently illegal in Java
И у вас есть эти объявления переменных:
Integer anInteger
Number aNumber
Object anObject
String aString
Ваше намерение с <T super Integer>
(если оно законно) заключается в том, что оно должно разрешать add(anInteger)
и add(aNumber)
, и, конечно, add(anObject)
, но NOT add(aString)
. String
- Object
, поэтому add(aString)
все равно будет компилироваться.
В правилах типизации генериков:
List<Animal> animals = new ArrayList<Dog>()
? List
отличается от List<Object>
, который отличается от List<?>
При использовании super
и extends
:
Java Generics: What is PECS?
Из Эффективное Java 2nd Edition : «производитель extends
потребитель super
» super
и extends
в Java Generics <E extends Number>
и <Number>
? List<? extends Number>
структуры данных? (ВЫ НЕ МОЖЕТЕ!) «Официальный» ответ на ваш вопрос можно найти в отчете об ошибке Sun / Oracle .
BT2: ОЦЕНКА
См.
http://lampwww.epfl.ch/~odersky/ftp/local-ti.ps
, в частности раздел 3 и последний абзац на странице 9. Переменные типа допуска с обеих сторон ограничений подтипа могут приводить к набору уравнений типа без единого наилучшего решения; следовательно, вывод типа не может быть выполнен с использованием каких-либо существующих стандартных алгоритмов. Вот почему переменные типа имеют только «расширяющие» границы.
С другой стороны, подстановочные знаки не обязательно должны быть выведены, поэтому нет необходимости в этом ограничении.
@ ###. ### 2004-05-25
Да; ключевым моментом является то, что подстановочные знаки, даже если они были захвачены, используются только в качестве входных данных процесса вывода; ничто с (только) нижняя граница должна быть выведена в результате.
@ ###. ### 2004-05-26
Я вижу проблему. Но я не вижу, как он отличается от проблем, которые мы имеем с нижними границами в подстановочных знаках во время вывода, например ::
List & lt ;? super Number & gt; s; boolean b; ... s = b? s: s;
В настоящее время мы выводим список & lt; X & gt; где X расширяет Object как тип условного выражения, что означает, что присвоение является незаконным.
@ ###. ### 2004-05-26
blockquote>К сожалению, разговор заканчивается. Бумага, на которую ссылалась (теперь мертвая) ссылка, была Inferred Type Instantiation для GJ . От взгляда на последнюю страницу это сводится к: если допускаются нижние границы, вывод типа может давать несколько решений, ни один из которых main .
Предположим, что у нас есть:
class A{
void methodA(){}
};
class B extends A{
void methodB(){}
}
class C extends B{
void methodC(){}
}
class D {
void methodD(){}
}
interface Job<T> {
void exec(T t);
}
class JobOnA implements Job<A>{
@Override
public void exec(A a) {
a.methodA();
}
}
class JobOnB implements Job<B>{
@Override
public void exec(B b) {
b.methodB();
}
}
class JobOnC implements Job<C>{
@Override
public void exec(C c) {
c.methodC();
}
}
class JobOnD implements Job<D>{
@Override
public void exec(D d) {
d.methodD();
}
}
class Manager<T>{
final T t;
Manager(T t){
this.t=t;
}
public void execute1(Job<T> job){
job.exec(t);
}
public <U> void execute2(Job<U> job){
U u= (U) t; //not safe
job.exec(u);
}
public <U extends T> void execute3(Job<U> job){
U u= (U) t; //not safe
job.exec(u);
}
//desired feature, not compiled for now
public <U super T> void execute4(Job<U> job){
U u= (U) t; //safe
job.exec(u);
}
}
void usage(){
B b = new B();
Manager<B> managerB = new Manager<>(b);
//TOO STRICT
managerB.execute1(new JobOnA());
managerB.execute1(new JobOnB()); //compiled
managerB.execute1(new JobOnC());
managerB.execute1(new JobOnD());
//TOO MUCH FREEDOM
managerB.execute2(new JobOnA()); //compiled
managerB.execute2(new JobOnB()); //compiled
managerB.execute2(new JobOnC()); //compiled !!
managerB.execute2(new JobOnD()); //compiled !!
//NOT ADEQUATE RESTRICTIONS
managerB.execute3(new JobOnA());
managerB.execute3(new JobOnB()); //compiled
managerB.execute3(new JobOnC()); //compiled !!
managerB.execute3(new JobOnD());
//SHOULD BE
managerB.execute4(new JobOnA()); //compiled
managerB.execute4(new JobOnB()); //compiled
managerB.execute4(new JobOnC());
managerB.execute4(new JobOnD());
}
Любые предложения по реализации execute4 сейчас?
========== edit =======
public void execute4(Job<? super T> job){
job.exec( t);
}
Спасибо всем:)
======= === edit ==========
private <U> void execute2(Job<U> job){
U u= (U) t; //now it's safe
job.exec(u);
}
public void execute4(Job<? super T> job){
execute2(job);
}
гораздо лучше, любой код с U внутри execute2
супер тип U становится именованным!
интересное обсуждение:)
Поскольку никто не дал удовлетворительного ответа, правильный ответ кажется «из-за недостатка языка Java».
polygenelubricants предоставили хороший обзор плохих вещей, происходящих с ковариантностью Java-массива, которая это ужасная особенность сама по себе. Рассмотрим следующий фрагмент кода:
String[] strings = new String[1];
Object[] objects = strings;
objects[0] = 0;
Этот явно неправильный код компилируется, не прибегая к какой-либо «супер» конструкции, поэтому ковариация массива не должна использоваться в качестве аргумента.
Теперь, здесь у меня есть вполне допустимый пример кода, требующего super
в параметре named type:
class Nullable<A> {
private A value;
// Does not compile!!
public <B super A> B withDefault(B defaultValue) {
return value == null ? defaultValue : value;
}
}
Потенциально поддерживая некоторое приятное использование:
Nullable<Integer> intOrNull = ...;
Integer i = intOrNull.withDefault(8);
Number n = intOrNull.withDefault(3.5);
Object o = intOrNull.withDefault("What's so bad about a String here?");
Последний фрагмент кода не компилируется, если я вообще удаляю B
, поэтому B
действительно необходим.
Обратите внимание, что функция, которую я пытаюсь реализовать, легко получить, если я инвертирую порядок объявлений параметров типа, изменив ограничение super
на extends
. Однако это возможно только в том случае, если я переписываю метод как статический:
// This one actually works and I use it.
public static <B, A extends B> B withDefault(Nullable<A> nullable, B defaultValue) { ... }
Дело в том, что это ограничение языка Java действительно ограничивает некоторые возможные полезные функции и может потребовать уродливые обходные пути. Интересно, что произойдет, если нам понадобится withDefault
, чтобы быть виртуальным.
Теперь, чтобы соотнести с тем, что говорят полигеномассы, мы используем B
здесь, чтобы не ограничивать тип объекта, переданного как defaultValue
( см. строку, используемую в примере), а скорее ограничить ожидания вызывающего абонента возвратом объекта. В качестве простого правила вы используете extends
с типами, которые вы требуете, и super
с типами, которые вы предоставляете.