为什么受保护的访问修饰符与非静态使用的静态修饰符一起使用时的工作方式不同

通常,当我们对类中的字段使用protected时,如果子类位于不同的包中,则其子类无法使用Base类的引用来访问它。 那是真实的 。 但是我发现当在字段中添加静态关键字时,它的行为会有所不同。 它变得易于访问。 这怎么可能 。 有人有答案吗?

package com.car; public class Car { static protected int carNo=10; } package com.bmw; import com.car.*; public class BMW extends Car { public static void main(String[] args) { //Its accessible here System.out.println(new Car().carNo); } } 

6.6.2.1。 访问受保护的会员

设C是声明受保护成员的类。 仅允许在C的子类S的主体内访问。

此外,如果Id表示实例字段或实例方法,则:

如果访问是通过限定名称Q.Id,其中Q是ExpressionName,则当且仅当表达式Q的类型是S或S的子类时才允许访问。

如果访问是通过字段访问表达式E.Id,其中E是主表达式,或者是通过方法调用表达式E.Id(…),其中E是主表达式,那么只允许访问如果E的类型是S或S的子类。

资料来源: https : //docs.oracle.com/javase/specs/jls/se7/html/jls-6.html#jls-6.6.2.1

看到

 public class BMW extends Car { public static void main(String[] args) { System.out.println(new BMW().carNo); } } 

是有效的,因为新BMW()是Car的子类,即使是在不同的包中。

 public class BMW extends Car { public static void main(String[] args) { System.out.println(new Car().carNo); } } 

是无效的,因为新的Car()不是Car的子类,而是在另一个包中调用它。 (请参阅Java:类是否是其自身的子类?如果类是其自身的子类,则进行讨论)

现在,如果carNo是静态的,这是合法的

 System.out.println(new Car().carNo); 

但是,这里的语法正确

 System.out.println(Car.carNo); 

因为carNo不是实例字段 ,因为它是静态的。 事实上,即使这样也可以在宝马内部工作

 System.out.println(carNo); 

因为

只有声明为protected或public的类的成员才会被声明在声明类声明的类中的子类inheritance。

如https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.2所述

主要方法是在宝马,这是汽车的子类。 因此,它可以访问受保护的变量。

它之前不可见的原因是因为静态方法(如main)无法访问非静态变量。 一旦两个标准都满了,主方法就可以访问它。

 class Car { protected int a = 9; } class BMW extends Car{ public static void main(String[] args) { int b = a; // cannot make a static reference to a non static field warning error shown by eclipse } } 

删除它的两种方法:要么静态的

 class Car { protected static int a = 9; } class BMW extends Car{ public static void main(String[] args) { int b = a; // cannot make a static reference to a non static field } } 

或者在非静态方法中调用main,main is static不能调用类变量

 class Car { protected static int a = 9; } class BMW extends Car{ public void m() { int b = a; } public static void main(String[] args) { } } 

你在这里混合两个概念:
1)从非静态上下文中访问静态变量
2)受保护的访问修饰符
在java中,您可以通过inheritance或仅在同一个包中访问受保护的成员

尝试访问noCar:

 class Car{ int noCar = 9; public static void main(String[] args) { int b = noCar; // cannot make a static reference to a non static field warning error shown by eclipse } } 

编辑:考虑包裹

 package com.bmw; import com.car.*; public class BMW extends Car { public static void main(String[] args) { System.out.println(new BMW().carNo); Car car = new Car(); // Car has no idea that BMW is the child class // and since it is not public we cannot access it directly //can be accessed like this car.getCarNo(); // you can do this because BMW has the variable carNo because of it extending Car BMW bmw = new BMW(); int a = bmw.carNo; } } package com.car; public class Car { protected int carNo=10; public int getCarNo() { return carNo; } public void setCarNo(int carNo) { this.carNo = carNo; } } 

原因是关键字“静态”。

Static将变量与类关联,而不关联实例。 由于该类是公共的,所有它的静态变量也将是公共的,即所有变量都可以从其他类访问。

宝马也扩展了汽车。 因此,宝马始终可以看到它。

是的,这很奇怪。 为了阐明这种行为,首先,回顾一下在没有静态关键字的情况下受保护修饰符如何工作以及为什么它以它的方式工作的方式可能会有所帮助。

如果类T声明了受保护的成员m,则T和属于与T相同的包的任何类都可以访问该成员,即可以说是tm; 引用的类型(t)必须是T或T的子类。另外,T的包外T的任何子类U都可以说是tm; 在这种情况下,t的类型必须是U或U的子类。

本声明的最后部分包含一个重要的限制。 Arnold,Gosling和Holmes 在Java编程语言 (第四版)的第3.5节中详细解释了它的动机:

限制背后的原因是:每个子类inheritance超类的契约并以某种方式扩展该契约。 假设一个子类作为其扩展契约的一部分,对超类的受保护成员的值施加约束。 如果不同的子类可以访问第一个子类的对象的受保护成员,那么它可以以破坏第一个子类的合同的方式操纵它们 – 这不应该是允许的。

让我们通过将您的汽车和宝马课程投入使用来尝试更好地理解这种解释。 以下是Car的修改版本。 我用同样受保护但非静态的色域替换了carNo字段。 该类还声明了一个明显的公共getter / setter对。

 package com.car; import java.awt.Color; public class Car { protected Color color; public Color getColor() { return color; } public void setColor(Color color) { this.color = color; } } 

这是宝马级,目前只是inheritance了Car的成员。

 package com.bmw; import com.car.Car; public class BMW extends Car { } 

最后,让我们添加Car的另一个子类。

 package com.ferrari; import com.car.Car; import java.awt.Color; public class Ferrari extends Car { public Ferrari() { color = Color.RED; } @Override public void setColor(Color color) { log("Nope. I'm proud of my color."); } ... } 

正如您所看到的,在我们的应用程序中,法拉利对象显示出对某种颜色的独家偏好(在现实世界中几乎也是如此)。 颜色字段在构造函数中设置,并通过直接覆盖setColor()使其成为只读。 顺便提一下,请注意,这里允许直接访问受颜色保护的成员,因为引用(隐含的)是正确的类型,访问子类的引用(法拉利在上面的描述中扮演U的角色)。

现在,假设宝马对象想要certificate自己的优势超过其他汽车,所以他们要求class级的程序员通过大胆的超越()方法来增强。 程序员有责任。

 ... public class BMW extends Car { public void overtake(Car car) { log("Wow! Become green with envy!"); car.setColor(Color.GREEN); } ... } 

但是,阅读应用程序日志后,程序员很快发现,虽然这对其他汽车来说效果很好,但法拉利的对象却顽固地抵制任何无礼。 然后,他请宝马对象寻求解决方案,试图绕过setColor()方法……

 ... public class BMW extends Car { public void overtake(Car car) { log("Wow! Become green with envy!"); car.color = Color.GREEN; // <- } ... } 

......这正是我们在Java中无法做到的。 法拉利子类的扩展合同限制了受颜色保护的成员的价值。 如果BMW子类可以通过汽车(或法拉利)参考直接访问色域,那么它将能够打破该合同。 Java不允许这样做。

因此,这就是为什么受保护的修饰符的行为与应用于非静态成员时的行为方式相同。 对于受保护的静态成员,事情会完全改变。 如果色场是静态的,宝马内部的任何方法都可以直接访问它。 在您的代码中,BMW类毫无障碍地访问carNo字段。

在上面的例子中,法拉利类可以通过覆盖实例setColor()方法来限制颜色字段的可能值,这实际上相当于改变而不违反超类的合同。

现在,Java是一种基于类的面向对象语言,它没有像Objective-C那样的意义上的类对象的概念。 在Objective-C中,类是字面上的对象,类方法(与Java静态方法类似但不相同)可以说是类对象的实例方法 - 具有这一事实的所有后果:特别是,它们可以被重写并用作多态操作,NSArray和NSMutableArray中的数组类方法就是一个明显的例子。

在Java中,没有类对象 - java.lang.Class的实例与Objective-C类对象完全不同。 实质上,静态方法具有关联命名空间的函数。 最重要的是,它们可以被inheritance,但不能被覆盖 - 只是隐藏,就像静态和非静态字段一样。 (顺便说一句,这意味着调用静态方法比调用实例方法更有效,因为不仅可以在编译时选择其forms,还可以在编译时选择它的实现。)

但是,这就是故事的结尾,如果静态成员无法被覆盖,他们也无法真正改变超类的合同。 并且,如果它们不能更改超类的契约,则子类不能仅通过访问后者的静态成员来破坏不同子类的契约。 如果我们记得避免这种违规行为正是限制受保护的非静态成员的原因,我们现在可以理解为什么Java的设计者最终解除了对受保护的静态成员的限制。 再一次,我们可以在Java编程语言第3.5节的简短段落中找到对这一思路的简明扼要:

受保护的静态成员可以在任何扩展类中访问...这是允许的,因为子类不能修改其静态成员的契约,因为它只能隐藏它们,而不是覆盖它们 - 因此,没有其他类的危险违反了合同。