Introduction
This article focuses on some of the new syntax features in JDK 1.8, specifically Lambda expressions, the Stream API, and the LocalDate date/time API.
Lambda
What is Lambda?
A Lambda expression (lambda expression) is an anonymous function. It is named after lambda calculus in mathematics, directly corresponding to lambda abstraction. It is an anonymous function, i.e., a function without a function name.
Structure of a Lambda Expression
- A Lambda expression can have zero or more parameters.
- The type of a parameter can be explicitly declared or inferred from context. For example,
(int a)has the same effect as(a). - All parameters must be enclosed in parentheses, separated by commas. For example:
(a, b)or(int a, int b)or(String a, int b, float c). - Empty parentheses represent an empty parameter set. For example:
() -> 42. - When there is only one parameter and its type can be inferred, the parentheses can be omitted. For example:
a -> a * a. - The body of a Lambda expression can contain zero or more statements.
- If the body of a Lambda expression has only one statement, the curly braces
{}can be omitted. The return type of the anonymous function is the same as the type of the body expression. - If the body of a Lambda expression contains more than one statement, the expression must be enclosed in curly braces
{}(forming a code block). The return type of the anonymous function matches the return type of the code block, or is void if there is no return.
Using Lambda Expressions
Let's start with a simple example to see the effect of Lambda.
Consider iterating over a Map:
Traditional iteration:
Map<String, String> map = new HashMap<>();
map.put("a", "a");
map.put("b", "b");
map.put("c", "c");
map.put("d", "d");
System.out.println("Traditional Map iteration:");
for (String key : map.keySet()) {
System.out.println("k=" + key + ", v=" + map.get(key));
}
Iteration using Lambda:
System.out.println("Lambda Map iteration:");
map.forEach((k, v) -> {
System.out.println("k=" + k + ", v=" + v);
});
The same applies to List. Additionally, List can be traversed using the double colon operator:
List<String> list = new ArrayList<String>();
list.add("a");
list.add("bb");
list.add("ccc");
list.add("dddd");
System.out.println("Lambda List iteration:");
list.forEach(v -> {
System.out.println(v);
});
System.out.println("Double colon operator iteration:");
list.forEach(System.out::println);
Output:
Traditional Map iteration:
k=a, v=a
k=b, v=b
k=c, v=c
k=d, v=d
Lambda Map iteration:
k=a, v=a
k=b, v=b
k=c, v=c
k=d, v=d
Lambda List iteration:
a
bb
ccc
dddd
Double colon operator iteration:
a
bb
ccc
dddd
Besides use in for loops, Lambda can also replace anonymous inner classes.
For example, creating a thread:
// Traditional way
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Traditional way created!");
}
};
// Lambda way
Runnable r2 = () -> System.out.println("Lambda way created!");
Note: In this example, the compiler automatically infers the
Runnableinterface based on the constructor signatureRunnable r { }.
Difference between Lambda and Anonymous Classes
A key difference between anonymous classes and Lambda expressions is the use of keywords. For anonymous classes, the keyword this refers to the anonymous class instance. For Lambda expressions, this refers to the enclosing class where the Lambda is written.
Important Notes on Lambda Usage
While Lambda simplifies code, it can also reduce readability.
Stream
What is Stream?
Stream provides a high-level abstraction for Java collection operations and expressions, offering an intuitive way similar to using SQL statements to query data from a database. The Stream API can significantly boost Java programmer productivity, enabling efficient, clean, and concise code. This style treats the collection of elements to be processed as a stream, which is transmitted through a pipeline and can be processed at pipeline nodes, such as filtering, sorting, and aggregating.
Stream Characteristics:
- Not a data structure: It has no internal storage; it only fetches data from a source (data structure, array, generator function, IO channel) using an operation pipeline. It never modifies the underlying data of the encapsulated data structure. For example, the
filteroperation of a Stream produces a new Stream without the filtered elements, rather than removing those elements from the source. - No index access: However, it can easily generate arrays or Lists.
- Laziness: Many Stream operations are deferred until it determines how much data is actually needed.
Intermediateoperations are always lazy. - Parallelism: When a Stream is parallelized, there is no need to write multithreading code; all operations on it are automatically performed in parallel.
- Can be infinite: Collections have a fixed size, but Streams do not have to be. Short-circuiting operations like
limit(n)andfindFirst()can operate on infinite Streams and complete quickly. - Important: All Stream operations must take lambda expressions as parameters.
Stream Operation Types:
- Intermediate: A stream can be followed by zero or more intermediate operations. Their main purpose is to open the stream, perform some degree of data mapping/filtering, and then return a new stream for the next operation. These operations are lazy, meaning merely calling these methods does not start traversing the stream.
- Terminal: A stream can have only one terminal operation. Once this operation is executed, the stream is "consumed" and cannot be operated on again. Thus, it must be the last operation of the stream. The execution of a Terminal operation actually starts the stream traversal and produces a result or a side effect.
Using Stream
Let's look at a simple example.
In development, we sometimes need to filter data. With traditional methods, we have to iterate and filter the data, which can be cumbersome. Using Stream makes this much more convenient.
First, filtering in the traditional way:
List<String> list = Arrays.asList("Alice", "Bob", "Charlie", "xuwujing");
System.out.println("Before filtering:" + list);
List<String> result = new ArrayList<>();
for (String str : list) {
if (!"Bob".equals(str)) {
result.add(str);
}
}
System.out.println("After filtering:" + result);
Filtering using Stream:
List<String> result2 = list.stream().filter(str -> !"Bob".equals(str)).collect(Collectors.toList());
System.out.println("Stream after filtering:" + result2);
Output:
Before filtering:[Alice, Bob, Charlie, xuwujing]
After filtering:[Alice, Charlie, xuwujing]
Stream after filtering:[Alice, Charlie, xuwujing]
Isn't it concise and convenient?
Stream has many more uses beyond just filter. Let's explore some of them.
1. Creating a Stream
Stream stream = Stream.of("a", "b", "c");
String[] strArray = new String[] { "a", "b", "c" };
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
List<String> list = Arrays.asList(strArray);
stream = list.stream();
2. Converting Between Streams
Note: A Stream can only be used once. The following code reuses
stream2multiple times for brevity, which would throw anIllegalStateException: stream has already been operated upon or closed.
try {
Stream<String> stream2 = Stream.of("a", "b", "c");
// to Array
String[] strArray1 = stream2.toArray(String[]::new);
// to Collection
List<String> list1 = stream2.collect(Collectors.toList());
List<String> list2 = stream2.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream2.collect(Collectors.toSet());
Stack stack1 = stream2.collect(Collectors.toCollection(Stack::new));
// to String
String str = stream.collect(Collectors.joining()).toString();
} catch (Exception e) {
e.printStackTrace();
}
3. Using map
The map method is used to map each element to a corresponding result (one-to-one).
Example 1: Convert to uppercase
List<String> list3 = Arrays.asList("zhangSan", "liSi", "wangWu");
System.out.println("Before mapping:" + list3);
List<String> list4 = list3.stream().map(String::toUpperCase).collect(Collectors.toList());
System.out.println("After mapping:" + list4);
// After mapping:[ZHANGSAN, LISI, WANGWU]
Example 2: Convert data type
List<String> list31 = Arrays.asList("1", "2", "3");
System.out.println("Before mapping:" + list31);
List<Integer> list41 = list31.stream().map(Integer::valueOf).collect(Collectors.toList());
System.out.println("After mapping:" + list41);
// [1, 2, 3]
Example 3: Get squares
List<Integer> list5 = Arrays.asList(new Integer[] { 1, 2, 3, 4, 5 });
List<Integer> list6 = list5.stream().map(n -> n * n).collect(Collectors.toList());
System.out.println("Squared values:" + list6);
// [1, 4, 9, 16, 25]
4. Using filter
The filter method is used to filter elements based on a given condition.
Example 2: Using filter with findAny to get an if/else value
List<String> list = Arrays.asList("Alice", "Bob", "Charlie", "xuwujing");
String result3 = list.stream().filter(str -> "Bob".equals(str)).findAny().orElse("Not found!");
String result4 = list.stream().filter(str -> "David".equals(str)).findAny().orElse("Not found!");
System.out.println("Stream filter 2:" + result3);
System.out.println("Stream filter 3:" + result4);
// Stream filter 2:Bob
// Stream filter 3:Not found!
Example 3: Using filter with mapToInt to calculate a sum
List<User> lists = new ArrayList<User>();
lists.add(new User(6, "Alice"));
lists.add(new User(2, "Bob"));
lists.add(new User(3, "Charlie"));
lists.add(new User(1, "Alice"));
int sum = lists.stream().filter(u -> "Alice".equals(u.getName())).mapToInt(u -> u.getId()).sum();
System.out.println("Sum:" + sum);
// 7
5. Using flatMap
The flatMap method is used to map each element to a corresponding result (one-to-many).
Example: Get words from a sentence
String worlds = "The way of the future";
List<String> list7 = new ArrayList<>();
list7.add(worlds);
List<String> list8 = list7.stream().flatMap(str -> Stream.of(str.split(" ")))
.filter(world -> world.length() > 0).collect(Collectors.toList());
System.out.println("Words:");
list8.forEach(System.out::println);
// Words:
// The
// way
// of
// the
// future
6. Using limit
The limit method is used to obtain a stream with a specified number of elements.
Example 1: Get the first n elements
Random rd = new Random();
System.out.println("First three elements:");
rd.ints().limit(3).forEach(System.out::println);
// First three elements:
// 1167267754
// -1164558977
// 1977868798
Example 2: Using limit with skip
skip discards the first n elements.
List<User> list9 = new ArrayList<User>();
for (int i = 1; i < 4; i++) {
User user = new User(i, "pancm" + i);
list9.add(user);
}
System.out.println("Before truncation:");
List<String> list10 = list9.stream().map(User::getName).limit(3).skip(2).collect(Collectors.toList());
System.out.println("After truncation:" + list10);
// Before truncation:
// Name:pancm1
// Name:pancm2
// Name:pancm3
// After truncation:[pancm3]
Note: The getName method in the User entity prints the name.
7. Using sorted
The sorted method is used to sort the stream in ascending order.
Example 1: Random values and sort
Random rd2 = new Random();
System.out.println("First three sorted elements:");
rd2.ints().limit(3).sorted().forEach(System.out::println);
// First three sorted elements:
// -2043456377
// -1778595703
// 1013369565
Example 2: Optimized sorting
Tip: Sorting after
limitcan be more efficient.
// Normal sorting
List<User> list11 = list9.stream().sorted((u1, u2) -> u1.getName().compareTo(u2.getName())).limit(3)
.collect(Collectors.toList());
System.out.println("Sorted data:" + list11);
// Optimized sorting
List<User> list12 = list9.stream().limit(3).sorted((u1, u2) -> u1.getName().compareTo(u2.getName()))
.collect(Collectors.toList());
System.out.println("Optimized sorted data:" + list12);
// Sorted data:[{"id":1,"name":"pancm1"}, {"id":2,"name":"pancm2"}, {"id":3,"name":"pancm3"}]
// Optimized sorted data:[{"id":1,"name":"pancm1"}, {"id":2,"name":"pancm2"}, {"id":3,"name":"pancm3"}]
8. Using peek
peek performs an operation on each element and returns a new Stream.
Example: Double operations
System.out.println("Using peek:");
Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Before transformation: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("After transformation: " + e))
.collect(Collectors.toList());
// Before transformation: three
// After transformation: THREE
// Before transformation: four
// After transformation: FOUR
9. Using parallel
parallelStream is an alternative for parallel processing of streams.
Example: Count empty strings
List<String> strings = Arrays.asList("a", "", "c", "", "e", "", " ");
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();
System.out.println("Number of empty strings:" + count);
10. Using max/min/distinct
Example 1: Get max and min
List<String> list13 = Arrays.asList("zhangsan", "lisi", "wangwu", "xuwujing");
int maxLines = list13.stream().mapToInt(String::length).max().getAsInt();
int minLines = list13.stream().mapToInt(String::length).min().getAsInt();
System.out.println("Max length:" + maxLines + ", Min length:" + minLines);
// Max length:8, Min length:4
Example 2: Get distinct values
String lines = "good good study day day up";
List<String> list14 = new ArrayList<String>();
list14.add(lines);
List<String> words = list14.stream()
.flatMap(line -> Stream.of(line.split(" ")))
.filter(word -> word.length() > 0)
.map(String::toLowerCase)
.distinct()
.sorted()
.collect(Collectors.toList());
System.out.println("After distinct:" + words);
// After distinct:[day, good, study, up]
11. Using Match
allMatch: Returns true if all elements in the Stream match the predicate.anyMatch: Returns true if atleast one element in the Stream matches the predicate.noneMatch: Returns true if no element in the Stream matches the predicate.
Example:
boolean all = lists.stream().allMatch(u -> u.getId() > 3);
System.out.println("All greater than 3:" + all);
boolean any = lists.stream().anyMatch(u -> u.getId() > 3);
System.out.println("Any greater than 3:" + any);
boolean none = lists.stream().noneMatch(u -> u.getId() > 3);
System.out.println("None greater than 3:" + none);
// All greater than 3:false
// Any greater than 3:true
// None greater than 3:false
12. Using reduce
The reduce operation combines Stream elements.
Example 1: Concatenate strings
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
System.out.println("Concatenated string:" + concat);
Example 2: Get minimum value
double minValue = Stream.of(-4.0, 1.0, 3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
System.out.println("Minimum value:" + minValue);
// Minimum value:-4.0
Example 3: Sum
// Sum without identity
int sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
System.out.println("Sum without identity:" + sumValue);
// Sum with identity
sumValue = Stream.of(1, 2, 3, 4).reduce(1, Integer::sum);
System.out.println("Sum with identity:" + sumValue);
// Sum without identity:10
// Sum with identity:11
Example 4: Filter and concatenate
concat = Stream.of("a", "B", "c", "D", "e", "F")
.filter(x -> x.compareTo("Z") > 0)
.reduce("", String::concat);
System.out.println("Filtered and concatenated:" + concat);
// Filtered and concatenated:ace
13. Using iterate
iterateis similar toreduce. It takes a seed value and aUnaryOperator(e.g.,f). The seed becomes the first element of the stream,f(seed)the second,f(f(seed))the third, and so on. When usingiterate, the pipeline must have an operation likelimitto restrict the Stream size.
Example: Generate an arithmetic sequence
System.out.println("Generate an arithmetic sequence starting from 2:");
Stream.iterate(2, n -> n + 2).limit(5).forEach(x -> System.out.print(x + " "));
// Generate an arithmetic sequence starting from 2:
// 2 4 6 8 10
14. Using Supplier
Custom stream generation rules can be defined by implementing the Supplier interface.
Example: Random get two user information
System.out.println("Custom stream generation:");
Stream.generate(new UserSupplier()).limit(2).forEach(u -> System.out.println(u.getId() + ", " + u.getName()));
// First run:
// Custom stream generation:
// 10, pancm7
// 11, pancm6
// Second run:
// Custom stream generation:
// 10, pancm4
// 11, pancm2
// Third run:
// Custom stream generation:
// 10, pancm4
// 11, pancm8
class UserSupplier implements Supplier<User> {
private int index = 10;
private Random random = new Random();
@Override
public User get() {
return new User(index++, "pancm" + random.nextInt(10));
}
}
15. Using groupingBy/partitioningBy
groupingBy: Groups elements.partitioningBy: Partitions elements.
Example 1: Group by
System.out.println("Group by id:");
Map<Integer, List<User>> personGroups = Stream.generate(new UserSupplier2()).limit(5)
.collect(Collectors.groupingBy(User::getId));
Iterator it = personGroups.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, List<User>> persons = (Map.Entry) it.next();
System.out.println("id " + persons.getKey() + " = " + persons.getValue());
}
// Group by id:
// id 10 = [{"id":10,"name":"pancm1"}]
// id 11 = [{"id":11,"name":"pancm3"}, {"id":11,"name":"pancm6"}, {"id":11,"name":"pancm4"}, {"id":11,"name":"pancm7"}]
class UserSupplier2 implements Supplier<User> {
private int index = 10;
private Random random = new Random();
@Override
public User get() {
return new User(index % 2 == 0 ? index++ : index, "pancm" + random.nextInt(10));
}
}
Example 2: Partition by
System.out.println("Partition by age:");
Map<Boolean, List<User>> children = Stream.generate(new UserSupplier3()).limit(5)
.collect(Collectors.partitioningBy(p -> p.getId() < 18));
System.out.println("Children: " + children.get(true));
System.out.println("Adults: " + children.get(false));
// Partition by age:
// Children: [{"id":16,"name":"pancm7"}, {"id":17,"name":"pancm2"}]
// Adults: [{"id":18,"name":"pancm4"}, {"id":19,"name":"pancm9"}, {"id":20,"name":"pancm6"}]
class UserSupplier3 implements Supplier<User> {
private int index = 16;
private Random random = new Random();
@Override
public User get() {
return new User(index++, "pancm" + random.nextInt(10));
}
}
16. Using summaryStatistics
IntSummaryStatistics is a state object for collecting statistics such as count, min, max, sum, and average.
Example:
List<Integer> numbers = Arrays.asList(1, 5, 7, 3, 9);
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Max: " + stats.getMax());
System.out.println("Min: " + stats.getMin());
System.out.println("Sum: " + stats.getSum());
System.out.println("Average: " + stats.getAverage());
// Max: 9
// Min: 1
// Sum: 25
// Average: 5.0
This concludes the introduction to Stream. JDK 1.8's Stream API has many more features, and you are encouraged to explore the official documentation.
LocalDateTime
Introduction
JDK 1.8 not only introduced lambda expressions and Stream but also a brand new date/time API. Before JDK 1.8, Java's handling of dates, calendars, and time was widely criticized.
java.util.Datewas mutable, andSimpleDateFormatwas not thread-safe, severely limiting its use. Hence, thejava.timepackage was introduced, where all classes are immutable and thread-safe.
Key Classes
Instant: Represents an instantaneous point on the time-line.LocalDate: A date without time-of-day, formatyyyy-MM-dd.LocalTime: A time without date, formatHH:mm:ss.SSS.LocalDateTime: Combines date and time, but without time-zone information.ZonedDateTime: A date-time with a time-zone, including offset from UTC/Greenwich.
Usage
1. Getting the Current Date/Time
Use the static factory method now().
LocalDate nowDate = LocalDate.now();
LocalDateTime nowDateTime = LocalDateTime.now();
System.out.println("Current date:" + nowDate);
System.out.println("Current date/time:" + nowDateTime);
// Current date:2018-12-19
// Current date/time:2018-12-19T15:24:35.822
2. Getting Year, Month, Day, Hour, Minute, Second
Use get methods after obtaining the date/time object.
LocalDateTime ldt = LocalDateTime.now();
System.out.println("Year:" + ldt.getYear()); // 2018
System.out.println("Day of year:" + ldt.getDayOfYear()); // 353
System.out.println("Month:" + ldt.getMonthValue());
System.out.println("Hour:" + ldt.getHour());
System.out.println("Minute:" + ldt.getMinute());
System.out.println("Current time:" + ldt.toString());
// Year:2018
// Day of year:353
// Month:12
// Hour:15
// Minute:24
// Current time:2018-12-19T15:24:35.833
3. Formatting Time
Use DateTimeFormatter.
LocalDateTime ldt = LocalDateTime.now();
System.out.println("Formatted time: " + ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
// Formatted time:2018-12-19 15:37:47.119
4. Adding/Subtracting Time
Add or subtract years, months, days, hours, minutes, seconds.
LocalDateTime ldt = LocalDateTime.now();
System.out.println("After 5 days:" + ldt.plusDays(5));
System.out.println("Before 5 days formatted:" + ldt.minusDays(5).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
System.out.println("Before 1 month:" + ldt.minusMonths(1).format(DateTimeFormatter.ofPattern("yyyyMM")));
System.out.println("After 1 month:" + ldt.plusMonths(1));
System.out.println("With year 2099:" + ldt.withYear(2099));
// After 5 days:2018-12-24T15:50:37.508
// Before 5 days formatted:2018-12-14
// Before 1 month:201712
// After 1 month:2018-02-04T09:19:29.499
// With year 2099:2099-12-19T15:50:37.508
5. Creating a Specific Date/Time
Use of method.
LocalDate ld3 = LocalDate.of(2017, Month.NOVEMBER, 17);
LocalDate ld4 = LocalDate.of(2018, 02, 11);
6. Comparing Date/Time Differences
Compare differences in years, months, days, hours, minutes, seconds.
Example 1: Difference in years, months, days
LocalDate ld = LocalDate.parse("2017-11-17");
LocalDate ld2 = LocalDate.parse("2018-01-05");
Period p = Period.between(ld, ld2);
System.out.println("Years difference: " + p.getYears() + " Months difference: " + p.getMonths() + " Days difference: " + p.getDays());
// Years difference: 0 Months difference: 1 Days difference: 19
Note: Here, months are not a full year, and days are not a full month. The actual difference is 1 month and 19 days, i.e., 49 days.
Example 2: Total difference using ChronoUnit
ChronoUnit is a standard set of date period units.
LocalDate startDate = LocalDate.of(2017, 11, 17);
LocalDate endDate = LocalDate.of(2018, 01, 05);
System.out.println("Months difference:" + ChronoUnit.MONTHS.between(startDate, endDate));
System.out.println("Days difference:" + ChronoUnit.DAYS.between(startDate, endDate));
// Months difference:1
// Days difference:49
Note:
ChronoUnitcan also calculate differences in hours, minutes, and seconds.
Example 3: Precise time difference using Duration
Duration models time in seconds and nanoseconds.
Instant inst1 = Instant.now();
System.out.println("Current timestamp:" + inst1);
Instant inst2 = inst1.plus(Duration.ofSeconds(10));
System.out.println("After addition:" + inst2);
System.out.println("Difference in milliseconds:" + Duration.between(inst1, inst2).toMillis());
System.out.println("Difference in seconds:" + Duration.between(inst1, inst2).getSeconds());
// Current timestamp:2018-12-19T08:14:21.675Z
// After addition:2018-12-19T08:14:31.675Z
// Difference in milliseconds:10000
// Difference in seconds:10
Example 4: Comparing time
LocalDateTime ldt4 = LocalDateTime.now();
LocalDateTime ldt5 = ldt4.plusMinutes(10);
System.out.println("Is current time after?:" + ldt4.isAfter(ldt5));
System.out.println("Is current time before?:" + ldt4.isBefore(ldt5));
// false
// true
7. Time Zone Calculations
Get the time in other time zones.
Example 1: Using Clock class
Clock provides access to the current instant, date, and time using a time-zone.
Clock clock = Clock.systemUTC();
System.out.println("Current timestamp:" + clock.millis());
Clock clock2 = Clock.system(ZoneId.of("Asia/Shanghai"));
System.out.println("Timestamp in Asia/Shanghai:" + clock2.millis());
Clock clock3 = Clock.system(ZoneId.of("America/New_York"));
System.out.println("Timestamp in America/New_York:" + clock3.millis());
// Current timestamp:1545209277657
// Timestamp in Asia/Shanghai:1545209277657
// Timestamp in America/New_York:1545209277658
Example 2: Using ZonedDateTime and ZoneId
ZoneId zoneId = ZoneId.of("America/New_York");
ZonedDateTime dateTime = ZonedDateTime.now(zoneId);
System.out.println("Current time in New York:" + dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
System.out.println("Current time in New York with zone:" + dateTime);
// Current time in New York:2018-12-19 03:52:22.494
// Current time in New York with zone:2018-12-19T03:52:22.494-05:00[America/New_York]
Summary of Java 8 Date/Time API:
ZoneIdis used to get time zones.LocalDateandLocalTimeclasses are provided.- All date/time classes in Java 8 are immutable and thread-safe, unlike the mutable and non-thread-safe
java.util.DateandSimpleDateFormat. - The main package is
java.time, containing classes for date, time, and intervals. Two sub-packages arejava.time.formatfor formatting andjava.time.temporalfor lower-level operations. - A time zone represents a region where a standard time is used. Each zone has an ID, typically in the format
region/city(e.g.,Asia/Tokyo), along with an offset from Greenwich/UTC. For example, Tokyo's offset is+09:00. OffsetDateTimecombinesLocalDateTimeandZoneOffsetto represent a full date and time with an offset from Greenwich/UTC.DateTimeFormatteris used for formatting and parsing. UnlikeSimpleDateFormat, it is immutable and thread-safe. It provides many built-in formatters and allows custom formats. Theparse()method parses a string into a date, throwingDateTimeParseExceptionon error. Theformat()method formats a date, throwingDateTimeExceptionon error.- Note the subtle difference between date formats
"MMM d yyyy"and"MMM dd yyyy". The first can parse both"Jan 2 2014"and"Jan 14 2014", while the second throws an exception for"Jan 2 2014"because it requires two digits for the day. To fix this, single-digit days must be zero-padded (e.g.,"Jan 02 2014").