A draft that looks finished#
Somebody set out to model a car. They named the class, listed four attributes, commented every one, wrote a method that changes an attribute and a method that returns it, and reached for a Scanner at the end. This is a normal, honest first attempt:
public class MyCar {
private int year; //Year of vehicle
private string make; //Make of Vehicle
private string model: //Model of Vehicle
private string color; //Color of vehicle
public void setYear(int year){
year = 2026;
}
public int getYear(){
return year;
}
Scanner Keyboard = new scanner(System.in);
How many things are wrong with it? Count them before you scroll. Most people find two or three. There are six, and the most expensive one compiles perfectly.
MyCar.java:4: error: ';' expected
private string model: //Model of Vehicle
^
MyCar.java:16: error: reached end of file while parsing
Scanner Keyboard = new scanner(System.in);
^
2 errors
Two errors, and neither one mentions string. That is the first thing worth understanding about javac: it cannot look for missing types until it has finished reading the shape of the file. A stray : and a missing } stop it at the parsing stage, so everything after that is still unexamined. Fix errors from the top down, and recompile after each one — the list you get next is a different list, not a shorter one.
This message almost always means one thing: a missing closing brace. The compiler read the whole file still waiting for a } that never arrived. It points at the last line because that is where it ran out of file, not because that line is wrong.
Fix the syntax, and the next layer appears#
Change the : to a ;, close the class with a }, and compile again. Same file, otherwise untouched:
MyCar.java:3: error: cannot find symbol
private string make; //Make of Vehicle
^
symbol: class string
location: class MyCar
MyCar.java:4: error: cannot find symbol
private string model; //Model of Vehicle
^
symbol: class string
location: class MyCar
MyCar.java:5: error: cannot find symbol
private string color; //Color of vehicle
^
symbol: class string
location: class MyCar
MyCar.java:15: error: cannot find symbol
Scanner Keyboard = new scanner(System.in);
^
symbol: class Scanner
location: class MyCar
MyCar.java:15: error: cannot find symbol
Scanner Keyboard = new scanner(System.in);
^
symbol: class scanner
location: class MyCar
5 errors
Five errors from two mistakes, both of them about capital letters.
String is a class, and Java is case sensitive all the way down. string is not a misspelling that the compiler forgives — it is a name for a class that does not exist, which is exactly what cannot find symbol: class string says. The same goes for new scanner(...). Convention is not decoration here: class names begin with a capital letter, variables and methods begin with a lowercase one, and the compiler enforces the first half of that rule whether you meant it or not.
Scanner on line 15 fails for a different reason. It is spelled correctly, and the compiler still cannot find it, because Scanner lives in java.util and nothing has said so. That needs a line above the class:
import java.util.Scanner;
String never needs an import because it lives in java.lang, which is imported into every file automatically. That is the whole difference between the two.
The Scanner should not be here at all#
Adding the import would make line 15 compile. It would still be the wrong line to write, and this is a design point worth more marks than the spelling ones.
MyCar models a car. A car has a year, a make, a model and a colour. A car does not have a keyboard. A Scanner field describes how one particular program happens to collect information — and the moment it is inside the class, MyCar can only be used by programs that have a console attached. Not a test, not a GUI, not a file reader.
Input belongs in the class that runs the program, next to main:
import java.util.Scanner;
public class Garage {
public static void main(String[] args) {
Scanner keyboard = new Scanner(System.in);
System.out.print("Year: ");
int year = keyboard.nextInt();
// ...and then hand the values to the class
}
}
The useful test, whenever you are deciding whether something belongs in a class: is this a fact about the object, or a fact about this program? Year, make, model and colour are facts about the car. The keyboard is a fact about the program.
Now it compiles, and it is still wrong#
Capitalise the types, drop the Scanner, and the class finally compiles. Here it is, clean:
public class MyCar {
private int year;
private String make;
private String model;
private String color;
public void setYear(int year){
year = 2026;
}
public int getYear(){
return year;
}
}
No errors. Not even a warning with javac -Xlint:all. So test it — make a car, set its year, read the year back:
public class Demo {
public static void main(String[] args) {
MyCar mine = new MyCar();
mine.setYear(2020);
System.out.println(mine.getYear());
}
}
What prints?
0
Not 2020, and not 2026 either. Zero.
Two separate things went wrong in three lines of method body, and neither one is a typo.
1. The parameter hides the field
Inside setYear(int year) there are two things called year: the field belonging to the object, and the parameter belonging to the method. When a name is ambiguous, Java always resolves it to the closest one — the parameter. So the line
year = 2026;
assigns 2026 to the parameter, which stops existing the instant the method returns. The field is never touched, so it keeps the value it was born with: 0, the default for an int.
This is called shadowing, and it is the single most common bug in a first set of setters. The fix is the keyword this, which means the object this method was called on:
public void setYear(int year) {
this.year = year; // this.year is the field, year is the parameter
}
Read it left to right and it says what it does: put the parameter into the field. Nothing else in the method needs this., because nothing else is ambiguous — getYear can return a bare year and be perfectly clear, since there is no parameter competing with it.
year = 2026; is a legal assignment to a legal variable. The compiler has no way to know you meant the other one, so it says nothing — not an error, not a warning, not with every lint check switched on. The only symptom is a getter that keeps returning 0 or null. If a setter appears to do nothing, look for a missing this. before you look anywhere else.
2. A setter that ignores its own parameter
Even with this. in place, this.year = 2026; would be wrong. A mutator's job is to store the value it was handed, not a value chosen when the code was written. Hard-coding 2026 makes setYear a method that can only ever mean one year, and the parameter becomes decoration.
The shape almost never varies:
public void setThing(Type thing) { this.thing = thing; }
public Type getThing() { return thing; }
Accessors, mutators, and what private buys you#
The two method names have formal words attached, and exams use them:
| Term | Also called | What it does | Shape |
|---|---|---|---|
| Accessor | getter | reports an attribute, changes nothing | public int getYear() |
| Mutator | setter | changes an attribute, returns nothing | public void setYear(int y) |
They only mean anything because the fields are private. Try to reach a private field from outside its class and the compiler stops you:
MyCar mine = new MyCar(2020, "Honda", "Civic", "blue");
mine.year = 1999;
P2.java:4: error: year has private access in MyCar
mine.year = 1999;
^
1 error
That error is the feature. Because the only route to year runs through setYear, the class gets to have an opinion about what a year is allowed to be, and that opinion lives in exactly one place:
public void setYear(int year) {
if (year >= 1886 && year <= 2100) {
this.year = year;
}
}
Make the field public and that check is unenforceable, because any line of code anywhere can walk straight past it. This is what encapsulation means in practice — not secrecy, but keeping the rules about an attribute in the same place as the attribute.
Java has four levels of access, and for a class like this one you only need the two ends:
| Modifier | Visible to | Use it for |
|---|---|---|
private | this class only | fields, and helper methods nobody outside should call |
| (none) | other classes in the same package | rarely a deliberate choice; it is what you get by accident |
protected | same package, plus subclasses | members a subclass genuinely needs |
public | everywhere | the class itself, its constructors, and its accessors and mutators |
The default choice for a class you are writing today: every field private, every method that the outside world is meant to call public. Deviate only when you can say why.
A car's colour can change; its make cannot become Toyota. Leaving out setMake is a decision, and a defensible one — a field with a getter and no setter is read-only to the outside world. If your assignment asks for a full set of accessors and mutators, write them all; otherwise, giving a setter to something that never changes is a small design mistake, not a safe default.
The constructor#
So far every MyCar is born empty and has to be filled in one setter at a time — four calls before the object is usable, and nothing stops you forgetting one. A constructor is the method that runs once, at creation, to put the object into a valid state immediately.
It has two distinguishing features and no others: its name is exactly the class name, and it declares no return type — not even void.
public MyCar(int year, String make, String model, String color) {
this.year = year;
this.make = make;
this.model = model;
this.color = color;
}
Same this. rule as the setter, four times over, and for the same reason. Now one line does what four did:
MyCar mine = new MyCar(2020, "Honda", "Civic", "blue");
There is a catch that surprises people, and it is worth meeting on purpose rather than at 2am. Until you wrote that constructor, new MyCar() worked — a class with no constructor gets a free no-argument one that leaves every field at its default. The moment you write any constructor, the free one disappears:
P3.java:3: error: constructor MyCar in class MyCar cannot be applied to given types;
MyCar p = new MyCar();
^
required: int,String,String,String
found: no arguments
reason: actual and formal argument lists differ in length
If you want both ways of making a car, write both — two constructors with different parameter lists is overloading, and it is normal:
public MyCar() {
this(2026, "unknown", "unknown", "unpainted");
}
this(...) on the first line of a constructor calls another constructor of the same class, so the real work stays written once.
Those defaults matter, because they are what an uninitialised field holds: 0 for int, 0.0 for double, false for boolean, and null for every object reference — String included. A String field you forgot to set is not ""; it is null, and it will print as the word null.
toString: the method that describes everything#
Requirement five — a method that returns a String describing all of the attributes — already has a name in Java, and using that name is what makes it work everywhere. Print an object without it and you get this:
MyCar@2a139a55
That is not an error, and not a bug. Every class in Java inherits a toString from Object, and the inherited one prints the class name, an @, and the object's hash code in hexadecimal. It is a machine's answer. The number changes between runs, and it is not an address you can use for anything.
Give the class its own and the machine's answer is replaced by yours:
@Override
public String toString() {
return year + " " + make + " " + model + " (" + color + ")";
}
The signature has to match exactly — public, returns String, named toString, no parameters. Name it describe() instead and it is a perfectly good method that Java will never call on your behalf.
And it does call it on your behalf, in two places you use constantly: System.out.println(mine), and any string concatenation such as "Repainted: " + mine. You never write mine.toString() in normal code.
@Override is forIt is optional — the method works without it. What it does is ask the compiler to check that you really are replacing an inherited method. Write toSting by mistake and, without the annotation, you get a new method and silent wrong output; with it, you get a compile error naming the problem. Put it on every method you intend to override.
The finished class#
Every fix, assembled. This compiles clean under javac -Xlint:all on OpenJDK 21:
public class MyCar {
// Attributes — private, so only MyCar can change them directly.
private int year;
private String make;
private String model;
private String color;
// Constructor — runs once, when the object is created.
public MyCar(int year, String make, String model, String color) {
this.year = year;
this.make = make;
this.model = model;
this.color = color;
}
// Mutators — change an attribute.
public void setYear(int year) { this.year = year; }
public void setColor(String color) { this.color = color; }
// Accessors — report an attribute.
public int getYear() { return year; }
public String getMake() { return make; }
public String getModel() { return model; }
public String getColor() { return color; }
// Describes every attribute as one String.
@Override
public String toString() {
return year + " " + make + " " + model + " (" + color + ")";
}
}
And the class that uses it. main lives here, not in MyCar — one class models the thing, the other runs the program:
public class Garage {
public static void main(String[] args) {
MyCar mine = new MyCar(2020, "Honda", "Civic", "blue");
MyCar hers = new MyCar(2017, "Toyota", "Corolla", "silver");
System.out.println(mine);
System.out.println(hers);
mine.setColor("red");
mine.setYear(2021);
System.out.println("Repainted: " + mine);
System.out.println("Year is now " + mine.getYear());
}
}
2020 Honda Civic (blue)
2017 Toyota Corolla (silver)
Repainted: 2021 Honda Civic (red)
Year is now 2021
Look at the third and fourth lines against the second. Changing mine did nothing at all to hers, because new was called twice and each call built a separate object with its own copy of all four fields. The class is one description; the objects are independent things made from it. That is the whole idea, and printing two of them side by side is the cheapest way to see it.
The six requirements, checked off#
The assignment on the board is nearly always worded the same way. Against the finished code:
| # | Requirement | Where it is |
|---|---|---|
| 1 | a class that models the object | public class MyCar |
| 2 | at least one attribute | four private fields |
| 3 | a method that changes an attribute | setYear, setColor — with this. |
| 4 | a method that returns an attribute | getYear and the other three accessors |
| 5 | a method returning a String describing all attributes | toString |
| 6 | at least two objects instantiated | mine and hers in Garage.main |
| — | appropriate access modifier on each member | fields private, class and methods public |
Where these go wrong#
- A setter without
this.. Compiles, runs, warns about nothing, and does nothing. Symptom: a getter that always returns0ornull. - A setter that hard-codes a value instead of storing its parameter. The parameter list is the giveaway — if the body never mentions the parameter, the method is ignoring its own input.
- Lowercase
stringorscanner.cannot find symbolnaming a class you did not think you wrote is nearly always a capitalisation problem. - A colon instead of a semicolon, or any missing brace. These stop the parser, which is why the error list afterwards looks unrelated to the mistake and gets longer once you fix them.
- A
Scannerfield inside the model class. Compiles once imported, and welds the class to a console it should know nothing about. - Writing a constructor and then calling
new MyCar(). The free no-argument constructor is gone the moment you write one of your own. - Naming the describe-everything method anything but
toString. It works when you call it and never runs whenprintlnwould have called it for you. - Two classes, one file, both
public. Only onepublicclass per file, and the file must be named after it —MyCar.javaandGarage.java, not both in one.
Test yourself in the free Kestrel Exams app
Topic-selectable practice — offline, no ads, no account.
Practice this topic →Frequently asked questions#
Why does my setter not change anything?
Almost always because the parameter has the same name as the field, so the assignment writes the parameter back onto itself and the field is never touched. Inside setYear(int year), the bare name year means the parameter. Write this.year = year, where this.year is the field and year is the parameter. It compiles either way and javac gives no warning, so the only symptom is a getter that keeps returning 0.
What is the difference between an accessor and a mutator?
An accessor returns the value of an attribute and changes nothing — getYear(). A mutator changes an attribute and usually returns nothing — setYear(int year). They are the same idea as getters and setters; accessor and mutator are the words most textbooks and exams use.
Why write private fields and public getters instead of just making the fields public?
Because a private field can only be changed by code inside the class, which means the class can enforce its own rules. If year is public, any code anywhere can set it to −4000 and the class cannot stop it. If year is private and the only way in is setYear, the check lives in one place. It also means you can change how the attribute is stored later without breaking every caller.
Do I have to write a constructor?
No. If you write no constructor at all, Java supplies a no-argument one that leaves every field at its default (0 for numbers, false for boolean, null for object references such as String). But the moment you write any constructor, that free one disappears. If you write MyCar(int, String, String, String) and then call new MyCar(), the compiler reports that the constructor cannot be applied to the given types.
Why does printing my object show something like MyCar@2a139a55?
That is the toString inherited from Object: the class name, an @, and the hash code in hexadecimal. It is not an error and not a memory address you can use. Write your own public String toString() that returns a readable line, and println will call it automatically.
Should a class like MyCar contain a Scanner?
No. A class that models an object should hold what the object is, not how a program gets input. Put the Scanner in the class with main, read the values there, and pass them into the constructor. A Scanner field also makes the class unusable anywhere without a console — a test, a GUI, or a file reader.
Is Java case sensitive for type names like String?
Yes, completely. String is a class and string is not a type at all, so private string make; fails with cannot find symbol: class string. The same applies to Scanner and scanner. Class names start with a capital letter by convention and the compiler treats a different capitalisation as a different, non-existent name.
Something here not clear? A topic you wish we covered? Tell us. We read every message, and a request is the fastest way to get a guide written — several of these exist because somebody asked.
