如何在Java中按日期对ArrayList的元素进行排序?

时间:2020-06-30 21:06:16

标签: java arraylist

我创建了一个待办事项程序,该程序记录了用户输入的任务。对于每个任务,用户必须输入名称,日期等。

当用户从菜单中选择“ 5”时,程序将按日期对这些任务进行排序。我需要根据任务日期和时间的升序对所有任务进行排序,即,日期和时间较早的任务将在日期和时间较晚的任务之前列出,并显示排序后的列表。

但是,当我运行代码时,会出现以下错误:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot format given Object as a Date
at java.base/java.text.DateFormat.format(DateFormat.java:338)
at java.base/java.text.Format.format(Format.java:158)
at ToDoList.lambda$0(ToDoList.java:238)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at ToDoList.sortTasks(ToDoList.java:238)
at ToDoList.main(ToDoList.java:106)

到目前为止,这是我的代码:(sortTasks()位于底部)

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner; 

class Task{

private String theTitle;
private Date theDate;
private String theTime;
private String theLocation;
private String theDuration;
private String theCategory;
SimpleDateFormat format=new SimpleDateFormat("dd/MM/yyyy");

Task(String title, Date date, String time, String location, String duration, String category) {
    
    theTitle = title;
    theDate = date;
    theTime = time;
    theLocation = location;
    theDuration = duration;
    theCategory = category;
    
}

public String getTitle() {
    
    return theTitle;
}

public Date getDate() {

    return theDate;
}

public String getTime() {
    
    return theTime;
}

public String getLocation() {
    
    return theLocation;
}

public String getDuration() {
    
    return theDuration;
}

public String getCategory() {
    
    return theCategory;
}

public String getItem() {
    
    return theTitle + ", " + format.format(theDate) + ", " + theTime + ", " + theLocation + ", " + theDuration + ", " + theCategory;
}

}


public class ToDoList {

public Task myTaskObj;
SimpleDateFormat format=new SimpleDateFormat("dd/MM/yyyy");
private static List<String> currentList = new ArrayList<String>();

public ToDoList() {
    
}

public static void main (String[] args) throws ParseException {
    
    ToDoList listObj = new ToDoList();
    
    int menuItem = -1;
    while (menuItem != 7) {
        menuItem = listObj.printMenu();
        switch (menuItem) {
        case 1:
            listObj.addItem();
            break;
        case 2:
            listObj.removeItem();
            break;
        case 3:
            listObj.removeAllTasks();
            break;
        case 4:
            listObj.showList();
            break;
        case 5: 
            listObj.sortTasks();
            break;
        case 6:
            listObj.searchTasks();
            break;
        case 7: 
            System.out.println("Goodbye!");
        default:
            System.out.println("Enter a valid option");
        }
    }   
    
}

public int printMenu() {
    
    Scanner scanner = new Scanner(System.in);
    System.out.println();
    System.out.println("----------------------");
    System.out.println("Main Menu");
    System.out.println("----------------------");
    System.out.println("1. Add a task");
    System.out.println("2. Delete a task");
    System.out.println("3. Delete all tasks");
    System.out.println("4. List all tasks");
    System.out.println("5. Sort tasks by date");
    System.out.println("6. Search for a task");
    System.out.println("7. Exit the program");
    System.out.println();
    System.out.print("Enter choice: ");
    int choice = scanner.nextInt();
    
    return choice;
    
}

public void showList() {
System.out.println();
System.out.println("----------------------");       
System.out.println("To-Do List");
System.out.println("----------------------");
int number = 0;
for (String item : currentList) {
    System.out.println(++number + ". " + item);
}
System.out.println("----------------------");


}

public void addItem() throws ParseException {
System.out.println("Add a task");
System.out.println("----------------------");

System.out.print("Enter the task title: ");
Scanner scanner = new Scanner(System.in);
String title = scanner.nextLine();

System.out.print("Enter the task date (dd/mm/yyyy): ");
Scanner scanner2 = new Scanner(System.in);
Date date=format.parse(scanner2.next());


System.out.print("Enter the task time: ");
Scanner scanner3 = new Scanner(System.in);
String time = scanner3.nextLine();

System.out.print("Enter the task location: ");
Scanner scanner4 = new Scanner(System.in);
String location = scanner4.nextLine();

System.out.println("Enter the task duration (optional - press enter to skip): ");
Scanner scanner5 = new Scanner(System.in);
String duration = scanner5.nextLine();

System.out.println("Enter the task category (optional - press enter to skip): ");
Scanner scanner6 = new Scanner(System.in);
String category = scanner6.nextLine();

myTaskObj = new Task(title, date, time, location, duration, category);

String theItem = myTaskObj.getItem();

currentList.add(theItem);
System.out.println("Task Added!");



}

public void removeItem() {
System.out.println("Delete a task");
System.out.println("----------------------");
Scanner scanner = new Scanner(System.in);
System.out.print("What do you want to remove? (Enter number): ");
int index = scanner.nextInt();
if((index-1)<0 || index>currentList.size()) {
    System.out.println("Wrong index number! Please enter in range of 1 to "+currentList.size());            
}else {
    currentList.remove(index-1);
}
System.out.println("----------------------");
System.out.println("Task Removed!");


}

public void removeAllTasks() {

System.out.println("Remove all tasks");
System.out.println("----------------------");
showList();

Scanner keyboard = new Scanner(System.in);
System.out.print("Are you sure you'd like to delete all tasks? 'Yes' or 'No': ");
String choice = keyboard.nextLine();
if(choice.equals("Yes")) {
    currentList.removeAll(currentList);
    System.out.println("All tasks deleted!");
}
else 
    if(choice.equals("No"))
    System.out.println("Tasks not deleted");

}

public void sortTasks() {

System.out.println("Sorted tasks by date (earliest first): ");

Collections.sort(currentList);

currentList.forEach(action-> System.out.println(format.format(action)));
}

}

2 个答案:

答案 0 :(得分:2)

首先,组织您的代码,以便于处理。将每个类放在自己的.java文件中。使用适当的缩进显示代码的层次结构。您的IDE可以为您提供帮助。

请记住separation of concerns。您的ToDoList类应专注于维护有关Task对象列表的有效状态。 ToDoList类对控制台上的用户交互一无所知。对于该用户交互,请创建一个单独的类。

Task类来看,您永远不要使用旧类java.util.Datejava.sql.DateSimpleDateFormat。几年前,它们被 java.time 类所取代,并被JSR 310一致采用。在UTC中,请使用Instant。对于没有日期且没有时区的仅日期值,请使用LocalDate。要解析/生成表示这些值的文本,请使用DateTimeFormatter

要预订将来的约会,我们必须将日期和时间与时区分开存储。政治家经常更改其辖区所在时区使用的偏移量。因此,明年1月23日下午3点可能与我们现在期望的时间不同。

因此,您的Task类需要一对成员字段:LocalDateTime用于表示日期和日期,以及一个ZoneId时区对象。我假设您是说这是任务应该开始的时间,因为您还有一个选项工期字段。

谈到持续时间,Java为此提供了一个类Duration。它表示时间轴上未附加的时间跨度,以24小时通用天,小时,分钟和小数秒为单位。

格式化程序不应在您的Task类上进行硬编码。相反,使用Task对象的调用方法应该将Locale对象以及FormatStyle传递给日期时间值的automatically localize the display。甚至更好的是,人们可能会争辩说,生成格式化的日期时间字符串甚至不应该是Task类的工作。任务对象应该只返回预计任务开始的预计时刻,通过将存储的ZonedDateTime对象应用于存储的ZoneId对象来返回LocalDateTime对象。

这里是将ZoneId应用于LocalDateTime以确定ZonedDateTime对象形式的时刻(时间轴上的一个点)的方法。

public ZonedDateTime projectedStartingMoment ( )
{
    ZonedDateTime zdt = this.startDateTime.atZone( this.zoneId );
    return Objects.requireNonNull( zdt );
}

➥这个即时生成的ZonedDateTime对象也是我们对这些任务进行排序所需要的,这是您问题的原始目的。为了对Task对象进行排序,我们将实现Comparable接口,这需要我们编写一个compareTo方法。在我们的compareTo中,我们生成ZonedDateTime对象,并对其进行比较以进行排序。不同的任务可能具有不同的时区,因此我们不能仅比较存储的LocalDateTime对象。

// Implement `Comparable` interface.
@Override
public int compare ( Task task1 , Task task2 )
{
    return task1.projectedStartingMoment().compareTo( task2.projectedStartingMoment() );
}

这是一张表格,可帮助您直接掌握各种日期时间类型。

Table of all date-time types in Java, both modern and legacy

我们忽略了位置和类别字段,因为它们与按日期排序问题无关。

我们需要能够将一个Task与另一个明确地区分开。在实际工作中,数据库可能会使用一个主键来跟踪每个任务记录。这样的主键通常是序列整数或UUID。在这里,我们使用UUID。我们覆盖Object::equalsObject::hashCode以使用此UUID标识符值。我们的SortedSet对象的Task集合可以使用这些方法。

无需在您的成员字段前面加上the

这样Task类看起来像这样。顺便说一句,可以将Java 15中新的Records功能用于该类,但是由于Java 15尚未发布,因此我们在这里不做。

package work.basil.example;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Comparator;
import java.util.Objects;
import java.util.UUID;

public class Task
        implements Comparable < Task >
{

    // Member fields.
    private UUID id;
    private String title;
    private LocalDateTime startDateTime;
    private ZoneId zoneId;
    private Duration duration;

    // Constructor
    public Task ( UUID id , String title , LocalDateTime startDateTime , ZoneId zoneId , Duration duration )
    {
        this.id = id;
        this.title = title;
        this.startDateTime = startDateTime;
        this.zoneId = zoneId;
        this.duration = duration;
    }

    // Logic

    public ZonedDateTime projectedStartingMoment ( )
    {
        ZonedDateTime zdt = this.startDateTime.atZone( this.zoneId );
        return Objects.requireNonNull( zdt );
    }

    public ZonedDateTime projectedEndingMoment ( )
    {
        ZonedDateTime zdt = this.startDateTime.atZone( this.zoneId ).plus( this.duration );  // Half-Open approach, for spans-of-time that neatly abut one another without gaps.
        return Objects.requireNonNull( zdt );
    }

    // Accessors
    // Getters only, immutable object.

    public UUID getId ( ) { return this.id; }

    public String getTitle ( ) { return this.title; }

    public LocalDateTime getStartDateTime ( ) { return this.startDateTime; }

    public ZoneId getZoneId ( ) { return this.zoneId; }

    public Duration getDuration ( ) { return this.duration; }


    // Object overrides.


    @Override
    public String toString ( )
    {
        return "Task{ " +
                "id=" + id +
                " | title='" + title + '\'' +
                " | startDateTime=" + startDateTime +
                " | zoneId=" + zoneId +
                " | duration=" + duration +
                " | projectedStartingMoment=" + projectedStartingMoment() +
                " }";
    }

    @Override
    public boolean equals ( Object o )
    {
        if ( this == o ) return true;
        if ( o == null || getClass() != o.getClass() ) return false;
        Task task = ( Task ) o;
        return getId().equals( task.getId() );
    }

    @Override
    public int hashCode ( )
    {
        return Objects.hash( getId() );
    }

    @Override
    public int compareTo ( Task other )
    {
        return this.projectedStartingMoment().compareTo( other.projectedStartingMoment() );
    }
}

接下来,我们将Task类的对象收集到ToDoList中。您的问题中的ToDoList类是混合关注点,处理用户交互和处理演示。这两个都属于您的应用程序类。可以这样考虑,如果以后要在控制台用户界面之外向应用程序添加GUI,则ToDoList::showList可能是不合适的,无关紧要的。这说明“ showList”工作不属于ToDoList类。

此时,我们的ToDoList类可以简单地是ListSet,而无需我们定义自己的类。但是在实际工作中,此类可能会承担额外的职责。因此,我们将继续创建该类。

package work.basil.example;

import java.util.*;

public class ToDoList
{
    private SortedSet < Task > tasks;


    // Constructors

    public ToDoList ( )
    {
        this.tasks = new TreeSet <>();
    }

    public ToDoList ( Collection < Task > tasks )
    {
        this(); // Call other constructor
        this.tasks.addAll( tasks );
    }

    // Logic

    public boolean addTask ( Task task )
    {
        Objects.requireNonNull( task ); // Fail fast. In real work, pass a message for the exception.
        boolean result = this.tasks.add( task );
        return result;
    }

    public boolean addTasks ( Collection tasks )
    {
        return this.tasks.addAll( Objects.requireNonNull( tasks ) );
    }

    public boolean remove ( Task task )
    {
        return this.tasks.remove( Objects.requireNonNull( task ) );
    }

    public void removeAll ( )
    {
        this.tasks.clear();
    }

    public List < Task > getTasksSortedByProjectedStartingMoment ( )
    {
        // Make a copy of our `SortedSet`, to be separate from our own here.
        // This way the calling method can do what they want, as can this class,
        // while not stepping on each other's feet.
        Objects.requireNonNull( this.tasks ); // Paranoid check.
        return List.copyOf( this.tasks );
    }
}

让我们利用这些类来查看它们的实际效果。我将使用可以作为您的新应用(main)类的开头来与控制台上的用户进行交互。但是我不会做所有的用户交互代码,因为这与问题无关。在这里,我只是实例化几个Task对象,将它们放在ToDoList中,然后返回按日期排序的列表。

为最终回答您的问题,我们调用了ToDoList::getTasksSortedByProjectedStartingMoment方法。

List < Task > tasksSorted = this.toDoList.getTasksSortedByProjectedStartingMoment();
    

完整的示例代码。

package work.basil.example;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneId;
import java.util.List;
import java.util.UUID;

public class ToDoListEditorConsole
{
    private ToDoList toDoList;

    public static void main ( String[] args )
    {
        ToDoListEditorConsole app = new ToDoListEditorConsole();
        app.launch();
    }

    private void launch ( )
    {
        this.toDoList = new ToDoList();
        this.demo();
    }

    private void demo ( )
    {
        // Make a few `Task` objects. All on the same day in the same zone, but different time-of-day.
        // Notice that our time-of-day values are *not* in chronological order.
        List < Task > tasks = List.of(
                new Task(
                        UUID.fromString( "98399344-bb31-11ea-b3de-0242ac130004" ) ,
                        "Eat apple" ,
                        LocalDateTime.of( 2021 , Month.JANUARY , 23 , 12 , 30 , 0 , 0 ) ,
                        ZoneId.of( "Africa/Tunis" ) , Duration.ofHours( 1 )
                ) ,
                new Task(
                        UUID.fromString( "1e4ded04-bb32-11ea-b3de-0242ac130004" ) ,
                        "Eat banana" ,
                        LocalDateTime.of( 2021 , Month.JANUARY , 23 , 20 , 00 , 0 , 0 ) ,
                        ZoneId.of( "Africa/Tunis" ) , Duration.ofHours( 1 )
                ) ,
                new Task(
                        UUID.fromString( "010fcde8-bb32-11ea-b3de-0242ac130004" ) ,
                        "Eat corn" ,
                        LocalDateTime.of( 2021 , Month.JANUARY , 23 , 15 , 00 , 0 , 0 ) ,
                        ZoneId.of( "Africa/Tunis" ) , Duration.ofMinutes( 30 )
                )

        );
        this.toDoList.addTasks( tasks );

        List < Task > tasksSorted = this.toDoList.getTasksSortedByProjectedStartingMoment();
        System.out.println( "Result:" );
        System.out.println( tasksSorted );
        System.out.println( "« fin »" );
    }
}

运行时,请注意香蕉和玉米任务(第2和第3)如何切换位置,现在按时间顺序进行了排序。

结果:

[任务{id = 98399344-bb31-11ea-b3de-0242ac130004 | title =“吃苹果” | startDateTime = 2021-01-23T12:30 | zoneId =非洲/突尼斯|持续时间= PT1H | projectedStartingMoment = 2021-01-23T12:30 + 01:00 [Africa / Tunis]},任务{id = 010fcde8-bb32-11ea-b3de-0242ac130004 | title =“吃玉米” | startDateTime = 2021-01-23T15:00 | zoneId =非洲/突尼斯|持续时间= PT30M | projectedStartingMoment = 2021-01-23T15:00 + 01:00 [Africa / Tunis]},任务{id = 1e4ded04-bb32-11ea-b3de-0242ac130004 | title ='吃香蕉'| startDateTime = 2021-01-23T20:00 | zoneId =非洲/突尼斯|持续时间= PT1H | projectedStartingMoment = 2021-01-23T20:00 + 01:00 [非洲/突尼斯]}]

«鳍»

答案 1 :(得分:0)

我建议您在这里面临的主要问题是,您将任务存储为字符串而不是Task对象。如果您正确地存储了它们,那么很多操作将变得更加容易。

因此,将您的列表更改为:

class ToDoList {
    private final List<Task> tasks = new ArrayList<>();
    ...
}

然后排序变得非常简单。例如,它可能看起来像:

public void sortTasks(Comparator<Task> order) {
    tasks.sort(order);
}

与用户反应的代码如下:

case 5: toDoList.sortTasks(Comparator.comparing(Task::getDate).thenComparing(Task::getTime));

添加选项以按照其他条件(例如sortTasks(Comparator.comparing(Task::getTitle))进行排序)变得很简单。

这也应该使输出更容易阅读,因为它可以嵌入到任务的toString方法中。

正确建立模型是编写更简洁的代码的基础。

相关问题