Czy istnieje sposób na przesłonięcie zmiennych klas w Javie?

161
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();
}

Funkcja doIt wypisze „tato”. Czy jest sposób, aby wydrukować „syn”?

Dikla
źródło
111
Jeśli kiedykolwiek zobaczysz chronioną statystykę, uruchom.
Tom Hawtin - tackline
23
@ TomHawtin-tackline wybacz mi moją ignorancję, ale dlaczego chronione zakłócenia są mile widziane? Próbowałem googlować, ale nie mogę znaleźć jasnej odpowiedzi. Dzięki
Tony Chan

Odpowiedzi:

68

Tak. Ale jeśli chodzi o zmienną, jest ona nadpisywana (Nadanie zmiennej zmiennej nowej wartości. Nadanie nowej definicji funkcji to Zastąp).Just don't declare the variable but initialize (change) in the constructor or static block.

Wartość zostanie odzwierciedlona podczas używania w blokach klasy nadrzędnej

jeśli zmienna jest statyczna, zmień jej wartość podczas samej inicjalizacji za pomocą bloku statycznego,

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

albo zmień konstruktora.

Możesz także zmienić wartość później w dowolnych blokach. Znajdzie to odzwierciedlenie w super klasie

Vivek MVK
źródło
10
Myślę, że odpowiedź powinna zawierać kod dla lepszej czytelności. Po prostu zmień implementację Son na class Son extends Dad { static { me = 'son' } }
Cléssio Mendes
97

Krótko mówiąc, nie, nie ma możliwości przesłonięcia zmiennej klasy.

Nie zastępujesz zmiennych klasowych w Javie, ukrywasz je. Przesłanianie dotyczy na przykład metod. Ukrywanie różni się od zastępowania.

W podanym przykładzie, deklarując zmienną klasy o nazwie „ja” w klasie Syn, ukrywasz zmienną klasową, którą odziedziczyłaby po swojej superklasie Tata o tej samej nazwie „ja”. Ukrycie zmiennej w ten sposób nie wpływa na wartość zmiennej klasy „ja” w nadklasie Tata.

W drugiej części twojego pytania, jak sprawić, by wypisał "syn", ustawiłbym wartość za pomocą konstruktora. Chociaż poniższy kod bardzo różni się od pierwotnego pytania, napisałbym go mniej więcej tak;

public class Person {
    private String name;

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

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

JLS podaje dużo więcej szczegółów na temat ukrywania w sekcji 8.3 - Deklaracje pól

Mikrofon
źródło
1
Przesłonięcie jest możliwe dzięki posiadaniu statycznego bloku w klasie pochodnej
siddagrl
1
@siddagrl: czy możesz coś rozwinąć?
Mr_and_Mrs_D
która z klas OP Personma zastąpić w tym przykładzie?
n611x007
1
@naxa Soni Dadmają dziedziczyć z Person, a następnie wywołać super("Son or Dad");ich konstruktory.
Panzercrisis
Czy ukrywanie takich zmiennych w Javie jest uważane za dobre czy złe?
Panzercrisis
31

Tak, po prostu zastąp printMe()metodę:

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

        @Override
        public void printMe() {
                System.out.println(me);
        }
}
Romain Linsolas
źródło
co jeśli biblioteka, której używasz, nie ma printMe()metody, a nawet ma tę metodę, że jest metodą statyczną?
Venkat Sudheer Reddy Aedama
1
Aby uczynić ten przykład bardziej realistycznym, możesz wziąć pod uwagę, że metoda „printMe” może być bardzo skomplikowaną funkcją z logiką, której nie chcesz powtarzać. W tym przykładzie chcesz po prostu zmienić imię i nazwisko osoby, a nie logikę, która je drukuje. Powinieneś utworzyć metodę o nazwie „getName”, którą nadpisałbyś, aby zwrócić „son”, a metoda printMe wywołałaby metodę getName jako część jej drukowania.
Zadzwoń
17

Możesz utworzyć metodę pobierającą, a następnie zastąpić ją. Jest to szczególnie przydatne, jeśli zmienna, którą nadpisujesz, jest samą w sobie podklasą. Wyobraź sobie, że Twoja superklasa ma Objectczłonka, ale w Twojej podklasie jest to teraz bardziej zdefiniowane jako 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
źródło
11

Jeśli masz zamiar go zastąpić, nie widzę żadnego uzasadnionego powodu, aby zachować to statyczne. Sugerowałbym użycie abstrakcji (patrz przykładowy kod). :

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

Teraz możemy dodać tatę:

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

syn:

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

a tata spotkał miłą panią:

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

Wygląda na to, że mamy rodzinę, powiedzmy światu ich imiona:

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 Wyjście: Tommy Nancy Dad
System.err jest taki sam jak powyżej (ma tylko czerwoną czcionkę)
JOption Wyjście:
Tommy, następnie
Nancy, a następnie
Dad

nckbrz
źródło
1
PO dotyczył zmiany dostępu do statyki. Dlatego „ja” było ogólnym „tatą” lub „synem” - czymś, co nie wymaga tworzenia dla każdego taty lub syna, a zatem jest statyczne.
RichieHH
Tak, masz rację, nie widziałem, żeby to było statyczne. Haha, to zmieniłoby moją odpowiedź na około 100 linii kodu mniej, jeśli w ogóle odpowiedź. Dzięki za
ostrzeżenie
6

To wygląda na błąd konstrukcyjny.

Usuń słowo kluczowe static i ustaw zmienną, na przykład w konstruktorze. W ten sposób Son po prostu ustawia zmienną na inną wartość w swoim konstruktorze.

Patrick Cornelissen
źródło
1
co w tym złego? Jeśli zmienna składowa „ja” ma być zastąpiona w twoim projekcie, to patrick's jest właściwym rozwiązaniem
Chii
Właściwie, jeśli wartość „ja” jest taka sama dla każdego wystąpienia, po prostu usunięcie „statycznego” byłoby w porządku. Inicjalizacja nie musi odbywać się w konstruktorze.
Nate Parsons
Zgadza się, chociaż technicznie (w kodzie bajtowym) myślę, że to prawie to samo ;-)
Patrick Cornelissen
4

Chociaż prawdą jest, że zmienne klasowe mogą być ukryte tylko w podklasach i nie mogą być nadpisywane, nadal można robić, co chcesz, bez nadpisywania printMe ()w podklasach, a odbicie jest twoim przyjacielem. W poniższym kodzie pomijam obsługę wyjątków dla przejrzystości. Zwróć uwagę, że zadeklarowanie mejako protectednie wydaje się mieć większego sensu w tym kontekście, ponieważ będzie ukryte w podklasach ...

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
źródło
Bardzo interesujące, 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();
}

... wypisze „syn”.

user2895864
źródło
czy to nie jest to samo, co oryginalne pytanie?
Corley Brigman
Czy możesz mi wytłumaczyć dlaczego ? (w Twojej odpowiedzi)
Mr_and_Mrs_D
3

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

Nazywa się Hiding Fields

Z linku powyżej

W ramach klasy pole, które ma taką samą nazwę jak pole w nadklasie, ukrywa pole nadklasy, nawet jeśli ich typy są różne. W ramach podklasy do pola w nadklasie nie można odwoływać się za pomocą jego prostej nazwy. Zamiast tego dostęp do pola należy uzyskać przez super, co jest omówione w następnej sekcji. Ogólnie rzecz biorąc, nie zalecamy ukrywania pól, ponieważ utrudnia to odczytanie kodu.

sadjkas sadal
źródło
1
Link do potencjalnego rozwiązania jest zawsze mile widziany, ale dodaj kontekst do niego, aby inni użytkownicy mieli pojęcie, co to jest i dlaczego się tam znajduje. Zawsze cytuj najbardziej odpowiednią część ważnego linku, na wypadek gdyby strona docelowa była nieosiągalna lub została trwale wyłączona. Weź pod uwagę, że fakt, że niewiele więcej niż łącze do zewnętrznej witryny jest możliwym powodem, dlaczego i jak są usuwane niektóre odpowiedzi? .
FelixSFD
2

tylko przez nadpisanie printMe():

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

odwołanie do mew Dad.printMemetodzie niejawnie wskazuje na pole statyczne Dad.me, więc w taki czy inny sposób zmieniasz to, co printMerobi w Son...

Dan Vinton
źródło
2

Nie można przesłonić zmiennych w klasie. Możesz przesłonić tylko metody. Powinieneś zachować zmienne jako prywatne, w przeciwnym razie możesz mieć wiele problemów.

Ionel Bratianu
źródło
Nie możesz zastąpić żadnych statystyk.
Tom Hawtin - tackline
(a przynajmniej to nie ma sensu, IFSWIM.)
Tom Hawtin - tackline
Prawidłowo, nie możesz zastąpić statyki. Możesz ich CIENIĆ lub niektórzy nazywali mnie MASKĄ.
Dale
2

Rzeczywiście drukuje „tato”, ponieważ pole nie jest nadpisane, ale ukryte. Istnieją trzy sposoby, aby wydrukować słowo „syn”:

Podejście 1: zastąp printMe

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();
}

Podejście 2: nie ukrywaj pola i inicjalizuj go w konstruktorze

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();
}

Podejście 3: użyj wartości statycznej, aby zainicjować pole w konstruktorze

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
źródło
2

Zmienne nie biorą udziału w nadpisywaniu. Tylko metody. Wywołanie metody jest rozwiązywane w czasie wykonywania, to znaczy decyzja o wywołaniu metody jest podejmowana w czasie wykonywania, ale zmienne są ustalane tylko w czasie kompilacji. Stąd ta zmienna jest wywoływana, której odwołanie jest używane do wywoływania, a nie obiektu wykonawczego.

Spójrz na następujący fragment:

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

}

Po uruchomieniu kodu konsola pokaże:

Inside Honda
Inside Honda
Inside bike

Max_Speed = 150
Max_Speed = 90
Max_Speed = 90
Ananta SE
źródło
0

Oczywiście używanie atrybutów prywatnych oraz metody pobierające i ustawiające byłoby zalecane, ale przetestowałem następujące elementy i działa ... Zobacz komentarz w kodzie

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();
    }
}

Więc ... czy właśnie przedefiniowałem zasady dziedziczenia, czy postawiłem Oracle w trudnej sytuacji? Dla mnie chroniona statyczna wartość String me jest wyraźnie nadpisywana, co widać po uruchomieniu tego programu. Nie ma też dla mnie sensu, dlaczego atrybuty nie powinny być nadrzędne.

Piksel
źródło
1
W tym przypadku „ukrywasz” zmienną przed superklasą. Różni się to od nadpisywania i nie ma nic wspólnego z dziedziczeniem. Inne klasy zobaczą jedną lub drugą zmienną w zależności od tego, czy odwołują się do klasy bazowej czy podklasy, a ta, którą widzą, jest ustalona w czasie kompilacji. Jeśli zmienisz nazwę „ja” w podklasie na coś innego, uzyskasz ten sam efekt. Większość IDE i narzędzi do walidacji kodu (findbugs itp.) Ostrzeże Cię, gdy ukryjesz takie zmienne, ponieważ zwykle nie jest to to, co chcesz robić.
AutomatedMike
Patrzysz na programowanie z perspektywy samego kodowania. Co jest w porządku, w takim przypadku koduj jak chcesz. Jeśli spojrzysz na kodowanie z perspektywy członka zespołu, wtedy reguły staną się jasne, na przykład dlaczego pola nie są zastępowalne i tak dalej. Mógłbym wygłosić przemówienie wyjaśniające dlaczego, ale jeśli nie widzisz tego z perspektywy członka zespołu, po prostu uznasz moje punkty za nieważne i rzucisz mi błędne argumenty.
nckbrz
0

Dlaczego miałbyś chcieć przesłonić zmienne, skoro można łatwo ponownie przypisać je w podklasach.

Podążam za tym wzorcem, aby obejść projekt języka. Załóżmy, że masz w swoim frameworku ważną klasę usług, która musi być używana w różnych smakach w wielu aplikacjach pochodnych. W takim przypadku najlepszym sposobem skonfigurowania logiki superklasy jest ponowne przypisanie jej zmiennych definiujących.

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
źródło
0

Nie. Zmienne klasowe (mają również zastosowanie do zmiennych instancji) nie mają funkcji przesłaniającej w Javie, ponieważ zmienne klasowe są wywoływane na podstawie typu wywoływanego obiektu. Dodano jeszcze jedną klasę (człowiek) w hierarchii, aby była bardziej przejrzysta. Więc teraz mamy

Syn przedłuża Tata przedłuża Człowieka

W poniższym kodzie próbujemy iterować po tablicy obiektów Human, Dad i Son, ale we wszystkich przypadkach wyświetla on wartości Human Class, ponieważ typ wywołującego obiektu to Human.

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

Wydrukuje

  • człowiek
  • człowiek
  • człowiek

Więc nie ma nadpisywania zmiennych klasy.

Jeśli chcemy uzyskać dostęp do zmiennej klasy rzeczywistego obiektu ze zmiennej referencyjnej jego klasy nadrzędnej, musimy wyraźnie powiedzieć o tym kompilatorowi, rzutując referencję nadrzędną (obiekt ludzki) na jego typ.

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

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

Wydrukuje

  • tata
  • syn

O tym, jak część tego pytania: - Jak już sugerowano, przesłonić metodę printMe () w klasie Son, a następnie wywołać

Son().printMe();

Zmienna klasy Dad „me” zostanie ukryta, ponieważ najbliższa deklaracja (z metody printme () klasy Son) elementu „me” (w klasie Son) będzie miała pierwszeństwo.

Deweloper
źródło
0

Po prostu wywołaj super.variable w konstruktorze podklasy

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());

}

}

Wyjście to 10.

fraktal80y
źródło