tl;博士
使用现代 Java 语言功能和类,定义您自己的枚举来表示季节和组。
Schedule.daysForGroupOnDate(
Group.D ,
LocalDate.now()
)
该方法生成Set 的DayOfWeek 枚举对象(不仅仅是文本!),例如DayOfWeek.TUESDAY 和DayOfWeek.THURSDAY。
现代 Java
谁能提出一个非常酷的方法来做到这一点?
是的。
现代 Java 有内置的类、集合和枚举来帮助您解决这个问题。
Java 中内置的 java.time 框架提供了Month 枚举和DayOfWeek 枚举。
EnumSet 和 EnumMap 提供了 Set 和 Map 的实现,这些实现经过优化,可与枚举一起使用,以便在很少的内存中快速执行。
您可以定义自己的enums 来代表您的季节和您的组(A、B 等)。 Java 中的枚举工具比其他语言中的枚举工具更有用、更强大。如果不熟悉,请参阅Oracle Tutorial。
定义自己的枚举的简单语法实际上提供了解决这个问题所需的大部分功能,消除了一些复杂的编码。 Java 9 (JEP 269) 中 collections factory methods 中用于集合和映射的新 literals syntax 使代码现在更加简单。
这是一个完整的工作应用程序。 请注意算法代码很少。定义您自己的自定义枚举可以完成大部分繁重的工作。
此应用程序代码的一个警告:它假设您的业务定义没有任何变化,或者至少如果有变化,您只关心当前规则,“最新的就是最好的”。如果您的规则随时间而变化,并且您需要表示所有过去、现在和未来的版本,我会构建一个非常不同的应用程序,可能会使用一个数据库来存储规则。但是这个应用程序在这里解决了所问的问题。
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;
// Constructor
Season ( List < Month > monthsArg )
{
this.months = monthsArg;
}
public List < Month > getMonths ( )
{
return this.months;
}
// For any given month, determine the season.
static public Season ofLocalMonth ( Month monthArg )
{
Season s = null;
for ( Season season : EnumSet.allOf( Season.class ) )
{
if ( season.getMonths().contains( monthArg ) )
{
s = season;
break; // Bail out of this FOR loop.
}
}
return s;
}
// For any given date, determine the season.
static public Season ofLocalDate ( LocalDate localDateArg )
{
Month month = localDateArg.getMonth();
Season s = Season.ofLocalMonth( month );
return s;
}
// Run `main` for demo/testing.
public static void main ( String[] args )
{
// Dump all these enum objects to console.
for ( Season season : EnumSet.allOf( Season.class ) )
{
System.out.println( "Season: " + season.toString() + " = " + season.getMonths() );
}
}
}
Group
将客户的草坪/院子的每组(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() );
}
}
}
Schedule
在这个Schedule 类中将所有内容放在一起。
这个类利用上面定义的两个枚举来完成有用的工作。到目前为止实现的唯一方法告诉您在特定日期允许特定组在一周中的哪几天。该方法确定哪个Season 适用于该日期。
在此处运行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;
}
// Run `main` for demo/testing.
public static void main ( String[] args )
{
Season.main( null );
Group.main( null );
// Dump all these enum objects to console.
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 ) ; // Standard ISO 8601 week, where week number one has the first Thursday of the calendar year, and week starts on Monday, so year is either 52 or 53 weeks long.
String message = "Group " + group + " – Watering days on " + localDate + " week # " + week + " is: " + days;
System.out.println( message );
}
}
}
控制台
在运行 Schedule.main 时,我们看到它被转储到控制台。
季节:春季 = [3 月、4 月]
季节:夏季 = [5 月、6 月、7 月、8 月]
季节:秋季 = [9 月、10 月]
季节:冬季 = [11 月、12 月、1 月、2 月]
组:A = {SPRING=[周二、周四]、FALL=[周二、周四]、夏季=[周一、周二、周三、周四、周五、周六、周日]、冬季=[周二]}
组:B = {SPRING=[FRIDAY], FALL=[TUESDAY, FRIDAY], SUMMER=[MONDAY, TUESDAY, WEDNESDAY, ThursDAY, FRIDAY, SATURDAY, SUNDAY], WINTER=[FRIDAY]}
组:C = {SPRING=[MONDAY], FALL=[MONDAY, TUESDAY], SUMMER=[MONDAY, TUESDAY, WEDNESDAY, ThursDAY, FRIDAY, SATURDAY, SUNDAY], WINTER=[MONDAY]}
组:D = {SPRING=[WEDNESDAY, FRIDAY], FALL=[FRIDAY], SUMMER=[MONDAY, TUESDAY, WEDNESDAY, THRSDAY, FRIDAY, SATURDAY, SUNDAY], WINTER=[WEDNESDAY]}
组:E = {SPRING=[TUESDAY], FALL=[TUESDAY, WEDNESDAY], SUMMER=[MONDAY, TUESDAY, WEDNESDAY, THRSDAY, FRIDAY, SATURDAY, SUNDAY], WINTER=[WEDNESDAY]}
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 周
了解definition of week 的ISO 8601 标准可能会对您有所帮助。该标准为“周”赋予了特定含义,并定义了一种文本格式,用于表示特定周或该周中的特定日期。
为了在 Java 中使用这些周,请考虑将 ThreeTen-Extra 库添加到您的项目中以使用 YearWeek 类。
LocalDate
LocalDate 类表示没有时间和时区的仅日期值。
时区对于确定日期至关重要。对于任何给定的时刻,日期在全球范围内因区域而异。例如,Paris France 中午夜过后几分钟是新的一天,而 Montréal Québec 中仍然是“昨天”。
如果没有指定时区,JVM 会隐式应用其当前的默认时区。该默认值可能随时更改,因此您的结果可能会有所不同。最好将您想要/预期的时区明确指定为参数。
以continent/region 的格式指定proper time zone name,例如America/Montreal、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 月的编号为 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 对象,而不是仅仅使用整数,以使您的代码更具自记录性、确保有效值并提供 type-safety。
LocalDate ld = LocalDate.of( 1986 , Month.FEBRUARY , 23 ) ;
不可变集合
上面看到的列表、集合和映射在理想情况下应该是不可变的集合,因为更改这些集合的成员可能会令人困惑和错误。
新的 Java 9 语法 List.of 和 Map.of 已经承诺是不可变的。但是,在我们的例子中,Map 理想情况下应该是EnumMap,以提高性能和内存效率。 Map.of 和 Set.of 的当前实现显然没有检测到枚举作为成员的使用,并通过内部使用 EnumMap 和 EnumSet 自动优化。有一个 OpenJDK issue 开放考虑此类问题:consider enhancements to EnumMap and EnumSet。
获取不可变 EnumSet 和不可变 EnumMap 的一种方法是通过 Google Guava 库:
每个结果都使用底层EnumSet/EnumMap。 get 和 put 等操作会抛出异常。因此,您可以获得与枚举相关的优化以及不变性。
这里是上面看到的Season 和Group 类,它们被修改为使用 Google Guava 23.6 库。
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 ) ), // `List.of` provides literals-style syntax, and returns an immutable `List`. New in Java 9.
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;
// Constructor
Season ( List < Month > monthsArg )
{
this.months = monthsArg;
}
public List < Month > getMonths ( )
{
return this.months;
}
// For any given month, determine the season.
static public Season ofLocalMonth ( Month monthArg )
{
Season s = null;
for ( Season season : EnumSet.allOf( Season.class ) )
{
if ( season.getMonths().contains( monthArg ) )
{
s = season;
break; // Bail out of this FOR loop.
}
}
return s;
}
// For any given date, determine the season.
static public Season ofLocalDate ( LocalDate localDateArg )
{
Month month = localDateArg.getMonth();
Season s = Season.ofLocalMonth( month );
return s;
}
// Run `main` for demo/testing.
public static void main ( String[] args )
{
// Dump all these enum objects to console.
for ( Season season : EnumSet.allOf( Season.class ) )
{
System.out.println( "Season: " + season.toString() + " = " + season.getMonths() );
}
}
}
Group 具有不变性
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 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.Date、Calendar 和 SimpleDateFormat。
Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。
要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。
从哪里获得 java.time 类?
ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如Interval、YearWeek、YearQuarter 和more。