Lesson 57Phase 6: Functional and modern Java
Annotations, docs, stack traces, and the IDE
Reading and using annotations, understanding a stack trace, and working effectively in a real editor.
By BasithUpdated 55 min
- @Override
- @Deprecated
- @SuppressWarnings
- @FunctionalInterface
- reading docs
- stack traces
Up to now we have written code that the computer runs. This lesson is about the layer around that code: small notes you leave for the compiler, the documentation you read instead of guessing, the crash report Java hands you when something goes wrong, and the editor that ties it all together. None of this is new Java syntax to memorize. It is the craft of working like an engineer.
- Read and use the three annotations worth knowing now:
@Override,@Deprecated,@SuppressWarnings. - Find a method's parameters, return type, and exceptions in the Java documentation.
- Read a stack trace top to bottom and point at the exact line that is your bug.
- Use auto-complete, go-to-definition, and a debugger, and see how they relate to the Atelier visualizer.
Annotations: notes you leave on your code#
An annotation is a small label, written with an @ sign, that you attach to a piece of code: a method, a field, a class. It does not change what the code doesat runtime on its own. It is metadata, which means “data about the code”. Some annotations are read by the compiler to check your work. Some are read by tools. Some are just documentation for the next human.
You have actually seen one already without us naming it. When you override a method, it is good practice to write @Override on the line above. Here is what it looks like in context.
public class Main { public static void main(String[] args) { Animal a = new Dog(); a.speak(); }} class Animal { void speak() { System.out.println("..."); }} class Dog extends Animal { @Override void speak() { System.out.println("Woof"); }}Woof
Read @Overrideas a promise to the compiler: “I intend for this method to replace one from the parent class.” The compiler then checks that the promise is true. If a method with that exact name and parameters really does exist in Animal, all is well. If it does not, the compiler stops you with an error instead of letting a silent bug through.
Key idea
@Overridedoes not make overriding happen. Overriding already happens by matching the method's name and parameters. The annotation just asks the compiler to confirm you really did what you meant to.
speak() but typed speek() by accident, or gave it a wrong parameter. Without @Override, Java happily treats it as a brand new method, your real override never runs, and you waste an afternoon. With @Override, the compiler says “this does not override anything” immediately. Put it on every method you intend as an override.Three more worth knowing
@Deprecated marks something as old and best avoided. The code still works, but you are warning future readers: there is a newer, better way, so do not reach for this in new code. When you call something marked @Deprecated, the compiler nudges you with a warning. You see this a lot in the real Java library, where a method from an old version stays around so it does not break existing programs, but is gently labelled “not this one anymore”.
public class Main { public static void main(String[] args) { Thermostat t = new Thermostat(); // The compiler warns here: oldReading() is deprecated. System.out.println(t.oldReading()); // Preferred replacement, no warning. System.out.println(t.reading()); }} class Thermostat { @Deprecated int oldReading() { return 13; } String reading() { return "13 C"; }}13 13 C
@SuppressWarningsgoes the other way. It tells the compiler “I know you want to warn me about this, and I have a good reason, so stay quiet here.” You name the specific warning you are silencing, for example @SuppressWarnings("deprecation") to silence the nudge about calling a deprecated method on purpose. Use it sparingly and locally. A warning is the compiler trying to help; silence it only when you have genuinely understood it and decided it does not apply.
@FunctionalInterface goes on an interface that is meant to have exactly one abstract method, the kind a lambda can stand in for, as in enums and lambdas. Like @Override, it asks the compiler to check: add a second abstract method by mistake and the interface stops compiling, with a message saying it is not a functional interface, instead of every lambda that uses it breaking somewhere else.
@Override@Deprecated@SuppressWarnings@FunctionalInterface@ labels as notes, not magic.Reading the documentation instead of guessing#
The Java standard library is enormous. Nobody memorizes it. The skill that separates a confident programmer from a stuck one is not knowing every method, it is knowing how to look one up quickly and read what it says. That reference is called the Javadoc: official, generated documentation for every class and method in the library.
When you look up a method, four things are worth finding every time. Read them in this order and a method that looked mysterious becomes a small contract you can trust.
- 1The signature: the method's name and the types it takes. For example,
String substring(int beginIndex, int endIndex). This tells you what to pass and in what order. - 2The parameters: what each argument means. Here,
beginIndexis where the slice starts andendIndexis one past where it ends. The docs spell out the subtle cases so you do not have to discover them by crashing. - 3The return: what you get back, and its type.
substringreturns a newString; it does not change the original. The docs say so explicitly. - 4The exceptions it can throw, sometimes labelled “Throws”.
substringthrows anIndexOutOfBoundsExceptionif you ask for a range outside the string. Knowing this ahead of time is how you write the guard before the crash, not after.
Here is the documented behavior put to work. We trust the docs, then confirm them by running the code and reading the output.
public class Main { public static void main(String[] args) { String word = "atelier"; // From the docs: substring(begin, end) takes chars // from begin up to but NOT including end. String part = word.substring(0, 4); System.out.println(part); System.out.println(part.length()); System.out.println(word); }}The output is atel, then 4, then atelier. Every line matches what the docs promised: the slice runs from index 0 up to but not including 4, its length is 4, and the original word is untouched because substring returns a new string. We did not have to guess any of that. We read the contract and the code obeyed it.
Reading a stack trace#
When a Java program hits an error it cannot handle, it stops and prints a stack trace: a report of what went wrong and where. Beginners often glance at the wall of text, feel a flash of panic, and scroll away. That is the wrong instinct. A stack trace is the single most useful gift the program can give you. It tells you almost exactly where the bug is, if you read it in the right order.
Read it in two passes.
- 1The top line tells you what happened: the exception type, then a colon, then a message.
NullPointerExceptionmeans you used something that wasnullas if it were a real object. The type and message are your headline. - 2The lines below, each starting with
at, are the call chain, newest first. The topmostatline is exactly where the program was when it crashed. Each line under it is the method that called the one above. You read down the chain until you find a line in your own code, and that is almost always your bug.
Run this program and read the trace it prints. Read the headline, then find the first line that points into code we wrote.
public class Main { public static void main(String[] args) { Greeter g = new Greeter(); System.out.println(g.greet(null)); // line 4 }} class Greeter { String greet(String name) { int n = name.length(); // line 10: name is null here return "Hello, " + name + " (" + n + ")"; }}NullPointerException: Cannot invoke "String.length()" because "name" is null
A variable held null, so there was no object to use.
Walk the trace. The headline says NullPointerException and even tells us the cause: name was null when we called length() on it. The first at line, at Greeter.greet(Main.java:10), points at line 10, inside greet: the line int n = name.length();. That is the crash site. The next line, Main.java:4, is who called greet, and there we see the culprit: we passed null in. The trace handed us both the symptom and its origin in three lines.
Key ideaTop line: what went wrong. First
atline: where it broke. Walk down to the first line in your own files: that is the bug to fix.
null was passed in. A stack trace shows you the crash site at the top and the path that led there below it. Read down the chain. The real fix is often a few frames lower than where the smoke is.Now step up to the same kind of error
The visualizer prints the same trace Java does, and it also stops on the failing line and marks it, with the memory view showing what every variable held. Run the snippet below. It uses an object that was never built, so calling a method on it fails the same way a NullPointerException does. Watch where the run stops and which line is marked.
public class Main { public static void main(String[] args) { Account a = new Account(); a.balance = 100; Account b = null; // never built b.deposit(50); // using a null object: this is the crash System.out.println(a.balance); }} class Account { int balance; void deposit(int amount) { balance = balance + amount; }}NullPointerException: Cannot invoke "Account.deposit(int)" because "b" is null
A variable held null, so there was no object to use.
The run stops at b.deposit(50), and the console shows Java's report: Cannot invoke "Account.deposit(int)" because "b" is null, at Main.main(Main.java:7). b was set to null and never pointed at a real Account, so there is no object to deposit into, and the memory view shows b holding null. The headline is “you used a null as if it were an object”, and the marked line is where you did it. Reading either one, your job is identical: find the null, then walk back to where it should have been a real object.
Working in a real editor#
An IDE, an Integrated Development Environment, is an editor built for one language with tools woven in: it understands your code as you type, not just the letters but the meaning. The popular ones for Java are IntelliJ IDEA, Eclipse, and VS Code with a Java extension. You do not need to master one today. You do need to know the four moves that make you fast, because each one removes a kind of guessing.
Auto-complete
Type a variable, then a dot, and the editor lists every method and field available on it. Instead of remembering whether a string's method is toUpper() or toUpperCase(), you type word. and pick from the list. Auto-complete is the docs brought into the exact spot you are typing. It also quietly teaches you what a type can do.
Go-to-definition
Hold the right key and click a method or class name, and the editor jumps to where it is defined. When you see g.greet(null) and wonder what greet actually does, you do not search the project by hand. You jump straight to its source. This turns a large unfamiliar codebase from a maze into something you can navigate.
The debugger
This is the big one, and it should feel familiar, because it is exactly what the Atelier visualizer has been doing for you all along. A debugger lets you pause a running program and look inside it. The vocabulary is small.
- A breakpoint is a marker you place on a line. The program runs normally until it reaches that line, then pauses so you can look around.
- Step over runs the current line and stops on the next one, treating any method call as a single step.
- Step into goes inside a method call so you can watch it run line by line, rather than skipping past it.
- Inspect variables shows you the current value of every variable in view while you are paused, so you can confirm what the program actually holds, not what you assumed.
Key ideaA debugger is a slow-motion, pausable view of your program with every variable on display. It turns “I think it does this” into “I can see it does this”.
trace snippet and stepped through it watching the values change, you were using a debugger. The step buttons are step over and step into. The state panel is inspect variables. The marked line is the breakpoint you are paused on. A real IDE debugger is the same idea pointed at your own running programs, with the difference that you choose where to pause.Try it
Answer each one in your head or on paper first, then open the answer.
tostring() in a subclass meaning to override toString() from the parent, and you add @Override above it. What happens, and why is that good?word.indexOf("e") and you are not sure what it returns when the letter is not found. What is the right first move, and what are the four things to read?java.lang.NullPointerException ... at Greeter.greet(Greeter.java:7) at Main.main(Main.java:4)Practice
Work through these on your own. Each one builds on this page.
@Override to each method you intended as an override. Then deliberately misspell one method name and confirm the compiler complains. Restore it afterwards. Write one sentence on what the annotation bought you.String.replace in the Java docs. In your own words, write down its signature, what its two parameters mean, what it returns, and whether it changes the original string. Then write a tiny program that confirms your reading.Account for b before depositing. Write two or three sentences explaining, using the words from the stack-trace section, what was wrong before and which line the report pointed at.Spotted a mistake, or a part that did not make sense? Report it by email