Inheritance is a form of software reusability in which programmers create classes that "inherit" an existing class's data and behaviors and enhance them with new capabilities. Software reusability saves time during application development. It also encourages the reuse of proven and debugged high-quality software, which increases the likelihood that a system will be implemented effectively. When creating a class, instead of writing completely new data members and member functions, the programmer can designate that the new class should inherit the members of the existing class. The existing class is called the base class, and the new class that is derived from the base class is called the derived class. In Java and the JavaFX scripting language, a base class is called a superclass and the derived class is called a subclass.
Inheritance is an integral part of the JavaFX scripting language. When you inherit, you say "This new class is like that old class with new data members and member functional that adds the additional capabilities." You state this in code by giving the name of the class as usual, but before the opening brace of the class body, put the keyword extends
followed by the name of the base class or superclass. When you do this, you automatically provide all the fields and methods in the base class to the derived class or subclass, and you can define your own data members and member functions in the derived class.
Listing 8-1 shows a simple example of how we can inherit a class in JavaFX.
Example 8.1. A simple inheritance example
class Shape{ var x : Number=10.0; var y : Number=10.0; function drawShape(){ println("Draw the shape"); } } class Circle extends Shape { var radius : Number; override function drawShape(){ println("Draw circle at x={x} y={y} with radius={radius}"); } } var circle : Circle = Circle{ x : 40 y : 50 radius : 5
} circle.drawShape();
Draw circle at x=40.0 y=50.0 with radius=5.0
In Listing 8-1, the class shape
represents any shape to draw on the computer. It's a base class or superclass, which has two data members, x
and y
, representing X and Y coordinates on the screen, and drawShape()
, a method to draw the shape.
Listing 8-1 also shows a class Circle
, which is extended from the Shap
e class. So class Circle
is a derived class, since it inherits all the data members and the member functions of the Shape
class and also defines its own data as well as member functions, thereby enhancing the functionality of the base class. Since a circle is also a shape, we are overriding member functions of the drawShape( )
method to draw the circle, and the overridden method is marked with the override
keyword. (Method overriding is covered in Chapter 5, "Functions.")
Finally, when you create an instance of the class Circle
, you have the data members x
, y
, and radius
. When you call the circle.drawShape()
method on an instance of the Circle
class, the Circle.drawShape()
method is invoked. If you need to invoke a member function of the Shape
class, then you should invoke drawShape()
on a Shape
class instance.
Since inheritance involves two or more classes, in general there is a relationship established between the base and the derived class. When you create an instance of the derived class, it contains within it a base class member as a subobject. This subobject is the same as if you had created an instance of the base class by itself. It's just that from the outside, the subobject of the base class is wrapped within the derived class object. When we initialize an object of the derived class, first the base class data member is initialized and then the derived class data member. Let us see an example that demonstrates the order in which the data members are initialized across base and the derived classes, through the init
block. (Refer to Chapter 6, "Class Definitions," for more information on the init
block. Briefly, it is equivalent to the constructor in Java and is called automatically when a class is instantiated. It is used to do any initialization such as grouping different shapes to create a custom UI control.) Listing 8-2 shows the code.
Example 8.2. Data member initialization order
class Base { var a : Integer; var str:String; init{ println("Base class init block."); a = 10; str="Base"; } } class Derived extends Base { var k:Number; init {
println("Derived class init block."); k=34.34; } function printValues() : Void { println("a = { der.a } str = {der.str} k = {der.k}"); } } var der:Derived = Derived{} der.printValues(); // a = 10 str = Base k = 34.34
Base class init block. Derived class init block. a = 10 str = Base k = 34.34
Listing 8-2 has two classes, named Base
and Derived
. Each class has its own data members and its own init
blocks to initialize the data members. When we create an object or instance of the class Derived
, the Base
class's init
block is called first and then the init
block of the derived
class. Hence the base
class's data member initialization is done prior to that of the derived
class. Finally, the printValues()
method is called to verify the initialized data member values.
Similar to overriding a member function of the base class, you can override the data members or instance variables of the base class as well. Overriding a data member is straightforward and simple. In the derived class, you declare a data member or instance variable with the same name as the superclass data member, but with the override
keyword as one of its modifiers, and without any type specifier. A data member can be initialized with a different value in the derived class when it is being overridden, and if the variable is left uninitialized in the derived class, the initialization specified in the base class would be considered.
Listing 8-3 shows an example.
Example 8.3. Overriding data members
class Game{ var player:Integer = 2; var item : String="Some Game"; var location:String="Out Door"; function printValues() : Void { println("player = {player} item = {item} location={location}"); } }
class Cricket extends Game{ override var item="Bat,Ball and stumps"; override var location; // data member is not initialized. override function printValues() : Void { println("player = {player} item = {item} location={location}"); } } var game:Game=Game{ } game.printValues(); var cri:Cricket = Cricket{} cri.printValues();
player = 2 item = Some Game location=Out Door player = 11 item = Bat,Ball and stumps location=Out Door
In Listing 8-3, we are overriding three data members of the base class Game
from within the derived class Cricket
, and we are initializing two of the overridden members with new values in the derived class. We are leaving the variable location
uninitialized. We are creating an instance of the Game
class as well as the Cricket
class and printing the values. In the output, notice that the value ofthe location
variable in the cri
instance is automatically initialized to Out Door
, demonstrating that the initialization is inherited from the base class.
Attempting to specify a type specifier in a statement overriding a data member or instance variable will cause a compiler error.
Like Java, JavaFX Script also supports the super
keyword, which can be used to invoke a method available in the base class from within any member function of the derived class. Let us see a simple example of how it can be used in JavaFX, in Listing 8-4.
Example 8.4. Usage of the super keyword
class A{ function fun1(){ println("Base class Function-1"); } function fun2(){ println("Base class Function-2"); } }
class B extends A{ function bFun1(){ println("Derived class Function-1"); } override function fun2(){ super.fun2(); println("Derived class Function-2"); } } var obj:B=B{} obj.fun2();
Base class Function-2 Derived class Function-2
The code shown in Listing 8-4 is self-explanatory; we call the base class's fun2()
function from within the overriding function of the derived class.
It is also possible to call a non-overridden function of the base class using the super
keyword, but doing that generally does not make sense, since you can already invoke the non-overridden member function directly.
JavaFX Script has supported multiple inheritance right from its inception, but the concept of multiple inheritance has been radically changed as of JavaFX 1.2. Prior to 1.2, a normal JavaFX class could directly extend from any number of other classes, but as of version 1.2 this has been streamlined by enforcing certain restrictions on inheriting multiple classes through mixins. The mixin
keyword in JavaFX Script refers to a new form of multiple inheritance with same features as before but with additional benefits such as much simpler and much faster code generation. The concept of a mixin is generic within OOP. Mixins are more or less like interfaces in Java except that they can have variables and implementations that can be inherited by all the classes that extend a mixin class.
The mixin
keyword is applied to classes, and such a class is described as a mixin class. A mixin class is a like a regular JavaFX class; it contains data members, member functions, an init
block anda postinit
block. The difference is that the class cannot be instantiated directly; it is instead designed to be extended and used by subclasses.
Some basic points should be remembered while implementing inheritance in JavaFX:
JavaFX Script classes are allowed to extend at most one other JavaFX Script class as a superclass. This superclass can be either a Java class or a JavaFX Script class.
JavaFX Script classes are allowed to extend any number of JavaFX Script mixin classes.
JavaFX mixin classes are allowed to extend any number of other JavaFX mixin classes and can also extend any number of Java interfaces.
As mentioned earlier, a mixin class can extend one regular (non-mixin) class and any number of mixin
classes.
Now let us see an example that demonstrates the difference between how Java and JavaFX achieve a similar functionality and how simple JavaFX is compared to Java (Listing 8-5).
Example 8.5. A comparison of mixins in Java and JavaFX
FileName: GreetWorld.java public interface GreetWorld { public void printGreetings(); } FileName: Greeting1.java public class Greeting1 implements GreetWorld { String courtesy = "Hello"; String name = "Praveen!"; public void printGreetings() { System.out.println(courtesy + " " + name); } public static void main (String args[]) { Greeting1 g = new Greeting1(); g.printGreetings(); } } FileName: Greeting2.java public class Greeting2 implements GreetWorld { String courtesy = "Hello"; String name = "Lawrence!"; public void printGreetings() { System.out.println(courtesy + " " + name); } public static void main (String args[]) { Greeting2 g = new Greeting2(); g.printGreetings(); } } FileName: Greetings.fx public mixin class GreetWorld { var courtesy: String = "Hello"; var name: String; public function printGreetings(): Void { println("{courtesy} {name}"); } } class Greeting1 extends GreetWorld { override var name = "Praveen!"; }
class Greeting2 extends GreetWorld { override var name = "Lawrence!"; } public function run() { var g1 = Greeting1 {} var g2 = Greeting2 {} g1.printGreetings(); g2.printGreetings(); }
(Executing Greeting1.java) Hello Praveen! (Executing Greeting2java) Hello Lawrence! (Executing Greetings.fx) Hello Praveen! Hello Lawrence!
As you see in Listing 8-5, the JavaFX code is much simpler than the Java code. You can easily see that there is an implementation for the method printGreetings()
in the mixin class, and so the subclasses do not have to implement the method again. The implementations of printGreetings
in Greeting1
and Greeting2
are identical, a duplication that is avoided in JavaFX. In addition to this, all the classes are maintained in a single file in JavaFX; in Java you need a separate file for each of the public classes and interfaces.
A mixin class cannot be instantiated directly and can only be extended. However, you can instantiate a derived class that extends a mixin class, and it is legal to cast a derived class's object to a mixin reference. It is also legal to use a mixin class with the instanceof
operator.
Listing 8-6 demonstrates how to create a subclass from a regular class and a mixin class.
Example 8.6. Deriving a mixin and a regular class together
class Base { var x : Integer; function showX() { println("x = {x}"); } } mixin class MixBase { var y : Integer; function showY( ){ println("y = {y}"); } }
class SubClass extends MixBase , Base { var z : Integer; function showZ() { println("z = {z}"); //super.showY(); - ILLEGAL: WILL NOT COMPILE //super.showX(); - LEGAL } } var obj = SubClass{ x : 10; y : 20; z : 30; } obj.showX(); // x = 10 obj.showY(); // y = 20 obj.showZ(); // z = 30
x = 10 y = 20 z = 30
Listing 8-6 shows how a regular class and a mixin class can be extended by a subclass. Notice that a mixin class can also have data members and functions just like a normal class, and those members can be accessed the same way as we do with a normal class.
Notice also that the contents of the mixin class, such as member functions and data members, are included in the derived class rather than inherited. They become part of the derived class during compilation. Hence, the super
keyword can only refer to the extension of a non-mixin class and not a mixin class. That's why super.showY()
will not compile if uncommented in this example, whereas super.showX()
will compile correctly.
When a class inherits from multiple other classes, the technique is called multiple inheritance. Java does not support multiple inheritance through classes, but it can be done using Java interfaces. By contrast, in the JavaFX scripting language, mixin inheritance allows you to extend multiple mixin classes, giving you the benefits of multiple inheritance. Now let us see how we can create a subclass extending from multiple mixin classes (Listing 8-7).
Example 8.7. Extending multiple mixin classes
public mixin class Mixin1 { var a : Integer; public function getA(){ return a; } }
public mixin class Mixin2 { var b : Integer; public function getB(){ return b; } } class Mixee extends Mixin1, Mixin2 { var c : Integer; public function getC(){ return c; } } function run () { var obj = Mixee{ a : 10;b:20;c:30; } println("a = {obj.getA()} , b = {obj.getB()} , c= {obj.getC()}"); // a = 10 , b = 20 , c= 30 } output :- a = 10 , b = 20 , c= 30
In Listing 8-7, we have defined two classes, named Mixin1
and Mixin2
. Each class contains a single Integer data member and a member function to return its data value. The class Mixee
extends both the Mixin1
and Mixin2
classes, inheriting the data members and member functions of both parent mixin classes and thus enabling multiple inheritance. Now with an object of the Mixee
class, you can access the data members and member functions of Mixee
, Mixin1
, or Mixin2
.
Recall from the beginning of this chapter that init
blocks are always executed in order from parent class to child class. Now when we have multiple parent classes, the same order of parent-to-child is maintained, and the parents' init
blocks would be called before the child's. But the order among multiple parents is chosen by the order in which the parent classes are mentioned after the extends
keyword in the derived class. If class A extends from B, C, and D, the order of initialization would be B
As you see in the output of Listing 8-8, the order is always from parent to child and the order among multiple parents is decided by the order in which the parent classes are extended by the derived class. In this case, Mixee
extends Mixin1
first and then Mixin2
, and so the init
blocks are executed in the order Mixin1
There are situations in which you would want to define a superclass that declares the structure of a given abstraction without providing a complete implementation of every method. That is, sometimes you will want to create a superclass that only defines a generalized form that will be shared by all of its subclasses, leaving it to each subclass to fill in the details. Such a class determines the nature of the methods that the subclasses must implement. One way this situation can occur is when a superclass is unable to create a meaningful implementation for a method.
In such a scenario, you can create a class and mark certain methods to be overridden by subclasses compulsorily, by specifying the abstract
type modifier. The responsibility of implementing these methods is with the subclass, since the parent class will not have any implementation. Thus, a subclass must override them—it cannot simply use the version defined in the superclass.
Any class that contains one or more abstract methods must also be declared abstract
. To declare a class abstract, you simply use the abstract
keyword in front of the class
keyword at the beginning of the class declaration. There can be no objects of an abstract class. That is, an abstract class cannot be directly instantiated, because an abstract class is not fully defined. But you can create a reference of the abstract class.
A mixin class can have abstract functions if you want to leave the implementation to the derived classes. But the class need not be referred to as abstract
, unlike a normal Java or JavaFX Script class.
Because the JavaFX Scripting language is built on top of Java, JavaFX classes can extend Java classes (including abstract classes) and Java interfaces. For example, suppose you have created an application in Java and now want to migrate it to JavaFX. You can just extend those Java classes or implement the Java interfaces in your JavaFX directly to import the same behavior (that is, the interface) that you have already built using Java. Listing 8-9 shows an example; we have a Java abstract class, which is in a separate .java
file, and this abstract class is extended and implemented by JavaFX subclasses.
Example 8.9. A JavaFX class extending an abstract Java class
Filename : Figure.java package inheritance; abstract class Figure { public float dim1; public float dim2; // abstract area class abstract float area(); } FileName : AbstractImplementation.fx package inheritance; class Rectangle extends Figure { override function area() : Number { println("Overriden area function in Rectangle class"); return dim1 * dim2; } } class Triangle extends Figure { override function area() : Number { println("Overriden area function in Triangle class"); return dim1 * dim2 / 2.0; } } function run(){ var rect = Rectangle{ } rect.dim1 = 5.0; rect.dim2 = 5.0; println(rect.area()); var triangle = Triangle{ } triangle.dim1 = 10.0; triangle.dim2 = 5.0; println(triangle.area()); }
Overriden area function in Rectangle class 25.0 Overriden area function in Triangle class 25.0
In Listing 8-9, we have a Java abstract class named Figure
, which has an abstract method named area
. This Java abstract class is stored in a file called Figure.java
. The class is extended by two JavaFX classes, Rectangle
and Triangle
, and it overrides the abstract method area
in a file called AbstractImplementation.fx
. Both the Java and JavaFX classes are in the package inheritance
. In the run()
function, we create instances of the Rectangle
and Triangle
classes and assign values to the data members. Finally, we call the area()
function of the respective implementations.
In Java, we often encounter situations where we need to implement interfaces anonymously, without specifying a class name, and the same thing can be done in JavaFX Script as well. One such example is the implementation of java.awt.event.ActionListener
, which is demonstrated in Listing 8-10.
Example 8.10. Implementing Java interfaces anonymously
import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; var counter: Integer = 0; var listener = ActionListener { public override function actionPerformed(ae: ActionEvent): Void { println("Timer Triggered {counter}"); counter ++; } } var timer: Timer = new Timer(1000, listener); timer.start();
Output
Timer Triggered 0 Timer Triggered 1 Timer Triggered 2 Timer Triggered 3 .. ..
In Listing 8-10, we are implementing a swing timer from Java to do some animation. A swing timer requires an action listener and a time interval at which the action listener's actionPerformed
must be invoked. Here we are creating an object, named listener
, of type ActionListener
, by providing an inline implementation of the interface and using it in the Timer
constructor. As you see in the output, the swing timer will invoke listener
's actionPerformed
() function every second (1000ms), infinitely. As you see the ActionListener
implementation, it is the same listener method in Java implemented in JavaFX Script syntax with keywords such as override
and function
.
Inheritance allows a class to be derived from an existing class. The derived class has all the data and functions of the parent class but adds new ones of its own. Inheritance makes possible reusability—the ability to use a class over and over in different programs with enhanced features. JavaFX supports all types of inheritance as Java does, including multiple inheritance, but in a slightly different way, using mixin classes. A mixin class resembles a regular class in that it contains data members and member functions but is prefixed with the mixin
keyword. A JavaFX Script class can extend at most one Java or one JavaFX Script class, but it can extend multiple JavaFX mixin classes. The class that is extended from the mixin class is called a mixee class. JavaFX classes can extend Java abstract classes and implement Java Interfaces. In the next chapter, you will learn more about one of the most important and powerful JavaFX features—data binding.