Julian H Julian H - 3 months ago 9
Java Question

How to expand a randomHuman constructor to take two types of objects?

I am a java-noob as I recently started to learn in a course.
I have created a class:Humans which have ability to store their name and age, and also a subclass Students which extends Humans and adds the Year they began there studies.
I have constructed a randomHuman constructor where I call it(in my main class) and create a list with the humans(with random name and age).
My problem is when i want to random 5 human non-students and 5 students and create this list, I'm not sure how to find out what type of object is sent to the random constructor, so i know if i should give it a year or not. And what type to tell the constructor to return.

I am sorry that this turned into an essay, but if anyone would be so kind to help then I would greatly appreciate it.

TLDR; How to expand a randomHuman constructor to take two types of objects?

Here is my main class:



public class Main {
public static void main(String []args){


Human newHuman = new Human( 18, "Tommy");
System.out.println("Age: " + newHuman.getAge());
System.out.println("Name: " + newHuman.getName());
System.out.println(newHuman.toString());

Human putte = new Human (25,"Putte");
System.out.println(putte);
//Varför blir det så?
//kanske lokal variabel

//Array RandomHumans
System.out.println(" ");
System.out.println("Array Human");
ArrayList<Human> myAl = new ArrayList();

for(int i = 0; i<15; i++){
Human xx =Human.randomHuman();
myAl.add(xx);
}
//Array RandomFysiker
for(int j = 0; j<myAl.size(); j++){
Human var = myAl.get(j);
System.out.println(var.toString());
}

System.out.println(" ");
System.out.println("Array Fysiker");
ArrayList<Fysiker> myAl2 = new ArrayList();
//puts the Fysiker in an array
for(int i = 0; i<15; i++){
Fysiker xx =Fysiker.randomHuman();
myAl2.add(xx);
}
//prints teh array
for(int j = 0; j<myAl2.size(); j++){
Fysiker var = myAl2.get(j);
System.out.println(var.toString());
}


}
}


and my Human class:

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

Human(int ageIn, String nameIn){ //Constructor
age=ageIn;
name=nameIn;
}
public int getAge(){
return age;
}
public String getName(){
return name;
}
public String toString(){
return "Name: " + getName() +"," + " Age: " + getAge();
}


//Random human
// Behöver ändra konstruktorn så att den kan kolla
// om objectet är Fysiker eller Human och sedan,
// Behandla dom olika
//Problem1: Hur kollar man? Föreslag if(obj instanceof Fysiker), men vad ska jag ha som obj
//Problem2: Vilken returtyp ska man då ha?
public static Human randomHuman(){
String[] anArrayOfStrings={"Tom", "Jon", "Chris","Julian","Roberto","Sam","Lisa","Roxanne","Rebecca","Anton","Johannes","Antonella","Bianca"};
int randomAge = (int) (100*Math.random());
String randomName = anArrayOfStrings[(int)(Math.random()*anArrayOfStrings.length)];
int RandomYear = (int) (Math.random()*(2013-1932) + 1932);
// if(xx instanceof Fysiker){
//
// }
return new Human(randomAge,randomName);
}
}


and the subclass Fysiker(aka student):

/**
*
* @author Julian
*/

public class Fysiker extends Human{
public int schoolYear;

public Fysiker(int startYear,int ageIn, String nameIn){
super(ageIn, nameIn);
if (age>15){
if (startYear>2013){
} else if (startYear<1932){
} else {
schoolYear = startYear;
}
} else {
}
}
public int getYear(){
return schoolYear;
}

public String toString(){
return super.toString() +","+" Startyear: " +getYear();
}
}

Answer

Actually, your randomHuman() method, as mentioned in the comments, is not a constructor at all. It's a static factory method, although I'm sure you're not aware of what that means as yet.

Basically, a constructor is not a method at all and doesn't have a return type. What a constructor does is provide an initialization for a new instance of the class, created by using new, although it can do things that don't strictly initiate the fields of that object.

A method, in contrast, can return something. And in your particular case, the last line actually tells you exactly what it returns - it's calling new for the class Human, so it will return an object of class Human, never a Student.

In fact, the class Human is not aware of the class Student. In principle, you could write a subclass for a class, years after the parent class has been written. Parent classes don't need to know about their descendents. They just decide what they allow those descendents to change and what they don't allow them to change.

You could, in theory, put a method in Human that creates a Student instance. But I'm pretty sure that's not needed in the current situation.

What you probably want to do is fill a list of humans outside the definition of either Human or Student. Filling a random list is probably not part of "being a human" or "being a student" is all about, so you should just do it in your Main class, calling new Human() or new Student() as you wish and filling them as appropriate. Since you know which new you called, you also know whether or not to use a random year.

You could do it in a static method in your Main class, to signify that this is something you do for testing, and not really part of the logic of either a Human or a Student.

As for being able to tell which object you now got from the list - you can do that with instanceof. But you'll also need to typecast it to Student if you want to access its getYear() method.

However - and this is the neat thing about polymorphism - if you just call the toString() method, and don't even check the type of the object, you'll get it with the year if it's really a Student object, and without it if it's a plain Human object.


Let's assume your teachers actually want you to extend the randomHuman method so that it sometimes gives Human instances, and sometimes Students. When it gives Student, it should of course provide it with a year.

As I said above, this is called Tight Coupling between parent and subclass, and is not recommended. If I wanted to build another human subclass, such as Politician, I'd have to call you and ask you to release a new version of Human that also sometimes gives random Politicians. So, under protest, I'll explain how to do it.

Your existing function is:

public static Human randomHuman(){
        String[] anArrayOfStrings={"Tom", "Jon", "Chris","Julian","Roberto","Sam","Lisa","Roxanne","Rebecca","Anton","Johannes","Antonella","Bianca"};
        int randomAge = (int) (100*Math.random());
        String randomName = anArrayOfStrings[(int)(Math.random()*anArrayOfStrings.length)];
        int RandomYear = (int) (Math.random()*(2013-1932) + 1932);
//        if(xx instanceof Fysiker){
//            
//        }
        return new Human(randomAge,randomName);
    }    

We change it like so:

public static Human randomHuman(){
    String[] anArrayOfStrings={"Tom", "Jon", "Chris","Julian","Roberto","Sam","Lisa","Roxanne","Rebecca","Anton","Johannes","Antonella","Bianca"};
    int randomAge = (int) (100*Math.random());
    String randomName = anArrayOfStrings[(int)(Math.random()*anArrayOfStrings.length)];

    Human result = null;

    if ( Math.random() < 0.5 ) {
        // With a probability of 50%, create a plain human
        result = new Human( randomAge, randomName );
    } else {
        // Create a student. Start by calculating a random year.
        int randomYear = (int) (Math.random()*(2013-1932) + 1932);
        result = new Fysiker( randomYear, randomAge, randomName );
    }

    return result;

}    

So, you decide that you want to make a plain human, and within the scope of that decision, you create it with new Human(...) and assign to the result variable.

If you decide to make a student, within the scope of that decision, you calculate a random year, and create it with new Fysiker(). You can assign it to the variable result because polymorphically, it's Human. But in reality, internally, it's a Student.

You return the result variable, which may contain either a Human or a Student at this point.