This page contains a bunch of examples that go over key
features of objects in Java. None of the examples are accompanied
by separate explanations; rather, the examples are explained
in comments embedded in the code. Prior to reading this, you
might want to read through simpler explanations here:
class A {
// Static data and methods:
static int data1 = 1;
static void methodOne ()
{
System.out.println ("method one: data1=" + data1);
}
// Dynamic data and methods:
int data2 = 2;
void methodTwo ()
{
System.out.println ("method two: data2=" + data2);
}
}
public class TestClasses {
public static void main (String[] argv)
{
// Call the static method directly using the class name,
// the dot-operator and the method name.
A.methodOne ();
// Access static data similarly:
System.out.println (A.data1); // Prints "1".
// Note: Java uses the term "class method" to refer to a static method.
// The term "class method" is just their jargon, and is not a reserved word.
// For a dynamic method, we need to first create an instance and
// then call the method using the instance variable.
A a = new A ();
a.methodTwo ();
// Access data similarly:
System.out.println (a.data2); // Prints "2".
// Create another instance:
A x = new A ();
x.data2 = 3;
x.methodTwo (); // Prints "3".
a.methodTwo (); // Prints "2" (Variable "a" was not affected).
// Note: the variable "a" is really a pointer that points to a blob
// of memory on the heap. This blob contains space for data like "data2".
// The blob assigned to variable "x" is different and has its own space
// allocated for dynamic data and methods.
// It also helps to think that it has space for methods like "methodTwo()".
// In an actual implementation, only a pointer to the method is stored
// in the blob, so that the real code is elsewhere (pointed to, by the pointer).
// Java jargon: the term "instance variables" is used for dynamic variables,
// just as they use "instance methods" instead of "dynamic methods".
}
}
class A {
// Static data:
static int data1 = 1;
static int data3 = 3;
// Two static methods:
static void methodOne ()
{
System.out.println ("method one: data1=" + data1);
}
static void methodThree ()
{
System.out.println ("method three: data3=" + data3);
}
// Instance data:
int data2 = 2;
int data4 = 4;
// Two instance methods:
void methodTwo ()
{
System.out.println ("method two: data2=" + data2);
}
void methodFour ()
{
System.out.println ("method four: data4=" + data4);
}
}
class B extends A {
// B's own static data, not in A:
static int data5 = 5;
// B's own static methods, not in A:
static void methodFive ()
{
System.out.println ("method five: data5=" + data5);
}
// B overrides A's static variable "data3":
static int data3 = 30;
// B overrides A's static method "methodThree":
static void methodThree ()
{
System.out.println ("B's method three: data3=" + data3);
}
// B's own instance data:
int data6 = 6;
// B's own instance method:
void methodSix ()
{
System.out.println ("method six: data6=" + data6);
}
// B overrides (overshadows) A's instance variable "data4":
int data4 = 40;
// B overrides A's instance method "methodFour":
void methodFour ()
{
System.out.println ("B's method four: data4=" + data4);
}
}
public class TestClasses2 {
public static void main (String[] argv)
{
// We don't need an instance for static methods and data.
// First, we can always access B's newly created methods (not in A):
B.methodFive ();
// Then, B inherits everything from A. For example, it inherits
// "methodOne", that we can call in B:
B.methodOne ();
// B overrode A's "methodThree()" with it's own (different) version:
B.methodThree (); // Prints 30.
System.out.println (B.data1); // Prints 1
System.out.println (B.data3); // Prints 30
// NOTE: It is RARE to encounter examples of overriding static
// methods, even though it can be done. Most often, it is
// instance methods that are overridden.
// We need an instance to access the instance methods.
B b = new B ();
// First, we'll access B's own newly created methods (not in A):
b.methodSix ();
// Then, B's "methodTwo()" that got inherited from A:
b.methodTwo ();
// Now, we'll access B's "methodTwo()" that overrode A's "methodTwo()":
b.methodFour (); // Prints 40
// Now for polymorphism. Here, we are declaring a variable "a" of type "A".
// To this variable, we are assigning a "B" instance so that "a" points
// to the B instance that "b" is also pointing to. What this really means
// is that the blob contains the method "methodFour()" in class B.
A a = b;
// This calls "methodFour()" in the class B, because the blob contains
// that method.
a.methodFour (); // Prints 40
// We've discussed overriding methods. Observe what happens with data.
System.out.println (b.data4); // Prints 40, as we expect.
// Here's a surprise: data is treated differently.
System.out.println (a.data4); // Prints 4.
// This prints the variable in class A. Why? Because data is "shadowed"
// rather than overridden. By casting, we remove the shadow.
// Why is it called polymorphism? Because, in the above example, the
// variable "a" is really pointing to something that's at least of
// of type A. It happens to be a "B" object, but every "B" object is
// an "A" object because it inherits everything from "A" (and maybe
// overrides some things in it). Thus, the B instance is a "morph"
// of the A instance.
// Why this is useful? The next example shows you why.
}
}
class A {
// Instance data and method:
int data2 = 2;
void methodTwo ()
{
System.out.println ("method two: data2=" + data2);
}
}
class FancyDataStructure {
// This data structure has been written for objects of type "A".
// The code could be long and tedious, but it's written just once,
// compiled and handed out to others for use.
static void insertStuff (A a)
{
// This method expects an "A" type object coming in.
// ... The actual code is not relevant to the example ...
}
}
class B extends A {
// B's own stuff:
void methodThree ()
{
// ... not relevant ...
}
}
public class TestClasses3 {
public static void main (String[] argv)
{
A a = new A ();
FancyDataStructure.insertStuff (a);
// Here, the method has been written, and let's say, compiled
// for "A" type objects. But, later we want to add "B" type
// objects as well. We can do that directly, as the method call above shows,
// without having to re-write the data structure for "B" type objects.
// We can do this because every B object is an A object. Indeed,
// the data structure can be compiled and written just ONCE for
// A type objects.
// OK, so let's add a "B" object to the data structure:
B b = new B ();
FancyDataStructure.insertStuff (b);
// Note that as far as FancyDataStructure is concerned, we inserted
// an "A" object into it.
// Now, there's another important use of overridding, as the
// next example shows.
}
}
class A {
int methodNeededInDataStructure ()
{
// ... whatever ...
return 1;
}
}
class FancyDataStructure {
// This data structure has been written for objects of type "A".
static void insertStuff (A a)
{
// This method expects an "A" type object coming in
// and is going to call the "methodNeededInDataStucture()", as in:
int x = a.methodNeededInDataStructure();
// ... The actual code is not relevant to the example ...
}
}
class B extends A {
// B's own stuff:
void methodThree ()
{
// ... not relevant ...
}
// B overriddes the method needed for the data structure.
int methodNeededInDataStructure ()
{
// ... whatever ...
return 2;
}
}
public class TestClasses4 {
public static void main (String[] argv)
{
A a = new A ();
FancyDataStructure.insertStuff (a);
B b = new B ();
FancyDataStructure.insertStuff (b);
// Note that as far as FancyDataStructure is concerned, we inserted
// an "A" object into it. However, it is really a "B" object that
// has its own implementation of the "methodNeeded ..." method that
// the data structure called.
}
}
class A {
int compare (A a)
{
// ... An "A" object knows how to make a comparison with
// another "A" object.
}
}
class FancyDataStructure {
// This data structure has been written for objects of type "A".
static void insertStuff (A a)
{
// This method expects an "A" type object coming in.
// Some where in here, there will be a need to make comparisons like
int compValue = a.compare (x);
if (compValue < 0) {
// ... yada, yada ...
}
// Here, x is some existing "A" object in the data structure.
}
}
class B extends A {
// B overriddes the method needed for the data structure.
// NOTE: it must have the same signature to override, and so must
// declare an "A" object as parameter.
int compare (A a)
{
// It knows that the input parameter will be a "B" object
// because it expects the user of "B" objects to store
// a bunch of "B" objects in the data structure.
B b = (B) a;
// Now make comparison that is relevant to "B" objects....
// Of course, a careless user of "B" objects can store both "A" and
// "B" objects, in which case this will be called with an
// honest-to-goodness "A" object. This will cause a casting exception.
// A slick programmer will anticipate this and have code that
// uses the "instanceof" operator like this ...
if (! (a instanceof B)) {
// ... take care of the problem ...
}
}
}
public class TestClasses5 {
public static void main (String[] argv)
{
// Insert one object ...
B b = new B ();
FancyDataStructure.insertStuff (b);
// Then, another ...
b = new B ();
FancyDataStructure.insertStuff (b);
}
}
abstract class Stuff {
abstract int compare (Stuff x);
}
class A extends Stuff {
int compare (Stuff x)
{
A a = (A) x;
// ... compare etc ...
}
}
class FancyDataStructure {
// This data structure has been written for objects of type "A".
static void insertStuff (Stuff x)
{
// This method expects an "Stuff" type object coming in.
// Some where in here, there will be a need to make comparisons like
int compValue = x.compare (y);
if (compValue < 0) {
// ... yada, yada ...
}
// Here, y is some existing "Stuff" object in the data structure.
}
}
public class TestClasses6 {
public static void main (String[] argv)
{
// Insert one object ...
A a = new A ();
FancyDataStructure.insertStuff (a);
a = new A ();
FancyDataStructure.insertStuff (a);
}
}
abstract class A {
// An abstract class can have non-abstract methods.
void methodOne ()
{
// ...
}
// If even one method is abstract, the class is abstract.
abstract void methodTwo ();
}
class B extends A {
// Inherits the non-abstract methods in A.
// Must implement the abstract method if you want to
// create instances of B.
void methodTwo ()
{
// ...
}
}
public class TestClasses7 {
public static void main (String[] argv)
{
B b = new B ();
// Call an inherited method.
b.methodOne ();
// Call an implemented method that was abstract.
b.methodTwo ();
}
}
abstract class A {
abstract void methodOne ();
}
abstract class B {
abstract void methodTwo ();
}
// Class C can only extend one other class. C cannot
// extend both A and B.
class C extends A {
void methodOne ()
{
// ...
}
}
public class TestClasses8 {
public static void main (String[] argv)
{
// ...
}
}
interface A {
abstract void methodOne ();
}
interface B {
abstract void methodTwo ();
}
// Class C can only extend one other class, but
// can implement many interfaces.
class C implements A, B {
public void methodOne ()
{
// ...
}
public void methodTwo ()
{
// ...
}
}
public class TestClasses9 {
public static void main (String[] argv)
{
C c = new C ();
c.methodOne ();
c.methodTwo ();
}
}
interface A {
abstract void methodOne ();
}
interface B {
abstract void methodTwo ();
}
class D {
void methodThree ()
{
// ...
}
}
// Class C can extend one class and implement as many interfaces
// as desired.
class C extends D implements A, B {
public void methodOne ()
{
// ...
}
public void methodTwo ()
{
// ...
}
}
public class TestClasses10 {
public static void main (String[] argv)
{
C c = new C ();
c.methodOne ();
c.methodTwo ();
c.methodThree ();
}
}