Publicado · en mejora
Guía de Java · 5/6
Por ahora, este capítulo solo está disponible en inglés.
Java reports failures with exceptions, split into checked exceptions, which the compiler makes you handle, and unchecked ones, which it does not. This chapter covers both, along with try-with-resources, custom exceptions, and unit testing with JUnit.
Every exception extends Throwable. The Exception branch covers problems a program can handle; the Error branch signals serious trouble you normally leave alone.
| Kind | Examples | Compiler enforcement |
|---|---|---|
| Checked | IOException, SQLException | Must be caught or declared with throws |
| Unchecked | NullPointerException, IllegalArgumentException | Not enforced |
| Error | OutOfMemoryError, StackOverflowError | Usually not handled |
Unchecked exceptions extend RuntimeException and usually signal programming mistakes such as bad arguments. Checked exceptions model outside failures, like a missing file, that callers should expect.
package com.example.config;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class ConfigLoader {
// a checked exception can be passed up to the caller with throws
static String load(Path path) throws IOException {
return Files.readString(path);
}
static int parsePort(String value) {
int port = Integer.parseInt(value); // may throw NumberFormatException (unchecked)
if (port < 1 || port > 65535) {
throw new IllegalArgumentException("port out of range: " + port);
}
return port;
}
}Wrap risky code in try and handle failures in catch clauses, most specific type first. One clause can catch several unrelated types with |. A finally block always runs.
try {
String text = ConfigLoader.load(Path.of("app.conf"));
int port = ConfigLoader.parsePort(text.strip());
System.out.println("port = " + port);
} catch (NoSuchFileException | AccessDeniedException e) {
System.err.println("Cannot access config file: " + e.getFile());
} catch (IOException e) {
System.err.println("Failed to read config: " + e.getMessage());
} catch (IllegalArgumentException e) { // also catches NumberFormatException
System.err.println("Bad port value: " + e.getMessage());
} finally {
System.out.println("Config load attempted");
}Never swallow an exception with an empty ; if you cannot handle it, let it propagate or wrap it in a more meaningful one.
catchAnything that must be closed, such as files, sockets, or connections, belongs in a try-with-resources header. It is closed automatically when the block exits, even on an exception. Any AutoCloseable works, and multiple resources close in reverse order.
static long countErrors(Path log) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(log)) {
return reader.lines()
.filter(line -> line.contains("ERROR"))
.count();
} // reader.close() runs automatically
}Domain-specific exceptions let callers tell failures apart. Extend Exception when callers must plan for the failure, RuntimeException otherwise, and accept a cause so wrapped stack traces are preserved.
public class OrderNotFoundException extends RuntimeException {
public OrderNotFoundException(long orderId) {
super("order not found: " + orderId);
}
public OrderNotFoundException(long orderId, Throwable cause) {
super("order not found: " + orderId, cause);
}
}JUnit is the standard testing framework for Java, and tests use the Jupiter API introduced with JUnit 5. With Maven, add junit-jupiter as a test-scoped dependency (the previous chapter shows Gradle); test classes go in the same package under src/test/java.
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>Annotate test methods with @Test and verify results with the static methods in Assertions. assertThrows checks that an exception is thrown and hands it back, so you can inspect its message too.
package com.example.config;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
class ConfigLoaderTest {
@Test
@DisplayName("a valid port is parsed to a number")
void parsesValidPort() {
assertEquals(8080, ConfigLoader.parsePort("8080"));
}
@Test
void rejectsOutOfRangePort() {
var e = assertThrows(IllegalArgumentException.class,
() -> ConfigLoader.parsePort("70000"));
assertTrue(e.getMessage().contains("70000"));
}
@ParameterizedTest
@ValueSource(strings = {"0", "-1", "65536"})
void rejectsInvalidPorts(String value) {
assertThrows(IllegalArgumentException.class, () -> ConfigLoader.parsePort(value));
}
}Run the suite with mvn test or ./gradlew test. @ParameterizedTest repeats a check over many inputs, and IDEs can run or debug a single test from the editor.
RuntimeException.cause constructor so wrapped failures keep their history.@Test, assertEquals, and assertThrows, and run tests through your build tool.
0 comentarios
Iniciar sesión · Inicia sesión para dejar un comentario.
Sé el primero en comentar.