天天看點

為什麼子類中不能通路另一個包中父類中的protected方法?

前言

   其實這個問題問法是錯的,在子類中是可以通路另一個包中父類中的protected方法,能問出這樣的問題,多半是在不同包的子類中建立了父類對象,通過父類對象去調用protected方法,結果發現編譯不通過,是以才會萌生這樣的疑問。

   正确的通路方式:

   1. super.method() //protected

   2. 建立子類對象instance,通過instance.method()通路

表面了解

為什麼子類中不能通路另一個包中父類中的protected方法?

   一般我們問這個這個問題呢,其實是沒有明白protected權限到底controll了什麼,都知道protected修飾的成員變量和方法可以被包外的子類通路到,這裡被包外子類通路實質并不是可以在子類中通過建立父類對象來通路這個protected方法, 而是可以通過建立子類對象來通路的。這也可以說是protected權限對包外通路時的一種限制,在包内則可以像public那樣可以在任意地方通路。

   産生這個問題的主要是我們把在包内通路的方式了解強加到了包外。其實我們又可以轉換問題為什麼跨包通路某個類的protected方法必須通過繼承呢?答案很簡單,通過繼承呢,子類就可以獲得了父類方法的位址資訊并把這些資訊儲存到自己的方法區,這樣就可以通過子類對象通路自己的方法區進而間接的通路父類的方法(重寫的話,就直接通路子類自己重寫後的方法)。

内部了解

   首先可以先了解一下靜态方法的通路和多态的實作:http://blog.csdn.net/dawn_after_dark/article/details/74357049;通過以上部落格了解方法表~

不同包下的執行個體方法通路:

package com.learn.pra05;
/**
 * 父類Person在com.learn.pra05;
 * @author mengyi
 *
 */
public class Person {
    /**
     * 保護類型的執行個體方法
     */
    protected void print(){
        System.out.println("Person!");
    }
}

package com.learn.pra06;

import com.learn.pra05.Person;
/**
 * 子類所在的包為com.learn.pra06
 * @author mengyi
 *
 */
public class Worker extends Person{
    public static void main(String[] args){
        Person person = new Person();
        person.print();
    }
}
           

但是上面會在

person.print();

報編譯錯誤,錯誤為:

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    The method print() from the type Person is not visible
    at com.learn.pra06.Worker.main(Worker.java:)
           

   說Person 的print 方法在這裡不可見,Worker确實繼承了Person,但隻是通過繼承産生了自己能通路的方法表包括父類的保護區域(執行個體方法),并無權限通路父類對象的方法表中保護區域,就是隻能通過Worker自己的對象去通路自己的方法表中保護區域來調用已繼承的print方法,Worker是無權限檢視Person方法表中保護區域資訊,更别說用通過Person對象調用了。這裡也就很好的解釋了為什麼繼承的子類看不到private方法,和為什麼包外無法通路父類的default方法,因為編譯器規定不能繼承過來,是以子類的方法表不存在這些修飾符修飾的方法,更别談調用了。

   這裡再給出《java in a nutshell》中的一段話:

protected access requires a little more elaboration. Suppose class A declares a protected field x and is extended by a class B, which is defined in a different package (this last point is important). Class B inherits the protected field x, and its code can access that field in the current instance of B or in any other instances of B that the code can refer to. This does not mean, however, that the code of class B can start reading the protected fields of arbitrary instances of A! If an object is an instance of A but is not an instance of B, its fields are obviously not inherited by B, and the code of class B cannot read them.

   跟我上面說的一樣,類B繼承了類A,包括保護型方法區域,是以能通過B對象來調用這個區域通路該方法。在類B是無法檢視對象A的保護型方法區域,是因為類B并沒有繼承對象A的保護型方法區域,類B隻是繼承了類A的保護型方法區域。這也很好解釋了下面說到的靜态方法的通路!

再舉一個例子:

package com.learn.pra05;

public class Person {
    protected void print(){
        System.out.println("Person!");
    }
}

package com.learn.pra06;

import com.learn.pra05.Person;
public class Worker extends Person{
    public void personPrint(){
        super.print();
    }
    public static void main(String[] args){
        Worker worker=new Worker();
        worker.personPrint();
    }
}
           

   上面卻能運作,不是說Work類不能通路到person對象的方法區嗎?但是super确實是特例,我的一種解釋就是super為worker對象裡的參數,該參數指向了person對象的方法區,是以我們通過super間接的通路到了,其實還是必須通過子類對象來調用父類的protected方法,符合protected權限的要求!

聰明的你肯定想到了若是protected static 修飾的保護型靜态方法呢?

不同包下的靜态方法通路:

package com.learn.pra05;
public class Person {
    protected static void speak(){
        System.out.println("Person speak!");
    }
}

package com.learn.pra06;

import com.learn.pra05.Person;
public class Worker extends Person{
    public static void main(String[] args){
        Person person = new Person();
        person.speak();
    }
}
           

   上面這段代碼不僅編譯通過,而且運作結果為:

Person speak!

   是否吃驚呢?還是早已知曉呢?解釋在上面已給出,此處再啰嗦一下,因為Work類繼承了Person類,包括保護型區域,而該區域裡含有

protected static void speak()

這個方法,又因為靜态方法通路會被編譯方法器靜态綁定無需經過對象去通路方法表,比如此次的位元組碼為:

INVOKESTATIC com/learn/pra05/Person.speak ()V

,是以類Work可以通路對象person的靜态區域(其實就是Person類的方法區域)!

總結

   以上隻是我個人的見解,歡迎指正,我相信在以後我會慢慢了解這一塊,可能這塊有的地方我寫的是錯的,但是不重要,重在思考與嘗試!