Есть ли способ переопределить переменные класса в Java?

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

Функция doIt выведет «папа». Есть ли способ заставить его печатать "сын"?

Dikla
источник
111
Если вы когда-нибудь увидели защищенную статику, запустите.
Том Хотин - tackline
23
@ TomHawtin-tackline, прости мое невежество, но почему защищенное статическое устройство не одобряется? Я попробовал поискать в Google, но не могу найти четкого ответа. Спасибо
Тони Чан

Ответы:

68

Да. Но что касается переменной, то она перезаписывается (Давать новое значение переменной. Давать новое определение функции - Переопределить).Just don't declare the variable but initialize (change) in the constructor or static block.

Значение будет отражено при использовании в блоках родительского класса.

если переменная статическая, то измените значение во время самой инициализации статическим блоком,

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

или изменить конструктор.

Вы также можете изменить значение позже в любых блоках. Это отразится на суперклассе

Вивек МВК
источник
10
Я думаю, что ответ должен иметь некоторый код для лучшей читабельности. Просто измените реализацию Сына на class Son extends Dad { static { me = 'son' } }
Клессио Мендес
97

Короче говоря, нет, нет способа переопределить переменную класса.

Вы не переопределяете переменные класса в Java, вы их скрываете. Переопределение, например, методы. Сокрытие отличается от переопределения.

В приведенном вами примере, объявив переменную класса с именем «me» в классе Son, вы скрываете переменную класса, которую она унаследовала бы от своего суперкласса Dad с тем же именем «me». Скрытие переменной таким образом не влияет на значение переменной класса 'me' в суперклассе Dad.

Для второй части вашего вопроса о том, как заставить его печатать «сын», я бы установил значение через конструктор. Хотя приведенный ниже код довольно сильно отличается от вашего исходного вопроса, я бы написал его примерно так;

public class Person {
    private String name;

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

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

JLS дает гораздо больше подробностей о сокрытии в разделе 8.3 - Объявление полей

Майк
источник
1
Переопределение возможно при наличии статического блока в производном классе
siddagrl
1
@siddagrl: можешь уточнить?
Mr_and_Mrs_D
какой из классов OP Personпредполагается заменить в этом примере?
n611x007
1
@naxa Sonи Dadдолжны наследовать от Person, а затем вызвать super("Son or Dad");их конструкторы.
Panzercrisis
Считается ли хорошим или плохим скрывать такие переменные в Java?
Panzercrisis
31

Да, просто переопределите printMe()метод:

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

        @Override
        public void printMe() {
                System.out.println(me);
        }
}
Ромэн Линсолас
источник
Что если используемая вами библиотека не имеет printMe()метода или даже имеет метод, который является статическим?
Венкат Судхер Редди Аедама
1
Чтобы сделать это более реальным примером, вы можете подумать, что метод printMe может быть очень сложной функцией с логикой, которую вы не хотите повторять. В этом примере вы просто хотите изменить имя человека, а не логику, которая его печатает. Вы должны создать метод с именем «getName», который вы переопределите, чтобы вернуть «son», и метод printMe вызовет метод getName как часть его печати.
Звонок
17

Вы можете создать геттер и затем переопределить его. Это особенно полезно, если переменная, которую вы переопределяете, является подклассом сама по себе. Представьте, что у вашего суперкласса есть Objectчлен, но в вашем подклассе он теперь определен как 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"
}
Джон Стриклер
источник
11

Если вы собираетесь переопределить это, я не вижу веской причины сохранять это статичным. Я бы предложил использовать абстракцию (см. Пример кода). :

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

Теперь мы можем добавить папу:

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

сын:

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

и папа встретил милую даму:

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

Похоже, у нас есть семья, давайте скажем миру их имена:

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: Tommy Nancy Dad
System.err такой же, как указано выше (только с красным шрифтом)
Вывод JOption:
Tommy, затем
Nancy, затем
Dad

nckbrz
источник
1
ОП касался изменения доступа к статике. Следовательно, «я» было общим «папой» или «сыном» - то, что не нужно создавать для каждого папы или сына и, следовательно, является статичным.
RichieHH
Да, ты прав, я не видел, чтобы это было статично. Ха-ха, это изменило бы мой ответ примерно на 100 строк кода, я бы ответил вообще. Спасибо за внимание
nckbrz
6

Это выглядит как недостаток дизайна.

Удалите ключевое слово static и установите переменную, например, в конструкторе. Таким образом, Son просто устанавливает переменную в другое значение в своем конструкторе.

Патрик Корнелиссен
источник
1
что в этом плохого? Если предполагается, что переменная-член 'me' может быть переопределена в вашем проекте, то решение Патрика - правильное решение
Chii
На самом деле, если бы значение me было одинаковым для каждого экземпляра, было бы просто удалить «static». Инициализация не должна быть в конструкторе.
Нейт Парсонс
Да, хотя технически (в байт-коде) я думаю, что это почти то же самое ;-)
Патрик Корнелиссен
4

Хотя верно, что переменные класса могут быть скрыты только в подклассах и не могут быть переопределены, все же можно делать то, что вы хотите, без переопределения printMe ()в подклассах, и отражение - ваш друг. В коде ниже я опускаю обработку исключений для ясности. Обратите внимание, что объявление meas protectedне имеет особого смысла в этом контексте, так как оно будет скрыто в подклассах ...

class Dad
  {
    static String me = "dad";

    public void printMe ()
      {
        java.lang.reflect.Field field = this.getClass ().getDeclaredField ("me");
        System.out.println (field.get (null));
      }
  }
Сергей Ушаков
источник
Очень интересно, Java.lang.reflect, да? ... +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();
}

... напечатает "сын".

user2895864
источник
Разве это не то же самое, что первоначальный вопрос?
Корли Бригман
Можешь объяснить почему? (в вашем ответе)
Mr_and_Mrs_D
3

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

Это называется скрытие полей

По ссылке выше

Внутри класса поле, которое имеет то же имя, что и поле в суперклассе, скрывает поле суперкласса, даже если их типы различны. Внутри подкласса на поле в суперклассе нельзя ссылаться по его простому имени. Вместо этого поле должно быть доступно через super, что рассматривается в следующем разделе. Вообще говоря, мы не рекомендуем скрывать поля, так как это затрудняет чтение кода.

саджкас садал
источник
1
Всегда приветствуется ссылка на потенциальное решение, но, пожалуйста, добавьте контекст вокруг ссылки, чтобы ваши коллеги-пользователи имели представление о том, что это такое и почему оно есть. Всегда указывайте наиболее релевантную часть важной ссылки, если целевой сайт недоступен или постоянно недоступен. Примите во внимание, что наличие чуть больше, чем ссылка на внешний сайт является возможной причиной, почему и как удаляются некоторые ответы? ,
FelixSFD
2

только путем переопределения printMe():

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

ссылка meна Dad.printMeметод неявно указывает на статическое поле Dad.me, поэтому вы так или иначе изменяете то, что printMeделает в Son...

Дэн Винтон
источник
2

Вы не можете переопределить переменные в классе. Вы можете переопределить только методы. Вы должны держать переменные закрытыми, иначе вы можете получить много проблем.

Ионел Братиану
источник
Вы не можете переопределить любую статику.
Том Хотин - tackline
(или по крайней мере это не имеет смысла, IFSWIM.)
Том Хотин - tackline
Правильно, вы не можете переопределить статику. Вы можете ТЕНЬ их или некоторые люди называли меня МАСКИЕМ.
Дейл
2

Это действительно печатает «папа», так как поле не переопределено, но скрыто. Есть три подхода, чтобы заставить его печатать «сын»:

Подход 1: переопределить 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();
}

Подход 2: не скрывайте поле и инициализируйте его в конструкторе

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

Подход 3: использовать статическое значение для инициализации поля в конструкторе

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();
}
Рик Шааф
источник
2

Переменные не участвуют в отбрасывании. Только методы делают. Вызов метода разрешается во время выполнения, то есть решение о вызове метода принимается во время выполнения, но переменные определяются только во время компиляции. Следовательно, вызывается эта переменная, чья ссылка используется для вызова, а не объекта времени выполнения.

Взгляните на следующий фрагмент:

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

}

Когда вы запустите код, консоль покажет:

Inside Honda
Inside Honda
Inside bike

Max_Speed = 150
Max_Speed = 90
Max_Speed = 90
Ананта С.Е.
источник
0

Конечно, рекомендуется использовать закрытые атрибуты, а также методы получения и установки, но я протестировал следующее, и это работает ... Смотрите комментарий в коде

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

Оооо ... я просто переопределил правила наследования, или я поставил Oracle в сложную ситуацию? Для меня защищенная статическая строка String Me явно переопределена, как вы можете видеть, когда вы запускаете эту программу. Кроме того, для меня нет никакого смысла, почему атрибуты не должны быть переопределенными.

пиксель
источник
1
В этом случае вы «прячете» переменную от суперкласса. Это отличается от переопределения и не имеет ничего общего с наследованием. Другие классы будут видеть одну или другую переменную в зависимости от того, ссылались ли они на базовый класс или подкласс, и тот, который они видят, фиксируется во время компиляции. Если вы измените имя «я» в подклассе на что-то другое, вы получите тот же эффект. Большинство IDE и инструментов проверки кода (findbugs и т. Д.) Предупреждают вас, когда вы прячете переменные, подобные этой, поскольку обычно это не то, что вы хотите делать.
AutomatedMike
Вы смотрите на программирование только с точки зрения кодирования. Что хорошо, код, как вы хотите в этом случае. Если вы посмотрите на кодирование с точки зрения члена команды, тогда станут ясны правила, например, почему поля не могут быть переопределены и т. Д. Я мог бы выступить с речью о том, почему, но если вы не видите этого с точки зрения члена команды, вы просто увидите мои баллы как недействительные и вернете мне неверные аргументы.
nckbrz
0

Почему вы хотите переопределить переменные, когда вы можете легко переназначить их в подклассах.

Я следую этой схеме, чтобы обойти языковой дизайн. Предположим, что в вашей среде есть весомый класс обслуживания, который необходимо использовать в разных вариантах в нескольких производных приложениях. В этом случае лучший способ настроить логику суперкласса - это переназначить его «определяющие» переменные.

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
источник
0

Нет. Переменные класса (также применимые к переменным экземпляра) не обладают переопределением в Java, поскольку переменные класса вызываются на основе типа вызывающего объекта. Добавлен еще один класс (Человек) в иерархию, чтобы сделать его более понятным. Итак, теперь у нас есть

Сын расширяет папа расширяет человека

В приведенном ниже коде мы пытаемся перебрать массив объектов Human, Dad и Son, но во всех случаях он печатает значения Human Class, поскольку тип вызывающего объекта был 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
        }
    }
}

Будет печатать

  • человек
  • человек
  • человек

Таким образом, нет переопределения переменных класса.

Если мы хотим получить доступ к переменной класса фактического объекта из ссылочной переменной его родительского класса, нам нужно явно сообщить об этом компилятору, приведя родительскую ссылку (объект Human) к его типу.

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

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

Будет печатать

  • папа
  • сын

О том, как часть этого вопроса: - Как уже предлагалось переопределить метод printMe () в классе Son, то при вызове

Son().printMe();

Переменная класса «папа» папы будет скрыта, потому что ближайшая декларация (из метода Sonme printme ()) «меня» (в классе сына) получит приоритет.

разработчик
источник
0

Просто вызовите super.variable в конструкторе подкласса

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

}

}

Выход 10.

fractal80y
источник