I have 3 classes, Person, Student, and Teacher. Student and Teacher extend Person. How can I access instance variables from within Person? Like if Student and Teacher both have an internal list of names, how can I access both name arrays from within the parent class?
class Person
{
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void printAllNames() { //<-- How do you do this?
for(Student s : students) //can't access students array
System.out.println(s.getName());
for(Teacher t : teachers)
System.out.println(t.getName());
}
}
class Student extends Person
{
protected Student[] students;
Student(String name)
{
this.name = name;
students[students.length] = this;
}
}
class Teacher extends Person
{
protected Teacher[] teachers;
Teacher(String name)
{
this.name = name;
teacher[teachers.length] = this;
}
}
You class design is incorrect. Your Student class should not contain a field students as other students are not part of a Student instance (i.e. composition should not be used here). Instead you should create lists of students outside of these classes if they are required. Same for teachers of course.
A student could have a list of friends, but such relations are better stored outside the class itself as well. The list of friends does is not part of a student. What does need to be part of student depends on what you are trying to model (an address could be part of student if that helps to define student for administration purposes).
If you want to access fields of a higher level class you could downcast to Student from Person but again, that would normally be considered as a technical solution for a design problem.
Ignoring the issues with the design and the use of arrays as others have mentioned, if you want to print the names depending on whether it's a student and teacher, you should make printAllNames abstract and have the descendant classes provide their own print implementation. The base class should not know about specifics of properties or methods in subclasses.
class Person
{
...
public abstract void printAllNames();
}
class Student extends Person
{
...
public void printAllNames() {
for (Student s : students)
System.out.println(s.getName());
}
}
class Teacher extends Person
{
...
public void printAllNames() {
for (Teacher t : teachers)
System.out.println(t.getName());
}
}
Your code is having issue in array.length() because there is no any method like length(). you can access that by array.length.
second there is no name variable in your Student class and you made name as private field in Person Class change that to public or protected.
third you can pass argument in your method like
public void printAllNames(Student[] students, Teacher[] teachers)
and check once.
Related
I have a class Person and its subclass Student:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Student extends Person {
private int grade;
public Student(String name, int grade) {
super(name);
this.grade = grade;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
public void printDescription() {
System.out.println("Name: " + getName());
System.out.println("Grade: " + Integer.toString(grade));
}
}
So Person have getter and setter for name property and Student have only getter and setter for its new grade property, as long as a printDescription() method.
The problem is how should I call the name property in Student's printDescription() method correctly?
I implemented it like in code above considering that Student inherits getter and setter from parent class.
But at my university Java teacher asks to use it like this:
public void printDescription() {
System.out.println("Name: " + super.getName());
System.out.println("Grade: " + Integer.toString(grade));
}
So he offers to directly call parent's getter.
I think it is not the best way because in case we override name's getter in Student class, getter from Person will still be called instead.
So what approach is best in this situation to use name property?
UPD: it is important to mention that for this task there is no requirement to call specifically superclass' getter implementation, this is why I was confused by teacher's recommendation to use super.
You're correct that if you override the method in the subclass and you're using the super keyword then you'll invoke the method on the parent class.
In this case unless you wanted to guarantee that the method in the parent class was used then it's fine to just invoke the method without the super keyword and that way if you override the method then you get the behaviour you want in the subclass.
if you extends a class , it will has all properties in its superclass. instead of calling super.getName() you can just call this.getName().
I came across this exercise online where I have two classes and I'm supposed to make the Tutor class immutable. However, the only thing I can think of is adding final to name field. When it comes to the constructor, I don't think I need to change the initialisation of the name variable as String is immutable. I'm not sure how to approach the collection and how to make this part of the constructor immutable. According to the exercise, I'm not supposed to change the Student class (which I can see is mutable)
public class Student {
private String name;
private String course;
public Student(String name, String course) {
this.name = name;
this.course = course;
}
public String getName() {
return name;
}
public String getCourse() {
return course;
}
public void setName(String name) {
this.name = name;
}
public void setCourse(String course) {
this.course = course;
}
}
public final class Tutor {
private String name;
private final Set<Student> tutees;
public Tutor(String name, Student[] students) {
this.name = name;
tutees = new HashSet<Student>();
for (int i = 0; i < students.length; i++)
tutees.add(students[i]);
}
public Set<Student> getTutees() {
return Collections.unmodifiableSet(tutees);
}
public String getName() {
return name;
}
}
The Tutor class presents many aspects promoting its immutability :
the class is final
the Set<Student> is protected against the modifications
no method allowing to change directly the state of the class
However, the defensive copy of the constructor is not complete.
It also has to copy the Students elements of the array passed. Otherwise the client of the constructor may change any instance of them and make so the Tutor instance mutable such as :
Student[] students = ...;
Tutor tutor = new Tutor(name, students);
students[0].setName("new Name!"); // break the immutability of Tutor
You should write something like :
public Tutor(String name, Student[] students){
this.name = name;
tutees = new HashSet<Student>();
for (Student student : students){
Student copy = new Student(student.getName(),
student.getCourse());
tutees.add(copy);
}
}
Additionally note that the Set returned by getTutees() is unmodifiable but elements contained in are as Student is mutable.
So to make Tutor immutable you also have to create a copy of the Student elements as you return getTutees() such as :
public Set<Student> getTutees(){
Set<Student> students = new HashSet<>();
for (Student student : tutees){
Student copy = new Student(student.getName(),
student.getCourse());
students.add(copy);
}
return Collections.unmodifiableSet(students);
}
As you may notice, getting the immutability in these conditions (an instance that we wish immutable but that contains a collection referencing mutable instances) requires to write more code (to read/to maintain/to test) and to perform more processing (so slower to execute).
If Student was an immutable class, the original getTutees() and the original constructor would be enough.
Proper way is to make an object immutable is to:
Declare the object final
Do not provide setter methods
Make all fields private
Make mutable fields final
Use deep copy in the constructor
Clone objects in getter methods, so you don't return actual reference.
Do you really need to return the Set of Students? If you really need that you can hide that by using an interface that provides only getters, something like
interface IStudent {
public String getName();
public String getCourse();
}
class Student : implements IStudent { ...}
and in your Tutor you return Set<IStudent>
To make the Tutor class immutable, you should use the "final" modifier on all the fields inside a Tutor, not on the Tutor's class definition.
Java SE 16
You can use JEP 395: Records feature, introduced as part of Java SE 16, to create an immutable class without requiring much ceremony.
If you have already gone through the above link, you must have figured out that you can do it simply as
record Tutor(String name, Set<Student> tutees) { }
What you get in turn are:
A final class Tutor.
A canonical constructor whose signature is the same as the header, Tutor(String name, Set<Student> tutees).
private final fields, name and tutees and their corresponding public accessor method with the same name and return type.
Automatically created equals, hashCode and toString methods.
Demo:
Student.java
record Student(String name, String course) { }
Tutor.java
import java.util.Set;
record Tutor(String name, Set<Student> tutees) { }
Main.java
import java.util.Set;
class Main {
public static void main(String[] args) {
Set<Student> cscStudents = Set.of(
new Student("Harry", "Java-8"),
new Student("Tina", "Java-9"),
new Student("Andy", "Java-11")
);
Set<Student> scienceStudents = Set.of(
new Student("Tony", "Phy"),
new Student("Kerry", "Chem"),
new Student("John", "Bio")
);
Tutor t1 = new Tutor("Mark", cscStudents);
Tutor t2 = new Tutor("Robin", scienceStudents);
Tutor t3 = new Tutor("Mark", Set.of(
new Student("Andy", "Java-11"),
new Student("Harry", "Java-8"),
new Student("Tina", "Java-9")
)
);
System.out.println(t1);
System.out.println();
System.out.println(t1.tutees());
System.out.println();
System.out.println("Students of " + t1.name() + ":");
t1.tutees()
.stream()
.forEach( t -> System.out.println(t.name()) );
System.out.println();
System.out.println(t1.equals(t2));
System.out.println(t1.equals(t3));
}
}
Output:
Tutor[name=Mark, tutees=[Student[name=Andy, course=Java-11], Student[name=Harry, course=Java-8], Student[name=Tina, course=Java-9]]]
[Student[name=Andy, course=Java-11], Student[name=Harry, course=Java-8], Student[name=Tina, course=Java-9]]
Students of Mark:
Andy
Harry
Tina
false
true
For an assignment I am designing a simple "chatroom" system. It is very rudimentary and simply involves objects of a Person class sending messages to each other through a separate Chatroom object.
Below is my Person class (did not include getter/setter methods for sake of brevity).
public class Person {
protected String firstName;
protected String lastName;
protected String message;
Person(String fn, String ln)
{
firstName = fn;
lastName = ln;
}
public void sendMessage(String msg, Person rec)
{
rec.receiveMessage(msg);
}
public String receiveMessage(String msg)
{
message = msg;
}
}
these Person objects are instantiated and send messages to each other through the Chatroom class using the sendMessage method.
Now I am supposed to design a larger Person class, into multiple classes by adding Address, Name and Contact classes to the multi class system. The problem is I don't know how to associate these classes with the Person class. At first I tried to make an inheritance relationship with the Name class adding the middleInitial field, but i dont see the utility in this.
public class Name extends Person {
protected String middleInitial;
Name(String fn, String ln) {
super(fn, ln);
}
public static void main(String args[])
{
}
public String getMI()
{
return middleInitial;
}
public void setMI(String mi)
{
middleInitial = mi;
}
I don't understand the need for these separate classes, couldn't all this information be stored in the Person object? Can someone help me understand a way to design these classes separately but all be part of the Person object?
In this case - yes name should be part of Person. In OOP (Object Oriented Programming) "extends" means "is-a". In your case "Name" is definitely not "is-a" Person. Person should contain name - so "Name" should be a property and not "extend" person.
I cannot access a field of a class that is a concrete type inheriting from an abstract class.
In Java I create a class of External student that extends Student
*/
public class ExternalStudent extends Student {
String currentSchool;
public ExternalStudent(String name, Integer age, String studentIdentifier, String currentSchool) {
super(name, age, studentIdentifier);
this.currentSchool = currentSchool;
}
}
where student is
public abstract class Student {
//Attributes
String studentIdentifier;
Integer age;
String name;
//Associations
List<Subject> subject = new ArrayList<Subject>();
PersonalDetails personaldetails;
//Methods
public void setSubject () {
this.subject.add(new Subject("Name"));
}
//Constructors
public Student(String name, Integer age, String studentIdentifier){
this.age = age;
this.name = name;
this.studentIdentifier = studentIdentifier;
}
}
and external student is set up by my class Application
public class ApplicationC {
//Attributes
private String personalStatement;
private String applicationForm;
//Associations
Registration registration;
Student student;
ApplicationTest applicationtest;
//Methods
public void setApplicationResult(String result){
this.applicationtest = new ApplicationTest(result);
}
//Constructor
public ApplicationC (String personalStatement, String name){
this.registration = new Registration();
this.student = new ExternalStudent("Tom",16,"78954","DHS");
}
}
I've set up a simple test class
public void testPostCondition() throws ParseException{
ApplicationC instance = new ApplicationC("test statement","test name");
instance.setApplicationResult("pass");
assertEquals("pass",instance.applicationtest.result);
instance.student.age = 16;
instance.student.studentIdentifier = "78954";
instance.student.name = "Tom";
instance.student.currentSchool = "test"; //Error as field does not exist
}
But I cannot access the current school of the student instance (who must be an externalStudent). How can I access this field in order to test my code?
In ApplicationC, the student field is declared with the Student class :
Student student;
Methods available on an objects relies on the declared type, not the object really instantiated.
And currentSchool is only declared in the subclass ExternalStudent.
So, you cannot access it in this way.
A workaround is downcasting Student to ExternalStudent :
((ExternalStudent)instance.student).studentIdentifier = "78954";
And generally, it is better to check the type of the instance before doing it :
if (instance.student instanceof ExternalStudent){
((ExternalStudent)instance.student).studentIdentifier = "78954";
}
As a general advice, in Java, you should favor the private modifier for fields and if you need to manipulate the base class and access to some fields specific to the subclass, you could define a method in the base class that returns null or Optional and override it in the subclass with the return of the field.
It avoids cast that may be error prone and that often are symptoms of a conception problem.
Your instance is an AplicationC,
So, "instance.student" is a "Student".
"Student" does not have the "currentSchool" property.
to get to it
* add "currentSchool" property to "Student"
or
* cast your "instance.student" to "ExternalStudent"
note: you will need to handle all the exceptions and over-head of casting etc'
Hope this helps
I have two classes, Person and Employee. Employee extends Person.
I have a method that reads a Person from storage, and am writing the method that reads an Employee.
I'd like to reuse the method I have for Person to read the properties that are the same for Employee, without copy-pasting the code, but can't seem to find a way to do it.
public Person getPersonFromStorage() {
Person person = new Person();
// ... logic
return person;
}
public Employee getEmployeeFromStorage() {
Employee employee = new Employee();
// ... logic for employee-specific fields
// I want to read the fields inherited from Person here
return employee;
}
I cannot cast the retrieved Person from getPersonFromStorage, because it is not an Employee. It could be, because it's not another subtype either, but it's not.
I could do the following:
public Person getPersonFromStorage(Person person) {
if(person==null) { person = new Person(); }
// ... logic
return person;
}
public Employee getEmployeeFromStorage() {
Employee employee = (Employee) getPersonFromStorage(new Employee());
// ... logic for employee-specific fields
return employee;
}
But I'd like to avoid this complexity if I can. I have the feeling I'm overlooking something elementary. Is there a better way to solve this?
Just offering a different architecture that I usually use in this instance. If you're talking about "from storage", to me that means some sort of persistent structure. Text File, database, etc. For the following example, let's assume you have your values in a text file.
Assume a file employees.txt, which contains one employee:
// Dave's person details.
Dave
Manchester
32
// Dave's employee details
Assassin
Mostly North Korea.
Then you've got a class Person, that looks a little like this:
public class Person
{
private String name;
private String location;
private int age;
public Person(String name, String location, int age)
{
// blah blah blah.
}
}
And a class Employee that looks like this:
public class Employee extends Person
{
private String jobTitle;
private String area;
public Employee() {
// etc.
}
}
In your Person class, you can create a constructor designed to read the parameters for a Person. Something like:
public Person(Scanner file)
{
this.name = file.nextLine();
this.location = file.nextLine();
this.age = file.nextInt();
file.nextLine(); // Make sure you're pointing at the new line!
}
And in your Employee class, you can create a constructor designed to read the parameters for an employee, while calling it's superclass to deal with the other values.
public Employee(Scanner file)
{
super(file);
this.jobTitle = scanner.nextLine();
this.area = scanner.nextLine();
}
Then all you have to do to call this is something like:
Scanner s = new Scanner("employees.txt");
Person p = new Employee(s);
or to make it more compact:
Person p = new Employee(new Scanner("employees.txt"));
This will go and parse the file, and return an object, while wrapping up all the logic for actually reading the file inside the classes that need the data.
Not a text file?
Well, it's not really vital. The important thing is just passing an object up the call chain, and those methods are doing what that particular class needs to do, then passing on the object. If it's a database row, it's exactly the same principle.
Your second code example is the way to go, except you don't even need the null check line. Just pass in a non-null Person that you've instantiated elsewhere.
For even better abstraction, see if you can make Person into an abstract class.
More elegant way is to overload Employee constructor to be able to create Employee instance from parent Person instance.
public Employee getEmployeeFromStorage() {
Employee employee = new Employee(getPersonFromStorage());
// ... logic for employee-specific fields
return employee;
}
You could use a protected factory method.
protected Person createNewInstance() { return new Person(); }
and use this in your getPersonFromStorage() method. The subclass would then override this method, thus changing the return type into an Employee, which you can then use like in your second example.
public class Person {
public Person getPersonFromStorage() {
Person person = createNewInstance();
// ... logic
return person;
}
protected Person createNewInstance() {
return new Person();
}
}
public class Employee extends Person {
public Employee getEmployeeFromStorage() {
Employee employee = (Employee) getPersonFromStorage();
// ... logic for employee-specific fields
return employee;
}
protected Person createNewInstance() {
return new Employee();
}
}
Alternatively you could also create an Employee constructor based on a Person
public class Employee extends Person {
public Employee(Person person) {
super();
// copy all fields from person
}
public static Employee getEmployeeFromStorage() {
Employee employee = new Employee(getPersonFromStorage());
// ... logic for employee-specific fields
return employee;
}
}
I also added static to the methods, assuming you're intending to create new instances with no direct relationship to existing objects. This would not work with the first variant.