简体   繁体   中英

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. In the first constructor that takes two parameters I have an if statement that checks some conditions. 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.

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.

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.

This means that your constructors should merely validate that the values are acceptable for Human and Student . 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.

So let's start with 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 . 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. 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.

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. This is because super(...) has to be the first instruction in the constructor. But it doesn't really matter much. It's not that a new object is allocated for super . The allocation has already been done, as super is part of the current object. 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.

The other checks are for the fields in Student itself so they come before the assignments are done.

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.

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:

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

A constructor with no arguments should have a first call to 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(); that is, a call to the other constructor. (source, 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.

So if your condition are not met in Student class you need to throw an exception.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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