0% found this document useful (0 votes)
8 views84 pages

Java Cheat Sheet

The document provides an overview of Java programming concepts, including compiling and running code, basic data types, control flow, classes, inheritance, polymorphism, and interfaces. It explains the structure of Java programs, the use of operators, input/output methods, and the principles of object-oriented design. Additionally, it covers advanced topics like abstract classes, callbacks, and controlled interaction with objects through encapsulation.
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)
8 views84 pages

Java Cheat Sheet

The document provides an overview of Java programming concepts, including compiling and running code, basic data types, control flow, classes, inheritance, polymorphism, and interfaces. It explains the structure of Java programs, the use of operators, input/output methods, and the principles of object-oriented design. Additionally, it covers advanced topics like abstract classes, callbacks, and controlled interaction with objects through encapsulation.
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

WEEK-2

A First Taste of Java


Why is it so complicated?
Java is designed to be platform-independent, object-oriented, and secure, which can introduce
some complexity. The syntax is strict, and it enforces strong typing, making it robust but
sometimes verbose.

Compiling and running Java code


To compile and run Java code:

1.​ Save your Java program in a file with a .java extension.


2.​ Use the javac command to compile the file:​
javac [Link]
3.​ This will generate a [Link] file containing bytecode.
4.​ Use the java command to run the compiled bytecode:​
java MyProgram

Basic Data Types in Java


Scalar Types

Java has several scalar types including:

●​ int (integer)
●​ double (double-precision floating point)
●​ char (character)
●​ boolean (true or false)

Example:

int number = 10;


double price = 9.99;
char letter = 'A';
boolean isJavaFun = true;

Declarations, assigning values


Declarations specify the type and name of a variable, while assigning values initializes the
variable.

Example:

int age; // Declaration


age = 25; // Assignment

Initialization, constants
Initialization assigns a value at the time of declaration. Constants use the final keyword and
cannot be changed once initialized.

Example:

int year = 2024; // Initialization


final double PI = 3.14159; // Constant

Operators, shortcuts, type casting

Java supports various operators such as arithmetic (+, -, *, /), assignment (=, +=, -=, etc.),
and comparison (==, !=, <, >).

Shortcuts:

int count = 10;


count += 5; // Equivalent to count = count + 5;
count++; // Increment by 1

Type casting:

double a = 9.78;
int b = (int) a; // Explicit type casting from double to int

Strings
Strings in Java are objects that represent sequences of characters. They are immutable.

Example:

String greeting = "Hello, World!";

Arrays

Arrays store multiple values of the same type in a single variable.

Example:

int[] numbers = {1, 2, 3, 4, 5};


String[] names = {"Alice", "Bob", "Charlie"};

Basic Input and Output in Java


Reading input
Using Console

Using the Console class to read input:

Console console = [Link]();


if (console != null) {
String name = [Link]("Enter your name: ");
[Link]("Hello, " + name);
}

Using Scanner

The Scanner class is more commonly used for reading input:


import [Link];

Scanner scanner = new Scanner([Link]);


[Link]("Enter your age: ");
int age = [Link]();
[Link]("You are " + age + " years old.");

Generating Output

Output is typically generated using [Link] or [Link]:

[Link]("Hello, World!"); // Prints with a newline


[Link]("Hello, "); // Prints without a newline
[Link]("World!"); // Continues on the same line

Control Flow in Java


Control flow
Control flow statements determine the order in which instructions are executed. This includes
conditionals and loops.

Conditional Execution

Conditional execution uses if, else if, and else statements.

int score = 85;

if (score >= 90) {


[Link]("Grade: A");
} else if (score >= 80) {
[Link]("Grade: B");
} else {
[Link]("Grade: C");
}
Conditional Loops

Conditional loops use while and do-while.

int i = 0;
while (i < 5) {
[Link](i);
i++;
}

int j = 0;
do {
[Link](j);
j++;
} while (j < 5);

Iteration

Iteration typically uses for loops.

for (int k = 0; k < 5; k++) {


[Link](k);
}

Iterating over elements directly

Enhanced for loop for arrays and collections:

int[] numbers = {1, 2, 3, 4, 5};


for (int number : numbers) {
[Link](number);
}

Multiway branching

Using switch for multiway branching:

int day = 3;
switch (day) {
case 1:
[Link]("Monday");
break;
case 2:
[Link]("Tuesday");
break;
case 3:
[Link]("Wednesday");
break;
default:
[Link]("Other day");
break;
}

Defining Classes and Objects in Java


Defining a class
A class is a blueprint for objects. It defines attributes and methods.

public class Dog {


// Attributes
String name;
int age;

// Methods
void bark() {
[Link]("Woof!");
}
}

Creating Object
Creating an object from a class:

Dog myDog = new Dog();


[Link] = "Buddy";
[Link] = 3;
[Link](); // Outputs: Woof!

Constructors
Constructors initialize new objects. They have the same name as the class.

public class Dog {


String name;
int age;

// Constructor
public Dog(String name, int age) {
[Link] = name;
[Link] = age;
}

void bark() {
[Link]("Woof!");
}
}

// Creating an object with a constructor


Dog myDog = new Dog("Buddy", 3);

Copy constructors
A copy constructor creates a new object as a copy of an existing object.

public class Dog {


String name;
int age;

public Dog(String name, int age) {


[Link] = name;
[Link] = age;
}

// Copy constructor
public Dog(Dog other) {
[Link] = [Link];
[Link] = [Link];
}
}

// Using the copy constructor


Dog originalDog = new Dog("Buddy", 3);
Dog copiedDog = new Dog(originalDog);

WEEK-3

Dynamic Dispatch and Polymorphism


Code

Dynamic dispatch and polymorphism are central to object-oriented programming in Java. Here's
a basic example illustrating these concepts:

class Animal {
void makeSound() {
[Link]("Animal sound");
}
}

class Dog extends Animal {


void makeSound() {
[Link]("Bark");
}
}

class Cat extends Animal {


void makeSound() {
[Link]("Meow");
}
}

public class TestPolymorphism {


public static void main(String[] args) {
Animal myAnimal = new Dog(); // Upcasting
[Link](); // Bark

myAnimal = new Cat(); // Upcasting


[Link](); // Meow
}
}

Dynamic Dispatch

Dynamic dispatch is the process of selecting which implementation of a polymorphic operation


(method or function) to call at runtime. The above example demonstrates dynamic dispatch: the
call to makeSound is determined at runtime based on the actual object type.

Polymorphism

Polymorphism allows objects of different classes to be treated as objects of a common


superclass. It is mainly achieved through inheritance and interfaces.

Functions, Signatures, and Overloading

Function overloading in Java allows multiple methods with the same name but different
parameter lists.

class MathUtils {
static int add(int a, int b) {
return a + b;
}
static double add(double a, double b) {
return a + b;
}
}

public class TestOverloading {


public static void main(String[] args) {
[Link]([Link](5, 10)); // Calls int version
[Link]([Link](5.5, 10.5)); // Calls double
version
}
}

Type Casting

Type casting is converting an object of one type to another. It can be implicit or explicit.

Animal myAnimal = new Dog(); // Upcasting (implicit)


Dog myDog = (Dog) myAnimal; // Downcasting (explicit)

Java Modifiers
public vs private

●​ public: The member is accessible from any other class.


●​ private: The member is accessible only within its own class.

public class Person {


public String name;
private int age;

public int getAge() {


return age;
}

public void setAge(int age) {


[Link] = age;
}
}

static Components

static components belong to the class rather than any instance of the class.

class Counter {
public static int count = 0;

public Counter() {
count++;
}
}

public class TestStatic {


public static void main(String[] args) {
new Counter();
new Counter();
[Link]([Link]); // Output: 2
}
}

final Components

final keyword can be used with variables, methods, and classes to restrict modification.

●​ final variable: cannot be reassigned.


●​ final method: cannot be overridden.
●​ final class: cannot be subclassed.

final class ImmutableClass {


final int MAX_VALUE = 100;

final void display() {


[Link]("This is a final method.");
}
}
Subclasses and Inheritance
A Java Class

A Java class serves as a blueprint for objects.

public class Animal {


String name;
int age;

void eat() {
[Link]("Eating...");
}
}

Subclasses

Subclasses inherit fields and methods from a superclass and can add their own.

public class Dog extends Animal {


void bark() {
[Link]("Bark");
}
}

Inheritance

Inheritance allows a class to inherit fields and methods from another class.

public class Cat extends Animal {


void meow() {
[Link]("Meow");
}
}
Summary

Inheritance promotes code reusability and establishes a relationship between parent and child
classes.

Subtyping vs Inheritance
Subclasses, Subtyping, and Inheritance
●​ Subclassing: Creating a new class based on an existing class.
●​ Subtyping: A form of polymorphism where a subclass is considered to be a subtype of its
superclass.

Subtyping vs Inheritance
Inheritance is about code reuse, while subtyping is about type compatibility. A subclass inherits
methods and fields, while subtyping ensures that an instance of the subclass can be used
wherever an instance of the superclass is expected.

The Java Class Hierarchy


Multiple Inheritance

Java does not support multiple inheritance with classes but allows it through interfaces.

interface CanFly {
void fly();
}

interface CanSwim {
void swim();
}

class Duck implements CanFly, CanSwim {


public void fly() {
[Link]("Flying");
}
public void swim() {
[Link]("Swimming");
}
}

Java Class Hierarchy

All classes in Java inherit from the Object class, the root of the class hierarchy.

public class TestObject {


public static void main(String[] args) {
Object obj = new String("Hello");
[Link]([Link]());
}
}

Writing Generic Functions — Using Object Class

Generic functions can work with any type of objects by using the Object class.

public class Utils {


public static void printObject(Object obj) {
[Link]([Link]());
}
}

Overriding Functions

Method overriding allows a subclass to provide a specific implementation of a method already


defined in its superclass.

class Animal {
void makeSound() {
[Link]("Animal sound");
}
}

class Dog extends Animal {


void makeSound() {
[Link]("Bark");
}
}

Overriding Looks for “Closest” Match

When a method is overridden in a subclass, the overridden method is called based on the
object's runtime type.

Animal myAnimal = new Dog();


[Link](); // Outputs: Bark

The Philosophy of OO Programming


Object-Oriented Design

Object-oriented design involves planning a system of interacting objects for the purpose of
solving a software problem.

Object-Oriented Design: Example

Consider designing a simple banking system with classes such as Bank, Account, and
Customer.

Designing Objects

Objects are designed to encapsulate data and behavior. They should represent entities relevant
to the problem domain.

Relationship Between Classes

Relationships between classes include:

●​ Inheritance (is-a)
●​ Association (has-a)
●​ Dependency (uses-a)

Example:
class Customer {
private String name;
private Account account;

public Customer(String name, Account account) {


[Link] = name;
[Link] = account;
}
}

class Account {
private double balance;

public Account(double balance) {


[Link] = balance;
}
}

WEEK-4

Abstract Classes and Interfaces


Abstract Classes

Abstract classes cannot be instantiated and are meant to be subclassed. They can have
abstract methods (without a body) that must be implemented by subclasses.

abstract class Animal {


abstract void makeSound();
void sleep() {
[Link]("Sleeping");
}
}

class Dog extends Animal {


void makeSound() {
[Link]("Bark");
}
}

Multiple Inheritance

Java does not support multiple inheritance with classes but allows it through interfaces.

interface CanFly {
void fly();
}

interface CanSwim {
void swim();
}

class Duck implements CanFly, CanSwim {


public void fly() {
[Link]("Flying");
}

public void swim() {


[Link]("Swimming");
}
}

Callbacks
Implementing a Callback Facility

A callback is a method that gets called in response to an event. Implementing callbacks usually
involves using interfaces.

interface EventListener {
void onEvent();
}

class EventNotifier {
private EventListener listener;

void setListener(EventListener listener) {


[Link] = listener;
}

void notifyEvent() {
if (listener != null) {
[Link]();
}
}
}

Implementing Callbacks

Implementing a callback by defining an interface and passing an implementation to a method.

class MyEventListener implements EventListener {


public void onEvent() {
[Link]("Event occurred!");
}
}

public class TestCallback {


public static void main(String[] args) {
EventNotifier notifier = new EventNotifier();
[Link](new MyEventListener());
[Link]();
}
}

A Generic Timer

A generic timer that accepts a callback interface.

interface TimerListener {
void onTimeout();
}

class Timer {
private TimerListener listener;

void setListener(TimerListener listener) {


[Link] = listener;
}

void start(int milliseconds) {


try {
[Link](milliseconds);
if (listener != null) {
[Link]();
}
} catch (InterruptedException e) {
[Link]();
}
}
}

public class TestTimer {


public static void main(String[] args) {
Timer timer = new Timer();
[Link](new TimerListener() {
public void onTimeout() {
[Link]("Time's up!");
}
});
[Link](2000);
}
}

Use Interfaces

Interfaces define methods that must be implemented by classes. They are used to provide a
contract for what a class can do, without dictating how it should do it.

Summary
Callbacks, abstract classes, and interfaces are powerful features in Java that help in creating
flexible and reusable code.

Controlled Interaction with Objects


Querying a Database

Controlled interaction involves restricting access to certain methods or fields of a class, often
using encapsulation principles.

public class Database {


private String[] data = {"Alice", "Bob", "Charlie"};

public String getData(int index) {


if (index >= 0 && index < [Link]) {
return data[index];
}
return null;
}
}

Interfaces
Interfaces

Interfaces are abstract types used to specify a set of methods that a class must implement.
interface Animal {
void makeSound();
void sleep();
}

class Dog implements Animal {


public void makeSound() {
[Link]("Bark");
}

public void sleep() {


[Link]("Sleeping");
}
}

Exposing Limited Capabilities

Interfaces allow exposing only specific capabilities of a class.

interface Readable {
String read();
}

class Book implements Readable {


private String content = "This is a book";

public String read() {


return content;
}
}

Adding Methods to Interfaces

In Java 8 and later, you can add default and static methods to interfaces.

interface Printable {
void print();
default void printWithPrefix(String prefix) {
[Link](prefix + ": " + toString());
}

static void printClassName() {


[Link]("Printable");
}
}

Dealing with Conflicts

When a class implements multiple interfaces that define the same default method, it must
override the method to resolve the conflict.

interface InterfaceA {
default void foo() {
[Link]("InterfaceA foo");
}
}

interface InterfaceB {
default void foo() {
[Link]("InterfaceB foo");
}
}

class MyClass implements InterfaceA, InterfaceB {


public void foo() {
[Link]("MyClass foo");
}
}

Iterators
Linear List

A linear list is a sequence of elements arranged in a linear order.


public class LinearList {
private int[] data;
private int size;

public LinearList(int capacity) {


data = new int[capacity];
size = 0;
}

public void add(int value) {


if (size < [Link]) {
data[size++] = value;
}
}

public int get(int index) {


if (index >= 0 && index < size) {
return data[index];
}
throw new IndexOutOfBoundsException();
}

public int size() {


return size;
}
}

Iteration

Iteration involves traversing through the elements of a collection.

public class TestIteration {


public static void main(String[] args) {
LinearList list = new LinearList(10);
[Link](1);
[Link](2);
[Link](3);
for (int i = 0; i < [Link](); i++) {
[Link]([Link](i));
}
}
}

Iterator

An iterator is an object that allows you to traverse a collection, one element at a time.

import [Link];

public class LinearList implements Iterable<Integer> {


private int[] data;
private int size;

public LinearList(int capacity) {


data = new int[capacity];
size = 0;
}

public void add(int value) {


if (size < [Link]) {
data[size++] = value;
}
}

public int get(int index) {


if (index >= 0 && index < size) {
return data[index];
}
throw new IndexOutOfBoundsException();
}

public int size() {


return size;
}
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
private int currentIndex = 0;

public boolean hasNext() {


return currentIndex < size;
}

public Integer next() {


return data[currentIndex++];
}
};
}
}

How Do We Implement Iterators in LinearList?

We implement Iterator by creating an inner class that overrides hasNext and next
methods.

import [Link];

public class LinearList implements Iterable<Integer> {


private int[] data;
private int size;

public LinearList(int capacity) {


data = new int[capacity];
size = 0;
}

public void add(int value) {


if (size < [Link]) {
data[size++] = value;
}
}

public int get(int index) {


if (index >= 0 && index < size) {
return data[index];
}
throw new IndexOutOfBoundsException();
}

public int size() {


return size;
}

public Iterator<Integer> iterator() {


return new LinearListIterator();
}

private class LinearListIterator implements Iterator<Integer> {


private int currentIndex = 0;

public boolean hasNext() {


return currentIndex < size;
}

public Integer next() {


return data[currentIndex++];
}
}
}

Using Iterator

Using an iterator to traverse a collection.

public class TestIterator {


public static void main(String[] args) {
LinearList list = new LinearList(10);
[Link](1);
[Link](2);
[Link](3);
Iterator<Integer> iterator = [Link]();
while ([Link]()) {
[Link]([Link]());
}
}
}

Summary

Iterators provide a standardized way to traverse collections. They enhance the flexibility and
readability of code.

Private Classes
Nested Objects

Nested classes are classes defined within other classes. They can be static or non-static (inner
classes).

public class OuterClass {


private int outerValue = 10;

class InnerClass {
void display() {
[Link]("Outer value: " + outerValue);
}
}
}

public class TestNestedClass {


public static void main(String[] args) {
OuterClass outer = new OuterClass();
[Link] inner = [Link] InnerClass();
[Link](); // Outputs: Outer value: 10
}
}
WEEK-5
Generics Programming in Java

Generic Functions

Generic functions use type parameters to operate on objects of various types while providing
compile-time type safety.

public class GenericMethod {


public static <T> void printArray(T[] array) {
for (T element : array) {
[Link](element + " ");
}
[Link]();
}
}

Java Generics — Type Quantifier

Generics in Java allow you to define classes, interfaces, and methods with placeholder types,
making your code more flexible and type-safe.

public class Box<T> {


private T item;

public void setItem(T item) {


[Link] = item;
}
public T getItem() {
return item;
}
}

Polymorphic Data Structures

Generics enable polymorphic data structures where the type can be specified at runtime.

public class Pair<K, V> {


private K key;
private V value;

public Pair(K key, V value) {


[Link] = key;
[Link] = value;
}

public K getKey() {
return key;
}

public V getValue() {
return value;
}
}

NOTE

Generics provide compile-time type safety and eliminate the need for casting.

Summary

Generics enhance code reusability, type safety, and readability, allowing for more flexible and
maintainable code.

Java Generics and Subtyping


Extending Subtyping in Contexts

Generics support subtyping where a generic type with a subclass as its type parameter is not a
subtype of the same generic type with a superclass as its type parameter.

List<Object> objectList = new ArrayList<>();


List<String> stringList = new ArrayList<>();
objectList = stringList; // Compile-time error

Generics and Subtypes

Generics can use subtyping with bounded type parameters.

public <T extends Number> void printNumbers(T[] numbers) {


for (T number : numbers) {
[Link](number);
}
}

Generic Methods

Methods can also be generic, using type parameters to work with different types.

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


for (T element : array) {
[Link](element + " ");
}
[Link]();
}

Wildcards

Wildcards (?) are used in generics to represent an unknown type.

public void printList(List<?> list) {


for (Object obj : list) {
[Link](obj);
}
}
Wildcard Type Variables

Wildcards can be used with type variables to create more flexible code.

public void addNumbers(List<? super Integer> list) {


[Link](1);
[Link](2);
[Link](3);
}

Bounded Wildcards

Bounded wildcards restrict the types that can be used as type arguments.

public void printUpperBounded(List<? extends Number> list) {


for (Number num : list) {
[Link](num);
}
}

Summary

Generics and subtyping allow for more flexible and reusable code, making it easier to write
type-safe and maintainable programs.

Java Generics at Runtime


Erasure of Generic Information

During compilation, generic type information is erased and replaced with raw types, a process
known as type erasure.

public class Box<T> {


private T item;

public void setItem(T item) {


[Link] = item;
}

public T getItem() {
return item;
}
}

At runtime, Box<Integer> and Box<String> both become Box.

Erasure and Overloading

Overloading methods that differ only by their generic type parameters is not allowed due to type
erasure.

public class ErasureExample {


public void print(List<String> list) { }
public void print(List<Integer> list) { } // Compile-time error
}

Arrays and Generics

Arrays and generics interact poorly because arrays are covariant and reifiable, while generics
are invariant and non-reifiable.

List<Integer>[] array = new List<Integer>[10]; // Compile-time error

Wrapper Classes

Wrapper classes are used to wrap primitive types in objects.

Integer intObject = [Link](5);


int intValue = [Link]();

Using Wrapper Classes

Wrapper classes are often used in collections and generics where primitive types are not
allowed.

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


[Link](10);
[Link](20);

Polymorphism
Polymorphism

Polymorphism allows objects to be treated as instances of their parent class rather than their
actual class.

Animal animal = new Dog();


[Link](); // Calls Dog's makeSound method

Structural Polymorphism

Structural polymorphism refers to the ability of different classes to be used interchangeably if


they follow the same interface.

interface Shape {
void draw();
}

class Circle implements Shape {


public void draw() {
[Link]("Drawing Circle");
}
}

class Square implements Shape {


public void draw() {
[Link]("Drawing Square");
}
}

Type Consistency

Type consistency ensures that operations on variables are type-safe.


List<String> strings = new ArrayList<>();
[Link]("hello");
String s = [Link](0); // No cast needed

Polymorphic Data Structures

Polymorphic data structures use generics to operate on different types.

public class GenericList<T> {


private List<T> list = new ArrayList<>();

public void add(T item) {


[Link](item);
}

public T get(int index) {


return [Link](index);
}
}

Generic Programming in Java

Generic programming allows for the creation of flexible and reusable software components.

public class GenericMethod {


public static <T> void printArray(T[] array) {
for (T element : array) {
[Link](element + " ");
}
[Link]();
}
}

Reflection
Reflection
Reflection allows programs to inspect and manipulate the runtime behavior of applications.

import [Link];

public class ReflectionExample {


public static void main(String[] args) {
try {
Class<?> c = [Link]("[Link]");
Method[] methods = [Link]();
for (Method method : methods) {
[Link]([Link]());
}
} catch (ClassNotFoundException e) {
[Link]();
}
}
}

Reflection in Java

Reflection in Java allows you to inspect classes, interfaces, fields, and methods at runtime
without knowing their names at compile time.

public class TestReflection {


public static void main(String[] args) throws Exception {
Class<?> clazz = [Link]("[Link]");
[Link]("Class name: " + [Link]());
}
}

Introspection in Java

Introspection is a subset of reflection, specifically dealing with analyzing a class's properties and
behavior.

import [Link];
import [Link];

public class IntrospectionExample {


public static void main(String[] args) throws Exception {
PropertyDescriptor[] propertyDescriptors =
[Link]([Link]).getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor :
propertyDescriptors) {
[Link]([Link]());
}
}
}

The Class Class

The Class class represents classes and interfaces in a running Java application.

public class TestClass {


public static void main(String[] args) {
Class<String> stringClass = [Link];
[Link]([Link]());
}
}

Using the Class Object

Using the Class object, you can get metadata about the class and create new instances.

public class ReflectionDemo {


public static void main(String[] args) throws Exception {
Class<?> clazz = [Link]("[Link]");
Object instance =
[Link]().newInstance();
[Link]([Link]().getName());
}
}

Reflection and Security

Reflection can pose security risks if used improperly. Ensure proper access controls are in place
when using reflection.
import [Link];

public class ReflectionSecurity {


public static void main(String[] args) throws Exception {
Field field = [Link]("value");
[Link](true);
char[] value = (char[]) [Link]("test");
value[0] = 'T';
[Link]("test"); // Outputs "Test"
}
}

Limitations of Java Reflection

Java reflection has limitations, such as performance overhead, lack of compile-time checking,
and potential security issues. It should be used judiciously.

WEEK-6
Collection
The Collection Interface

The Collection interface is the root interface in the collection hierarchy. It represents a group
of objects known as elements.

Collection<String> collection = new ArrayList<>();


[Link]("Element 1");
[Link]("Element 2");

Using Iterators

Iterators allow traversing the elements of a collection.

Iterator<String> iterator = [Link]();


while ([Link]()) {
[Link]([Link]());
}

Removing Elements

Elements can be removed using an iterator's remove method.

Iterator<String> iterator = [Link]();


while ([Link]()) {
String element = [Link]();
if ([Link]("Element 1")) {
[Link]();
}
}

The Collection Interface — The Full Story

The Collection interface includes methods for adding, removing, and querying elements, and
checking the collection's size.

public interface Collection<E> extends Iterable<E> {


int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
boolean add(E e);
boolean remove(Object o);
void clear();
}
The AbstractCollection Class

The AbstractCollection class provides a skeletal implementation of the Collection


interface, to minimize the effort required to implement this interface.

public abstract class AbstractCollection<E> implements Collection<E> {


public boolean isEmpty() {
return size() == 0;
}

public boolean contains(Object o) {


for (E element : this) {
if ([Link](o)) {
return true;
}
}
return false;
}

public abstract Iterator<E> iterator();


public abstract int size();
}

Concrete Collections
Built-in Data Types

Java provides several concrete implementations of the Collection interface, such as


ArrayList, HashSet, and LinkedList.

List<String> arrayList = new ArrayList<>();


Set<String> hashSet = new HashSet<>();
Queue<String> linkedList = new LinkedList<>();

The List Interface

The List interface extends Collection and represents an ordered collection.


List<String> list = new ArrayList<>();
[Link]("Element 1");
[Link]("Element 2");
[Link]([Link](0)); // Outputs: Element 1

The List Interface and Random Access

List allows random access to elements.

[Link]([Link](1)); // Outputs: Element 2

The AbstractList Class

The AbstractList class provides a skeletal implementation of the List interface.

public abstract class AbstractList<E> extends AbstractCollection<E>


implements List<E> {
public boolean add(E e) {
add(size(), e);
return true;
}

public abstract E get(int index);


public abstract E set(int index, E element);
public abstract void add(int index, E element);
public abstract E remove(int index);
}

Using Concrete List Classes

Concrete list classes like ArrayList and LinkedList can be used for various list operations.

List<String> arrayList = new ArrayList<>();


[Link]("Element 1");
[Link]("Element 2");

List<String> linkedList = new LinkedList<>();


[Link]("Element A");
[Link]("Element B");

The Set Interface

The Set interface extends Collection and represents a collection that does not allow
duplicate elements.

Set<String> set = new HashSet<>();


[Link]("Element 1");
[Link]("Element 1"); // No effect, duplicate element

Concrete Sets

Concrete implementations of the Set interface include HashSet, LinkedHashSet, and


TreeSet.

Set<String> hashSet = new HashSet<>();


Set<String> linkedHashSet = new LinkedHashSet<>();
Set<String> treeSet = new TreeSet<>();

The Queue Interface

The Queue interface extends Collection and represents a collection designed for holding
elements prior to processing.

Queue<String> queue = new LinkedList<>();


[Link]("Element 1");
[Link]("Element 2");
[Link]([Link]()); // Outputs: Element 1

Maps
Maps
Maps are collections that map keys to values. The Map interface does not extend Collection.
Map<String, String> map = new HashMap<>();
[Link]("key1", "value1");
[Link]("key2", "value2");

Updating a Map

Maps allow updating values associated with keys.

[Link]("key1", "newValue1");

Extracting Keys and Values

You can extract keys, values, and key-value pairs from a map.

Set<String> keys = [Link]();


Collection<String> values = [Link]();
Set<[Link]<String, String>> entries = [Link]();

Concrete Implementation of Map

Concrete implementations of the Map interface include HashMap, TreeMap, and


LinkedHashMap.

Map<String, String> hashMap = new HashMap<>();


Map<String, String> treeMap = new TreeMap<>();
Map<String, String> linkedHashMap = new LinkedHashMap<>();

The Benefits of Indirection


Abstract Data Types

Abstract data types (ADTs) provide a way to specify the behavior of a data structure
independently of its implementation.

interface Stack<E> {
void push(E item);
E pop();
boolean isEmpty();
}

Multiple Implementations

ADTs can have multiple implementations, allowing flexibility in choosing the most appropriate
implementation.

class ArrayStack<E> implements Stack<E> {


private List<E> list = new ArrayList<>();

public void push(E item) {


[Link](item);
}

public E pop() {
if ([Link]()) {
throw new EmptyStackException();
}
return [Link]([Link]() - 1);
}

public boolean isEmpty() {


return [Link]();
}
}

Adding Indirection

Indirection involves adding an extra layer to improve flexibility, such as using interfaces and
abstract classes.

public class Client {


private Stack<Integer> stack;

public Client(Stack<Integer> stack) {


[Link] = stack;
}
public void performOperations() {
[Link](10);
[Link](20);
[Link]([Link]()); // Outputs: 20
}
}

public class Main {


public static void main(String[] args) {
Stack<Integer> stack = new ArrayStack<>();
Client client = new Client(stack);
[Link]();
}
}

WEEK-7
Assertions
Documenting and Checking Assumptions

Assertions are used to document and verify assumptions made in the code. They help identify
bugs by catching incorrect assumptions during runtime.

public class AssertionsExample {


public static void main(String[] args) {
int age = -1;
assert age >= 0 : "Age cannot be negative";
}
}
Assertions

Assertions are a development tool used to test code during development.

public class AssertionsExample {


public static void main(String[] args) {
int num = -10;
assert num > 0 : "Number must be positive";
}
}

Enabling and Disabling Assertions

Assertions can be enabled or disabled at runtime using the -ea or -da flags.

java -ea AssertionsExample # Enable assertions


java -da AssertionsExample # Disable assertions

Dealing with Errors


Exception Handling

Exception handling is a mechanism to handle runtime errors. It ensures the flow of the program
does not break when an exception occurs.

try {
int division = 10 / 0;
} catch (ArithmeticException e) {
[Link]("ArithmeticException caught: " +
[Link]());
}

Java’s Classification of Errors


Java classifies errors into Error and Exception. Error represents serious issues that a
typical application should not try to catch, while Exception represents conditions that a
reasonable application might want to catch.

Summary

Exception handling in Java is essential for building robust and error-resilient applications.
Proper use of try-catch blocks, custom exceptions, and resource management can significantly
enhance the stability and maintainability of your code.

Exceptions in Java
Catching and Handling Exceptions

Exceptions are caught using a try-catch block.

try {
int[] array = new int[5];
array[10] = 50; // This will throw ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException e) {
[Link]("Exception caught: " + [Link]());
}

Catching Multiple Exceptions

You can catch multiple exceptions in a single catch block.

try {
int num = [Link]("XYZ");
int division = 10 / 0;
} catch (NumberFormatException | ArithmeticException e) {
[Link]("Exception caught: " + [Link]());
}

Notify Checked Exceptions

Methods can declare checked exceptions they may throw.

public void readFile(String filePath) throws IOException {


BufferedReader reader = new BufferedReader(new
FileReader(filePath));
String line = [Link]();
[Link]();
}

Throwing Exceptions

Exceptions can be thrown using the throw keyword.

public void validateAge(int age) {


if (age < 18) {
throw new IllegalArgumentException("Age must be 18 or older");
}
}

Customized Exceptions

Custom exceptions can be created by extending the Exception class.

public class CustomException extends Exception {


public CustomException(String message) {
super(message);
}
}

More on Catching Exceptions

Use multiple catch blocks to handle different exceptions separately.

try {
int[] array = new int[5];
array[10] = 50;
} catch (ArrayIndexOutOfBoundsException e) {
[Link]("Array index out of bounds: " +
[Link]());
} catch (Exception e) {
[Link]("General exception: " + [Link]());
}

Cleaning Up Resources

Use finally block to clean up resources.

BufferedReader reader = null;


try {
reader = new BufferedReader(new FileReader("[Link]"));
String line = [Link]();
} catch (IOException e) {
[Link]();
} finally {
try {
if (reader != null) {
[Link]();
}
} catch (IOException e) {
[Link]();
}
}

Logging
Diagnostic Messages

Diagnostic messages provide information about the application's state, useful for debugging and
monitoring.

[Link]("Starting application...");

Logging

Java provides a logging API through [Link] package.

import [Link];
public class LoggingExample {
private static final Logger logger =
[Link]([Link]());

public static void main(String[] args) {


[Link]("Application started");
}
}

Logging Levels

Different logging levels indicate the severity of the log messages.

[Link]("Severe message");
[Link]("Warning message");
[Link]("Info message");
[Link]("Config message");
[Link]("Fine message");
[Link]("Finer message");
[Link]("Finest message");

Summary

Logging is a critical part of application development and maintenance. It helps in tracking the
flow of the application and diagnosing issues. Java's logging framework offers a flexible and
configurable way to handle logging.

Packages
Packages

Packages are used to group related classes and interfaces. They provide access protection and
namespace management.

package [Link];

public class MyClass {


// Class code here
}

Creating and Naming Packages

Packages are typically named using the domain name of the organization in reverse.

package [Link];

More About Visibility

Access modifiers determine the visibility of classes, methods, and fields.

●​ Public: Accessible from any other class.


●​ Protected: Accessible within the same package and subclasses.
●​ Default (package-private): Accessible only within the same package.
●​ Private: Accessible only within the same class.

public class MyClass {


public int publicField;
protected int protectedField;
int defaultField;
private int privateField;
}

WEEK-8
Cloning
Copying an Object

Copying an object in Java can be done in two main ways: shallow copy and deep copy.
The clone() Method

The clone() method is used to create a copy of an object. The class must implement the
Cloneable interface, and override the clone() method.

public class Person implements Cloneable {


private String name;

public Person(String name) {


[Link] = name;
}

@Override
protected Object clone() throws CloneNotSupportedException {
return [Link]();
}
}

Shallow Copy

A shallow copy copies the object but not the objects it references.

public class Person implements Cloneable {


private String name;
private Address address;

public Person(String name, Address address) {


[Link] = name;
[Link] = address;
}

@Override
protected Object clone() throws CloneNotSupportedException {
return [Link]();
}
}

Person original = new Person("John", new Address("123 Street"));


Person copy = (Person) [Link]();
Deep Copy

A deep copy copies the object and the objects it references.

public class Person implements Cloneable {


private String name;
private Address address;

public Person(String name, Address address) {


[Link] = name;
[Link] = address;
}

@Override
protected Object clone() throws CloneNotSupportedException {
Person cloned = (Person) [Link]();
[Link] = (Address) [Link]();
return cloned;
}
}

Restrictions on clone()

The clone() method has restrictions:

●​ Classes must implement Cloneable.


●​ The clone() method in Object class performs a shallow copy.
●​ Custom deep cloning requires overriding the clone() method.

Higher Order Functions


Passing Functions

Java supports higher-order functions through functional interfaces and lambda expressions.

interface Operation {
int execute(int a, int b);
}

public class HigherOrderFunctionExample {


public static void main(String[] args) {
Operation addition = (a, b) -> a + b;
[Link](performOperation(5, 3, addition)); //
Outputs: 8
}

public static int performOperation(int a, int b, Operation op) {


return [Link](a, b);
}
}

Functional Interfaces

Functional interfaces have a single abstract method and can be implemented using lambda
expressions.

@FunctionalInterface
interface Greeting {
void sayHello(String name);
}

public class FunctionalInterfaceExample {


public static void main(String[] args) {
Greeting greeting = name -> [Link]("Hello, " +
name);
[Link]("Alice");
}
}

Lambda Expressions

Lambda expressions provide a concise way to implement functional interfaces.

Operation subtraction = (a, b) -> a - b;


[Link](performOperation(5, 3, subtraction)); // Outputs: 2
Passed Named Functions

Methods can be passed as arguments using method references.

public class MethodReferenceExample {


public static void main(String[] args) {
List<String> names = [Link]("Alice", "Bob", "Charlie");
[Link]([Link]::println);
}
}

Method References

Method references simplify the syntax for passing methods as arguments.

Operation multiplication = Integer::sum;


[Link](performOperation(5, 3, multiplication)); //
Outputs: 8

Streams
Operating on Collections

Streams allow operations on collections in a functional style.

List<String> names = [Link]("Alice", "Bob", "Charlie");


[Link]().forEach([Link]::println);

Why Streams?

Streams provide a way to process collections of data in a declarative manner, supporting


operations such as filtering, mapping, and reducing.

Working with Streams

Streams are created from collections and support various operations.

List<Integer> numbers = [Link](1, 2, 3, 4, 5);


[Link]()
.filter(n -> n % 2 == 0)
.forEach([Link]::println); // Outputs: 2, 4

Creating Streams

Streams can be created from collections, arrays, or custom sources.

Stream<String> stream = [Link]("Alice", "Bob", "Charlie");

Processing Streams

Streams can be processed using intermediate operations (e.g., filter, map) and terminal
operations (e.g., forEach, reduce).

List<String> names = [Link]("Alice", "Bob", "Charlie");


long count = [Link]()
.filter(name -> [Link]("A"))
.count();
[Link](count); // Outputs: 1

Stream Transformations

Stream transformations apply functions to elements in the stream.

List<Integer> numbers = [Link](1, 2, 3);


List<Integer> squares = [Link]()
.map(n -> n * n)
.collect([Link]());
[Link](squares); // Outputs: [1, 4, 9]

Reducing a Stream to a Result

Streams can be reduced to a single result using the reduce method.

List<Integer> numbers = [Link](1, 2, 3, 4, 5);


int sum = [Link]()
.reduce(0, Integer::sum);
[Link](sum); // Outputs: 15

Type Inference
Type Declarations vs Type Inference

Type declarations explicitly specify the type, while type inference allows the compiler to deduce
the type.

List<String> names = new ArrayList<>(); // Type inference


List<String> names = new ArrayList<String>(); // Type declaration

Type Inference

Type inference simplifies code by reducing verbosity.

var names = new ArrayList<String>();


[Link]("Alice");
[Link]([Link](0)); // Outputs: Alice

Type Inference in Java

Java supports type inference for local variables, method parameters, and return types.

var list = [Link]("Alice", "Bob", "Charlie");


for (var name : list) {
[Link](name);
}
WEEK-9
Collecting Results from Streams
Collecting Values from a Stream

Use the collect method to gather the elements of a stream into a collection.

List<String> names = [Link]("Alice", "Bob", "Charlie");


List<String> filteredNames = [Link]()
.filter(name ->
[Link]("A"))
.collect([Link]());
[Link](filteredNames); // Outputs: [Alice]

Storing a Stream as a Collection

You can collect the stream elements into various collection types.

Set<String> nameSet = [Link]()


.collect([Link]());

Stream Summaries

Use collectors to get summary statistics.

IntSummaryStatistics stats = [Link]()


.mapToInt(Integer::intValue)
.summaryStatistics();
[Link]([Link]());
[Link]([Link]());

Converting a Stream to a Map

Convert a stream to a map using [Link].


Map<String, Integer> nameLengthMap = [Link]()

.collect([Link](name -> name, name -> [Link]()));


[Link](nameLengthMap);

Grouping and Partitioning Values

Group or partition the stream elements based on a criterion.

Map<Boolean, List<String>> partitionedNames = [Link]()

.collect([Link](name -> [Link]() > 3));


[Link](partitionedNames);

Input/Output Streams
Input and Output Streams

Java provides InputStream and OutputStream classes for handling byte-based input and
output.

Reading Raw Bytes

Read raw bytes from an InputStream.

try (InputStream in = new FileInputStream("[Link]")) {


int byteData;
while ((byteData = [Link]()) != -1) {
[Link]((char) byteData);
}
} catch (IOException e) {
[Link]();
}

Writing Raw Bytes

Write raw bytes to an OutputStream.


try (OutputStream out = new FileOutputStream("[Link]")) {
[Link]("Hello, World!".getBytes());
} catch (IOException e) {
[Link]();
}

Connecting a Stream to an External Source

Streams can be connected to files, network sockets, etc.

try (InputStream in = new URL("[Link] {


// Read from URL
} catch (IOException e) {
[Link]();
}

Reading Text

Use BufferedReader for reading text efficiently.

try (BufferedReader reader = new BufferedReader(new


FileReader("[Link]"))) {
String line;
while ((line = [Link]()) != null) {
[Link](line);
}
} catch (IOException e) {
[Link]();
}

Writing Text

Use BufferedWriter for writing text efficiently.

try (BufferedWriter writer = new BufferedWriter(new


FileWriter("[Link]"))) {
[Link]("Hello, World!");
} catch (IOException e) {
[Link]();
}

Reading Binary Data

Use DataInputStream to read binary data.

try (DataInputStream in = new DataInputStream(new


FileInputStream("[Link]"))) {
int number = [Link]();
[Link](number);
} catch (IOException e) {
[Link]();
}

Writing Binary Data

Use DataOutputStream to write binary data.

try (DataOutputStream out = new DataOutputStream(new


FileOutputStream("[Link]"))) {
[Link](42);
} catch (IOException e) {
[Link]();
}

Other Features

Java I/O streams support various features like buffering, character encoding, and more for
efficient and flexible input/output operations.

Optional Types
Dealing with Empty Streams

Use Optional to handle cases where a stream might be empty.


Optional<String> firstName = [Link]()
.filter(name ->
[Link]("Z"))
.findFirst();

Handling Missing Optional Values

Use orElse or orElseGet to handle missing values.

String name = [Link]("No name found");

Ignoring Missing Values

Use ifPresent to perform an action if a value is present.

[Link]([Link]::println);

Creating an Optional Value

Create an Optional value using [Link] or [Link].

Optional<String> optionalName = [Link]("Alice");


Optional<String> optionalEmpty = [Link](null);

Passing on Optional Values

Pass Optional values between methods.

public Optional<String> findName(List<String> names) {


return [Link]()
.filter(name -> [Link]("A"))
.findFirst();
}

Composing Optional Values of Different Types

Combine multiple Optional values using flatMap.


Optional<String> fullName = [Link](fName ->
[Link](lName -> fName + " " + lName));

Turning an Optional into a Stream

Convert an Optional to a stream.

List<String> nameList = [Link]()


.collect([Link]());

Summary

Optional helps in handling null values gracefully, avoiding NullPointerException, and


writing more readable and maintainable code.

Serialization
Reading and Writing Objects

Serialize objects to a stream using ObjectOutputStream and deserialize using


ObjectInputStream.

try (ObjectOutputStream out = new ObjectOutputStream(new


FileOutputStream("[Link]"))) {
[Link](new Person("Alice", 30));
} catch (IOException e) {
[Link]();
}

try (ObjectInputStream in = new ObjectInputStream(new


FileInputStream("[Link]"))) {
Person person = (Person) [Link]();
[Link](person);
} catch (IOException | ClassNotFoundException e) {
[Link]();
}
How Serialization Works

Serialization converts an object into a byte stream, while deserialization reconstructs the object
from the byte stream.

Customizing Serialization

Customize serialization by implementing readObject and writeObject methods.

private void writeObject(ObjectOutputStream out) throws IOException {


[Link]();
[Link](age);
}

private void readObject(ObjectInputStream in) throws IOException,


ClassNotFoundException {
[Link]();
age = [Link]();
}

WEEK-10
Concurrency: Threads & Processes
Concurrent Programming

Concurrent programming involves multiple threads executing independently but possibly


interacting with each other. It is used to improve the efficiency and responsiveness of
applications.

Creating Threads in Java


Threads can be created by extending the Thread class or implementing the Runnable
interface.

// Extending Thread class


class MyThread extends Thread {
public void run() {
[Link]("Thread is running");
}
}

public class ThreadExample {


public static void main(String[] args) {
MyThread t1 = new MyThread();
[Link]();
}
}

// Implementing Runnable interface


class MyRunnable implements Runnable {
public void run() {
[Link]("Runnable is running");
}
}

public class RunnableExample {


public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
[Link]();
}
}

Java Threads

Java threads are lightweight processes that share the same address space. They can be
created using the Thread class or the ExecutorService.

Monitors
Problem Overview: Atomic Test-and-Set

The atomic test-and-set problem involves ensuring that a variable can only be modified by one
thread at a time to avoid race conditions.

Monitors

Monitors are synchronization constructs that provide a way to achieve mutual exclusion and
condition synchronization. They use synchronized methods or blocks in Java.

public class MonitorExample {


private int count = 0;

public synchronized void increment() {


count++;
}

public synchronized int getCount() {


return count;
}
}

Monitors: External Queue

Monitors can have an external queue to manage waiting threads.

Making Monitors More Flexible

Monitors can be made more flexible by using condition variables to allow threads to wait and be
notified under certain conditions.

Monitors — wait()

The wait() method causes the current thread to wait until another thread invokes the
notify() or notifyAll() method for the same object.

public synchronized void waitMethod() throws InterruptedException {


wait();
}
Monitors — notify()

The notify() method wakes up a single thread that is waiting on the object's monitor.

public synchronized void notifyMethod() {


notify();
}

Monitors — wait() and notify()

Using wait() and notify() together allows threads to communicate about the state of the
object.

public synchronized void waitAndNotify() throws InterruptedException {


wait();
// Some code
notify();
}

Condition Variables

Condition variables are used to manage the state and the condition under which threads should
wait and be notified.

Mutual Exclusion
Mutual Exclusion

Mutual exclusion ensures that only one thread can access a critical section at a time. This can
be achieved using synchronized blocks or locks.

public class MutexExample {


private final Object lock = new Object();

public void criticalSection() {


synchronized (lock) {
// Critical section code
}
}
}

First Attempt: Mutual Exclusion for Two Processes

The simplest mutual exclusion can be achieved by using flags for each process to indicate
whether it wants to enter the critical section.

Second Attempt: Mutual Exclusion for Two Processes

A more sophisticated approach uses turn variables to alternate access between processes.

Peterson’s Algorithm

Peterson's algorithm ensures mutual exclusion between two processes using two flags and a
turn variable.

class Peterson {
private volatile boolean[] flag = new boolean[2];
private volatile int turn;

public void enterCritical(int i) {


int j = 1 - i;
flag[i] = true;
turn = j;
while (flag[j] && turn == j) {
// busy wait
}
}

public void leaveCritical(int i) {


flag[i] = false;
}
}

Beyond Two Processes

For more than two processes, algorithms like the Bakery algorithm or using more sophisticated
locking mechanisms are required.
Race Conditions
Example: Maintain Data Consistency

Race conditions occur when multiple threads access shared data concurrently and try to change
it at the same time.

public class RaceConditionExample {


private int count = 0;

public void increment() {


count++;
}

public static void main(String[] args) {


RaceConditionExample example = new RaceConditionExample();
Runnable task = example::increment;

Thread t1 = new Thread(task);


Thread t2 = new Thread(task);

[Link]();
[Link]();
}
}

Race Conditions and Mutual Exclusion

Race conditions can be prevented by using mutual exclusion techniques like synchronized
blocks or locks to ensure only one thread can modify the shared data at a time.

Test and Set


Test and Set

The test-and-set operation is used to achieve mutual exclusion by atomically testing and setting
a lock variable.
public class TestAndSetLock {
private AtomicBoolean lock = new AtomicBoolean(false);

public void lock() {


while ([Link](true)) {
// busy wait
}
}

public void unlock() {


[Link](false);
}
}

Semaphores

Semaphores are synchronization constructs that control access to a resource by multiple


threads. They can be used to implement mutual exclusion and manage resource allocation.

Semaphore semaphore = new Semaphore(1);

public void accessResource() throws InterruptedException {


[Link]();
try {
// access resource
} finally {
[Link]();
}
}

Using Semaphores

Semaphores can be used for various synchronization tasks like managing a pool of resources.

Semaphore semaphore = new Semaphore(5); // Allows up to 5 permits

public void accessResource() throws InterruptedException {


[Link]();
try {
// access resource
} finally {
[Link]();
}
}

Problems with Semaphores

Semaphores can lead to issues like deadlock if not used properly, where two or more threads
are waiting for each other to release resources.

WEEK-11
Example: Concurrency Programming
An Exercise in Concurrent Programming

Create a simple counter that multiple threads will increment concurrently to demonstrate
synchronization and race conditions.

An Example

Here’s an example of a counter with multiple threads incrementing it without synchronization,


leading to race conditions.

public class Counter {


private int count = 0;
public void increment() {
count++;
}

public int getCount() {


return count;
}

public static void main(String[] args) throws InterruptedException


{
Counter counter = new Counter();
Runnable task = counter::increment;

Thread t1 = new Thread(task);


Thread t2 = new Thread(task);
[Link]();
[Link]();

[Link]();
[Link]();

[Link]([Link]()); // Expected 2 but may


be less due to race conditions
}
}

Analysis

The counter may not reach the expected value due to race conditions. Both threads read and
write the count variable simultaneously, leading to inconsistent results.

Code for enter

Implement the enter method to handle synchronization using the synchronized keyword.

public synchronized void enter() {


count++;
}
Code for leave

Ensure proper cleanup or state reset if needed in the leave method.

public synchronized void leave() {


// Cleanup or state reset if necessary
}

Summary

Synchronization is crucial in concurrent programming to prevent race conditions and ensure


data consistency.

Monitors in Java
Monitors in Java

Monitors in Java are implemented using the synchronized keyword, which can be applied to
methods or blocks to ensure that only one thread can access the critical section at a time.

Object Locks

Each object in Java has an intrinsic lock or monitor that is used for synchronization. When a
thread enters a synchronized method or block, it acquires the object's lock.

Object Locks...

An object lock ensures that only one thread can execute a synchronized method or block at any
given time.

public class SynchronizedExample {


private int count = 0;

public synchronized void increment() {


count++;
}

public synchronized int getCount() {


return count;
}
}

Reentrant Locks

Reentrant locks are provided by the [Link] package and offer more
flexibility than the intrinsic locks.

import [Link];

public class ReentrantLockExample {


private final ReentrantLock lock = new ReentrantLock();
private int count = 0;

public void increment() {


[Link]();
try {
count++;
} finally {
[Link]();
}
}

public int getCount() {


return count;
}
}

Thread Safe Collection


Concurrency and Collections

Standard collections in Java are not thread-safe and can lead to concurrency issues when
accessed by multiple threads simultaneously.

Thread Safety and Correctness

Thread safety ensures that a class behaves correctly when accessed from multiple threads. For
collections, this means preventing race conditions and ensuring data consistency.
Thread Safe Collections

Java provides thread-safe collections like ConcurrentHashMap, CopyOnWriteArrayList,


and BlockingQueue.

import [Link];

public class ThreadSafeCollectionsExample {


private ConcurrentHashMap<String, Integer> map = new
ConcurrentHashMap<>();

public void add(String key, Integer value) {


[Link](key, value);
}

public Integer get(String key) {


return [Link](key);
}
}

Using Thread Safe Queues for Synchronization

Thread-safe queues like BlockingQueue can be used for producer-consumer scenarios.

import [Link];
import [Link];

public class BlockingQueueExample {


private BlockingQueue<Integer> queue = new
ArrayBlockingQueue<>(10);

public void produce(int value) throws InterruptedException {


[Link](value);
}

public int consume() throws InterruptedException {


return [Link]();
}
}
Blocking Queues

BlockingQueue provides thread-safe operations for adding and removing elements, blocking
when the queue is full or empty.

public class ProducerConsumer {


private BlockingQueue<Integer> queue = new
ArrayBlockingQueue<>(10);

public void produce(int value) throws InterruptedException {


[Link](value);
}

public int consume() throws InterruptedException {


return [Link]();
}

public static void main(String[] args) throws InterruptedException


{
ProducerConsumer pc = new ProducerConsumer();
Runnable producer = () -> {
try {
for (int i = 0; i < 10; i++) {
[Link](i);
[Link]("Produced: " + i);
}
} catch (InterruptedException e) {
[Link]().interrupt();
}
};

Runnable consumer = () -> {


try {
for (int i = 0; i < 10; i++) {
int value = [Link]();
[Link]("Consumed: " + value);
}
} catch (InterruptedException e) {
[Link]().interrupt();
}
};

Thread producerThread = new Thread(producer);


Thread consumerThread = new Thread(consumer);

[Link]();
[Link]();

[Link]();
[Link]();
}
}

Thread in Java
Life Cycle of a Java Thread

A Java thread goes through several states: New, Runnable, Blocked, Waiting, Timed Waiting,
and Terminated.

●​ New: Thread is created but not yet started.


●​ Runnable: Thread is eligible to run but may not be running.
●​ Blocked: Thread is blocked waiting for a monitor lock.
●​ Waiting: Thread is waiting indefinitely for another thread to perform a particular action.
●​ Timed Waiting: Thread is waiting for another thread to perform a specific action for up to
a specified waiting time.
●​ Terminated: Thread has exited.

Interrupts

Threads can be interrupted to stop their execution or to handle particular events.

public class InterruptExample {


public static void main(String[] args) throws InterruptedException
{
Thread thread = new Thread(() -> {
while (![Link]().isInterrupted()) {
[Link]("Running");
}
[Link]("Interrupted");
});

[Link]();
[Link](1000);
[Link]();
}
}

More About Threads

Java provides various utilities for advanced thread management like ExecutorService,
CountDownLatch, CyclicBarrier, and ForkJoinPool for handling complex concurrent
tasks efficiently.

WEEK-12
Graphical Interfaces and Event-Driven
Programming
GUIs and Events

Graphical User Interfaces (GUIs) in Java are typically created using the Swing framework.
Events in GUIs are actions that occur as a result of user interaction, such as clicking a button or
typing in a text field. Event-driven programming is a paradigm where the flow of the program is
determined by these events.

Keeping Track of Events

To handle events, you need to implement event listeners that define how the application should
respond to various actions.

Better Programming Language (PL) Support for Events

Java provides robust support for event-driven programming through its event-handling model,
which includes event listeners, event objects, and event sources.

Example

Here’s a simple example of a GUI with a button that responds to a click event.

import [Link].*;
import [Link];
import [Link];

public class SimpleGUI {


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

[Link](new ActionListener() {
public void actionPerformed(ActionEvent e) {
[Link]("Button was clicked!");
}
});

[Link](JFrame.EXIT_ON_CLOSE);
[Link]().add(button);
[Link](300, 200);
[Link](true);
}
}

Timer
Timers can be used to perform actions at regular intervals. The [Link] class is
useful for this purpose.

import [Link].*;
import [Link];
import [Link];

public class TimerExample {


public static void main(String[] args) {
Timer timer = new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
[Link]("Timer ticked");
}
});
[Link]();

[Link](null, "Close to stop timer");


[Link](0);
}
}

Summary

Event-driven programming in Java using Swing involves creating GUI components and handling
events with event listeners. This approach makes it easy to create interactive applications.

More Swing Examples


Connecting Multiple Events to a Listener

A single listener can handle multiple events from different sources.

import [Link].*;
import [Link];
import [Link];

public class MultipleEventsExample {


public static void main(String[] args) {
JFrame frame = new JFrame("Multiple Events Example");
JButton button1 = new JButton("Button 1");
JButton button2 = new JButton("Button 2");

ActionListener listener = new ActionListener() {


public void actionPerformed(ActionEvent e) {
[Link]([Link]() + " was
clicked");
}
};

[Link](listener);
[Link](listener);

[Link](new [Link]());
[Link](button1);
[Link](button2);
[Link](300, 200);
[Link](JFrame.EXIT_ON_CLOSE);
[Link](true);
}
}

Multicasting: Multiple Listeners for an Event

An event source can have multiple listeners.

import [Link].*;
import [Link];
import [Link];

public class MultipleListenersExample {


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

[Link](new ActionListener() {
public void actionPerformed(ActionEvent e) {
[Link]("First listener");
}
});

[Link](new ActionListener() {
public void actionPerformed(ActionEvent e) {
[Link]("Second listener");
}
});

[Link](JFrame.EXIT_ON_CLOSE);
[Link]().add(button);
[Link](300, 200);
[Link](true);
}
}

Other Elements - Checkboxes

Checkboxes allow users to make binary choices.

import [Link].*;
import [Link];
import [Link];

public class CheckBoxExample {


public static void main(String[] args) {
JFrame frame = new JFrame("Checkbox Example");
JCheckBox checkBox = new JCheckBox("Check Me");

[Link](new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if ([Link]() == [Link]) {
[Link]("Checkbox selected");
} else {
[Link]("Checkbox deselected");
}
}
});

[Link](JFrame.EXIT_ON_CLOSE);
[Link]().add(checkBox);
[Link](300, 200);
[Link](true);
}
}

Summary

Swing allows for sophisticated event handling, including connecting multiple events to a single
listener and multicasting events to multiple listeners. It also provides various UI components like
checkboxes.

Swing ToolKit
Event Driven Programming in Java

Event-driven programming with Swing involves creating UI components and defining how they
respond to user actions through event listeners.

A Button That Paints Its Background Red


import [Link].*;
import [Link].*;
import [Link];
import [Link];

public class RedButtonExample {


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

[Link](new ActionListener() {
public void actionPerformed(ActionEvent e) {
[Link]([Link]);
}
});
[Link](JFrame.EXIT_ON_CLOSE);
[Link]().add(button);
[Link](300, 200);
[Link](true);
}
}

Embedding the Button Inside a Panel


import [Link].*;
import [Link].*;
import [Link];
import [Link];

public class ButtonInPanelExample {


public static void main(String[] args) {
JFrame frame = new JFrame("Button in Panel Example");
JPanel panel = new JPanel();
JButton button = new JButton("Click Me");

[Link](new ActionListener() {
public void actionPerformed(ActionEvent e) {
[Link]([Link]);
}
});

[Link](button);
[Link](panel);

[Link](JFrame.EXIT_ON_CLOSE);
[Link](300, 200);
[Link](true);
}
}

Frame
A frame is the main window that can contain other UI components.

import [Link].*;

public class FrameExample {


public static void main(String[] args) {
JFrame frame = new JFrame("Frame Example");
[Link](JFrame.EXIT_ON_CLOSE);
[Link](400, 300);
[Link](true);
}
}

main Function

The main function is the entry point of a Java application. It sets up and displays the GUI.

public class MainFunctionExample {


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

[Link](e -> [Link]("Button


clicked"));

[Link](JFrame.EXIT_ON_CLOSE);
[Link]().add(button);
[Link](300, 200);
[Link](true);
}
}

Prepared by Rishi Patel

You might also like