private
variables with public
getters and setters.public class Person {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
abstract
classes and interfaces
.interface Vehicle {
void start();
}
class Animal {
void makeSound() { System.out.println("Sound"); }
}
class Dog extends Animal {
void bark() { System.out.println("Bark"); }
}
Overloading (compile-time):
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
Overriding (runtime):
class Animal {
void sound() { System.out.println("Animal sound"); }
}
class Cat extends Animal {
@Override
void sound() { System.out.println("Meow"); }
}
private
, protected
, public
, defaultthis
, super
, final
, static
, abstract
equals()
, hashCode()
, toString()
, clone()
Serializable
Shape → Circle/Rectangle
)equals()
and hashCode()
correctlysuper
keyword do?Collection (Interface) ├── List (Interface) │ ├── ArrayList │ ├── LinkedList │ ├── Vector │ └── Stack │ ├── Set (Interface) │ ├── HashSet │ ├── LinkedHashSet │ └── TreeSet │ ├── Queue (Interface) │ ├── PriorityQueue │ └── Deque (Interface) │ └── ArrayDeque │ └── Map (Interface) Not part of Collection interface ├── HashMap ├── LinkedHashMap ├── TreeMap └── Hashtable
Implementation | Description | Time Complexity |
---|---|---|
ArrayList | Resizable array | get: O(1), add: O(1) amortized |
LinkedList | Doubly-linked list | get: O(n), add: O(1) |
Vector | Synchronized ArrayList (legacy) | Thread-safe but slower |
Stack | LIFO structure (extends Vector) | push/pop: O(1) |
Implementation | Description | Time Complexity |
---|---|---|
HashSet | Hash table implementation | add/contains: O(1) |
LinkedHashSet | Maintains insertion order | Slightly slower than HashSet |
TreeSet | Red-Black tree implementation | add/contains: O(log n) |
PriorityQueue
is an exception (priority-based)Implementation | Description | Time Complexity |
---|---|---|
PriorityQueue | Heap implementation | offer/poll: O(log n) |
ArrayDeque | Resizable array deque | add/remove: O(1) |
Implementation | Description | Time Complexity |
---|---|---|
HashMap | Hash table implementation | get/put: O(1) |
LinkedHashMap | Maintains insertion/access order | Similar to HashMap |
TreeMap | Red-Black tree implementation | get/put: O(log n) |
Hashtable | Synchronized (legacy) | Thread-safe but slower |
sort()
, shuffle()
, reverse()
synchronizedCollection()
, unmodifiableCollection()
binarySearch()
, frequency()
, disjoint()
asList()
, sort()
, binarySearch()
fill()
, copyOf()
, equals()
List<String> filtered = list.stream()
.filter(s -> s.startsWith("A"))
.sorted()
.collect(Collectors.toList());
List<String> immutableList = List.of("a", "b", "c");
Set<String> immutableSet = Set.of("a", "b");
Map<String, Integer> immutableMap = Map.of("a", 1, "b", 2);
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());
List<String> list = new ArrayList<>(); // Good
ArrayList<String> list = new ArrayList<>(); // Avoid
new ArrayList<>(100); // Avoids resizing
A functional interface is an interface that contains exactly one abstract method (SAM - Single Abstract Method). They form the basis of lambda expressions in Java.
default
or static
methods@FunctionalInterface
(optional but recommended)Interface | Method Signature | Description |
---|---|---|
Predicate<T> | boolean test(T t) | Tests a condition |
Function<T,R> | R apply(T t) | Transforms input to output |
Consumer<T> | void accept(T t) | Performs operation on input |
Supplier<T> | T get() | Provides a value |
UnaryOperator<T> | T apply(T t) | Special case of Function where input/output types are same |
BiFunction<T,U,R> | R apply(T t, U u) | Takes two inputs, produces output |
@FunctionalInterface
interface StringProcessor {
String process(String input);
default void printInfo() {
System.out.println("This processes strings");
}
}
(parameters) -> { body }
// Old way (anonymous class)
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
// Lambda equivalent
Runnable r = () -> System.out.println("Hello");
Examples:
// Predicate
Predicate<Integer> isEven = n -> n % 2 == 0;
// Function
Function<String, Integer> strLength = s -> s.length();
// Consumer
Consumer<String> printer = s -> System.out.println(s);
// Supplier
Supplier<Double> randomSupplier = () -> Math.random();
// Method reference equivalent
Consumer<String> printer = System.out::println;
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.filter(name -> name.startsWith("A"))
.map(String::toUpperCase)
.forEach(System.out::println);
new Thread(() -> System.out.println("Running in thread")).start();
button.addActionListener(e -> System.out.println("Button clicked"));
Default Methods in Interfaces
@FunctionalInterface
interface Greeter {
void greet(String name);
default void greetDefault() {
greet("World");
}
}
Static Methods in Interfaces
@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
static MathOperation add() {
return (a, b) -> a + b;
}
}
// Method 1: Extending Thread class
class MyThread extends Thread {
public void run() {
System.out.println("Thread running");
}
}
// Method 2: Implementing Runnable
class MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable running");
}
}
// Usage
Thread t1 = new MyThread();
Thread t2 = new Thread(new MyRunnable());
t1.start();
t2.start();
public synchronized void increment() {
count++;
}
public void increment() {
synchronized(this) {
count++;
}
}
private volatile boolean running = true;
List<String> results = dataList.parallelStream()
.filter(item -> item.startsWith("A"))
.collect(Collectors.toList());
Ensures a class has only one instance and provides global access.
// Thread-safe implementation
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
@Builder
public class Pizza {
private String dough;
private String sauce;
private String topping;
....
}
// Usage
Pizza pizza = new Pizza.Builder()
.dough("thin crust")
.sauce("tomato")
.topping("cheese")
.build();
public interface Vehicle {
void manufacture();
}
public class Car implements Vehicle {
public void manufacture() {
System.out.println("Manufacturing Car");
}
}
public class Bike implements Vehicle {
public void manufacture() {
System.out.println("Manufacturing Bike");
}
}
public class VehicleFactory {
public Vehicle getVehicle(String type) {
if ("car".equalsIgnoreCase(type)) {
return new Car();
} else if ("bike".equalsIgnoreCase(type)) {
return new Bike();
}
return null;
}
}
A Spring Bean is just an object that is managed by the Spring IoC (Inversion of Control) container. Spring creates and manages the lifecycle and dependencies of these objects for you.
You declare a bean either through:
@Configuration
and @Bean
@Component
, @Service
, @Repository
, and @Controller
Spring handles:
By default, Spring Beans are singleton (only one instance per Spring container). But you can change the scope:
singleton
(default)prototype
(new instance every time)request
, session
, application
, etc. (in web contexts)@Component
@Scope("prototype")
public class MyBean {
// new instance every time it’s injected
}
Spring handles injecting dependencies:
@Autowired
on constructor@Autowired
on setter method@Autowired
on field (not always recommended for testability)Using Java Config:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
Using Component Scanning:
@Component
public class MyServiceImpl implements MyService {
// Spring will auto-detect and create this bean
}
You can hook into bean lifecycle:
@PostConstruct
: Method to run after bean is created@PreDestroy
: Method to run before bean is destroyed@PostConstruct
public void init() {
// Initialization logic
}
@PreDestroy
public void cleanup() {
// Cleanup logic
}
Beans are central to:
If you're doing Spring Boot development, it makes heavy use of Spring Beans behind the scenes.
Dependency Injection (DI) is a design pattern where the dependencies (objects a class needs to function) are provided rather than created by the class itself.
In Spring, DI is the cornerstone of its IoC (Inversion of Control) container. It allows for loose coupling and better testability.
Preferred way for mandatory dependencies.
@Component
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
Used for optional dependencies.
@Component
public class NotificationService {
private EmailService emailService;
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
Quick and concise, but not ideal for testing.
@Component
public class OrderService {
@Autowired
private PaymentService paymentService;
}
@Autowired
– Automatically injects dependencies@Qualifier
– Used to resolve ambiguity when multiple beans of the same type exist@Inject
– From javax.inject
, works similarly to @Autowired
@Resource
– From JSR-250, resolves dependencies by name@Component
public class MessageService {
private final Notification notification;
@Autowired
public MessageService(@Qualifier("emailNotification") Notification notification) {
this.notification = notification;
}
}
Spring's DI mechanism:
If you're using Spring Boot, DI works out of the box thanks to @SpringBootApplication
which triggers component scanning and bean creation automatically.
A Spring Controller is a class annotated with @Controller
or @RestController
that handles incoming HTTP requests and returns responses. It's part of the Spring MVC framework.
@Controller
Used in MVC-based web apps. Returns a view name (typically HTML/JSP) instead of the actual response body.
@Controller
public class HomeController {
@GetMapping("/")
public String homePage() {
return "home"; // returns home.jsp or home.html
}
}
@RestController
Shorthand for @Controller
+ @ResponseBody
. Used in RESTful services. Returns data directly (like JSON or XML).
@RestController
public class ApiController {
@GetMapping("/api/greet")
public String greet() {
return "Hello from API!";
}
}
@RequestMapping
– General-purpose for any HTTP method@GetMapping
– For GET requests@PostMapping
– For POST requests@PutMapping
– For PUT requests@DeleteMapping
– For DELETE requests@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
// logic to get user
}
@PostMapping
public User createUser(@RequestBody User user) {
// logic to create user
}
}
@PathVariable
– Extract value from URI@RequestParam
– Extract query parameters@RequestBody
– Deserialize request JSON to Java object@ResponseBody
– Serialize Java object to JSON response@GetMapping("/search")
public List<Product> search(@RequestParam String name) {
return productService.searchByName(name);
}
@RestController
@RequestMapping("/api")
public class ExampleController {
@GetMapping("/hello")
public String hello() {
return "Hello World!";
}
@PostMapping("/echo")
public String echo(@RequestBody String message) {
return "Echo: " + message;
}
}
In Spring Boot:
@SpringBootApplication
enables component scanning.@Controller
for web views, @RestController
for REST APIs.