我创建了一个待办事项程序,该程序记录了用户输入的任务。对于每个任务,用户必须输入名称,日期等。
当用户从菜单中选择“ 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)));
}
}
答案 0 :(得分:2)
首先,组织您的代码,以便于处理。将每个类放在自己的.java
文件中。使用适当的缩进显示代码的层次结构。您的IDE可以为您提供帮助。
请记住separation of concerns。您的ToDoList
类应专注于维护有关Task
对象列表的有效状态。 ToDoList
类对控制台上的用户交互一无所知。对于该用户交互,请创建一个单独的类。
从Task
类来看,您永远不要使用旧类java.util.Date
,java.sql.Date
和SimpleDateFormat
。几年前,它们被 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() );
}
这是一张表格,可帮助您直接掌握各种日期时间类型。
我们忽略了位置和类别字段,因为它们与按日期排序问题无关。
我们需要能够将一个Task
与另一个明确地区分开。在实际工作中,数据库可能会使用一个主键来跟踪每个任务记录。这样的主键通常是序列整数或UUID。在这里,我们使用UUID。我们覆盖Object::equals
和Object::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
类可以简单地是List
或Set
,而无需我们定义自己的类。但是在实际工作中,此类可能会承担额外的职责。因此,我们将继续创建该类。
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
方法中。
正确建立模型是编写更简洁的代码的基础。