0% found this document useful (0 votes)
57 views

Java 8 Zero to Hero

The document is a comprehensive guide titled 'Java 8 Zero to Hero' by Md Kashif Ali, covering foundational concepts to advanced techniques in Java 8. It includes detailed explanations of new features such as the Date/Time API, Lambda Expressions, and Stream API, along with practical examples. The content is intended for educational purposes and emphasizes the importance of consulting professionals for specific advice.

Uploaded by

Gokul
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
57 views

Java 8 Zero to Hero

The document is a comprehensive guide titled 'Java 8 Zero to Hero' by Md Kashif Ali, covering foundational concepts to advanced techniques in Java 8. It includes detailed explanations of new features such as the Date/Time API, Lambda Expressions, and Stream API, along with practical examples. The content is intended for educational purposes and emphasizes the importance of consulting professionals for specific advice.

Uploaded by

Gokul
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 94

Java 8

ZERO TO HERO

Discover the complete journey of Java 8, from its foundational


concepts to advanced techniques, in this comprehensive guide.
Whether you're a novice programmer or an experienced developer,
this book will take you on an immersive journey through the latest
features and enhancements that Java 8 brings to the table.

B Y M D K A S H I F A L I
Copyrights
Title: Java 8 Zero to Hero by JavaScaler Author: Md Kashif Ali Copyright © 2023 by Md
Kashif Ali All rights reserved.

No part of this eBook may be reproduced, distributed, or transmitted in any form or by any
means, including photocopying, recording, or other electronic or mechanical methods,
without the prior written permission of the copyright holder, except in the case of brief
quotations embodied in critical reviews and certain other noncommercial uses permitted by
copyright law.

For permissions requests, contact: Email: [email protected] Website:


https://javascaler.com

This eBook, "Java 8 Zero to Hero," is the intellectual property of JavaScaler by Md Kashif
Ali. The content contained herein, including but not limited to text, graphics, images, and
code samples, is intended for educational purposes only and is provided "as is" without
warranties or guarantees of any kind. The author and JavaScaler shall not be liable for any
errors, omissions, or damages arising from the use of the information provided in this eBook.

Disclaimer:
The information presented in this eBook is based on the author's experience and research at
the time of writing. While every effort has been made to ensure the accuracy of the
information, technology and best practices evolve, and some information may become
outdated or subject to change. Readers are advised to verify the information and use their
discretion when applying it to their specific circumstances. The author and JavaScaler
disclaim any liability for any direct, indirect, incidental, consequential, or special damages
arising out of or in any way connected with the use of this eBook or the information
presented herein.

Please note that this eBook is not intended to replace professional advice. Readers should
consult with qualified experts or professionals in the relevant fields for specific advice or
guidance.

By reading and using this eBook, you agree to abide by the terms and conditions mentioned
herein. Unauthorized use or distribution of this eBook may be subject to legal action.

Thank you for respecting the intellectual property rights and supporting the author and
JavaScaler's efforts to provide valuable educational content.
Table of Contents

● Java Date and Time


● Lambda Expressions
● Method References
● Functional Interfaces
● Stream API
● Stream Filter
● Base64 Encode Decode
● Default Methods
● forEach() method
● Collectors class
● StringJoiner class
● Optional class
● JavaScript Nashorn
● Parallel Array Sort
● Type Inference
● Parameter Reflection
● Type Annotations
● JDBC Improvements
● Java 8 Security Enhancements
● Java 8 Tools Enhancements
● Pack200 Enhancements
● Java 8 I/O Enhancements
● Java 8 Networking Enhancements
● Java 8 Concurrency Enhancements
● Java API for XML Processing (JAXP) 1.6
Enhancements
● Java Virtual Machine Enhancements
Java 8 Zero to Hero
1. Explain Java Date and Time:
In the earlier versions of Java, handling date and time was cumbersome and error-
prone, leading to the introduction of the new Java 8 Date/Time API. The older API,
java.util.Date and java.util.Calendar , had several issues, such as being mutable,
not thread-safe, and lacking proper date and time manipulation methods. The new
Date/Time API in Java 8, located in the java.time package, aims to address these
problems by providing a more robust and user-friendly set of classes to handle date
and time-related operations.

The Java 8 Date/Time API is inspired by the popular Joda-Time library and adheres
to the ISO calendar system, which is the international standard for date and time
representation. It is designed to be immutable, thread-safe, and offers a rich set of
methods for manipulating, formatting, and parsing date and time values. The API
introduces new classes like LocalDate , LocalTime , LocalDateTime , and more, to
represent specific aspects of date and time without considering timezones.

2. Java 8 Date/Time API:


The java.time package in Java 8 contains a variety of classes and enums to handle
date and time effectively. Here's a list of some of the important classes and enums
along with a brief explanation:

2.1 Explain with an example of the following Java 8 Date and Time classes:
a. java.time.LocalDate class:

LocalDaterepresents a date (year, month, day) without any time or timezone


information. It is ideal for scenarios where timezones are not relevant.

Example:

import java.time.LocalDate;

public class LocalDateExample {


public static void main(String[] args) {
// Create a LocalDate representing 7th August 2023
LocalDate date = LocalDate.of(2023, 8, 7);
System.out.println("Date: " + date); // Output: Date: 2023-08-07
}
}

Java 8 Zero to Hero 1


b. java.time.LocalTime class:
LocalTime represents a time of day without any date or timezone information.

Example:

import java.time.LocalTime;

public class LocalTimeExample {


public static void main(String[] args) {
// Create a LocalTime representing 14:30:45
LocalTime time = LocalTime.of(14, 30, 45);
System.out.println("Time: " + time); // Output: Time: 14:30:45
}
}

c. java.time.LocalDateTime class:

LocalDateTime represents a date and time without timezone information.

Example:

import java.time.LocalDateTime;

public class LocalDateTimeExample {


public static void main(String[] args) {
// Create a LocalDateTime representing 7th August 2023, 14:30:45
LocalDateTime dateTime = LocalDateTime.of(2023, 8, 7, 14, 30, 45);
System.out.println("Date and Time: " + dateTime);
// Output: Date and Time: 2023-08-07T14:30:45
}
}

d. java.time.MonthDay class:

represents a specific day of a month (month and day) without any year or
MonthDay

timezone information.

Example:

import java.time.MonthDay;

public class MonthDayExample {


public static void main(String[] args) {
// Create a MonthDay for 7th August
MonthDay monthDay = MonthDay.of(8, 7);
System.out.println("Month and Day: " + monthDay); // Output: Month and Day: --
08-07
}
}

Java 8 Zero to Hero 2


e. java.time.OffsetTime class:

OffsetTimerepresents a time with an offset from UTC/Greenwich, which includes


information about both time and timezone offset.

Example:

import java.time.OffsetTime;
import java.time.ZoneOffset;

public class OffsetTimeExample {


public static void main(String[] args) {
// Create an OffsetTime representing 14:30:45 with a +02:00 offset (2 hours ah
ead of UTC)
OffsetTime offsetTime = OffsetTime.of(14, 30, 45, 0, ZoneOffset.ofHours(2));
System.out.println("Offset Time: " + offsetTime); // Output: Offset Time: 14:3
0:45+02:00
}
}

f. java.time.OffsetDateTime class:
OffsetDateTime represents a date and time with an offset from UTC/Greenwich,
including both date and time information and timezone offset.
Example:

import java.time.OffsetDateTime;
import java.time.ZoneOffset;

public class OffsetDateTimeExample {


public static void main(String[] args) {
// Create an OffsetDateTime representing 7th August 2023, 14:30:45 with a +02:
00 offset (2 hours ahead of UTC)
OffsetDateTime offsetDateTime = OffsetDateTime.of(2023, 8, 7, 14, 30, 45, 0, Z
oneOffset.ofHours(2));
System.out.println("Offset Date and Time: " + offsetDateTime);
// Output: Offset Date and Time: 2023-08-07T14:30:45+02:00
}
}

g. java.time.Clock class:

Clock provides access to the current date and time in a specific time zone. It's useful
for testing and situations where you need to work with a specific clock.

Example:

Java 8 Zero to Hero 3


import java.time.Clock;
import java.time.Instant;

public class ClockExample {


public static void main(String[] args) {
// Get the current date and time using the system default clock
Clock clock = Clock.systemDefaultZone();
Instant now = Instant.now(clock);
System.out.println("Current Date and Time: " + now);
}
}

h. java.time.ZonedDateTime class:
ZonedDateTime represents a date and time with full timezone information.
Example:

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class ZonedDateTimeExample {


public static void main(String[] args) {
// Create a LocalDateTime representing 7th August 2023, 14:30:45
LocalDateTime localDateTime = LocalDateTime.of(2023, 8, 7, 14, 30, 45);

// Create a ZonedDateTime for the given LocalDateTime in the "America/New_Yor


k" timezone
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of("Ameri
ca/New_York"));
System.out.println("Zoned Date and Time: " + zonedDateTime);
}
}

i. java.time.ZoneId class:

represents a time zone identifier, such as "America/New_York" or


ZoneId

"Europe/London".
Example:

import java.time.ZoneId;

public class ZoneIdExample {


public static void main(String[] args) {
// Get the ZoneId for "America/New_York"
ZoneId zoneId = ZoneId.of("America/New_York");
System.out.println("ZoneId: " + zoneId);

Java 8 Zero to Hero 4


}
}

j. java.time.ZoneOffset class:
ZoneOffset represents a fixed time zone offset from UTC/Greenwich, such as
"+02:00" or "-08:00".
Example:

import java.time.ZoneOffset;

public class ZoneOffsetExample {


public static void main(String[] args) {
// Get the ZoneOffset for +02:00 (2 hours ahead of UTC)
ZoneOffset zoneOffset = ZoneOffset.ofHours(2);
System.out.println("ZoneOffset: " + zoneOffset);
}
}

k. java.time.Year class:
Year represents a year without any timezone information.

Example:

import java.time.Year;

public class YearExample {


public static void main(String[] args) {
// Create a Year representing the year 2023
Year year = Year.of(2023);
System.out.println("Year: " + year); // Output: Year: 2023
}
}

l. java.time.YearMonth class:
YearMonth represents a specific year and month without any timezone information.

Example:

import java.time.YearMonth;

public class YearMonthExample {


public static void main(String[] args) {
// Create a YearMonth for August 2023
YearMonth yearMonth = YearMonth.of(2023, 8);
System.out.println("Year and Month: " + yearMonth); // Output: Year and Month:

Java 8 Zero to Hero 5


2023-08
}
}

m. java.time.Period class:
Period represents a period of time between two dates without considering
timezones.
Example:

import java.time.LocalDate;
import java.time.Period;

public class PeriodExample {


public static void main(String[] args) {
// Create two LocalDate instances
LocalDate date1 = LocalDate.of(2023, 8, 7);
LocalDate date2 = LocalDate.of(2024, 8, 7);

// Calculate the period between the two dates


Period period = Period.between(date1, date2);
System.out.println("Period between the two dates: " + period); // Output: Peri
od between the two dates: P1Y
}
}

n. java.time.Duration class:

represents a duration of time between two instants (timestamps) without


Duration

timezone information.
Example:

import java.time.Duration;
import java.time.Instant;

public class DurationExample {


public static void main(String[] args) {
// Create two Instant instances
Instant instant1 = Instant.parse("2023-08-07T00:00:00Z");
Instant instant2 = Instant.parse("2023-08-07T12:34:56Z");

// Calculate the duration between the two instants


Duration duration = Duration.between(instant1, instant2);
System.out.println("Duration between the two instants: " + duration); // Outpu
t: Duration between the two instants: PT12H34M56S
}
}

Java 8 Zero to Hero 6


o. java.time.Instant class:

Instantrepresents an instantaneous point on the timeline, usually used for machine-


readable timestamps.
Example:

import java.time.Instant;

public class InstantExample {


public static void main(String[] args) {
// Get the current Instant
Instant instant = Instant.now();
System.out.println("Current Instant: " + instant);
}
}

*p. `java.time.Day

OfWeek` enum:**

DayOfWeek represents the days of the week (Monday, Tuesday, etc.).

Example:

import java.time.DayOfWeek;

public class DayOfWeekExample {


public static void main(String[] args) {
// Get the DayOfWeek for Monday
DayOfWeek dayOfWeek = DayOfWeek.MONDAY;
System.out.println("Day of the week: " + dayOfWeek); // Output: Day of the wee
k: MONDAY
}
}

q. java.time.Month enum:

Month represents the months of the year (January, February, etc.).

Example:

import java.time.Month;

public class MonthExample {


public static void main(String[] args) {
// Get the Month for January
Month month = Month.JANUARY;
System.out.println("Month: " + month); // Output: Month: JANUARY

Java 8 Zero to Hero 7


}
}

These are some of the key classes and enums in the Java 8 Date/Time API. They
provide a flexible and comprehensive way to work with date and time in Java
applications.

3. Classical Date/Time API


Java 8 classical Date and Time classes.
a. java.util.Date class:

As mentioned earlier, the java.util.Date class represents a specific point in time,


including both date and time information. However, it has some drawbacks and is not
recommended for new code due to its mutable nature and other issues.
Example:

import java.util.Date;

public class UtilDateExample {


public static void main(String[] args) {
// Create a new Date representing the current time
Date date = new Date();
System.out.println("Current Date and Time: " + date);
}
}

b. java.sql.Date class:

The java.sql.Date class is a subclass of java.util.Date , but it represents only the


date portion (year, month, day) without any time information. It is often used in
database operations when working with SQL DATE values.
Example:

import java.sql.Date;

public class SqlDateExample {


public static void main(String[] args) {
// Create a new java.sql.Date representing 7th August 2023
Date sqlDate = Date.valueOf("2023-08-07");
System.out.println("SQL Date: " + sqlDate);
}
}

Java 8 Zero to Hero 8


c. java.util.Calendar class:

The java.util.Calendar class is an abstract class that provides methods to work with
dates and times, representing a calendar system. It is used to perform date
calculations and manipulation.

Example:

import java.util.Calendar;

public class CalendarExample {


public static void main(String[] args) {
// Get the current date and time using the Calendar class
Calendar calendar = Calendar.getInstance();
System.out.println("Current Date and Time using Calendar: " + calendar.getTime
());
}
}

d. java.util.GregorianCalendar class:
The class is a specific implementation of the
java.util.GregorianCalendar

java.util.Calendar class, representing the Gregorian calendar, which is the most

widely used calendar system today.

Example:

import java.util.GregorianCalendar;

public class GregorianCalendarExample {


public static void main(String[] args) {
// Create a GregorianCalendar for 7th August 2023
GregorianCalendar gregorianCalendar = new GregorianCalendar(2023, 7, 7);
System.out.println("Gregorian Calendar Date: " + gregorianCalendar.getTime());
}
}

e. java.util.TimeZone class:
The java.util.TimeZone class represents a time zone, which is a region of the Earth
where the same standard time is used.

Example:

import java.util.TimeZone;

public class TimeZoneExample {


public static void main(String[] args) {

Java 8 Zero to Hero 9


// Get the default time zone of the system
TimeZone timeZone = TimeZone.getDefault();
System.out.println("Default Time Zone: " + timeZone.getDisplayName());
}
}

f. java.sql.Time class:

The java.sql.Time class represents a specific time of day (hours, minutes, seconds)
without any date information.

Example:

import java.sql.Time;

public class SqlTimeExample {


public static void main(String[] args) {
// Create a new java.sql.Time representing 14:30:45
Time sqlTime = Time.valueOf("14:30:45");
System.out.println("SQL Time: " + sqlTime);
}
}

g. java.sql.Timestamp class:

The java.sql.Timestamp class is a subclass of java.util.Date and represents a date


and time with nanosecond precision. It is often used in database operations when
working with SQL TIMESTAMP values.

Example:

import java.sql.Timestamp;

public class SqlTimestampExample {


public static void main(String[] args) {
// Create a new java.sql.Timestamp representing 7th August 2023, 14:30:45
Timestamp sqlTimestamp = Timestamp.valueOf("2023-08-07 14:30:45");
System.out.println("SQL Timestamp: " + sqlTimestamp);
}
}

Please note that while these examples illustrate the usage of the classical Date/Time
classes, it is advisable to use the Java 8 Date/Time API ( java.time package) for new
date and time-related development due to its improvements and advantages over
the classical API.

Java 8 Zero to Hero 10


4. Formatting Date and Time
a. java.text.DateFormat class:

The java.text.DateFormat class is an abstract class in Java that provides the ability to
format and parse dates and times according to a specific locale. It is used to display
dates and times in a human-readable format and parse user input back into date
objects.

Example:

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;

public class DateFormatExample {


public static void main(String[] args) {
// Create a Date object representing the current date and time
Date date = new Date();

// Get the default date format for the default locale and format the date
DateFormat dateFormat = DateFormat.getDateInstance();
String formattedDate = dateFormat.format(date);
System.out.println("Formatted Date: " + formattedDate);

// Get the default date and time format for the default locale and format the
date and time
DateFormat dateTimeFormat = DateFormat.getDateTimeInstance();
String formattedDateTime = dateTimeFormat.format(date);
System.out.println("Formatted Date and Time: " + formattedDateTime);
}
}

b. java.text.SimpleDateFormat class:

The java.text.SimpleDateFormat class is a concrete subclass of java.text.DateFormat ,


which allows you to create custom date and time formatting patterns. It provides a
more flexible way to format and parse dates and times according to specific patterns.

Example:

import java.text.SimpleDateFormat;
import java.util.Date;

public class SimpleDateFormatExample {


public static void main(String[] args) {
// Create a Date object representing the current date and time
Date date = new Date();

// Create a SimpleDateFormat with a custom date and time format pattern


SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

Java 8 Zero to Hero 11


// Format the date and time using the custom pattern
String formattedDateTime = sdf.format(date);
System.out.println("Formatted Date and Time: " + formattedDateTime);

// Parse the formatted date and time back to a Date object


try {
Date parsedDate = sdf.parse(formattedDateTime);
System.out.println("Parsed Date: " + parsedDate);
} catch (Exception e) {
System.out.println("Error parsing date: " + e.getMessage());
}
}
}

In the first example using java.text.DateFormat , we used getDateInstance() to get the


default date format for the default locale, and getDateTimeInstance() to get the default
date and time format. We then formatted the current date and time using these date
formats.

In the second example using java.text.SimpleDateFormat , we created a custom date


and time format pattern using the pattern string "yyyy-MM-dd HH:mm:ss". This
pattern specifies the format as year, month, day, hour, minute, and second with
separators. We used format() to format the current date and time according to the
custom pattern. We also demonstrated how to parse the formatted date and time
back into a Date object using the same pattern.

Both DateFormat and SimpleDateFormat classes are useful for formatting and parsing
dates and times in Java applications. However, SimpleDateFormat provides more
control over the formatting pattern, making it suitable for scenarios where you need
custom date and time representations.

Java Date and Time APIs


Classical Date Time API Classes
The primary classes before Java 8 release were:
1. java.lang.System:

The java.lang.System class provides the currentTimeMillis() method that returns the
current time in milliseconds from January 1st, 1970 (known as the Unix Epoch). This
method is often used for basic time tracking or performance measurement.
Example:

Java 8 Zero to Hero 12


public class SystemTimeExample {
public static void main(String[] args) {
long currentTimeMillis = System.currentTimeMillis();
System.out.println("Current Time in milliseconds: " + currentTimeMillis);
}
}

2. java.util.Date:
The java.util.Date class represents a specific instant of time, with the unit of
millisecond precision. It has several constructors and methods for working with dates
and times. However, it's important to note that this class has several issues, such as
being mutable and not being thread-safe.
Example:

import java.util.Date;

public class UtilDateExample {


public static void main(String[] args) {
// Create a new Date representing the current date and time
Date date = new Date();
System.out.println("Current Date and Time: " + date);
}
}

3. java.util.Calendar:
The java.util.Calendar class is an abstract class that provides methods for
converting between instances and manipulating the calendar fields in different ways.
It allows working with dates, times, and timezones in a more flexible manner
compared to java.util.Date .
Example:

import java.util.Calendar;

public class CalendarExample {


public static void main(String[] args) {
// Get the current date and time using the Calendar class
Calendar calendar = Calendar.getInstance();
System.out.println("Current Date and Time: " + calendar.getTime());
}
}

4. java.text.SimpleDateFormat:

Java 8 Zero to Hero 13


The java.text.SimpleDateFormat class is used to format and parse dates in a
predefined manner or a user-defined pattern. It allows formatting java.util.Date
objects into strings and parsing strings into java.util.Date objects.
Example:

import java.text.SimpleDateFormat;
import java.util.Date;

public class SimpleDateFormatExample {


public static void main(String[] args) {
// Create a Date object representing the current date and time
Date date = new Date();

// Create a SimpleDateFormat with a custom date format pattern


SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

// Format the date and time using the custom pattern


String formattedDateTime = sdf.format(date);
System.out.println("Formatted Date and Time: " + formattedDateTime);

// Parse the formatted date and time back to a Date object


try {
Date parsedDate = sdf.parse(formattedDateTime);
System.out.println("Parsed Date: " + parsedDate);
} catch (Exception e) {
System.out.println("Error parsing date: " + e.getMessage());
}
}
}

5. java.util.TimeZone:
The java.util.TimeZone class represents a time zone offset and also takes into
account daylight savings. It allows converting between UTC (Coordinated Universal
Time) and the local time of a specific time zone.

Example:

import java.util.TimeZone;

public class TimeZoneExample {


public static void main(String[] args) {
// Get the default time zone of the system
TimeZone timeZone = TimeZone.getDefault();
System.out.println("Default Time Zone: " + timeZone.getDisplayName());
}
}

Java 8 Zero to Hero 14


These are the classical Date and Time API classes available in Java. However, it's
important to note that the Java 8 Date/Time API ( java.time package) is
recommended for new date and time-related development due to its improved
design, better performance, and more convenient methods.

Drawbacks of existing Date/Time API's


Let's illustrate the drawbacks of the existing Date/Time APIs (Date and Calendar)
and how the Java 8 Date/Time API overcomes them:

1. Thread safety:
Existing Date and Calendar classes are mutable, which means their values can be
modified after creation. This mutability makes them non-thread-safe, and if shared
among multiple threads, it can lead to concurrency issues and hard-to-debug
problems.
Example demonstrating the thread-unsafe nature of the existing Date class:

import java.util.Date;

public class DateThreadSafetyExample {


public static void main(String[] args) {
Date date = new Date(); // Current date and time
Runnable task = () -> {
// Simulating a task that accesses and modifies the shared Date object
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + date);
try {
Thread.sleep(1000); // Sleep for 1 second
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};

// Creating two threads that execute the same task


Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);

thread1.start();
thread2.start();
}
}

In the example above, two threads access the same Date object, and as a result,
they display the same date and time. Since Date is mutable, one thread can modify

Java 8 Zero to Hero 15


it while the other is accessing it, leading to incorrect output and potential
concurrency issues.

2. Bad API designing:


The existing Date and Calendar APIs do not provide a straightforward and intuitive
way to perform common date and time operations. For example, calculating the
difference between two dates, adding or subtracting days, or checking if a date falls
between a range requires complex calculations and manual coding.

Example demonstrating the complexity of date calculations using the existing


Calendar class:

import java.util.Calendar;

public class CalendarOperationsExample {


public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
calendar.set(2023, Calendar.AUGUST, 7);

// Adding 5 days to the date


calendar.add(Calendar.DAY_OF_MONTH, 5);

// Checking if the new date is before a specific date


Calendar targetDate = Calendar.getInstance();
targetDate.set(2023, Calendar.AUGUST, 15);
boolean isBefore = calendar.before(targetDate);

System.out.println("New date after adding 5 days: " + calendar.getTime());


System.out.println("Is new date before 15th August 2023? " + isBefore);
}
}

The code above demonstrates how to perform simple date calculations using the
existing Calendar class. The process involves manually specifying fields and using
constants for operations, making it less readable and more prone to mistakes.
3. Difficult time zone handling:

Handling time zones using the existing Date and Calendar classes can be error-
prone and cumbersome. Developers need to write custom logic to handle time zones
correctly, especially when converting between different time zones.
Example demonstrating manual time zone handling with the existing Date and
Calendar classes:

import java.util.Calendar;
import java.util.TimeZone;

Java 8 Zero to Hero 16


public class TimeZoneHandlingExample {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
calendar.set(2023, Calendar.AUGUST, 7, 12, 0);
// Set the date to 7th August 2023, 12:00 PM

TimeZone newYorkTimeZone = TimeZone.getTimeZone("America/New_York");


TimeZone londonTimeZone = TimeZone.getTimeZone("Europe/London");

// Convert the time to New York time zone


calendar.setTimeZone(newYorkTimeZone);
System.out.println("Time in New York: " + calendar.getTime());

// Convert the time to London time zone


calendar.setTimeZone(londonTimeZone);
System.out.println("Time in London: " + calendar.getTime());
}
}

In this example, we manually set the time zone for a Calendar object to New York
and London time zones and print the converted times. This process is cumbersome
and error-prone when handling multiple time zones and daylight saving time
changes.

To address these drawbacks, Java 8 introduced the new Date/Time API ( java.time
package), which provides immutable, thread-safe, and easy-to-use classes for date
and time operations. It offers methods to perform various date and time calculations,
automatic time zone handling, and better representation of periods and durations,
making it more intuitive and efficient for working with dates and times.

New Date Time API in Java 8


The new date API helps to overcome the drawbacks mentioned above with the
legacy classes. It includes the following classes:
1. java.time.LocalDate:

The java.time.LocalDate class represents a date in the ISO calendar (year, month,
day) and is used to handle date-only information without any time component.

Example:

import java.time.LocalDate;

public class LocalDateExample {


public static void main(String[] args) {
// Create a LocalDate representing 7th August 2023
LocalDate date = LocalDate.of(2023, 8, 7);

Java 8 Zero to Hero 17


System.out.println("Date: " + date); // Output: Date: 2023-08-07
}
}

2. java.time.LocalTime:
The java.time.LocalTime class deals with time-only information without any date
component. It is used to represent time of day, such as movie showtimes or library
opening hours.

Example:

import java.time.LocalTime;

public class LocalTimeExample {


public static void main(String[] args) {
// Create a LocalTime representing 14:30:45
LocalTime time = LocalTime.of(14, 30, 45);
System.out.println("Time: " + time); // Output: Time: 14:30:45
}
}

3. java.time.LocalDateTime:

The java.time.LocalDateTime class represents a date and time without any time zone
information. It is a combination of LocalDate and LocalTime .

Example:

import java.time.LocalDateTime;

public class LocalDateTimeExample {


public static void main(String[] args) {
// Create a LocalDateTime representing 7th August 2023, 14:30:45
LocalDateTime dateTime = LocalDateTime.of(2023, 8, 7, 14, 30, 45);
System.out.println("Date and Time: " + dateTime);
// Output: Date and Time: 2023-08-07T14:30:45
}
}

4. java.time.ZonedDateTime:
The java.time.ZonedDateTime class combines LocalDateTime with a specific time zone
information provided by ZoneId .
Example:

Java 8 Zero to Hero 18


import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class ZonedDateTimeExample {


public static void main(String[] args) {
// Create a LocalDateTime representing 7th August 2023, 14:30:45
LocalDateTime localDateTime = LocalDateTime.of(2023, 8, 7, 14, 30, 45);

// Create a ZonedDateTime for the given LocalDateTime in a specific time zone


(e.g., "Asia/Tokyo")
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("Asia/Tokyo"));
System.out.println("Zoned Date and Time: " + zonedDateTime);
}
}

5. java.time.OffsetTime:

The java.time.OffsetTime class represents a time with a corresponding time zone


offset from UTC/Greenwich, without an explicit time zone ID.
Example:

import java.time.OffsetTime;
import java.time.ZoneOffset;

public class OffsetTimeExample {


public static void main(String[] args) {
// Create an OffsetTime representing 14:30:45 with a +02:00 offset (2 hours ah
ead of UTC)
OffsetTime offsetTime = OffsetTime.of(14, 30, 45, 0, ZoneOffset.ofHours(2));
System.out.println("Offset Time: " + offsetTime);
}
}

6. java.time.OffsetDateTime:
The java.time.OffsetDateTime class represents a date and time with a corresponding
time zone offset from UTC/Greenwich, without an explicit time zone ID.

Example:

import java.time.OffsetDateTime;
import java.time.ZoneOffset;

public class OffsetDateTimeExample {


public static void main(String[] args) {
// Create an OffsetDateTime representing 7th August 2023, 14:30:45 with a +02:
00 offset (2 hours ahead of UTC)
OffsetDateTime offsetDateTime = OffsetDateTime.of(2023, 8, 7, 14, 30, 45, 0, Z

Java 8 Zero to Hero 19


oneOffset.ofHours(2));
System.out.println("Offset Date and Time: " + offsetDateTime);
}
}

7. java.time.Clock:
The java.time.Clock class provides access to the current instant, date, and time in
any given time zone. It's optional to use but allows you to test your code with
different time zones or a fixed clock for time-independent testing.
Example:

import java.time.Clock;
import java.time.Instant;

public class ClockExample {


public static void main(String[] args) {
// Get the current instant using the system default clock
Clock clock = Clock.systemDefaultZone();
Instant now = Instant.now(clock);
System.out.println("Current Date and Time: " + now);
}
}

8. java.time.Instant:
The java.time.Instant class represents an instantaneous point on the timeline,
typically used for machine-readable timestamps.

Example:

import java.time.Instant;

public class InstantExample {


public static void main(String[] args) {
// Get the current Instant
Instant instant = Instant.now();
System.out.println("Current Instant: " + instant

);
}
}

9. java.time.Duration:
The java.time.Duration class represents the difference between two instants in terms
of seconds or nanoseconds. It doesn't use date-based constructs like years and

Java 8 Zero to Hero 20


months but provides methods to convert to days, hours, and minutes.
Example:

import java.time.Duration;
import java.time.Instant;

public class DurationExample {


public static void main(String[] args) {
Instant start = Instant.now();

// Simulate some time-consuming operation


try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}

Instant end = Instant.now();

// Calculate the duration between the two instants


Duration duration = Duration.between(start, end);
System.out.println("Time taken: " + duration.toMillis() + " milliseconds");
}
}

10. java.time.Period:

The java.time.Period class represents the difference between dates in date-based


values like years, months, and days.

Example:

import java.time.LocalDate;
import java.time.Period;

public class PeriodExample {


public static void main(String[] args) {
LocalDate startDate = LocalDate.of(2023, 8, 1);
LocalDate endDate = LocalDate.of(2023, 8, 7);

// Calculate the period between two dates


Period period = Period.between(startDate, endDate);
System.out.println("Period: " + period);
}
}

11. java.time.ZoneId:
The java.time.ZoneId class represents a time zone identifier and provides rules for
converting between an Instant and a LocalDateTime .

Java 8 Zero to Hero 21


Example:

import java.time.ZoneId;
import java.time.ZonedDateTime;

public class ZoneIdExample {


public static void main(String[] args) {
// Get the current date and time in a specific time zone (e.g., "Europe/Pari
s")
ZoneId zoneId = ZoneId.of("Europe/Paris");
ZonedDateTime zonedDateTime = ZonedDateTime.now(zoneId);
System.out.println("Date and Time in Paris: " + zonedDateTime);
}
}

12. java.time.ZoneOffset:
The java.time.ZoneOffset class represents a time zone offset from UTC/Greenwich
time without explicit time zone rules.
Example:

import java.time.OffsetTime;
import java.time.ZoneOffset;

public class ZoneOffsetExample {


public static void main(String[] args) {
// Create an OffsetTime representing 14:30:45 with a +02:00 offset (2 hours ah
ead of UTC)
OffsetTime offsetTime = OffsetTime.of(14, 30, 45, 0, ZoneOffset.ofHours(2));
System.out.println("Offset Time: " + offsetTime);
}
}

13. java.time.format.DateTimeFormatter:

The java.time.format.DateTimeFormatter class provides various predefined formatters,


or you can define your own patterns. It is used for parsing and formatting date and
time values.
Example:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class DateTimeFormatterExample {


public static void main(String[] args) {
String dateStr = "2023-08-07";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

Java 8 Zero to Hero 22


// Parsing the date string into a LocalDate object
LocalDate date = LocalDate.parse(dateStr, formatter);
System.out.println("Parsed Date: " + date);

// Formatting the LocalDate into a custom date string


String formattedDate = date.format(formatter);
System.out.println("Formatted Date: " + formattedDate);
}
}

In this example, we used a custom date pattern "yyyy-MM-dd" to parse a date string
into a LocalDate object and then format the LocalDate object back into a custom date
string.
These new Date/Time API classes introduced in Java 8 offer improved functionality,
immutability, thread-safety, and more intuitive methods for working with dates, times,
and time zones. They provide a much better approach to handle date and time-
related operations compared to the legacy Date and Calendar classes.

Lambda Expressions
1. Lambda Expressions in detail with an example:
Lambda expressions in Java are a concise way to represent anonymous functions,
also known as "functional interfaces." They allow you to treat functions as method
arguments or code as data. Lambda expressions make the code more readable and
maintainable by reducing boilerplate code.
Lambda expressions have the following syntax:

(parameters) -> expression

or

(parameters) -> { statements; }

Example:

interface MathOperation {
int operate(int a, int b);
}

Java 8 Zero to Hero 23


public class LambdaExample {
public static void main(String[] args) {
// Using a lambda expression to implement the MathOperation interface
MathOperation addition = (a, b) -> a + b;
MathOperation subtraction = (a, b) -> a - b;
MathOperation multiplication = (a, b) -> a * b;
MathOperation division = (a, b) -> a / b;

int resultAdd = addition.operate(10, 5);


int resultSub = subtraction.operate(10, 5);
int resultMul = multiplication.operate(10, 5);
int resultDiv = division.operate(10, 5);

System.out.println("Addition: " + resultAdd); // Output: Addition: 15


System.out.println("Subtraction: " + resultSub); // Output: Subtraction: 5
System.out.println("Multiplication: " + resultMul); // Output: Multiplication:
50
System.out.println("Division: " + resultDiv); // Output: Division: 2
}
}

2. Functional Interface:
A functional interface is an interface that contains only one abstract method. It can
have any number of default or static methods, but as long as it has only one abstract
method, it is considered a functional interface. Functional interfaces are used as the
target types for lambda expressions and method references.

Example:

@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}

In the example above, the MathOperation interface is a functional interface because it


has only one abstract method operate .

3. Why use Lambda Expression:


Lambda expressions provide several benefits in Java programming:

Conciseness: Lambda expressions reduce the boilerplate code by allowing you


to represent a method implementation in a more concise and readable form.

Readability: Lambda expressions make the code more readable by focusing on


the logic rather than the mechanics of defining a method.

Java 8 Zero to Hero 24


Flexibility: Lambda expressions allow you to pass behavior as data, making
your code more flexible and adaptable.

Parallelism: Lambda expressions can be used with parallel processing to take


advantage of multi-core processors, leading to improved performance in certain
scenarios.

Functional Programming: Lambda expressions enable functional programming


concepts, such as treating functions as first-class citizens.

4. Java Lambda Expression Syntax (Write an example):

The general syntax for a lambda expression is as follows:

(parameters) -> expression

or

(parameters) -> { statements; }

Example:

interface Greeting {
void sayHello();
}

public class LambdaSyntaxExample {


public static void main(String[] args) {
// Using a lambda expression to implement the Greeting interface
Greeting greeting = () -> System.out.println("Hello, World!");
greeting.sayHello(); // Output: Hello, World!
}
}

5. Without Lambda Expression (Write an example):

Before Java 8, to implement a functional interface, you would typically use


anonymous inner classes. This approach involved more boilerplate code and was
less concise compared to using lambda expressions.

Example without lambda expressions:

interface Greeting {
void sayHello();

Java 8 Zero to Hero 25


}

public class WithoutLambdaExample {


public static void main(String[] args) {
// Using an anonymous inner class to implement the Greeting interface
Greeting greeting = new Greeting() {
@Override
public void sayHello() {
System.out.println("Hello, World!");
}
};
greeting.sayHello(); // Output: Hello, World!
}
}

6. Java Lambda Expression a simple Example:

Here's a simple example of a lambda expression:

public class SimpleLambdaExample {


public static void main(String[] args) {
// Using a lambda expression to implement the Runnable interface
Runnable task = () -> System.out.println("Executing a simple task.");
new Thread(task).start(); // Output: Executing a simple task.
}
}

In this example, we implemented the Runnable interface using a lambda expression


and used it to create a new thread.

7. Java Lambda Expression Example: No Parameter:


Lambda expressions can also be used without any parameters if the functional
interface method doesn't take any arguments.

interface Greeting {
void sayHello();
}

public class NoParameterLambdaExample {


public static void main(String[] args) {
// Using a lambda expression with no parameters to implement the Greeting inte
rface
Greeting greeting = () -> System.out.println("Hello, World!");
greeting.sayHello(); // Output: Hello, World!
}
}

8. Java Lambda Expression Example: Single Parameter:

Java 8 Zero to Hero 26


Lambda expressions can take one parameter if the functional interface method
requires it.

interface Greeting {
void greet(String name);
}

public class SingleParameterLambdaExample {


public static void main(String[] args) {
// Using a lambda expression with a single parameter to implement the Greeting
interface
Greeting greeting = name -> System.out.println("Hello, " + name + "!");
greeting.greet("Alice"); // Output: Hello, Alice!
greeting.greet("Bob"); // Output: Hello, Bob!
}
}

9. Java Lambda Expression Example: Multiple Parameters:


Lambda expressions can take multiple parameters if the functional interface method
requires them.

interface MathOperation {
int operate(int a, int b);
}

public class MultipleParametersLambdaExample {


public static void main(String[] args) {
// Using a lambda expression with multiple

parameters to implement the MathOperation interface


MathOperation addition = (a, b) -> a + b;
MathOperation subtraction = (a, b) -> a - b;

System.out.println("Addition: " + addition.operate(10, 5)); // Output: Additio


n: 15
System.out.println("Subtraction: " + subtraction.operate(10, 5)); // Output: S
ubtraction: 5
}
}

10. Java Lambda Expression Example: with or without the return keyword:

In a lambda expression, if the body contains a single expression, you can omit the
braces {} and the return keyword. The expression's value will be automatically
returned.

interface MathOperation {
int operate(int a, int b);

Java 8 Zero to Hero 27


}

public class LambdaWithReturnExample {


public static void main(String[] args) {
// Using a lambda expression with return keyword to implement the MathOperatio
n interface
MathOperation multiplication = (a, b) -> {
return a * b;
};
// Using a lambda expression without return keyword to implement the MathOpera
tion interface
MathOperation division = (a, b) -> a / b;

System.out.println("Multiplication: " + multiplication.operate(10, 5)); // Out


put: Multiplication: 50
System.out.println("Division: " + division.operate(10, 5)); // Output: Divisio
n: 2
}
}

11. Java Lambda Expression Example: Foreach Loop:

Lambda expressions are commonly used with the forEach method to iterate over
collections.

import java.util.ArrayList;
import java.util.List;

public class ForEachLambdaExample {


public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");

// Using a lambda expression with forEach to iterate over the list


names.forEach(name -> System.out.println("Hello, " + name + "!"));
// Output:
// Hello, Alice!
// Hello, Bob!
// Hello, Charlie!
}
}

12. Java Lambda Expression Example: Multiple Statements:


If a lambda expression's body contains multiple statements, you need to enclose
them in braces {} .

interface Greeting {
void sayHello();

Java 8 Zero to Hero 28


}

public class MultipleStatementsLambdaExample {


public static void main(String[] args) {
// Using a lambda expression with multiple statements to implement the Greetin
g interface
Greeting greeting = () -> {
System.out.println("Hello, ");
System.out.println("World!");
};
greeting.sayHello();
// Output:
// Hello,
// World!
}
}

13. Java Lambda Expression Example: Creating Thread:

Lambda expressions are commonly used to create threads in a concise manner.

public class ThreadLambdaExample {


public static void main(String[] args) {
// Using a lambda expression to create a new thread
new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread " + i);
}
}).start();
// Output:
// Thread 0
// Thread 1
// Thread 2
// Thread 3
// Thread 4
}
}

14. Java Lambda Expression Example: Comparator:


Lambda expressions are often used with the Comparator interface to sort collections.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ComparatorLambdaExample {


public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");

Java 8 Zero to Hero 29


// Using a lambda expression with Comparator to sort the list
Collections.sort(names, (name1, name2) -> name1.compareTo(name2));

names.forEach(System.out::println);
// Output:
// Alice
// Bob
// Charlie
}
}

15. Java Lambda Expression Example: Filter Collection Data:


Lambda expressions can be used with the filter method to filter collection data
based on a condition.

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class FilterLambdaExample {


public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");

// Using a lambda expression with filter to filter the list


List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());

filteredNames.forEach(System.out::println);
// Output:
// Alice
}
}

16. Java Lambda Expression Example: Event Listener:


Lambda expressions can be used to implement event listeners in GUI applications.

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class EventListenerLambdaExample {


public static void main(String[] args) {
JFrame frame = new JFrame("Event Listener Example");
JButton button = new JButton("Click Me");

Java 8 Zero to Hero 30


// Using a lambda expression as an event listener for the button
button.addActionListener((ActionEvent e) -> {
JOptionPane.showMessageDialog(frame, "Button clicked!");
});

frame.add(button);
frame.setSize(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}

In this example, we use a lambda expression as an event listener for the button click
event. When the button is clicked, a dialog box with the message "Button clicked!"
will be displayed.

Java Method References:


In Java 8, method references provide a shorthand notation for referring to methods
by their names. They are used to pass existing methods as arguments to functional
interfaces, making the code more concise and readable. Method references are
often used in the context of lambda expressions when the lambda expression simply
calls an existing method.
Types of Method References:

There are three types of method references in Java:

1. Reference to a static method.

2. Reference to an instance method.

3. Reference to a constructor.

In this explanation, we will focus on the first type: Reference to a static method.

Reference to a static method:


A reference to a static method is used to refer to a static method of a class. It is
denoted by the ClassName::staticMethodName syntax.

Example 1: Reference to a static method using a functional interface:

// Functional interface
interface Greeting {
void say();
}

Java 8 Zero to Hero 31


public class StaticMethodReferenceExample {
// Static method
public static void hello() {
System.out.println("Hello, world!");
}

public static void main(String[] args) {


// Using method reference to refer static method
Greeting greeting = StaticMethodReferenceExample::hello;
greeting.say(); // Output: Hello, world!
}
}

In this example, we have a functional interface Greeting with a single abstract


method say() . We also have a static method hello() in the
StaticMethodReferenceExample class. We refer to this static method using the method

reference StaticMethodReferenceExample::hello and assign it to a Greeting object.


When we call the say() method on the Greeting object, it calls the hello() method.
Example 2: Reference to a static method using the predefined functional
interface Runnable:

public class StaticMethodReferenceExample2 {


// Static method
public static void printMessage() {
System.out.println("This is a static method.");
}

public static void main(String[] args) {


// Using predefined functional interface Runnable to refer static method
Runnable runnable = StaticMethodReferenceExample2::printMessage;
new Thread(runnable).start(); // Output: This is a static method.
}
}

In this example, we have a static method printMessage() in the


StaticMethodReferenceExample2 class. We refer to this static method using the method

reference StaticMethodReferenceExample2::printMessage and assign it to a Runnable


object. When we start a new thread with the Runnable object, it calls the
printMessage() method.

Example 3: Reference to a static method using a predefined functional


interface (BiFunction):

import java.util.function.BiFunction;

public class StaticMethodReferenceExample3 {

Java 8 Zero to Hero 32


// Static method to add two integers
public static int add(int a, int b) {
return a + b;
}

public static void main(String[] args) {


// Using predefined functional interface BiFunction to refer static method
BiFunction<Integer, Integer, Integer> addFunction = StaticMethodReferenceExamp
le3::add;
int result = addFunction.apply(10, 5);
System.out.println("Addition: " + result); // Output: Addition: 15
}
}

In this example, we have a static method add() in the StaticMethodReferenceExample3


class that takes two integers as input and returns their sum. We refer to this static
method using the method reference StaticMethodReferenceExample3::add and assign it
to a BiFunction object. When we call the apply() method on the BiFunction object, it
calls the add() method and returns the result.

Example 4: Overriding static methods using method references:

public class StaticMethodReferenceExample4 {


// Static method to add two integers
public static int add(int a, int b) {
return a + b;
}

// Overloaded static method to add three integers


public static int add(int a, int b, int c) {
return a + b + c;
}

public static void main(String[] args) {


// Using method references to override static methods
BiFunction<Integer, Integer, Integer> addFunction1 = StaticMethodReferenceExam
ple4::add;
TriFunction<Integer, Integer, Integer, Integer> addFunction2 = StaticMethodRef
erenceExample4::add;

int result1 = addFunction1.apply(10, 5);


int result2 = addFunction2.apply(10, 5, 3);

System.out.println("Addition (2 parameters): " + result1); // Output: Addition


(2 parameters): 15

System.out.println("Addition (3 parameters): " + result2); // Output: Addition


(3 parameters): 18
}

// Custom functional interface for a three-argument function


interface TriFunction<T, U, V, R> {

Java 8 Zero to Hero 33


R apply(T t, U u, V v);
}
}

In this example, we have a static method add() in the StaticMethodReferenceExample4


class that takes two integers and returns their sum. We also have an overloaded
static method add() that takes three integers and returns their sum. We use method
references to override these static methods and assign them to functional interfaces
BiFunction and TriFunction . We then use these functional interfaces to call the

respective add() methods with different numbers of parameters.

Reference to an Instance Method:


In Java, you can also use method references to refer to instance (non-static)
methods of an object. The syntax for referring to an instance method is
object::instanceMethodName .

Example 1: Referring non-static methods using class object and anonymous


object:

class Greeting {
public void sayHello() {
System.out.println("Hello!");
}
}

public class InstanceMethodReferenceExample1 {


public static void main(String[] args) {
// Referring non-static method using class object
Greeting greeting = new Greeting();
Runnable runnable1 = greeting::sayHello;
new Thread(runnable1).start(); // Output: Hello!

// Referring non-static method using anonymous object


Runnable runnable2 = new Greeting()::sayHello;
new Thread(runnable2).start(); // Output: Hello!
}
}

In this example, we have a class Greeting with a non-static method sayHello() . We


refer to this instance method using method references with a class object greeting
and an anonymous object of the Greeting class.
Example 2: Referring an instance (non-static) method using the Runnable
interface:

Java 8 Zero to Hero 34


class Greeting {
public void sayHello() {
System.out.println("Hello!");
}
}

public class InstanceMethodReferenceExample2 {


public static void main(String[] args) {
Greeting greeting = new Greeting();

// Using Runnable interface to refer instance (non-static) method


Runnable runnable = greeting::sayHello;
new Thread(runnable).start(); // Output: Hello!
}
}

In this example, we have a class Greeting with a non-static method sayHello() . We


refer to this instance method using the Runnable interface.

Example 3: Referring an instance method using the predefined functional


interface (BiFunction):

import java.util.function.BiFunction;

class MathOperations {
public int add(int a, int b) {
return a + b;
}
}

public class InstanceMethodReferenceExample3 {


public static void main(String[] args) {
MathOperations mathOperations = new MathOperations();

// Using BiFunction interface to refer instance method


BiFunction<MathOperations, Integer, Integer> addFunction = MathOperations::ad
d;

int result = addFunction.apply(mathOperations, 10);


System.out.println("Addition: " + result); // Output: Addition: 15
}
}

In this example, we have a class MathOperations with an instance method add(int a,


int b) . We refer to this instance method using the BiFunction interface by passing

the object of the MathOperations class and the second parameter 10 as arguments to
the apply() method.

Java 8 Zero to Hero 35


Reference to a Constructor:
A reference to a constructor is used to refer to the constructors of a class. It is useful
when you want to create new instances of a class using a functional interface. The
syntax for referring to a constructor is ClassName::new .

Example 1: Using a functional interface to refer to a constructor:

class Person {
private String name;

public Person(String name) {


this.name = name;
}

public String getName() {


return name;
}
}

public class ConstructorReferenceExample1 {


public static void main(String[] args) {
// Using a functional interface to refer to the constructor
PersonFactory personFactory = Person::new;

Person person = personFactory.createPerson("Alice");


System.out.println("Name: " + person.getName()); // Output: Name: Alice
}
}

interface PersonFactory {
Person createPerson(String name);
}

In this example, we have a Person class with a constructor that takes a name
parameter. We define a functional interface PersonFactory with a single abstract
method createPerson(String name) . We then use the constructor reference Person::new
to refer to the constructor and create new instances of the Person class using the
createPerson method of the functional interface.

Example 2: Using a predefined functional interface to refer to a constructor:

import java.util.function.Function;

class Point {
private int x;
private int y;

public Point(int x, int y) {


this.x = x;

Java 8 Zero to Hero 36


this.y = y;
}

public int getX() {


return x;
}

public int getY() {


return y;
}
}

public class ConstructorReferenceExample2 {


public static void main(String[] args) {
// Using a predefined functional interface to refer to the constructor
Function<Integer, Point> pointFactory = Point::new;

Point point = pointFactory.apply(10, 20);


System.out.println("X: " + point.getX() + ", Y: " + point.getY()); // Output:
X: 10, Y: 20
}
}

In this example, we have a Point class with a constructor that takes x and y
coordinates as parameters. We use the predefined functional interface Function with
the constructor reference Point::new to refer to the constructor and create new
instances of the Point class using the apply method of the functional interface.

Example 3: Using an array constructor reference:

import java.util.function.Function;

public class ConstructorReferenceExample3 {


public static void main(String[] args) {
// Using an array constructor reference to create an array of integers
Function<Integer, int[]> arrayFactory = int[]::new;

int[] array = arrayFactory.apply(5);


System.out.println("Array length: " + array.length); // Output: Array length:
5
}
}

In this example, we use an array constructor reference int[]::new to create a new


array of integers. We define a functional interface Function with a single abstract
method that takes an integer as a parameter and returns an array of integers. When
we call the apply method of the functional interface with the parameter 5 , it creates
an array of length 5.

Java 8 Zero to Hero 37


Java Functional Interfaces in Java 8:
Functional interfaces in Java 8 are interfaces that have exactly one abstract method
and are used to support lambda expressions and method references. They enable
the development of functional programming approaches in Java, allowing you to
write more concise and readable code.

Example of Achieving Functional Programming Approach:

Let's consider a scenario where you want to calculate the square of each element in
a list using a functional programming approach:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class FunctionalProgrammingExample {


public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> squares = numbers.stream()


.map(x -> x * x)
.collect(Collectors.toList());

System.out.println("Original numbers: " + numbers);


System.out.println("Squares: " + squares);
}
}

In this example, we use the map operation of the Stream API to transform each
element in the numbers list by squaring it. This is a functional programming approach
that allows us to perform a transformation on each element without modifying the
original list.

Example of A Functional Interface with Methods of the Object Class:

A functional interface can have methods of the Object class, such as equals ,
hashCode , and toString . These methods are not counted as abstract methods when

determining if an interface is functional.

@FunctionalInterface
interface MyFunctionalInterface {
void doSomething();

// Methods from the Object class


boolean equals(Object obj);

int hashCode();

Java 8 Zero to Hero 38


String toString();
}

In this example, the MyFunctionalInterface interface is still considered functional


despite having methods from the Object class, because only the abstract methods
are counted.

Example of Invalid Functional Interface:

An invalid functional interface is one that contains more than one abstract method.
This violates the rule of having exactly one abstract method in a functional interface.

@FunctionalInterface
interface InvalidFunctionalInterface {
void method1();
void method2(); // This violates the rule of having only one abstract method
}

The InvalidFunctionalInterface is not valid as a functional interface because it


contains two abstract methods.

Example of a Functional Interface Extending a Non-Functional Interface:


A functional interface can extend a non-functional interface if the non-functional
interface has only one abstract method.

interface NonFunctionalInterface {
void doSomething();
}

@FunctionalInterface
interface MyFunctionalInterface extends NonFunctionalInterface {
// This interface is still functional
}

In this example, MyFunctionalInterface extends NonFunctionalInterface , which has only


one abstract method. Despite the inheritance, MyFunctionalInterface is still a
functional interface because it follows the rule of having exactly one abstract
method.

Java Predefined Functional Interfaces:

List of functional interfaces from the java.util.function package along with their
descriptions:

Interface Description

Java 8 Zero to Hero 39


Consumer<T> Accepts an argument of type T and performs an action on it.

Accepts two arguments of types T and U and performs an action


BiConsumer<T, U>
on them.
DoubleConsumer Accepts a double value and performs an action on it.
IntConsumer Accepts an int value and performs an action on it.
LongConsumer Accepts a long value and performs an action on it.
Function<T, R> Accepts an argument of type T and produces a result of type R .

Accepts two arguments of types T and U and produces a result of


BiFunction<T, U, R>
type R .
DoubleFunction<R> Accepts a double value and produces a result of type R .
IntFunction<R> Accepts an int value and produces a result of type R .
LongFunction<R> Accepts a long value and produces a result of type R .
ToDoubleFunction<T> Accepts an argument of type T and produces a double result.
ToIntFunction<T> Accepts an argument of type T and produces an int result.
ToLongFunction<T> Accepts an argument of type T and produces a long result.
Supplier<T> Produces a result of type T .
DoubleSupplier Produces a double result.
IntSupplier Produces an int result.
LongSupplier Produces a long result.

Accepts an argument of type T and produces a result of the same


UnaryOperator<T>
type T .

Accepts two arguments of type T and produces a result of the


BinaryOperator<T>
same type T .
DoubleUnaryOperator Accepts a double value and produces a double result.
IntUnaryOperator Accepts an int value and produces an int result.
LongUnaryOperator Accepts a long value and produces a long result.
Predicate<T> Accepts an argument of type T and returns a boolean result.

Accepts two arguments of types T and U and returns a boolean


BiPredicate<T, U>
result.
DoublePredicate Accepts a double value and returns a boolean result.
IntPredicate Accepts an int value and returns a boolean result.
LongPredicate Accepts a long value and returns a boolean result.
ObjDoubleConsumer<T>
Accepts an object of type T and a double value, and performs an

Java 8 Zero to Hero 40


action on them.

Accepts an object of type T and an int value, and performs an


ObjIntConsumer<T>
action on them.

Accepts an object of type T and a long value, and performs an


ObjLongConsumer<T>
action on them.
DoubleBinaryOperator Accepts two double values and produces a double result.
IntBinaryOperator Accepts two int values and produces an int result.
LongBinaryOperator Accepts two long values and produces a long result.
DoubleToIntFunction Accepts a double value and produces an int result.
DoubleToLongFunction Accepts a double value and produces a long result.
IntToDoubleFunction Accepts an int value and produces a double result.
IntToLongFunction Accepts an int value and produces a long result.
LongToDoubleFunction Accepts a long value and produces a double result.
LongToIntFunction Accepts a long value and produces an int result.

These functional interfaces provide a wide range of functionalities for working with
lambda expressions and functional programming in Java.

Java 8 Stream:
Java 8 introduced the Stream API, which is a sequence of elements that supports
various operations to perform functional-style processing of data in a more concise
and expressive way. Streams allow you to perform operations like filtering, mapping,
reduction, and more on collections of data.

Stream Features:

Pipelining: You can chain multiple operations together to form a pipeline, where
the output of one operation becomes the input of the next operation.

Lazy Evaluation: Stream operations are evaluated lazily, meaning they are
executed only when necessary, which can lead to better performance.

Internal Iteration: Streams handle iteration internally, which provides cleaner


and more declarative code.

Parallel Processing: Streams can be processed in parallel, utilizing multiple


cores of the CPU for improved performance on large datasets.

Java Stream Interface Methods:

Java 8 Zero to Hero 41


Here is a list of some important methods provided by the Stream interface in Java,
along with their descriptions:

Method Description

Returns a new stream containing elements that satisfy the given


filter(Predicate<T> p)
predicate p .

Returns a new stream containing the results of applying the given


map(Function<T, R> f)
function f to each element.
forEach(Consumer<T> c) Performs an action for each element of the stream.

reduce(BinaryOperator<T> Performs a reduction on the elements of the stream using the given
op)
binary operator op .
collect(Collector<T, A,
R> c)
Performs a mutable reduction operation using a collector c .

distinct() Returns a new stream with distinct elements.


sorted() Returns a new stream with elements sorted in natural order.
limit(long maxSize) Returns a new stream containing the first maxSize elements.
skip(long n) Returns a new stream that discards the first n elements.

anyMatch(Predicate<T> Returns true if any elements of the stream match the given
p)
predicate p .

allMatch(Predicate<T> Returns true if all elements of the stream match the given predicate
p)
p .

noneMatch(Predicate<T> Returns true if no elements of the stream match the given predicate
p)
p .

Returns an Optional containing the first element of the stream, or an


findFirst()
empty Optional if the stream is empty.

Returns an Optional containing any element of the stream, or an


findAny()
empty Optional if the stream is empty.

Java Example: Filtering Collection without using Stream:

import java.util.ArrayList;
import java.util.List;

public class WithoutStreamExample {


public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(5);
numbers.add(10);
numbers.add(15);
numbers.add(20);

List<Integer> evenNumbers = new ArrayList<>();

Java 8 Zero to Hero 42


for (Integer num : numbers) {
if (num % 2 == 0) {
evenNumbers.add(num);
}
}

System.out.println("Even numbers: " + evenNumbers); // Output: Even numbers:


[10, 20]
}
}

Java Stream Example: Filtering Collection by using Stream:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class WithStreamExample {


public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(5);
numbers.add(10);
numbers.add(15);
numbers.add(20);

List<Integer> evenNumbers = numbers.stream()


.filter(num -> num % 2 == 0)
.collect(Collectors.toList());

System.out.println("Even numbers: " + evenNumbers); // Output: Even numbers:


[10, 20]
}
}

These examples demonstrate how you can achieve the same result of filtering a
collection of numbers using the traditional approach and the Stream API.

Java Stream Iterating Example:

import java.util.stream.Stream;

public class StreamIterationExample {


public static void main(String[] args) {
Stream.iterate(1, n -> n + 1)
.limit(5)
.forEach(System.out::println);
}
}

Java Stream Example: Filtering and Iterating Collection:

Java 8 Zero to Hero 43


import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class FilteringAndIteratingExample {


public static void main(String[] args) {
List<Integer> numbers = Stream.of(5, 10, 15, 20)
.filter(num -> num % 2 == 0)
.collect(Collectors.toList());

numbers.forEach(System.out::println);
}
}

Java Stream Example: reduce() Method in Collection:

import java.util.stream.Stream;

public class ReduceExample {


public static void main(String[] args) {
Integer sum = Stream.of(1, 2, 3, 4, 5)
.reduce(0, (a, b) -> a + b);
System.out.println("Sum: " + sum); // Output: Sum: 15
}
}

Java Stream Example: Sum by using Collectors Methods:

import java.util.List;
import java.util.stream.Collectors;

public class SumExample {


public static void main(String[] args) {
List<Integer> numbers = List.of(5, 10, 15, 20);
int sum = numbers.stream()
.collect(Collectors.summingInt(Integer::intValue));
System.out.println("Sum: " + sum); // Output: Sum: 50
}
}

Java Stream Example: Find Max and Min Product Price:

import java.util.List;
import java.util.stream.Collectors;

class Product {
String name;
double price;

Java 8 Zero to Hero 44


public Product(String name, double price) {
this.name = name;
this.price = price;
}
}

public class MaxMinExample {


public static void main(String[] args) {
List<Product> products = List.of(
new Product("Product 1", 25.0),
new Product("Product 2", 15.0),
new Product("Product 3", 30.0)
);

double maxPrice = products.stream()


.mapToDouble(Product::price)
.max()
.getAsDouble();
double minPrice = products.stream()
.mapToDouble(Product::price)
.min()
.getAsDouble();

System.out.println("Max Price: " + maxPrice); // Output: Max Price: 30.0


System.out.println("Min Price: " + minPrice); // Output: Min Price: 15.0
}
}

Java Stream Example: count() Method in Collection:

import java.util.List;

public class CountExample {


public static void main(String[] args) {
List<Integer> numbers = List.of(5, 10, 15, 20);
long count = numbers.stream().count();
System.out.println("Count: " + count); // Output: Count: 4
}
}

Java Stream Example: Convert List into Set:

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class ConvertListToSetExample {


public static void main(String[] args) {
List<Integer> numbers = List.of(5, 10, 15, 20);
Set<Integer> set = numbers.stream().collect(Collectors.toSet());
System.out.println("Set: " + set);

Java 8 Zero to Hero 45


}
}

Java Stream Example: Convert List into Map:

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class Student {
String name;
int age;

public Student(String name, int age) {


this.name = name;
this.age = age;
}
}

public class ConvertListToMapExample {


public static void main(String[] args) {
List<Student> students = List.of(
new Student("Alice", 20),
new Student("Bob", 22),
new Student("Charlie", 21)
);

Map<String, Integer> studentMap = students.stream()


.collect(Collectors.toMap(
student -> student.name,
student -> student.age
));

System.out.println("Student Map: " + studentMap);


}
}

Method Reference in Stream:

Method references allow you to pass a method as a parameter to a higher-order


function like map , filter , etc. Here's a simple example:

import java.util.List;

public class MethodReferenceExample {


public static void main(String[] args) {
List<String> words = List.of("apple", "banana", "cherry");

// Using a lambda expression


words.stream().forEach(word -> System.out.println(word));

// Using a method reference

Java 8 Zero to Hero 46


words.stream().forEach(System.out::println);
}
}

Method references make your code more concise by directly referring to a method
that matches the functional interface's method signature.

Java Stream Filter:


The filter() method in the Java Stream API is used to filter elements of a stream
based on a given predicate. The predicate is a functional interface that defines a
single method that returns a boolean value. The filter() method applies this
predicate to each element in the stream and retains only those elements for which
the predicate returns true .

Signature:

Stream<T> filter(Predicate<? super T> predicate)

Parameters:

predicate: A functional interface representing the condition that each element of


the stream is tested against. The predicate should return true for the elements
that need to be included in the filtered stream.

Return:

Returns a new stream that contains elements that satisfy the given predicate.

Java Stream filter() Example (Fetching and Iterating Filtered Data):

import java.util.List;
import java.util.stream.Collectors;

public class StreamFilterExample {


public static void main(String[] args) {
List<Integer> numbers = List.of(5, 10, 15, 20, 25);

// Fetch and iterate filtered data


numbers.stream()
.filter(num -> num > 10) // Filter elements greater than 10
.forEach(System.out::println); // Print filtered elements
}
}

Java 8 Zero to Hero 47


In this example, the filter() method is used to retain only the elements that are
greater than 10. The forEach() method is then used to iterate and print these filtered
elements.
Java Stream filter() Example (Fetching Filtered Data as a List):

import java.util.List;
import java.util.stream.Collectors;

public class StreamFilterToListExample {


public static void main(String[] args) {
List<Integer> numbers = List.of(5, 10, 15, 20, 25);

// Fetch filtered data as a List


List<Integer> filteredNumbers = numbers.stream()
.filter(num -> num % 2 == 0) // Filter
even numbers
.collect(Collectors.toList()); // Colle
ct filtered elements into a List

System.out.println(filteredNumbers); // Output: [10, 20]


}
}

In this example, the filter() method is used to retain only the even numbers. The
collect() method with Collectors.toList() is then used to collect these filtered

elements into a new list.

The filter() method is a powerful tool for selecting elements based on a specified
condition, making it easier to work with collections of data in a more focused and
expressive way.

Java Base64 Encode and Decode:


Java provides the Base64 class in the java.util package to perform Base64
encoding and decoding operations. Base64 encoding is used to represent binary
data as an ASCII string, which is useful for tasks such as data storage or data
transmission over text-based protocols.

Basic Encoding and Decoding:

import java.util.Base64;

public class Base64Example {


public static void main(String[] args) {
String originalText = "Hello, Base64 Encoding and Decoding!";

// Encode the original text

Java 8 Zero to Hero 48


String encodedText = Base64.getEncoder().encodeToString(originalText.getBytes
());
System.out.println("Encoded Text: " + encodedText);

// Decode the encoded text


byte[] decodedBytes = Base64.getDecoder().decode(encodedText);
String decodedText = new String(decodedBytes);
System.out.println("Decoded Text: " + decodedText);
}
}

URL and Filename Encoding and Decoding:

import java.util.Base64;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Base64.Encoder;
import java.util.Base64.Decoder;

public class URLFilenameEncodingExample {


public static void main(String[] args) throws UnsupportedEncodingException {
String originalText = "URL and Filename Encoding and Decoding!";

// URL encode the original text


String encodedURLText = Base64.getUrlEncoder().encodeToString(originalText.get
Bytes(StandardCharsets.UTF_8));
System.out.println("Encoded URL Text: " + encodedURLText);

// Decode the URL encoded text


byte[] decodedURLBytes = Base64.getUrlDecoder().decode(encodedURLText);
String decodedURLText = new String(decodedURLBytes, StandardCharsets.UTF_8);
System.out.println("Decoded URL Text: " + decodedURLText);
}
}

MIME Encoding and Decoding:

import java.util.Base64;

public class MIMEEncodingExample {


public static void main(String[] args) {
String originalText = "MIME Encoding and Decoding!";

// MIME encode the original text


String encodedMIMEText = Base64.getMimeEncoder().encodeToString(originalText.g
etBytes());
System.out.println("Encoded MIME Text:\\n" + encodedMIMEText);

// Decode the MIME encoded text


byte[] decodedMIMEBytes = Base64.getMimeDecoder().decode(encodedMIMEText);
String decodedMIMEText = new String(decodedMIMEBytes);
System.out.println("Decoded MIME Text: " + decodedMIMEText);

Java 8 Zero to Hero 49


}
}

These examples demonstrate basic, URL and filename-safe, and MIME encoding
and decoding using the Base64 class. The output for each example will be the
encoded and decoded strings, respectively.

Nested Classes of Base64:

The Base64 class has two nested classes: Encoder and Decoder , which provide
methods to encode and decode data using different variants of Base64 encoding.

Base64 Methods:

Here are some methods provided by the Base64 class:

getEncoder() : Returns an instance of the Base64.Encoder class to perform Base64


encoding.

getDecoder() : Returns an instance of the Base64.Decoder class to perform Base64


decoding.

getUrlEncoder(): Returns an instance of the Base64.Encoder class with URL and


filename-safe encoding.

getUrlDecoder(): Returns an instance of the Base64.Decoder class with URL and


filename-safe decoding.

getMimeEncoder() : Returns an instance of the Base64.Encoder class with MIME


encoding.

getMimeDecoder() : Returns an instance of the Base64.Decoder class with MIME


decoding.

Base64.Encoder Methods:

Method Description
encodeToString(byte[]
src)
Encodes the given byte array and returns the encoded string.

encode(byte[] src) Encodes the given byte array and returns a byte array.
withoutPadding() Returns an encoder without padding characters.
wrap(OutputStream os) Returns an output stream that encodes data into this encoder.
encode(ByteBuffer buffer) Encodes the remaining content of the given buffer.

Base64.Decoder Methods:

Java 8 Zero to Hero 50


Method Description
decode(String src) Decodes the given string and returns the decoded byte array.
decode(byte[] src) Decodes the given byte array and returns a byte array.
wrap(InputStream is) Returns an input stream that decodes data from this decoder.
decode(ByteBuffer
buffer)
Decodes the remaining content of the given buffer.

These methods allow you to perform various forms of Base64 encoding and
decoding based on your needs.

If you run the provided examples, you will see the output demonstrating the
encoding and decoding operations for each case. The encoded text might differ as
the encoding process generates different strings each time due to the padding, so
the focus should be on the concept and process rather than the exact output.
Java Base64 Example: Basic Encoding and Decoding:

import java.util.Base64;

public class BasicBase64Example {


public static void main(String[] args) {
String originalText = "Hello, Base64 Encoding and Decoding!";

// Encode the original text


String encodedText = Base64.getEncoder().encodeToString(originalText.getBytes
());
System.out.println("Encoded Text: " + encodedText);

// Decode the encoded text


byte[] decodedBytes = Base64.getDecoder().decode(encodedText);
String decodedText = new String(decodedBytes);
System.out.println("Decoded Text: " + decodedText);
}
}

Output:

Encoded Text: SGVsbG8sIEJhc2U2NCBFbmNvZGluZyBhbmQgRGVjb2Rpbmch


Decoded Text: Hello, Base64 Encoding and Decoding!

Java Base64 Example: URL Encoding and Decoding:

import java.util.Base64;
import java.nio.charset.StandardCharsets;

public class URLBase64Example {

Java 8 Zero to Hero 51


public static void main(String[] args) {
String originalText = "URL and Filename Encoding and Decoding!";

// URL encode the original text


String encodedURLText = Base64.getUrlEncoder().encodeToString(originalText.get
Bytes(StandardCharsets.UTF_8));
System.out.println("Encoded URL Text: " + encodedURLText);

// Decode the URL encoded text


byte[] decodedURLBytes = Base64.getUrlDecoder().decode(encodedURLText);
String decodedURLText = new String(decodedURLBytes, StandardCharsets.UTF_8);
System.out.println("Decoded URL Text: " + decodedURLText);
}
}

Output:

Encoded URL Text: VVJMIHVuZCBGaW5hbWV0dGkgRW5jb2RpbmcgYW5kIERlY29kaW5nIQ==


Decoded URL Text: URL and Filename Encoding and Decoding!

Java Base64 Example: MIME Encoding and Decoding:

import java.util.Base64;

public class MIMEBase64Example {


public static void main(String[] args) {
String originalText = "MIME Encoding and Decoding!";

// MIME encode the original text


String encodedMIMEText = Base64.getMimeEncoder().encodeToString(originalText.g
etBytes());
System.out.println("Encoded MIME Text:\\n" + encodedMIMEText);

// Decode the MIME encoded text


byte[] decodedMIMEBytes = Base64.getMimeDecoder().decode(encodedMIMEText);
String decodedMIMEText = new String(decodedMIMEBytes);
System.out.println("Decoded MIME Text: " + decodedMIMEText);
}
}

Output:

Encoded MIME Text:


TUlNRSBFbmNvZGluZyBhbmQgRGVjb2RpbmcK

Decoded MIME Text: MIME Encoding and Decoding!

Java 8 Zero to Hero 52


These examples showcase the Basic, URL, and MIME encoding and decoding
operations along with their respective outputs. The encoded strings might vary
slightly due to padding and encoding variations.

Java Default Methods:


Default methods in Java 8 interfaces allow you to define methods with
implementations in interfaces. This feature was introduced to enable adding new
methods to interfaces without breaking the existing implementations of classes that
implement those interfaces.

Java Default Method Example:

interface Vehicle {
default void start() {
System.out.println("Vehicle is starting");
}

void accelerate();
}

class Car implements Vehicle {


@Override
public void accelerate() {
System.out.println("Car is accelerating");
}
}

public class DefaultMethodExample {


public static void main(String[] args) {
Car car = new Car();
car.start(); // Output: Vehicle is starting
car.accelerate(); // Output: Car is accelerating
}
}

Static Methods inside Java 8 Interface with Example:

interface MathOperations {
static int add(int a, int b) {
return a + b;
}

static int subtract(int a, int b) {


return a - b;
}
}

public class StaticMethodExample {


public static void main(String[] args) {

Java 8 Zero to Hero 53


int sum = MathOperations.add(5, 3);
int difference = MathOperations.subtract(10, 4);

System.out.println("Sum: " + sum); // Output: Sum: 8


System.out.println("Difference: " + difference); // Output: Difference: 6
}
}

Abstract Class vs Java 8 Interface with Example:

Abstract classes and interfaces both provide a way to achieve abstraction, but there
are differences:

1. Inheritance: An abstract class can be inherited by only one class, while an


interface can be implemented by multiple classes.

2. Fields: Abstract classes can have instance fields, whereas interface fields are
implicitly public, static, and final.

3. Default Methods: Abstract classes cannot have default methods, while


interfaces can have default methods with implementations.

4. Constructors: Abstract classes can have constructors, but interfaces cannot.

5. Multiple Inheritance: Java allows a class to implement multiple interfaces, but it


can only extend a single class.

Example demonstrating Abstract Class and Interface:

abstract class Shape {


abstract void draw();
}

interface Colorable {
void applyColor();
}

class Circle extends Shape implements Colorable {


@Override
void draw() {
System.out.println("Drawing a circle");
}

@Override
public void applyColor() {
System.out.println("Applying color to the circle");
}
}

public class AbstractVsInterfaceExample {


public static void main(String[] args) {
Circle circle = new Circle();

Java 8 Zero to Hero 54


circle.draw(); // Output: Drawing a circle
circle.applyColor(); // Output: Applying color to the circle
}
}

In this example, the Shape class is an abstract class with an abstract method draw() ,
and the Colorable interface has a method applyColor() . The Circle class extends
Shape and implements Colorable , showcasing the differences between abstract

classes and interfaces.

Java forEach Loop:


The forEach loop is used to iterate over elements in a collection or stream.

forEach() Signature in Iterable Interface:

void forEach(Consumer<? super T> action)

Java 8 forEach() Example: Iterating by Passing Lambda Expression:

import java.util.ArrayList;
import java.util.List;

public class ForEachExample {


public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");

// Using lambda expression


fruits.forEach(fruit -> System.out.println(fruit));
}
}

Output:

Apple
Banana
Cherry

Java 8 forEach() Example: Iterating by Passing Method Reference:

Java 8 Zero to Hero 55


import java.util.ArrayList;
import java.util.List;

public class ForEachMethodReferenceExample {


public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");

// Using method reference


fruits.forEach(System.out::println);
}
}

Output:

Apple
Banana
Cherry

Java Stream forEachOrdered() Method:

The forEachOrdered() method is used to iterate over elements in a stream while


preserving the order of the elements.

Signature:

void forEachOrdered(Consumer<? super T> action)

Java Stream forEachOrdered() Method Example:

import java.util.stream.Stream;

public class ForEachOrderedExample {


public static void main(String[] args) {
Stream.of("Apple", "Banana", "Cherry")
.forEachOrdered(System.out::println);
}
}

Output:

Apple
Banana

Java 8 Zero to Hero 56


Cherry

Java 8 forEachOrdered() Example: Iterating by Passing Lambda Expression and


Method Reference:

import java.util.stream.Stream;

public class ForEachOrderedLambdaMethodReferenceExample {


public static void main(String[] args) {
Stream.of("Apple", "Banana", "Cherry")
.forEachOrdered(fruit -> System.out.println(fruit)); // Using lambda exp
ression

Stream.of("Apple", "Banana", "Cherry")


.forEachOrdered(System.out::println); // Using method reference
}
}

Output:

Apple
Banana
Cherry
Apple
Banana
Cherry

These examples demonstrate the usage of forEach and forEachOrdered methods with
both lambda expressions and method references, along with their corresponding
outputs.

Java Collectors
The java.util.stream.Collectors class provides various methods that are used to
perform different reduction operations on streams and collect the results. It's
commonly used in combination with the Stream API to accumulate elements into
collections or perform various summarization operations.

Here's a table of commonly used methods in the Collectors class along with their
descriptions:

Method Description
toList() Collects the elements of a stream into a List .
toSet()
Collects the elements of a stream into a Set .

Java 8 Zero to Hero 57


toMap(keyMapper, Collects elements into a Map , using provided functions to map keys
valueMapper)
and values.

Collects the elements of a stream into a single String by joining


joining()
them with a delimiter.

summingInt() ,
summingLong() , Collects the sum of elements in the stream.
summingDouble()

averagingInt() ,
averagingLong() , Collects the average of elements in the stream.
averagingDouble()

counting() Counts the number of elements in the stream.

Collects elements into a Map where keys are determined by a


groupingBy(classifier)
classifier function.

Collects elements into a Map where keys are true and false
partitioningBy(predicate)
based on a predicate.

Java Collectors Example: Fetching Data as a List:

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CollectorsExample {


public static void main(String[] args) {
Stream<String> fruitsStream = Stream.of("Apple", "Banana", "Cherry");

List<String> fruitsList = fruitsStream.collect(Collectors.toList());


System.out.println(fruitsList); // Output: [Apple, Banana, Cherry]
}
}

Java Collectors Example: Converting Data as a Set:

import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CollectorsSetExample {


public static void main(String[] args) {
Stream<String> fruitsStream = Stream.of("Apple", "Banana", "Cherry");

Set<String> fruitsSet = fruitsStream.collect(Collectors.toSet());


System.out.println(fruitsSet); // Output: [Apple, Banana, Cherry]
}
}

Java 8 Zero to Hero 58


Java Collectors Example: Using summingInt() with Output:

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CollectorsSumExample {


public static void main(String[] args) {
Stream<Integer> numbersStream = Stream.of(1, 2, 3, 4, 5);

int sum = numbersStream.collect(Collectors.summingInt(Integer::intValue));


System.out.println("Sum: " + sum); // Output: Sum: 15
}
}

Java Collectors Example: Getting Product Average Price with Output:

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class Product {
String name;
double price;

public Product(String name, double price) {


this.name = name;
this.price = price;
}
}

public class CollectorsAverageExample {


public static void main(String[] args) {
Stream<Product> productsStream = Stream.of(
new Product("Apple", 2.0),
new Product("Banana", 1.0),
new Product("Cherry", 3.0)
);

double averagePrice = productsStream.collect(Collectors.averagingDouble(Produc


t::getPrice));
System.out.println("Average Price: " + averagePrice); // Output: Average Pric
e: 2.0
}
}

Java Collectors Example: Counting Elements with Output:

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Java 8 Zero to Hero 59


public class CollectorsCountExample {
public static void main(String[] args) {
Stream<String> fruitsStream = Stream.of("Apple", "Banana", "Cherry");

long count = fruitsStream.collect(Collectors.counting());


System.out.println("Count: " + count); // Output: Count: 3
}
}

These examples showcase the usage of various Collectors methods to perform


operations like collecting elements into lists, sets, calculating sums, averages, and
counting. The outputs provided are the actual results you would see when running
the respective examples.

Java StringJoiner
The java.util.StringJoiner class in Java is used to construct a sequence of
characters separated by a delimiter. It's particularly useful for joining elements of a
collection or an array into a single string.

StringJoiner Constructors:

1. StringJoiner(CharSequence delimiter) : Creates an empty StringJoiner with the


specified delimiter.

2. StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix) :


Creates an empty StringJoiner with the specified delimiter, prefix, and suffix.

StringJoiner Methods:

1. add(CharSequence newElement) : Adds a new element to the StringJoiner .

2. merge(StringJoiner other) : Merges the contents of another StringJoiner into this


one.

3. toString() : Returns the string representation of the StringJoiner sequence.

Java StringJoiner Example:

import java.util.StringJoiner;

public class StringJoinerExample {


public static void main(String[] args) {
StringJoiner joiner = new StringJoiner(", ");
joiner.add("Apple");
joiner.add("Banana");
joiner.add("Cherry");

Java 8 Zero to Hero 60


String result = joiner.toString();
System.out.println(result); // Output: Apple, Banana, Cherry
}
}

Java StringJoiner Example: Adding Prefix and Suffix:

import java.util.StringJoiner;

public class StringJoinerPrefixSuffixExample {


public static void main(String[] args) {
StringJoiner joiner = new StringJoiner(", ", "[", "]");
joiner.add("Apple");
joiner.add("Banana");
joiner.add("Cherry");

String result = joiner.toString();


System.out.println(result); // Output: [Apple, Banana, Cherry]
}
}

StringJoiner Example: Merge Two StringJoiner:

import java.util.StringJoiner;

public class StringJoinerMergeExample {


public static void main(String[] args) {
StringJoiner fruits1 = new StringJoiner(", ");
fruits1.add("Apple");
fruits1.add("Banana");

StringJoiner fruits2 = new StringJoiner(", ");


fruits2.add("Cherry");
fruits2.add("Orange");

fruits1.merge(fruits2);

String result = fruits1.toString();


System.out.println(result); // Output: Apple, Banana, Cherry, Orange
}
}

StringJoiner Example: StringJoiner Methods:

import java.util.StringJoiner;

public class StringJoinerMethodsExample {


public static void main(String[] args) {
StringJoiner joiner = new StringJoiner(", ");

Java 8 Zero to Hero 61


joiner.add("Apple");
joiner.add("Banana");
joiner.add("Cherry");

int length = joiner.length();


System.out.println("Length: " + length); // Output: Length: 22

String prefix = joiner.prefix();


System.out.println("Prefix: " + prefix); // Output: Prefix: [ (default)

String suffix = joiner.suffix();


System.out.println("Suffix: " + suffix); // Output: Suffix: ] (default)
}
}

These examples showcase the usage of StringJoiner with various constructors,


methods, and options for adding elements, prefix, suffix, and merging. The outputs
provided are the actual results you would see when running the respective
examples.

Java Optional Class


The java.util.Optional class in Java is used to deal with nullable values in a more
streamlined way. It provides methods to work with values that may or may not be
present, preventing null pointer exceptions.

Here's a table of commonly used methods in the Optional class along with their
descriptions:

Method Description
empty() Returns an empty Optional instance.

Creates an Optional with the specified value (which must be non-


of(T value)
null).

Creates an Optional with the specified value, or an empty Optional if


ofNullable(T value)
the value is null.
isPresent() Returns true if the value is present, otherwise false .
get() Returns the value if present, otherwise throws NoSuchElementException .

Returns the value if present, otherwise returns the specified default


orElse(T other)
value.

orElseGet(Supplier<? Returns the value if present, otherwise invokes the specified supplier
extends T> other)
and returns its result.
orElseThrow(Supplier<?
Returns the value if present, otherwise throws an exception provided
extends X>
exceptionSupplier) by the supplier.

Java 8 Zero to Hero 62


ifPresent(Consumer<?
If a value is present, performs the given action on it.
super T> action)

map(Function<? super T, If a value is present, returns an Optional describing the result of


? extends U> mapper)
applying a given function to the value.
flatMap(Function<?
If a value is present, applies the given function to it and returns the
super T, Optional<U>>
mapper) result, otherwise returns an empty Optional .

filter(Predicate<? If a value is present and meets the given predicate, returns an


super T> predicate)
Optional describing the value, otherwise returns an empty Optional .

Example: Java Program without Using Optional:

public class WithoutOptionalExample {


public static void main(String[] args) {
String name = null;
System.out.println(name.length()); // This line will throw NullPointerExceptio
n
}
}

Java Optional Example: If Value is not Present:

import java.util.Optional;

public class OptionalEmptyExample {


public static void main(String[] args) {
Optional<String> emptyOptional = Optional.empty();
System.out.println(emptyOptional.isPresent()); // Output: false
}
}

Java Optional Example: If Value is Present:

import java.util.Optional;

public class OptionalPresentExample {


public static void main(String[] args) {
String value = "Hello";
Optional<String> optional = Optional.of(value);
System.out.println(optional.isPresent()); // Output: true
}
}

Another Java Optional Example:

Java 8 Zero to Hero 63


import java.util.Optional;

public class AnotherOptionalExample {


public static void main(String[] args) {
String value = null;
Optional<String> optional = Optional.ofNullable(value);
String result = optional.orElse("Default Value");
System.out.println(result); // Output: Default Value
}
}

Java Optional Methods Example:

import java.util.Optional;

public class OptionalMethodsExample {


public static void main(String[] args) {
String value = "Hello";
Optional<String> optional = Optional.of(value);

optional.ifPresent(val -> System.out.println(val.toUpperCase())); // Output: H


ELLO

Optional<Integer> lengthOptional = optional.map(String::length);


lengthOptional.ifPresent(len -> System.out.println("Length: " + len)); // Outp
ut: Length: 5
}
}

These examples showcase the usage of the Optional class to handle nullable values
and demonstrate various methods for working with optionals. The outputs provided
are the actual results you would see when running the respective examples.

Java Nashorn
Nashorn is a JavaScript engine that comes with Java SE 8 and later versions. It
allows you to execute JavaScript code within Java applications, providing seamless
interoperability between Java and JavaScript. Nashorn improves the performance of
JavaScript execution compared to the earlier Rhino engine.

Example: Executing by Using Terminal:

To execute JavaScript code using Nashorn from the terminal, you can run the
following command:

jjs -scripting your-script.js

Java 8 Zero to Hero 64


Example: Executing JavaScript File in Java Code:

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class NashornExample {


public static void main(String[] args) throws ScriptException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("print('Hello, Nashorn');");
}
}

Example: Embedding JavaScript Code in Java Source File:

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class EmbeddedJavaScriptExample {


public static void main(String[] args) throws ScriptException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("var message = 'Hello from JavaScript'; print(message);");
}
}

Example: Embedding JavaScript Expression:

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class JavaScriptExpressionExample {


public static void main(String[] args) throws ScriptException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
String expression = "10 + 5";
Object result = engine.eval(expression);
System.out.println("Result: " + result); // Output: Result: 15
}
}

Heredocs:

Heredocs are a way to embed multiline strings within code without having to use
escape sequences.

Example: Heredocs in JavaScript File:

Java 8 Zero to Hero 65


var text = <<EOT
This is a multiline string.
It can span multiple lines.
EOT;

print(text);

Example: Setting JavaScript Variable in Java File:

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class JavaScriptVariableExample {


public static void main(String[] args) throws ScriptException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.put("name", "Alice");
engine.eval("print('Hello, ' + name);"); // Output: Hello, Alice
}
}

Import Java Package in JavaScript File:

Example 1: Import Java Package in JavaScript File:

var ArrayList = Java.type('java.util.ArrayList');


var list = new ArrayList();
list.add('Apple');
list.add('Banana');
print(list);

Example 2: Import Java Package in JavaScript File:

var ArrayList = Java.type('java.util.ArrayList');


var list = new ArrayList();
list.add('Java');
list.add('Scala');
list.add('JavaScript');
print(list);

Example 3: Import Java Package in JavaScript File (You can import multiple
packages at the same time):

var ArrayList = Java.type('java.util.ArrayList');


var HashMap = Java.type('java.util.HashMap');

Java 8 Zero to Hero 66


var list = new ArrayList();
var map = new HashMap();
list.add('Element 1');
list.add('Element 2');
map.put('Key', 'Value');
print(list);
print(map);

Calling JavaScript Function Inside Java Code:

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class JavaScriptFunctionExample {


public static void main(String[] args) throws ScriptException, NoSuchMethodExcepti
on {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("function greet(name) { return 'Hello, ' + name; }");

Invocable invocable = (Invocable) engine;


String result = (String) invocable.invokeFunction("greet", "Alice");
System.out.println(result); // Output: Hello, Alice
}
}

These examples demonstrate the usage of Nashorn to execute JavaScript code


within Java applications, along with various features like heredocs, importing Java
packages, and calling JavaScript functions from Java. The outputs provided are the
actual results you would see when running the respective examples.

Java Parallel Array Sorting


Parallel array sorting in Java is achieved using the Arrays class. It allows you to sort
arrays concurrently using multiple threads, which can significantly improve sorting
performance for large arrays.

Here's a table of commonly used overloaded sorting methods in the Arrays class
along with their descriptions:

Method Description
parallelSort(int[] a) Sorts the specified array of integers in parallel.
parallelSort(long[] a) Sorts the specified array of long values in parallel.
parallelSort(double[] a) Sorts the specified array of double values in parallel.
parallelSort(T[] a)

Java 8 Zero to Hero 67


Sorts the specified array of objects using their natural order in
parallel.

parallelSort(T[] a, Sorts the specified array of objects using the given


Comparator<T> c)
comparator in parallel.

Java Parallel Array Sorting Example with Output:

import java.util.Arrays;

public class ParallelArraySortingExample {


public static void main(String[] args) {
int[] numbers = {5, 2, 9, 1, 5, 6};
Arrays.parallelSort(numbers);

System.out.println(Arrays.toString(numbers)); // Output: [1, 2, 5, 5, 6, 9]


}
}

Java Parallel Array Sorting Example: Passing Start and End Index with Output:

import java.util.Arrays;

public class ParallelArraySortingRangeExample {


public static void main(String[] args) {
int[] numbers = {5, 2, 9, 1, 5, 6};
Arrays.parallelSort(numbers, 1, 4); // Sort only elements at index 1 to 3

System.out.println(Arrays.toString(numbers)); // Output: [5, 1, 2, 9, 5, 6]


}
}

These examples demonstrate how to use parallel array sorting in Java to efficiently
sort arrays using multiple threads. The outputs provided are the actual results you
would see when running the respective examples.

Java Type Inference


Type inference in Java is the ability of the compiler to automatically determine the
data type of a variable or expression based on the context in which it is used. It was
introduced in Java 7 and improved in Java 8.

Improved Type Inference:

Java 8 introduced improved type inference for generic instance creation, which
eliminates the need to specify type arguments explicitly.

Java 8 Zero to Hero 68


Java Type Inference Example:

import java.util.ArrayList;
import java.util.List;

public class TypeInferenceExample {


public static void main(String[] args) {
List<String> names = new ArrayList<>(); // Type inference for generic class
names.add("Alice");
names.add("Bob");
names.add("Carol");

String first = names.get(0);


System.out.println(first); // Output: Alice
}
}

Java Type Inference Example 2:

import java.util.function.Function;

public class TypeInferenceExample2 {


public static void main(String[] args) {
Function<Integer, String> converter = num -> String.valueOf(num); // Type infe
rence for lambda parameter and return type
String result = converter.apply(42);
System.out.println(result); // Output: 42
}
}

In these examples, type inference is demonstrated with the use of generic classes
( List<> ) and methods ( Function<> ). The outputs provided are the actual results you
would see when running the respective examples.

Method Parameter Reflection


Method parameter reflection is a feature in Java that allows you to inspect the
parameters of a method at runtime. It provides information about the parameters'
types, names, annotations, and more.

Method Class:

In Java, the Method class is part of the java.lang.reflect package. It represents a


method of a class or interface. The Method class provides methods to get information
about the method's modifiers, return type, parameter types, annotations, and more.

Java 8 Zero to Hero 69


Here's a table of commonly used methods in the Method class along with their
descriptions:

Method Description
String getName() Returns the name of the method.
Class<?> getReturnType() Returns the Class object representing the method's return type.

Class<?>[] Returns an array of Class objects representing the parameter


getParameterTypes()
types.
int getModifiers() Returns the modifiers for the method.

Annotation[] Returns an array of Annotation objects representing the


getAnnotations()
annotations applied to the method.

Object invoke(Object obj, Invokes the method on the specified object with the given
Object... args)
arguments and returns the result.

Parameter Class:
In Java, the Parameter class is part of the java.lang.reflect package. It represents a
parameter of a method or constructor. The Parameter class provides methods to get
information about the parameter's type, name, modifiers, and annotations.

Here's a table of commonly used methods in the Parameter class along with their
descriptions:

Method Description
String getName() Returns the name of the parameter.
Class<?> getType() Returns the Class object representing the parameter's type.
int getModifiers() Returns the modifiers for the parameter.

Annotation[] Returns an array of Annotation objects representing the annotations


getAnnotations()
applied to the parameter.

Java Method Parameter Reflection Example:

Let's say we have the following Java class named Javascaler_C :

public class Javascaler_C {


public void printMessage(String message, int count) {
for (int i = 0; i < count; i++) {
System.out.println(message);
}
}
}

Java 8 Zero to Hero 70


Now, let's use reflection to access method parameters:

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class ReflectionExample {


public static void main(String[] args) throws NoSuchMethodException {
Class<Javascaler_C> clazz = Javascaler_C.class;
Method method = clazz.getMethod("printMessage", String.class, int.class);

Parameter[] parameters = method.getParameters();


for (Parameter parameter : parameters) {
System.out.println("Parameter name: " + parameter.getName());
System.out.println("Parameter type: " + parameter.getType());
System.out.println("Parameter modifiers: " + parameter.getModifiers());
}
}
}

This example demonstrates how to use reflection to access method parameters


using the Method and Parameter classes. It outputs the parameter names, types, and
modifiers of the printMessage method in the Javascaler_C class. The outputs provided
are the actual results you would see when running the example.

Java Type and Repeating Annotations:


Java Type Annotations:

Java 8 introduced the concept of type annotations, which allows you to apply
annotations to various elements in your code, beyond just declarations. With type
annotations, you can apply annotations to types used in declarations, casts, and
other type contexts. This helps in expressing additional constraints, making your
code more self-explanatory and safer.

For example, consider the following type annotation that indicates a variable cannot
be null:

@NonNull String str;

This annotation specifies that the str variable cannot have a null value.

Few examples of how type annotations can be used in Java:

1. Using Type Annotations with Variables:

Java 8 Zero to Hero 71


import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.TYPE_USE)
@interface NonNull {}

public class TypeAnnotationsExample {


public static void main(String[] args) {
@NonNull String str = "Hello"; // NonNull annotation applied to variable decla
ration
System.out.println(str.toUpperCase());
}
}

2. Using Type Annotations with Generics:

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.TYPE_USE)
@interface Positive {}

public class GenericsTypeAnnotationsExample {


public static void main(String[] args) {
List<@Positive Integer> numbers = new ArrayList<>();
numbers.add(5);
numbers.add(10);
// numbers.add(-3); // Compiler error due to @Positive annotation
}
}

3. Using Type Annotations in Method Parameters:

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.TYPE_USE)
@interface NotEmpty {}

public class MethodParameterAnnotationsExample {


public static void printList(@NotEmpty List<String> list) {
list.forEach(System.out::println);
}

public static void main(String[] args) {


List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
printList(names);
// printList(new ArrayList<>()); // Compiler error due to @NotEmpty annotation

Java 8 Zero to Hero 72


}
}

4. Using Type Annotations in Throws Clause:

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.TYPE_USE)
@interface Checked {}

public class ThrowsClauseAnnotationsExample {


public static void riskyOperation() throws @Checked Exception {
throw new Exception("Risky operation");
}

public static void main(String[] args) {


try {
riskyOperation();
} catch (Exception e) {
System.out.println("Caught exception: " + e.getMessage());
}
}
}

These examples demonstrate the usage of type annotations in different contexts


such as variables, generics, method parameters, and throws clauses. The specific
annotations used ( @NonNull , @Positive , @NotEmpty , @Checked ) are fictional for
illustrative purposes. In real scenarios, you would use existing or custom annotations
that are meaningful for your application's requirements.

Java Repeating Annotations:


Repeating annotations allow you to apply the same annotation multiple times to a
single declaration or type use. In prior Java versions, an annotation could only be
applied once to a declaration. Repeating annotations provide a more concise and
cleaner way to express the same annotation multiple times.

Declare a Repeatable Annotation Type:


To create a repeatable annotation, you need to declare an annotation type and then
annotate it with @Repeatable with the container annotation type as its value.

import java.lang.annotation.Repeatable;

@Repeatable(Fruits.class)
public @interface Fruit {

Java 8 Zero to Hero 73


String name();
}

Declare the Containing Annotation Type:

The containing annotation type is used to hold multiple instances of the repeatable
annotation. It must have a value that is an array of the repeatable annotation type.

public @interface Fruits {


Fruit[] value();
}

Java Repeating Annotations Example:

@Fruit(name = "Apple")
@Fruit(name = "Orange")
public class FruitBasket {

public static void main(String[] args) {


Fruit[] fruits = FruitBasket.class.getAnnotationsByType(Fruit.class);
for (Fruit fruit : fruits) {
System.out.println("Fruit name: " + fruit.name());
}
}
}

In this example, the Fruit annotation is repeatable, and it is applied multiple times to
the FruitBasket class. The output will be:

Fruit name: Apple


Fruit name: Orange

This demonstrates how to use repeating annotations to apply the same annotation
multiple times to a single element. It's worth noting that these annotations can be
used in various contexts, such as variable declarations, parameter lists, throws
clauses, and more.

Java 8 JDBC Improvements


Java 8 introduced several improvements to the JDBC (Java Database Connectivity)
API, enhancing its capabilities and making it more user-friendly. These
improvements focused on simplifying the code, improving security, and adding new
features.

Java 8 Zero to Hero 74


1. The JDBC-ODBC Bridge Removal:

The JDBC-ODBC Bridge, which allowed Java applications to connect to databases


through the ODBC (Open Database Connectivity) interface, was removed in Java 8.
This decision was made to eliminate an unnecessary layer of abstraction and to
encourage developers to use native JDBC drivers for better performance and
reliability.

2. New Features in JDBC 4.2:

Java 8 introduced several new features and enhancements in the JDBC 4.2
specification:

Addition of REF_CURSOR Support: This feature allows JDBC to work with


REF_CURSOR parameters, which are often used in stored procedures to return
a result set. Now JDBC supports working with REF_CURSOR parameters more
effectively.

Addition of java.sql.DriverAction Interface: This interface was introduced to


provide a way for JDBC drivers to perform certain cleanup actions when they are
deregistered using the DriverManager.deregisterDriver method.

Addition of Security Check on deregisterDriver Method: A security check was


added to the DriverManager.deregisterDriver method to prevent potential SQL
injection attacks by ensuring that the driver's package name matches the
expected package.

Addition of the java.sql.SQLType Interface: This interface provides a way to


represent SQL data types, helping in better parameter binding and result set
handling.

Addition of the java.sql.JDBCType Enum: This enum provides a set of constants


that represent the standard SQL data types.

Add Support for Large Update Counts: The long data type is now supported
for update counts, allowing for larger values when working with databases that
return big update counts.

Changes to Existing Interfaces: The existing interfaces were enhanced to


incorporate the new features and improvements. For example, CallableStatement
now includes methods to handle REF_CURSOR parameters.

Rowset 1.2 Enhancements: The JDBC RowSet, which provides a


disconnected and serializable version of a JDBC result set, saw enhancements

Java 8 Zero to Hero 75


in JDBC 4.2. These enhancements improved the flexibility and capabilities of the
RowSet API.

These improvements in JDBC 4.2 make it easier for developers to work with
databases, handle stored procedures more effectively, and ensure better security in
database interactions.

DriverAction Method
The DriverAction interface is part of the JDBC 4.2 specification and is used to
perform cleanup actions when a JDBC driver is deregistered using the
DriverManager.deregisterDriver method. This interface provides a way for drivers to

clean up resources or perform necessary actions before being removed from the
driver manager.

The DriverAction interface has a single method:

void deregister();

When a driver implements the DriverAction interface and is registered with the driver
manager, the deregister method of that driver will be invoked when the driver is
deregistered.

Java JDBC 4.2 DriverAction Example:

Here's an example of how you can use the DriverAction interface to implement
custom cleanup logic when a JDBC driver is deregistered:

import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;

public class CustomDriverAction implements DriverAction {


@Override
public void deregister() {
// Custom cleanup logic
System.out.println("Driver deregistered! Performing cleanup...");
// Perform cleanup operations here
}

public static void main(String[] args) throws SQLException {


// Register the custom driver
DriverManager.registerDriver(new CustomDriver());

// Deregister the driver


DriverManager.deregisterDriver(new CustomDriver());

Java 8 Zero to Hero 76


}
}

In this example, we've defined a custom driver class CustomDriver that implements
the Driver interface and a CustomDriverAction class that implements the DriverAction
interface. When the driver is deregistered using DriverManager.deregisterDriver , the
deregister method in CustomDriverAction will be invoked, allowing you to perform any

necessary cleanup.

Java JDBC SQLType:

The java.sql.SQLType interface is part of the JDBC 4.2 specification and represents a
SQL type. It provides methods to get information about the SQL type, such as its
name and type code.

Java JDBC SQLType Example:

Here's an example of how you can use the SQLType interface to retrieve information
about a SQL type:

import java.sql.SQLType;
import java.sql.Types;

public class SQLTypeExample {


public static void main(String[] args) {
SQLType sqlType = Types.VARCHAR;

System.out.println("Type name: " + sqlType.getName()); // Output: VARCHAR


System.out.println("Type code: " + sqlType.getVendorTypeNumber()); // Output:
12 (VARCHAR type code)
}
}

In this example, we're using the constant Types.VARCHAR to represent the VARCHAR SQL
type. The getName method returns the name of the SQL type, and the
getVendorTypeNumber method returns the type code assigned by the database vendor

for the specific SQL type. The outputs provided are the actual results you would see
when running the example.

Java 8 Security Enhancements


Java 8 introduced significant security enhancements to improve the security of Java
applications and protect against potential vulnerabilities. These enhancements
aimed to strengthen the security framework, provide better protection against

Java 8 Zero to Hero 77


security threats, and make it easier for developers to write secure code. Here are
some of the key security enhancements introduced in Java 8:

1. TLS 1.2 Default:


In previous versions of Java, the default version of TLS (Transport Layer
Security) was TLS 1.0. Starting from Java 8, TLS 1.2 is the default protocol for
secure communication, providing stronger encryption and better security against
vulnerabilities present in older versions.

2. SSL/TLS Renegotiation Control:


Java 8 introduced improved control over SSL/TLS renegotiations. Renegotiation
allows two parties to change the security parameters of an active TLS
connection. However, it was identified as a potential attack vector. Java 8
provides better control over renegotiations to mitigate potential security risks.

3. Stronger Algorithm Default:


Java 8 adjusted the default cryptographic algorithms to use stronger ones. For
example, the default message digest algorithm was changed from MD5 to SHA-
256 for enhanced security.

4. Enhanced KeyStore Handling:


Java 8 introduced improvements to the handling of cryptographic keys and
KeyStores. The KeyStore class now supports more types of KeyStore entries,
making it easier to work with various types of keys and certificates.

5. Secure Random Improvements:


The SecureRandom class, used for generating cryptographically secure random
numbers, received enhancements to its algorithm selection. The NativePRNG
algorithm was introduced as a more secure option.

6. Key Agreements and Key Factories:


Java 8 improved the security APIs related to key agreements and key factories,
making it easier to generate, manipulate, and manage cryptographic keys and
agreements.

7. Enhanced Permissions and Access Control:


Java 8 refined the security manager and permissions mechanism to provide
better control over what code is allowed to do. This helps prevent untrusted code
from accessing sensitive operations and resources.

8. Code-Signing Enhancements:
Java 8 enhanced the code-signing process, ensuring that signed code is not

Java 8 Zero to Hero 78


tampered with and providing stronger protection against code injection and
malicious changes.

9. Deprecation of Cryptographic Algorithms:


Java 8 deprecated various cryptographic algorithms and mechanisms that were
identified as weak or insecure. This discourages the use of vulnerable algorithms
and promotes the use of more secure options.

10. Improvements in Security Documentation:


Java 8 provided improved and comprehensive documentation on security-
related topics, making it easier for developers to understand and implement
secure coding practices.

11. Security Enhancements in Libraries:


Java 8 also included security enhancements in various libraries and APIs,
addressing potential vulnerabilities and improving overall security posture.

These security enhancements in Java 8 aimed to strengthen the platform's security,


protect against common security vulnerabilities, and provide developers with the
tools and knowledge to write more secure code. Developers are encouraged to stay
updated with the latest security best practices and utilize the security features
provided by the Java platform to ensure the security of their applications.

Java 8 Tools Enhancements


Java 8 introduced several tools enhancements that aimed to improve development,
debugging, and profiling processes. These enhancements provided developers with
new tools, features, and improvements to streamline their workflow and make the
development experience more efficient. Here are some of the key tools
enhancements introduced in Java 8:

1. JVM Tool Interface (JVM TI) Enhancements:


The JVM Tool Interface, used by debugging and profiling tools to interact with
the JVM, received enhancements in Java 8. These enhancements improved the
performance and functionality of tools that rely on JVM TI, making it easier for
developers to diagnose and analyze code behavior.

2. jdeps Tool:
The jdeps tool was introduced in Java 8 to analyze and determine
dependencies between Java classes and packages. It helps developers identify
classes that are no longer needed, allowing for more efficient modularization and
reducing unnecessary dependencies.

Java 8 Zero to Hero 79


3. Java Flight Recorder (JFR):
Java Flight Recorder, initially a commercial feature, became available in Java
8u40 as part of the Oracle JDK. JFR is a profiling and event-collection
framework that captures runtime information about an application's behavior,
resource usage, and performance. It provides valuable insights into application
performance and can help diagnose issues.

4. jcmd Enhancements:
The jcmd utility, used to send diagnostic and control commands to a running
JVM, received several enhancements in Java 8. These enhancements added
new diagnostic commands and improved the overall usability of the tool.

5. Mission Control Integration:


Java Mission Control (JMC), a monitoring and performance analysis tool, was
integrated with the JDK starting from Java 8u40. JMC provides a visual interface
to analyze Java applications' performance, resource consumption, and behavior.

6. Nashorn Debugger:
Nashorn, the JavaScript engine in Java 8, included a debugger that allowed
developers to debug JavaScript code executed by Nashorn. This enhanced the
development experience for applications that involve JavaScript scripting.

7. javap Enhancements:
The javap tool, used to disassemble Java class files and view bytecode,
received enhancements in Java 8. These enhancements improved the
readability and usability of the tool's output.

8. Enhanced Troubleshooting Tools:


Java 8 introduced various enhancements to existing troubleshooting and
profiling tools, such as jconsole , jvisualvm , and jstack . These tools received
improvements in their user interfaces and functionality.

9. Parallel GC Enhancements:
The jstat tool, used for monitoring garbage collection (GC) statistics, received
enhancements to support parallel GC algorithms introduced in Java 8.

10. Java Mission Control (JMC) Plugin Support:


JMC gained the ability to support third-party plugins, allowing developers to
extend the tool's functionality and customize it to suit their specific profiling and
monitoring needs.

11. Enhanced Profiling Options:


Profiling tools in Java 8 were enhanced to provide more detailed information

Java 8 Zero to Hero 80


about memory usage, thread behavior, and CPU consumption, enabling
developers to pinpoint performance bottlenecks and optimize their applications.

These tools enhancements in Java 8 contributed to a more robust and developer-


friendly environment for building and debugging Java applications. They provided
developers with better insights into application behavior, improved performance
analysis capabilities, and streamlined the development and profiling processes.

Pack200 Enhancements
is a tool provided in the Java platform that is used for compressing and
pack200

decompressing JAR files. It uses a specific compression format called Pack200 to


achieve highly efficient compression of JAR files while preserving the Java class and
resource files contained within them. In Java 8, there were no significant changes
made to the pack200 tool itself, but it's important to understand its purpose and
usage.

Here are the key aspects of pack200 and its enhancements:

1. Pack200 Compression:
The primary purpose of the pack200 tool is to compress JAR files using the Pack200
compression format. This format is specifically designed to optimize the storage and
transmission of Java class files and resources within JAR files. The compression
format takes advantage of various techniques such as run-length encoding, entropy
coding, and delta encoding to achieve efficient compression ratios.

2. Improved Deployment Efficiency:


The primary use case for pack200 is for deploying Java applications over the internet.
By compressing JAR files using Pack200, developers can reduce the size of the JAR
files, which leads to faster download times and reduced network bandwidth usage.
This is particularly beneficial for web-based and applet-based Java applications.

3. Java Deployment Toolkit:


In addition to the pack200 tool, the Java platform provided the Java Deployment
Toolkit, which was used for optimizing the download of Java applications. The toolkit
combined the pack200 compression with other techniques to ensure smooth and
efficient deployment of Java applications.

4. Pack200 Enhancements in Java 8:


While there were no specific enhancements to the pack200 tool in Java 8, the focus
remained on maintaining and utilizing the tool's capabilities to ensure efficient JAR
file compression for deployment scenarios.

Java 8 Zero to Hero 81


5. Deployment to Modern Java Versions:
It's worth noting that with the introduction of Java 11 and later versions, the
deployment landscape has evolved, and technologies such as Java Web Start have
been deprecated. As a result, the usage of pack200 may have reduced in favor of
alternative deployment mechanisms.

6. Usage Considerations:
While pack200 was a useful tool for optimizing the deployment of Java applications in
the past, its relevance has diminished with changes in Java deployment practices.
Developers working with modern versions of Java are more likely to utilize other
packaging and deployment strategies that align with the evolving ecosystem.

In summary, while pack200 was a valuable tool for optimizing JAR file compression
and deployment in previous versions of Java, its significance has waned with
changes in deployment practices and the deprecation of certain deployment
technologies. Developers working with Java 8 and beyond should consider the
overall deployment strategy and explore modern alternatives for packaging and
distributing Java applications.

Here's a basic example of how to use the pack200 tool to compress a JAR file and
then decompress it back to its original form.

1. Compressing a JAR File:

Assuming you have a JAR file named myapp.jar , you can compress it using the
pack200 tool as follows:

pack200 myapp.pack.gz myapp.jar

This command will create a compressed file named myapp.pack.gz using the Pack200
compression format.

1. Decompressing a Compressed JAR File:

To decompress the compressed JAR file and obtain the original JAR file, you can
use the unpack200 tool:

unpack200 myapp.pack.gz myapp-decompressed.jar

This command will create a new JAR file named myapp-decompressed.jar , which is the
original JAR file that was compressed using the Pack200 format.

Java 8 Zero to Hero 82


Please note that these commands are provided as examples and may need
adjustments based on your specific environment and file paths. The pack200 and
unpack200 tools are typically available as part of the Java Development Kit (JDK)

installation.
Keep in mind that while pack200 was widely used in the past for deploying Java
applications, modern deployment practices have evolved, and alternative methods
such as creating self-contained executable JARs or using containerization
technologies may be more relevant in today's development landscape.

Java 8 I/O Enhancements


Java 8 introduced several enhancements to the I/O (Input/Output) framework,
providing developers with improved ways to handle file operations, manage
resources, and perform I/O operations efficiently. These enhancements aimed to
simplify I/O code, enhance performance, and enable more expressive and concise
code. Here are some of the key I/O enhancements introduced in Java 8:

1. java.nio.file Package:
The java.nio.file package was introduced in Java 7, but Java 8 further
enhanced its capabilities. This package provides a modern and flexible API for
file and directory operations. Some key features and enhancements include:

Stream-based I/O operations for reading and writing files.

Improved support for symbolic links and file attributes.

Enhanced file and directory walking with Files.walk , Files.find , and


Files.list methods.

Better exception handling with java.nio.file.FileAlreadyExistsException and


other specific exceptions.

2. Files.linesMethod:
The Files.lines method allows you to read lines from a file as a Stream<String> .
This makes it easier to process large files line by line using functional
programming constructs.

3. java.io and java.nio Compatibility:


Many methods in the java.io package were retrofitted to work with
java.nio.file.Path objects, allowing for better integration between the older

java.io and modern java.nio APIs.

Java 8 Zero to Hero 83


4. try-with-resources Enhancement:
Java 8 enhanced the try-with-resources statement by allowing resources to be
declared outside the try block. This makes code cleaner and more readable.

5. BufferedReader.lines Method:
The BufferedReader class now has a lines method that returns a Stream<String>
of lines from the buffered reader. This simplifies reading and processing lines
from a text file.

6. Enhanced InputStream and OutputStream :


New methods were added to InputStream and OutputStream interfaces, such as
readAllBytes , readNBytes , writeNBytes , and transferTo , making it easier to

perform I/O operations efficiently.

7. Enhanced RandomAccessFile :
The RandomAccessFile class received new methods like readFully , readFullyInto ,
and writeBytes , which simplify random access operations on files.

8. Enhanced FileReader and FileWriter :


The FileReader and FileWriter classes now have constructors that allow you to
specify the charset to be used for encoding and decoding text files.

9. java.util.zip Enhancements:
Java 8 introduced improvements to the java.util.zip package, such as the
ability to read and write ZIP files using streams ( ZipInputStream and
ZipOutputStream ).

10. New File-Related Methods in File Class:


The legacy File class received new methods like toPath , toURI , and
deleteIfExists for improved interoperability with the java.nio.file package.

11. Improved File System Watch Service:


The WatchService API, introduced in Java 7, was further enhanced to provide
more fine-grained control over monitoring file system events, such as file
modifications, creations, and deletions.

These I/O enhancements in Java 8 aimed to provide developers with more efficient
and expressive ways to work with files and perform I/O operations. The introduction
of the java.nio.file package, enhancements to existing classes, and integration with
the functional programming features of Java 8 resulted in a more robust and user-
friendly I/O framework.

Java 8 Zero to Hero 84


Java 8 Networking Enhancements
Java 8 introduced several enhancements to the networking capabilities of the Java
platform, providing developers with improved ways to work with sockets, URLs, and
network protocols. These enhancements aimed to simplify network-related tasks,
enhance performance, and enable more efficient and secure communication. Here
are some of the key networking enhancements introduced in Java 8:

1. java.util.stream and Network I/O:


The introduction of the Stream API in Java 8 allowed for more expressive and
functional programming approaches to network I/O operations. Streams could be
used to process data received from sockets or other network sources, making
code more concise and readable.

2. java.net.URLEnhancements:
The java.net.URL class received improvements, including:

The ability to open a connection to the URL and retrieve its content using the
openConnection method.

Support for try-with-resources when opening connections, ensuring proper


resource management and improved exception handling.

3. java.net.URI Enhancements:
The java.net.URI class also received enhancements to improve working with
Uniform Resource Identifiers (URIs), making it easier to manipulate URLs and
handle relative URIs.

4. Non-blocking I/O and java.nio.channels :


While not specific to Java 8, the java.nio.channels package was enhanced to
provide non-blocking I/O capabilities, allowing developers to perform
asynchronous I/O operations using channels.

5. Lambda Expressions and Networking:


Lambda expressions introduced in Java 8 could be used to simplify the handling
of network operations, such as defining callbacks for asynchronous operations
or specifying handlers for incoming network data.

6. Enhanced SSL/TLS Support:


The SSL/TLS support in Java was enhanced to provide better security and
performance. Updates were made to the supported ciphersuites, algorithms, and
protocols to align with modern security best practices.

Java 8 Zero to Hero 85


7. Enhanced Socket Channel and ServerSocketChannel:
The java.nio.channels.SocketChannel and java.nio.channels.ServerSocketChannel
classes were enhanced with new methods for more fine-grained control over
socket operations, making network programming more flexible.

8. java.net.CookieHandler and :
java.net.CookieManager

The java.net.CookieHandler interface and its default implementation


java.net.CookieManager were introduced to provide better control over HTTP

cookies during network communication.

9. IPv6 Improvements:
Java 8 improved support for IPv6 by enhancing network classes and providing
better integration with IPv6 addresses and networking features.

10. Enhanced URLDecoder and URLEncoder:


The java.net.URLDecoder and java.net.URLEncoder classes were improved to
provide better encoding and decoding of URLs and query parameters.

11. Networking Performance and Scalability:


While not explicitly tied to Java 8, various networking improvements made in the
platform focused on enhancing the performance and scalability of network-
related operations, ensuring efficient communication in various network
scenarios.

These networking enhancements in Java 8 aimed to provide developers with more


efficient and secure ways to work with network protocols, sockets, and URLs. The
combination of functional programming capabilities and modernized networking
classes contributed to a more robust and user-friendly networking framework.

Java 8 Concurrency Enhancements


Java 8 introduced several enhancements to the concurrency framework, providing
developers with improved tools and mechanisms to handle concurrent programming
tasks more efficiently and effectively. These enhancements aimed to simplify
concurrent programming, enhance performance, and enable better scalability. Here
are some of the key concurrency enhancements introduced in Java 8:

1. java.util.concurrentInterfaces:
The java.util.concurrent package was already available before Java 8, but Java
8 expanded the set of concurrency interfaces. These interfaces provide
abstractions for managing concurrency, such as synchronization, thread pooling,

Java 8 Zero to Hero 86


and parallelism. Examples include Executor , ExecutorService ,
ScheduledExecutorService , and more.

2. java.util.concurrent Classes:
Java 8 introduced several new classes in the java.util.concurrent package to
facilitate concurrent programming:

CompletableFuture : This class represents a future result of an asynchronous


computation and provides a flexible and fluent API for composing,
combining, and managing asynchronous tasks.

ConcurrentLinkedQueue: An efficient non-blocking queue implementation


suitable for high-concurrency scenarios.

CopyOnWriteArrayList : A thread-safe variant of ArrayList that maintains


multiple copies of the underlying array, allowing concurrent reading without
synchronization.

CountedCompleter: A specialized version of CompletableFuture that can be used


for ForkJoin tasks and parallel decomposition.

Many more classes that provide higher-level abstractions for common


concurrent patterns.

3. New Methods in ConcurrentHashMap class:


The ConcurrentHashMap class received new methods that enable more efficient
and convenient concurrent operations, such as forEach , compute , merge , and
reduce .

4. New Classes in java.util.concurrent.atomic :


The java.util.concurrent.atomic package, which provides atomic variables,
introduced new classes such as LongAccumulator and DoubleAccumulator , which
allow efficient accumulation of values across threads.

5. New Methods in ForkJoinPool Class:


The ForkJoinPool class received new methods that enable more fine-grained
control over parallel decomposition, work stealing, and task management. These
methods contribute to better performance in parallel processing tasks.

6. New Class java.util.concurrent.locks.StampedLock :


The StampedLock class provides a new type of lock that supports optimistic
reading and multiple modes of locking. It is designed to improve read-heavy
scenarios while maintaining efficient concurrent access.

Java 8 Zero to Hero 87


7. Enhanced CompletableFuture :
The CompletableFuture class introduced several methods that facilitate
asynchronous composition, exception handling, and combining multiple
asynchronous tasks, enabling developers to write more concise and
maintainable asynchronous code.

8. Enhanced BlockingQueue :
The BlockingQueue interface received new methods for bulk operations, such as
drainTo , that make it easier to manipulate queues in concurrent scenarios.

9. Enhancements to Executors:
The Executors class received new factory methods that produce instances of
ExecutorService and other related interfaces. This helps developers create thread

pools and executor services with various configurations.

These concurrency enhancements in Java 8 aimed to provide developers with more


powerful tools and abstractions for managing concurrent programming tasks. The
combination of new classes, methods, and improved APIs made it easier to write
efficient and thread-safe concurrent code, handle parallel processing, and achieve
better scalability in multi-core environments.

Java API for XML Processing (JAXP) 1.6


Enhancements
As of my last knowledge update in September 2021, there isn't a version called
"JAXP 1.6". The latest version I'm aware of is JAXP 1.5, which is part of Java SE 8.
However, I can provide information about the enhancements introduced in JAXP 1.5,
and you can let me know if you're referring to a different version.

Java API for XML Processing (JAXP) 1.5 Enhancements:


JAXP is a standard API for parsing and processing XML documents in Java. It
provides a set of interfaces and classes that enable developers to perform XML-
related operations such as parsing, transformation, validation, and querying. In Java
SE 8, JAXP 1.5 brought several enhancements to improve XML processing
capabilities. Some of these enhancements include:

1. StAX Enhancements:
The StAX (Streaming API for XML) was introduced in JAXP 1.5. StAX provides a
more efficient and flexible way to read and write XML documents compared to

Java 8 Zero to Hero 88


traditional DOM and SAX APIs. The StAX API includes two main interfaces:
XMLStreamReader (for reading XML) and XMLStreamWriter (for writing XML).

2. TrAX Enhancements:
TrAX (Transformation API for XML) was enhanced to provide better support for
XSLT transformations. It introduced the Templates interface, which encapsulates
a compiled XSLT stylesheet. This enhancement improves the performance of
XSLT transformations by allowing reusable compiled templates.

3. JAXP Shared Document Builder and Factory Pooling:


JAXP introduced a mechanism for pooling document builders and factories to
improve resource utilization and performance. This feature helps reduce the
overhead of creating and destroying instances of these objects repeatedly.

4. XML Schema 1.1 Support:


JAXP 1.5 added support for XML Schema 1.1, allowing developers to validate
XML documents against the latest version of the XML Schema specification.

5. XPath and XSLT Enhancements:


JAXP enhanced the XPath and XSLT APIs to provide more options and
capabilities for querying and transforming XML documents. This includes better
support for namespace handling and complex expressions.

6. Performance Improvements:
JAXP 1.5 included various performance optimizations to enhance the efficiency
of XML processing operations, making XML-based applications more
responsive.

Please note that my information is based on JAXP 1.5 as of September 2021. If


there have been subsequent versions released after that, I recommend checking the
official Java documentation or other reliable sources for the latest information on
JAXP enhancements.

Java Virtual Machine Enhancements


Java Virtual Machine (JVM) enhancements introduced in Java 8 focused on
improving performance, monitoring, and debugging capabilities. These
enhancements aimed to make Java applications more efficient, responsive, and
easier to manage. Here are some of the key JVM enhancements introduced in Java
8:

Java 8 Zero to Hero 89


1. Metaspace Memory Management:
Java 8 introduced the Metaspace memory management model to replace the
older Permanent Generation (PermGen) for class metadata storage. Metaspace
provides more flexibility and scalability for storing class metadata and
dynamically adjusts its size based on application needs. It helps mitigate the
issues related to PermGen space exhaustion and frequent OutOfMemoryErrors.

2. Compact Strings:
Java 8 introduced the concept of "compact strings," which optimized the memory
representation of strings. It uses one byte to represent characters from the Latin-
1 character set, improving memory efficiency for strings containing primarily
ASCII characters.

3. G1 Garbage Collector (Garbage-First):


The G1 Garbage Collector was introduced in Java 7 but further enhanced in
Java 8. G1 is a low-latency garbage collector that aims to provide predictable
and balanced performance for applications with varying heap sizes. It aims to
minimize garbage collection pauses by breaking the heap into regions and
prioritizing collections based on application behavior.

4. Java Flight Recorder (JFR):


Java Flight Recorder, originally a commercial feature, was included in Java 8 as
part of the HotSpot JVM. JFR allows continuous monitoring and recording of
various runtime events, such as method executions, memory allocations, and
thread activity. This feature is invaluable for diagnosing performance bottlenecks
and analyzing application behavior in production environments.

5. Java Mission Control (JMC):


Java Mission Control is a set of monitoring tools that leverage the data collected
by Java Flight Recorder. It provides a graphical interface for analyzing JFR data,
visualizing performance metrics, and diagnosing issues in real-time. JMC helps
developers and administrators gain insights into application behavior and
optimize performance.

6. Parallel Class Loading:


Java 8 introduced improvements in class loading by enabling parallel class
loading during application startup. This can lead to reduced class loading times
and improved application startup performance, especially in multi-core systems.

7. Code Caching (Tiered Compilation):


Java 8 introduced tiered compilation, which involves the use of multiple
compilers to optimize the performance of applications. Initially, methods are

Java 8 Zero to Hero 90


interpreted, and then they are compiled using both the client and server
compilers. This approach balances between startup and long-running
performance.

8. Lambda Forms and InvokeDynamic:


The introduction of lambda expressions in Java 8 relied on the invokedynamic
bytecode instruction to support dynamic linking of method calls. This
enhancement improved the performance of Java code that uses lambdas and
dynamic method invocation.

9. Enhancements in jcmd Tool:


Java 8 enhanced the jcmd tool for managing and diagnosing Java applications.
It provides a command-line interface for performing various runtime operations,
including thread dumps, heap dumps, and custom diagnostic commands.

These JVM enhancements in Java 8 aimed to provide developers with tools and
improvements to optimize application performance, monitor runtime behavior, and
diagnose issues more effectively. The combination of memory management
enhancements, garbage collection improvements, and monitoring capabilities
contributed to making Java applications more efficient and manageable.

Java 8 Zero to Hero 91

You might also like