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

Collection and Layered Architecture Notes

architecture notes for better understanding
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views

Collection and Layered Architecture Notes

architecture notes for better understanding
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 20

Java 8 introduced several significant features that changed how Java is

used, focusing on functional programming, stream processing, and better


handling of data and collections. Here’s a summary of the key features:

1. Lambda Expressions

 Purpose: Introduces functional programming by allowing you to


write code in a concise and functional style.

 Syntax:

(parameters) -> expression

Example:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

numbers.forEach(n -> System.out.println(n));

 Benefits: Simplifies the use of anonymous classes, especially with


collections and functional interfaces.

2. Functional Interfaces

 Purpose: A functional interface is an interface that has only one


abstract method. Commonly used with lambda expressions.

 Example:

@FunctionalInterface

interface MyFunctionalInterface {

void doSomething();

Some built-in functional interfaces:

o Predicate<T>

o Function<T, R>

o Supplier<T>

o Consumer<T>

3. Stream API

 Purpose: Provides a new way to process collections, using


declarative (rather than imperative) programming.

 Usage: You can chain operations like filter(), map(), and reduce(),
making operations on collections easier.
 Example:

List<String> names = Arrays.asList("John", "Jane", "Tom", "Mary");

List<String> filteredNames = names.stream()

.filter(name -> name.startsWith("J"))

.collect(Collectors.toList());

 Benefits: Facilitates parallel processing, functional-style operations


on sequences of elements, and optimized performance.

4. Default Methods ,static,Private methods in Interfaces

 Purpose: Allows adding default implementations in interfaces


without breaking the existing implementations of the interface.

 Example:

interface MyInterface {

default void printMessage() {

System.out.println("This is a default method");

 Benefits: Provides backward compatibility and adds flexibility to


interface design.

package sample;
public interface Shape {
public float calculateArea();
default void display() {
System.out.println("In inteface");
}
}

package sample;

public interface Shape {

float PI=3.14f;
public float calculateArea();

/*

* Include common functionalites

*/

default void describeShape() {

System.out.println("This is generic Shape");

static float getPi() {

//Without Object access this with in implemented class

return 3.14f;

private boolean getValidRange(){

//Internally do some evaluation

return true;

package sample;

import java.util.ArrayList;

import java.util.Arrays;

import java.util.Collections;

import java.util.Iterator;

import java.util.LinkedHashMap;

import java.util.LinkedHashSet;

import java.util.List;

import java.util.Map;
import java.util.Map.Entry;

import java.util.Scanner;

import java.util.Set;

import java.util.TreeMap;

import java.util.TreeSet;

public class Main {

public static void main(String[] args) {

Shape shape;

shape=new Circle();

shape.describeShape();

shape=new Square();

shape.describeShape();

5. Method References

 Purpose: Shorthand notation of a lambda expression to refer to


methods or constructors directly.

 Types:

o Static method reference (ClassName::staticMethod)

o Instance method reference (object::instanceMethod)

o Constructor reference (ClassName::new)

 Example:

List<String> names = Arrays.asList("John", "Jane", "Tom", "Mary");

names.forEach((str)->System.out.ptintln(str))

names.forEach(System.out::println); // Method reference

6. Optional Class

Purpose: The Optional class in Java, introduced in Java 8, is used to


represent a value that may or may not be present. It's a container
object that either contains a value or is empty. Using Optional helps
avoid NullPointerException by providing a more explicit way to handle
missing values.

 Usage:

Optional<String> optionalName = Optional.ofNullable(getName());

optionalName.ifPresent(System.out::println);

Methods: isPresent(), ifPresent(), orElse(), orElseGet(),


orElseThrow().

public class Main {


public static void main(String[] args) {
Optional<String> str=getName();

System.out.println(str.orElse("aaa"));
}
public static Optional<String> getName() {
String name=null;
Scanner input=new Scanner(System.in);
name=input.nextLine();
if(name.matches("[a-zA-Z ]{1,}")) {
name=name+" is valid ";
}else {
name=null;
}
Optional<String>
op=Optional.ofNullable(name);
return op;

}
}

Here are scenarios where Optional can be used, along with examples:

1. Returning a Value That May Be Absent

When a method might return a value or might return null, you can use
Optional to make this clear. Instead of returning null, you return an
empty Optional.

Example:
import java.util.Optional;

public class User {

private String email;

public User(String email) {

this.email = email;

public Optional<String> getEmail() {

return Optional.ofNullable(email); // If email is null,


Optional.empty() is returned.

public static void main(String[] args) {

User userWithMail = new User("[email protected]");

User userWithoutMail = new User(null);

// Handling optional email

System.out.println(userWithMail.getEmail().orElse("Email not
provided"));

System.out.println(userWithoutMail.getEmail().orElse("Email not
provided"));

2. Avoiding null Checks

Optional helps you avoid excessive null checks and handle the absence
of values more cleanly.

Example:

public class OptionalDemo {

public static void main(String[] args) {

Optional<String> optionalValue = getValue();

// Without Optional, you'd check for null

if (optionalValue.isPresent()) {

System.out.println("Value is: " + optionalValue.get());


} else {

System.out.println("Value is not present");

// Alternatively using ifPresent

optionalValue.ifPresent(value -> System.out.println("Value: " +


value));

public static Optional<String> getValue() {

return Optional.ofNullable(null); // or return


Optional.of("SomeValue");

3. Returning Default Values

You can provide a default value when the Optional is empty using the
orElse() or orElseGet() methods.

Example:

import java.util.Optional;

public class DefaultValueDemo {

public static void main(String[] args) {

Optional<String> optionalValue = Optional.ofNullable(null);

// Return default value if not present

String result = optionalValue.orElse("Default Value");

System.out.println(result); // Output: Default Value

// Using orElseGet with a supplier

String anotherResult = optionalValue.orElseGet(() -> "Computed


Default Value");

System.out.println(anotherResult); // Output: Computed Default


Value

}
}

5. Handling Empty Optionals with orElseThrow()

You can throw an exception when a value is not present by using the
orElseThrow() method.

Example:

import java.util.Optional;

public class OptionalOrElseThrowExample {

public static void main(String[] args) {

Optional<String> name = Optional.ofNullable(null);

// Throw exception if value not present

String result = name.orElseThrow(() -> new


IllegalArgumentException("Name not found"));

System.out.println(result);

6. Filtering Optionals

You can filter the value inside an Optional based on a condition, which
returns the original Optional if the condition is met or an empty
Optional otherwise.

Example:

import java.util.Optional;

public class OptionalFilterExample {

public static void main(String[] args) {

Optional<String> name = Optional.of("John");

// Filter the name by a condition

Optional<String> longName = name.filter(n -> n.length() > 5);

System.out.println(longName.isPresent()); // Output: false

Use Cases of Optional:


 Returning values from repositories or services: Avoid returning
null for absent database records or service results.

 Avoiding null checks: Instead of checking null for every potential


absence of value, use Optional to make the code more readable.

When Not to Use Optional:

 In fields or instance variables: Optional is not recommended as a


field type for objects. Instead, it should be used as a return type for
methods.

 For performance-sensitive code: While Optional improves


readability, it comes with overhead. In high-performance code
paths, its use may add unnecessary complexity.

Optional enhances code readability and reduces the chance of


NullPointerException but should be used judiciously.

7. Date and Time API (java.time package)

 Purpose: Replaces the old java.util.Date and java.util.Calendar with


a new, more powerful and flexible date-time API.

 Example:

LocalDate today = LocalDate.now();

LocalDate birthDate = LocalDate.of(1990, Month.JANUARY, 1);

Period age = Period.between(birthDate, today);

System.out.println(age.getYears());

 Classes:

o LocalDate

o LocalTime

o LocalDateTime

o ZonedDateTime

o Duration, Period

8. Collectors

 Purpose: Provides mechanisms to accumulate elements of streams


into collections or aggregate results.

 Example:

List<String> names = Arrays.asList("John", "Jane", "Tom", "Mary");


String result = names.stream()

.collect(Collectors.joining(", "));

System.out.println(result); // Output: John, Jane, Tom, Mary

Example 1: Collecting a List into a Map

Let's take a list of strings and create a map where the key is the string
itself, and the value is the length of the string.

import java.util.*;

import java.util.stream.Collectors;

public class CollectorsToMapExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("John", "Jane", "Tom", "Mary");

Map<String, Integer> nameLengthMap = names.stream()

.collect(Collectors.toMap(name -> name, name ->


name.length()));

System.out.println(nameLengthMap); // Output: {John=4, Jane=4,


Tom=3, Mary=4}

 Explanation: Each name in the list is mapped to its length. The


result is a map with names as keys and their lengths as values.

Example 2: Collecting Custom Objects into a Map

Let's take a list of custom Person objects and collect them into a map
where the key is the id and the value is the name.

import java.util.*;

import java.util.stream.Collectors;

class Person {

int id;

String name;
public Person(int id, String name) {

this.id = id;

this.name = name;

public int getId() {

return id;

public String getName() {

return name;

public class CollectorsToMapExample {

public static void main(String[] args) {

List<Person> people = Arrays.asList(

new Person(1, "John"),

new Person(2, "Jane"),

new Person(3, "Tom")

);

Map<Integer, String> personMap = people.stream()

.collect(Collectors.toMap(Person::getId, Person::getName));

System.out.println(personMap); // Output: {1=John, 2=Jane, 3=Tom}

}
 Explanation: The Person object's id is used as the key, and the
name is used as the value in the resulting map.

7. ForEach() Method

 Purpose: Provides a simple way to iterate over elements in a


collection or stream.

 Example:

List<String> names = Arrays.asList("John", "Jane", "Tom", "Mary");

names.forEach(name -> System.out.println(name));

Finding the Maximum or Minimum Value by a Custom Comparator

Scenario:

You have a list of Transaction objects, each with an amount. You want to
find the transaction with the highest amount.

import java.util.*;

import java.util.stream.Collectors;

class Transaction {

int id;

double amount;

public Transaction(int id, double amount) {

this.id = id;

this.amount = amount;

public int getId() {

return id;

public double getAmount() {

return amount;
}

public class AdvancedProcessing {

public static void main(String[] args) {

List<Transaction> transactions = Arrays.asList(

new Transaction(1, 500.0),

new Transaction(2, 1500.0),

new Transaction(3, 1000.0)

);

Optional<Transaction> maxTransaction = transactions.stream()

.max(Comparator.comparingDouble(Transaction::getAmount));

maxTransaction.ifPresent(tx -> System.out.println("Max Transaction


ID: " + tx.getId() + ", Amount: " + tx.getAmount()));

// Output: Max Transaction ID: 2, Amount: 1500.0

Explanation:

max(Comparator.comparingDouble()) is used to find the transaction with


the highest amount.Optional ensures that the result is safely handled,
especially when the collection might be empty.

3. Partitioning Data Based on a Predicate

Scenario:

You have a list of products, and you want to partition them into two
groups: one group for products that cost more than $100 and another for
products that cost less than or equal to $100.

import java.util.*;

import java.util.stream.Collectors;
class Product {

String name;

double price;

public Product(String name, double price) {

this.name = name;

this.price = price;

public class AdvancedProcessing {

public static void main(String[] args) {

List<Product> products = Arrays.asList(

new Product("Laptop", 1000.0),

new Product("Mouse", 25.0),

new Product("Keyboard", 50.0),

new Product("Monitor", 200.0)

);

Map<Boolean, List<Product>> partitioned = products.stream()

.collect(Collectors.partitioningBy(p -> p.price > 100));

System.out.println("Expensive products: " + partitioned.get(true));

System.out.println("Affordable products: " + partitioned.get(false));

Explanation:
partitioningBy() splits the collection into two lists based on whether the
condition price > 100 is true or false.

4. FlatMapping Complex Nested Structures

Scenario:

You have a Department class that contains a list of Employee objects. You
want to extract all unique employee names from all departments.

import java.util.*;

import java.util.stream.Collectors;

class Employee {

String name;

public Employee(String name) {

this.name = name;

class Department {

String name;

List<Employee> employees;

public Department(String name, List<Employee> employees) {

this.name = name;

this.employees = employees;

public class AdvancedProcessing {

public static void main(String[] args) {

List<Employee> dept1Employees = Arrays.asList(new


Employee("John"), new Employee("Jane"));
List<Employee> dept2Employees = Arrays.asList(new
Employee("Tom"), new Employee("John"));

List<Department> departments = Arrays.asList(

new Department("HR", dept1Employees),

new Department("Finance", dept2Employees)

);

Set<String> employeeNames = departments.stream()

.flatMap(dept -> dept.employees.stream())

.map(emp -> emp.name)

.collect(Collectors.toSet());

System.out.println(employeeNames); // Output: [John, Jane, Tom]

Explanation:

flatMap() is used to flatten the nested structure (List<Employee> in each


department) into a single stream of employees.

Collectors.toSet() ensures uniqueness by collecting only distinct names.

1. Navigating (Searching) TreeSets and TreeMaps

 TreeSet and TreeMap are part of Java’s java.util package and are
implemented using red-black trees, ensuring that elements are
stored in a sorted order.

 TreeSet:
o Automatically sorts elements in natural order (or by a custom
comparator).
 TreeSet<String> set=new
TreeSet<String>(Comparator.reverseOrder());
 set.add("aa");
 set.add("mm");
 set.add("bb");
 System.out.println(set);

o Efficient searching with O(log n) time complexity.

 TreeMap:

o Stores key-value pairs, with keys sorted in natural order.

o Searching by key is efficient due to its internal tree structure.

Common Methods for Navigation:

 TreeSet methods for searching and navigation:

o first(): Returns the first (lowest) element.

o last(): Returns the last (highest) element.

o headSet(E element): Returns a subset of elements less than


the given element.

o tailSet(E element): Returns a subset of elements greater than


or equal to the given element.

o subSet(E fromElement, E toElement): Returns a subset from a


range.

o higher(), lower(): Find elements just greater or lesser than a


given value.

 TreeMap methods:

o firstKey(), lastKey(): Find the first and last keys.

o headMap(K toKey), tailMap(K fromKey): Return a portion of the


map based on key ranges.

o higherKey(), lowerKey(): Keys just higher or lower than a given


key.

2. Other Navigation Methods

 NavigableMap and NavigableSet interfaces in Java add additional


navigation methods.
 For example, TreeMap implements NavigableMap, providing
methods like:

o descendingKeySet(): Returns keys in reverse order.

o floorKey(), ceilingKey(): Finds the largest or smallest key equal


to or greater/lesser than a given key.

3. Backed Collections

 Backed Collections refer to a collection where changes in the


original collection reflect in its subset or view. Examples:

o Methods like subSet(), headSet(), and tailSet() in TreeSet or


TreeMap return backed collections.

o Changes in the original set/map reflect in the view, and vice


versa.

4. Generic Types

 Generics allow types (classes and interfaces) to be parameterized.

 Syntax: class ClassName<T> { }

o Example: TreeSet<String> ensures the set contains only


String objects.

 Provides type safety, allowing compile-time checks and reducing the


need for type casting.

5. Generics and Legacy Code

 Before generics (Java 5), collections used raw types (List, Set)
without type safety.

 Mixing generics with legacy code involves using raw types with
generic collections.

 Example:

List rawList = new ArrayList();

List<String> stringList = rawList; // Compiles but unsafe

6. Mixing Generic and Non-generic Collections

 Mixing generic and non-generic (raw) collections can cause


unchecked warnings and runtime exceptions (e.g.,
ClassCastException).

 Best practice: Use generics consistently to avoid type safety


issues.
7. Polymorphism and Generics

 Polymorphism allows a method to operate on objects of different


classes.

 Generic methods can be polymorphic, meaning the same method


can operate on different types:

public <T> void printArray(T[] array) {

for (T element : array) {

System.out.println(element);

8. Layered Architecture

 In a layered architecture, the application is divided into layers


with specific responsibilities. Common layers include:

o Presentation Layer: User interaction.

o Business Logic Layer: Core logic of the application.

o Data Access Layer: Interacts with the database.

Common Design Patterns in Layered Architecture:

1. Business Delegate Pattern:

o Provides a simplified interface to the business services (like


the Business Logic Layer).

o Decouples the presentation layer from the business layer,


reducing direct dependencies.

o Example:

public class BusinessDelegate {

private BusinessService service;

public void doTask() {

service.execute();

2. Data Access Object (DAO) Pattern:


o Provides an abstract interface to access data.

o Separates the data persistence logic from the business logic.

o Example:

public class UserDAO {

public User getUser(int id) {

// logic to retrieve user from database

3. Transfer Object (TO) Pattern:

o Used to pass data between layers, typically the data access


layer and business layer.

o Example:

public class UserTO {

private int id;

private String name;

// Getters and setters

4. Iterator Pattern:

o Used to access elements of a collection sequentially without


exposing the collection’s underlying structure.

o Example:

public interface Iterator {

boolean hasNext();

Object next();

You might also like