Module 02: Java Language Fundamentals (for Frontend Developers)
Goal: Master the core syntax needed to read and understand Java code, building a mental mapping quickly by comparing with JavaScript/TypeScript.
Preface: The Relationship Between Java and JavaScript
Despite the similar names, Java and JavaScript are completely different languages. However, as a frontend developer, the programming concepts you already know (variables, functions, conditionals, loops, objects, async) will transfer directly.
Key differences at a glance:
| Feature | JavaScript / TypeScript | Java |
|---|---|---|
| Type checking | Runtime (JS) / Compile-time + Runtime (TS) | Compile-time (strongly typed) |
| Execution | Interpreted (V8 engine) | Compiled to bytecode → executed by JVM |
| Entry point | Scripts run directly / main() not required | Must have public static void main() |
| File structure | One file can export multiple things | One file can only have one public class |
| Package management | import + package.json | import + package declaration + Gradle/Maven |
| null handling | null / undefined | Only null (no undefined) |
| Classes | Optional (functional style works too) | Mandatory (everything lives inside a class) |
1. Basic Syntax Comparison
1.1 Variable Declarations
// Java: type first, variable name second — type declaration is required
String name = "Alice"; // similar to TS: let name: string = "Alice"
int age = 25; // similar to TS: let age: number = 25
boolean isActive = true; // similar to TS: let isActive: boolean = true
double price = 19.99; // floating-point uses double (64-bit)
float ratio = 0.5f; // floating-point can also use float (32-bit), requires f suffix
long timestamp = 1700000000L; // long integers require the L suffix
char letter = 'A'; // single quotes for a single character (not a string)
// Constants: use final instead of const
final String APP_NAME = "MyApp"; // similar to TS: const APP_NAME = "MyApp"
// Type inference (Java 10+): similar to TS's let x = ...
var count = 42; // compiler infers int
var message = "hello"; // compiler infers StringKey differences from JS:
- Java doesn't have
let/const/varkeyword choices (before Java 10 you must write the type; Java 10+ can usevarfor inference) - Java has no
undefined— uninitialized reference types default tonull - Java's primitive types (
int,double,boolean,char) are not objects; they have corresponding wrapper classes (Integer,Double,Boolean,Character)
1.2 Primitive Types vs Reference Types
This is one of the biggest differences between Java and JS. In JS, everything is an object; in Java, that's not the case:
// Primitive types: store the value directly, not objects
int a = 10;
int b = a; // b is a copy of a; modifying b does not affect a
b = 20;
// now a = 10, b = 20
// Reference types: store a reference (address) to the object
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1; // arr2 points to the same array
arr2[0] = 99;
// now arr1[0] is also 99, because they point to the same objectPrimitive type reference table:
| Java Primitive | Wrapper Class | JS/TS Equivalent | Default Value |
|---|---|---|---|
int | Integer | number | 0 |
long | Long | number / bigint | 0L |
double | Double | number | 0.0 |
float | Float | number | 0.0f |
boolean | Boolean | boolean | false |
char | Character | string (single char) | '\u0000' |
byte | Byte | No direct equivalent | 0 |
short | Short | No direct equivalent | 0 |
Why do wrapper classes exist? Because Java generics don't support primitive types.
List<int>is illegal — you must useList<Integer>. This is similar to the difference betweennumberandNumberin TypeScript, but in Java the distinction is more critical.
Stack vs. Heap
Understanding the Java memory model helps diagnose memory leaks and performance issues:
public void run() {
int count = 10; // primitive: stored on the stack frame
User user = new User("Zhang"); // user reference on stack, object on heap
}| Area | Stores | Lifetime | Frontend Analogy |
|---|---|---|---|
| Stack | Local variables, method parameters, primitives | Released when method returns | Function call stack |
| Heap | All new objects and arrays | Recycled by GC | JS heap memory |
Diagnostic tip:
OutOfMemoryErrorusually relates to the heap (too many objects or large images);StackOverflowErrorrelates to the stack (infinite recursion).
1.3 Strings
// Strings are immutable (same as JS)
String greeting = "Hello";
String name = "World";
// Concatenation (use + or String.format)
String message = greeting + ", " + name + "!";
String formatted = String.format("%s, %s!", greeting, name);
// Template literals (Java has no template literals — this is the closest alternative)
// JS: `Hello, ${name}! You are ${age} years old.`
// Java:
String result = String.format("Hello, %s! You are %d years old.", name, age);
// Common methods (compared with JS)
greeting.length(); // JS: greeting.length (note the parentheses)
greeting.toUpperCase(); // JS: greeting.toUpperCase()
greeting.contains("ell"); // JS: greeting.includes("ell")
greeting.startsWith("He"); // JS: greeting.startsWith("He")
greeting.indexOf("l"); // JS: greeting.indexOf("l")
greeting.substring(1, 3); // JS: greeting.substring(1, 3) → "el"
greeting.replace("l", "r"); // JS: greeting.replaceAll("l", "r")
greeting.trim(); // JS: greeting.trim()
greeting.isEmpty(); // JS: greeting.length === 0
greeting.split(","); // JS: greeting.split(",")
greeting.equals("Hello"); // ⚠️ Java uses .equals() to compare string content! Not ==⚠️ Most common pitfall: In Java,
==compares references (addresses), not values. String comparison must use.equals():javaString a = new String("hello"); String b = new String("hello"); a == b // false! compares reference addresses a.equals(b) // true! compares content
1.4 Arrays
// Declaration and initialization
int[] numbers = {1, 2, 3, 4, 5}; // direct initialization
String[] names = new String[3]; // specify length, elements default to null
int[] scores = new int[]{90, 85, 78}; // alternative syntax
// Access and modify
int first = numbers[0]; // same as JS
numbers[2] = 99; // same as JS
int length = numbers.length; // ⚠️ No parentheses! It's a property, not a method
// Iteration
for (int i = 0; i < numbers.length; i++) {
System.out.println(numbers[i]);
}
// Enhanced for loop (similar to JS's for...of)
for (int num : numbers) {
System.out.println(num);
}
// Multi-dimensional arrays
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};Note for frontend developers: Java arrays have a fixed length — you can't
push/poplike JS arrays. For dynamically-sized collections, useArrayList(see below).
2. Control Flow
2.1 Conditional Statements
// if-else: exactly the same as JS
if (age >= 18) {
System.out.println("Adult");
} else if (age >= 12) {
System.out.println("Teenager");
} else {
System.out.println("Child");
}
// Ternary operator: same as JS
String status = age >= 18 ? "Adult" : "Minor";
// switch (traditional form)
switch (day) {
case 1:
System.out.println("Monday");
break; // ⚠️ break is required, otherwise execution falls through to the next case
case 2:
System.out.println("Tuesday");
break;
default:
System.out.println("Other");
}
// switch expression (Java 14+, more modern syntax)
String dayName = switch (day) {
case 1 -> "Monday";
case 2 -> "Tuesday";
case 3 -> "Wednesday";
default -> "Other";
};2.2 Loops
// for loop: same as JS
for (int i = 0; i < 10; i++) {
if (i == 5) break;
if (i == 3) continue;
System.out.println(i);
}
// while: same as JS
while (condition) {
// ...
}
// do-while: same as JS
do {
// ...
} while (condition);3. Object-Oriented Programming (OOP)
This is the core of Java and the area with the biggest differences from JS. JS's class is just syntactic sugar; Java's class is a first-class citizen of the language.
3.1 Classes and Objects
// Defining a class
public class User {
// Fields (similar to JS class properties)
private String name; // private = cannot be accessed from outside
private int age;
private String email;
// Constructor (similar to JS's constructor)
public User(String name, int age) {
this.name = name; // this is conceptually similar to JS's this
this.age = age;
this.email = ""; // default value
}
// Methods
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setEmail(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
// Similar to JS's toString()
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
// Usage
User user = new User("Alice", 25);
System.out.println(user.getName()); // "Alice"
System.out.println(user); // automatically calls toString()Why so many getters/setters? This is a Java coding convention (the JavaBean specification). In legacy projects you'll see tons of
getXxx()/setXxx()methods. Kotlin's property syntax eliminates this boilerplate.
3.2 Access Modifiers
| Modifier | Within Class | Same Package | Subclass | Anywhere | JS/TS Equivalent |
|---|---|---|---|---|---|
public | ✅ | ✅ | ✅ | ✅ | public |
protected | ✅ | ✅ | ✅ | ❌ | protected |
| (no modifier) | ✅ | ✅ | ❌ | ❌ | No direct equivalent |
private | ✅ | ❌ | ❌ | ❌ | private |
In legacy projects,
private+getter/setteris the most common pattern, known as "encapsulation."
3.3 Inheritance
// Parent class
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void speak() {
System.out.println(name + " makes a sound");
}
}
// Child class: use extends (same as JS's class extends)
public class Dog extends Animal {
private String breed;
public Dog(String name, String breed) {
super(name); // call parent constructor (similar to JS's super())
this.breed = breed;
}
@Override // override parent method (similar to JS method overriding)
public void speak() {
System.out.println(name + " barks");
}
public String getBreed() {
return breed;
}
}Differences from JS:
- Java only supports single inheritance (a class can only
extendsone parent class) - A class marked
finalcannot be subclassed (no direct equivalent in JS) - The
@Overrideannotation is not required, but strongly recommended — the compiler will check for you
3.3.1 Polymorphism
Polymorphism is one of the three pillars of OOP (encapsulation, inheritance, polymorphism). Simply put: the same method call produces different behavior depending on the object:
// Parent reference pointing to child objects
Animal animal1 = new Dog("Rex", 3, "Golden Retriever");
Animal animal2 = new Cat("Whiskers", 2, true);
// Same method call — runtime determines which version to execute based on actual type
animal1.speak(); // outputs "Rex barks" (actually calls Dog.speak)
animal2.speak(); // outputs "Whiskers makes a sound...meow" (actually calls Cat.speak)
// Frontend analogy: TS interface + implementations
// interface Animal { speak(): void; }
// class Dog implements Animal { speak() { console.log('Woof!'); } }
// class Cat implements Animal { speak() { console.log('Meow!'); } }
// const animals: Animal[] = [new Dog(), new Cat()];
// animals.forEach(a => a.speak()); // each outputs differently
// Polymorphism in Android — OnClickListener pattern
View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// v could be Button, ImageView, TextView, etc.
// Runtime type determines the actual behavior
}
};Type casting: Polymorphism is often combined with type checking:
javaif (animal instanceof Dog) { Dog dog = (Dog) animal; // downcast dog.getBreed(); } // Java 16+ pattern matching (more concise) if (animal instanceof Dog dog) { dog.getBreed(); // automatic casting }
3.4 Interfaces
Interfaces are Java's way of defining "contracts," similar to TypeScript's interface:
// Defining an interface (similar to TS: interface Clickable { void onClick(); String getLabel(); })
public interface Clickable {
void onClick();
String getLabel();
// Java 8+: default methods (with implementation)
default void onDoubleClick() {
onClick();
onClick();
}
}
// Implementing an interface (use implements, similar to TS's class Xxx implements Clickable)
public class Button implements Clickable {
private String label;
public Button(String label) {
this.label = label;
}
@Override
public void onClick() {
System.out.println("Button clicked: " + label);
}
@Override
public String getLabel() {
return label;
}
}
// A class can implement multiple interfaces (compensating for single inheritance)
public class ImageButton extends ImageView implements Clickable, LongPressable {
// Must implement all abstract methods from Clickable and LongPressable
}Why are interfaces important? Java doesn't allow multiple inheritance, but it does allow implementing multiple interfaces. When you see code like
implements Serializable, Parcelable, OnClickListenerin legacy projects, it means the class adheres to multiple contracts simultaneously.
3.5 Abstract Classes
// Abstract class: cannot be instantiated directly, must be subclassed
public abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
// Abstract method: no body — subclasses must implement it
public abstract double getArea();
// Concrete method: has implementation, subclasses can use it directly
public String getColor() {
return color;
}
}
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
// Shape shape = new Shape("red"); // ❌ Compile error — cannot instantiate an abstract class
Shape circle = new Circle("red", 5.0); // ✅ Instantiate via a subclass3.6 Static Members
public class MathUtils {
// Static field: belongs to the class itself, not to any instance
public static final double PI = 3.14159265;
// Static method: can be called without creating an instance
public static int add(int a, int b) {
return a + b;
}
public static int max(int a, int b) {
return a > b ? a : b;
}
}
// Usage (similar to JS: Math.PI, Math.max())
double area = MathUtils.PI * r * r;
int sum = MathUtils.add(3, 5);Frontend comparison:
Math.PI,Array.isArray(), andObject.keys()in JS are all examples of static members.
4. Collections Framework
Java's collections framework is richer than JS's Array / Map / Set. This is the part you'll encounter most frequently in legacy projects.
4.1 List (Corresponds to JS Array)
import java.util.ArrayList;
import java.util.List;
// Declaration (program to the interface — recommended)
List<String> names = new ArrayList<>();
// Add elements (similar to JS push)
names.add("Alice");
names.add("Bob");
names.add("Charlie");
// Get elements
String first = names.get(0); // similar to JS: names[0]
// Modify elements
names.set(1, "Bobby"); // similar to JS: names[1] = "Bobby"
// Remove elements
names.remove("Charlie"); // remove by value
names.remove(0); // remove by index
// Size
int size = names.size(); // similar to JS: names.length
// Contains
boolean has = names.contains("Alice"); // similar to JS: names.includes("Alice")
// Iteration
for (String name : names) { // similar to JS: for (const name of names)
System.out.println(name);
}
// Stream API (Java 8+, similar to JS array method chaining)
List<String> filtered = names.stream()
.filter(name -> name.startsWith("A")) // similar to .filter()
.map(String::toUpperCase) // similar to .map()
.sorted() // similar to .sort()
.toList(); // collect into a List
// Searching
boolean anyMatch = names.stream().anyMatch(n -> n.length() > 2); // similar to .some()
boolean allMatch = names.stream().allMatch(n -> n.length() > 0); // similar to .every()
Optional<String> found = names.stream().findFirst(); // similar to .find()4.2 Map (Corresponds to JS Map / Object)
import java.util.HashMap;
import java.util.Map;
// Creation (similar to JS: const user = new Map() or const user = {})
Map<String, Object> user = new HashMap<>();
// Add/modify key-value pairs
user.put("name", "Alice");
user.put("age", 25);
user.put("email", "alice@example.com");
// Get values
String name = (String) user.get("name"); // type casting required!
Integer age = (Integer) user.get("age");
// Get with default value (similar to JS: obj.key ?? defaultValue)
String phone = (String) user.getOrDefault("phone", "Not set");
// Check if key exists
boolean hasName = user.containsKey("name"); // similar to JS: "name" in obj
// Remove
user.remove("email");
// Iteration
for (Map.Entry<String, Object> entry : user.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// Typed Map (recommended — avoids type casting)
Map<String, String> config = new HashMap<>();
config.put("theme", "dark");
config.put("lang", "en-US");
String theme = config.get("theme"); // no casting needed4.3 Set (Corresponds to JS Set)
import java.util.HashSet;
import java.util.Set;
Set<String> tags = new HashSet<>();
tags.add("java");
tags.add("android");
tags.add("java"); // duplicate — will not be added
int size = tags.size(); // 2
boolean hasJava = tags.contains("java"); // true
for (String tag : tags) {
System.out.println(tag);
}4.4 Collection Type Selection Guide
| Requirement | Use | JS Equivalent |
|---|---|---|
| Ordered list, allows duplicates | ArrayList | Array |
| Key-value pairs | HashMap | Map / {} |
| Unique values only | HashSet | Set |
| Ordered key-value pairs | LinkedHashMap | No direct equivalent |
| Sorted key-value pairs | TreeMap | No direct equivalent |
| Thread-safe list | CopyOnWriteArrayList | No direct equivalent |
Common Stream API Pitfalls
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// ❌ Wrong: a Stream can only be consumed once
Stream<String> stream = names.stream();
stream.forEach(System.out::println);
stream.filter(s -> s.startsWith("A")); // IllegalStateException: stream has already been operated upon
// ✅ Correct: recreate the stream when needed
names.stream().forEach(System.out::println);
List<String> aNames = names.stream()
.filter(s -> s.startsWith("A"))
.collect(Collectors.toList());
// ❌ Wrong: modifying an external variable inside a stream
int totalLength = 0;
names.forEach(name -> totalLength += name.length()); // compile error
// ✅ Correct: use collect to aggregate
int total = names.stream().mapToInt(String::length).sum();
// ⚠️ Note: parallel streams are uncommon on the Android main thread and can cause thread-safety issues
names.parallelStream().forEach(...); // use with caution| Scenario | Recommended | Not Recommended |
|---|---|---|
| Iterate once | list.forEach(...) | manual for loop (also fine) |
| filter + map + collect | stream().filter().map().collect() | multiple loops |
| sum / max / min | mapToInt().sum() / max() | manual accumulation |
| Android main thread | avoid heavy Stream operations | processing large data on main thread |
5. Exception Handling
// Similar to JS's try...catch...finally
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.err.println("Math error: " + e.getMessage());
} catch (Exception e) {
System.err.println("Other error: " + e.getMessage());
} finally {
// Always executes regardless of exception (same as JS)
System.out.println("Clean up resources");
}
// Throwing exceptions
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
this.age = age;
}
// Multi-catch (Java 7+)
try {
// ...
} catch (IOException | SQLException e) {
System.err.println("Operation failed: " + e.getMessage());
}Key differences from JS:
- Java exceptions are divided into checked exceptions and unchecked exceptions
- Checked exceptions must be declared with
throwsor handled withtry-catch, otherwise the code won't compile - You'll see a lot of
try-catchblocks in legacy projects, many of which exist to handle checked exceptions
// Checked exception: must be declared in the method signature
public void readFile(String path) throws IOException {
// If the caller doesn't handle IOException, compilation will fail
BufferedReader reader = new BufferedReader(new FileReader(path));
String line = reader.readLine();
reader.close();
}
// Unchecked exception: no declaration needed (subclass of RuntimeException)
public void divide(int a, int b) {
// ArithmeticException is unchecked — no throws needed
System.out.println(a / b);
}Exception Handling Best Practices
// ✅ Prefer catching specific exceptions and never swallow them silently
try {
processFile(path);
} catch (IOException e) {
Log.e(TAG, "Failed to read file: " + path, e); // log full stack trace
throw new BusinessException("Unable to load config", e); // wrap and rethrow
}
// ❌ Don't do this: swallowed exceptions are hard to debug
try {
processFile(path);
} catch (Exception e) {
// nothing here!
}
// ✅ Use try-with-resources to auto-close resources
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
// read operations
} catch (IOException e) {
Log.e(TAG, "IO error", e);
}
// ✅ In Android, handle async callback exceptions on the main thread
new Thread(() -> {
try {
String result = fetchFromNetwork();
runOnUiThread(() -> textView.setText(result));
} catch (IOException e) {
runOnUiThread(() -> Toast.makeText(this, "Network error", Toast.LENGTH_SHORT).show());
}
}).start();| Anti-pattern | Problem | Recommended |
|---|---|---|
catch (Exception e) { e.printStackTrace(); } | Logs may be invisible in production | Use Log.e(TAG, msg, e) or report |
catch (Exception e) {} | Silent failure, hard to locate | At least log or rethrow |
return inside finally | Swallows exceptions | Avoid returning in finally |
| Catch all exceptions on UI thread | May hide ANR root cause | Distinguish recoverable vs fatal |
6. Lambda Expressions and Functional Programming (Java 8+)
Java 8 introduced Lambdas, allowing Java to write functional-style code like JS:
// JS: const add = (a, b) => a + b;
// Java:
// Note: Java Lambdas must be assigned to a functional interface (an interface with exactly one abstract method)
// Using built-in functional interfaces
import java.util.function.*;
Function<String, Integer> strLength = s -> s.length();
// similar to JS: const strLength = (s) => s.length;
Consumer<String> printer = s -> System.out.println(s);
// similar to JS: const printer = (s) => console.log(s);
Predicate<Integer> isPositive = n -> n > 0;
// similar to JS: const isPositive = (n) => n > 0;
Supplier<Double> random = () -> Math.random();
// similar to JS: const random = () => Math.random();
// Using with collections (most common scenario)
List<String> names = List.of("Alice", "Bob", "Charlie", "Dave");
names.stream()
.filter(name -> name.length() > 3) // Predicate
.map(String::toUpperCase) // Function (method reference)
.forEach(System.out::println); // Consumer
// Sorting (similar to JS sort)
names.sort((a, b) -> a.length() - b.length());
// or shorthand:
names.sort(Comparator.comparingInt(String::length));Method References
// When a Lambda body just calls a method, you can use a method reference as shorthand
// s -> System.out.println(s) is equivalent to System.out::println
// s -> s.length() is equivalent to String::length
// () -> new ArrayList<>() is equivalent to ArrayList::newOptional (Java 8+)
Optional is used to explicitly express that a value may be absent, avoiding scattered null checks:
// JS/TS analogy: optional chaining + nullish coalescing
// const name = user?.name ?? "Anonymous";
Optional<User> userOpt = findUserById(1);
String name = userOpt.map(User::getName)
.orElse("Anonymous");
// Don't do this (defeats the purpose)
Optional<User> user = Optional.ofNullable(getUser()); // ✅ OK to wrap
User u = user.get(); // ❌ may throw NoSuchElementException
// Recommended usage
userOpt.ifPresent(u -> System.out.println(u.getName()));Android note:
Optionalis natively supported only on Android API 24+. For legacy projects (minSdk < 24), use Guava'sOptionalor stick to explicitnullchecks.
7. Generics
Generics are similar to TypeScript generics, used to create reusable, type-safe code:
// Similar to TS: class Box<T> { private value: T; ... }
public class Box<T> {
private T value;
public Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
// Usage
Box<String> stringBox = new Box<>("hello");
String val = stringBox.getValue(); // no casting needed
Box<Integer> intBox = new Box<>(42);
int num = intBox.getValue();
// Generic methods
public <T> void printArray(T[] array) {
for (T item : array) {
System.out.println(item);
}
}
// Generic wildcards (you'll see these in legacy projects)
// ? extends Number → upper bound, must be Number or a subclass
// ? super Integer → lower bound, must be Integer or a superclass
// ? → unbounded, any type
public void process(List<? extends Number> numbers) {
for (Number n : numbers) {
System.out.println(n.doubleValue());
}
}Generic Type Erasure and PECS
Type Erasure
Java generics are erased to Object (or the upper bound) after compilation, for backward compatibility with older JVMs:
// Source
List<String> names = new ArrayList<>();
// After compilation, roughly equivalent to
List names = new ArrayList();
String name = (String) names.get(0); // compiler inserts castFrontend note: TypeScript types are also erased at compile time, but Java's erasure is stricter — you cannot directly retrieve
List<String>'s type parameter at runtime. That's why Gson needs aTypeTokento parseList<User>.
PECS Principle
PECS = Producer-Extends, Consumer-Super:
// Producer: only reads data, use ? extends T
public void printNumbers(List<? extends Number> numbers) {
for (Number n : numbers) {
System.out.println(n);
}
// numbers.add(1); // ❌ compile error, cannot guarantee type safety
}
// Consumer: only writes data, use ? super T
public void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
// Integer i = list.get(0); // ❌ compile error, can only read as Object
}| Wildcard | Meaning | Use Case |
|---|---|---|
? extends T | Upper bound, T or subclass | Read-only (Producer) |
? super T | Lower bound, T or superclass | Write-only (Consumer) |
? | Unbounded | Neither read nor write specific type |
8. Enums
// Similar to TS: enum Status { PENDING, ACTIVE, COMPLETED }
public enum Status {
PENDING,
ACTIVE,
COMPLETED,
CANCELLED
}
// Usage
Status currentStatus = Status.ACTIVE;
switch (currentStatus) {
case PENDING:
System.out.println("Waiting");
break;
case ACTIVE:
System.out.println("In progress");
break;
case COMPLETED:
System.out.println("Done");
break;
case CANCELLED:
System.out.println("Cancelled");
break;
}
// Enum with data (Java-specific, more powerful than TS enums)
public enum Priority {
LOW(1, "Low"),
MEDIUM(2, "Medium"),
HIGH(3, "High");
private final int level;
private final String label;
Priority(int level, String label) {
this.level = level;
this.label = label;
}
public int getLevel() { return level; }
public String getLabel() { return label; }
}
Priority high = Priority.HIGH;
System.out.println(high.getLabel()); // "High"
System.out.println(high.getLevel()); // 39. Annotations
Annotations are metadata tags in Java code, similar to TypeScript decorators:
// Common built-in annotations
@Override // marks a method that overrides a parent method
@Deprecated // marks an API as outdated
@SuppressWarnings("unchecked") // suppresses compiler warnings
// Common annotations in Android development
@NonNull // parameter/return value must not be null
@Nullable // parameter/return value may be null
@SuppressLint("...") // suppresses Android Lint warnings
@RequiresApi(21) // requires a specific API level
// Custom annotations you'll see in legacy projects
@BindView(R.id.tv_name) // ButterKnife
@Inject // Dagger/Hilt
@SerializedName("user_name") // Gson
@Entity(tableName = "users") // Room10. I/O Basics
import java.io.*;
// Reading a file
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
// try-with-resources automatically closes resources (similar to JS's using proposal)
// Writing a file
try (FileWriter writer = new FileWriter("output.txt")) {
writer.write("Hello, World!");
} catch (IOException e) {
e.printStackTrace();
}Note: In Android development, you rarely use raw Java I/O directly. Networking uses OkHttp/Retrofit, databases use Room, and file operations use APIs provided by Context. However, understanding I/O basics helps when reading legacy project code.
11. Multithreading Fundamentals
JS is single-threaded (handling async via the event loop); Java is multi-threaded. This is a key concept for frontend developers to understand.
11.1 Thread and Runnable
// Approach 1: Extend Thread (not recommended — Java single inheritance limitation)
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Running in thread: " + Thread.currentThread().getName());
}
}
new MyThread().start();
// Approach 2: Implement Runnable (recommended — more flexible)
// Frontend analogy: similar to setTimeout(() => { ... }, 0) but truly parallel
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Thread: " + Thread.currentThread().getName());
}
};
new Thread(task).start();
// Approach 3: Lambda shorthand (most common)
new Thread(() -> {
System.out.println("Thread running");
}).start();11.2 Main Thread vs Worker Threads
// ⚠️ Core Android rules:
// - Main thread (UI thread): handles UI updates and user interaction; never run heavy work here
// - Worker threads: execute network requests, DB operations, file I/O
// - Worker threads CANNOT directly update UI! Must switch back to main thread
// Common pattern in an Activity
new Thread(() -> {
// Worker thread: perform heavy work
String result = fetchDataFromNetwork();
// Switch back to main thread to update UI
runOnUiThread(() -> {
textView.setText(result);
});
}).start();11.3 Handler Mechanism (Android-specific)
// Handler is the core mechanism for inter-thread communication in Android
// Frontend analogy: similar to postMessage + onmessage, but more complex
// Create a Handler on the main thread
Handler mainHandler = new Handler(Looper.getMainLooper());
// Send messages from a worker thread to the main thread
new Thread(() -> {
String data = doHeavyWork();
// Approach 1: post a Runnable
mainHandler.post(() -> {
textView.setText(data); // runs on main thread
});
// Approach 2: send a Message
Message msg = mainHandler.obtainMessage();
msg.what = 1;
msg.obj = data;
mainHandler.sendMessage(msg);
}).start();11.4 ExecutorService (Thread Pool)
import java.util.concurrent.*;
// Create a thread pool (frontend analogy: like Promise concurrency control)
ExecutorService executor = Executors.newFixedThreadPool(4); // fixed 4 threads
// Submit a task
Future<String> future = executor.submit(() -> {
Thread.sleep(2000); // simulate heavy work
return "Task complete";
});
// Get result (blocks current thread)
try {
String result = future.get(); // similar to JS: await promise
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// Shut down the pool
executor.shutdown();
// CompletableFuture (Java 8+, similar to Promise chains)
CompletableFuture.supplyAsync(() -> fetchUser())
.thenApply(user -> user.getName()) // like .then()
.thenAccept(name -> System.out.println(name))
.exceptionally(e -> { e.printStackTrace(); return null; }); // like .catch()11.5 Async Patterns Evolution in Android
// 1️⃣ Legacy: AsyncTask (deprecated, but you'll see it in old code)
public class LoadDataTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
return fetchData(params[0]); // worker thread
}
@Override
protected void onPostExecute(String result) {
textView.setText(result); // main thread
}
}
new LoadDataTask().execute("https://api.example.com");
// 2️⃣ Transitional: RxJava (reactive programming, like RxJS)
Observable.fromCallable(() -> fetchData(url))
.subscribeOn(Schedulers.io()) // execute on IO thread
.observeOn(AndroidSchedulers.mainThread()) // observe on main thread
.subscribe(
result -> textView.setText(result), // success
error -> showError(error) // failure
);
// 3️⃣ Modern: Kotlin Coroutines (preferred for new projects)
// viewModelScope.launch {
// val result = withContext(Dispatchers.IO) { fetchData(url) }
// textView.text = result
// }Frontend developer comparison:
JS Async Pattern Java/Android Equivalent setTimeout/setIntervalHandler.postDelayed()PromiseFuture/CompletableFutureasync/awaitKotlin Coroutines / CompletableFuture.get()Web WorkerThread/ExecutorServicepostMessageHandler.sendMessage()RxJS RxJava
12. Android-Specific Java Knowledge
12.1 Context
Context is the most important concept in Android — nearly every operation requires it:
// Context is the gateway to system resources and app information
// Frontend analogy: like window + document + app config combined
// Common Context sources
Activity activity = this; // Activity IS a Context
Context appContext = getApplicationContext(); // app-level Context
Context context = getContext(); // get Context from a View
// Common uses
Toast.makeText(context, "Hint message", Toast.LENGTH_SHORT).show(); // show toast
context.startActivity(new Intent(context, DetailActivity.class)); // navigate
context.getSharedPreferences("prefs", Context.MODE_PRIVATE); // store config
context.getResources().getString(R.string.app_name); // get resources12.2 Serializable vs Parcelable
// Serializable: standard Java serialization (simple but slow)
// Frontend analogy: similar to JSON.stringify / JSON.parse
public class User implements Serializable {
private String name;
private int age;
// automatically supports serialization, no extra code needed
}
// Passing Serializable objects
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("user", user);
// Parcelable: Android-specific serialization (fast but verbose)
public class User implements Parcelable {
private String name;
private int age;
protected User(Parcel in) {
name = in.readString();
age = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) { return new User(in); }
@Override
public User[] newArray(int size) { return new User[size]; }
};
@Override
public int describeContents() { return 0; }
}Rule of thumb: Use Parcelable for passing data between Activities/Fragments; use Serializable or JSON for persistent storage.
12.3 Anonymous Inner Classes
Legacy Android projects heavily use anonymous inner classes (frontend developers will recognize these as callback patterns):
// Frontend analogy: element.addEventListener('click', function(e) { ... })
// Button click listener
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// handle click
}
});
// Lambda shorthand (Java 8+)
button.setOnClickListener(v -> {
// handle click
});
// Text change listener (frontend analogy: input.addEventListener('input', ...))
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// handle text change
}
@Override
public void afterTextChanged(Editable s) {}
});12.4 Common Android Annotations in Detail
// @Override: method overrides parent (compile-time check)
@Override
protected void onCreate(Bundle savedInstanceState) { ... }
// @NonNull / @Nullable: null-safety annotations
public void setName(@NonNull String name) { ... } // warning if caller passes null
public @Nullable String getNickname() { ... } // return value may be null
// @SuppressLint: suppress specific Lint warnings
@SuppressLint("SetTextI18n") // suppress hardcoded string warning
textView.setText("Hello " + name);
// @RequiresApi: marks APIs requiring specific OS versions
@RequiresApi(api = Build.VERSION_CODES.O)
public void useNotificationChannel() { ... }
// @Deprecated: marks outdated APIs
@Deprecated
public void oldMethod() { ... }
// Third-party library annotations (common in legacy projects)
@SerializedName("user_name") // Gson: JSON field mapping
@Entity(tableName = "users") // Room: database table mapping
@Inject // Dagger/Hilt: dependency injection
@BindView(R.id.tv_name) // ButterKnife: View binding12.5 The R File and Resource References
// R is an auto-generated resource index, similar to importing assets in frontend
// R.id.xxx → View IDs in layouts
// R.string.xxx → strings in strings.xml
// R.layout.xxx → layout files
// R.drawable.xxx → image resources
// R.color.xxx → color values
// In an Activity
setContentView(R.layout.activity_main); // load layout
TextView tv = findViewById(R.id.tv_title); // find View
String appName = getString(R.string.app_name); // get string
int color = getColor(R.color.primary); // get color12.6 Lifecycle and Callback Patterns
// Android components interact with the system via lifecycle callbacks
// Frontend analogy: similar to React's useEffect + component lifecycle
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // must call parent method
// like React: componentDidMount initialization
}
@Override
protected void onResume() {
super.onResume();
// page is visible and interactive (like page focus)
}
@Override
protected void onPause() {
super.onPause();
// page loses focus (like page blur)
}
@Override
protected void onDestroy() {
super.onDestroy();
// page destroyed (like React: componentWillUnmount)
}
}Common questions from frontend developers:
Frontend Concept Android Equivalent windowContextdocument.getElementById()findViewById()addEventListenersetOnXxxListener()localStorageSharedPreferencesfetch()OkHttp/RetrofitReact component lifecycle Activity lifecycle Router / routing Intent
13. Common Design Patterns at a Glance
You'll frequently encounter the following patterns in legacy projects:
Singleton Pattern
// Similar to a module singleton in JS
public class DatabaseHelper {
private static DatabaseHelper instance;
private DatabaseHelper() {} // private constructor
public static synchronized DatabaseHelper getInstance() {
if (instance == null) {
instance = new DatabaseHelper();
}
return instance;
}
// Business methods...
}
// Usage
DatabaseHelper db = DatabaseHelper.getInstance();Observer Pattern
// Ubiquitous in Android: OnClickListener, TextWatcher, and LiveData are all observer pattern
public interface OnDataChangeListener {
void onDataChanged(String newData);
}
public class DataManager {
private List<OnDataChangeListener> listeners = new ArrayList<>();
public void addListener(OnDataChangeListener listener) {
listeners.add(listener);
}
public void notifyDataChanged(String data) {
for (OnDataChangeListener listener : listeners) {
listener.onDataChanged(data);
}
}
}Builder Pattern
// Similar to JS chaining: new Request().url("...").method("GET").build()
AlertDialog dialog = new AlertDialog.Builder(context)
.setTitle("Notice")
.setMessage("Are you sure you want to exit?")
.setPositiveButton("OK", (d, w) -> finish())
.setNegativeButton("Cancel", null)
.create();
dialog.show();14. Things Java Doesn't Have That JS Does
| JS Feature | Java Alternative |
|---|---|
undefined | Doesn't exist — only null |
== loose comparison | Java's == is strict reference comparison; use .equals() for value comparison |
| Destructuring assignment | Doesn't exist — assign one by one |
Spread operator ... | Doesn't exist — copy manually |
Optional chaining ?. | Doesn't exist — check manually: if (obj != null) { obj.xxx } |
Nullish coalescing ?? | Doesn't exist — use ternary: obj != null ? obj : default |
| Promise / async-await | Java uses CompletableFuture, ExecutorService; Android uses callbacks/coroutines |
| Built-in JSON | Requires third-party libraries: Gson, Jackson, Moshi |
typeof | instanceof or .getClass() |
Array.isArray() | obj instanceof List or obj.getClass().isArray() |
| Template literals | String.format() or StringBuilder |
Quick Reference for Frontend Developers
A quick translation guide when reading Java code:
| Java Code You See | Equivalent JS/TS Mental Model |
|---|---|
String name = "hi" | const name = "hi" |
new ArrayList<>() | [] |
new HashMap<>() | {} or new Map() |
.add(item) | .push(item) |
.get(index) | [index] |
.size() | .length |
.equals(other) | === other |
instanceof | instanceof |
null | null (no undefined) |
this.xxx | this.xxx |
super.xxx | super.xxx |
static | static |
final | const (approximately) |
@Override | No direct equivalent (method overriding is implicit) |
try-catch-finally | try-catch-finally |
implements Interface | implements Interface (TS) |
extends Class | extends Class |
import com.xxx.Yyy | import { Yyy } from "com/xxx" |
System.out.println() | console.log() |
Next Steps
Now that you've got a handle on Java fundamentals, move on to Module 03: Project Structure and Build System to learn how Android projects are organized and how the Gradle build system works.