Java Programming Tutorial
Welcome to this comprehensive Java tutorial! Java is a versatile, object-oriented programming language widely used for building enterprise applications, web applications, and more. In this guide, we’ll explore core Java concepts, including variable types, data types, operators, user input, date-time handling, etc.
Java - Variable Types
In Java, variables are containers for storing data values. The type of a variable determines what kind of data it can hold. There are several types of variables in Java:
- Local Variables: Defined within methods or blocks, accessible only within those blocks.
- Instance Variables: Non-static variables defined within a class, each object has its copy.
- Class/Static Variables: Defined with the
statickeyword, shared across all instances.
public class VariableExample {
int instanceVariable = 10; // Instance variable
static int staticVariable = 20; // Static variable
public void methodExample() {
int localVariable = 30; // Local variable
System.out.println("Local: " + localVariable);
}
}
Java - Data Types
Java has two main types of data types: primitive types and non-primitive types. The primitive data types include int, double, float, char, and more.
public class DataTypesExample {
public static void main(String[] args) {
int integer = 10;
double decimal = 5.5;
char letter = 'A';
boolean flag = true;
System.out.println("Integer: " + integer);
System.out.println("Double: " + decimal);
System.out.println("Char: " + letter);
System.out.println("Boolean: " + flag);
}
}
Java - Type Casting
Type casting in Java allows you to convert a variable of one type to another. It can be implicit (done automatically) or explicit (done manually).
public class TypeCastingExample {
public static void main(String[] args) {
int myInt = 10;
double myDouble = myInt; // Automatic casting: int to double
double anotherDouble = 9.78;
int anotherInt = (int) anotherDouble; // Manual casting: double to int
System.out.println("Double value: " + myDouble);
System.out.println("Int value: " + anotherInt);
}
}
Java - Basic Operators
Operators in Java are special symbols that perform specific operations on one, two, or three operands and return a result. Common operators include:
- Arithmetic Operators: +, -, *, /
- Relational Operators: ==, !=, >, <
- Logical Operators: &&, ||, !
public class OperatorsExample {
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println("Sum: " + (a + b)); // Addition
System.out.println("Greater: " + (a > b)); // Relational
System.out.println("And: " + (a > 5 && b < 30)); // Logical
}
}
Java - Comments
Comments are notes added to code to explain parts of it. Java supports single-line (//) and multi-line (/* ... */) comments.
public class CommentsExample {
public static void main(String[] args) {
// This is a single-line comment
System.out.println("Single-line comment example");
/*
* This is a multi-line comment
* It spans multiple lines
*/
System.out.println("Multi-line comment example");
}
}
Java - User Input
To take user input in Java, you can use the Scanner class from the java.util package.
import java.util.Scanner;
public class UserInputExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("Enter a number: ");
int num = scanner.nextInt();
System.out.println("You entered: " + num);
scanner.close();
}
}
Java - Date & Time
Java provides the LocalDate, LocalTime, and LocalDateTime classes for handling date and time. These classes are part of the java.time package.
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
public class DateTimeExample {
public static void main(String[] args) {
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
LocalDateTime dateTime = LocalDateTime.now();
System.out.println("Current Date: " + date);
System.out.println("Current Time: " + time);
System.out.println("Current Date & Time: " + dateTime);
}
}
Java Control Statements
Control statements in Java determine the flow of program execution based on certain conditions and repetitive actions. This guide covers essential control structures like if-else, switch, and loop controls (for, while, do-while).
Decision Making
Decision-making statements in Java, such as if and switch, allow code to be executed conditionally based on certain criteria. Let’s explore how these statements work.
Java - If-else Statement
The if-else statement executes code based on a true or false condition. The else block runs if the condition in if is false.
public class IfElseExample {
public static void main(String[] args) {
int number = 10;
if (number > 0) {
System.out.println("Positive number");
} else {
System.out.println("Non-positive number");
}
}
}
Java - Switch Statement
The switch statement evaluates an expression and executes code based on matching cases. It’s an alternative to using multiple if-else statements when you have specific, constant values to check.
public class SwitchExample {
public static void main(String[] args) {
int day = 3;
switch (day) {
case 1:
System.out.println("Monday");
break;
case 2:
System.out.println("Tuesday");
break;
case 3:
System.out.println("Wednesday");
break;
default:
System.out.println("Invalid day");
break;
}
}
}
Java Loop Control
Loops allow repetitive execution of code. Java provides several types of loops: for, while, do-while, and for-each.
Java - For Loop
The for loop repeats code a specific number of times. It’s ideal when you know in advance how many iterations are needed.
public class ForLoopExample {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
System.out.println("Iteration: " + i);
}
}
}
Java - For-Each Loop
The for-each loop iterates through arrays or collections, allowing easy access to each element without using an index.
public class ForEachExample {
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
for (int num : numbers) {
System.out.println("Number: " + num);
}
}
}
Java - While Loop
The while loop runs as long as a specified condition remains true. It’s often used when the number of iterations is unknown.
public class WhileLoopExample {
public static void main(String[] args) {
int count = 0;
while (count < 5) {
System.out.println("Count: " + count);
count++;
}
}
}
Java - Do-While Loop
The do-while loop is similar to the while loop but guarantees at least one iteration because the condition is evaluated after the loop body.
public class DoWhileExample {
public static void main(String[] args) {
int count = 0;
do {
System.out.println("Count: " + count);
count++;
} while (count < 5);
}
}
Java - Break Statement
The break statement exits a loop or switch statement before it completes. It’s helpful for terminating loops when a condition is met.
public class BreakExample {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
if (i == 5) {
break;
}
System.out.println("Iteration: " + i);
}
}
}
Java - Continue Statement
The continue statement skips the current iteration of a loop and proceeds with the next one. It’s often used to bypass specific conditions within loops.
public class ContinueExample {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
continue;
}
System.out.println("Odd number: " + i);
}
}
}
Java Object-Oriented Concepts
Java is a popular Object-Oriented Programming (OOP) language. This tutorial covers core OOP concepts in Java such as classes, objects, methods, constructors, variable scope, and access modifiers. Each section provides detailed explanations, code examples, and practical applications to help you understand the fundamentals of Java OOP.
Java - OOPs Concepts
OOP in Java is based on four main principles: Encapsulation, Inheritance, Polymorphism, and Abstraction. These principles allow for modular, reusable, and flexible code, where classes and objects interact to model real-world entities.
Java - Objects and Classes
A class is a blueprint that defines the properties and behaviors of an object. Objects are instances of classes, created to store data and perform actions as defined by their class.
// Example of a class
public class Car {
String model;
int year;
public void displayInfo() {
System.out.println("Model: " + model + ", Year: " + year);
}
}
// Creating an object of the Car class
Car myCar = new Car();
Java - Class Attributes
Class attributes (or fields) store the state of each object. Attributes can be instance variables, unique to each instance, or static variables, shared across all instances.
public class Car {
String color; // Instance variable
static int wheels = 4; // Static variable, shared by all cars
}
Note: Use static variables for values that are common to all instances, like wheels in this example.
Java - Methods
Methods define the actions that objects of a class can perform. They operate on an object’s attributes and may return values.
public class Car {
public void start() {
System.out.println("Car is starting...");
}
}
Note: Methods are usually declared with an access modifier (like public) and can take parameters or return values.
Java - Methods and Overloading
Methods can be overloaded in Java by defining multiple methods with the same name but different parameters.
public class MathOperations {
public int add(int a, int b) {
return a + b;
}
// Overloaded add method
public double add(double a, double b) {
return a + b;
}
}
Note: Method overloading allows flexibility in method functionality while keeping names consistent.
Java - Variable Scope
Variables in Java have different scopes based on where they are declared: Instance variables (class level), Local variables (method level), and Parameters (method arguments).
public class ScopeExample {
int instanceVar = 10; // Instance variable
public void display() {
int localVar = 5; // Local variable
System.out.println("LocalVar: " + localVar);
}
}
Note: Local variables must be initialized before use, while instance variables have default values.
Java - Constructors
A constructor initializes new objects. It has the same name as the class and no return type. Constructors can also be overloaded to initialize objects in different ways.
public class Car {
String model;
int year;
// Constructor
public Car(String model, int year) {
this.model = model;
this.year = year;
}
}
Note: If no constructor is defined, Java provides a default, no-argument constructor.
Java - Access Modifiers
Access modifiers define the visibility of classes, methods, and variables. In Java, the main access modifiers are public, protected, default (no modifier), and private.
- Public: Accessible from any class.
- Protected: Accessible within the same package and by subclasses.
- Default: Accessible within the same package only.
- Private: Accessible only within the same class.
public class Car {
private String model; // Private access
public int year; // Public access
// Public method
public void setModel(String model) {
this.model = model;
}
}
Java - Inheritance
Inheritance is a core concept in OOP that enables one class (subclass) to inherit fields and methods from another class (superclass). This promotes code reuse and establishes an "is-a" relationship.
// Superclass
public class Vehicle {
protected int speed;
public void displaySpeed() {
System.out.println("Speed: " + speed);
}
}
// Subclass
public class Car extends Vehicle {
private String model;
public Car(String model, int speed) {
this.model = model;
this.speed = speed;
}
public void displayInfo() {
System.out.println("Model: " + model);
displaySpeed();
}
}
// Usage
Car myCar = new Car("Toyota", 120);
myCar.displayInfo(); // Output: Model: Toyota, Speed: 120
Note: Use extends to implement inheritance. The subclass inherits all accessible fields and methods from the superclass, allowing for extended functionality.
Java - Aggregation
Aggregation represents a "has-a" relationship, where one class is composed of other classes. This helps build complex types from simpler ones and promotes modularity.
// Class representing an Engine
public class Engine {
private String type;
public Engine(String type) {
this.type = type;
}
public String getType() {
return type;
}
}
// Class representing a Car that has an Engine
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void displayEngineType() {
System.out.println("Engine Type: " + engine.getType());
}
}
// Usage
Engine v8Engine = new Engine("V8");
Car myCar = new Car(v8Engine);
myCar.displayEngineType(); // Output: Engine Type: V8
Note: Aggregation is used when a class is composed of other classes, enhancing code modularity and structure.
Java - Polymorphism
Polymorphism in Java allows one interface or method to behave differently based on the objects that call it. Java supports runtime polymorphism through method overriding and compile-time polymorphism through method overloading.
Here’s an example of polymorphism using method overriding:
// Superclass
public class Animal {
public void sound() {
System.out.println("Animal makes a sound");
}
}
// Subclass Dog overrides sound()
public class Dog extends Animal {
@Override
public void sound() {
System.out.println("Dog barks");
}
}
// Subclass Cat overrides sound()
public class Cat extends Animal {
@Override
public void sound() {
System.out.println("Cat meows");
}
}
// Usage
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.sound(); // Output: Dog barks
myCat.sound(); // Output: Cat meows
Note: Polymorphism allows for flexible and scalable code by enabling a single method or interface to have multiple forms.
Java - Method Overriding
Method Overriding occurs when a subclass provides a specific implementation for a method that is already defined in its superclass. This allows the subclass to offer customized behavior.
// Superclass
public class Vehicle {
public void start() {
System.out.println("Vehicle is starting");
}
}
// Subclass
public class Car extends Vehicle {
@Override
public void start() {
System.out.println("Car is starting");
}
}
// Usage
Vehicle myVehicle = new Vehicle();
Car myCar = new Car();
myVehicle.start(); // Output: Vehicle is starting
myCar.start(); // Output: Car is starting
Note: Method overriding requires the @Override annotation to indicate that a method is intentionally being overridden.
Java - Method Overloading
Method Overloading allows a class to have multiple methods with the same name but different parameter lists (number, type, or order of parameters). This enhances flexibility by enabling methods to perform similar tasks with varying inputs.
public class Calculator {
// Method to add two integers
public int add(int a, int b) {
return a + b;
}
// Overloaded method to add three integers
public int add(int a, int b, int c) {
return a + b + c;
}
}
// Usage
Calculator calc = new Calculator();
int result1 = calc.add(2, 3); // Output: 5
int result2 = calc.add(2, 3, 4); // Output: 9
Note: Method overloading provides convenience by allowing similar operations to be performed with different sets of parameters.
Java - Dynamic Binding
Dynamic Binding, or late binding, occurs when method calls are resolved at runtime based on the object type rather than the reference type. This enables polymorphism, where different classes can provide their own implementations of an overridden method.
// Superclass
public class Animal {
public void sound() {
System.out.println("Animal sound");
}
}
// Subclass Dog overrides sound()
public class Dog extends Animal {
@Override
public void sound() {
System.out.println("Bark");
}
}
// Subclass Cat overrides sound()
public class Cat extends Animal {
@Override
public void sound() {
System.out.println("Meow");
}
}
// Usage
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.sound(); // Output: Bark (Dynamic Binding)
myCat.sound(); // Output: Meow (Dynamic Binding)
Note: Dynamic binding allows methods to be resolved at runtime, making code more flexible. This is commonly used in polymorphism, where subclasses provide different implementations of the same method.
Java - Static Binding
Static Binding, or early binding, is when method calls or fields are resolved at compile-time. Static binding is applied to static methods, private methods, and final methods because their implementations are known at compile-time.
public class StaticExample {
public static void printMessage() {
System.out.println("Static method");
}
private void privateMethod() {
System.out.println("Private method");
}
public static void main(String[] args) {
StaticExample.printMessage(); // Output: Static method (Static Binding)
StaticExample example = new StaticExample();
example.privateMethod(); // Output: Private method (Static Binding)
}
}
Note: Static binding is determined during compile-time for methods that do not participate in polymorphism. Private, final, and static methods are resolved using static binding.
Java - Instance Initializer Block
Instance Initializer Block is used to initialize instance variables in a Java class. This block runs each time an instance of the class is created, allowing for complex initializations beyond constructors. It executes before the constructor code.
public class InstanceInitializerExample {
private int value;
// Instance Initializer Block
{
value = 10;
System.out.println("Instance initializer block executed, value set to 10");
}
// Constructor
public InstanceInitializerExample() {
System.out.println("Constructor executed");
}
public static void main(String[] args) {
InstanceInitializerExample example = new InstanceInitializerExample();
// Output:
// Instance initializer block executed, value set to 10
// Constructor executed
}
}
Note: Instance initializer blocks are especially useful for code that is shared across multiple constructors. They execute each time an object is created, even before the constructor.
Java - Abstraction
Abstraction hides implementation details and only exposes the functionality. In Java, abstraction is achieved using abstract classes and interfaces. Abstract classes allow concrete methods along with abstract methods, while interfaces (Java 8+) can also have default and static methods.
// Abstract class
public abstract class Shape {
abstract void draw();
public void commonMethod() {
System.out.println("Common functionality for all shapes");
}
}
// Subclass Circle implementing draw() method
public class Circle extends Shape {
@Override
public void draw() {
System.out.println("Drawing a Circle");
}
}
// Usage
Shape myShape = new Circle();
myShape.draw(); // Output: Drawing a Circle
myShape.commonMethod(); // Output: Common functionality for all shapes
Note: Abstract classes provide partial abstraction by allowing a mix of abstract and non-abstract methods, making them suitable for base classes with shared functionality. Interfaces (since Java 8) can contain default methods, allowing a form of multiple inheritance.
Java - Encapsulation
Encapsulation is a fundamental OOP principle that involves bundling data (attributes) and methods that operate on the data within a single unit, typically a class. Encapsulation restricts direct access to an object's internals by using access modifiers, primarily achieved through getter and setter methods.
public class BankAccount {
// Private fields to restrict direct access
private double balance;
// Constructor
public BankAccount(double initialBalance) {
if (initialBalance >= 0) {
this.balance = initialBalance;
}
}
// Getter for balance
public double getBalance() {
return balance;
}
// Setter for balance with validation
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
// Withdraw method
public boolean withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return true;
}
return false;
}
}
Note: Encapsulation secures data and allows validation logic to be added to the setter methods, ensuring consistency and protecting against invalid states.
Java - Interfaces
An Interface in Java is a reference type that defines a contract. Classes that implement an interface must provide implementations for all methods declared in the interface. Interfaces are central to achieving abstraction and multiple inheritance in Java.
// Interface definition
public interface Payment {
void processPayment(double amount);
}
// Class implementing the interface
public class CreditCardPayment implements Payment {
@Override
public void processPayment(double amount) {
System.out.println("Processing credit card payment of $" + amount);
}
}
// Another class implementing the interface
public class PayPalPayment implements Payment {
@Override
public void processPayment(double amount) {
System.out.println("Processing PayPal payment of $" + amount);
}
}
// Usage
Payment payment = new CreditCardPayment();
payment.processPayment(100.0);
Note: Java 8 and later versions allow default and static methods in interfaces, enabling some implementation while retaining multiple inheritance.
Java - Packages
Packages are namespaces that group related classes and interfaces. They help organize code, avoid naming conflicts, and control access. To use a package, it must be declared at the top of a file using the `package` keyword, and you can import it into other classes as needed.
// File: src/com/mycompany/account/BankAccount.java
package com.mycompany.account;
public class BankAccount {
private double balance;
public BankAccount(double balance) {
this.balance = balance;
}
public double getBalance() {
return balance;
}
}
// Another file importing the BankAccount class
import com.mycompany.account.BankAccount;
public class Main {
public static void main(String[] args) {
BankAccount account = new BankAccount(500.0);
System.out.println("Balance: " + account.getBalance());
}
}
Note: The package structure typically reflects the directory structure. Packages provide a way to encapsulate code modules, making it easier to manage large projects.
Java - Inner Classes
Inner Classes are classes defined within other classes. Inner classes have access to the members of the outer class, even if they are private. Java supports several types of inner classes: member inner classes, static nested classes, local inner classes, and anonymous inner classes.
Member Inner Class
Member inner classes are non-static classes defined inside a class. They can access all members of the outer class.
public class OuterClass {
private String message = "Hello from Outer Class";
// Inner class
public class InnerClass {
public void printMessage() {
System.out.println(message); // Accessing outer class's private field
}
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();
inner.printMessage(); // Output: Hello from Outer Class
}
}
Static Nested Class
A Static Nested Class is a static inner class that does not have access to non-static members of the outer class.
public class OuterClass {
private static String message = "Hello from Static Nested Class";
// Static nested class
public static class StaticNestedClass {
public void printMessage() {
System.out.println(message);
}
}
public static void main(String[] args) {
OuterClass.StaticNestedClass nested = new OuterClass.StaticNestedClass();
nested.printMessage(); // Output: Hello from Static Nested Class
}
}
Anonymous Inner Class
An Anonymous Inner Class is a one-time-use inner class without a name, typically used when extending a class or implementing an interface.
abstract class Greeting {
abstract void greet();
}
public class Main {
public static void main(String[] args) {
Greeting greeting = new Greeting() {
@Override
void greet() {
System.out.println("Hello from Anonymous Inner Class");
}
};
greeting.greet(); // Output: Hello from Anonymous Inner Class
}
}
Note: Inner classes enhance encapsulation and organization, especially when certain classes are tightly coupled with their outer class.
Java OOPs Mics
Java - Wrapper Classes
Wrapper Classes in Java are used to convert primitive data types into objects. Each primitive type (like int, char, double, etc.) has a corresponding wrapper class (Integer, Character, Double, etc.). These wrapper classes are part of the java.lang package and provide useful methods for converting between primitive types and strings, as well as performing operations on them.
public class WrapperClassExample {
public static void main(String[] args) {
// Using Integer Wrapper Class to convert int to Integer object
int primitiveValue = 100;
Integer integerObject = Integer.valueOf(primitiveValue);
// Using Integer methods
String str = "200";
int parsedValue = Integer.parseInt(str);
System.out.println("Parsed value: " + parsedValue); // Output: Parsed value: 200
// Auto-boxing: automatically converting primitive to object
Integer autoBoxedValue = 300;
System.out.println("Auto-boxed Integer: " + autoBoxedValue); // Output: Auto-boxed Integer: 300
}
}
Note: Wrapper classes allow primitive data types to be used in contexts that require objects, such as collections. They also provide utility methods for parsing strings into numbers and performing arithmetic operations.
Java - Enums
An Enum in Java is a special data type that represents a group of constants. Enums are more powerful than simple constants because they can have fields, methods, and constructors, just like normal classes.
Enums are typically used when you have a fixed set of related constants, such as days of the week, months of the year, or directions (North, South, East, West).
public enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
// Adding a method to the Enum
public String getDayType() {
if (this == SUNDAY || this == SATURDAY) {
return "Weekend";
} else {
return "Weekday";
}
}
}
public class Main {
public static void main(String[] args) {
// Using an Enum constant
Day today = Day.MONDAY;
System.out.println("Today is " + today);
System.out.println("Today is a " + today.getDayType()); // Output: Today is a Weekday
}
}
Note: Enums in Java are more than just constants—they are full-fledged classes that can have fields and methods. Enums are implicitly final, meaning they can't be subclassed.
Java - Enum Constructor
Java Enums can also have constructors to assign specific values to their constants. By defining a constructor, we can pass parameters to each Enum constant at the time of declaration.
Enums can have multiple fields, which can be initialized via the constructor. This allows for more complex data structures.
public enum Day {
SUNDAY("Weekend"), MONDAY("Weekday"), TUESDAY("Weekday"), WEDNESDAY("Weekday"),
THURSDAY("Weekday"), FRIDAY("Weekday"), SATURDAY("Weekend");
private String dayType;
// Enum constructor
Day(String dayType) {
this.dayType = dayType;
}
// Getter method for the field
public String getDayType() {
return dayType;
}
}
public class Main {
public static void main(String[] args) {
// Accessing Enum constants and their values
Day today = Day.MONDAY;
System.out.println("Today is " + today + " and it's a " + today.getDayType());
// Output: Today is MONDAY and it's a Weekday
}
}
Note: In this example, each Enum constant is passed a string indicating whether the day is a "Weekend" or "Weekday". This is accomplished using a constructor that is invoked during Enum constant creation.
Java - Enum Strings
You can associate specific strings or values with each Enum constant, and you can also easily convert Enum constants to strings or parse strings to Enum constants.
public enum Direction {
NORTH("North"), EAST("East"), SOUTH("South"), WEST("West");
private String direction;
Direction(String direction) {
this.direction = direction;
}
// Getter method for the string value
public String getDirection() {
return direction;
}
// Static method to get Enum constant from string
public static Direction fromString(String text) {
for (Direction d : Direction.values()) {
if (d.direction.equalsIgnoreCase(text)) {
return d;
}
}
return null;
}
}
public class Main {
public static void main(String[] args) {
Direction north = Direction.NORTH;
System.out.println("Direction: " + north.getDirection()); // Output: Direction: North
// Converting String to Enum constant
Direction dir = Direction.fromString("East");
System.out.println("Parsed Direction: " + dir); // Output: Parsed Direction: EAST
}
}
Note: Here, we have associated a string with each Enum constant (e.g., "North", "East"). We also provide a static method fromString() that allows us to parse a string and get the corresponding Enum constant.
Java - Boolean Class
The Boolean class wraps the boolean primitive value in an object. It provides methods to convert a string to a boolean and to perform logical operations.
public class BooleanExample {
public static void main(String[] args) {
Boolean trueValue = Boolean.valueOf("true"); // Converts string to boolean
Boolean falseValue = Boolean.valueOf("false"); // Converts string to boolean
System.out.println("True Value: " + trueValue); // Output: true
System.out.println("False Value: " + falseValue); // Output: false
}
}
Note: The Boolean class provides the valueOf() method to convert strings like "true" or "false" to their respective boolean values. You can also use TRUE and FALSE constants from the Boolean class.
Java - Character Class
The Character class wraps the char primitive value in an object. It provides methods for performing operations on characters like checking if a character is a letter or digit, converting to uppercase or lowercase, and more.
public class CharacterExample {
public static void main(String[] args) {
char c = 'a';
// Checking if character is a letter
boolean isLetter = Character.isLetter(c);
System.out.println("Is letter: " + isLetter); // Output: true
// Converting character to uppercase
char upperCase = Character.toUpperCase(c);
System.out.println("Uppercase: " + upperCase); // Output: A
}
}
Note: The Character class offers utility methods such as isLetter(), isDigit(), toUpperCase(), and toLowerCase() to manipulate characters.
Java - Arrays
The Arrays class provides utility methods to operate on arrays. It includes methods for sorting, searching, comparing, and filling arrays, among others.
import java.util.Arrays;
public class ArraysExample {
public static void main(String[] args) {
int[] arr = {5, 3, 8, 1};
// Sorting the array
Arrays.sort(arr);
System.out.println("Sorted Array: " + Arrays.toString(arr)); // Output: [1, 3, 5, 8]
// Searching for an element
int index = Arrays.binarySearch(arr, 3);
System.out.println("Index of 3: " + index); // Output: 1
}
}
Note: The Arrays class provides many useful methods like sort(), binarySearch(), fill(), equals(), etc. for working with arrays.
Java - Math Class
The Math class contains methods for performing basic numeric operations such as exponentiation, logarithms, square roots, and trigonometric functions.
public class MathExample {
public static void main(String[] args) {
double sqrtVal = Math.sqrt(25);
double powVal = Math.pow(2, 3);
double maxVal = Math.max(10, 20);
double minVal = Math.min(10, 20);
System.out.println("Square root of 25: " + sqrtVal); // Output: 5.0
System.out.println("2 raised to the power of 3: " + powVal); // Output: 8.0
System.out.println("Maximum value: " + maxVal); // Output: 20.0
System.out.println("Minimum value: " + minVal); // Output: 10.0
}
}
Note: The Math class provides methods like sqrt(), pow(), max(), min(), abs(), and many others for performing mathematical operations.
Java File Handling
In Java, file handling is an essential skill for dealing with files and directories. Using classes from the java.io and java.nio packages, you can perform tasks such as creating, reading, writing, and deleting files, as well as handling I/O streams for processing data.
Java - Files
Java provides the File class from the java.io package for creating, deleting, and manipulating files. The File class allows you to work with both files and directories.
import java.io.File;
public class FileExample {
public static void main(String[] args) {
File file = new File("example.txt");
// Check if file exists
if(file.exists()) {
System.out.println("File exists.");
} else {
System.out.println("File does not exist.");
}
}
}
Note: The File class allows you to check whether a file exists using the exists() method. You can also perform other operations like renaming, checking the file's length, or checking if it's a directory.
Java - Create a File
You can create a file in Java using the createNewFile() method of the File class. This method returns true if the file was successfully created, and false if the file already exists.
import java.io.File;
import java.io.IOException;
public class CreateFileExample {
public static void main(String[] args) {
File file = new File("newFile.txt");
try {
if(file.createNewFile()) {
System.out.println("File created: " + file.getName());
} else {
System.out.println("File already exists.");
}
} catch (IOException e) {
System.out.println("An error occurred.");
e.printStackTrace();
}
}
}
Note: The createNewFile() method creates a new file if it doesn't already exist and throws an IOException if the operation fails.
Java - Write to File
You can write to a file using classes such as FileWriter or BufferedWriter in Java. These classes allow you to write text data to a file.
import java.io.FileWriter;
import java.io.IOException;
public class WriteToFileExample {
public static void main(String[] args) {
try {
FileWriter writer = new FileWriter("output.txt");
writer.write("Hello, Java File Handling!");
writer.close();
System.out.println("Successfully wrote to the file.");
} catch (IOException e) {
System.out.println("An error occurred.");
e.printStackTrace();
}
}
}
Note: The FileWriter class allows you to write data to a file. If the file does not exist, it will be created.
Java - Read Files
You can read the contents of a file in Java using classes like FileReader and BufferedReader. These classes allow you to read text from a file line by line.
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;
public class ReadFromFileExample {
public static void main(String[] args) {
try {
FileReader reader = new FileReader("output.txt");
BufferedReader bufferedReader = new BufferedReader(reader);
String line = bufferedReader.readLine();
while (line != null) {
System.out.println(line);
line = bufferedReader.readLine();
}
bufferedReader.close();
} catch (IOException e) {
System.out.println("An error occurred.");
e.printStackTrace();
}
}
}
Note: The BufferedReader class is used to read data from a file in an efficient manner, and readLine() reads each line of the file.
Java - Delete Files
To delete a file, you can use the delete() method of the File class. This method returns true if the file was successfully deleted, or false if the file could not be deleted.
import java.io.File;
public class DeleteFileExample {
public static void main(String[] args) {
File file = new File("output.txt");
if(file.delete()) {
System.out.println("File deleted successfully.");
} else {
System.out.println("Failed to delete the file.");
}
}
}
Note: The delete() method deletes the file if it exists and returns a boolean indicating whether the operation was successful.
Java - Directories
Java provides methods for creating and managing directories. The mkdir() method creates a single directory, while mkdirs() can create a directory along with any necessary parent directories.
import java.io.File;
public class DirectoryExample {
public static void main(String[] args) {
File dir = new File("newDirectory");
if(dir.mkdir()) {
System.out.println("Directory created successfully.");
} else {
System.out.println("Failed to create the directory.");
}
}
}
Note: The mkdir() method creates only one directory, while mkdirs() creates the entire path if it does not exist.
Java - I/O Streams
Java uses I/O streams to read and write data. There are two main types of streams:
- Byte Streams: For handling binary data (e.g., FileInputStream, FileOutputStream).
- Character Streams: For handling text data (e.g., FileReader, FileWriter).
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt")) {
int content;
while ((content = fis.read()) != -1) {
fos.write(content);
}
System.out.println("File copied successfully.");
} catch (IOException e) {
System.out.println("An error occurred.");
e.printStackTrace();
}
}
}
Note: Byte streams are useful for handling binary data, while character streams are better suited for text data.
Java Error & Exceptions
Error handling is an essential part of writing robust Java applications. In Java, exceptions represent runtime errors that occur during the execution of a program, and they can be caught and handled using mechanisms like try-catch, finally, and throw. Let's explore how to handle and propagate errors in Java.
Java - Exceptions
An exception in Java is an event that disrupts the normal flow of the program. When an exceptional condition occurs, the JVM creates an exception object and hands it over to the runtime system for handling.
public class ExceptionExample {
public static void main(String[] args) {
try {
int result = 10 / 0; // This will throw ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Exception: " + e);
}
}
}
In this example, a division by zero throws an ArithmeticException, which is caught by the catch block.
Java - try-catch Block
The try-catch block is used to handle exceptions in Java. The try block contains code that might throw an exception, and the catch block handles the exception when it occurs.
public class TryCatchExample {
public static void main(String[] args) {
try {
String str = null;
System.out.println(str.length()); // This will throw NullPointerException
} catch (NullPointerException e) {
System.out.println("Exception caught: " + e);
}
}
}
In this example, trying to call length() on a null string results in a NullPointerException, which is caught and handled by the catch block.
Java - try-with-resources
The try-with-resources statement, introduced in Java 7, ensures that resources like files, streams, or database connections are closed automatically after usage. Resources that implement AutoCloseable or Closeable interfaces can be used in this block.
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;
public class TryWithResourcesExample {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.println("An error occurred: " + e);
}
}
}
In this example, the BufferedReader is automatically closed when the try block finishes execution, whether or not an exception occurs.
Java - Multi-catch Block
Java 7 introduced multi-catch blocks, allowing multiple exceptions to be caught in a single catch block. This can simplify your code when you want to handle multiple exception types in the same way.
public class MultiCatchExample {
public static void main(String[] args) {
try {
int[] arr = new int[5];
arr[10] = 50; // ArrayIndexOutOfBoundsException
} catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {
System.out.println("Exception caught: " + e);
}
}
}
In this example, both ArithmeticException and ArrayIndexOutOfBoundsException are caught by the same catch block.
Java - Nested try Block
You can place a try-catch block inside another try-catch block. This is known as a nested try block and is useful for handling different exceptions at different levels.
public class NestedTryCatchExample {
public static void main(String[] args) {
try {
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Inner exception: " + e);
}
} catch (Exception e) {
System.out.println("Outer exception: " + e);
}
}
}
In this example, the inner try-catch block catches the ArithmeticException, while the outer block catches other exceptions.
Java - Finally Block
The finally block in Java is used to execute code after a try-catch block, regardless of whether an exception was thrown or not. It's typically used to close resources like files or database connections.
public class FinallyExample {
public static void main(String[] args) {
try {
int result = 10 / 2;
} catch (ArithmeticException e) {
System.out.println("Exception: " + e);
} finally {
System.out.println("This will always be executed.");
}
}
}
Even if an exception occurs or doesn't occur, the finally block will always execute, making it a good place for cleanup tasks.
Java - throw Exception
The throw keyword in Java is used to explicitly throw an exception. This can be useful for creating custom exceptions or rethrowing an existing exception.
public class ThrowExample {
public static void main(String[] args) {
try {
throw new ArithmeticException("Explicitly thrown exception");
} catch (ArithmeticException e) {
System.out.println("Caught exception: " + e);
}
}
}
The throw keyword allows you to throw exceptions manually, which can be caught and handled in a catch block.
Java - Exception Propagation
Exception propagation refers to the process by which an exception is passed from one method to another. In Java, an exception can propagate up the call stack until it is caught.
public class ExceptionPropagationExample {
public static void method1() {
throw new ArithmeticException("Exception in method1");
}
public static void method2() {
method1();
}
public static void main(String[] args) {
try {
method2();
} catch (ArithmeticException e) {
System.out.println("Exception propagated: " + e);
}
}
}
In this example, the exception thrown in method1() is propagated to method2() and caught in the main() method.
Java - Built-in Exceptions
Java has several built-in exceptions, such as ArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException, etc. These exceptions are thrown by the JVM when it encounters specific error conditions.
Java - Custom Exception
In Java, you can create your own custom exception by extending the Exception class. Custom exceptions allow you to define error conditions specific to your application.
public class CustomExceptionExample {
static class InvalidAgeException extends Exception {
public InvalidAgeException(String message) {
super(message);
}
}
public static void main(String[] args) {
try {
throw new InvalidAgeException("Age must be above 18");
} catch (InvalidAgeException e) {
System.out.println("Caught: " + e.getMessage());
}
}
}
In this example, a custom exception InvalidAgeException is created and thrown when the condition is violated.
Java Multithreading
Java Multithreading enables concurrent execution of two or more parts of a program, known as threads. By utilizing multithreading, we can execute code more efficiently and improve the performance of applications. In this tutorial, we will explore how to create and manage threads in Java, understand the thread lifecycle, and manage thread execution.
Java - Multithreading
Multithreading allows Java to perform multiple tasks concurrently. Each task in Java is represented by a thread, and all the threads run in parallel, sharing CPU resources. Threads are lightweight and run within a process, allowing more efficient use of the CPU compared to running multiple processes.
public class MultithreadingExample {
public static void main(String[] args) {
// Creating a thread by implementing the Runnable interface
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Thread is running");
}
};
Thread thread = new Thread(task);
thread.start(); // Starting the thread
}
}
This example demonstrates creating and starting a thread in Java using the Runnable interface.
Java - Thread Life Cycle
A thread in Java follows a specific lifecycle during its execution. The lifecycle of a thread can be in any of the following states:
- New: When a thread is created but not yet started.
- Runnable: After calling the
start()method, the thread is ready for execution, but it may not be running yet. - Blocked: When a thread is waiting to acquire a lock or resource that is being used by another thread.
- Waiting: A thread is in a waiting state when it is waiting indefinitely for another thread to perform a particular action.
- Timed Waiting: When a thread is waiting for a specific period.
- Terminated: A thread enters this state when it finishes executing or is terminated by calling the
stop()method.
Java - Creating a Thread
You can create a thread in Java by either extending the Thread class or implementing the Runnable interface. Both methods have their uses, and we'll discuss both approaches below.
Using Thread class
// Creating a thread by extending the Thread class
public class MyThread extends Thread {
public void run() {
System.out.println("Thread is running using Thread class");
}
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // Starting the thread
}
}
In this example, the run() method is overridden in a subclass of the Thread class to define the task to be performed by the thread. The thread is then started using the start() method.
Using Runnable interface
// Creating a thread by implementing the Runnable interface
public class RunnableExample implements Runnable {
public void run() {
System.out.println("Thread is running using Runnable interface");
}
public static void main(String[] args) {
RunnableExample task = new RunnableExample();
Thread t = new Thread(task);
t.start(); // Starting the thread
}
}
In this example, the run() method is implemented in a class that implements the Runnable interface. A new Thread is created by passing the instance of the Runnable class, and the thread is started using the start() method.
Java - Starting a Thread
To start a thread in Java, you need to call the start() method on an instance of the Thread class. The start() method invokes the run() method, which contains the code to be executed by the thread.
// Starting a thread
public class StartThreadExample {
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Thread started and running");
};
Thread t = new Thread(task);
t.start(); // The thread will now execute concurrently
}
}
In this example, we create a thread and start it using the start() method. The thread will begin execution concurrently with the main program.
Java - Joining Threads
The join() method in Java is used to pause the execution of the current thread until the thread on which join() is called finishes its execution.
// Example of joining threads
public class JoinExample {
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
System.out.println("Thread is running");
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join(); // Main thread will wait for t1 to finish
t2.join(); // Main thread will wait for t2 to finish
System.out.println("Both threads are finished");
}
}
In this example, the main thread waits for both t1 and t2 to finish execution by calling the join() method.
Java - Naming Threads
You can give a name to a thread for easier identification, especially when debugging multithreaded programs. This can be done using the setName() method.
// Example of naming a thread
public class NamingThreadExample {
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Thread " + Thread.currentThread().getName() + " is running");
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.setName("Thread-1");
t2.setName("Thread-2");
t1.start();
t2.start();
}
}
In this example, we set custom names for the threads using the setName() method. Each thread prints its name during execution.
Java - Thread Scheduler
The Thread Scheduler is part of the Java runtime system responsible for managing the execution of threads. It decides which thread to run, when to run it, and for how long. It works based on a preemptive scheduling model (where the scheduler forcibly takes control of the CPU from one thread and gives it to another) or a time-sharing model (where threads are given fixed time slices).
The thread scheduler determines the execution order of threads based on various factors such as priority, thread state, and whether the thread is in the ready queue or blocked waiting for resources.
Java - Thread Pools
In Java, creating and managing a large number of threads can be inefficient, especially for applications that need to perform many short-lived tasks. A Thread Pool is a collection of threads that are managed by a ThreadPoolExecutor in the java.util.concurrent package.
A thread pool reduces the overhead of thread creation and destruction by reusing existing threads for multiple tasks. It can help manage system resources more efficiently, avoid thread creation overhead, and limit the number of concurrent threads.
// Example of using a thread pool in Java
import java.util.concurrent.*;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(4); // Creating a thread pool with 4 threads
Runnable task = () -> {
System.out.println("Task executed by thread: " + Thread.currentThread().getName());
};
for (int i = 0; i < 10; i++) {
executor.submit(task); // Submitting tasks to the thread pool
}
executor.shutdown(); // Shutting down the executor
}
}
In this example, a fixed-size thread pool is created using the Executors.newFixedThreadPool() method. Tasks are submitted to the pool for execution.
Java - Main Thread
Every Java program begins execution from a special thread known as the Main Thread. It is the thread that starts executing when the program begins. The main thread is responsible for managing and running the application, and it can spawn additional threads for parallel execution.
Once all tasks are complete, the main thread terminates, which causes the termination of the entire program. If other threads are still running, the Java Virtual Machine (JVM) waits for them to complete before shutting down.
Java - Thread Priority
In Java, you can set the priority of a thread using the Thread.setPriority() method. A thread's priority helps the thread scheduler determine the order in which threads should be executed.
Java threads have a priority ranging from Thread.MIN_PRIORITY (1) to Thread.MAX_PRIORITY (10), with the default priority being Thread.NORM_PRIORITY (5).
// Example of setting thread priority
public class ThreadPriorityExample {
public static void main(String[] args) {
Thread highPriorityThread = new Thread(() -> {
System.out.println("High-priority thread is running");
});
highPriorityThread.setPriority(Thread.MAX_PRIORITY); // Setting highest priority
Thread lowPriorityThread = new Thread(() -> {
System.out.println("Low-priority thread is running");
});
lowPriorityThread.setPriority(Thread.MIN_PRIORITY); // Setting lowest priority
highPriorityThread.start();
lowPriorityThread.start();
}
}
In this example, two threads are created with different priorities: one with the highest priority and the other with the lowest priority.
Java - Daemon Threads
A Daemon Thread is a special type of thread that runs in the background, typically for performing low-priority tasks. It is automatically terminated when all non-daemon threads finish executing. The JVM does not wait for daemon threads to finish before it shuts down.
Daemon threads are useful for tasks like garbage collection, monitoring, or handling background services.
// Example of creating a daemon thread
public class DaemonThreadExample {
public static void main(String[] args) {
Thread daemonThread = new Thread(() -> {
while (true) {
System.out.println("Daemon thread is running");
}
});
daemonThread.setDaemon(true); // Marking the thread as a daemon
daemonThread.start();
System.out.println("Main thread is running");
}
}
In this example, a daemon thread is created by calling setDaemon(true). The daemon thread will keep running in the background until all non-daemon threads finish.
Java - Thread Group
A Thread Group is a group of threads that share a common parent. Thread groups help in organizing and managing threads, especially when dealing with large numbers of threads.
Thread groups allow you to manage sets of threads collectively (e.g., interrupting or killing multiple threads at once).
// Example of creating and using a thread group
public class ThreadGroupExample {
public static void main(String[] args) {
ThreadGroup group = new ThreadGroup("MyThreadGroup");
Runnable task = () -> {
System.out.println("Thread is running in group: " + Thread.currentThread().getThreadGroup().getName());
};
Thread t1 = new Thread(group, task);
Thread t2 = new Thread(group, task);
t1.start();
t2.start();
}
}
In this example, we create a thread group named "MyThreadGroup" and assign two threads to that group. Each thread prints the name of the group it belongs to.
Java - Shutdown Hook
A Shutdown Hook is a special thread that runs when the JVM shuts down (e.g., when the program terminates or when the user presses Ctrl+C). This can be useful for performing clean-up operations like releasing resources, closing files, or logging shutdown details.
// Example of using a shutdown hook
public class ShutdownHookExample {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("JVM is shutting down. Performing cleanup.");
}));
System.out.println("Program is running");
}
}
The shutdown hook is added to the JVM using addShutdownHook(). The hook runs when the program is about to terminate.
Java Synchronization
Java synchronization is an essential concept for handling concurrent programming. By synchronizing code sections, you can ensure that only one thread accesses shared resources at a time. This tutorial explores the key concepts of synchronization, including block synchronization, static synchronization, inter-thread communication, deadlock, thread interruption, thread control, and reentrant monitors.
Java - Synchronization
Synchronization is used in Java to control the access of multiple threads to shared resources. Without synchronization, multiple threads could modify shared variables simultaneously, causing inconsistent results. The synchronized keyword is used to enforce mutual exclusion.
// Example of synchronization
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
public class SyncExample {
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
t1.start();
t2.start();
}
}
In this example, the increment() method is synchronized, ensuring that only one thread can modify the counter at a time.
Java - Block Synchronization
Block Synchronization is a more fine-grained approach than synchronizing entire methods. It allows you to lock a specific block of code, making the synchronization more efficient when only certain parts of a method require thread safety.
// Example of block synchronization
class Counter {
private int count = 0;
public void increment() {
synchronized(this) {
count++;
}
}
public synchronized int getCount() {
return count;
}
}
Here, synchronization is applied only to the block inside the increment() method, ensuring that only the increment operation is thread-safe.
Java - Static Synchronization
Static Synchronization is used when synchronizing a class method rather than an instance method. This is useful when multiple threads are trying to access a static method and you want to ensure that only one thread can execute the static method at a time.
// Example of static synchronization
class Counter {
private static int count = 0;
public static synchronized void increment() {
count++;
}
public static synchronized int getCount() {
return count;
}
}
The increment() and getCount() methods are static and synchronized. Only one thread at a time can access these methods, even if they belong to different instances.
Java - Inter-thread Communication
Inter-thread communication refers to the exchange of information between threads, often necessary when one thread needs to wait for another to complete a task. The wait(), notify(), and notifyAll() methods are used for this purpose.
// Example of inter-thread communication
class SharedResource {
private boolean ready = false;
public synchronized void waitForReady() throws InterruptedException {
while (!ready) {
wait();
}
}
public synchronized void setReady() {
ready = true;
notify();
}
}
In this example, one thread waits for a condition to be met by calling wait(), and the other thread can notify the waiting thread with notify().
Java - Thread Deadlock
Thread Deadlock occurs when two or more threads are blocked forever, waiting for each other to release resources. This usually happens when threads acquire multiple locks and wait for each other to release them.
To avoid deadlock, use careful resource ordering and ensure that threads do not hold multiple locks simultaneously, or use timeouts to break out of the deadlock.
// Example of deadlock
class A {
public synchronized void methodA(B b) {
b.last();
}
public synchronized void last() {}
}
class B {
public synchronized void methodB(A a) {
a.last();
}
public synchronized void last() {}
}
In this example, thread 1 locks methodA() of object A and waits for object B, while thread 2 locks methodB() of object B, resulting in a deadlock.
Java - Interrupting a Thread
Interrupting a Thread allows you to stop a thread's execution, typically when it is stuck or needs to stop early. The interrupt() method is used to request that a thread stop executing. It is the responsibility of the thread to handle its interruption and clean up accordingly.
// Example of interrupting a thread
class MyRunnable implements Runnable {
public void run() {
try {
for (int i = 0; i < 10; i++) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
System.out.println(i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Thread interrupted");
}
}
}
public class ThreadInterruptExample {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new MyRunnable());
t.start();
Thread.sleep(5000);
t.interrupt(); // Interrupt the thread after 5 seconds
}
}
In this example, we interrupt the thread after 5 seconds. The thread checks if it has been interrupted using Thread.interrupted().
Java - Thread Control
Thread Control involves managing thread states such as starting, stopping, sleeping, and waiting. Java provides several methods like sleep(), join(), and wait() to control thread execution.
// Example of thread control
public class ThreadControlExample {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("Thread started");
try {
Thread.sleep(2000); // Sleep for 2 seconds
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread resumed");
});
t.start();
t.join(); // Wait for the thread to finish
}
}
In this example, the main thread waits for the new thread to finish using join(), and the new thread sleeps for 2 seconds using sleep().
Java - Reentrant Monitor
A Reentrant Monitor allows a thread to acquire the same lock multiple times without causing a deadlock. This is useful for situations where a method calls another method that also requires synchronization.
// Example of reentrant monitor
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
if (count < 5) {
increment(); // Reentrant call
}
}
public synchronized int getCount() {
return count;
}
}
In this example, the increment() method is reentrant, allowing the thread to acquire the lock multiple times without deadlocking.
Java Networking
Networking is one of the most important aspects of modern software development, allowing applications to communicate over local networks or the internet. Java provides various classes and interfaces for working with networking protocols like TCP/IP, UDP, and HTTP. In this tutorial, we will explore Java networking concepts, including socket programming, URL processing, HTTP requests, and the use of generics in networking.
Java - Networking
Networking in Java allows you to connect two or more computers (or systems) for communication. The `java.net` package provides classes and methods to establish communication between devices over a network, whether it is through IP addressing, DNS, or more advanced protocols such as HTTP or FTP.
// Example of Java networking (Server & Client)
import java.io.*;
import java.net.*;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(5000);
System.out.println("Server is running...");
Socket socket = serverSocket.accept();
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Hello Client!");
}
}
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 5000);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println(in.readLine());
}
}
In this example, a simple server listens for incoming connections on port 5000, and a client connects to the server and receives a message.
Java - Socket Programming
Socket programming in Java allows you to establish client-server communication by creating server and client sockets. The Socket class is used to create client-side sockets, while the ServerSocket class is used to create server-side sockets.
// Example of server and client using socket programming
import java.io.*;
import java.net.*;
public class SimpleServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(1234);
System.out.println("Waiting for client...");
Socket socket = serverSocket.accept();
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Hello from the server!");
}
}
public class SimpleClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 1234);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println(in.readLine());
}
}
This example demonstrates a basic client-server communication using socket programming. The server waits for a connection, and the client connects and reads the server's message.
Java - URL Processing
URL Processing allows you to handle URLs and perform operations like parsing and retrieving content from the web. The URL class in Java provides methods to represent a URL and retrieve information about it, such as the protocol, host, and file.
// Example of URL processing in Java
import java.net.*;
public class URLExample {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://www.example.com");
System.out.println("Protocol: " + url.getProtocol());
System.out.println("Host: " + url.getHost());
System.out.println("Path: " + url.getPath());
}
}
This code demonstrates how to extract the protocol, host, and path from a URL using the URL class.
Java - URL Class
The URL class is part of the java.net package and is used to represent a Uniform Resource Locator (URL). This class provides several methods for retrieving the components of a URL (e.g., host, port, query string).
// Example of using the URL class
import java.net.*;
public class URLClassExample {
public static void main(String[] args) throws Exception {
URL url = new URL("https://www.example.com/index.html?name=value");
System.out.println("Host: " + url.getHost());
System.out.println("Path: " + url.getPath());
System.out.println("Query: " + url.getQuery());
}
}
The URL class can be used to obtain information like host, path, and query parameters, making it a valuable tool for web-related Java applications.
Java - URLConnection Class
The URLConnection class provides an abstraction for interacting with resources pointed to by a URL. It can be used to open a connection, read data from a URL, and set request properties for HTTP or FTP connections.
// Example of URLConnection in Java
import java.net.*;
import java.io.*;
public class URLConnectionExample {
public static void main(String[] args) throws Exception {
URL url = new URL("http://www.example.com");
URLConnection connection = url.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println(inputLine);
}
in.close();
}
}
This example demonstrates how to open a connection to a URL and read the content from the website. The URLConnection class allows you to customize how you interact with the URL.
Java - HttpURLConnection Class
The HttpURLConnection class is a subclass of URLConnection specifically for handling HTTP connections. It provides methods to send HTTP requests, manage response codes, and interact with HTTP headers.
// Example of using HttpURLConnection
import java.net.*;
import java.io.*;
public class HttpURLConnectionExample {
public static void main(String[] args) throws Exception {
URL url = new URL("https://www.example.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println(inputLine);
}
in.close();
}
}
This code demonstrates how to use the HttpURLConnection class to make an HTTP GET request to a URL and read the response.
Java - Socket Class
The Socket class in Java is used for establishing client-side socket communication. It enables you to send and receive data over TCP/IP protocols. The Socket class provides methods to open and close connections, send data, and read data from the connected server.
// Example of using Socket class
import java.io.*;
import java.net.*;
public class SocketClassExample {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("localhost", 5000);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Hello Server!");
socket.close();
}
}
This example shows how a client can send a message to a server using the Socket class.
Java - Generics
Generics in Java allow you to write classes, interfaces, and methods that work with any type of data. With generics, you can ensure type safety at compile time, preventing errors at runtime. Generics are commonly used in collections to specify the type of data the collection will hold.
// Example of generics with a collection
import java.util.*;
public class GenericsExample {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("Hello");
list.add("World");
for (String item : list) {
System.out.println(item);
}
}
}
This example demonstrates how to use generics with an ArrayList. The list is type-safe and can only hold String objects.
Java Collections
The Java Collections Framework (JCF) is a unified architecture that provides a set of interfaces and classes to handle various types of collections of objects. This framework is part of the java.util package, and it includes several data structures (like lists, sets, and maps) and algorithms for sorting, searching, and manipulating collections. In this tutorial, we'll take a closer look at the Collection interface and its core functionality.
Java - Collections
The Java Collections Framework provides several interfaces and classes for working with groups of objects. The main goal of this framework is to provide a standard way to handle collections of objects, such as lists, sets, and maps. It also defines methods to perform common operations like searching, sorting, and iterating over elements.
The java.util package contains several core interfaces such as List, Set, Queue, and Map, and their respective implementations (e.g., ArrayList, HashSet, PriorityQueue, and HashMap).
// Example: Using ArrayList from the Java Collections Framework
import java.util.*;
public class CollectionsExample {
public static void main(String[] args) {
List fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
// Iterating over a list
for (String fruit : fruits) {
System.out.println(fruit);
}
}
}
In this example, an ArrayList is used to store a collection of strings. The List interface allows elements to be accessed by index, while the ArrayList class provides a resizable array implementation.
Java - Collection Interface
The Collection interface is the root of the collection hierarchy in the Java Collections Framework. It defines basic methods for adding, removing, and checking the presence of elements in a collection. The Collection interface is implemented by other interfaces like List, Set, and Queue.
Some key methods of the Collection interface include:
- add(E element) - Adds an element to the collection.
- remove(Object o) - Removes a specific element from the collection.
- contains(Object o) - Checks if a specific element is present in the collection.
- size() - Returns the number of elements in the collection.
- isEmpty() - Returns true if the collection contains no elements.
- clear() - Removes all elements from the collection.
- iterator() - Returns an iterator to iterate over the collection's elements.
// Example: Using Collection interface methods
import java.util.*;
public class CollectionInterfaceExample {
public static void main(String[] args) {
Collection fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
System.out.println("Collection size: " + fruits.size()); // 3
System.out.println("Contains 'Banana': " + fruits.contains("Banana")); // true
// Iterating over elements using iterator
Iterator iterator = fruits.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
In this example, the Collection interface is used with an ArrayList to demonstrate methods like size(), contains(), and iterator(). These are common operations you can perform on collections in Java.
Java - Collection Implementations
The Java Collections Framework provides several implementations of the Collection interface. Here are some of the most commonly used implementations:
- ArrayList - Implements the
Listinterface and provides a dynamic array that can grow as needed. - HashSet - Implements the
Setinterface and stores unique elements in an unordered collection. - LinkedList - Implements both
ListandDequeinterfaces and is based on a doubly-linked list, providing fast insertion and removal. - TreeSet - Implements the
Setinterface and stores elements in a sorted order using a red-black tree. - PriorityQueue - Implements the
Queueinterface and stores elements in a priority order. - HashMap - Implements the
Mapinterface and stores key-value pairs in an unordered collection. - TreeMap - Implements the
Mapinterface and stores key-value pairs in a sorted order.
// Example: Using HashSet
import java.util.*;
public class CollectionImplementationsExample {
public static void main(String[] args) {
Set set = new HashSet<>();
set.add("Apple");
set.add("Banana");
set.add("Apple"); // Duplicate element, will not be added
System.out.println("Set contains: " + set);
}
}
In this example, a HashSet is used to store unique elements. Notice that even though "Apple" is added twice, the set only contains one "Apple" due to the set's uniqueness constraint.
Java - Collection Algorithms
The Java Collections Framework also provides a set of utility methods for performing common algorithms on collections. These methods are defined in the Collections utility class.
Some of the most commonly used algorithms include:
- sort(List<T> list) - Sorts the elements in a list.
- reverse(List<?> list) - Reverses the order of elements in a list.
- shuffle(List<?> list) - Shuffles the elements in a list randomly.
- binarySearch(List<? extends Comparable<? super T>> list, T key) - Searches for an element in a sorted list.
- max(Collection<? extends T> coll) - Returns the maximum element in a collection based on its natural ordering or a provided comparator.
- min(Collection<? extends T> coll) - Returns the minimum element in a collection.
// Example: Using Collections utility methods
import java.util.*;
public class CollectionAlgorithmsExample {
public static void main(String[] args) {
List numbers = new ArrayList<>();
numbers.add(3);
numbers.add(1);
numbers.add(2);
// Sorting the list
Collections.sort(numbers);
System.out.println("Sorted list: " + numbers);
// Finding the maximum element
int max = Collections.max(numbers);
System.out.println("Maximum element: " + max);
}
}
In this example, we use Collections.sort() to sort the list and Collections.max() to find the maximum value in the list.
Conclusion
Java’s rich set of libraries and frameworks enables developers to build scalable, maintainable, and high-performance applications across various domains.
In conclusion, Java provides a massive toolkit that extends far beyond basic programming, and as you progress in your learning, embracing these advanced tools and libraries will significantly enhance your ability to build sophisticated and efficient software solutions. With its strong community support and an ever-expanding ecosystem, Java continues to be one of the most widely used and in-demand programming languages in the world.