I'm working on a project for my data structures class and I've been having a hard time wrapping my head around Abstract class inheritance and how it can be manipulated through subclasses, object comparison, method args, etc. I've been reading through Stack Overflow posts and Java tutorials, and while I've learned a ton, I'm still not understanding some things. I haven't coded in Java in 2+ years and have been using mostly C lately so I'm feeling very rusty and overwhelmed.
Project Overview
I was asked to build a database of sorts that represents data for my university students and professors. I have an abstract superclass, Person, which has 2 child classes Student and Instructor. Lastly (the class I'm currently working on), is a class called UniversityPeople which references an array of Student[] objects as well as an array of Instructor[] objects. The UniversityPeople class has methods to add(), delete(), search(), validate data, ect. for the 2 object arrays.
I know this implementation seems really strange, but I have to follow the instructions unfortunately. I've tried to make the methods work with both arrays seamlessly which is partially where I'm getting confused. Here are my questions:
1.) This is kind of a general question, but one I feel is important to understanding inheritance. I've done tons of reading on this, but I'm still pretty confused. If I create an object that uses the type of the superclass but is a subclass object, like: Person newStudent = new Student(), will this object have access to the methods in it's own class, or is it restricted to the superclass methods only? My understanding was, if the superclass is abstract, the children still inherit their methods when instantiated in this way.
2.) I don't understand how to differentiate between the two object arrays in the methods. For example, I'm writing a clear() function that sets the specified object array(Student[] or Instructor[]) to null when called. I first tried using the instanceOf method to test what type the array was but the compiler warned me I couldn't cast type Student to Person or something like that. Here is the constructor for the class as well as the clear function I'm having trouble with. I'm not sure if I'm understanding how inheritance fully works here, so if there's a better way please enlighten me:
Constructor:
//Global declarations
//Declares arrays of student and instructor classes
private Student[] studentArray;
private Instructor[] instArray;
private int studentCount;
private int instCount;
/*
checks user input. If user enters 1, create student array.
if user enters 2, create Instructor array
*/
public UniversityPeople(int type, int size)
{
if (type == 1)
{
studentArray = new Student[size];
}
else if (type == 2)
{
instArray = new Instructor[size];
}
}
Separate insert methods for students/instructors because I couldn't figure out a way to do 1, if it's even possible:
/*
checks if array is full or duplcate id exists.
If not, new Student object is created and
inserted into the student array
*/
public void insertStudent(String lastName, String firstName, int id,
int age, String yearStanding, double GPA)
{
boolean checkFull = isFull();
boolean checkId = containsID(studentArray, id);
if (checkFull)
{
System.out.println("Array is full. Please delete a data member.");
}
else if (checkId)
{
System.out.println("Duplicate ID. Please check data and re-enter.");
}
if (!checkFull && !checkId)
{
Student newStudent = new Student(lastName, firstName, id, age, yearStanding, GPA);
newStudent = studentArray[studentCount];
studentCount++;
}
else
System.err.println("There was an error while attempting to \n" +
"process your request. Please check your \n" +
"data entry and try again.");
}//end insertStudent
/*
checks if array is full or duplicate id exists.
If not, new Instructor object is created and
inserted into instructor array
*/
public void insertInstructor (String lastName, String firstName, int id,
int age, int officeNum, String subjectTaught)
{
boolean checkFull = isFull();
boolean checkId = containsID(instArray, id);
if (checkFull)
{
System.out.println("Array is full. Please delete a data member.");
}
else if (checkId)
{
System.out.println("Duplicate ID. Please check data and re-enter.");
}
if (!checkFull && !checkId)
{
Instructor newInstructor =
new Instructor(lastName, firstName, id, age, officeNum, subjectTaught);
newInstructor = instArray[instCount];
instCount++;
}
else
System.err.println("There was an error while attempting to \n" +
"process your request. Please check your \n" +
"data entry and try again.");
}//end insertInstructor
Lastly, a method to find someone in the array from their id and a method to clear whatever array is specified. I've made the arg type Person[] array in most of these methods which I hope is acceptable.
public int findPerson(Person[] array, int id)
{
for (int i = 0; i < array.length; i++)
{
if (array[i].getId() == id)
{
return i;
}
}
return -1;
}
//Sets specified array to null. I need to reset the count of objects in
//the specified array to 0, but i'm really confused about how that works. Below is the best
//understanding I could come up with
public void clear(Person[] array)
{
for (int i = 0; i < array.length; i++)
{
array[i] = null;
}
if (array.getClass().equals(Student.class))
{
studentCount = 0;
}
else if (array.getClass().equals(Instructor.class))
{
instCount = 0;
}
else
{
System.out.println("There was an issue validating the object type.");
}
}
I'm especially confused about the implementation of the clear() method as I've never had to do this type of object comparison before. I'm not sure why the instanceOf operator comparison was giving me weird results.
[...] will this object have access to the methods in it's own class, or is
it restricted to the superclass methods only? My understanding was, if
the superclass is abstract, the children still inherit their methods
when instantiated in this way.
It doesn't matter, whether the superclass is abstract. A child class inherits the parent class' methods and attributes. Whether these are accessible by the child class depends on visibility modifiers.
I'm having a hard time understanding how to differentiate between the
two object arrays in the methods. For example, i'm writing a clear() function that sets the specified object array(Student[] or Instructor[]) to null when called.
You could overload the clear() method like:
public void clear(Student[] array) {...}
public void clear(Instructor[] array) {...}
or (if I understand the purpose of your example correctly) compare the input array's reference with the array references in your class definition:
public void clear(Person[] array) {
if(array == this.studentArray) {
// clear students
} else if(array == this.instArray) {
// clear instructors
}
}
But if you want to actually compare the arrays' types, you can do:
array.getClass().equals(Student[].class)
or
array instanceof Student[]
For the clear method :
array = new Person[array.length] // re-init arrayt ot it's inital size.
studentCount = studentArrayl.length;
instCount = instArray.length;
that should réinit the given array to a new empty array of the correct length and reasigne correct number to studentCount and instCount.
Regards.
Nemesis.
For #1, yes, newStudent will have access to it's own methods and also it's superclass unless it's #Overriden.
For #2, you are having a hard time because your UniversityPeople class is doing too many things. This is will become a maintenance issue and is a code smell. UniversityPeople violates the single responsibility principle. Your class should do one thing only. Why not do a UniversityInstructor and a UniveristyStudent? But since you are following instructions, here's an idea for your clear function. Before you null your array, get one content in the array. Say Person person = array[0];, then you can do an if (person instanceof Student) and then if (person instanceof Instructor). This way you'll know the type of you array and proceed accordingly.
Related
I'm basically making a class for each university course where any number of students can apply. I wanted to know how can I have a constructor where it doesn't matter if my number of 'Student' objects is 1 or 15 or more...cause I know there's a way, just can't remember it...
import java.util.ArrayList;
public class Course {
String name;
ArrayList <Student>listofstudents=new ArrayList<>();
int noofStudents;
//The class Student is defined.
Course(String name,Student s1, Student s2){
this.name=name;
listofstudents.add(s1);
listofstudents.add(s2);
//Do I have to do this for every possible number of student?
}
}
As your other answer observes, constructors can have variable arguments just like regular methods can have. However, you should consider just saying no. That is, the students enrolled in a given course are a secondary attribute, and you can put a Course into a valid state without them. Consider, then, not accepting the students via the constructor, but instead having one or more methods to add students to existing Courses. (You might need that anyway.) Then just add the students via that route after initializing the Course.
void addStudent(Student s) {
listofstudents.add(s);
}
void addStudents(Student ... s) {
if (s != null) {
listofstudents.addAll(java.util.Arrays.asList(s));
}
}
You can use varargs. Variable Arguments is a technology that allows you to create methods with an arbitrary number of arguments.
import java.util.ArrayList;
public class Course {
String name;
ArrayList <Student>listofstudents=new ArrayList<>();
int noofStudents;
//The class Student is defined.
Course(String name,Student ... s){
Student [] stds = s;
this.name=name;
//your code
}
}
Varargs are straightforward to use. But there're a few rules we have to keep in mind:
Each method can only have one varargs parameter
The varargs argument must be the last parameter
Every time we use varargs, the Java compiler creates an array to hold the given parameters.
Now you can use the constructor in this way:
new Course("Name");
new Course("Name", new Student());
new Course("Name", new Student(), new Student(), new Student());
I have a Java class that has a few members. I want to write a custom cast for it. I was wondering how is it possible to do so?
Let's assume the class is as follows:
class Person {
private int age;
private float weight;
// getters and setters and etc
}
I would like the int cast to return the member age of an object, and the float cast to return the weight of an object.
For instance:
public class Main {
public static void main(String[] args) {
// create an object
Person P = new Person();
P.setAge(21);
P.setWeight(88.0);
// case 1: casting object to an existing data type
int personAge = (int) P; // would return the age
float personWeight = (float) P; // would return the weight
// case 2: casting an existing data type to an object
Person P2 = (Person) personAge; // this would create an instance of the object whose age is assigned and weight is not assigned
}
}
I was wondering if it is possible to do the opposite. In particular, casting int to Person would return an instance of Person that its age is assigned and similarly for float.
I know this question may not have an answer. But because I did not find any useful results in my search, I decided to ask it.
P.S. I understand that for a String, the toString method would take care of case 1.
You can't overload the cast operator. Java doesn't support it and probably never will.
To convert a single value to an instance of the desired class, we use static factory methods.
public static Person fromAge(int age) {
return new Person(age);
}
They often return a partially constructed object. In the snippet above, a newly constructed person has only age set: other fields will have their default values.
To do the opposite, we use getters.
public int getAge() {
return age;
}
However, since toString is already there, it makes sense to add other data types as well.
toInt makes no sense when it's applied to me (as an instance of the Person class). It could be my height, my weight, my age, a number of times I went to a bathroom today, etc. I can't represent myself by one int number, neither can a large majority of classes.
On the other hand, toString can do this job pretty well: I can give you (read return) a summary of my hobbies, my biometric information, even my picture. Or I can leave it to the default implementation, which still would satisfactorily represent an object.
You wouldn't use a cast for this just write methods in your Person class to get those values.
public int getAge()
{
return age;
}
etc.
So I've only done this once and I'm unsure whether the approach I did is the appropriate way.
But my approach was a method called typeConverter, which i would give an object as parameter, then you could take that parameter and look what object type it is and then create a new Person, with your value.
Although this approach could cause problems, when your class would have two integer fields. But I think you could find a solution for this, by giving it another parameter that defines, which field you'd want to convert it to.
I'm really sorry for my poor english, but I hope you get the principle.
so I've build these two classes:
1. Genre which implements Comparable
2. GenreManager which takes a Collection of genres and creates an internal copy of it. Later in GenreManager, I will need to add new Genres by getting a name as an input, and I need to assign this Genre the next free id number, which is basically the next smallest positive number after the smallest used id.
I am trying to use Collections.sort() to sort my list but I am getting the following error:
"no instance(s) of type variable(s) T exist so that Collection conforms to List." and I am not sure what this is referring to... I've tried ready a bunch of posts about this on here but couldn't figure out the solution... Here is part of the code:
public class Genre implements Comparable<Genre>{
private int id;
private String name;
public Genre(int id, String name){
this.id = Validate.requireNonNegative(id);
this.name = Validate.requireNonNullNotEmpty(name);
}
#Override
public int compareTo(Genre o) {
int res = Integer.valueOf(id).compareTo(o.id);
if (res != 0){
return res;
}
else{
return this.name.compareToIgnoreCase(o.name);
}
}
}
public class GenreManager{
private Collection<Genre> genres;
private Collection<Genre> sortedTree;
public GenreManager(){
this.genres = new ArrayList<Genre>();
}
public GenreManager(Collection<Genre> genres){
// check for duplicates
for (Genre x : genres){
for (Genre y : genres){
if (x.equals(y) || x.getName().equals(y.getName()))
throw new IllegalArgumentException("List contains duplicates");
}
}
this.genres = new ArrayList<Genre>(Collections.sort(genres));
}
}
I am trying to do the sorting in the constructor above. Can someone tell me how to go around this?
I tried playing around a little bit, trying to change the private variable from Collection<Genre> to List<Genre> for example and similar things but nothing worked... I also tried casting the input of the .sort method to (List<Genre>) but it didn't work either.
PS: I can't change any of the method header or class headers.
Thanks!
As per request, here's a compilation of my comments to answer the question:
The immediate problem is that Collections.sort(List<T>) takes a List parameter and not just a Collection because collections in general don't have to be sortable (e.g. hash sets aren't). Additionally the method returns void and sorts the passed list in place, i.e. the way you call it won't compile.
Taking all this into consideration your code might be changed to something like this:
public class GenreManager{
private List<Genre> genres;
...
public GenreManager(Collection<Genre> genres){
...
//create a list out of the passed collection
this.genres = new ArrayList<Genre>( genres );
//sort the list
Collections.sort(this.genres);
}
}
The other problem with the code you posted is that for any non-empty collection it will throw the IllegalArgumentException because elements are compared to themselves. Adding a check for x != y to the condition would solve that but the code is still somewhat slow because it has a time complexity of O(n2).
This can be solved to use a set instead of a list. However, a HashSet would depend on how equals() and hashCode() define equality, which doesn't seem to match your requirements. That could be solved by using a wrapper object that implements both methods as needed.
A better approach might be to use a TreeSet though. TreeSet uses comparisons to determine order and equality (if the compare result is 0) and thus would allow you to either let your Genre class implement Comparable as you did or provide a separate Comparator (e.g. if you need multiple different definitions of equality).
If you just want to eliminate duplicates, your code could then look like this:
public class GenreManager{
private SortedSet<Genre> genres;
...
public GenreManager(Collection<Genre> genres){
this.genres = new TreeSet<>( genres );
}
}
If you want to know what duplicates are in the collection you could do it like this:
public GenreManager(Collection<Genre> genres){
this.genres = new TreeSet<>(); //the generic type is inferred from this.genres
for( Genre element : genres ) {
//If the element didn't exist in the set add() will return true, false if it existed
boolean nonDuplicate = this.genres.add( element );
//handle the duplicate element here
}
}
As it was mentioned before, your code has several errors which makes it unusable:
Checking equality of elements with themselves.
Collections.sort method takes a List of Comparable as an argument, when Collection is a little higher in a hierarchy, which means you can't use it as a parameter. To resolve it change declaration of variable genres to List.
method Collections.sort returns void, so you can't pass its return value as an argument to ArrayList constructor. Instead, try assigning genres variable first and then sorting it via Collections.sort as
this.genres = new ArrayList/LinkedList(genres)
Collections.sort(this.genres)
Again, you may consider using TreeSet as it holds all elements sorted and without duplicates, so your constructor will just look like
this.genres = new TreeSet(genres)
In addition, it prevents duplicates even during adding, so if you have 10 elements, adding already existing one won't make any changes to your set. But using this data structure you should check variable for null before adding, as it will produce NullPointerException
Why is there no type-safe equals() in Java? I would think that would help catching some errors during compile time instead of later runtime error.
As a trivial example consider this:
class Person {
Integer birthYear;
}
class Car {
Long releaseYear;
}
Having a method to take in a Person and collection of Cars and suppose to list all the Cars released in the same year the person was born could end up using:
if (person.birthYear.equals(car.releaseYear)) {
...magic happens...
}
But no magic would ever happen using this. Even if the fields are of same type during the creation of the code, either could be changed at a later point without getting compile error on the comparison code.
What would be best practice to avoid these sort of problems?
By design, Java's equals() method takes an Object so that you can make a heterogeneous collection of objects and compare them against each other for equality.
For example, you might have a list of arbitrary objects:
List<Object> lst = new ArrayList<>();
lst.add("abc");
lst.add(123); // Integer
lst.add(456L); // Long
Then the fact that equals() takes an Object means you can implement:
void indexOf(List<Object> lst, Object target) {
for (int i = 0; i < lst.size(); i++) {
if (lst.get(i).equals(target))
return i;
}
return -1;
}
To answer your concern specifically, the only way to be "type-safe" is to define a new method name like strictEquals(), and only implement the parameter with your type, not the Object type. For example:
class Person {
boolean strictEquals(Person other) { ... }
}
class Car {
boolean strictEquals(Car other) { ... }
}
Regarding your use of Integer and Long as fields, don't do that. Use the primitive types int and long instead, and use the == operator to compare values. This has a number of advantages, like better performance, no NullPointerException, and the ability to compare int to long properly (whereas Integer.equals(Long) will always return false due to different types, even if the objects have the same numerical value).
I missed one of my lectures in Java, and the subject was classes, methods, constructors etc. The homework is one task:
Create a class Person, objects of which describe persons, and which
contains only two felds: name (String) and year of birth (int). In
this class, define
constructor taking name and year of birth;
constructor taking only name and setting the year of birth to default value 1990;
method isFemale returning true if this person is a woman (we assume, not very sensibly, that only women and all women have names ending
with the letter 'a'); otherwise the method returns false;
static function getOlder taking two references to objects of class Person and returning the reference to the older of these two persons;
static function getOldest taking the reference to an array of references to objects of class Person and returning the reference to
the oldest person represented in the array;
static function getYoungestFemale taking the reference to an array of refe- rences to objects of class Person and returning the reference
to the youngest woman represented in the array, or null if there is no
woman in the array.
In a separate class, write a main function in which the whole
functionality of the class Person is tested.
I checked some tutorials and explanations, I didn't go straight here asking for help but after 2 hours of ripping my hair out I've been only able to come up with this:
public class Person {
String name;
int yob; //year of birth
public Person() {
Person jan = new Person("Jan", 1995); //the names are polish
Person joanna = new Person("Joanna", 1993);
Person michal = new Person("Michal", 1980);
Person beata = new Person("Beata", 1979);
Person kazimierz = new Person("Kazimierz", 1998);
Person magdalena = new Person("Magdalena", 1999);
}
public Person(String name, int yob) {
this.name = name;
this.yob = yob;
}
public Person(String name) {
this.name = name;
this.yob = 1990;
}
public static boolean isFemale(String name) {
if(name.equals("Joanna")) {
return true;
} else {
return false;
}
}
public static String getOlder(Person x?, Person y?) { // if I understand the task correctly, I should reference any two names?
if(x?.yob>y?.yob) {
return x?.name;
} else {
return y?.name;
}
//getOldest and getYoungestFemale methods go here
}
}
However, I can't wrap my head around the last three steps. My brain is literally boiling. It would really help if anyone could explain the last three bullet points (getOlder reference to any 2 people and getOldest/getYoungestFemale)
If you don't have time to explain, some example of a "method taking a reference to an array" should be enough for me to get a basic understanding.
Thanks in advance.
Usually.. you don't call it "reference to an array of references of something" You just say "array of something". Even though arrays of objects are arrays of references to objects. Just like a variable for an object is just a reference to an object.
Type heyImAReference = new ImTheObject();
So when you write
Person person = new Person();
You'll have the class Person as type, person as a reference to an instance (or object) of that class and the resulting entity of new Person() as the actual thing that is being referenced. Usually called "instance" or in your case "object".
When it comes to arrays of persons and you do
Person[] persons = new Person[5];
You create via new Person[5] an array instance that has 5 slots, in each slot can go a Person instance figuratively, actually though you have 5 references. Person[0] being the first, Person[1] being the second and so on. So that is an "array of references to objects of class Person".
And persons is a reference to that. So it is a "reference to an array of references to objects of class Person"
static function getOldest taking the reference to an array of references to objects of class Person and returning the reference to the oldest person represented in the array
means nothing more than
static Person getOldest(Person[] persons) {
...
}
I would call that a method that takes an array of Persons and returns a Person. Though technically, it's all just references that go in and come out. The Person objects don't "move"
Firstly create another class which will have main method. Within main create an array:
Person[] parr = new Person[6];
//Then fill all your person to this array:
parr[0] = new Person("name", year);
parr[1] = ....
Then pass this array handler to your methods:
Person p1 = Person.findSomething(parr);
In Person class:
public static Person findSomething(Person[] parr){
for (Person p : parr){
if (p.name.endsWith("a")) return p;
}
return null;
}
Here are some hints which should help you work out the answer yourself without me giving away the solution ;)
1)
public static String getOlder(Person x?, Person y?) {
// if I understand the task correctly, I should reference any two names?
if(x?.yob>y?.yob) {
return x?.name;
} else {
return y?.name;
}
}
This code is almost correct! Just remove the question marks! Also remember that the older person will have an earlier yob. EDIT, also you need to return the reference to the person, not their name, so return either x or y.
2) getOldest and getYoungestWoman
Person[]
is an array of references to Person objects. You should be able to read up on how to loop through the elements of an array and compare values.
3) an extra: if you declare those 6 Person objects inside the constructor, you won't be able to access them in other methods of the class. it is ok to create the Person objects there, but you must declare them outside the constructor. Declare them in the class.