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

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 будет печатать "dad". Есть ли способ заставить его напечатать "сын"?

16 ответов


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

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

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

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

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

вы также можете изменить значение каких-либо блоков. Он получит отражено в супер классе


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

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

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

для второй части вашего вопроса о том, как заставить его печатать "son", я бы установил значение через конструктор. Хотя приведенный ниже код довольно сильно отличается от вашего первоначального вопроса, я бы написал что-то вроде этого;

public class Person {
    private String name;

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

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

JLS дает намного больше информации о скрытии в разделе 8.3 - Поле Заявления


да, просто переопределить printMe() способ:

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

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

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

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

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

}


Это похоже на недостаток дизайна.

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


хотя верно, что переменные класса могут быть скрыты только в подклассах, а не переопределены, по-прежнему можно делать то, что вы хотите, не переопределяя printMe () в подклассах, и отражение это ваш друг. В приведенном ниже коде я опускаю обработку исключений для ясности. Обратите внимание, что объявление me as 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));
      }
  }

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

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


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

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

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


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


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

подход 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();
}

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

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

из ссылки выше

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


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

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 ... я только что переопределил правила наследования или поставил Oracle в сложную ситуацию ? Для меня защищенная статическая строка me явно переопределена, как вы можете видеть при выполнении этой программы. Кроме того, для меня не имеет никакого смысла, почему атрибуты не должны быть переопределяемый.


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

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

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

}

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

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

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

печати

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

поэтому нет переопределения переменных класса.

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

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

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

будет печать

  • папа
  • сын

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

Son().printMe();

переменная класса "me" папы будет скрыта, потому что ближайшее объявление(из метода son class printme ()) "me"(в классе Son) получит приоритет.


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

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

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