简短概述
使用现代Java语言特性和类,定义自己的枚举来表示季节和组。
Schedule.daysForGroupOnDate(
Group.D ,
LocalDate.now()
)
该方法返回一个Set
对象,其中包含DayOfWeek
枚举对象(而不仅仅是文本!),例如DayOfWeek.TUESDAY
和DayOfWeek.THURSDAY
。
现代Java
有人能提供一种非常酷的方法来解决这个问题吗?
可以。
现代Java内置了类、集合和枚举,可帮助您解决此问题。
Java内置的java.time框架提供了Month
枚举和DayOfWeek
枚举。
EnumSet
和EnumMap
提供了Set
和Map
的实现,这些实现针对枚举类型进行了优化,以在非常少的内存中快速执行。
您可以定义自己的枚举类型来表示季节和组(A、B等)。Java中的枚举工具比其他语言中看到的要更有用和强大。如果不熟悉,请参阅Oracle教程。
定义自己的枚举类型的简单语法实际上提供了解决这个问题所需的大部分功能,消除了一些复杂的编码。Java 9中集合工厂方法中的
collections factory methods新的
literals syntax用于设置和映射(
JEP 269)现在使代码更加简单。
这是一个完整的工作应用程序。请注意算法中有多少代码。定义自己的自定义枚举类型完成了大部分的重活。
这个应用程序代码的一个警告:它假设您的业务定义不会发生任何变化,或者至少如果有变化,您只关心当前规则,“最新的是最好的”。如果您的规则随时间改变,并且您需要表示所有过去、现在和未来版本,我将构建一个非常不同的应用程序,可能需要使用数据库来存储规则。但是,这个应用程序可以解决所提出的问题。
Season
将您的季节表示为枚举Season
。每个季节对象都包含一个List
,其中包含定义该特定季节长度的Month
枚举对象。Java 9中添加的新List.of
语法通过静态工厂方法以字面量语法定义不可变列表。
package com.basilbourque.watering;
import java.time.LocalDate;
import java.time.Month;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
public enum Season
{
SPRING( List.of( Month.MARCH , Month.APRIL ) ),
SUMMER( List.of( Month.MAY , Month.JUNE, Month.JULY , Month.AUGUST ) ),
FALL( List.of( Month.SEPTEMBER , Month.OCTOBER ) ),
WINTER( List.of( Month.NOVEMBER , Month.DECEMBER , Month.JANUARY , Month.FEBRUARY ) );
private List< Month > months;
Season ( List < Month > monthsArg )
{
this.months = monthsArg;
}
public List < Month > getMonths ( )
{
return this.months;
}
static public Season ofLocalMonth ( Month monthArg )
{
Season s = null;
for ( Season season : EnumSet.allOf( Season.class ) )
{
if ( season.getMonths().contains( monthArg ) )
{
s = season;
break;
}
}
return s;
}
static public Season ofLocalDate ( LocalDate localDateArg )
{
Month month = localDateArg.getMonth();
Season s = Season.ofLocalMonth( month );
return s;
}
public static void main ( String[] args )
{
for ( Season season : EnumSet.allOf( Season.class ) )
{
System.out.println( "Season: " + season.toString() + " = " + season.getMonths() );
}
}
}
分组
将每个客户草坪/庭院的分组(A、B、C、D、E)表示为名为Group
的枚举。每个这些枚举对象都持有一个Map
,将Season
枚举对象映射到DayOfWeek
枚举对象的Set
。例如,在Season.SPRING
中的Group.A
允许在两天浇水,即DayOfWeek.TUESDAY
和DayOfWeek.THURSDAY
。
package com.basilbourque.watering;
import java.time.DayOfWeek;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
public enum Group
{
A(
Map.of(
Season.SPRING , EnumSet.of( DayOfWeek.TUESDAY , DayOfWeek.THURSDAY ) ,
Season.SUMMER , EnumSet.allOf( DayOfWeek.class ) ,
Season.FALL , EnumSet.of( DayOfWeek.TUESDAY , DayOfWeek.THURSDAY ) ,
Season.WINTER , EnumSet.of( DayOfWeek.TUESDAY )
)
),
B(
Map.of(
Season.SPRING , EnumSet.of( DayOfWeek.FRIDAY ) ,
Season.SUMMER , EnumSet.allOf( DayOfWeek.class ) ,
Season.FALL , EnumSet.of( DayOfWeek.TUESDAY , DayOfWeek.FRIDAY ) ,
Season.WINTER , EnumSet.of( DayOfWeek.FRIDAY )
)
),
C(
Map.of(
Season.SPRING , EnumSet.of( DayOfWeek.MONDAY ) ,
Season.SUMMER , EnumSet.allOf( DayOfWeek.class ) ,
Season.FALL , EnumSet.of( DayOfWeek.MONDAY , DayOfWeek.TUESDAY ) ,
Season.WINTER , EnumSet.of( DayOfWeek.MONDAY )
)
),
D(
Map.of(
Season.SPRING , EnumSet.of( DayOfWeek.WEDNESDAY , DayOfWeek.FRIDAY ) ,
Season.SUMMER , EnumSet.allOf( DayOfWeek.class ) ,
Season.FALL , EnumSet.of( DayOfWeek.FRIDAY ) ,
Season.WINTER , EnumSet.of( DayOfWeek.WEDNESDAY )
)
),
E(
Map.of(
Season.SPRING , EnumSet.of( DayOfWeek.TUESDAY ) ,
Season.SUMMER , EnumSet.allOf( DayOfWeek.class ) ,
Season.FALL , EnumSet.of( DayOfWeek.TUESDAY , DayOfWeek.WEDNESDAY ) ,
Season.WINTER , EnumSet.of( DayOfWeek.WEDNESDAY )
)
);
private Map < Season, Set < DayOfWeek > > map;
// Constructor
Group ( Map < Season, Set < DayOfWeek > > mapArg )
{
this.map = mapArg;
}
// Getter
private Map < Season, Set < DayOfWeek > > getMapOfSeasonToDaysOfWeek() {
return this.map ;
}
// Retrieve the DayOfWeek set for this particular Group.
public Set<DayOfWeek> daysForSeason (Season season ) {
Set<DayOfWeek> days = this.map.get( season ) ; // Retrieve the value (set of days) for this key (a season) for this particular grouping of lawns/yards.
return days;
}
// Run `main` for demo/testing.
public static void main ( String[] args )
{
// Dump all these enum objects to console.
for ( Group group : EnumSet.allOf( Group.class ) )
{
System.out.println( "Group: " + group.toString() + " = " + group.getMapOfSeasonToDaysOfWeek() );
}
}
}
日程表
在这个日程表
类中将所有内容整合起来。
该类利用上面定义的两个枚举类型来完成有用的工作。目前只实现了一个方法,它可以告诉您特定日期和特定组别允许哪些星期几进行浇水。该方法确定适用于该日期的季节
。
在此运行main
方法以转储我们的两个枚举类型的内容,并报告每个组别在特定硬编码日期允许浇水的星期几。
package com.basilbourque.watering;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.IsoFields;
import java.util.EnumSet;
import java.util.Set;
public class Schedule
{
static private DateTimeFormatter isoWeekFormatter = DateTimeFormatter.ofPattern( "uuuu-'W'ww" ) ;
static public Set < DayOfWeek > daysForGroupOnDate ( Group group , LocalDate localDate )
{
Season season = Season.ofLocalDate( localDate );
Set < DayOfWeek > days = group.daysForSeason( season );
return days;
}
public static void main ( String[] args )
{
Season.main( null );
Group.main( null );
for ( Group group : EnumSet.allOf( Group.class ) )
{
LocalDate localDate = LocalDate.now( ZoneId.of( "Africa/Tunis" ) );
Set < DayOfWeek > days = Schedule.daysForGroupOnDate( group , localDate );
String week = localDate.format( Schedule.isoWeekFormatter ) ;
String message = "Group " + group + " – Watering days on " + localDate + " week # " + week + " is: " + days;
System.out.println( message );
}
}
}
控制台
运行Schedule.main
时,我们可以在控制台中看到以下输出。
季节:春天 = [三月,四月]
季节:夏天 = [五月,六月,七月,八月]
季节:秋天 = [九月,十月]
季节:冬天 = [十一月,十二月,一月,二月]
组:A = {春天=[星期二,星期四],秋天=[星期二,星期四],夏天=[星期一,星期二,星期三,星期四,星期五,星期六,星期日],冬天=[星期二]}
组:B = {春天=[星期五],秋天=[星期二,星期五],夏天=[星期一,星期二,星期三,星期四,星期五,星期六,星期日],冬天=[星期五]}
组:C = {春天=[星期一],秋天=[星期一,星期二],夏天=[星期一,星期二,星期三,星期四,星期五,星期六,星期日],冬天=[星期一]}
组:D = {春天=[星期三,星期五],秋天=[星期五],夏天=[星期一,星期二,星期三,星期四,星期五,星期六,星期日],冬天=[星期三]}
组:E = {春天=[星期二],秋天=[星期二,星期三],夏天=[星期一,星期二,星期三,星期四,星期五,星期六,星期日],冬天=[星期三]}
A组 - 2018-01-30周的浇水日#2018-W05是:[星期二]
B组 - 2018-01-30周的浇水日#2018-W05是:[星期五]
C组 - 2018-01-30周的浇水日#2018-W05是:[星期一]
D组 - 2018-01-30周的浇水日#2018-W05是:[星期三]
E组 - 2018-01-30周的浇水日#2018-W05是:[星期三]
ISO 8601周
了解ISO 8601标准对于周的定义可能会有所帮助。该标准为“周”给出了特定的含义,并定义了表示特定周或特定周内某一天的文本格式。
如果在Java中处理这样的周,考虑将ThreeTen-Extra库添加到您的项目中,以利用YearWeek
类。
LocalDate
LocalDate
类表示仅带日期的值,不带时间和时区。
时区对于确定日期非常重要。对于任何给定的时刻,日期因时区而异。例如,在法国巴黎午夜过后几分钟是新的一天,而在加拿大魁北克省蒙特利尔仍然是“昨天”。
如果没有指定时区,则JVM隐式地应用其当前默认时区。该默认值可能随时更改,因此您的结果可能会有所不同。最好将所需/预期的时区明确指定为参数。
请以大陆/地区
的格式指定正确的时区名称,例如{{link2:America/Montreal
}}、{{link3:Africa/Casablanca
}}或Pacific/Auckland
。不要使用3-4个字母的缩写,如EST
或IST
,因为它们不是真正的时区,没有标准化,甚至不是唯一的!
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
如果您想使用JVM的当前默认时区,请请求并将其作为参数传递。如果省略,则隐式应用JVM的当前默认值。最好明确指出。
ZoneId z = ZoneId.systemDefault() ; // Get JVM’s current default time zone.
或者指定一个日期。您可以通过数字设置月份,1-12代表一月到十二月。
LocalDate ld = LocalDate.of( 1986 , 2 , 23 ) ; // Years use sane direct numbering (1986 means year 1986). Months use sane numbering, 1-12 for January-December.
或者更好的方法是使用预定义的
Month
枚举对象,每个月份一个。提示:在整个代码库中使用这些
Month
对象,而不是仅使用整数数字,可以使您的代码更加自我记录、确保有效值并提供
类型安全。
LocalDate ld = LocalDate.of( 1986 , Month.FEBRUARY , 23 ) ;
不可变集合
上面提到的列表、集合和映射应该是不可变集合,因为更改这些集合的成员可能会导致混乱和错误。
新的Java 9语法List.of
和Map.of
已经承诺是不可变的。然而,在我们的情况下,Map
应该是一个EnumMap
,以提高性能和内存效率。目前Map.of
和Set.of
的实现似乎没有检测到枚举作为成员的使用,并自动优化使用内部的EnumMap
和EnumSet
。有一个OpenJDK问题开放来考虑这些问题:考虑增强EnumMap和EnumSet。
获取不可变的EnumSet和不可变的EnumMap的一种方法是通过Google Guava库:
每个结果都使用底层的EnumSet
/EnumMap
。像get
和put
这样的操作会抛出异常。因此,您可以获得与枚举相关的优化以及不可变性。
这里是使用Google Guava 23.6库修改后的Season
和Group
类。
带有不可变性的Season
package com.basilbourque.watering;
import java.time.LocalDate;
import java.time.Month;
import java.util.EnumSet;
import java.util.List;
public enum Season
{
SPRING( List.of( Month.MARCH , Month.APRIL ) ),
SUMMER( List.of( Month.MAY , Month.JUNE, Month.JULY , Month.AUGUST ) ),
FALL( List.of( Month.SEPTEMBER , Month.OCTOBER ) ),
WINTER( List.of( Month.NOVEMBER , Month.DECEMBER , Month.JANUARY , Month.FEBRUARY ) );
private List< Month > months;
Season ( List < Month > monthsArg )
{
this.months = monthsArg;
}
public List < Month > getMonths ( )
{
return this.months;
}
static public Season ofLocalMonth ( Month monthArg )
{
Season s = null;
for ( Season season : EnumSet.allOf( Season.class ) )
{
if ( season.getMonths().contains( monthArg ) )
{
s = season;
break;
}
}
return s;
}
static public Season ofLocalDate ( LocalDate localDateArg )
{
Month month = localDateArg.getMonth();
Season s = Season.ofLocalMonth( month );
return s;
}
public static void main ( String[] args )
{
for ( Season season : EnumSet.allOf( Season.class ) )
{
System.out.println( "Season: " + season.toString() + " = " + season.getMonths() );
}
}
}
具有不可变性的
组
package com.basilbourque.watering;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.time.DayOfWeek;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
public enum Group
{
A(
Maps.immutableEnumMap(
Map.of( // `Map.of` provides literals-style syntax, and returns an immutable `Map`. New in Java 9.
Season.SPRING , Sets.immutableEnumSet( DayOfWeek.TUESDAY , DayOfWeek.THURSDAY ) ,
Season.SUMMER , Sets.immutableEnumSet( EnumSet.allOf( DayOfWeek.class ) ) ,
Season.FALL , Sets.immutableEnumSet( DayOfWeek.TUESDAY , DayOfWeek.THURSDAY ) ,
Season.WINTER , Sets.immutableEnumSet( DayOfWeek.TUESDAY )
)
)
),
B(
Maps.immutableEnumMap(
Map.of(
Season.SPRING , Sets.immutableEnumSet( DayOfWeek.FRIDAY ) ,
Season.SUMMER , Sets.immutableEnumSet( EnumSet.allOf( DayOfWeek.class ) ) ,
Season.FALL , Sets.immutableEnumSet( DayOfWeek.TUESDAY , DayOfWeek.FRIDAY ) ,
Season.WINTER , Sets.immutableEnumSet( DayOfWeek.FRIDAY )
)
)
),
C(
Maps.immutableEnumMap(
Map.of(
Season.SPRING , Sets.immutableEnumSet( DayOfWeek.MONDAY ) ,
Season.SUMMER , Sets.immutableEnumSet( EnumSet.allOf( DayOfWeek.class ) ) ,
Season.FALL , Sets.immutableEnumSet( DayOfWeek.MONDAY , DayOfWeek.TUESDAY ) ,
Season.WINTER , Sets.immutableEnumSet( DayOfWeek.MONDAY )
)
)
),
D(
Maps.immutableEnumMap(
Map.of(
Season.SPRING , Sets.immutableEnumSet( DayOfWeek.WEDNESDAY , DayOfWeek.FRIDAY ) ,
Season.SUMMER , Sets.immutableEnumSet( EnumSet.allOf( DayOfWeek.class ) ) ,
Season.FALL , Sets.immutableEnumSet( DayOfWeek.FRIDAY ) ,
Season.WINTER , Sets.immutableEnumSet( DayOfWeek.WEDNESDAY )
)
)
),
E(
Maps.immutableEnumMap(
Map.of(
Season.SPRING , Sets.immutableEnumSet( DayOfWeek.TUESDAY ) ,
Season.SUMMER , Sets.immutableEnumSet( EnumSet.allOf( DayOfWeek.class ) ) ,
Season.FALL , Sets.immutableEnumSet( EnumSet.of( DayOfWeek.TUESDAY , DayOfWeek.WEDNESDAY ) ) ,
Season.WINTER , Sets.immutableEnumSet( DayOfWeek.WEDNESDAY )
)
)
);
private Map < Season, Set < DayOfWeek > > map;
// Constructor
Group ( Map < Season, Set < DayOfWeek > > mapArg )
{
this.map = mapArg;
}
// Getter
private Map < Season, Set < DayOfWeek > > getMapOfSeasonToDaysOfWeek ( )
{
return this.map;
}
// Retrieve the DayOfWeek set for this particular Group.
public Set < DayOfWeek > daysForSeason ( Season season )
{
Set < DayOfWeek > days = this.map.get( season ); // Retrieve the value (set of days) for this key (a season) for this particular grouping of lawns/yards.
return days;
}
// Run `main` for demo/testing.
public static void main ( String[] args )
{
// Dump all these enum objects to console.
for ( Group group : EnumSet.allOf( Group.class ) )
{
System.out.println( "Group: " + group.toString() + " = " + group.getMapOfSeasonToDaysOfWeek() );
}
}
}
关于java.time
java.time框架内置于Java 8及以上版本中。这些类取代了老旧的遗留日期时间类,如java.util.Date
、Calendar
和SimpleDateFormat
。
Joda-Time项目现在处于维护模式,建议迁移到java.time类。
想要了解更多,请查看Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范是JSR 310。
如何获取java.time类?
ThreeTen-Extra 项目通过添加额外的类扩展了 java.time。该项目是 java.time 可能未来增加内容的试验场。您可能会在这里找到一些有用的类,例如 Interval
, YearWeek
, YearQuarter
和 更多。