Unit5 - Subjective Questions
CSE310 • Practice Questions with Detailed Answers
Differentiate between Byte Streams and Character Streams in Java I/O.
In Java, Input/Output operations are handled using streams. The main difference lies in the type of data they handle:
1. Byte Streams:
- Definition: Byte streams provide a convenient means for handling input and output of bytes ($8$-bit blocks).
- Usage: They are used for reading/writing binary data such as images, audio files, video, etc.
- Base Classes: The abstract superclasses are
InputStreamandOutputStream. - Examples:
FileInputStream,FileOutputStream.
2. Character Streams:
- Definition: Character streams are designed for handling input and output of characters. They use Unicode and therefore support internationalization ($16$-bit Unicode).
- Usage: They are used for reading/writing text files.
- Base Classes: The abstract superclasses are
ReaderandWriter. - Examples:
FileReader,FileWriter.
Key Difference:
While byte streams process data byte by byte, character streams automatically handle the translation to and from the local character set.
Explain the hierarchy of Java I/O Streams.
The java.io package contains a large number of classes. The hierarchy is primarily divided based on the data type (Byte vs. Character) and direction (Input vs. Output).
1. Byte Stream Hierarchy:
- InputStream (Abstract Class): The superclass of all classes representing an input stream of bytes.
FileInputStream: Reads from a file.BufferedInputStream: Adds buffering to input streams.ObjectInputStream: Used for deserialization.
- OutputStream (Abstract Class): The superclass of all classes representing an output stream of bytes.
FileOutputStream: Writes to a file.BufferedOutputStream: Adds buffering.PrintStream: Adds functionality to print representations of various data values (e.g.,System.out).
2. Character Stream Hierarchy:
- Reader (Abstract Class): Abstract class for reading character streams.
InputStreamReader: Bridge from byte streams to character streams.FileReader: Convenience class for reading files.
BufferedReader: Reads text efficiently.
- Writer (Abstract Class): Abstract class for writing character streams.
OutputStreamWriter: Bridge from character streams to byte streams.FileWriter: Convenience class for writing files.
PrintWriter: Prints formatted representations of objects.
What is Object Serialization and Deserialization? Write a code snippet to demonstrate writing an object to a file.
Serialization is the process of converting the state of an object into a byte stream. This byte stream can then be saved to a database, a file, or transferred over a network. Deserialization is the reverse process where the byte stream is used to recreate the actual Java object in memory.
Requirements:
- The class must implement the
java.io.Serializableinterface (a marker interface).
Code Example (Serialization):
java
import java.io.*;
class Student implements Serializable {
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
}
public class SerializeDemo {
public static void main(String args[]) {
try {
Student s1 = new Student(101, "Alice");
FileOutputStream fout = new FileOutputStream("student.txt");
ObjectOutputStream out = new ObjectOutputStream(fout);
out.writeObject(s1);
out.flush();
out.close();
System.out.println("Success: Object serialized.");
} catch(Exception e) { System.out.println(e); }
}
}
Explain the significance of the transient keyword in Java Serialization.
The transient keyword is used in the context of Serialization to indicate that a specific data member (field) of a class should not be serialized.
Significance:
- Security: Sensitive information like passwords or SSNs should not be saved to a file or sent over a network. Marking these fields as
transientprevents them from being persisted. - Resource Optimization: Fields that are derived from other fields or represent current runtime state (like a thread ID or a file handle) usually do not need to be saved. Not serializing them saves space.
Behavior:
When an object is deserialized, the transient fields are initialized to their default values (e.g., null for objects, $0$ for integers, false for booleans).
Example:
java
class User implements Serializable {
String username;
transient String password; // Will not be saved
}
Discuss the Try-with-resources statement. How does it improve I/O exception handling?
Try-with-resources is a feature introduced in Java 7 that allows us to declare resources to be used in a try block with the assurance that the resources will be closed automatically after the execution of that block.
Key Features:
- Automatic Resource Management: It eliminates the need for a
finallyblock solely to close resources (like streams or database connections). - Interface Requirement: The resource must implement the
java.lang.AutoCloseableorjava.io.Closeableinterface. - Syntax: The resource is declared within parentheses immediately after the
trykeyword.
Comparison:
Traditional Approach:
java
FileReader fr = null;
try {
fr = new FileReader("test.txt");
// read file
} catch (IOException e) {
// handle exception
} finally {
if (fr != null) try { fr.close(); } catch (IOException e) { }
}
Try-with-resources:
java
try (FileReader fr = new FileReader("test.txt")) {
// read file
} catch (IOException e) {
// handle exception
} // fr is closed automatically here
Define Generics in Java. What are the main advantages of using Generics?
Generics allow types (classes and interfaces) to be parameters when defining classes, interfaces, and methods. It enables programmers to specify the type of object a collection or class can hold.
Advantages:
- Stronger Type Checks at Compile Time: The Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. This catches bugs early.
- Elimination of Casts: With non-generic code, explicit casting is required.
- Without Generics:
List list = new ArrayList(); list.add("hello"); String s = (String) list.get(0); - With Generics:
List<String> list = new ArrayList<>(); list.add("hello"); String s = list.get(0);
- Without Generics:
- Enabling Generic Algorithms: Programmers can implement generic algorithms that work on collections of different types, customizable and type-safe.
- Code Reusability: One class or method can be written to handle any reference type.
Create a custom Generic class named Box that can store an object of any type T. Include methods to set and get the object.
To create a generic class, we declare the type parameter (usually named T) in angle brackets after the class name.
Code Implementation:
java
// Generic Class Definition
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
// Using Type Inference (Diamond Operator)
Box<Integer> integerBox = new Box<>();
Box<String> stringBox = new Box<>();
integerBox.set(10);
stringBox.set("Hello Generics");
System.out.printf("Integer Value : %d
", integerBox.get());
System.out.printf("String Value : %s
", stringBox.get());
}
}
In this example, Box<T> can be instantiated with Integer, String, or any other object type, providing type safety without casting.
Explain Bounded Types in Generics. How do you restrict a Generic type to accept only numbers?
Sometimes, we want to restrict the types that can be used as type arguments in a parameterized type. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses (like Integer, Double). This is done using Bounded Types.
Syntax:
To declare a bounded type parameter, list the type parameter's name, followed by the extends keyword, followed by its upper bound.
Example (Restricting to Numbers):
java
public class Stats<T extends Number> {
T[] nums;
Stats(T[] o) {
nums = o;
}
// Return type double in all cases
double average() {
double sum = 0.0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i].doubleValue();
// .doubleValue() is available because T extends Number
}
return sum / nums.length;
}
}
In this case, Stats<String> would cause a compile-time error because String is not a subclass of Number.
What are Wildcards in Java Generics? Explain the difference between Upper Bounded, Lower Bounded, and Unbounded wildcards.
The wildcard symbol ? in Generics represents an unknown type. It is useful when writing methods that interact with generic classes of various types.
1. Unbounded Wildcard (?):
- Syntax:
List<?> - Usage: When the code is using methods in the generic class that don't depend on the type parameter (e.g.,
List.size()).
2. Upper Bounded Wildcard (? extends Type):
- Syntax:
List<? extends Number> - Usage: Restricts the unknown type to be a specific type or a subclass of it. It implies "read-only" access mostly (Producer logic). You can read
Numberfrom it, but you cannot add specific types because the exact subtype is unknown.
3. Lower Bounded Wildcard (? super Type):
- Syntax:
List<? super Integer> - Usage: Restricts the unknown type to be a specific type or a superclass of it. It allows adding elements (Consumer logic). You can add an
Integerto this list because it is guaranteed to be a list ofIntegeror one of its parents (likeNumberorObject).
Describe the Lifecycle of a Thread in Java. What are the various states a thread can be in?
A thread in Java goes through various states during its lifecycle, defined in the Thread.State enum.
- New: A thread is in this state when an instance of the
Threadclass is created using thenewoperator, but thestart()method has not been invoked yet. - Runnable: When
start()is called, the thread enters the Runnable state. It is ready to run and is waiting for CPU time from the thread scheduler. It may be actually running or just waiting in the queue. - Running: This is the state where the processor is actually executing the thread's code. (Note: Java combines Runnable and Running states under
RUNNABLEin the API). - Blocked/Waiting/Timed Waiting:
- Blocked: Waiting to acquire a lock to enter a synchronized block/method.
- Waiting: Waiting indefinitely for another thread to perform an action (e.g.,
wait(),join()). - Timed Waiting: Waiting for a specified period (e.g.,
sleep(1000),wait(1000)).
- Terminated (Dead): The thread enters this state when its
run()method completes execution or an unhandled exception terminates it. Once dead, it cannot be restarted.
Compare implementing the Runnable interface versus extending the Thread class for creating threads.
Both approaches are used to create threads, but implementing Runnable is generally preferred.
| Feature | Extending Thread Class |
Implementing Runnable Interface |
|---|---|---|
| Inheritance | Java supports single inheritance. If you extend Thread, your class cannot extend any other class. |
Your class can implement Runnable and still extend another class (e.g., extends JFrame implements Runnable). |
| Resource Sharing | Each thread creates a unique object and associated class. Sharing the same object instance across threads is harder. | You can pass the same Runnable instance to multiple Thread objects, allowing easy sharing of data/resources. |
| Coupling | Tightly couples the task code with the thread control mechanism. | Decouples the task (Runnable) from the runner (Thread). |
| Overhead | More overhead as the class inherits all Thread class methods. |
Less overhead, just defines a task. |
Conclusion: Use Runnable for flexibility and object-oriented design; use Thread extension only if you need to override Thread methods other than run().
Explain the concept of Thread Synchronization. Why is it necessary?
Synchronization is the capability to control the access of multiple threads to any shared resource. In Java, it is achieved using the synchronized keyword (on methods or blocks).
Necessity (The Race Condition):
When multiple threads try to access and modify a shared resource (like a variable or file) simultaneously, it can lead to inconsistent results or data corruption. This scenario is called a Race Condition.
How it works:
Synchronization implements the concept of a Monitor or Lock. Only one thread can own the monitor of an object at a time. If a thread enters a synchronized block, it locks the object. Other threads attempting to enter the block must wait until the first thread finishes and releases the lock.
Example:
If two threads try to increment a shared counter count++ (which is read-modify-write), without synchronization, both might read the old value ($5$), increment it, and write back ($6$), effectively losing one increment operation.
How is Inter-thread Communication achieved in Java? Explain wait(), notify(), and notifyAll().
Inter-thread communication allows synchronized threads to communicate with each other to perform tasks cooperatively (e.g., Producer-Consumer problem). It is implemented using methods defined in the Object class (not Thread class), because locks are held on objects.
wait():- Causes the current thread to release the lock and wait until another thread invokes
notify()ornotifyAll()on the same object. - Must be called from within a synchronized context.
- Causes the current thread to release the lock and wait until another thread invokes
notify():- Wakes up a single thread that is waiting on this object's monitor. If multiple threads are waiting, one is chosen arbitrarily.
- The awakened thread will not proceed until the current thread releases the lock.
notifyAll():- Wakes up all threads that are waiting on this object's monitor.
Flow: A thread enters a sync block checks condition if false, calls wait() (releasing lock) sleeps. Another thread enters changes condition calls notify() exits. The first thread wakes up, re-acquires lock, and proceeds.
What are Thread Priorities? How can we set and get the priority of a thread in Java?
Every thread in Java has a priority that helps the operating system determine the order in which threads are scheduled. Priorities are integers ranging from $1$ to $10$.
Constants in Thread class:
Thread.MIN_PRIORITY($1$)Thread.NORM_PRIORITY($5$) - Default priority.Thread.MAX_PRIORITY($10$)
Methods:
public final void setPriority(int newPriority): Sets the priority of the thread. It throwsIllegalArgumentExceptionif the priority is not between 1 and 10.public final int getPriority(): Returns the current priority of the thread.
Note: Thread priority serves as a hint to the scheduler. It does not guarantee that a higher priority thread will always execute before a lower priority thread; it depends heavily on the OS implementation.
Write a Java program to create two threads: one printing even numbers and the other printing odd numbers up to 10.
java
class OddEvenPrinter {
public static void main(String[] args) {
Thread oddThread = new Thread(() -> {
for (int i = 1; i <= 10; i += 2) {
System.out.println("Odd: " + i);
try { Thread.sleep(100); } catch (InterruptedException e) { }
}
});
Thread evenThread = new Thread(() -> {
for (int i = 2; i <= 10; i += 2) {
System.out.println("Even: " + i);
try { Thread.sleep(100); } catch (InterruptedException e) { }
}
});
oddThread.start();
evenThread.start();
}
}
Note: Without synchronization, the output order between Odd and Even lines is not strictly guaranteed, but numbers within the threads will be sequential.
What is the Type Inference Diamond <> operator introduced in Java 7? Give an example.
The Diamond Operator <> allows the compiler to infer the type arguments for a generic class constructor invocation. Before Java 7, you had to repeat the type parameters on both sides of the assignment.
Before Java 7:
java
Map<String, List<String>> myMap = new HashMap<String, List<String>>();
This was verbose and repetitive.
With Java 7 (Diamond Operator):
java
Map<String, List<String>> myMap = new HashMap<>();
The compiler looks at the left-hand side (Map<String, List<String>>) and infers that the HashMap on the right must be of the same types. The empty angle brackets <> resemble a diamond, hence the name.
Explain the difference between join() and yield() methods in the Thread class.
1. join() Method:
- Function: It allows one thread to wait for the completion of another. If thread A calls
B.join(), thread A halts execution and enters the Waiting state until thread B finishes execution (dies). - Usage: Used when a thread needs the result of another thread to proceed.
- Exception: Throws
InterruptedException.
2. yield() Method:
- Function: It indicates that the current thread is willing to yield its current use of the processor. The scheduler moves the current thread from Running state to Runnable state.
- Effect: It allows other threads of the same or higher priority to get a chance to run. If there are no waiting threads or they have lower priority, the yielding thread may continue running immediately.
- Reliability: It is a static method and provides a hint to the scheduler, which may be ignored.
What is a Deadlock? Describe a scenario where a deadlock might occur in Java.
Deadlock is a situation in multithreading where two or more threads are blocked forever, waiting for each other to release a lock.
Scenario (Circular Dependency):
Imagine two threads (T1 and T2) and two resources (Lock A and Lock B).
- T1 acquires Lock A.
- T2 acquires Lock B.
- T1 tries to acquire Lock B (while holding A) -> T1 blocks because T2 has B.
- T2 tries to acquire Lock A (while holding B) -> T2 blocks because T1 has A.
Neither thread can proceed, and neither will release the lock they currently hold. This results in a deadlock.
Prevention:
Deadlocks can be avoided by ensuring that all threads acquire locks in a consistent, predefined order (e.g., always acquire Lock A before Lock B).
Explain the purpose of the File class in java.io. It is a stream?
The java.io.File class is not a stream. It is an abstract representation of file and directory pathnames.
Purpose:
- Metadata Access: It is used to access information about a file or directory, such as its name, path, parent directory, size (
length()), and last modification time (lastModified()). - File Manipulation: It provides methods to create new empty files (
createNewFile()), delete files (delete()), rename files (renameTo()), and create directories (mkdir()). - Navigation: It allows listing the files inside a directory (
list(),listFiles()). - Permission Checks: Checking if a file is readable (
canRead()), writable, or executable.
It does not handle the actual reading or writing of data content; for that, Streams (like FileInputStream) or Readers are used, often taking a File object as a constructor argument.
What is Type Erasure? How does it affect Generic code at runtime?
Type Erasure is the process enforced by the Java compiler to ensure that Generics are backward compatible with older versions of Java (pre-Java 5).
Mechanism:
When the code is compiled, the compiler:
- Replaces all type parameters in generic types with their bounds or
Objectif the type parameters are unbounded. (e.g.,Box<T>becomes justBoxandTbecomesObject). - Inserts type casts if necessary to preserve type safety.
- Generates bridge methods to preserve polymorphism in extended generic types.
Effect at Runtime:
Because of erasure, the specific type information (e.g., List<String>) is not available at runtime. The JVM only sees the raw type List. This means you cannot perform operations like if (obj instanceof List<String>) (illegal) or create arrays of generic types like new T[10].