Say I have a class like so:
public class ManyFields {
public Object1 object1;
public Object2 object2;
public Object3 object3;
// etc.
}
I want to make sure these fields are not null upon trying to do anything with a ManyFieldsobject. So perhaps I'd have a validation method like so:
public Object ensureNotNull(Object o) {
if (o.object1 != null) o.object1 = new Object1();
// and so on and so forth
return o;
}
Is there a better, but also performant, approach here? Rather than checking if each field is null individually? I did explore reflection like so:
for (Field f : fields) {
try {
if (f.get(c) == null) {
f.set(c, f.getType().newInstance());
}
} catch (Exception e) {
e.printStackTrace();
}
}
However, I've read this reflection isn't the most performant-friendly approach to initializing fields. Any suggestions would help? Thanks.
Reflection is the worst and slowest way to do almost anything.
What you want is to encapsulate your fields: make them private, so your class has total control over how they get changed.
If there aren’t too many of them, you can make them all final and have the constructor set them:
public class Person {
private final String firstName;
private final String lastName;
public Person(String firstName,
String lastName) {
this.firstName = Objects.requireNonNull(firstName,
"First name cannot be null.");
this.lastName = Objects.requireNonNull(lastName,
"Last name cannot be null.");
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
With this approach, it is completely impossible for outside code to set the fields to null values.¹ If a caller passes null, the constructor never completes, so there can never be an existing instance of Person which has a field with a null value. Which means you can make a written guarantee that callers will never have to check if the values are null:
/**
* Returns this user's first name. This value will never be null.
*/
public String getFirstName() {
return firstName;
}
/**
* Returns this user's last name. This value will never be null.
*/
public String getLastName() {
return lastName;
}
You can even take it a step farther, and validate the values beyond just checking for null:
public Person(String firstName,
String lastName) {
this.firstName = Objects.requireNonNull(firstName,
"First name cannot be null.");
this.lastName = Objects.requireNonNull(lastName,
"Last name cannot be null.");
if (firstName.isBlank()) {
throw new IllegalArgumentException("First name cannot be blank.");
}
if (lastName.isBlank()) {
throw new IllegalArgumentException("Last name cannot be blank.");
}
}
If you have many fields, you can just use get-methods and set-methods instead of setting everything in the constructor. In that case, it is useful to initialize each field to a non-null value, so the class is able to make the same non-null guarantee as with the constructor approach:
public class Person {
private String firstName = "(unknown)";
private String lastName = "(unknown)";
private String socialSecurityNumber = "000-00-0000";
private LocalDate dateOfBirth = LocalDate.MAX;
public String getFirstName() {
return firstName;
}
public String setFirstName(String name) {
this.firstName = Objects.requireNonNull(name,
"Name cannot be null.");
}
public String getLastName() {
return lastName;
}
public String setLastName(String name) {
this.lastName = Objects.requireNonNull(name,
"Name cannot be null.");
}
public String getSocialSecurityNumber() {
return socialSecurityNumber;
}
public void setSocialSecuityNumber(String num) {
Objects.requireNonNull(num, "Argument cannot be null.");
if (!num.matches("\\d{3}-\\d{2}-\\d{4}")) {
throw new IllegalArgumentException(
"Argument must be in the format nnn-nn-nnnn.");
}
this.socialSecurityNumber = num;
}
public LocalDate getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(LocalDate date) {
Objects.requireNonNull(date, "Date cannot be null.");
int age = date.getYear() - LocalDate.now().getYear();
if (age > 150) {
throw new IllegalArgumentException(
"Invalid date: no one is more than 150 years old.");
}
if (age < 0) {
throw new IllegalArgumentException(
"Invalid date: cannot be born in the future.");
}
this.dateOfBirth = date;
}
}
1. It is technically possible for outside code to use reflection’s setAccessible method to hack the private members of a class that is not in a module, unless a SecurityManager is installed. However, people who hack into things in this manner should expect the class to break in unexpected ways, as they are essentially “voiding the warranty.” Meaning, it’s bad and no one should do it, and in fact later versions of Java won’t let them do it.
I need to write a method that removes students from the ArrayList (myRoster) by student ID. If the student ID doesn't exist, the method should print an error message indicating that it is not found. I have written a remove method where I'm able to remove item by index. The error message 'Student with ID 3 was not found' is returning 6 times (3 from first remove and 3 from second error message). But I want to get one error message for second remove method which I'm calling in main method. A little help would be much appreciated.
Student Class
public class Student {
private int StudentID;
private String FirstName;
private String LastName;
private String Email;
private int age;
private int[] Grades;
//Constructor
public Student(int S_ID,String fName,String lName,String email,int Age,
int[] grade){
setStudentID(S_ID);
setFirstName(fName);
setLastName(lName);
setEmail(email);
setAge(Age);
setGrade(grade);
}
//Accessor Methods (get methods)
public int getStudentID(){
return StudentID;
}
public String getFirstName(){
return FirstName;
}
public String getLastName(){
return LastName;
}
public String getEmail(){
return Email;
}
public int getAge(){
return age;
}
public int[] getGrades(){
return Grades;
}
//Mutator methods (set methods)
public void setStudentID(int StudentID){
this.StudentID=StudentID;
}
public void setFirstName(String FirstName){
this.FirstName=FirstName;
}
public void setLastName(String LastName){
this.LastName=LastName;
}
public void setEmail(String Email){
this.Email=Email;
}
public void setAge(int age){
this.age=age;
}
public void setGrade(int Grade[]){
this.Grades=Grade;
}
}
Roster Class
import java.util.ArrayList;
public class Roster {
private static ArrayList<Student> myRoster= new ArrayList<>();
public static void main(String[] args) {
add(1,"John", "Smith", "John1989#gmail.com", 20, 88,79, 59);
add(2,"Suzan", "Erickson", "Erickson_1990#gmailcom",19,91,72,85);
add(3,"Jack","Napoli","The_lawyer99yahoo.com",19,85,84,87);
add(4,"Erin", "Black","Erin.black#comcast.net",22,91,98,82 );
add(5,"Henry","Adam","adam1#gmail.com",25,85,84,79);
remove(3);
remove(3);//expected: This should print a message saying such a student with this ID was not found
}
public static void add(int S_ID,String fName,String lName,String email,int
Age, int grade1, int grade2, int grade3){
int[] Grades={grade1, grade2,grade3};
Student newStudent= new Student(S_ID, fName, lName, email, Age, Grades);
myRoster.add(newStudent);
}
public static void remove(int StudentID){
for (int i = 0; i < myRoster.size(); i++){
if(i == StudentID){
myRoster.remove(i);
}else{
System.out.println("Student with ID "+StudentID+" was not found");
}
}
}
}
}
You should never attempt to remove an element from a list while iterating over it.
Your comparison is incorrect, reason being that you're comparing the for loop control variable i with the parameter StudentID rather it should be if(myRoster.get(i).getStudentID == StudentID).
The reasoning as to why the text "Student with ID "+StudentID+" was not found" is being printed to the console multiple times is because you've inserted it inside the loop, meaning each time the parameter StudentID doesn't match the value that it's being compared to, it will print the same message...
To accomplish your task you can simply use the ArrayList#removeIf method to remove the Student with the specified StudentID, else if the ArrayList#removeIf returns false then you can print the appropriate message as shown within the solution below.
public static void remove(int StudentID) {
if (!myRoster.removeIf(s -> s.getStudentId() == StudentID))
System.out.println("Student with ID " + StudentID + " was not found");
}
Your remove method is currently giving the error message once for every student in the list that doesn't have the desired student ID.
for (int i=0;i<myRoster.size();i++){
if(i==StudentID){
myRoster.remove(i);
}else{
System.out.println("Student with ID "+StudentID+" was not found"); }
That print statement is called any time the student at index i does not have the desired ID, not when there are no students that have that ID.
An easy way to fix this would be to put a boolean at the start of the method and set it to false. Then, in the if (i == StudentID) block, remove the student and set the value to true. Then, after the loop is complete, check if the boolean is true; if it is true, then the desired student has been removed. If it is still false, then there is no student with the desired ID.
It should look something like this:
public static void remove(int studentID) {
boolean studentFound = false;
for (int i=0;i<myRoster.size();i++){
if(i==StudentID){
myRoster.remove(i);
i--; // you have to decrement this value to account for the missing item
studentFound = true; // student with desired ID is removed
}
if (!studentFound) System.out.println("Student with ID "+StudentID+" was not found");
}
You problem seems to be that your loop in the remove() method isn't looping over studentIDs but just the looping index. You should loop over studentIDs. Your code prints each time an ID doesn't match the given ID. That's why you get multiple print messages. You should instead only print if no one matches (i.e. roster size doesn't change).
public static void remove(int StudentID){
int initialSize = myRoster.size(); //store initial size of roster to check if it changes
for (int i=0;i<myRoster.size();i++){
if(myRoster.get(i).getStudentID == StudentID){
myRoster.remove(i);
}
}
//checks if a student was removed which means the ID was found.
if(initialSize == myRoster.size()){
System.out.println("Student with ID "+StudentID+" was not found");
}
}
You can use Iterator interface as well for your purpose as shown below.
public static void remove(int StudentID) {
Iterator<Student> it = myRoster.iterator();
while(it.hasNext()) {
Student stud = (Student)it.next();
if (stud.getStudentID() == StudentID) {
it.remove();
return;
}
}
System.out.println("Student with ID " + StudentID + " was not found");
}
Yes you can do this ,please some add things in your Student class
public class Student {
private int StudentID;
private String FirstName;
private String LastName;
private String Email;
private int age;
private int[] Grades;
public Student(int S_ID ){
this.setStudentID(S_ID);
}
//Constructor
public Student(int S_ID,String fName,String lName,String email,int Age,
int[] grade){
setStudentID(S_ID);
setFirstName(fName);
setLastName(lName);
setEmail(email);
setAge(Age);
setGrade(grade);
}
//Accessor Methods (get methods)
public int getStudentID(){
return StudentID;
}
public String getFirstName(){
return FirstName;
}
public String getLastName(){
return LastName;
}
public String getEmail(){
return Email;
}
public int getAge(){
return age;
}
public int[] getGrades(){
return Grades;
}
//Mutator methods (set methods)
public void setStudentID(int StudentID){
this.StudentID=StudentID;
}
public void setFirstName(String FirstName){
this.FirstName=FirstName;
}
public void setLastName(String LastName){
this.LastName=LastName;
}
public void setEmail(String Email){
this.Email=Email;
}
public void setAge(int age){
this.age=age;
}
public void setGrade(int Grade[]){
this.Grades=Grade;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return StudentID == student.StudentID;
}
#Override
public int hashCode() {
return StudentID;
}
}
and in main area class you can do
public class Test {
public static void main(String []args){
List<Student> list = new ArrayList<>();
list.add(new Student(1,"a","a","a",1, new int[]{1}));
list.add(new Student(2,"b","b","b",2, new int[]{2}));
list.remove(new Student(1));
list.forEach(System.out::println);
}
}
Output
com.traveliko.platform.web.frontend.Student#2
In my program I'm trying to compare names by last name, and if those are the same then compare by using the first name. However, I can't quite figure out how to compare the strings.
Can someone help me out with this?
public class Student implements IComparable
{
String firstName;
String lastName;
int score;
public Student()
{
}
public void setFirstName(String firstName)
{
this.firstName = firstName;
}
public String getFirstName()
{
return firstName;
}
public void getLastName(String lastName)
{
this.lastName = lastName;
}
public String getLastName()
{
return lastName;
}
public void getScore(int score)
{
this.score = score;
}
public int getScore()
{
return score;
}
#Override
public int compareTo(Object o)
{
//Compares Student objects by last name. If the last names are the same
//it compares by first name.
Student s = (Student) o;
if (this.getLastName().toUpperCase() < s.getLastName().toUpperCase())
return -1;
else if (this.getLastName().toUpperCase() > s.getLastName().toUpperCase())
return 1;
else
{
if(this.getFirstName().toUpperCase( < s.getFirstName().toUpperCase()
return -1;
else if (this.getFirstName().toUpperCase( > s.getFirstName().toUpperCase()
return 1;
else
return 0;
}
}
}
Don't make things more complicated:
String class already provides compareToIgnoreCase method
value returned by compare methods of String is already good to be directly returned
Basically the same functionality could be expressed with:
int compare = getLastName().compareToIgnoreCase(o.getLastName());
return compare == 0 ? getFirstName().compareToIgnoreCase(o.getFirstName()) : compare;
Mind that you need to check that o instanceof Student if you have an Object argument.
I don't get why you are using a custom IComparable interface, which sounds much like the one provided in C#, since Java provides Comparable<T> which is generic and doesn't require checking for the runtime type of the argument (since it's not Object anymore but T).
I have code like this:
public class Main {
public static void main(String[] args) {
List<Object> arrayList = new ArrayList<Object>();
arrayList.add(new Student("First", "Last", "10"));
System.out.println(arrayList);
}
}
With Student Class is:
public class Student extends Human {
private String grade;
public Student(String first, String last, String gradeValue) {
super(first, last);
this.setGrade(gradeValue);
}
public void setGrade(String grade) {
this.grade = grade;
}
public String getGrade() {
return grade;
}
}
It's will extends from Human Class:
public abstract class Human {
private String firstname;
private String lastname;
public Human(String first, String last) {
this.setFirstname(first);
this.setLastname(last);
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getLastname() {
return lastname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getFirstname() {
return firstname;
}
}
Main ideas is I try to create a list 10 students with FirstName LastName and Grade.
Now when I try to print the list in main method, it's show me this: [Student#6fbae5f5].
What I want it show is: First Last 10.
Please note that I try to add more student to the list and it have to show like this:
FirstName1 LastName1 10
FirstName2 LastName2 3
FirstName3 LastName3 7
......................
Add below code in your Student class
#Override
public String toString() {
return "Student [getFirstname()=" + getFirstname() + ", getLastname()="
+ getLastname() + ", getGrade()=" + getGrade() + "]";
}
Since each object has toString() method, the default is displaying the class name representation, then adding # sign and then the hashcode. In your case, you're printing the object itself.
If you want to print the content of the arrayList, you should loop on it:
for(Student student : arrayList) {
System.out.println(student)
}
That's after you'll override toString in Student.
1.Add this to Human Class:
#Override
public String toString() {
// TODO Auto-generated method stub
return firstname + " " + lastname;
}
2. Add this to Student Class:
#Override
public String toString() {
// TODO Auto-generated method stub
return super.toString() + " " + grade;
}
so I have 2 files called Employee.java and Write.java (These two are within the same package). Say within Employee.java I have
public class Employee {
private String firstName = "test";
private String lastName = "ing";
public Employee(String first, String last) {
firstName = first;
lastName = last;
}
public Employee(Employee copy) {
firstName = copy.firstName;
lastName = copy.lastName;
}
}
Then within my Write.java I want to create an object of type Employee called temp. Like
public void obtainInfo(Employee temp) {
String firstName = temp.firstName;
String lastName = temp.lastName;
}
However I get an error that tells me that it cannot find the symbol in the line that is.
public void obtainInfo(Employee temp) {
I was just wondering where I went wrong that I can't create an object within different files. Despite them being in the same package I can't access them?
I want to be able to incorporate this later on to help me build a text file from reading an array list, but I figured to first start with reading a single line from an object.
It sounds to me that you are trying to set something up so that you can make a copy of an Employee and be able to write the properties to a text file. This won't write to a text file but I think it may clear some things up for you.
public class Employee {
private String firstName;
private String lastName;
public Employee(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Employee(Employee copy) {
firstName = copy.firstName;
lastName = copy.lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("Employee");
sb.append("{firstName='").append(firstName).append('\'');
sb.append(", lastName='").append(lastName).append('\'');
sb.append('}');
return sb.toString();
}
}
TestClass.java
public class TestClass {
public static void main(String[] args){
//First we have to have one to copy
Employee emp = new Employee("Joe", "Dirt");
//Now we have a copy
Employee emp2 = new Employee(emp);
//Calls the Employee.toString() method and sends it to System.out
System.out.println("Employee 1 : " + emp);
System.out.println("Copy of Employee 1 : " + emp2);
}
}
Make sure that Write.java's class has the same level of access as Employee (IE: Public). If this is not the issue, I would show the code from Write.java specifically as that is most likely where the problem is coming from.