关于JAVA中四种权限修饰词的思考和总结
本来我以为我已经理解了JAVA里面四种权限词及其用法了,结果前几天写实验的时候,才发现原来自己很多细节根本没有去思考过,更别说搞懂了。于是在查阅资料博客后,以这个为主题写一篇博客,总结一下。
JAVA中有四种修饰词:Public,Protected,default,private。
一、public
public关键词修饰的类、属性或者方法,是在何时何地都可以被任意访问到。这个很好理解,就是完全公开的权限,任人访问。
但是在我们设计一个ADT的时候,往往不能把所有方法或属性简单地设置成为public的。如果全部设成public的话,会破坏一个模块的独立性,增加各个模块间的耦合度,并且还会导致表示泄露的问题,破坏了表示独立性。
因此,原则上来讲,为了表示的内部封装,所有的属性都不能设置为public,而要根据继承时的权限设置为另外三种。而为了模块之间的独立性,一般只需要把实现接口中的方法设置为public,暴露给外部,而其他只有内部方法实现时使用的方法应该设置为其他权限词,比如protected或者private。
二、protected
- 基类的 protected 成员是包内可见的,并且对子类可见;
- 若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法。、、
用protected修饰的属性或者方法,可以对子类可见。不论是在同一个包下或不同包下的子类,都可以访问到。
刚开始我只记住这个结论,但是由于JAVA编程经验的匮乏,并没有深入去思考这个问题。现在讲一下我目前的认识。
用protected修饰的属性或者方法,对于子类是可见的。也就是说,子类知道有这么个东西,可以看得见摸得着,那么进而就可以去使用了。这里的使用是指,可以在子类的方法里去调用或者去重写。具体我们在最后进行对比。
三、default
default修饰词是只有同一个包内可见。一个文件中,只能有一个类是public向外暴露的,而其他类都只能设定为default,设置为protected和private会报错!
用default修饰的类或者方法,可以看作是一个包的内部专属。我的理解是用于实现一个包内的内部辅助类。由于一个模块应该是高度聚合的,所以一个包内的各种类应该更倾向于使用相同的辅助类,这样设计也可以提高代码的复用性。
而且我们看到,对于类的修饰只有public和default俩种,protected和private只可以修饰属性和方法。当然想一想也挺合理的,private是私有的意思,一个类如果是私有的,那么它属于谁呢?属于包吗?这不就是default的功能嘛。同理protected,类的上一层是包,而包和包之间没有继承的关系,自然也不需要protected权限。
四、private
private是最狭隘的权限修饰词,用private修饰的属性或方法只有本类中可以看到,哪怕是子类中,也是看不到的。
怎么理解这个看不到呢?可以这样想,如果一个子类继承了一个父类,他能够看到的是父类中public和protected修饰的属性或者方法,进而可以在自己声明的方法中访问或者来调用。而对于private属性的变量或方法,子类根本不知道它的存在,无法直接在自己扩展的方法中访问或调用。
注意我这里说的是“直接”!下面我们详细讨论一下private和protected在子类父类继承关系中的差异,下面是本篇中的精华所在!
一 属性的protected和private区别
先看protected:
public class Main {
public static void main(String[] args) {
A a=new A();
System.out.println("父类中方法:");
a.printProtectedValue();
B b=new B();
System.out.println("子类中方法:");
b.printProtectedValue();
}
public void test(){
}
}
class A {
protected String a="父亲";
public A(){
}
public void printProtectedValue(){
System.out.println(this.a);
}
}
class B extends A{
protected String a="孩子";
public B(){
super();
}
}
在父类中我们声明了一个protected属性的变量,然后在子类中对该变量重新赋值,用父类中的同一个方法进行访问,查看得到的结果:
那如果调用的方法是子类中的呢?
向子类中重写同名方法:
class B extends A{
protected String a;
public B(){
super();
a="孩子";
}
@Override
public void printProtectedValue(){
System.out.println(this.a);
}
}
输出结果如下:
为了进一步讨论,在子类中同时也调用父类的方法:
public class Main {
public static void main(String[] args) {
A a=new A();
System.out.println("父类中方法:");
a.printProtectedValue();
B b=new B();
System.out.println("子类中方法:");
A c=new B();
b.printProtectedValue();
System.out.println("谁的方法呢?");
c.printProtectedValue();
}
public void test(){
}
}
class A {
protected String a;
public A(){
a="父亲";
}
public void printProtectedValue(){
System.out.println(this.a);
}
}
class B extends A{
protected String a;
public B(){
super();
a="孩子";
}
@Override
public void printProtectedValue(){
super.printProtectedValue();
System.out.println(this.a);
}
}
得到如下结果:
那子类访问父类的属性会怎样呢?
重写子类中方法如下:
@Override
public void printProtectedValue(){
super.printProtectedValue();
System.out.println(this.a);
System.out.println(super.a);
}
得到结果:
欸,发现在子类中,似乎有俩个a属性,分别是继承于父类的,还有在子类中定义的!
到现在我们可以得到一个大致的结论了。对于protected属性,如果父类和子类中重复定义了,并不会像方法那样进行重写,而是会同时存在的!在子类中,可以同时存在super.a和this.a俩个属性,并且俩个都可以来访问,这样就相当于这俩个属性其实只是名字一样,本质上是俩个不同的属性。
在父类中定义的方法,访问a属性时,就是父类的a属性(这是当然!),而在子类的方法中,父类的属性需要使用super.a方式来显式地访问,不然会访问子类中的属性。子类同时有父类和子类中属性的访问权限。
好,至此我们已经知道了在继承中,属性并不会发生重写,而是简单的附加。这个应该就类似于链接时俩个函数内的同名静态变量,编译器命名为x.a和y.a,变作了俩个不同名的属性。而修饰词只是界限了访问权限的区别。
那下面我们改变a为private会怎样呢?
发现子类中没有父类的a访问权限了,这在我们的意料之中。那我们运行一下呢?
public class Main {
public static void main(String[] args) {
A a=new A();
System.out.println("父类中方法:");
a.printProtectedValue();
B b=new B();
System.out.println("子类中方法:");
A c=new B();
b.printProtectedValue();
System.out.println("谁的方法呢?");
c.printProtectedValue();
}
public void test(){
}
}
class A {
private String a;
public A(){
a="父亲";
}
public void printProtectedValue(){
System.out.println(this.a);
}
}
class B extends A{
protected String a;
public B(){
super();
a="孩子";
}
@Override
public void printProtectedValue(){
super.printProtectedValue();
System.out.println(this.a);
}
}
父类方法访问父类属性,子类方法访问子类属性,只不过区别是private中子类没有父类的访问权限了,跟我们的预期一致!
二、方法的protected和private区别
这里我们改一下顺序,先看private方法。
public class Main {
public static void main(String[] args) {
A a=new A();
System.out.println("父类中方法:");
a.printPerson();
B b=new B();
System.out.println("子类中方法:");
b.printPerson();
}
}
class A {
protected String a="A";
public A(){
}
public void printPerson() {
System.out.println(getInfo());
}
private String getInfo() {
return "父亲:"+this.a;
}
public void printProtectedValue(){
System.out.println(this.a);
}
}
class B extends A{
protected String a="B";
public B(){
super();
}
private String getInfo() {
return "孩子"+this.a;
}
@Override
public void printPerson() {
super.printPerson();
System.out.println(getInfo());
}
}
可以看到,即使是父类和子类中有同名的方法,但是编译器没有要求加上@override标签,说明子类中看不到父类中的方法,俩个方法间没有任何关系。
然后看一下输出结果:
结果似乎跟属性一样,对于private方法,父类中调用父类方法,子类中调用子类方法。这里我没有单独写一个不重写的printPerson方法,因为预期结果就是只会调用父类中方法。
那么如果方法是protected呢?
public class Main {
public static void main(String[] args) {
A a=new A();
System.out.println("父类中方法:");
a.printPerson();
B b=new B();
System.out.println("子类中方法:");
b.printPerson();
}
}
class A {
protected String a="A";
public A(){
}
public void printPerson() {
System.out.println(getInfo());
}
protected String getInfo() {
return "父亲:"+this.a;
}
public void printProtectedValue(){
System.out.println(this.a);
}
}
class B extends A{
protected String a="B";
public B(){
super();
}
@Override
protected String getInfo() {
return "孩子:"+this.a;
}
// @Override
// public void printPerson() {
// super.printPerson();
// System.out.println(getInfo());
// }
}
先不对printPerson重写,查看一下结果:
可以看到,虽然printPerson都是父类中的方法,但是调用的方法却改变了:在子类中,会调用子类重写的方法。有点像动态链接,在运行时决定调用具体的方法,父类的方法被重写了!
那我们取消最后重写的注释再运行一遍呢?
可以看到,哪怕是显式地调用父类中方法,也会进而调用重写后的方法。对于protected的方法,父类子类中只能有一个!
如果父类中是private,子类中是protected呢?
public class Main {
public static void main(String[] args) {
A a=new A();
System.out.println("父类中方法:");
a.printPerson();
B b=new B();
System.out.println("子类中方法:");
b.printPerson();
}
}
class A {
protected String a="A";
public A(){
}
public void printPerson() {
System.out.println(getInfo());
}
private String getInfo() {
return "父亲:"+this.a;
}
public void printProtectedValue(){
System.out.println(this.a);
}
}
class B extends A{
protected String a="B";
public B(){
super();
}
protected String getInfo() {
return "孩子:"+this.a;
}
@Override
public void printPerson() {
super.printPerson();
System.out.println(getInfo());
}
}
结果如下:
这时编译器会提醒你不需要加override注释,并且结果可以看到父类中方法并没有覆盖,说明父类和子类中俩个同名的方法是共存的!
那么如果父类中是protected,子类中是private的呢?????
答案是不允许这种情况!
三、总结
至此,我们可以对继承关系中protected和private俩种修饰词做一下总结。
对于属性来讲,可以具有多态性,无论是private还是protected,父类子类中都可以存在同名的属性,以为这他俩在编译时其实被区分为了不同的属性,只不过是编辑时名字一样,在符号表中其实是不一样的名字。而俩种修饰词的区别只是在于访问的权限不同,子类方法无法访问父类的private属性!
对于方法来讲,不具有多态性。如果一个方法是protected或更高的话,那么父类子类中只能有一个相同的方法,在运行时会动态地决定执行父类还是子类中的方法(这里相同指需要override的方法)。而对于private方法,子类并不能看到父类中相同的方法,故不存在重写的情况,在运行时相当于俩个不同的方法!而且子类中也不能对父类中private方法进行重写。
四、进一步讨论
这些只是作为一个观察者角度来看java中权限修饰词的表现,如果要深入了解其中机制,需要对JAVA虚拟机进行深一步地了解,目前没有这样的时间,留以以后再去研究~