关于Java中的protected,还有一点需要注意

对于protected访问符,大家也应该是很熟悉的。无非是:

  • 可以在同一个包中的其他类访问
  • 可以在不同包中的子类中访问

对于第一个没什么疑惑,但是对于第二个,往往会忽略掉一点东西。看以下代码

// Person.java
package a;
public class Person {
  protected void run() {
    System.out.println("I am running");
  }
}
// Man.java
package b;
import a.Person;
public class Man extends Person {
  public static void main() {
    Man man = new Man();
    man.run();
    Person person = new Person();
    person.run(); // 这行代码会报错,不能这样访问。
  }
}

在IDE中写入上面的代码时,person.run();无法通过编译,因为Person的run方法是protected,不能这样访问。这样就奇怪了吧,明明是在子类里面,为什么不能这样访问呢?这说明,第一个访问,还有些细节,没有被说明,而一般的资料往往都没有介绍这个细节,大部分人也很少在开始就遇到这样的问题,故而这个细节一直被忽略。

关于这个问题的解释,我一直没有找到很好的解释,在学习JVM的时候,找到了一些可以用来解释。

这里需要对Java虚拟机指令有一定了解,我也不准备详细讲解虚拟机指令,因此直接给出结论吧。

涉及到的指令是invokespecial,Java虚拟机规范是这么描述的:

Invoke instance method; special handling for superclass, private, and instance initialization method invocations.

这说明,在Java代码中,若是调用父类的方法、构造器或者私有方法时,会被翻译为invokespecial字节码。

我们继续看invokespecial的关键描述:

If the resolved method is protected , and it is a member of a superclass of the current class, and the method is not declared in the same run-time package (§5.3) as the current class, then the class of objectref must be either the current class or a subclass of the current class.

意思就是说,若解析的方法(可以简单的认为被调用的方法)是protected修饰的,并且这个方法,是当前类的父类中的方法,而且这个方法没有声明在当前类的同一个运行时包内(可以简单的认为,声明该方法的包,和当前类所在包不是同一个包),那么,调用该方法的对象所对应的类要么是当前类,要么是当前类的子类。

提炼一下,当被调用的方法被protected修饰时,在满足以下两个条件时:

  • 调用的方法是当前类的父类中的方法
  • 当前类和声明该方法的类不在同一包中

必然可以得出以下结论:

  • 调用该方法的对象所对应的类要么是当前类,要么是当前类的子类

我们把上面的代码,和现在的例子进行一下对比就知道了:

  • 当前类是Man,而run方法是Person中的方法,因此满足被调用的方法是当前类的父类
  • Man声明在b包中,Person声明在a包中,故满足不在同一包内

因此,两个条件均满足,所以,调用该方法的对象所对应的类要么是当前类Man,要么是当前类的子类。而Person是当前类的父类,于是不能够进行调用。可以看到,代码前面使用man.run()是没有问题的,因为man对应着当前类。

那么我们进行一下类型强转呢?

例如如下代码(Person代码不变):

// Man.java
package b;
import a.Person;
public class Man {
  public static void main() {
    Man man = new Man();
    man.run();
    Person person = new Person();
    ((Man)person).run(); // 这行代码能通过编译,但是运行时会抛出java.lang.ClassCastException
  }
}

在IDE下,进行强转之后,代码并不会报错,正常编译,而运行之后,还是抛出异常了:java.lang.ClassCastException这个异常主要是在类型转换的时候发生,原因在于,person是一个Person对象(主要是指动态类型是Person对象),当强制转化为Person的子类时,编译可以通过,但是运行时无法通过的,原因在于,无论怎么强制转化,person的动态类型始终没有变化(即,在运行时,强制类型转化,并没有对堆上的数据进行改变,改变的只是变量person的引用类型,例如上面的强转,将person从指向Person,变成了指向Man)。讲到这里貌似有点偏题了。。。

前面讲到的是protected修饰的方法,那么变量呢?其实也是一样,对于非静态的变量(静态变量不会被继承,并没有啥可说的),在进行读写时,涉及到的指令是getfieldputfield。同样,我在Java虚拟机规范的getfield中也找到了如下描述:

If the field is protected , and it is a member of a superclass of the current class, and the field is not declared in the same run-time package (§5.3) as the current class, then the class of objectref must be either the current class or a subclass of the current class.

在putfield中也有同样的描述,只不过还有其他的描述,这说明,protected修饰的变量和protected修饰的方法在访问的规则上是一样的。

小结

要完全看懂本文,还是需要对Java虚拟机有一定的了解,因为我本身也没有找到很好的资料来解释前面的问题(毕竟很多书都是忽略这些东西的,作为基础书籍,确实不应该在这方面太细致),幸而在Java虚拟机规范中找到了相应的说明,故以此来进行解释,说起来这种解释也不一定合适。毕竟,在编译阶段,应当用Java语法规则来解释,而不是Java虚拟机规范,因为这两者本身并不相同,甚至对某些东西,这两个还会出现不一致的情况,最典型的就是,我们在同一代码块不能定义相同的变量名,即不能在同一代码块定义int a; float a;,而这是Java语法的规定,而Java虚拟机并没有这样规定,关于为什么Java虚拟机可以接受这样的定义,这就涉及到名字和类型描述符的一些知识了,具体请自行查阅。

以上内容都是在学习的时候一些自己的思考,可能不是很严谨,甚至还有可能是错误的,还请读者能够有思考的能力,若发现其中不当之处,也请不吝赐教,不胜感激。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,504评论 18 399
  • 五、Java 虚拟机 一、什么是Java虚拟机Java虚拟机是一个想象中的机器,在实际的计算机上通过软件模拟来实现...
    壹点零阅读 720评论 0 0
  • 今天七号,长假马上就要结束了。 安静的假期里,回头一看,做了好多事儿…… > 1.新媒体运营在线课程学习,累计时间...
    殷春燕阅读 217评论 0 0
  • RACCommand RACCommand 属性与方法
    李潇南阅读 529评论 0 0