Lesson Monday

As we begin to implement more and more tools and concepts into our Java applications, we also open the door for a wider variety of bugs. Throughout this course we'll periodically pause to walk through how to identify and resolve common errors Java students experience.

Compiler Errors

As we learned earlier, Java is a compiled language. Our .java source code files must be compiled into .class bytecode files the computer is capable of reading.

Because the compiler must compile all source code before our programs can run, it is usually the first to spot errors. At this point in the course, most errors you see will come from the compiler. This lesson will walk through how to decipher compiler errors by addressing three of the most common error messages.

Anatomy of a Compiler Error

While there are many different reasons for the compiler to throw errors, most error messages look fairly similar. Here's an example:

/Users/epicodus_staff/Desktop/perry/Java-Prework-Car-Dealership/src/main/java/App.java
Error:(13, 29) java: constructor Vehicle in class models.Vehicle cannot be applied to given types;
  required: int,java.lang.String,java.lang.String,int,int
  found: int,java.lang.String,java.lang.String,int
  reason: actual and formal argument lists differ in length

Each compiler error message follows this general format. Let's consider each piece closely:

  • /Users/staff/Desktop/java/car-dealership/src/main/java/App.java:9. Each error message should contain the file path of the source code file containing the error. Use this to hone in on where the issue is occurring.

  • If you see multiple different filenames with differing line numbers that indicate errors, such as one in a testing file and one in a class file, the testing file is likely calling the method that contains the error.

  • A brief explanation of the error should be provided directly after the file path. In the example above, we're provided: error: constructor Vehicle in class Vehicle cannot be applied to given types;. Always read this line very, very carefully.

  • After this explanation, the offending line of code is usually included.

  • After this line of broken code, there is often section containing additional details. This content differs slightly depending on the type of error, but will usually contain valuable details. In the message above, the following is included:

  required: int,java.lang.String,java.lang.String,int,int
  found: int,java.lang.String,java.lang.String,int
  reason: actual and formal argument lists differ in length

Remember, our error said constructor Vehicle in class Vehicle cannot be applied to given types. This section is providing more details about why we cannot apply the constructor:

  • required: Refers to the arguments the constructor requires.
  • found: Refers to the actual arguments the compiler found us using in the source code.
  • reason: Provides a reason why these arguments don't work. In our case, actual and formal argument lists differ in length. That is, the number of arguments the constructor requires and the number of arguments we actually gave it are different.

If we check the specific line of code in the file provided by the error, we can see that this usage of the constructor is missing an argument:

App.java
public class App {
  public static void main(String[] args) {
    Console myConsole = System.console();

    Vehicle hatchback = new Vehicle(1994, "Subaru", "Legacy", 170000);  # <-- missing the fifth argument.
    Vehicle suv = new Vehicle(2002, "Ford", "Explorer", 100000, 7000);
    Vehicle sedan = new Vehicle(2015, "Toyota", "Camry", 50000, 30000);
...

Other Common Errors

Constructor Cannot be Applied to Given Types

The "constructor cannot be applied to given types" error depicted in the example above is fairly common. It generally means that a constructor was unable to create an object with the information provided to it. As we saw, you can use error's details to figure out why it was unable to create an object.

Incompatible Types

Another common error is "incompatible types". This usually occurs when we declare something as one data type, but place an object of another data type in its spot. This error looks something like this:

/Users/epicodus_staff/Desktop/perry/Java-Prework-Car-Dealership/src/main/java/App.java
Error:(17, 66) java: incompatible types: java.lang.String cannot be converted to int

Again, notice the error provides the specific file and line of code. It also says String cannot be converted to int. If you look closely, you'll notice that the fourth argument in the constructor is a string:

Vehicle crossover = new Vehicle(1998, "Toyota", "Rav-4", "200000", 3500);

But, when we defined our constructor, we clearly stated that this argument should be an int:

Vehicle.java
...
  public Vehicle(int year, String brand, String model, int miles, int price) {
    this.year = year;
    this.brand = brand;
    this.model = model;
    this.miles = miles;
    this.price = price;
  }
...

If we remove the quotation marks, the issue should be solved. When this error occurs pay special attention to the declared data types in the line(s) of code referenced. At least one piece of information does not match the intended data type.

Cannot Find Symbol

"Cannot find symbol" is another common error. It looks something like this:

/Users/epicodus_staff/Desktop/perry/Java-Prework-Car-Dealership/src/main/java/models/Vehicle.java
Error:(19, 22) java: cannot find symbol
  symbol:   variable rice
  location: class models.Vehicle

A symbol is a line of code that represents something else. When we create variables or methods, their names are symbols. This error usually means we spelled something incorrectly, or didn't define something properly. In our case, we can look closely at the specific file and line of code the error provides:

Vehicle.java
  public Vehicle(int year, String brand, String model, int miles, int price) {
    this.year = year;
    this.brand = brand;
    this.model = model;
    this.miles = miles;
    this.price = rice;
  }
...

And look, there's the error! The price = rice; line provided by the message is clearly trying to set the price member variable equal to rice. But we didn't pass in an argument named rice we passed in an argument named price:

src/main/java/models/Vehicle.java
...
  public Vehicle(int year, String brand, String model, int miles, int price) {
...

So, Java is throwing an error because we never defined anything named rice. It does not understand what rice is. Or, it 'cannot find the symbol' rice. If we fix our typo by changing rice to the correct price, the error should be resolved.

As you'll discover, there are many different errors the compiler can throw. These are only a few of the most common. But if you follow the process demonstrated here to carefully decipher error messages, you'll be able to squash bugs of any variety.

Using System.out to Log Values

We are already familiar with using System.out.println();. It's a simple way to communicate with the user and ask them questions. But System.out is also useful for catching errors and debugging. We can use it anywhere in our application; inside complex branching, a loop, or even inside a test. This can be especially helpful when working with objects in tests, which we will do later this week.

Here is an example test using System.out.println():

src/test/java/models/PingPongTest.java
@Test
public void runPingPong_replaceMultiplesOf3_ArrayList() throws Exception {
   PingPong testPingPong = new PingPong();

   ArrayList<Object> expectedOutput = new ArrayList<Object>();
   System.out.println(expectedOutput.toString());
   expectedOutput.add(1);
   expectedOutput.add(2);
   expectedOutput.add("ping");
   System.out.println(expectedOutput.toString());
   assertEquals(expectedOutput, testPingPong.runPingPong(3));
}

and here is how this will display in the console:

[]
[1, 2, ping]

Process finished with exit code 0

Before you ask for help from your instructor, be sure to try and resolve the issues you are having by getting more information with a System.out.

Using the Debugger

Similar to the JavaScript debugger; we used in Intro to Programming, IntelliJ uses a debugger to help resolve bugs in tests and application code. If code compiles without an error and doesn't crash, but doesn't give you the result we expect, using System.outand the debugger can help track down what's actually happening. Here's how we use the debugger in IntelliJ.

Open a file we wish to debug. Click on the right-hand side of the editor window, next to the line number. We should see a red dot appear. This is called a breakpoint. (Note that we cannot set a breakpoint on an empty line).

intellij-breakpoint

A breakpoint is like writing the keyword debugger into our JavaScript code. When the parser hits the breakpoint, code execution will freeze, and we can step through code slowly to inspect it for issues.

If we click the Run button, we won't see any changes though. We need to run the app in "debug mode" for the breakpoint to fire. Find the small button next to Run button that looks suspiciously like a beetle.

intellij-debug-button

Now, if we hit this button, our code will execute in debug mode. Find an app to experiment with, set a breakpoint, and try out debug mode now.

When the debugger fires, we'll see controls similar to the JavaScript debugger.

intellij-debug-window

The most important areas of this screen are numbered for reference:

  1. Here are currently-active variables and their content. We can pop open anything preceded with an |> to inspect it's contents. This is very useful to determine if objects or other variables contain the correct content.

  2. Shows the current execution point

  3. Step into the next line in the file. This is the command we'll use most frequently.

  4. (Blue Button) Step to the next line executed. This means to step into code, such as a function. In many cases this will be code we did not write, such as a function call. Try stepping out (5) if we get stranded. We can ignore the Red "force step in" button, generally speaking.

  5. Step Out button. If we stepped into a function; whether our own, or method we did not write, we can step back out with this button.

  6. Re-Run the application in debug mode, say, after a code change.

  7. Resume the app, ignoring the breakpoint.

  8. Pause

  9. Stop the app completely.

  10. Here we can see the lines being executed, and the variables they are producing.

Get comfortable using the debugger for both testing and frontend developing. It can save a huge amount of trial, error, and frustration. To run the debugger in a test, set a breakpoint and hit the beetle button, or, when right-clicking on the editor tab, select Debug yourFilenameTest.

And don't forget to remove breakpoints when you are done debugging!