Reputation: 49
Now I am doing a small project. And this project includes the compare among several dates. So I convert "yyyy-MM-dd" to millisecond by using SimpleDateFormat. However, it did not work out. By the way, when I use more easy way, that is, compare year, month and day one by one, it works out! So the problem is about the SimpleDateFormat.
Here are the code:
package GenericsPractice;
public class Employee {
private String name;
private int age;
private MyDate birthday;
public Employee(String name, int age, MyDate birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
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 MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday.toString();
}
}
package GenericsPractice;
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class MyDate {
private int month;
private int day;
private int year;
public MyDate(int month, int day, int year) {
this.month = month;
this.day = day;
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public long dateToMills() throws ParseException {
String date = (getYear())+"-"+(getMonth())+"-"+(getDay());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
long time = sdf.parse(date).getTime();
return time;
}
@Override
public String toString() {
return "month=" + month +
", day=" + day +
", year=" + year +
'}';
}
}
import GenericsPractice.Employee;
import GenericsPractice.MyDate;
import java.text.ParseException;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
public class EmployeeTest {
public static void main(String[] args) {
// TreeSet<Employee> set = new TreeSet<>(new Comparator<Employee>() {
// @Override
// public int compare(Employee o1, Employee o2) {
// int year = o1.getBirthday().getYear() - o2.getBirthday().getYear();
// int month = o1.getBirthday().getMonth() - o2.getBirthday().getMonth();
// int day = o1.getBirthday().getDay() - o2.getBirthday().getDay();
// if(year != 0){
// return year;
// }
// if(month != 0){
// return month;
// }
// if(day != 0){
// return day;
// }
// return o1.getName().compareTo(o2.getName());
// }
// });
TreeSet<Employee> set = new TreeSet<>(new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
try {
return (int)(o1.getBirthday().dateToMills()-o2.getBirthday().dateToMills());
} catch (ParseException e) {
e.printStackTrace();
}
throw new RuntimeException("输入类型不一致");
}
});
set.add(new Employee("Chandler",23,new MyDate(10,18,1997)));
set.add(new Employee("Ross",25,new MyDate(10,18,1992)));
set.add(new Employee("Joey",20,new MyDate(3,15,1999)));
set.add(new Employee("Monica",35,new MyDate(12,31,1990)));
set.add(new Employee("Rachel",21,new MyDate(6,2,1998)));
Iterator<Employee> iterator = set.iterator();
while(iterator.hasNext()){
Employee obj = iterator.next();
System.out.println(obj);
}
}
}
When running, it did not sort by date:
Employee{name='Chandler', age=23, birthday=month=10, day=18, year=1997}
Employee{name='Monica', age=35, birthday=month=12, day=31, year=1990}
Employee{name='Ross', age=25, birthday=month=10, day=18, year=1992}
Employee{name='Joey', age=20, birthday=month=3, day=15, year=1999}
Employee{name='Rachel', age=21, birthday=month=6, day=2, year=1998}
Upvotes: 0
Views: 257
Reputation: 338835
You said:
So I convert "yyyy-MM-dd" to millisecond by using SimpleDateFormat
No, do not represent a date as a count of milliseconds. And, No, never use SimpleDateFormat
. These terrible date-time classes were supplanted years ago. Use only the modern java.time classes defined in JSR 310.
Your code:
private int age;
No, do not store age. You can dynamically calculate an age by comparing their date of birth to the current date.
Also, you need a time zone to calculate age. For any given moment, the current date varies around the globe by time zone. It is "tomorrow" in Tokyo Japan while simultaneously "yesterday" in Toledo Ohio US.
Your code:
class MyDate
No, do not invent your own class to represent a date. We have the java.time.LocalDate
class for that.
Note that LocalDate
implements Comparable
. So LocalDate
objects know how to compare and sort amongst themselves. This behavior will be useful in code seen below.
To simplify your class definition, let's make it a record
. Records are a new feature in Java 16, for a class whose main purpose is transparently and immutably carrying data. The compiler implicitly creates the constructor, the getters, hashCode
& equals
, and toString
. You could just as well define a conventional class.
package org.example;
import java.time.LocalDate;
public record Person( String name , LocalDate dateOfBirth )
{
}
If you want an age-calculator as mentioned above, add a age
method.
package org.example;
import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneId;
public record Person(String name , LocalDate dateOfBirth)
{
public int age ( ZoneId zoneId ) { return Period.between( this.dateOfBirth , LocalDate.now( zoneId ) ).getYears(); }
}
In real work, I would add an explicit constructor that used Objects.requireNonNull
to ensure no null
arguments were received. Ditto for age
method, verify ZoneId
argument is not null.
For specifying our input objects, let's use Set.of
syntax. Then later feed the resulting unmodifiable Set
object to our TreeSet
.
Set < Person > peopleInput =
Set.of(
new Person( "Chandler" , LocalDate.of( 1997 , 10 , 18 ) ) ,
new Person( "Ross" , LocalDate.of( 1992 , 10 , 18 ) ) ,
new Person( "Joey" , LocalDate.of( 1999 , 3 , 15 ) ) ,
new Person( "Monica" , LocalDate.of( 1990 , 12 , 31 ) ) ,
new Person( "Rachel" , LocalDate.of( 1998 , 6 , 2 ) )
);
Much easier nowadays to use lambda/streams syntax for specifying a Comparator
. You could write a conventional comparator as seen in your Question, but doing so is more likely to be incorrect, and less readable.
As discussed in my Answer to your similar Question yesterday, the Comparator
/Comparable
implementation used in your NavigableSet
must be consistent with equals
. If two objects being compared are named a
and b
, then “consistent with equals” means that where a.equals(b)
returns true
, so must a.compareTo(b) == 0
return true
. So we must include both member fields name
& dateOfBirth
as those fields are used in our record’s default implementation of equals
.
Comparator < Person > comparingByDateOfBirthAndThenName =
Comparator
.comparing( Person :: dateOfBirth )
.thenComparing( Person :: name );
Construct our TreeSet
using that Comparator
object.
NavigableSet < Person > people = new TreeSet <>( comparingByDateOfBirthAndThenName );
Populate with the objects we instantiated via Set.of
. As the objects are added, the TreeSet
, being a NavigableSet
, uses our Comparator
object to sort them.
people.addAll( peopleInput );
Dump to console.
System.out.println( "peopleInput = " + peopleInput );
System.out.println( "people = " + people );
When run.
peopleInput = [Person[name=Chandler, dateOfBirth=1997-10-18], Person[name=Rachel, dateOfBirth=1998-06-02], Person[name=Joey, dateOfBirth=1999-03-15], Person[name=Ross, dateOfBirth=1992-10-18], Person[name=Monica, dateOfBirth=1990-12-31]]
people = [Person[name=Monica, dateOfBirth=1990-12-31], Person[name=Ross, dateOfBirth=1992-10-18], Person[name=Chandler, dateOfBirth=1997-10-18], Person[name=Rachel, dateOfBirth=1998-06-02], Person[name=Joey, dateOfBirth=1999-03-15]]
Upvotes: 2