My professor gave us this homework exercise and has created a project with
a bunch of unit tests.
Our goal is to make sure we can pass those unit tests.
We have three classes.
A class called Person with a name and an age,
a class Speaker that extends Person,
and a class Attendee that also extends Person.
I am struggling with making sure that there are no duplicate people. generateRandomString() was implemented by the professor and just returns a random string.
I already created the class,
it's constructor,
getters,
and setters.
I also overrode the method equals() in the class Person
This is the test our professor gave us:
#Test
public void testNoDuplicatePerson() {
HashSet<Person> people = new HashSet<Person>();
String name = generateRandomString();
Person p = new Speaker(name);
people.add(p);
assertEquals(1,people.size());
p = new Attendee(name);
people.add(p);
assertEquals(1,people.size());
}
How can I pass this test?
EDIT: I decided to post the code of the three classes:
Person
```java
public abstract class Person {
private String name;
private int age;
public Person(String name) {
this.name = name;
this.age = 0;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean equals(Object o) {
if (o == null || !(o instanceof Person))
return false;
Person converted = (Person) o;
if (this.getName().equals(converted.getName()) && this.getAge() == converted.getAge())
return true;
return false;
}
}
Speaker:
public class Speaker extends Person {
private int fee;
public Speaker(String name) {
super(name);
this.fee = 0;
}
public Speaker(String name, int age) {
super(name, age);
this.fee = 0;
}
public Speaker(String name, int age, int fee) {
super(name, age);
this.fee = fee;
}
public int getFee() {
return fee;
}
public void setFee(int fee) {
this.fee = fee;
}
public String toString() {
return "Speaker " + this.getName() + " as a fee value of " + this.getFee() + ".";
}
}
Attendee:
public class Attendee extends Person {
private boolean paid;
public Attendee(String name) {
super(name);
this.paid=false;
}
public Attendee(String name, int age) {
super(name, age);
this.paid=false;
}
public boolean hasPaid(){
if (this.paid==true)
return true;
return false;
}
public String toString(){
return "Attendee "+this.getName()+(this.hasPaid() ? " has":" hasn't")+" paid its registration.";
}
}
As mentioned by #JB Nizet, when you override the equals method of a class,
you must override the hashCode method.
Note the name of the class in the jUnit test: HashSet.
That depends on the hashCode method.
Solution:
a. Learn to read the java API docs.
Here is a link to the v8 JavaDocs.
Read the HashSet page.
b. Implement the hashCode method.
You clearly have Internet access,
so, if you don't know how to implement a hashCode method,
try a google search for "how do I implement a Java hashCode method".
Hint: String already implements a hashCode method.
Related
I have encountered a rather difficult problem, Suppose there is an entity class, the code is as follows:
class Human {
private Integer age; // age of the human
private String describe; // description of the human based on their age
/**
* Setter method for the age of the human
*
* #param age the age of the human
*/
public void setAge(Integer age) {
this.age = age;
}
/**
* Setter method for the description of the human
* The description is determined based on their age
*
* #param gender the gender of the human
*/
public void setDescribe(String gender) {
String describe = "";
if (this.age < 30) {
describe = "young " + gender;
} else if ( this.age <= 55 && this.age >= 30) {
describe = "middle-aged " + gender;
} else {
describe = "old " + gender;
}
this.describe = describe;
}
}
As shown in the code (just an example, the attribute or class may be arbitrary), if I use spring and use spring to generate beans, I must ensure that method setAge is called first. How can I ensure this?
If there is a table in the database that stores age and gender, how can I ensure that setAge is called first when I use jpa, mybatis or other libraries to reflect entities?
I tried to search, but I didn't find the answer. The following are the keywords I searched and some related answers, but it doesn't seem to solve my doubts:
Spring init bean, how to ensure that a setter method is executed first
Spring reflection entity class, how to ensure that a setter method is executed first
spring call setter after other setter
When jpa reflects entity classes setter methods call order
Spring setter method order
Spring - why initialization is called after setter method
For tgdavies comment,if the demand of the product is to expand the gender description function and add the age description,the class like this:
class Human {
private Integer age;
private String gender;
public void setAge(Integer age) {
this.age = age;
}
public void setGender(String gender) {
String describe = "";
if (this.age < 30) {
describe = "young " + gender;
} else if ( this.age <= 55 && this.age >= 30) {
describe = "middle-aged" + gender;
} else {
describe = "old" + gender;
}
this.gender = describe;
}
}
Test with:
spring-core-5.2.12.RELEASE.jar
spring-beans-5.2.12.RELEASE.jar
java version "1.8.0_121"
Looking forward to a better solution.
Here is my code for testing and debugging:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
public class RepositoryBean {
public static void main(String[] args) {
ApplicationContext AcContext = new AnnotationConfigApplicationContext(User.class, Configs.class);
System.out.println(AcContext.getBean("user", User.class));
}
}
#Configuration
class Configs {
#Bean
public String name() { return "the name"; }
#Bean
public int age() { return 100; }
}
#Component
class User {
private int age;
private String name;
public User() { }
#Autowired
public void setName(String name) { this.name = name; }
public User(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() { return age; }
public String getName() { return name; }
#Autowired
public void setAge(int age) {
this.age = age;
// text = Objects.requireNonNull(this.name) + age;
}
#Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
I am full of curiosity about this question. I have tried to find the answer from the source code these days (only for spring, other middleware, I have no energy to do it)
First of all, the conclusion is that when spring initializes beans, it cannot control the order in which the setter methods are called(According to the results of my investigation of the source code, there may be omissions. If there is an error in my conclusion, I hope everyone can help point out).
Now, please take a look at the documentation of Class#getDeclaredMethods:
The elements in the returned array are not sorted and are not in any particular order.
Through reflection, when using getDeclaredMethods to get the Method array, there is no "order". No "order" means that it is not sorted according to any rules, but the returned order seems to be regular every time it is called, i use the following code to test:
class User {
private int age;
private String name;
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getName() { return name; }
}
public static void main(String[] args) {
Class<User> c = User.class;
Method[] m = c.getDeclaredMethods();
Arrays.stream(m).forEach(System.out::println);
}
No matter how many times I run it(run about 20 times in a row, and run once in a while later), no matter what the setter method and attribute order in the User class are, the final output result, setAge is before setName:
public int com.anno.User.getAge()
public void com.anno.User.setAge(int)
public java.lang.String com.anno.User.getName()
public void com.anno.User.setName(java.lang.String)
or:
public void com.anno.User.setAge(int)
public int com.anno.User.getAge()
public java.lang.String com.anno.User.getName()
public void com.anno.User.setName(java.lang.String)
Therefore, in spring, when obtaining the method list through reflection, it is impossible to specify the order, and track the entire operation chain, and finally to the execution of the setter method, the method list still maintains the order in which it was obtained.
During the entire initialization process, the processing of sorting the data in the method data is not seen.
The method org.springframework.util.ReflectionUtils#getDeclaredMethods(Class<?>, boolean) use Class#getDeclaredMethods to gets the array of all methods in User:
private static Method[] getDeclaredMethods(Class<?> clazz, boolean defensive) {
Assert.notNull(clazz, "Class must not be null");
Method[] result = declaredMethodsCache.get(clazz);
if (result == null) {
try {
Method[] declaredMethods = clazz.getDeclaredMethods();
List<Method> defaultMethods = findConcreteMethodsOnInterfaces(clazz);
if (defaultMethods != null) {
result = new Method[declaredMethods.length + defaultMethods.size()];
System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length);
int index = declaredMethods.length;
for (Method defaultMethod : defaultMethods) {
result[index] = defaultMethod;
index++;
}
}
else {
result = declaredMethods;
}
declaredMethodsCache.put(clazz, (result.length == 0 ? EMPTY_METHOD_ARRAY : result));
}
catch (Throwable ex) {
throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() +
"] from ClassLoader [" + clazz.getClassLoader() + "]", ex);
}
}
return (result.length == 0 || !defensive) ? result : result.clone();
}
Other related methods are as follows, please check by yourself:
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata
org.springframework.util.ReflectionUtils#doWithLocalMethods
The final conclusion is that no matter how the setter methods and attributes are arranged in the User entity, setAge will be called first when the bean is initialized.
In other words, the final setter method will only be called in the order of the methods in the method array returned by Class#getDeclaredMethods()
I'm not sure if other libraries provide the ability to specify the order in which the setter methods are called during reflection, so I can only assume that the order in which the setter methods are called cannot be guaranteed.
some advices
I think I understand your question, you seem to want to encapsulate the changing part into the setter method, so that even in your application, there are many places referencing this entity (say there are 100 places referencing it), you can just pass
Modify the setter method once to make the function effective for all referenced places.
I think there is really no way to control its calling order. Apart from spring, we can't figure out all the implementations of other libraries.
But we can do conversion during the development process, you can look at the concepts of PO (persistant object)
, BO(business object), DTO(data transfer object), VO(value object).
You can use PO, get the data from the xml、yml、configuration center or other data sources:
class HumanPO {
private Integer age;
private String gender;
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getGender() { return gender; }
public void setGender(String gender) { this.gender = gender; }
}
Then, convert it to a business object 'BO' where you can encapsulate logic:
class HumanBO {
private Integer age;
private String gender;
private String describe;
public static HumanBO PoConvertToBo(HumanPO humanPo) {
HumanBO rt = new HumanBO();
rt.setAge(humanPo.getAge());
rt.setGender(humanPo.getGender());
rt.setDescribe(humanPo.getGender());
return rt;
}
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getGender() { return gender; }
public void setGender(String gender) { this.gender = gender; }
public String getDescribe() { return describe; }
public void setDescribe(String gender) {
String describe = "";
if (this.age < 30) {
describe = "young " + gender;
} else if ( this.age <= 55 && this.age >= 30) {
describe = "middle-aged " + gender;
} else {
describe = "old " + gender;
}
this.describe = describe;
}
}
Now, you can implement your changing needs by modifying HumanBO.
If you have a table in the database containing age and gender you could use something like this:
#Entity
public class Human {
#Id
private String id;
private Integer age;
private String gender;
#Transient
private String describe;
public void setId(String id){
this.id = id;
}
public void setAge(Integer age){
this.age = age;
}
public void setGender(String gender){
this.gender = gender;
}
#PostLoad
public void initDescribe(){
if (this.age < 30) {
this.describe = "young " + this.gender;
} else if ( this.age <= 55 && this.age >= 30) {
this.describe = "middle-aged " + this.gender;
} else {
this.describe = "old " + this.gender;
}
}
}
Since the describe field is not in the database table it can be annotated with #Transient and the #PostLoad annotation will ensure that the initDescribe() method is called after age and gender fields are set.
So as I understand, Goat class has no problem with a code,
I want to assign the first two elements from a new object, but I am not sure why it gives an runtime error
class Pet {
public String name;
public boolean indoor;
public Pet(String name, Boolean indoor) {
this.name = name;
this.indoor = indoor;
}
public String toString(){
return name + ", " + indoor;
}
}
class Goat extends Pet {
public int age;
public String diet;
public Goat(String name, boolean indoor, int age, String diet) {
super(name, indoor);
this.age = age;
this.diet = diet;
}
Here is the test code and error
}
The error is being thrown because, Pet class constructor is expecting Boolean and Goat class constructor is passing boolean. Although compiler is compiling code, but at runtime java is not able to find boolean type constructor. Hence, throwing no method error.
Correct Code will be
class Pet {
public String name;
public boolean indoor;
public Pet(String name, boolean indoor) {
this.name = name;
this.indoor = indoor;
}
public String toString(){
return name + ", " + indoor;
}
}
class Goat extends Pet {
public int age;
public String diet;
public Goat(String name, boolean indoor, int age, String diet) {
super(name, indoor);
this.age = age;
this.diet = diet;
}
I have two objects that have the same name but contain different ages(values), I tried adding these objects to a map to remove duplicates but it won't remove. This is the model code I am testing:
two ab = new two("john", "20");
two ac = new two("chan", "30");
two ad = new two("john", "34");
ArrayList<two> ae = new ArrayList<>();
public void adding(){
ae.add(ab);
ae.add(ac);
ae.add(ad);
System.out.println(ae);
}
public void removeDuplicate(){
Set<two> lhs = new HashSet<>();
lhs.addAll(ae);
ae.clear();
ae.addAll(lhs);
System.out.println(ae);
}
public static void main(String args[]){
one five = new one();
five.adding();
five.removeDuplicate();
}
This is the class that is used for object type:
package teeestserrr;
public class two {
private String name;
private String age;
public two(String name, String age){
this.age = age;
this.name = name;
}
public String getName(){
return name;
}
public String getAge(){
return age;
}
public String toString(){
return name + " " + age;
}
}
Results are :
[john, chan, john]
[chan, john, john]
I also tried to make the toString return only name but the map used to remove duplicates doesn't seem to work even in that case. I don't understand and I cannot identify the underlying problem. Any help is appreciated.
You have to override equals and hashcode method in two.
Equals should give result according to only name field and also hashcode should be formed using only name field, that will help you to achieve your aim.
package teeestserrr;
public class two {
private String name;
private String age;
public two(String name, String age){
this.age = age;
this.name = name;
}
public String getName(){
return name;
}
public String getAge(){
return age;
}
public String toString(){
return name + " " + age;
}
#Override
public boolean equals(Object obj){
if(obj==null)
return false;
if(name==null){
return false;
}
if(!(obj instanceof two)){
return false;
}
two another = (two)obj;
return this.name.equals(another.name);
}
public int hashCode(){
return name==null?0:name.hashCode();
}
}
I am fairly new to Inheritance, and I'm not sure if I am doing it right but I seem to be on the right track. The program runs fine except the output I am getting isn't right. I think the problem is to do with my constructors.
public class Person {
protected static String name;
protected static int birthYear;
public Person(String name, int birthYear) {
}
public String name (String n) {
n = name;
return n;
}
public int birthYear (int bY) {
bY = birthYear;
return bY;
}
#Override
public String toString() {
return String.format(name + birthYear);
}
}
public class Student extends Person {
protected String major;
public Student(String name, int birthYear, String major) {
super(name, birthYear);
major = "";
}
public String major(String maj) {
maj = major;
return maj;
}
public String toString() {
super.toString();
return super.toString() + major;
}
}
public class Instructor extends Person {
protected static int salary;
public Instructor(String name, int birthYear, int salary) {
super(name, birthYear);
salary = 0;
}
public int salary(int sal) {
sal = salary;
return sal;
}
public String toString() {
super.toString();
return super.toString() + salary;
}
}
public class PersonTester {
public static void main(String[] args) {
Person p = new Person("Perry", 1959);
Student s = new Student("Sylvia", 1979, "Computer Science");
Instructor e = new Instructor("Edgar", 1969, 65000);
System.out.println(p);
System.out.println("Expected: Person[name=Perry,birthYear=1959]");
System.out.println(s);
System.out.println("Expected:" +
"Student[super=Person[name=Sylvia,birthYear=1979],major=Computer]");
System.out.println(e);
System.out.println("Expected:" + "Instructor[super=Person[name=Edgar,birthYear=1969],salary=65000.0]");
}
}
OUTPUT I AM GETTING:
null0
Expected: Person[name=Perry,birthYear=1959]
null0null
Expected: Student[super=Person[name=Sylvia,birthYear=1979],major=Computer Science]
null00
Expected: Instructor[super=Person[name=Edgar,birthYear=1969],salary=65000.0]
Try changing your constructor in Person to:
public Person(String name, int birthYear) {
this.name = name;
this.birthYear = birthYear;
}
Currently, the constructor has an empty body, so when you call super(name, birthYear); in the subclass constructor, nothing actually happens.
Your Student constructor also has an error. You forgot to initialize the major field.
public Student(String name, int birthYear, String major) {
super(name, birthYear);
this.major = major;
}
You have the same problem in the Instructor constructor...
public Instructor(String name, int birthYear, int salary) {
super(name, birthYear);
this.salary = salary;
}
Finally, you need to take away the static keywords before the fields in Person. This is because static ensures, that there will always be one (and only one) instance of those fields per class, as opposed to one per instance, like you want it to be:
protected String name;
protected int birthYear;
Same thing for the salary field in Instructor.
n = name; this causing your problem. It must be name = n;. All your setter function contain this problem, correct them all and tell me result.
public class People {
// Code to create a random set of people omitted
public Set getAllPeople() {
return people;
}
public void setPerson(Person person) {
if (person.getId() == -1) {
person.setId(getNextId());
}
people.remove(person);
people.add(person);
}
public void deletePerson(Person person) {
people.remove(person);
}
private Set people = new HashSet();
}
public class Person
{
private int id;
private String name;
private String address;
private float salary;
// Getters, setters, equals and toString omitted
}
While looking after the DWR website i found this example.It states that they omitted Getters, setters, equals and toString. How to write those for this program. I wish to run this program and see. Any Suggestions Please. Help out..
Getters and Setters are used to retrieve your "private" variables ( = variables visible inside the class they are defined only), from outside the class.
For instance:
private String name;
would have a getter like this:
public String getName() {
return name;
}
And a setter like this:
public void setName(String name) {
this.name = name;
}
(you could use "protected" if you only wanted this variable to be visible in the package, and not in the whole project).
the toString() method is here if you want to display some information about your object, which might be useful from a debugging point of view.
The equals method would be used to know how you want to compare to objects of Person type (by ids only for instance).
Have a look at this link to have more info on what is equals.
As RonK suggested, be sure to implement hashCode if you do implement equals, they go together, and have to use the same fields (part of the contract).
The rule is that if:
objectA.equals(objectB) returns true
then
objectA.hashCode() has to be equal to objectB.hashCode()
for each property in Person class you need to define 2 methods
for example id:
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
and you need to override equals and hashcode method to put your own condition for equality
public boolean equals(Object that) {
if (that == null) {
return false;
}
if (!(that instanceof Person)) {
return false;
}
return this.id == ((Person) that).id;
}
public int hashCode() {
return id * 17;
}
public class Person
{
//Id should be unique
private int id;
private String name;
private String address;
private float salary;
public Person(int id, String name, String address, float salary)
{
this.id = id;
this.name = name; //Maybe check for null
this.address = address; //Maybe check for null
this.salary = salary; //Maybe check for > 0
}
public int getId()
{
return id;
}
//No setID() - do you want that? you properly shouldn't
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getAddress()
{
return address;
}
public void setAddress(String address)
{
this.address = address; //Maybe check for null
}
public float getSalary()
{
return salary;
}
public setSalary(float salary)
{
this.salary = salary;
}
//A person is equal if they have the same ID
#Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Person person = (Person)obj;
return person.id == id;
}
#Override
public int hashCode()
{
return id;
}
//Just returns the name but you could return more details
#Override
public String toString()
{
return name;
}
}
Added hashCode which is essential - especially if you use it in a HashSet.