Java Tutorial: Generics, Packages, and Exceptions
Generics
Generics provide a way to create classes, interfaces, and methods that operate on types while providing compile-time type safety. They allow you to write reusable code that can work with different data types.
Questions and Exercises
-
Write a generic method to count elements in a collection that have a specific property (e.g., odd numbers, prime numbers, palindromes).
public static <T> int countElements(Collection<T> collection, Predicate<T> predicate) { int count = 0; for (T element : collection) { if (predicate.test(element)) { count++; } } return count; } -
Will the following class compile? If not, why?
public final class Algorithm { public static <T> T max(T x, T y) { return x > y ? x : y; } }No, it won't compile because the > operator cannot be used with generic types. You need to restrict T to a type that implements Comparable.
-
Write a generic method to swap two distinct elements in an array.
public static <T> void swap(T[] array, int index1, int index2) { T temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } -
If the compiler erases all type parameters at compile time, why should you use generics?
Generics provide compile-time type safety, allowing you to catch type errors early and write more robust code.
-
What is the class converted to after type erasure?
public class Pair<K, V> { private K key; private V value; // constructors, getters, setters }After type erasure, it becomes:
public class Pair { private Object key; private Object value; // constructors, getters, setters } -
What is the method converted to after type erasure?
public static <T extends Comparable<T>> int findFirstGreaterThan(T[] at, T elem) { // implementation }After type erasure:
public static int findFirstGreaterThan(Comparable[] at, Comparable elem) { // implementation } -
Will the following method compile? If not, why?
public static void print(List<? extends Number> list) { for (Number n : list) System.out.print(n + " "); System.out.println(); }Yes, it will compile. The wildcard ? extends Number allows reading from the list.
-
Write a generic method to find the maximum element in a list range [begin, end).
public static <T extends Comparable<T>> T findMax(List<T> list, int begin, int end) { T max = list.get(begin); for (int i = begin + 1; i < end; i++) { if (list.get(i).compareTo(max) > 0) { max = list.get(i); } } return max; } -
Will the following class compile? If not, why?
public class Singleton<T> { private static T instance = null; public static T getInstance() { if (instance == null) instance = new Singleton<T>(); return instance; } }No, it won't compile because you cannot create a new instance of a generic type T directly. You need to use reflection or a factory method.
-
Given the following classes:
class Shape { /* ... */ } class Circle extends Shape { /* ... */ } class Rectangle extends Shape { /* ... */ } class Node<T> { /* ... */ }Will the following code compile? If not, why?
Node<Circle> nc = new Node<>(); Node<Shape> ns = nc;No, it won't compile because generics are invariant. You cannot assign a Node<Circle> to a Node<Shape>.
-
Consider this class:
class Node<T> implements Comparable<T> { public int compareTo(T obj) { /* ... */ } }Will the following code compile? If not, why?
Node<String> node = new Node<>(); Comparable<String> comp = node;Yes, it will compile because Node<String> implements Comparable<String>.
-
How would you call the following method to find the first integer in a list that is coprime with a specified list of integers?
public static <T> int findFirst(List<T> list, int begin, int end, UnaryPredicate<T> p)You would implement a UnaryPredicate that checks if two integers are coprime and pass it to the method.
Packages
Packages are used to organize classes and interfaces into namespaces, avoiding naming conflicts and controlling access. They provide a way to group related types together.
Creating and Using Packages
To create a package, add a package statement at the top of your source file:
package com.example.graphics;
Place your source files in a directory structure that matches the package name. For example, if your package is com.example.graphics, your files should be in com/example/graphics/.
Package Naming
Package names should be in lowercase and typically use a reversed domain name as a prefix, e.g., com.example.mypackage.
Using Package Members
To use classes from other packages, you can either use the fully qualified name or import the class:
// Using fully qualified name
com.example.graphics.Rectangle rect = new com.example.graphics.Rectangle();
// Importing the class
import com.example.graphics.Rectangle;
Rectangle rect = new Rectangle();
You can also import an entire package:
import com.example.graphics.*;
Managing Source Files and Class Files
Source files should be organized in directories matching the package structure. Class files should be placed in a similar structure, and the classpath should be set to include these directories.
Questions and Exercises
-
What line of code do you need to add to each source file to place each class in the correct package?
Add a package statement at the top of each file, e.g.,
package mygame.server;. -
What subdirectories do you need to create in your development directory, and where should each source file be placed?
Create subdirectories
mygame/server,mygame/shared, andmygame/client. PlaceServer.javainmygame/server,Utilities.javainmygame/shared, andClient.javainmygame/client. -
Do you think you need to make any other changes to the source files to compile them correctly?
No, as long as the package statements and directory structure are correct, the files should compile.
Exceptions
Exceptions are events that disrupt the normal flow of a program. Java provides a robust exception handling mechanism to manage errors and other exceptional situations.
What is an Exception?
An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions. When an exception occurs, an object is created and thrown to the runtime system.
Catching or Declaring Exceptions
Methods that can throw checked exceptions must either catch them or declare them in a throws clause.
Catching and Handling Exceptions
Use try-catch blocks to handle exceptions:
try {
// code that might throw an exception
} catch (ExceptionType e) {
// handle the exception
} finally {
// cleanup code
}
Try-with-Resources Statement
The try-with-resources statement ensures that resources are closed automatically:
try (FileReader fr = new FileReader("file.txt");
BufferedReader br = new BufferedReader(fr)) {
// use the resource
}
Questions and Exercises
-
Is the following code legal?
try { } finally { }Yes, it is legal. The try block is empty, but the finally block will always execute.
-
What types of exceptions can the following handler catch? What is the problem with using this type of exception handler?
catch (Exception e) { }It can catch any exception, but it's too broad and can hide specific exceptions.
-
What is wrong with the following exception handler? Will this code compile?
try { } catch (Exception e) { } catch (ArithmeticException a) { }The code will not compile because the more specific catch block (ArithmeticException) must come before the more general one (Exception).
-
Match each situation in the first list with an item in the second list.
int[] A; A[0] = 0;- JVM starts running your program but cannot find Java platform classes.
- A program is reading a stream and reaches the end-of-stream marker.
- A program tries to read a stream again before closing it and after reaching the end-of-stream marker.
- Error
- Checked exception
- Compile error
- No exception
1. Compile error
2. Error
3. No exception
4. Checked exception
Basic I/O
Java provides classes for performing input and output operations. I/O streams represent sources and destinations of data.
Byte Streams
Byte streams handle raw binary data. Use InputStream and OutputStream for byte-level operations.
try (FileInputStream in = new FileInputStream("input.txt");
FileOutputStream out = new FileOutputStream("output.txt")) {
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
}
Character Streams
Character streams handle text data, automatically converting between internal and local character sets.
try (FileReader reader = new FileReader("input.txt");
FileWriter writer = new FileWriter("output.txt")) {
int c;
while ((c = reader.read()) != -1) {
writer.write(c);
}
}
Line-Oriented I/O
Use BufferedReader and PrintWriter for line-oriented input and output.
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
PrintWriter writer = new PrintWriter(new FileWriter("output.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
writer.println(line);
}
}