2013-12-19 21 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的私有字段,然後我們稱之爲平方米,該實例。

我有幾個X的子類,他們都是一樣的,他們也聲明他們操作的私有成員。爲了達到這個目標,他們總是需要在m2開始時進行演員表演。這裏是其中的一個:

public class Y extends X{ 

    private int j; 

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

} 

但是 - 我可以保證的X的子類的實例的M1每次調用總會有一個參數是相同類型的,例如當我有一個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 
    } 
} 

這是一個很好的問題,它肯定讓我吃驚。但是,我們實際上可以指出的是,這並不工作,要麼從公式中刪除泛型:

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); 
} 

是演員所以壞?不是真的。當然,這與避免使用中間變量的演員一樣好或更好。和任何upcast一樣,我會假設它會被編譯器刪除。它僅作爲編譯器的提示而存在。我們當然不必擔心演員的安全,因爲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