Gibt es eine Möglichkeit, Klassenvariablen in Java zu überschreiben?

162
class Dad
{
    protected static String me = "dad";

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    protected static String me = "son";
}

public void doIt()
{
    new Son().printMe();
}

Die Funktion doIt gibt "dad" aus. Gibt es eine Möglichkeit, "Sohn" zu drucken?

Dikla
quelle
111
Wenn Sie jemals eine geschützte statische Aufladung sehen, führen Sie sie aus.
Tom Hawtin - Tackline
23
@ TomHawtin-Tackline verzeihen meine Unwissenheit, aber warum ist eine geschützte Statik verpönt? Ich habe versucht zu googeln, kann aber keine klare Antwort finden. Vielen Dank
Tony Chan
16
Überprüfen Sie diese Frage: Warum sollten wir nicht geschützte statische in Java verwenden
Zeeshan

Antworten:

69

Ja. Wenn es sich jedoch um die Variable handelt, wird sie überschrieben (Variablen einen neuen Wert zu geben. Eine neue Definition für die Funktion ist Überschreiben).Just don't declare the variable but initialize (change) in the constructor or static block.

Der Wert wird bei Verwendung in den Blöcken der übergeordneten Klasse wiedergegeben

Wenn die Variable statisch ist, ändern Sie den Wert während der Initialisierung selbst mit dem statischen Block.

class Son extends Dad {
    static { 
       me = 'son'; 
    }
}

oder sonst im Konstruktor ändern.

Sie können den Wert auch später in beliebigen Blöcken ändern. Es wird sich in der Superklasse widerspiegeln

Vivek MVK
quelle
10
Ich denke, die Antwort sollte einen Code für eine bessere Lesbarkeit enthalten. Ändern Sie einfach die Son-Implementierung in class Son extends Dad { static { me = 'son' } }
Cléssio Mendes
97

Kurz gesagt, nein, es gibt keine Möglichkeit, eine Klassenvariable zu überschreiben.

Sie überschreiben Klassenvariablen in Java nicht, Sie verbergen sie. Überschreiben ist zum Beispiel Methoden. Das Verstecken unterscheidet sich vom Überschreiben.

In dem Beispiel, das Sie gegeben haben, verbergen Sie durch Deklarieren der Klassenvariablen mit dem Namen 'me' in class Son die Klassenvariable, die sie von ihrem übergeordneten Dad mit dem gleichen Namen 'me' geerbt hätte. Das Ausblenden einer Variablen auf diese Weise wirkt sich nicht auf den Wert der Klassenvariablen 'me' im Dad der Oberklasse aus.

Für den zweiten Teil Ihrer Frage, wie "Sohn" gedruckt werden soll, würde ich den Wert über den Konstruktor festlegen. Obwohl der folgende Code ziemlich stark von Ihrer ursprünglichen Frage abweicht, würde ich ihn in etwa so schreiben.

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public void printName() {
        System.out.println(name);
    }
}

Das JLS bietet in Abschnitt 8.3 - Felddeklarationen viel mehr Details zum Verstecken

Mike
quelle
1
Überschreiben ist möglich, indem ein statischer Block in der abgeleiteten Klasse vorhanden ist
siddagrl
1
@siddagrl: kannst du das näher erläutern?
Mr_and_Mrs_D
Welche der OP-Klassen Personsoll in diesem Beispiel ersetzt werden?
n611x007
1
@naxa Sonund Dadsollen von erben Person, dann super("Son or Dad");ihre Konstruktoren aufrufen .
Panzercrisis
Wird es als gut oder schlecht angesehen, solche Variablen in Java auszublenden?
Panzercrisis
31

Ja, überschreiben Sie einfach die printMe()Methode:

class Son extends Dad {
        public static final String me = "son";

        @Override
        public void printMe() {
                System.out.println(me);
        }
}
Romain Linsolas
quelle
Was ist, wenn die von Ihnen verwendete Bibliothek keine printMe()Methode hat oder sogar diese Methode eine statische Methode ist?
Venkat Sudheer Reddy Aedama
1
Um dies zu einem realistischeren Beispiel zu machen, können Sie davon ausgehen, dass die "printMe" -Methode eine sehr komplizierte Funktion mit Logik ist, die Sie nicht wiederholen möchten. In diesem Beispiel möchten Sie nur den Namen der Person ändern, nicht die Logik, die sie druckt. Sie sollten eine Methode namens "getName" erstellen, die Sie überschreiben würden, um "son" zurückzugeben, und die printMe-Methode würde die getName-Methode als Teil ihres Druckvorgangs aufrufen.
Ring
17

Sie können einen Getter erstellen und diesen dann überschreiben. Dies ist besonders nützlich, wenn die Variable, die Sie überschreiben, eine Unterklasse von sich selbst ist. Stellen Sie sich vor, Ihre Superklasse hat ein ObjectMitglied, aber in Ihrer Unterklasse ist dies jetzt definierter als Integer.

class Dad
{
        private static final String me = "dad";

        protected String getMe() {
            return me;
        }

        public void printMe()
        {
                System.out.println(getMe());
        }
}

class Son extends Dad
{
        private static final String me = "son";

        @Override
        protected String getMe() {
            return me;
        }
}

public void doIt()
{
        new Son().printMe(); //Prints "son"
}
John Strickler
quelle
11

Wenn Sie es überschreiben wollen, sehe ich keinen gültigen Grund, diese statisch zu halten. Ich würde die Verwendung von Abstraktion vorschlagen (siehe Beispielcode). ::

     public interface Person {
        public abstract String getName();
       //this will be different for each person, so no need to make it concrete
        public abstract void setName(String name);
    }

Jetzt können wir den Vater hinzufügen:

public class Dad implements Person {

    private String name;

    public Dad(String name) {
        setName(name);
    }

    @Override
    public final String getName() {
    return name;
    }

    @Override
    public final void setName(String name) {
        this.name = name;
    }
}

der Sohn:

public class Son implements Person {

    private String name;

    public Son(String name) {
        setName(name);
    }

    @Override
    public final String getName() {
        return name;
    }

    @Override
    public final void setName(String name) {
        this.name = name;
    }
}

und Papa traf eine nette Dame:

public class StepMom implements Person {

    private String name;

    public StepMom(String name) {
        setName(name);
    }

    @Override
    public final String getName() {
        return name;
    }

    @Override
    public final void setName(String name) {
        this.name = name;
    }
}

Sieht so aus, als hätten wir eine Familie. Sagen wir der Welt ihre Namen:

public class ConsoleGUI {

    public static void main(String[] args) {
        List<Person> family = new ArrayList<Person>();
        family.add(new Son("Tommy"));
        family.add(new StepMom("Nancy"));
        family.add(new Dad("Dad"));
        for (Person person : family) {
            //using the getName vs printName lets the caller, in this case the
            //ConsoleGUI determine versus being forced to output through the console. 
            System.out.print(person.getName() + " ");
            System.err.print(person.getName() + " ");
            JOptionPane.showMessageDialog(null, person.getName());
    }
}

}}

System.out-Ausgabe: Tommy Nancy Dad
System.err ist das gleiche wie oben (hat nur rote Schrift).
JOption-Ausgabe:
Tommy, dann
Nancy, dann
Dad

nckbrz
quelle
1
Das OP befasste sich mit der Änderung des Zugangs zur Statik. Daher war das "Ich" ein generischer "Vater" oder "Sohn" - etwas, das nicht für jeden Vater oder Sohn erstellt werden muss und daher statisch ist.
RichieHH
Ja, du hast Recht, ich habe nicht gesehen, dass es statisch ist. Haha, das würde meine Antwort auf ungefähr 100 Codezeilen weniger ändern, wenn überhaupt. Vielen Dank für die Heads-Ups
nckbrz
6

Dies sieht aus wie ein Designfehler.

Entfernen Sie das statische Schlüsselwort und legen Sie die Variable beispielsweise im Konstruktor fest. Auf diese Weise setzt Son die Variable in seinem Konstruktor nur auf einen anderen Wert.

Patrick Cornelissen
quelle
1
Was ist daran falsch? Wenn die 'me'-Mitgliedsvariable in Ihrem Design überschreibbar sein soll, dann ist patrick's die richtige Lösung
Chii
Wenn der Wert von mir für jede Instanz gleich ist, wäre es in Ordnung, nur 'statisch' zu entfernen. Die Initialisierung muss nicht im Konstruktor erfolgen.
Nate Parsons
Richtig, obwohl technisch (in Bytecode) denke ich, dass es fast das gleiche ist ;-)
Patrick Cornelissen
4

Obwohl es stimmt, dass Klassenvariablen möglicherweise nur in Unterklassen versteckt und nicht überschrieben werden, ist es dennoch möglich, das zu tun, was Sie wollen, ohne printMe ()in Unterklassen zu überschreiben , und Reflektion ist Ihr Freund. Im folgenden Code verzichte ich aus Gründen der Übersichtlichkeit auf die Ausnahmebehandlung. Bitte beachten Sie, dass das Deklarieren meals protectedin diesem Zusammenhang wenig sinnvoll erscheint, da es in Unterklassen versteckt wird ...

class Dad
  {
    static String me = "dad";

    public void printMe ()
      {
        java.lang.reflect.Field field = this.getClass ().getDeclaredField ("me");
        System.out.println (field.get (null));
      }
  }
Sergey Ushakov
quelle
Sehr interessant, Java.lang.reflect huh? ... +1
nckbrz
3
class Dad
{
    protected static String me = "dad";

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    protected static String _me = me = "son";
}

public void doIt()
{
    new Son().printMe();
}

... druckt "Sohn".

user2895864
quelle
Ist das nicht dasselbe wie die ursprüngliche Frage?
Corley Brigman
Kannst du erklären warum ? (in Ihrer Antwort)
Mr_and_Mrs_D
3

https://docs.oracle.com/javase/tutorial/java/IandI/hidevariables.html

Es heißt Hiding Fields

Über den obigen Link

Innerhalb einer Klasse verbirgt ein Feld, das denselben Namen wie ein Feld in der Oberklasse hat, das Feld der Oberklasse, auch wenn ihre Typen unterschiedlich sind. Innerhalb der Unterklasse kann das Feld in der Oberklasse nicht durch seinen einfachen Namen referenziert werden. Stattdessen muss auf das Feld über Super zugegriffen werden, was im nächsten Abschnitt behandelt wird. Im Allgemeinen empfehlen wir, Felder nicht auszublenden, da dies das Lesen von Code erschwert.

sadjkas sadal
quelle
1
Ein Link zu einer möglichen Lösung ist immer willkommen. Fügen Sie jedoch einen Kontext um den Link hinzu, damit Ihre Mitbenutzer eine Vorstellung davon haben, was es ist und warum es dort ist. Zitieren Sie immer den relevantesten Teil eines wichtigen Links, falls die Zielwebsite nicht erreichbar ist oder dauerhaft offline geht. Berücksichtigen Sie, dass kaum mehr als ein Link zu einer externen Website ein möglicher Grund dafür ist, warum und wie einige Antworten gelöscht werden. .
FelixSFD
2

nur durch Überschreiben printMe():

class Son extends Dad 
{
    public void printMe() 
    {
        System.out.println("son");
    }
}

Der Verweis auf mein der Dad.printMeMethode verweist implizit auf das statische Feld Dad.me. Auf die eine oder andere Weise ändern Sie also, was printMein Son...

Dan Vinton
quelle
2

Sie können Variablen in einer Klasse nicht überschreiben. Sie können nur Methoden überschreiben. Sie sollten die Variablen privat halten, da sonst viele Probleme auftreten können.

Ionel Bratianu
quelle
Sie können keine Statik überschreiben.
Tom Hawtin - Tackline
(oder zumindest macht es keinen Sinn, IFSWIM.)
Tom Hawtin - Tackline
Richtig, Sie können die Statik nicht überschreiben. Sie können sie SCHATTEN oder einige Leute nannten mich MASKING.
Dale
2

Es gibt tatsächlich "Papa" aus, da das Feld nicht überschrieben, sondern ausgeblendet wird. Es gibt drei Ansätze, um "Sohn" zu drucken:

Ansatz 1: printMe überschreiben

class Dad
{
    protected static String me = "dad";

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    protected static String me = "son";

    @override
    public void printMe()
    {
        System.out.println(me);
    }
}

public void doIt()
{
    new Son().printMe();
}

Ansatz 2: Verstecken Sie das Feld nicht und initialisieren Sie es im Konstruktor

class Dad
{
    protected static String me = "dad";

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    public Son()
    {
        me = "son";
    }
}

public void doIt()
{
    new Son().printMe();
}

Ansatz 3: Verwenden Sie den statischen Wert, um ein Feld im Konstruktor zu initialisieren

class Dad
{
    private static String meInit = "Dad";

    protected String me;

    public Dad() 
    {
       me = meInit;
    }

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    private static String meInit = "son";

    public Son()
    {
        me = meInit;
    }

}

public void doIt()
{
    new Son().printMe();
}
Rik Schaaf
quelle
2

Variablen nehmen nicht am Überbinden teil. Nur Methoden tun dies. Ein Methodenaufruf wird zur Laufzeit aufgelöst, dh die Entscheidung zum Aufrufen einer Methode wird zur Laufzeit getroffen, die Variablen werden jedoch nur zur Kompilierungszeit entschieden. Daher wird diese Variable aufgerufen, deren Referenz zum Aufrufen verwendet wird und nicht des Laufzeitobjekts.

Schauen Sie sich folgenden Ausschnitt an:

package com.demo;

class Bike {
  int max_speed = 90;
  public void disp_speed() {
    System.out.println("Inside bike");
 }
}

public class Honda_bikes extends Bike {
  int max_speed = 150;
  public void disp_speed() {
    System.out.println("Inside Honda");
}

public static void main(String[] args) {
    Honda_bikes obj1 = new Honda_bikes();
    Bike obj2 = new Honda_bikes();
    Bike obj3 = new Bike();

    obj1.disp_speed();
    obj2.disp_speed();
    obj3.disp_speed();

    System.out.println("Max_Speed = " + obj1.max_speed);
    System.out.println("Max_Speed = " + obj2.max_speed);
    System.out.println("Max_Speed = " + obj3.max_speed);
  }

}

Wenn Sie den Code ausführen, zeigt die Konsole Folgendes an:

Inside Honda
Inside Honda
Inside bike

Max_Speed = 150
Max_Speed = 90
Max_Speed = 90
Ananta SE
quelle
0

Natürlich wird empfohlen, private Attribute sowie Getter und Setter zu verwenden, aber ich habe Folgendes getestet, und es funktioniert ... Siehe den Kommentar im Code

class Dad
{
    protected static String me = "dad";

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    protected static String me = "son";

    /* 
    Adding Method printMe() to this class, outputs son 
    even though Attribute me from class Dad can apparently not be overridden
    */

    public void printMe()
    {
        System.out.println(me);
    }
}

class Tester
{
    public static void main(String[] arg)
    {
        new Son().printMe();
    }
}

Sooo ... habe ich gerade die Vererbungsregeln neu definiert oder Oracle in eine schwierige Situation gebracht? Für mich ist der geschützte statische String me eindeutig überschrieben, wie Sie sehen können, wenn Sie dieses Programm ausführen. Außerdem macht es für mich keinen Sinn, warum Attribute nicht überschreibbar sein sollten.

Pixel
quelle
1
In diesem Fall "verstecken" Sie die Variable vor der Superklasse. Dies unterscheidet sich vom Überschreiben und hat nichts mit Vererbung zu tun. Andere Klassen sehen die eine oder andere Variable, je nachdem, ob sie auf die Basisklasse oder die Unterklasse verwiesen haben und die, die sie sehen, zur Kompilierungszeit festgelegt ist. Wenn Sie den Namen "ich" in der Unterklasse in etwas anderes ändern würden, würden Sie den gleichen Effekt erzielen. Die meisten IDEs und Tools zur Codeüberprüfung (Findbugs usw.) warnen Sie, wenn Sie solche Variablen ausblenden, da dies normalerweise nicht das ist, was Sie tun möchten.
AutomatedMike
Sie betrachten die Programmierung nur aus der Perspektive der Codierung. Was in Ordnung ist, Code wie Sie wollen in diesem Fall. Wenn Sie die Codierung aus der Sicht eines Teammitglieds betrachten, werden die Regeln klar, z. B. warum Felder nicht überschreibbar sind und so weiter. Ich könnte Ihnen eine Rede darüber halten, warum, aber wenn Sie es nicht aus der Perspektive eines Teammitglieds sehen, werden Sie meine Punkte nur als ungültig ansehen und ungültige Argumente zurückwerfen.
Nckbrz
0

Warum sollten Sie Variablen überschreiben, wenn Sie sie in den Unterklassen leicht neu zuweisen können?

Ich folge diesem Muster, um das Sprachdesign zu umgehen. Angenommen, Sie haben eine gewichtige Serviceklasse in Ihrem Framework, die in mehreren abgeleiteten Anwendungen in verschiedenen Varianten verwendet werden muss. In diesem Fall können Sie die Superklassenlogik am besten konfigurieren, indem Sie ihre definierenden Variablen neu zuweisen.

public interface ExtensibleService{
void init();
}

public class WeightyLogicService implements ExtensibleService{
    private String directoryPath="c:\hello";

    public void doLogic(){
         //never forget to call init() before invocation or build safeguards
         init();
       //some logic goes here
   }

   public void init(){}    

}

public class WeightyLogicService_myAdaptation extends WeightyLogicService {
   @Override
   public void init(){
    directoryPath="c:\my_hello";
   }

}
premganz
quelle
0

Nein. Klassenvariablen (gelten auch für Instanzvariablen) weisen in Java keine übergeordnete Funktion auf, da Klassenvariablen auf der Grundlage des Typs des aufrufenden Objekts aufgerufen werden. Es wurde eine weitere Klasse (Mensch) in der Hierarchie hinzugefügt, um dies klarer zu machen. Also jetzt haben wir

Sohn erweitert Vater erweitert Mensch

Im folgenden Code versuchen wir, über ein Array von Human-, Dad- und Son-Objekten zu iterieren, aber es gibt in allen Fällen die Werte der Human Class aus, da der Typ des aufrufenden Objekts Human war.

    class Human
{
    static String me = "human";

    public void printMe()
    {
        System.out.println(me);
    }
}
class Dad extends Human
{
    static String me = "dad";

}

class Son extends Dad
{
    static String me = "son";
}


public class ClassVariables {
    public static void main(String[] abc)   {
        Human[] humans = new Human[3];
        humans[0] = new Human();
        humans[1] = new Dad();
        humans[2] = new Son();
        for(Human human: humans)   {
            System.out.println(human.me);        // prints human for all objects
        }
    }
}

Wird gedruckt

  • Mensch
  • Mensch
  • Mensch

Also kein Überschreiben von Klassenvariablen.

Wenn wir von einer Referenzvariablen der übergeordneten Klasse auf die Klassenvariable des tatsächlichen Objekts zugreifen möchten, müssen wir dies dem Compiler explizit mitteilen, indem wir die übergeordnete Referenz (menschliches Objekt) in ihren Typ umwandeln.

    System.out.println(((Dad)humans[1]).me);        // prints dad

    System.out.println(((Son)humans[2]).me);        // prints son

Wird gedruckt

  • Papa
  • Sohn

Wie Teil dieser Frage: - Wie bereits vorgeschlagen, überschreiben Sie die printMe () -Methode in der Son-Klasse und rufen Sie sie dann auf

Son().printMe();

Die Klassenvariable "me" von Dad wird ausgeblendet, da die nächste Deklaration (von der son class printme () -Methode) des "me" (in der Son-Klasse) Vorrang hat.

Entwickler
quelle
0

Rufen Sie einfach super.variable im Unterklassenkonstruktor auf

public abstract class Beverage {

int cost;


int getCost() {

    return cost;

}

}`

public class Coffee extends Beverage {


int cost = 10;
Coffee(){
    super.cost = cost;
}


}`

public class Driver {

public static void main(String[] args) {

    Beverage coffee = new Coffee();

    System.out.println(coffee.getCost());

}

}

Die Ausgabe ist 10.

fraktal80y
quelle