By example: Java Modifiers
We have previously lectured about how modifers work in Java. Today we are going to work with some live examples. The goal of this lecture is for you to understand how each modifier changes the scope or access of the variable or function.
You should expect to see questions on the upcoming exam that match the examples given below. Your best way to study for this portion of the exam is to work through these programs yourself with a compiler until you understand for yourself which variables can be accessed from different parts of the programs.
These are the Java modifiers that we are going to discuss:
- static
- final
- public
- private
- <no-modifier> == 'package-public'
Note that we have dropped the 'protected' modifier for today. We will revisit protected once we discuss class inheritence.
These modifiers make class members accessible or inaccessible at different times. The hardest part of understanding them is usually getting your head around the idea that it can be beneficial to make a class member inaccessible. It seems counter-intuitive, but making class members inaccessible (and thus increasing your javac compile errors) actually makes it faster and easier to write your program.
Several of the above modifiers may be used together, but not all of them. From the table below, you may pick exactly one from each column:
| Static/Non-Static | Mutable/Non-Mutable | Accessability |
|---|---|---|
| static | final | public |
| <no-modifier> | <no-modifier> | protected |
| private | ||
| <no-modifier> |
Learning by Destructive Testing
Sometimes the best way to learn how to write code is to intentionally write code that will not work, and then observe how it does not work. For this exercise, we are going to try to compile two programs that should not work. We will learn about how the modifiers affect the code by observing where the programs break.
Just ask yourself: How would the Mythbusters teach themselves Java?
Copy the text below and save it to two files names Foo.java and Bar.java. They should be in the same directory. We are going to change these files to demonstrate how the modifiers work.
| Foo.java | Bar.java |
|---|---|
public class Foo {
public Foo() {
System.out.println("Created a Foo.");
}
public String a = "Foo:A";
private String b = "Foo:B";
String c = "Foo:C";
static public String d = "Foo:D";
static private String e = "Foo:E";
static String f = "Foo:F";
public static void main(String[] args) {
Foo f = new Foo();
System.out.println(f.a);
System.out.println(f.b);
System.out.println(f.c);
System.out.println(f.d);
System.out.println(f.e);
System.out.println(f.f);
System.out.println(Foo.a);
System.out.println(Foo.b);
System.out.println(Foo.c);
System.out.println(Foo.d);
System.out.println(Foo.e);
System.out.println(Foo.f);
}
}
|
public class Bar {
public Bar() {
System.out.println("Created a Bar.");
}
public static void main(String[] args) {
Foo f = new Foo();
System.out.println(f.a);
System.out.println(f.b);
System.out.println(f.c);
System.out.println(f.d);
System.out.println(f.e);
System.out.println(f.f);
System.out.println(Foo.a);
System.out.println(Foo.b);
System.out.println(Foo.c);
System.out.println(Foo.d);
System.out.println(Foo.e);
System.out.println(Foo.f);
}
}
|
Compile Foo.java
You should get three errors compiling Foo.java. They should look something like this:
Foo.java:23: non-static variable a cannot be referenced from a static context
System.out.println(Foo.a);
What do they mean? You will see the above error message frequently. What type of variable is Foo.a? (static or non-static?) What type of method is calling it? (static or non-static?)
Cut the three offending lines and paste them into Foo's constructor. The program should compile fine now.
Is the reverse of this error a problem: i.e. can you reference a static variable from a non-static context?
Why is this? We have previously explained how static members are different from non-static members. Once you can understand why they act differently, you will find it easy to figure out when they are in scope to each other.
Compile Bar.java
You will need to make Foo.java compileable before you can run Bar.java.
You should get six errors when you compile Bar.java.
Three of the errors are for trying to access non-static variables through static means. (Foo.a, Foo.b, and Foo.c) These are the same errors from Foo.java. Comment out these three lines and recompile.
The three remaining errors are for private access to variables. Let's create public get() methods to access these three. Add the following code to Foo.java:
public String getB(){
return b;
}
public String getE(){
return e;
}
Now modify the error lines in Bar.java to call these functions vice referencing the variables directly:
System.out.println(f.getB());
System.out.println(f.getE());
System.out.println(Foo.getE());
Compile Bar.java again. You should get one new error:
Bar.java: non-static method getE() cannot be referenced from a static context
System.out.println(Foo.getE());
Why are we getting that error if 'e' is a static variable? Because we forgot to make the return function static. We can call f.getE() just fine, since 'f' is an allocated variable. But we need to make getE() static if we want to be able to call it with Foo.getE() and use it in a static context.
Make the following change to Foo.java:
static public String getE(){
return e;
}
Bar.java should compile and run now. Take a few minutes and look over which variables are available to each program, and whether they can be referenced in a static context. Expect to be given code that looks like this on an exam, and to be asked which lines will be able to access a variable and which will fail to compile.
So what is up with 'package-public'?
We have not talked about variables 'c' and 'f' yet. A variable with no modifier is declared 'package-public'. This means that it is treated as a public variable to any file in the same package (directory). It is treated as private to any file from a different package.
We have not yet gotten to the point where we can create new packages, so you are just going to have to take our word for this right now. As long as you understand when public and private members are accessible, it is a small leap to understand 'package-public'.
It is generally considered poor form to rely on the 'package-public' default when writing a larger program. For any program larger than one file, you should explicitly state whether you want a class member to be public, private or protected.
We finally get to final
'final' is easy - it indicates that a variable should be treated as a constant. According to the specification, a final variable can only be set one time. Copy the code below to a file named 'baz.java' and try to compile it.
public class Baz {
final int test = 1;
public static void main(String[] args) {
Baz b = new Baz();
b.test = 2;
}
}
The compile should fail with an expected error:
Baz.java: cannot assign a value to final variable test
b.test = 2;
But according to the spec, we can change it once, right? So what if we did not actually initialize it when we created it? Try the following code:
public class Baz {
final int test;
public static void main(String[] args) {
Baz b = new Baz();
b.test = 2;
}
}
Did that work any better? Why not? (Hint: what happens if you print a variable to which you have not assigned a value? Does it automatically get a default value? Well, that automatic value counts as the first initialization, so you cannot change it.)
Let's try one more thing: does it make sense to use 'final' for a function? As far as we know, functions cannot change, so that does not seem to make sense. Sense or not, let's give it a try. Compile the following code:
public class Baz {
final int test = 1;
final int myfunc() {
return 3;
}
public static void main(String[] args) {
Baz b = new Baz();
System.out.println(b.myfunc());
}
}
It turns out that the modifier 'final' can be applied to functions. Functions actually can change in some ways that we have not discussed yet. We will revisit this when we get to class inheritence. For now, just remember that 'final' indicates that either a variable or method cannot be changed once it is set.
Experimenting with modifier order
Try some different combinations of the modifiers we have seen. See which compile and which throw errors:
final static public String a; static final public String b; public static final String c; static public final String d; etc. public String e; String public f; public private String g; etc.
Another great test question would be for us to list 4-5 iterations of modifiers and for you to indicate which ones will compile. Order matters, and there are simple patterns to how that order is defined. Try enough combinations to figure out for yourselves which orders are allowable.