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

时间:2016-01-13 13:33:23

标签: java

通常情况下,当我们对类中的字段使用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);
    }
}

5 个答案:

答案 0 :(得分:3)

  

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: Is a class a subclass of itself?

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

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

但是,这里正确的语法是

System.out.println(Car.carNo);

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

System.out.println(carNo);

,因为

  

只有声明为protected或public的类的成员才会被包含在其中的包中声明的子类继承   声明了哪个类

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

所述

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

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

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

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是静态的,不能调用类变量

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中,您可以通过继承或仅在同一个包中访问受保护的成员

尝试在此处访问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;
    }

}

答案 3 :(得分:0)

原因是关键字“静态”。

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

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

答案 4 :(得分:0)

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

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

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

  

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

让我们通过将您的汽车和宝马课程付诸实践来更好地理解这一解释。以下是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;
    }
}

这是宝马级,目前只是继承了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的角色)。

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

...

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类对象完全不同。实质上,静态方法具有关联命名空间的函数。最重要的是,它们可以被继承,但不能被覆盖 - 只是隐藏,就像静态和非静态字段一样。 (顺便说一句,这意味着调用静态方法比调用实例方法更有效,因为不仅可以在编译时选择其形式,还可以在编译时选择它的实现。)

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

  

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