2013-12-19 3 views
11

У меня была проблема, я мог бы решить сам, но я до сих пор не понимаю, почему мой исходный код не работает, или если есть более элегантное решение, чем тот, который я найденный. Я представляю упрощенную версию моего кода здесь.Ошибка «с частным доступом» с генераторами

Рассмотрим следующий абстрактный суперкласс X:

public abstract class X{ 

    private int i; 

    public void m1(X x){ 
    x.i = 1; 
    m2(x); 
    } 

    public abstract void m2(X x); 

} 

Когда m1 называется, мы манипулируем личное поле X экземпляра прошло, а затем мы называем м2 с этим экземпляром.

У меня есть несколько подклассов X, они все одинаковы в том смысле, что они также объявляют частные члены, которыми они манипулируют. Чтобы достичь этого, им всегда нужно сделать бросок в начале м2. Вот один из них:

public class Y extends X{ 

    private int j; 

    public void m2(X x){ 
    Y y = (Y) x; 
    y.j = 0; 
    } 

} 

Но - я могу гарантировать, что каждый вызов m1 экземпляра подкласса X всегда будет иметь параметр, который имеет тот же тип, например, когда у меня есть экземпляр Y, параметр метода m1 всегда будет другим экземпляром Y.

Из-за этой гарантии я хотел сделать ненужным бросок, введя обобщения. Это, как я хочу, чтобы мои подклассы выглядеть следующим образом:

public class Y extends X<Y>{ 

    private int j; 

    public void m2(Y y){ 
    y.j = 0; 
    } 

} 

Как суперкласс X должен выглядеть, как сейчас? Моя первая попытка была что:

public abstract class X<T extends X<T>>{ 

    private int i; 

    public void m1(T x){ 
    x.i = 1; 
    m2(x); 
    } 

    public abstract void m2(T x); 

} 

Но - это не работает, когда я компилирую это, я получаю следующее сообщение об ошибке:

X.java:6: error: i has private access in X 

Это обычно то, что вы получаете вы пытаетесь получить доступ к частному членов другого класса. Очевидно, что Java не признает, что T всегда является экземпляром X, хотя я использовал «T extends X» в объявлении.

Я установил X, как это:

public abstract class X<T extends X<T>>{ 

    private int i; 

    public void m1(T x){ 
    X<?> y = x; 
    y.i = 1; 
    m2(x); 
    } 

    public abstract void m2(T x); 

} 

По крайней мере, я не использую слепков больше - но почему это дополнительное задание нужно? И почему исходный код не работал? Кроме того, мне показалось странным, что я должен был использовать X<?> и не мог использовать X<T>.

ответ

6

Я считаю, что мы можем уменьшить ваш вопрос до: Почему следующий пример не скомпилирован?

public class Foo { 
    private final String bar = "bar"; 

    public <T extends Foo> void printFoo(T baz) { 
     System.out.println(baz.bar); //bar is not visible 
    } 
} 

Это большой вопрос, и он уверен, застало меня врасплох. Но мы действительно можем удалить Generics из уравнения, отметив, что это не работает, либо:

public class Foo { 

    private final String bar = "bar"; 

    public void printFoo(SubFoo baz) { 
     System.out.println(baz.bar); //bar is not visible 
    } 
} 

class SubFoo extends Foo {  
} 

Другими словами, вопрос заключается в том, что вы имеете дело с подклассом Foo, а не сам Foo. В случае T мы не знаем , который относится к подклассу, но мы знаем, что это подкласс, или Foo.

Как вы уже поняли, решение (удивительно, по крайней мере, для меня) является: вентиляционной шахты

System.out.println(((Foo)baz).bar); 

Или для общего случая:

public <T extends Foo> void printFoo(T baz) { 
    System.out.println(((Foo)baz).bar); 
} 

является литейный так Плохо? На самом деле, нет. Это, безусловно, так же хорошо или лучше, чем избегать приведения с промежуточной переменной. Как и при любом повышении, я предполагаю, что он будет удален компилятором. Он существует только как подсказка для компилятора. Нам, конечно, не нужно беспокоиться о безопасности приведения, потому что стирание T уже Foo.

Я могу только предположить, что это ограничение необходимо для того, чтобы иметь четкое представление о доступе ... так SubFoo может переобъявить bar себя, он может стать неоднозначным, который bar идет речь, и поэтому бросок необходимо. Это показано в этом сложном примере:

public class Foo { 

    private final String bar = "hello"; 


    static class SubFoo extends Foo { 
     private final String bar = "world"; 
    } 

    public <T extends SubFoo> void printFoo(T baz) { 
//  System.out.println(baz.bar); // doesn't compile 
     System.out.println(((Foo)baz).bar); //hello 
     System.out.println(((SubFoo)baz).bar); //world 
    } 

    public static void main(String[] args) { 
     new Foo().printFoo(new SubFoo()); //prints "hello\nworld" 
    } 
} 

В связи с этим, он служит больше как спецификатор, чем в гипсе.

+0

И меня неожиданно показало, что ваш второй образец кода не будет компилироваться, но, зная это, компилятор был совершенно прав, чтобы критиковать мой оригинальный код. Большое спасибо за подробное объяснение! – Bernhard

+0

Несомненно, сообщение должно состоять в том, что 'private' является неправильным модификатором доступа для использования в любом поле, которое вы хотите использовать для подклассов? Почему бы вам не использовать защищенный модификатор или предоставить метод getter? – Bobulous

+0

Arkanon - Я не получаю доступ к полю в подклассах, поэтому я хочу сохранить его в тайне. Если вы снова посмотрите на код, вы увидите, что я обращаюсь к частному полю внутри класса, я его объявляю. В противном случае это была бы ошибка компиляции, даже при использовании! – Bernhard

Смежные вопросы