简体   繁体   English

用Java调用自身的构造函数

[英]A constructor calling itself in java

I have two constructors in my class that extends another class. 我的课堂上有两个构造函数,它们扩展了另一个课堂。 The first one takes two parameters. 第一个有两个参数。 The second takes no parameters and starts with this(param1,param2) that calls the first constructor. 第二个不带参数,并以this(param1,param2)开头,后者调用第一个构造函数。 In the first constructor that takes two parameters I have an if statement that checks some conditions. 在带有两个参数的第一个构造函数中,我有一个if语句来检查某些条件。 If the conditions are met then that is ok and the program can assign values to two variables. 如果满足条件,则可以,程序可以将值分配给两个变量。 Otherwise I want the first constructor to be called again until those conditions are met. 否则,我希望第一个构造函数再次被调用,直到满足这些条件。 Can it be done? 能做到吗 My goal is to add a student to an array and print 10 students out that are over 18 and started school no earlier than 1980 and no later than 2015. 我的目标是将一个学生添加到数组中,并打印出10个年龄在18岁以上的学生,并且不早于1980年且不迟于2015年开始上学。

This is the subclass 这是子类

public class Student extends Human{
    private int startYear;
    private int currentYear;

    public Student(int startYear, int currentYear){
        super(); // the constructor with no argument is called (i.e. the constructor that slumps age and name)
        if(Integer.parseInt(this.getAge())>18 && this.startYear<=2015 && this.startYear>=1980){
            this.startYear = startYear;
            this.currentYear = currentYear;
        } else {
            // What do I put here to call this constructor again?
        }
    }

    public Student(){
        this((int)(Math.random()*2011),(int)(Math.random()*4));
    }

This is the superclass 这是超类

public class Human {
    private int age;
    private String name;

    /**
     * creates a list of names
     */
    private static String[] nameArray = {"Name1", "Name2", "Name3"};

    /**
     * This is a constructor
     * @param ageIn
     * @param nameIn
     */
    public Human(int ageIn, String nameIn){
        this.age = ageIn;
        this.name = nameIn;
    }

    /**
     * constructor that does not take any parameters
     * this() refers to the "head constructor" in this class.
     */
    public Human(){
        this((int)(Math.random()*100), nameArray[(int)(Math.random()*nameArray.length)]);
}
/**
 * create setter
 * @param ageIn
 */
public void setAge(int ageIn){
    this.age = ageIn;
}

/**
 * create setter
 * @param nameIn
 */
public void setName(String nameIn){
    this.name = nameIn;
}

/**
 * returns a string with name and age
 * @return
 */
public String toString(){
    return "Name: " + this.name + ", " + "Age: " + this.age;
}

I believe you have a design problem. 我相信您有设计上的问题。

You seem to be trying to create constructors for Human and Students that will set up an object with random values for the various fields. 您似乎正在尝试为“ Human和“ Students创建构造函数,这些构造函数将为各个字段设置具有随机值的对象。

First, this goal in itself is questionable. 首先,这个目标本身值得怀疑。 You should design objects not with just one assignment in mind. 设计对象时,不仅要考虑一项任务。 The objects should be designed to represent the real world entities in a way that will be generally useful. 对象应设计为以通常有用的方式表示真实世界的实体。 Constructors should normally have a predictable result. 构造函数通常应具有可预测的结果。 They may set up reasonable defaults if values are not provided. 如果未提供值,他们可以设置合理的默认值。

Therefore, it would be much better to design Human and Student without randomness - with plain constructors that accept values given by callers - and then design a separate method that will generate random values which will be useful for populating an array for a specific task. 因此,这将是更好的设计HumanStudent没有随机性-与接受来电者给定的值平原构造-然后设计一个单独的方法,将产生,这将是用于填充数组特定任务有用的随机值。

This means that your constructors should merely validate that the values are acceptable for Human and Student . 这意味着您的构造函数应仅验证HumanStudent可接受的值。 Normally, if a parameter is passed to a constructor which is not valid for that constructor, the constructor throws an exception. 通常,如果将参数传递给对该构造函数无效的构造函数,则该构造函数将引发异常。 A commonly-used exception would be IllegalArgumentException , which is a RuntimeException and doesn't need to be declared in the the throws clause of the constructor. 常用的异常是IllegalArgumentException ,它是RuntimeException ,不需要在构造函数的throws子句中声明。

So let's start with Human . 因此,让我们从Human开始。

public class Human {
    private int age;
    private String name;

    /**
     * This is a constructor
     * @param ageIn
     * @param nameIn
     */
    public Human(int ageIn, String nameIn){
        if ( ageIn < 0 ) {
            throw new IllegalArgumentException( "Negative age is not acceptable for human: " + ageIn );
        }
        if ( ageIn > 150 ) {
            throw new IllegalArgumentException( "Age is limited to 150 years for a human, supplied value was: " + ageIn );
        }
        if ( nameIn == null ) {
            throw new IllegalArgumentException( "A non-null string is expected for human" );
        }
        age = ageIn;
        name = nameIn;
    }

    // Getters, setters
}

Now, this class doesn't even have a no-args constructor, because for the time being, there are no "reasonable" defaults for a Human . 现在,该类甚至没有no-args构造函数,因为暂时而言, Human没有“合理的”默认值。 Whenever you create one, an age and a name are mandatory. 每当您创建一个时,年龄和名称都是必填项。

As you can see, I'm checking that the age is within reasonable limits for a human, and that the name is not null. 如您所见,我正在检查年龄是否在合理范围内,并且该名称不为null。 If the parameters are not reasonable, an exception will be thrown and object creation will fail. 如果参数不合理,将引发异常,并且对象创建将失败。

Next, move to the student. 接下来,转到学生。 A Student seems to be a human whose age is over 18, and who started studying somewhere between 1980 and 2015. Student似乎是18岁以上的人,他在1980年至2015年之间开始学习。

public class Student extends Human{
    private int startYear;
    private int currentYear;

    public Student( int age, String name, int startYear, int currentYear ) {

        super( age, name );
        if ( age < 18 ) {
            throw new IllegalArgumentException( "A student cannot be less than 18 years old. Supplied age is: " + age );
        }
        if ( startYear < 1980 || startYear > 2015 ) {
            throw new IllegalArgumentException( "A Student must have started studying between 1980 and 2015. Supplied start year is: " + startYear );
        }
        if ( currentYear < 1 || currentYear > 4 ) {
            throw new IllegalArgumentException( "A student must currently be in his 1st to 4th year. Supplied curret year is: " + currentYear );
        }
        this.startYear = startYear;
        this.currentYear = currentYear;
    }

    // Getters, setters, etc

}

Now you may notice that we pass the age as-is to the super(age,name) and test the constraint only after that. 现在您可能会注意到,我们将年龄原样传递给super(age,name)并仅之后测试约束。 This is because super(...) has to be the first instruction in the constructor. 这是因为super(...)必须是构造函数中的第一条指令。 But it doesn't really matter much. 但这并不重要。 It's not that a new object is allocated for super . 不是为super分配了新对象。 The allocation has already been done, as super is part of the current object. 分配已经完成,因为super是当前对象的一部分。 So if the age is less than 18, the super constructor may have done a couple of assignments that will not be used anyway because it's going to be destroyed now that it turns out that the age is illegal for a student - but that's not much of a waste, and can't be avoided anyway. 因此,如果年龄小于18岁,那么超级构造函数可能已经做过几次任务,这些任务无论如何都不会使用,因为事实证明该年龄对学生而言是非法的,因此它将被销毁-但这并不是很多浪费,无论如何都无法避免。

The other checks are for the fields in Student itself so they come before the assignments are done. 其他检查是针对“ Student本身中的字段的,因此它们会在作业完成之前出现。

Now how do we generate a proper "random" student for your assignment? 现在,我们如何为您的工作分配合适的“随机”学生?

You can create a static method in Student if you think it's going to be useful for other assignments, or create it in one of the classes you are writing for your current assignment, perhaps your Main class, or perhaps a Utility class, etc. 如果您认为在其他作业中将有用的话,可以在Student创建一个静态方法,或者在您为当前作业编写的一个类中创建它,也许是Main类,或者Utility类等。

Wherever we add it, it's in that class that we want to have the static array of possible names: 无论我们在哪里添加它,我们都希望在该类中具有可能名称的静态数组:

private static final String[] namesForRandomStudents = { "John Smith",
                                                         "George Robinson",
                                                         "Sarah Carpenter",
                                                         "Judy Thatcher"
                                                         // etc
                                                       };

And maybe add a Random object which will be used for student name generation internally: 也许添加一个Random对象,该对象将在内部用于生成学生姓名:

private static final Random studentRandomGenerator = new Random();

Now you can declare your static factory method (static methods that are used for generating instances of a certain class are called static factory methods). 现在,您可以声明您的静态工厂方法(用于生成特定类实例的静态方法称为静态工厂方法)。

Naively, you may think that the best approach is to just generate random ages and years, pass them to the constructor and catch the exception, until you succeed in generating a "good" result: 天真的,您可能认为最好的方法是只生成随机的年龄和年份,将它们传递给构造函数并捕获异常,直到成功生成“好”结果为止:

/**
 * NAIVE APPROACH, NOT RECOMMENDED
 */
public static Student getRandomStudentInstance() {

    boolean failed = true;
    Student result = null;
    do {
        try {
            result = new Student(
                            studentRandomGenerator.nextInt(),
                            namesForRandomStudents[studentRandomGenerator.nextInt(namesForRandomStudents.length)],
                            studentRandomGenerator.nextInt(),
                            studentRandomGenerator.nextInt());
            failed = false;
        } catch ( IllegalArgumentException e ) {
            // Nothing to do here, failed will be true so
            // we'll attempt to generate again
        }
    } while ( failed );

    return result;
}

But this is bad. 但这是不好的。 You may create tons of objects that will fail, before you succeed. 在成功之前,您可能会创建大量将失败的对象。 This will take lots of time, also because the catching mechanism itself is heavier than just looping etc. 这将花费大量时间,这还因为捕获机制本身比仅循环等要重。

In general, if you get a RuntimeException (in this case an IllegalArgumentException ) thrown, it means you should have checked the value before passing it. 通常,如果抛出RuntimeException (在这种情况下为IllegalArgumentException ),则意味着您应该传递值之前检查该值。 This is true in this case as well. 在这种情况下也是如此。 It's better to make sure that you send in reasonable values that will not cause an exception at all: 最好确保您发送的合理值根本不会引起异常:

/**
 * Improved version
 */
public static Student getRandomStudentInstance() {

    // Generate an age between 18 and 40
    int age = studentRandomGenerator.nextInt(23) + 18;

    // Generate a name from the array:
    String name = namesForRandomStudents[studentRandomGenerator.nextInt(namesForRandomStudents.length)];

    // Generate a start year between 2000 and 2015 inclusive
    int startYear = studentRandomGenerator.nextInt(16) + 2000;

    // Generate student's current year between 1 and 4 inclusive
    int currentYear = studentRandomGenerator.nextInt(4) + 1;

    return new Student(age,name,startYear,currentYear);
}

Now you know that the values are reasonable. 现在您知道这些值是合理的。 You could extend the ranges of choice to the entire legal range, I simply picked values that will "look nice", no centenarian students or students who started studying during the Reagan era. 您可以将选择范围扩展到整个法律范围,我只是选择了“看起来不错”的值,没有百岁老人或在里根时代就开始学习的学生。

You can call this factory method as many times as you wish, to fill a list of random students etc., and because you have kept the "generating" method separate from your constructors, you'll be able to use the Student and Human classes in future tasks which will not rely on random generation, but instead, for example, fill in values from a database. 您可以根据需要多次调用此工厂方法,以填充随机学生等的列表,并且由于将“生成”方法与构造函数分开,因此可以使用StudentHuman类在将来的任务中,这些任务将不依赖于随机生成,而是例如从数据库中填充值。

A constructor with no arguments should have a first call to super(); 没有参数的构造函数应该首先调用super(); if none is given, the compiler will make it. 如果没有给出,编译器将完成。 If you have a second constructor, and it has arguments, the first call should be to this(); 如果您有第二个构造函数,并且有参数,则第一个调用应该是this(); that is, a call to the other constructor. 也就是说,对另一个构造函数的调用。 (source, OCA) (来源,OCA)

A constructor is called only once: when the class instance is created. 构造函数仅被调用一次:创建类实例时。 You CAN put a loop in there that checks a certain condition. 您可以在其中放置一个检查特定条件的循环。 From within that loop you can call a method as many times as you want. 在该循环中,您可以根据需要多次调用一个方法。

So, let the working be done within methods, and let the constructors call the methods, multiple times if need be by using loops. 因此,让工作在方法内完成,并让构造函数调用方法,如果需要的话,可以使用循环多次。

it's not possible. 这是不可能的。 When you call a super() you are creating object Human with some random parameters. 调用super()您将创建带有某些随机参数的Human对象。

So if your condition are not met in Student class you need to throw an exception. 因此,如果Student班不满足您的条件,则需要引发异常。

I think you should change your code to : 我认为您应该将代码更改为:

super(); // the constructor with no argument is called (i.e. the constructor that slumps age and name)
if(Integer.parseInt(this.getAge())>18 && this.startYear<=2015 && this.startYear>=1980){
    this.startYear = startYear;
    this.currentYear = currentYear;
} else {
   throw new IllegalArgumentException();
}

So, you want to generate random Students ? 那么,您想生成随机学生吗?

Constructors should remain constructors, and only assign variables. 构造函数应保留为构造函数,并且仅分配变量。

public class Student extends Human{
    private int startYear;
    private int currentYear;

    /*
     * Just a classic Student constructor with parameters
     */
    public Student(int startYear, int currentYear){
        this.startYear = startYear;
        this.currentYear = currentYear;
    }


    /*
     * Generate a random student aged over 18 and started school between 1980 and 2015.
     */
    public static Student generateRandomStudent(){
       // random age>18
       int age = 18 + Math.random()*(100-18);
       // random name chosen in the Human array
       String name = Human.nameArray[(int)(Math.random()*nameArray.length)]
       // random startYear between 1980 and 2015
       int startYear = 1980 + Math.random()*(2015-1980);
       // What the purpose of currentYear ?
       int currentYear = 2015;

       // Create the desired student
       Student randomStudent = new Student(startYear, currentYear)
       randomStudent.setAge(age);
       randomStudent.setName(name);

       return randomStudent;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM