super

8.1.3 Superclasses and Subclasses

The optional extends clause in a class declaration specifies the direct superclass of the current class. A class is said to be a direct subclass of the class it extends. The direct superclass is the class from whose implementation the implementation of the current class is derived. The extends clause must not appear in the definition of the class java.lang.Object (§20.1), because it is the primordial class and has no direct superclass. If the class declaration for any other class has no extends clause, then the class has the class java.lang.Object as its implicit direct superclass.

Super:

extends ClassType

The following is repeated from §4.3 to make the presentation here clearer:

ClassType:

TypeName

The ClassType must name an accessible (§6.6) class type, or a compile-time error occurs. All classes in the current package are accessible. Classes in other packages are accessible if the host system permits access to the package (§7.2) and the class is declared public. If the specified ClassType names a class that is final (§8.1.2.2), then a compile-time error occurs; final classes are not allowed to have subclasses.

In the example:


class Point { int x, y; }

final class ColoredPoint extends Point { int color; }
class Colored3DPoint extends ColoredPoint { int z; } // error

the relationships are as follows:

The declaration of class Colored3dPoint causes a compile-time error because it attempts to extend the final class ColoredPoint.

The subclass relationship is the transitive closure of the direct subclass relationship. A class A is a subclass of class C if either of the following is true:

Class C is said to be a superclass of class A whenever A is a subclass of C.

In the example:


class Point { int x, y; }

class ColoredPoint extends Point { int color; }
final class Colored3dPoint extends ColoredPoint { int z; }

the relationships are as follows:

A compile-time error occurs if a class is declared to be a subclass of itself. For example:


class Point extends ColoredPoint { int x, y; }
class ColoredPoint extends Point { int color; }

causes a compile-time error. If circularly declared classes are detected at run time, as classes are loaded, then a ClassCircularityError is thrown.

13.4.4 Superclasses and Superinterfaces

A ClassCircularityError is thrown at load time if a class would be a superclass of itself. Changes to the class hierarchy that could result in such a circularity when newly compiled binaries are loaded with pre-existing binaries are not recommended for widely distributed classes.

Changing the direct superclass or the set of direct superinterfaces of a class type will not break compatibility with pre-existing binaries, provided that the total set of superclasses or superinterfaces, respectively, of the class type loses no members.

Changes to the set of superclasses of a class will not break compatibility with pre-existing binaries simply because of uses of class variables and class methods. This is because uses of class variables and class methods are resolved at compile time to symbolic references to the name of the class that declares them. Such uses therefore depend only on the continuing existence of the class declaring the variable or method, not on the shape of the class hierarchy.

If a change to the direct superclass or the set of direct superinterfaces results in any class or interface no longer being a superclass or superinterface, respectively, then link-time errors may result if pre-existing binaries are loaded with the binary of the modified class. Such changes are not recommended for widely distributed classes. The resulting errors are detected by the verifier of the Java Virtual Machine when an operation that previously compiled would violate the type system. For example, suppose that the following test program:

class Hyper { char h = 'h'; } 
class Super extends Hyper { char s = 's'; }
class Test extends Super {
    public static void main(String[] args) {
        Hyper h = new Super();
        System.out.println(h.h);
    }
}

is compiled and executed, producing the output:

h

Suppose that a new version of class Super is then compiled:

class Super { char s = 's'; }

This version of class Super is not a subclass of Hyper. If we then run the existing binaries of Hyper and Test with the new version of Super, then a VerifyError is thrown at link time. The verifier objects because the result of new Super() cannot be assigned to a variable of type Hyper, because Super is not a subclass of Hyper.

It is instructive to consider what might happen without the verification step: the program might run and print:

s

This demonstrates that without the verifier the type system could be defeated by linking inconsistent binary files, even though each was produced by a correct Java compiler.

As a further example, here is an implementation of a cast from a reference type to int, which could be made to run in certain implementations of Java if they failed to perform the verification process. Assume an implementation that uses method dispatch tables and whose linker assigns offsets into those tables in a sequential and straightforward manner. Then suppose that the following Java code is compiled:

class Hyper { int zero(Object o) { return 0; } }
class Super extends Hyper { int peek(int i) { return i; }  }

class Test extends Super {
  public static void main(String[] args) throws Throwable {
    Super as = new Super();
    System.out.println(as);
    System.out.println(Integer.toHexString(as.zero(as)));
  }
}

The assumed implementation determines that the class Super has two methods: the first is method zero inherited from class Hyper, and the second is the method peek. Any subclass of Super would also have these same two methods in the first two entries of its method table. (Actually, all these methods would be preceded in the method tables by all the methods inherited from class Object but, to simplify the discussion, we ignore that here.) For the method invocation as.zero(as), the compiler specifies that the first method of the method table should be invoked; this is always correct if type safety is preserved.

If the compiled code is then executed, it prints something like:


Super@ee300858
0

which is the correct output. But if a new version of Super is compiled, which is the same except for the extends clause:

class Super { int peek(int i) { return i; }  }

then the first method in the method table for Super will now be peek, not zero. Using the new binary code for Super with the old binary code for Hyper and Test will cause the method invocation as.zero(as) to dispatch to the method peek in Super, rather than the method zero in Hyper. This is a type violation, of course; the argument is of type Super but the parameter is of type int. With a few plausible assumptions about internal data representations and the consequences of the type violation, execution of this incorrect program might produce the output:


Super@ee300848
ee300848

A poke method, capable of altering any location in memory, could be concocted in a similar manner. This is left as an exercise for the reader.

The lesson is that a implementation of Java that lacks a verifier or fails to use it will not maintain type safety and is, therefore, not a valid Java implementation.