Week 1: Revision

Hey peeps! We are done with some major chunk in our Java Journey.
Let’s revisit our old learnings and redo it all over again.
I will be using ChatGPT to generate few code snippets, try to understand it and answer the questions! I’ll be writing here my own answers.
Code Snippet 1
class Animal {
String name = "Animal";
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
String name = "Dog";
@Override
void sound() {
System.out.println("Dog barks");
}
void printNames() {
System.out.println("Child name: " + name);
System.out.println("Parent name: " + super.name);
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
a.sound();
Dog d = new Dog();
d.printNames();
}
}
Questions:
What will the output of
a.sound()
be and why?How is
super.name
different fromname
inprintNames()
?Is the object
a
an Animal or a Dog?What OOP concepts are demonstrated here?
Answers:
Dog barks, method overriding
Even though
a
is declared asAnimal
, it holds aDog
object.
Sincesound()
is overridden, the runtime type (Dog
) decides which version is called — this is runtime polymorphism.super always refers to the parent class. here it prints dog and then animal
So
name
insideDog
refers to"Dog"
,
andsuper.name
refers to the parent (Animal
)’sname
, which is"Animal"
.It is a dog object with the class reference of animal
This is the power of upcasting
Polymorphism, Inheritance
Code Snippet 2
interface Calculator {
int operation(int a, int b);
}
public class MathOps {
public static void main(String[] args) {
Calculator add = (a, b) -> a + b;
Calculator multiply = new Calculator() {
@Override
public int operation(int a, int b) {
return a * b;
}
};
System.out.println("Add: " + add.operation(3, 4));
System.out.println("Multiply: " + multiply.operation(3, 4));
}
}
Questions:
What is the difference between
add
andmultiply
declarations?Why is the lambda syntax allowed for
add
?Can we create a class that implements
Calculator
instead of these approaches?What concept allows us to use lambda expressions here?
Answers:
add declaration uses lambda expression to reduce the implementation of the interface class Calculator. Whereas multiply declaration explicitly declares the whole implementation of the interface through anonymous inner class.
because calculator is a single abstract method interface (functional interface)
yess and even there the operation method must be defined.
Functional interfaces
Code Snippet 3
import java.util.*;
public class GenericTest {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++) list.add(i); // [0, 1, 2, 3, 4]
list.remove(2); // removing what?
System.out.println(list); // what’s printed?
}
}
Questions:
What will the output be?
What exactly is removed by
list.remove(2)
and why?How would you remove the number 2 instead of index 2?
What Java feature causes this confusion?
Answers: I got most of these wrong :(
Java automatically calls
.toString()
→ prints the elements in array-style.refers to index, so the element at index 2 (which is 2) will be removed.
list.remove(2)
→ callsremove(int index)
list.remove(Integer.valueOf(2))
→ callsremove(Object o)
This is where autoboxing causes confusion.
Because of autoboxing,
2
can be:a
primitive int
(callsremove(index)
)or an
Integer
object (callsremove(Object)
)The confusion arises from method overloading + autoboxing.
Code Snippet 4
public class ExecutionOrder {
static {
System.out.println("Static block of outer class");
}
{
System.out.println("Instance block of outer class");
}
ExecutionOrder() {
System.out.println("Constructor of outer class");
}
class Inner {
{
System.out.println("Instance block of inner class");
}
Inner() {
System.out.println("Constructor of inner class");
}
}
public static void main(String[] args) {
ExecutionOrder eo = new ExecutionOrder();
ExecutionOrder.Inner inner = eo.new Inner();
}
}
Questions:
What is the exact output, line by line?
Why does the outer class’s static block run only once?
In what order do the constructor and instance blocks run in both classes?
What would happen if
Inner
was marked asstatic
?
Answers:
static block of outer class
instance block of outer class
constructor of outer class
instance block of inner class
Constructor of inner class
Because a class is loaded only once by ClassLoader. And static blocks run only once the class is loaded
Instance block first, constructor second
It becomes a static nested class instead of an inner (non-static) class
You no longer need an instance of
ExecutionOrder
to create it:ExecutionOrder.Inner inner = new ExecutionOrder.Inner();
Also: The constructor is still a constructor regardless of static or not.
Code Snippet 5
public class ThreadDemo {
static class MyTask implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " - Task Started");
try {
Thread.sleep(1000); // simulate work
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " - Interrupted!");
}
System.out.println(Thread.currentThread().getName() + " - Task Ended");
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new MyTask(), "Worker-1");
System.out.println("Before start - State: " + t1.getState());
t1.start();
System.out.println("After start - State: " + t1.getState());
t1.join(); // Wait for thread to finish
System.out.println("After join - State: " + t1.getState());
}
}
Questions:
What are the 3 thread states you expect to see printed?
Why do we use
Thread.sleep(1000)
insiderun()
?What happens if we call
start()
ont1
a second time?Why use
join()
here?What does
Thread.currentThread().getName()
do?
Answers:
new, runnable, terminated
to simulate work, any CRUD or some processing takes time to complete.
You cannot restart a thread once it's started or finished. Threads are one-time-use.
To "restart", you’d need to create a new
Thread
object with the same Runnable.Correct Answer:
IllegalThreadStateException
is thrown.to await for the thread to complete, asynchronous.
join()
tells the main thread to wait untilt1
finishesgets Worker-1, the name assigned during thread creation.
Bonus Snippet
import java.util.*;
public class ListTrap {
public static void main(String[] args) {
List<String> items = new ArrayList<>(Arrays.asList("apple", "banana", "cherry", "banana"));
for (String item : items) {
if (item.equals("banana")) {
items.remove(item);
}
}
System.out.println(items);
}
}
Questions:
Will this code compile and run? If not, what exception (if any) is thrown?
What’s the issue here?
How would you fix this properly? Give 2 ways.
Why doesn’t this happen in a
for
loop with indices?
Answers:
It will compile, but at runtime it throws:
CopyEditjava.util.ConcurrentModificationException
The loop uses the enhanced for-loop, which under the hood uses an Iterator.
The moment you modify the list directly (
items.remove(item)
) while iterating, you violate the fail-fast behavior ofArrayList
's iterator.Even though there’s no actual thread concurrency, the internal iterator detects that the list has changed during iteration.
(a) Use
Iterator
anditerator.remove()
– Safe removal during iterationIterator<String> iterator = items.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if (item.equals("banana")) { iterator.remove(); // ✅ safe } }
(b) Use
removeIf()
method (Java 8+) (ideal, modern way)items.removeIf(item -> item.equals("banana"));
Because you're not using an iterator — you're just indexing into the list manually.
But if you don’t
i--
, you might skip elements because the list shrinks.for (int i = 0; i < items.size(); i++) { if (items.get(i).equals("banana")) { items.remove(i); i--; // 👈 prevent skipping next element } }
That’s it for our revision, if the need arises, I’ll add another revision blog on the leftover topics!
Stay tuned, everyone!
Subscribe to my newsletter
Read articles from Nagraj Math directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
