Modifiers are keywords that let us fine-tune access to our class and its members, their scope, and behavior in certain situations. For example, we can control which classes/objects can access certain members of our class, whether a class can be inherited or not, whether we can override a method later, whether we should override a method later, etc. Show Modifier keywords are written before the variable/method/class (return) type and name, e.g. private int myVar or public String toString(). Modifiers in Java fall into one of two groups - access and non-access: native is not covered in more detail below since is a simple keyword that marks a method that will be implemented in other languages, not in Java. It works together with the Java Native Interface (JNI). It's used when we want to write performance critical sections of code in more performance-friendly languages (like C). Want to learn more about access modifiers, as opposed to non-access? If so, check out our article Access Modifiers in Java. Non-Access ModifiersThese types of modifiers are used to control a variety of things, such as inheritance capabilities, whether all objects of our class share the same member value or have their own values of those members, whether a method can be overridden in a subclass, etc. A brief overview of these modifiers can be found in the following table:
The static ModifierThe static modifier makes a class member independent of any object of that class. There are a few features to keep in mind here:
Note: It's very important to note that static variables and methods can't access non-static (instance) variables and methods. On the other hand, non-static variables and methods can access static variables and methods. This is logical, as static members exist even without an object of that class, whereas instance members exist only after a class has been instantiated. Static VariablesFor variables, we use static if we want the variable to be common/shared for all objects. Let's take a look at how static variables behave differently from regular instance variables: class StaticExample { public static int staticInt = 0; public int normalInt = 0; public StaticExample() { staticInt++; normalInt++; } } System.out.println(StaticExample.staticInt); StaticExample object1 = new StaticExample(); System.out.println(object1.staticInt); System.out.println(object1.normalInt); StaticExample object2 = new StaticExample(); System.out.println(object2.staticInt); System.out.println(object2.normalInt); object1.staticInt = 10; object1.normalInt = 10; System.out.println(object2.staticInt); System.out.println(object2.normalInt);Static MethodsThe most common example of using static is the main() method, it is declared as static because it must be called before any objects exist. Another common example is the Math class since we use the methods of that class without making an instance of it first (like Math.abs()). A good way to think about static methods is "Does it make sense to use this method without first creating an object of this class?" (e.g. you don't need to instantiate the Math class in order to calculate the absolute value of a number). Static methods can be used to access and modify static members of a class. Though, they're commonly used to manipulate method parameters or compute something and return a value. These methods are referred to as utility methods: static int average(int num1, int num2) { return (num1+num2)/2; }This utility method can be used to calculate the average of two numbers, for an example. As mentioned above, the Math class is often used for calling static methods. If we look at the source code, we can notice that it mostly offers utility methods: public static int abs(int i) { return (i < 0) ? -i : i; } public static int min(int a, int b) { return (a < b) ? a : b; } public static int max(int a, int b) { return (a > b) ? a : b; }Static BlocksThere's also a static block. A static block gets executed only once when the class is first instantiated (or a static member has been called, even if the class isn't instantiated), and before the rest of the code. Let's add a static block to our StaticExample class: class StaticExample() { ... static { System.out.println("Static block"); } ... } StaticExample object1 = new StaticExample(); StaticExample object2 = new StaticExample();Irrespective of their position in the class, static blocks are initialized before any other non-static blocks, including constructors: class StaticExample() { public StaticExample() { System.out.println("Hello from the constructor!"); } static { System.out.println("Hello from a static block!"); } }Instantiating this class would output: Hello from a static block! Hello from the constructor!If multiple static blocks are present, they will run in their respective order: public class StaticExample { static { System.out.println("Hello from the static block! 1"); } public StaticExample() { System.out.println("Hello from the constructor!"); } static { System.out.println("Hello from the static block! 2"); } }Instantiating this class would output: Hello from the static block! 1 Hello from the static block! 2 Hello from the constructor!Static ImportsAs already mentioned, it's better to call static members prefixed with the class name, rather than the instance name. Also, in some cases, we never really instantiate a class with static methods, such as the Math class, which offers numerous utility methods regarding maths. That being said, if we use a class' static members often, we can import individual members or all of them using a static import. This allows us to skip prefixing their calls with the class name: Or, if we'd like to import all static members of ClassOne, we could do it like so: package packageTwo; import static packageOne.ClassOne.*; public class ClassTwo { public ClassTwo() { i = 20; j = 10; } }The same applies to methods: Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it! package packageTwo; import static packageOne.ClassOne.*; public class ClassTwo { public ClassTwo() { hello(); } }Running this would output: Hello World!This may not seem that important, but it helps when we call many static members of a class: public int someFormula(int num1, int num2, int num3) { return Math.ceil(Math.max(Math.abs(num1), Math.abs(num2))+Math.max(Math.abs(num2), Math.abs(num3)))/(Math.min(Math.abs(num1), Math.abs(num2))+Math.min(Math.abs(num2), Math.abs(num3))); } import static java.lang.Math.*; public int someFormula(int num1, int num2, int num3) { return ceil(max(abs(num1), abs(num2))+max(abs(num2), abs(num3)))/(min(abs(num1), abs(num2))+min(abs(num2), abs(num3))); }The final ModifierThe keyword final can have one of three meanings:
Named ConstantsAdding the final modifier to a variable declaration makes that variable unchangeable once it's initialized. The final modifier is often used together with the static modifier if we're defining constants. If we only apply static to a variable, it can still be changed easily. There's also a naming convention tied to this: static final double GRAVITATIONAL_ACCELERATION = 9.81;Variables such as these are often included in utility classes, such as the Math class, accompanied by numerous utility methods. Though, in some cases, they also warrant their own classes, such as Constants.java: public static final float LEARNING_RATE = 0.3f; public static final float MOMENTUM = 0.6f; public static final int ITERATIONS = 10000;Note: when using final with object reference variables, be careful of what type of behavior you expect. Consider the following: class MyClass { int a; int b; public MyClass() { a = 2; b = 3; } } final MyClass object1 = new MyClass(); MyClass object2 = new MyClass();The reference variable object1 is indeed final and it's value can't change, but what does that mean for reference variables anyway? It means that object1 can't change which object it is pointing to anymore, but we can change the object itself. This is something that often confuses people: object1.a = 5;Method parameters can also be declared final. This is used to make sure our method doesn't change the parameter it receives when it's called. Local variables can also be declared final. This is used to make sure the variable receives a value only once. Preventing OverridingIf you specify the final modifier while defining a method, any future subclass can't override it. class FinalExample { final void printSomething() { System.out.println("Something"); } } class ExtendsFinalExample extends FinalExample { void printSomething(String something) { System.out.println(something); } }One small bonus of declaring truly final methods as final is a slight performance boost whenever we call this method. Usually, Java resolves method calls dynamically at run-time, but with methods declared final, Java can resolve a call to it at compile time, or if a method is really small it can simply inline calls to that method since it "knows" that it won't be overridden. This eliminates the overhead associated with a method call. Preventing InheritanceThis usage of final is fairly straight-forward, a class defined with final cannot be inherited. This of course implicitly declares all methods of that class final as well (they can't be overridden if the class can't be inherited in the first place). final class FinalExample {...}The abstract ModifierThe abstract modifier is used to define methods that will be implemented in a subclass later on. Most often it's used to suggest that some functionality should be implemented in a subclass, or (for some reason) it can't be implemented in the superclass. If a class contains an abstract method, it must also be declared abstract. Note: You can not create an object of an abstract class. In order to do that, you need to provide an implementation for all the abstract methods. An example would be if we had a simple class called Employee that encapsulates data and methods for an employee. Let's say that not every employee is paid in the same way, some types of employees are paid by the hour and some are paid a fixed salary. abstract class Employee { int totalHours; int perHour; int fixedRate; ... abstract int salary(); ... } class Contractor extends Employee { ... int salary() { return totalHours*perHour; } ... } class FullTimeEmployee extends Employee { ... int salary() { return fixedRate; } ... } class Intern extends Employee { ... int salary() { return 0; } ... }If a subclass doesn't provide an implementation to all abstract methods in the superclass, it has to be declared as abstract as well, and an object of that class can't be created. Note: abstract is used heavily with polymorphism, e.g. we'd say ArrayList<Employee> employees = new ArrayList();, and add Contractor, FullTimeEmployee, and Intern objects to it. Even though we can't create an object of the Employee class, we can still use it as a reference variable type. The synchronized ModifierWhen two or more threads need to use the same resource, we somehow need to make sure that only one of them has access to it at a time, i.e. we need to synchronize them. This can be achieved in several ways, and one simple and readable way (albeit with somewhat limited usage) is by using the synchronized keyword. An important concept to understand before you see how to use this keyword is the concept of a monitor. Every object in Java has its own implicit monitor associated with it. A monitor is a "mutually exclusive" lock, meaning that only one thread can "own" a monitor at a time. When a thread enters the monitor, no other thread can enter it until the first thread exits. This is what synchronized does. Threads are beyond the scope of this article so I will focus on the syntax of synchronized only. We can synchronize access to methods, and blocks of code. Synchronizing blocks of code works by providing an object instance that we want to synchronize access to and the code that we want to perform related to that object. class SynchronizedExample { ... SomeClass object = new SomeClass(); .... synchronized(object) { } synchronized void doSomething() { ... } ... }The volatile ModifierThe volatile modifier tells Java that a variable can be changed unexpectedly by some other part of the program (like in multithreaded programming), and so that variable's value is always read from main memory (and not from CPU cache), and that every change to the volatile variable is stored in main memory (and not in CPU cache). With this in mind, volatile should only be used when necessary, since reading/writing to memory every time is more expensive than doing so with CPU cache and only reading/writing to memory when necessary. In simplified terms - when a thread reads a volatile variable value, it is guaranteed that it will read the most recently written value. Basically, a volatile variable does the same thing that synchronized methods/blocks do, we just can't declare a variable as synchronized. The transient ModifierWhen a variable is declared as transient, that means that its value isn't saved when the object is stored in memory. When we try to read an object that contains transient variables, all transient variable values will be set to null (or default values for primitive types), no matter what they were when we wrote the object to the file. Another example of usage would be when a variable's value should be derived based on other data (such as someone's current age), and isn't part of the persistent object state. Note: Something very interesting happens when we use transient and final together. If we have a transient final variable that is evaluated as a constant expression (Strings or primitive types) the JVM will always serialize it, ignoring any potential transient modifier. When transient final is used with reference variables, we get the expected, default behavior of transient. ConclusionModifiers are keywords that let us fine-tune access to our class and its members, their scope and behavior in certain situations. They provide fundamental traits for our classes and their members. Every developer should be thoroughly be acquainted with them to make the best use of them. Like being aware that protected access control can easily be bypassed, or the transient final modifier when it comes to constant expressions. |