Classes as objects #
Some object-oriented languages (like Java, C# or C++) provide ways to access or manipulate classes (almost) as if they were objects.
Static attributes and methods #
Static attributes #
Java (as well as C# and C++) supports attributes that do not belong to a specific instance, but to a class instead. These are often called static attributes (or sometimes class attributes or class variables), as opposed to the attributes that we have seen so far, called instance attributes (or member variables).
Static attributes are marked with the keyword static
.
Example. Consider a version of our game where characteristics for each unit type (e.g. default health, etc.) can be declared manually, in a text file. The path to this file could be stored as a static attribute, as follows:
public class Unicorn { static String configFile = "units/unicorn.txt"; int health; public Unicorn(){ health = getHealthFromConfigFile(configFile); } public regen(){ health++; } }
Each instance of Unicorn
carries its own value for the attribute health
.
But the value of configFile
is unique.
For instance, the following program creates in memory a unique string for configFile
, which is carried by the class.
Unicorn u1 = new Unicorn();
Unicorn u2 = new Unicorn();
Static methods #
Similarly to a static attribute, a static method does not depend on a specific object, as opposed to an instance method.
Example (continued).
We can add to our class
Unicorn
a static method that checks whether the configuration file exists:public class Unicorn{ ... static boolean configFileExists() { return Files.exists(Paths.get(configFile)); } }
(note that the method
getHealthFromConfigFile
also may also be static, if its execution is identical for all instances ofUnicorn
).
Remember that an instance method can be called outside of the class where it is declared, using an instance of the class followed by .
:
Unicorn myUnicorn = new Unicorn();
myUnicorn.regen();
A static method can (syntactically) be called in the same way, for instance:
Unicorn myUnicorn = new Unicorn();
boolean valid = myUnicorn.configFileExists();
However, this is often discouraged, because this syntax can be misleading (in this example, configFileExists
can be mistaken for an instance method).
Alternatively, since the method depends on the class (rather than an instance), the call can be prefixed with the class name. This syntax is often preferred, because it leaves no ambiguity. For instance:
boolean valid = Unicorn.configFileExists();
Note that there may be several instances of a same class in memory during the execution of a program, or no instance at all.
This is why a static method cannot access an instance attribute or instance method.
For instance, adding the following static method to our class would cause a compilation error, because health
is an instance attribute.
public class Unicorn {
static void reduceHealth(){
health--;
}
}
Inheritance #
Two static methods with the same name and signature can be declared in a class and a superclass. In Java’s terminology, this is not called method overriding, but instead method hiding. An important difference is that dynamic dispatch (a.k.a. runtime polymorphism) does not apply to this case (the method to be called is determined at compile time, rather than run time).
Example (continued).
Let us extend our example with a subclass of Unicorn that hides the static method
configFileExists
.public class EvilUnicorn extends Unicorn { static boolean configFileExists(){ return false; } }
Now consider the following program, where the method getUnicorns
produces an array of unicorns that depend on the player’s input.
The specific type of objects in this array (Unicorn
or EvilUnicorn
) cannot be determined at compile time.
Unicorn[] unicorns = getUnicorns();
for (Unicorn unicorn: unicorns){
System.out.println(unicorn.configFileExists());
}
}
In this example, the method of the superclass (i.e. Unicorn.configFileExists
) will be executed for each object in the array, regardless of its type.
This is another reason why calling a static method via an instance (rather than via a class name) is often discouraged.
In this example, writing Unicorn.configFileExists()
(with a capital U
) would make it clear that this loop serves no purpose (the same method is executed unicorns.length
times).
Java interfaces #
Since Java 8 (2014), an interface can also carry static methods and attributes.
Reflection #
Java (as well as C#) offers a mechanism called reflection to intuitively treat classes analogously to objects.
Precisely, each class of a program (e.g. the class Unicorn
) is associated with an object that represents it.
This object is itself an instance of the Java class called Class
.
It can be accessed either:
- via an instance of the class:
Unicorn myUnicorn = new Unicorn();
Class unicornClass = myUnicorn.getClass();
- or via the qualified name of the class (assuming for instance that the file
Unicorn.java
is in the folder<workingDirectory>/src/main/java/org/units
):
Class unicornClass = Class.forName("org.units.Unicorn");
The instance methods of the class Class
are listed here.
Among others, they allow:
- listing the attributes of the class,
- retrieving its immediate superclass, or the interfaces that it implements,
- creating a new instance of this class,
- etc.
Reflection is a powerful feature. It is rarely used in everyday code, but can be helpful in specific situations. Notably, it is used (internally) by numerous Java frameworks such as Spring, Jackson or JUnit.