【问题标题】:Is my usage of a superclass constructor call here necessary?我在这里使用超类构造函数调用是否有必要?
【发布时间】:2016-07-19 18:34:24
【问题描述】:

我对 Java 还很陌生,但仍在练习面向对象设计和编程的基础知识。我有几个关于继承、对象创建和 super() 调用的正确用法的问题。

请考虑以下几点:

超类,

package pet

public class Dog {

    private String foodDogEats;

    public Dog() {
        this.foodDogEats;
    }

    public String getDogFood() {
        return foodDogEats;
    }

    public void setDogFood() {
        foodDogEats = "kibble";
    }

}

子类,

package pet

public class Fido extends Dog {

    public Fido() {
        super();
        Dog dog = New Dog();
        this.whatDoesFidoEat();
    }

    public whatDoesFidoEat(Dog dog) {
        System.out.println("Fido eats " + dog.getDogFood());
    }

}

和控制器。

package pet

public class MyDogFido {

    public void MyDogFido() {
        Fido fido = new Fido();
    }

}

本示例中的对象是,实例化一个新的Fido 对象,并在此过程中执行whatDoesFidoEat() 中的打印行为。我有几个问题-

因为我已经调用了一个新的Fido 对象(它是Dog 的扩展,如果我记得正确地继承了它的方法和变量),是Fido() 中一个新的Dog 对象的实例化构造函数多余?

super() 的使用怎么样?

哪种行为是正确的——在Fido() 中实例化Dog 对象,调用super(),或两者兼而有之?

我最初在控制类中使用两者编写了一些类似的代码。不过,写完这个问题后,我的直觉告诉我,我应该只打电话给super()。

请随意评论我的示例代码(特别是 Dog() 构造函数中的 this.foodDogEats; 行 - 也不确定那里的用法)。

谢谢!

【问题讨论】:

  • 需要注意的是,如果超类使用private 数据成员,则只能使用超类的get/set 方法访问它们。您可以使用protected 允许子类通过this 直接访问数据成员。 private 通常仍用于在 get/set 方法中发生某些逻辑的情况下,这些逻辑是超类正常运行所需的。
  • 这与问题并不完全相关,但最好避免从构造函数调用非最终方法(就像在 Fido() 构造函数中所做的那样)。如果您继承 Fido 并覆盖 getDogFood() 方法,您最终可能会出现一些非常意外的行为。

标签: java oop inheritance constructor


【解决方案1】:

你说得对,Dog dog = new Dog(); 行是多余的。它将创建第二个完全独立的 Dog 对象。

super() 调用也是不必要的。所有 Java 构造函数都会隐式调用 super(),除非您通过调用不同的超级构造函数来覆盖该行为。

【讨论】:

  • 感谢您的澄清!所以基本上两者都不是——当我创建一个 Fido 对象时,它会为我做所有事情吗?那么超类变量的设置呢?我需要调用超类的 setter 方法吗?如果在超类构造函数中调用它,我可以跳过它吗?如果是这样,这样做是否可以接受?
  • 如果超类有私有变量,它必须自己设置它们。如果子类想在构造时设置这些值,您可以将它们作为参数传递给超级构造函数。
  • 为了封装而将超类中的所有变量设为私有会更好吗?
【解决方案2】:

因为我已经在调用一个新的Fido 对象(它是Dog 的扩展,如果我记得正确地继承了它的方法和变量),是在Fido() 中实例化一个新的Dog 对象构造函数多余?

这不是多余的本身,但它相当无意义和浪费。您正在实例化一个新的不同 Dog,然后忽略它。垃圾收集器最终会清理它,但你一开始就不需要它。您的Fido,您可以在类构造函数和方法中将其称为this,是 Dog。你不需要在构造函数中做任何事情来做到这一点,你也不能在构造函数中做任何事情来阻止它。

super()怎么用?

如果一个类的构造函数没有显式调用同一类的另一个构造函数或超类的一个构造函数,那么它会隐式调用super()。在这种情况下,如果超类没有这样的空构造函数,那么这是一个错误。显式调用 super() 并不完全是多余的,因为这会抑制隐式调用,但没有必要这样做。

哪个是正确的行为 - 在 Fido() 中实例化 Dog 对象,调用 super(),或两者兼而有之?

您通过直接或间接调用其构造函数之一来初始化超类。如果超类有一个空构造函数,并且你想调用它,那么该调用可以是隐式的。

请随意评论我的示例代码(特别是 Dog() 构造函数中的 this.foodDogEats; 行 - 也不确定那里的用法)。

您引用的行是错误的。如果允许,我希望它评估为默认值this.foodDogEats (null),没有其他效果。那将毫无意义。

此外,您的 setDogFood() 方法很奇怪。属性设置器通常具有以下形式:

    public void setDogFood(String what) {
        foodDogEats = what;
    }

您的原始版本有点毫无意义。

【讨论】:

  • 感谢您的澄清!关于setDogFood() - 在变量是预先确定且不变的情况下,在Dog() 构造函数中声明该变量的值会更好吗?在Dog 类代码块中设置它怎么样?如果我在代码块中设置它,我应该如何为它添加关键字以便在保留子类访问权限的同时正确封装?
  • @drs,您可以避免提供更改Dog 食物的方法,但是您不能让它真正不变,同时允许它在子类中可变。在任何情况下,您提供的方法都不会在您的代码中调用;除非被调用,否则它无效。如果在新实例中需要不同于null 的值,那么设置该值是构造函数的工作。也许您想要一个接受适当食物作为参数的构造函数。
【解决方案3】:

您的代码应该在超类中显示编译器错误。

让我们来看看你的逻辑:

在你拥有的超类中

this.foodDogEats; // should be a compiler error

你认为它有什么作用?它什么也不做,因为它只是一个声明。 'this' 关键字仅表示您指的是您创建的对象的特定实例中的变量,但您对它什么都不做,这会导致错误。

在您的子类中,您不需要调用 super,因为默认情况下会自动调用 super,除非您在超类中没有无参数构造函数。

【讨论】:

  • 感谢您的澄清!当我在不同的类中创建该类的新对象并希望引用该特定变量时,我只需要担心使用this?
  • 只有当您使用继承并且该类中有一个也存在于超类中的变量、对象或方法时(意味着它们具有相同的名称),您才需要使用“this”关键字)。 'this' 关键字让运行时知道您的意思是使用在子类中定义的实例,而不是在超类中定义的实例。否则同名会发生冲突。
  • 在这种情况下,应该在子类构造函数中使用this吗?
  • 在这种特定情况下,您的子类中也不需要“this”,因为据我所知,您没有任何名称冲突,但如果您确实有这样的冲突,那么您应该使用它
【解决方案4】:

如果构造函数没有显式调用超类构造函数,则 Java 编译器会自动插入对超类的无参数构造函数的调用。如果超类没有无参数构造函数,则会出现编译时错误。 Object 确实有这样的构造函数,所以如果 Object 是唯一的超类,没有问题。

【讨论】:

    【解决方案5】:

    您对super() 的使用是正确的。 您完全没有必要使用Dog 对象。

    通过扩展Dog,Fido继承了Dog的非私有字段和方法。因此,您可以简化Fido:

    package pet;
    
    public class Fido extends Dog {
    
        //Since this is a default constructor (no args and super() only),
        //you don't even have to declare this.
        public Fido() {
            super();
        }
    
        public void whatDoesFidoEat() {
            System.out.println("Fido eats " + foodDogEats);
        }
    
    }
    

    然后,简化您的使用:

    package pet;
    
    public class MyDogFido {
    
        public void MyDogFido() {
            Fido fido = new Fido();
            //This is a setter method for your foodDogEats. It should be declared in Dog.
            fido.setFoodDogEats("Beggin' Strips");
            fido.whatDoesFidoEat();
        }
    
    }
    

    您仍然可以让您的 Fido() 构造函数调用 whatDoesFidoEat,但由于对象的创建似乎并不依赖于方法,我发现最好避免在构造函数中进行此类调用。

    编辑:

    如果你想在构造时定义foodDogEats,你可以定义一个构造函数Fido(String):

    public Fido(String foodDogEats) {
        this.foodDogEats = foodDogEats;
    }
    

    或者,如果您的 foodDogEats 依赖于 class 而不是 instance,并且永远不会改变,您可以将变量设为 public static final FOOD_DOG_EATS。注意 - 通过Fido.FOOD_DOG_EATS 访问它与Dog.FOOD_DOG_EATS 不同,因为静态变量不是继承的。

    【讨论】:

    • 感谢您的澄清!所以一般来说,你认为我不应该在构造函数中调用 setter 方法吗?在诸如此类的特定用法中,变量foodDogEats 被预先确定为具有静态值怎么办?如果它是预先确定的并且不会改变,我是否应该使用一个设置器而不是在构造函数中设置值(或事先在类代码块中使用public static final)?
    • 你在正确的轨道上,但这也取决于场景。我将在编辑中详细说明。
    • 嗯.. 如果我需要一个子类来继承一个静态(读取:不变)值,最好让超类构造函数设置这个值吗?
    • “静态(读取:不变)” - final 是不变的。 static 是与类相关联的东西,而不是对象(可以读作:“一致”?)。如果您选择使用static 变量,则不应在构造函数中定义它。 static final 变量应在类级别声明后立即设置,而非最终的static 变量应由静态块设置。
    • 抱歉给您带来了困惑,感谢您的澄清 - final 是我想在这种情况下使用的关键字。我应该通过类代码块中的声明来设置关键字final 的变量的值吗?另外,我应该如何为这些添加关键字 - public、protected 或 private?我知道我应该默认封装和使用私有,但我想确保子类也具有相同的变量值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-25
    • 2013-07-19
    • 2019-05-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多