Java Final Notes
Java Final Notes
Table of Content
1. Introduction to Java
History of Java
2. Java Fundamentals
Arrays
Strings
Encapsulation
Inheritance
Polymorphism
Abstraction
Interfaces
Packages
4. Exception Handling
Introduction to Exceptions
File I/O
Serialization
6. Generics
Introduction to Generics
Generic Classes
Generic Methods
Wildcards
7. Collections Framework
Iterators
8. Multithreading
Introduction to Threads
Synchronization
Thread Safety
Thread Communication
Thread Pools
File Handling
Connecting to Databases
Transaction Management
Introduction to Swing
Event Handling
Layout Managers
Introduction to Reflection
Lambda Expressions
Functional Interfaces
Stream API
Creational Patterns
Structural Patterns
Behavioral Patterns
Naming Conventions
Memory Management
Performance Optimization
JavaFX Basics
GUI Components
Introduction to Java:
History of Java:
Java was developed by James Gosling and his team at Sun Microsystems (now
owned by Oracle Corporation) in the mid-1990s.
It was originally designed for programming consumer electronic devices, but its
focus shifted towards internet programming.
Simple and easy to learn: Java has a straightforward syntax and a rich set of
libraries, making it accessible for beginners.
Platform independence: Java programs can run on any platform with a Java
Virtual Machine (JVM), making them highly portable.
Robust and secure: Java includes features like garbage collection and built-in
exception handling, ensuring reliable and secure code.
The Java Virtual Machine (JVM) is an integral part of the Java platform and acts
as an execution environment for Java programs.
It provides a layer of abstraction between the Java code and the underlying
operating system.
JVM interprets compiled Java bytecode and translates it into machine code
specific to the host system.
It includes the Java compiler (javac) to compile Java source code into bytecode.
JDK also contains the Java Runtime Environment (JRE), which includes the
JVM and necessary libraries to run Java applications.
Developers use the JDK to write, compile, and debug Java programs.
To start programming in Java, you need to install the Java Development Kit
(JDK) on your system.
The "main" method is the entry point of the program, and it prints the string
"Hello, World!" to the console using the System.out.println statement.
To run the Java program, you need to compile it using the javac command, and
then execute it using the java command.
Java Fundamentals:
Variables and Data Types:
Variables are used to store data in memory during program execution. In Java,
you need to declare a variable before using it.
Java supports various data types, including primitive types (int, double, boolean)
and reference types (String, arrays, objects).
Primitive data types in Java are categorized into four groups: integer types,
floating-point types, character type, and boolean type.
Example expressions:
int x = 10;
int y = 5;
int sum = x + y; // arithmetic operation
Control flow statements are used to control the execution flow of a program
based on certain conditions or repetitive tasks.
Switch statements are used for multi-way branching based on different cases.
Loops (for, while, do-while) enable you to repeatedly execute a block of code.
int day = 2;
String dayName;
switch (day) {
case 1:
dayName = "Monday";
break;
case 2:
Arrays:
Arrays are used to store multiple values of the same data type in a single
variable.
Strings:
String class.
Classes define the properties (attributes) and behaviors (methods) that objects
of that class can have.
// Class definition
public class Car {
// Instance variables
private String make;
private String model;
private int year;
// Constructor
public Car(String make, String model, int year) {
this.make = make;
this.model = model;
this.year = year;
}
// Instance method
public void startEngine() {
System.out.println("Engine started.");
}
}
// Object instantiation
Car myCar = new Car("Toyota", "Camry", 2022);
It provides data hiding and protects the internal state of objects from direct
manipulation.
Access to the data is typically controlled through getter and setter methods.
Example of encapsulation:
Inheritance:
The class that inherits is called the subclass or derived class, and the class
being inherited from is called the superclass or base class.
Subclasses can extend or override the inherited members and also add new
members.
Example of inheritance:
@Override
public void draw() {
System.out.println("Drawing circle at (" + x + ", " + y + ") with radius " + r
adius);
}
}
Polymorphism:
Example of polymorphism:
class
dog.makeSound(); // Polymorphic call to makeSound method of Dog class
Abstraction:
It focuses on the essential properties and behaviors of an object while hiding the
unnecessary details.
@Override
public void draw() {
System.out.println("Drawing circle at (" + x + ", " + y + ") with radius " + r
adius);
}
}
Interfaces:
They provide a way to achieve multiple inheritance and enable loose coupling
between classes.
Example of interfaces:
@Override
public void draw() {
System.out.println("Drawing circle with radius " + radius);
}
}
Packages:
Packages are used to organize classes into a hierarchical structure and avoid
naming conflicts.
They provide a way to group related classes and provide access control.
Packages are declared at the beginning of Java source files using the package
keyword.
Example of packages:
package com.company.project;
Exception Handling:
Introduction to Exceptions:
Exceptions are events that occur during the execution of a program that disrupt
the normal flow of code.
Exceptions can occur due to various reasons, such as invalid input, resource
unavailability, or programming errors.
The code that may throw an exception is placed inside the try block.
If an exception occurs within the try block, it is caught and handled in the catch
block.
try {
// Code that may throw an exception
int result = divide(10, 0);
System.out.println("Result: " + result);
} catch (ArithmeticException ex) {
// Exception handling for ArithmeticException
System.out.println("Error: " + ex.getMessage());
} finally {
// Code that will always execute
System.out.println("Finally block executed.");
}
In addition to built-in exceptions, you can create your own exception classes by
extending the Exception class or its subclasses.
In the example above, the CustomException class extends the Exception class to
create a custom exception. The validateInput method throws the CustomException if
the input value is negative, and it is caught and handled in the main method.
In Java, I/O operations are performed using streams, which provide a convenient
way to read from or write to a data source.
Streams can be classified into two types: byte streams and character streams.
Byte streams ( InputStream and OutputStream ) are used for binary data, while
character streams ( Reader and Writer ) are used for text data.
Readers and writers are designed to handle character-based data and provide
methods for reading and writing characters or strings.
import java.io.*;
File I/O:
File I/O operations involve reading from or writing to files on the file system.
File I/O operations are typically performed using byte streams or character
streams along with file-related classes.
import java.io.*;
Serialization:
Serialization is the process of converting an object into a byte stream, which can
be saved to a file or sent over the network.
The ObjectOutputStream and ObjectInputStream classes are used to write and read
serialized objects, respectively.
import java.io.*;
Generics:
Introduction to Generics:
Generics in Java provide a way to create reusable code that can work with
different types.
They allow the definition of classes, interfaces, and methods that can operate on
parameters of specified types.
Generics enhance type safety and eliminate the need for explicit type casting.
Generics are widely used in collections and algorithms to provide type-safe data
structures and operations.
Generic Classes:
Generic classes are classes that can work with different types.
They are declared using type parameters, which are specified within angle
brackets (<>) after the class name.
Type parameters can be used as placeholders for actual types that will be
provided when creating objects of the generic class.
public T getValue() {
return value;
}
Box<Integer> integerBox = new Box<>(10); // Create a Box object with Integer type
int value = integerBox.getValue(); // Get the value as an Integer
Generic methods are methods that can work with different types.
They are declared using type parameters, which are specified within angle
brackets (<>) before the return type.
Type parameters can be used as placeholders for actual types that will be
determined at the time of method invocation.
Wildcards:
There are two types of wildcards: the upper bounded wildcard ( <?> ) and the
lower bounded wildcard ( <? extends T> or <? super T> ).
The upper bounded wildcard restricts the type to be a specific type or any of its
subtypes.
The lower bounded wildcard restricts the type to be a specific type or any of its
supertypes.
In the example above, the sum method accepts a list of any type that extends
Number , allowing it to handle Integer , Double , and other number types.
Collections Framework:
Lists, Sets, and Maps:
Lists are ordered collections that allow duplicate elements. They maintain the
order of insertion.
Sets are collections that do not allow duplicate elements. They typically do not
maintain any specific order.
Maps are key-value pairs, where each element is associated with a unique key.
import java.util.*;
HashSet is an implementation of the Set interface that uses a hash table to store
elements. It does not maintain any specific order.
import java.util.*;
Iterators:
The Iterator interface provides methods like hasNext() to check if there are
more elements, and next() to retrieve the next element.
The Collections Framework provides utility methods for sorting and searching
elements in lists.
The Collections class contains static methods like sort() to sort lists, and
binarySearch() to perform binary search on sorted lists.
In the example above, the sort() method is used to sort the list in ascending order,
and the binarySearch() method is used to search for the element 3 in the sorted list.
Multithreading:
Introduction to Threads:
Threads are lightweight units of execution within a program that can run
concurrently.
Extending the Thread class requires overriding the run() method, which
contains the code to be executed by the thread.
Synchronization:
Thread Safety:
Thread safety refers to the property of code or data structures that can be safely
accessed and manipulated by multiple threads without causing data corruption
or inconsistencies.
Common techniques for achieving thread safety include synchronization, the use
of atomic operations, and immutability.
Thread Communication:
Instead of creating a new thread for each task, a thread pool maintains a pool of
worker threads that can be used to execute tasks.
Java provides the Executor framework for managing thread pools, which
includes classes like ThreadPoolExecutor and Executors .
executor.shutdown();
File handling in Java involves reading from or writing to files on the file system.
The java.io package provides classes and interfaces for file I/O operations.
Streams in Java are used for reading from or writing to a source, such as files,
network connections, or in-memory buffers.
Byte streams ( InputStream , OutputStream ) are used for binary data, while
character streams ( Reader , Writer ) are used for text data.
Byte streams provide methods for reading or writing individual bytes or arrays of
bytes.
import java.io.*;
import java.io.*;
import java.net.*;
// Server
public class Server {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(1234)) {
System.out.println("Server listening on port 1234...");
Socket socket = serverSocket.accept();
System.out.println("Client connected: " + socket.getInetAddress());
socket.close();
} catch (IOException ex) {
System.out.println("Server error: " + ex.getMessage());
}
}
}
// Client
public class Client {
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 1234)) {
System.out.println("Connected to server: " + socket.getInetAddress());
socket.close();
} catch (IOException ex) {
System.out.println("Client error: " + ex.getMessage());
}
}
}
In the example above, the server listens on port 1234 using a ServerSocket . The
client connects to the server using a Socket with the server's IP address and port
Relational databases are the most common type of databases, which organize
data into tables with rows and columns.
Connecting to Databases:
The JDBC API provides a set of interfaces and classes for database
connectivity.
To connect to a database, you need to load the appropriate JDBC driver and
establish a connection using a connection string that specifies the database
URL, username, and password.
import java.sql.*;
The JDBC API provides interfaces such as Statement and PreparedStatement for
executing SQL statements.
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
System.out.println("ID: " + id + ", Name: " + name + ", Age: " + age);
}
} catch (SQLException ex) {
System.out.println("Query execution error: " + ex.getMessage());
}
Transaction Management:
try {
connection.setAutoCommit(false);
That covers the brief notes and examples for JDBC (Java Database Connectivity). If
you have any more topics, please let me know!
Swing is a Java GUI toolkit provided by Oracle for building desktop applications.
It offers a rich set of components, such as buttons, labels, text fields, and
dialogs, for creating interactive user interfaces.
Swing provides a wide range of components that can be used to build GUIs.
Components are added to containers, such as frames or panels, to form the GUI
layout.
import javax.swing.*;
frame.pack();
frame.setVisible(true);
}
}
Event Handling:
Events are generated by user interactions or system actions, and event listeners
are used to handle these events.
Swing components use the observer design pattern, where listeners are
registered with components to receive and handle events.
button.addActionListener(e -> {
// Handle button click event
System.out.println("Button clicked!");
});
Layout Managers:
Layout managers in Swing are used to arrange and position components within
containers.
Layout managers provide flexibility in designing GUIs that can adapt to different
screen sizes and resolutions.
import javax.swing.*;
import java.awt.*;
panel.add(label);
panel.add(button);
panel.add(textField);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}
In the example above, a JPanel is used as a container with the FlowLayout layout
manager to arrange the components in a horizontal flow.
Java Reflection:
Introduction to Reflection:
It enables dynamic access to class information and the ability to create, modify,
or invoke objects and their members dynamically.
Reflection is often used in frameworks, libraries, and tools that require runtime
analysis or dynamic behavior.
import java.lang.reflect.*;
// Get superclass
Class<? super Person> superClass = personClass.getSuperclass();
System.out.println("Superclass: " + superClass.getName());
// Get constructors
Constructor<?>[] constructors = personClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("Constructor: " + constructor.getName());
}
// Get methods
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println("Method: " + method.getName());
}
// Get fields
Field[] fields = personClass.getFields();
for (Field field : fields) {
System.out.println("Field: " + field.getName());
}
}
}
class Person {
private String name;
public int age;
class.
class Calculator {
public int add(int a, int b) {
return a + b;
}
}
Reflection allows you to access and modify the fields and methods of objects
dynamically at runtime.
You can retrieve field values, set field values, invoke methods, and perform other
operations on objects using reflection.
try {
Class<?> personClass = person.getClass();
nameField = personClass.getDeclaredField("name");
nameField.setAccessible(true);
String name = (String) nameField.get(person);
// Invoking methods
Method sayHelloMethod = personClass.getDeclaredMethod("sayHello");
sayHelloMethod.setAccessible(true);
sayHelloMethod.invoke(person);
} catch (NoSuchFieldException | IllegalAccessException |
NoSuchMethodException | InvocationTargetException ex) {
System.out.println("Error: " + ex.getMessage());
}
}
}
In the example above, reflection is used to access and modify the private fields of a
Person object. The getDeclaredField() method is used to retrieve the field, and the
Java 8 Features:
Lambda Expressions:
Functional Interfaces:
They are used as the basis for lambda expressions and method references.
Stream API:
Streams allow for operations like filtering, mapping, reducing, and collecting on
collections in a concise manner.
System.out.println(sum); // 12
interface Vehicle {
default void start() {
System.out.println("Starting the vehicle...");
}
Prior to Java 8, date and time manipulation was done using the java.util.Date
and java.util.Calendar classes, which were error-prone and cumbersome.
Java 8 introduced a new Date and Time API ( java.time ) that provides a more
comprehensive and intuitive way of working with dates, times, and intervals.
Singleton: Ensures that only one instance of a class is created and provides a
global point of access to it.
Factory Method: Defines an interface for creating objects, but lets subclasses
decide which class to instantiate.
Prototype: Creates new objects by copying existing objects and modifying them
as needed.
Structural Patterns:
Adapter: Converts the interface of a class into another interface that clients
expect, allowing classes with incompatible interfaces to work together.
Behavioral Patterns:
These are just a few examples of design patterns. There are many more design
patterns available, each addressing different design problems and providing effective
solutions. It's important to choose the appropriate design pattern based on the
problem at hand and the specific requirements of the software system.
Remember that design patterns are not strict rules but guidelines that can be
adapted and modified to suit the specific needs of a project. They help in achieving
software that is modular, maintainable, and extensible.
Use meaningful and descriptive names for variables, methods, classes, and
packages.
Use PascalCase naming for class and interface names (e.g., Customer ,
OrderService ).
Code Formatting:
Follow a consistent and logical order for class members (fields, constructors,
methods).
Favor checked exceptions for conditions that can be recovered from, and
unchecked exceptions for fatal or unexpected conditions.
Memory Management:
Release resources explicitly when they are no longer needed, such as closing
files or database connections.
Performance Optimization:
Use efficient algorithms and data structures to optimize time and space
complexity.
In addition to the above, it's important to follow general best practices such as writing
modular and reusable code, documenting your code, writing meaningful comments,
and writing unit tests to ensure code correctness.
Adhering to coding standards and best practices improves code maintainability,
readability, and collaboration among developers. It also helps in producing high-
quality code that is easier to debug, maintain, and extend.
Write tests that cover different scenarios and edge cases to ensure robust code.
Use test fixtures and setup methods to prepare the environment for testing.
Organize tests into test suites and test classes for better maintainability.
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@Test
public void testAddition() {
Use print statements or logging to display variable values and trace program
flow.
Step through the code line by line to understand the execution flow.
Explore features like variable inspection, call stack, watches, and expression
evaluation in your debugger.
Use conditional breakpoints to break execution only when specific conditions are
met.
Analyze error messages, exceptions, and stack traces to identify the root cause
of issues.
Collaborate with colleagues or use code review tools to get a fresh perspective
on your code.
Introduction to JavaFX:
JavaFX is a Java framework for building desktop applications with rich graphical
user interfaces (GUIs). It provides a set of libraries and APIs to create interactive
and visually appealing applications. Here are the key concepts of JavaFX:
JavaFX Basics:
The entry point of a JavaFX application is the Application class, which contains
the start() method.
The start() method sets up the primary stage (window) and creates the scene
graph.
GUI Components:
JavaFX provides a wide range of GUI components, including buttons, labels, text
fields, checkboxes, radio buttons, lists, tables, and more.
Components are organized in a hierarchical structure, with the Scene as the top-
level container and Parent classes as intermediate containers.
Layout managers, such as VBox , HBox , BorderPane , and GridPane , are used to
control the positioning and sizing of components within containers.
JavaFX provides an event model based on the observer pattern, where event
sources generate events and event handlers respond to them.
Event sources include GUI components like buttons, mouse clicks, key presses,
etc.
Event handling in JavaFX can be done by registering event handlers using the
setOn<EventName>() methods or through FXML file bindings.
import javafx.application.Application;
import javafx.scene.Scene;
@Override
public void start(Stage primaryStage) {
Button button = new Button("Click Me");
button.setOnAction(event -> System.out.println("Button clicked!"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(scene);
primaryStage.show();
}
}
In this example, we create a Button and add an event handler using a lambda
expression. The button is placed in a StackPane , which is then added to the Scene of
the primary stage. Finally, the stage is displayed.
JavaFX offers many more features, including animations, multimedia, charts, and
CSS styling. It provides a modern and flexible platform for developing desktop
applications with rich user interfaces.
Note: JavaFX has been decoupled from the core JDK since Java 11 and is now
available as a separate library. It is recommended to use the latest version of
JavaFX with the corresponding JDK.