Table Of Contents
1. Types
Type | Size (Bits) | Default Value | Range | Example |
---|---|---|---|---|
byte |
8 | 0 | -128 to 127 | byte b = 10; |
short |
16 | 0 | -32,768 to 32,767 | short s = 100; |
int |
32 | 0 | -2,147,483,648 to 2,147,483,647 | int i = 12345; |
long |
64 | 0L | -2^63 to (2^63)-1 | long l = 123456L; |
float |
32 | 0.0f | ~7 decimal digits | float f = 3.14f; |
double |
64 | 0.0d | ~16 decimal digits | double d = 3.14; |
char |
16 | ‘\u0000’ | 0 to 65,535 (Unicode) | char c = 'A'; |
boolean |
1 bit (logical) | false |
true or false |
boolean b = true; |
1.1 Common Errors and Pitfalls
Numeric Overflow
- Cause: Exceeding the range of a numeric type.
- Example:
byte b = 127; b++; // Wraps to -128
Uninitialized Variables
- Cause: Using a local variable without initializing it.
- Example:
int x; System.out.println(x); // Compilation Error
Missing Type Parameters
- Cause: Not specifying type parameters in generics, leading to runtime issues.
- Example:
List list = new ArrayList(); // Raw type, can cause ClassCastException list.add(123); String str = (String) list.get(0); // Fails at runtime
1.2 String
public class Test {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "Hello";
String s3 = new String("Hello");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
}
}
- s1 == s2:
- Both s1 and s2 reference the same string literal “Hello”, which is stored in the string pool.
- Since both variables point to the same object in memory, s1 == s2 evaluates to true.
- s1 == s3:
- s3 is created with the new keyword, which creates a new object in memory, even though it has the same content as s1.
- Since s1 and s3 refer to different objects, s1 == s3 evaluates to false.
1.3 Integer
Use equals
to compare whether objects are logically equal - two numbers has the same value in this case.
Integer x = new Integer(5);
Integer y = new Integer(5);
System.out.println(x == y); // false (different objects in memory)
Integer z = 5;
Integer w = 5;
System.out.println(z == w); // true (cached values in range [-128, 127])
1.4 Order of Operations
- Java follows operator precedence:
- Pre/Post-Increment and Decrement (x++, –y) are evaluated first.
- Multiplication Divide (* /) is evaluated next.
- Addition Deduction (+ -) is evaluated last.
Example:
class Test {
public static void main(String[] args) {
int x = 5;
int y = 10;
int z = x++ + --y * x;
System.out.println(z); // Output: 59
}
}
2. Class
2.1 Inheritance
- Java does not support multiple inheritance with classes. A class can only extend one superclass at a time.
- This restriction avoids ambiguity problems caused by multiple inheritance, such as the Diamond Problem.
- In Java, when a class constructor is invoked, the constructor of its superclass is automatically called before the subclass constructor.
2.2 Access Modifiers (public/protected/private)
Modifier | Class | Package | Subclass | World |
---|---|---|---|---|
public |
✔️ | ✔️ | ✔️ | ✔️ |
protected |
✔️ | ✔️ | ✔️ | ❌ |
(Default) | ✔️ | ✔️ | ❌ | ❌ |
private |
✔️ | ❌ | ❌ | ❌ |
- In Java, default visibility also called package-private.
2.3 Abstract Class
Abstract classes in Java can have constructors. However, you cannot directly instantiate an abstract class using the new
keyword.
2.4 Inner Class
There are four main types of inner classes:
-
Non-Static Nested Classes (Regular Inner Classes)
- To create an instance, an instance of the outer class is required:
Outer.Inner inner = outer.new Inner();
- To create an instance, an instance of the outer class is required:
-
Static Nested Classes
- Static nested classes can access only static members of the enclosing class.
- No instance of the outer class is required to create an instance:
Outer.StaticNested nested = new Outer.StaticNested();
-
Local Inner Classes
- Local inner classes are defined inside a method or block and are scoped to that block.
- Can access final or effectively final variables of the enclosing method.
-
Anonymous Inner Classes
- Anonymous inner classes are declared and instantiated simultaneously.
- Typically used for creating one-time implementations, especially with interfaces or abstract classes.
- Example:
interface Greeting { void sayHello(); } public class Main { public static void main(String[] args) { Greeting greeting = new Greeting() { // Anonymous Inner Class @Override public void sayHello() { System.out.println("Hello from Anonymous Inner Class"); } }; greeting.sayHello(); // Output: Hello from Anonymous Inner Class } }
2.5 Static Methods
- Static methods are not overridden; they are instead hidden when redefined in a subclass.
- The method called is determined by the type of the reference variable, not the actual object type.
- This is because static methods are resolved at compile-time, not runtime.
class Parent {
public static void display() {
System.out.println("Parent static method");
}
}
class Child extends Parent {
public static void display() {
System.out.println("Child static method");
}
}
public class Test {
public static void main(String[] args) {
Parent obj = new Child();
obj.display(); // Output: Parent static method
}
}
3. Interface & Method References
- Since Java 8, interfaces can define static methods.
3.1 Default Methods
Introduced in Java 8, default methods allow interfaces to have method implementations without breaking existing implementations of those interfaces.
interface MyInterface {
default void printMessage() {
System.out.println("This is a default method.");
}
}
class MyClass implements MyInterface {}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.printMessage(); // Output: This is a default method.
}
}
3.2 @FunctionalInterface
Annotation
The @FunctionalInterface
annotation is used in Java to mark an interface as a functional interface
. A functional interface is an interface with exactly one abstract method
, making it suitable for use with lambda expressions
.
- The interface must have exactly one abstract method.
- It can have:
- Default methods (introduced in Java 8).
- Static methods.
- Methods inherited from Object (like toString(), equals(), etc.).
Examples:
@FunctionalInterface
interface Greeting {
void sayHello(String name); // Single abstract method
}
public class Main {
public static void main(String[] args) {
Greeting greeting = (name) -> System.out.println("Hello, " + name);
greeting.sayHello("Alice"); // Output: Hello, Alice
}
}
3.3 Method References
Method references were introduced in Java 8 as a shorthand for writing lambda expressions.
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
String[] names = {"Bob", "Alice", "John"};
// Using a lambda expression
Arrays.sort(names, (a, b) -> a.compareToIgnoreCase(b));
// Using a method reference
Arrays.sort(names, String::compareToIgnoreCase);
System.out.println(Arrays.toString(names)); // Output: [Alice, Bob, John]
}
}
4. Lambdas
Lambdas were also introduced in Java 8, enabling functional programming. A lambda expression is a concise way to represent a method in code.
List<String> names = Arrays.asList("Java", "Python", "C++");
names.forEach(name -> System.out.println(name));
Lambdas are commonly used in streams and functional interfaces.
5. Streams
Streams provide a functional approach to processing collections of data in Java 8 and later.
Features:
- Operate on data declaratively (e.g., filter, map).
- Can be parallelized for performance.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squared = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println(squared); // Output: [1, 4, 9, 16, 25]
6. transient
Keyword
The transient keyword is used in the context of serialization. When an object is serialized (converted to a byte stream), the transient keyword prevents certain fields from being included in the serialized data.
Example:
import java.io.*;
class User implements Serializable {
private String username;
private transient String password; // Excluded from serialization
public User(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public String toString() {
return "User{username='" + username + "', password='" + password + "'}";
}
}
public class Main {
public static void main(String[] args) throws Exception {
User user = new User("Alice", "secret123");
// Serialize the object
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"));
oos.writeObject(user);
oos.close();
// Deserialize the object
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"));
User deserializedUser = (User) ois.readObject();
ois.close();
// Password is not serialized
System.out.println(deserializedUser); // Output: User{username='Alice', password='null'}
}
}
7. volatile
Keyword
The volatile keyword is used in the context of multithreading. It ensures that a variable’s value is always read directly from main memory, making it visible to all threads.
- Volatile Does Not Ensure Atomicity:
- For operations like count++ (read-modify-write), use
synchronized
orAtomicInteger
.
- For operations like count++ (read-modify-write), use
8. Collections
There are widely used standardized implementations of common data structures.
- ArrayList
- LinkedList
- Vector
- Stack
- HashSet
- TreeSet
- Deque
- PriorityQueue
- HashMap
All the above are not thread-safe except for Vector and Stack. For those are not thread-safe, the following is a common mistake that can lead to a java.util.ConcurrentModificationException
. So, consider using the synchronized version of these implementations if modifications are required during iteration or when multiple threads are modifying the collection concurrently.
for (Integer num : list) {
if (num == 2) {
list.remove(num); // ConcurrentModificationException
}
}
for (Map.Entry<Integer, String> entry : map.entrySet()) {
if (entry.getKey() == 2) {
map.remove(entry.getKey()); // ConcurrentModificationException
}
}
9. Other Noteworthy Java Features
8.1 var Keywords
Introduced in Java 10, var allows the compiler to infer the type of variables. For long or nested types, var reduces verbosity.
Rules and Characteristics of var
- The type is determined by the compiler at compile time, not at runtime.
- Works only with local variables (inside methods, constructors, etc.).
var x = null
; is not allowed because the type cannot be inferred.- The variable must be initialized during declaration.
Map<String, List<Integer>> map = new HashMap<>();
// With var
var map = new HashMap<String, List<Integer>>();
8.2 Records
A Record is a special kind of class in Java used to store immutable data. It automatically generates:
-
Constructor for all fields.
-
Getters for each field.
-
toString()
,equals()
, andhashCode()
implementations. -
Immutability is crucial for functional programming and thread safety.
-
With Records, all fields are final by default, ensuring immutability.
-
Ideal for POJOs and Data Transfer Objects (DTOs).
public record Point(int x, int y) {}