Chapter 7. Access Specifiers

An important aspect of object-oriented programming is data encapsulation (also called data hiding), whereby the implementation details of a class are kept hidden from the users of the class. Not only the implementation but also the data can be kept hidden from the user. Or one can choose to provide varying degrees of restricted access to those data members through functions.

In general, this concept can be simply stated as "differentiating things that change from things that do not." This is particularly important when you are writing an API library that the application developer is going to use. Any user of your API library should be able to rely on your API when writing the application and should not be forced to rewrite code when you come up with a newer version of the library. On the other hand, you as a library creator should have the freedom to change the implementation (for the better) without breaking the existing applications written using your API. While you can achieve this by not removing any of the existing methods, there are further problems to handle. Existing applications might have used your data members, and it's hard to for the API author to figure out which of the data members are accessed by the client applications and which are not. So you will finally end up not being able to change anything, fearing incompatibility with existing applications.

So in any object-oriented design, it is important to identify what should be exposed to API users and what should be kept hidden so that the API author can change the hidden things as needed later on. (These revisions are made for many reasons, such as improving functionality, performance, reliability and so on.) In other words, the interface of the class remains the same while the implementation changes. This is where access specifiers (also called access modifiers) are useful.

The concept of access specifiers—keywords that identify specific levels of access— was developed mainly to control how a class and its members are accessed by the users of the class. With the help of access specifiers, you can clearly define what can and cannot be accessed by users of your library, which gives you the freedom to improve your library at will without breaking any existing applications.

JavaFX Script offers the following access specifiers you may know from Java; you will see each of them in detail in this chapter.

  • public

  • protected

  • package

  • Script-private (when nothing is specified explicitly)

While these look similar to the access specifiers that Java offers, there are subtle differences between how Java and JavaFX Script deal with them, as you will learn when we discuss them in detail. They are primary access modifiers that are applicable to all forms of access: creation and reference to classes, defining and calling functions in a class or outside of the class, reading and writing of script or instance variables, and overriding and setting or binding in an object literal of an instance variable.

There are also two access specifiers that are entirely new in JavaFX Script:

  • public-read

  • public-init

These are applicable only to variable declarations (var) and are additive in nature with respect the primary modifiers specified before. In this chapter, we will refer to these as secondary specifiers or modifiers.

Now let us see the basic syntax of specifying the primary and secondary access specifiers.

Syntax

[<secondary_access_specifier> <primary_access_specifier>] var variable name[: data type] = <initial value>;

or

primary access-specifier function function name()[: return data type] { }

Any elements appearing between square brackets are optional.

The Script—The .fx File

Before going into the details of the access specifiers, you should understand how JavaFX organizes the code within a JavaFX script. A JavaFX script is the .fx file that will have the JavaFX code and can optionally contain zero or more classes. Unlike Java, JavaFX does not require you to write all the code within a class, and an application may or may not have a class. So you don't have to always create a class, and you can write expressions, variable declarations, functions and so on directly within the script. A script is also compiled into a Java class internally, but that's more of an implementation detail. Understanding the script paradigm is important because the concept of script is new in JavaFX and does not exist in Java, and JavaFX offers specific access specifiers at the script level.

The variables and functions created directly within the script are considered to be equivalent of the Static modifier in Java. Your script, in addition to expressions, vars, functions, and so on can also have one or more class definitions in itself. You learned about classes in Chapter 6, "Class Definitions," and here you will learn more about how a class member interacts with a script member that resides outside of the class when these members have different access specifiers.

I'll show how to specify access modifiers for the class definitions later in this chapter; first you will see the applicability of access modifiers for the class/script members.

The context in which script variables and functions are defined is called a static context, and a class definition is a nonstatic context. So accessing a class variable from outside the class would require an instance of the class, because accessing an instance variable of a class is not permitted from a static context. This is true in Java, too, where a nonstatic variable cannot be accessed directly from a static context.

Note

JavaFX Script, unlike Java, allows you to have different names for a script and a class defined within that script. JavaFX Script does not strictly require you to give a script the same name you have given a class within it. Similarly, you can have multiple public classes defined within the same script.

The Script-Private Access Specifier

The default access specifier in JavaFX Script is the script-private. This is the access specifier assigned by the compiler when one is not explicitly specified, as you have seen in all the previous examples.

A script-private member is accessible only within its own script. In other words, within the same .fx file and not outside. The script may or may not have a class definition in it, nevertheless the script-private members will be accessible within the class definition also provided if the class definition resides in the same .fx file.

This access specifier is applicable to variable declarations, functions, class definitions, and so on.

Figure 7-1 illustrates script-only access for members defined within script1 in Package B.

Script-only access

Figure 7.1. Script-only access

As you see in the diagram, only script1 has access to the members, and no access is allowed outside of the script. Listing 7-1, a script named Employee.fx, and Listing 7-2, the EmployeeList.fx script, demonstrate this level of access.

Example 7.1. Variables with default access: Employee.fx

// Script Private variables
var empId: String;
var firstName: String;
var lastName: String;
var deptId: Number;
var designation: String;

public function populateData(empID: String) {
    empId = empID;
// Typically other details are fetched from web-service or database using the empid.
    // Hardcoding here
    if (empId == "23456") {
        firstName = "Praveen";
        lastName = "Mohan";
        deptId = 44;
        designation = "Manager";
    } else {
        println("Invalid Employee ID");
    }
}

public function printData() {
    println("Details of employee with ID: {empId}");
    println("First Name: {firstName}");
    println("Last Name: {lastName}");
    println("Department ID: {deptId}");
    println("Designation: {designation}");
}

public function run() {
    empId = "11111";
    firstName = "Lawrence";
    lastName = "PremKumar";
    deptId = 33;
    designation = "Developer";
    printData();
}

Example 7.2. Accessing script-private variables of another script: EmployeeList.fx

var employee = Employee {};
var id:String = "23456";
//employee.empId = id;  - ERROR: Not accessible here
//employee.firstName = "Praveen"; - ERROR: Not accessible here
employee.populateData(id);
employee.printData();

First compile and run Employee.fx to see its result.

Output

Details of employee with ID: 11111
First Name: Lawrence
Last Name: PremKumar
Department ID: 33.0
Designation: Developer

Then compile and run EmployeeList.fx.

Output

Details of employee with ID: 23456
First Name: Praveen
Last Name: Mohan
Department ID: 44.0
Designation: Manager

In this example, you see many of the variables declared without any access specifier; those variables assume the default access specifier, script-private. You see that those variables are accessible directly within the script in the run() method. Thus, compiling and executing Employee.fx yields the expected result. However, you cannot access those variables from another script directly, and doing so will cause a compilation error—typically demonstrated by the commented lines in EmployeeList.fx. So a public function is provided in Employee.fx for initializing those variables from a different script. You can uncomment the commented lines in EmployeeList.fx and see for yourself what kind of error the compiler throws.

Now let's us see how such variables are accessed from within the class definitions available in the same script. Listing 7-3 is a script named ScriptPrivateWithClassDef.fx.

Example 7.3. Script-private access from within the class: ScriptPrivateWithClassDef.fx

var PI: Number = 3.14;
var area: Number;
function printArea() {
    println("Area: {area}");
}
class CustomCircle {
    var r: Number;
    init {
        area = calculateArea();
        printArea();
    }
    function calculateArea() {
        PI * r * r;
    }
}
CustomCircle { r: 25 }; //Anonymous Object Literal

Output

Area: 1962.5

As you see, the variables declared outside the class (the script variables) can be accessed like static variables from within the class, and there is no need to use an instance or the member operator. However, the reverse situation would require an instance—that is, a script-private variable defined within a class would require an instance of the class to be accessed from a static context. Listing 7-4 redefines the previous example to demonstrate this.

Example 7.4. Accessing class variables from within the script: ScriptPrivateClassDef2.fx

var PI: Number = 3.14;
class CustomCircle {
    var r: Number;
    var area: Number;
    init {
        calculateArea();
    }
    function calculateArea() {
        area = PI * r * r;
    }
    function printArea() {
        println("Area: {area}");
    }
}

var circle = CustomCircle { r: 25 };
circle.printArea();
println(circle.area);

Output

Area: 1962.5
1962.5

In Listing 7-4, notice a change from Listing 7-3: the area variable and printArea function from the script have been moved into the class definition. Now you need to have an instance of CustomCircle in order to access the area variable or the printArea function and hence we are assigning the CustomCircle instance to a variable circle (instead of leaving it anonymous, as we did in Listing 7-3) and accessing the class members with the help of this variable.

It is also possible to access the script-private variables defined in one class from another class if both the classes are defined within the same source file. Listing 7-5 demonstrates this approach.

Example 7.5. Access across multiple classes in the script: ScriptPrivateClassDef3.fx

var PI: Number = 3.14;
class CustomCircle {
    var r: Number;
    var area: Number;
    function calculateArea() {
        area = PI * r * r;
    }
    function printArea() {
        calculateArea();
        println("Area: {area}");
    }
}
class ShapeBuilder {
    var circleRadius:Number = 10;
    var circle: CustomCircle = CustomCircle { r: bind circleRadius }
    init {
        circle.printArea();
        circleRadius = 25;
        circle.printArea();
    }
}
ShapeBuilder{};

Output

Area: 314.0
Area: 1962.5

In Listing 7-5, because the CustomCircle and ShapeBuilder classes are defined in the same script, the class ShapeBuilder can create an instance of the class CustomCircle within itself and can access the attributes of class CustomCircle using the instance circle. Please note that it works only if both the classes are defined in the same script. For instance, if you move ShapeBuilder to a different script (a different .fx file—ShapeBuilder.fx), you will no longer be able to access CustomCircle or its attributes. You would get a compilation error attempting to compile ShapeBuilder.fx because the class CustomCircle and its attributes are script-private. So when you need separate scripts, you will have to look for other access specifiers that provide broader access than script-private.

Packages

As listed at the beginning of the chapter, the next level of access is package. Before going into the package access specifier, however, it's a good idea to understand the concept of packages and why they are required. The concept is inherited from Java and its implementation in JavaFX Script is not very different. By definition, a package is a collection of related classes and scripts grouped together under a common package name. Each package is given a name, and the combination of the package and class/script name forms the fully qualified name of the class/script, which uniquely identifies it. The package can include Java classes as well. Packages help API developers organize their source files appropriately by combining related classes/scripts into a single collection; This in turn helps the API users (application developers) to use only the collection that the application requires, and not all the classes.

This also helps you avoid naming conflicts in your program. When you use the OOP paradigm to create an application, you typically model the problem domain by creating your own data types and assign each of them an identifiable name. Each type that you create must be unique so that you can identify and use it appropriately in your program. This could get complicated when you start using types from other vendors, especially when writing a larger application. In such a case, you can choose to assign your types their own unique package name that does not conflict with the names that others have chosen. As a result, you don't have to deal with this complexity in your code and can focus on the actual complexity of the application as such. Conflicts can arise not only from external vendor APIs but also with the APIs developed in-house, typically in a multi-developer environment. In such a case, it is very much possible that your co-developer may choose to have the same name as you for one of his/her classes, potentially creating a conflict within the same API library. This is where subpackages become helpful.

You can also organize the packages hierarchically; a package can have subpackages. Anything contained within a package is called a package member.These can be classes, scripts, or subpackages.

With the help of access specifiers, you can grant special privileges for the classes/scripts within the same package, and you can share and reuse the code within the same package. The logical grouping of classes and scripts makes it convenient for the users of your API as well to import only the classes that are needed. It improves the readability and maintainability of the whole application over all.

A package can be defined by using the package keyword, which should be specified as the first line in each of the source files that you want to organize together.

For example, If you have three source files, A.fx, B.fx, and C.fx, that you want to organize into a single package named tasks, add the following line in each of these source files as the first line.

package tasks;

You should also physically organize the files in the same way, by creating a folder named asks and moving these source files into the folder.

A fully qualified name of a class or a script is the class/script name prefixed with appropriate package names, separated by the dot (.) operator.

So for our tasks example, the fully qualified names would be

tasks.A
tasks.B
tasks.C

In order to use these classes in a different package, you have to import these classes specifically. If you want to import all three classes, you can just use a wild-card character, as follows:

import tasks.*;

Otherwise, you can import only the necessary classes, as in these statements:

import tasks.A;
import tasks.C;

A typical fully-qualified name of a class looks like the following:

com.foo.ui.Bar

where com, foo and ui are packages, and Bar is the actual JavaFX script (Bar.fx) that resides within the com/foo/ui package. The com.foo portion is typically the reverse domain name of the organization that creates the API library. In this notation foo is a subpackage of com, and ui is a subpackage of foo. The Java Virtual Machine does not differentiate between main and subpackages when granting package-specific access, and JVM does not treat these packages in an hierarchical way. As far as JVM is concerned, a sibling package is treated the same way as a subpackage, and whatever rights tare available within a particular package are not granted either to a sibling package or to a subpackage. Thus, subpackages are useful more for the API author/user, because the sources are organized in a much better way and form a hierarchy. They do not make much difference to the JVM.

In all the examples you have seen so far, there is no package name assigned explicitly. In such a case, the compiler uses a default package name implicitly, and all the scripts or classes are considered to be part of this default package automatically.

JavaFX Graphics API has its own set of classes and packages, such as the following:

javafx.scene
javafx.scene.shape
javafx.scene.paint
javafx.scene.text

and so on. You will see many of them when we introduce you to graphics and animation in later chapters.

Statics in JavaFX Script

Script variables are considered to be equivalent to the static modifier in Java and can be imported into another JavaFX Script. A script variable is one declared outside of any class definitions within a script. Such variables can be imported into another JavaFX script—as long as the access specifier for those script variables allows them to be accessed from a different script. In other words, the variables that have script-private default access will not be imported, because their scope is limited to the script where they are created).

Syntax

import script name.*;

Here, script name is the name of the .fx file such as Bar.fx. Note that we are using the name of the script in the import and not any class or package as such. We will explore the importing of packages and classes in more detail in the next section. In this type of importing, the specified script may or may not contain any class. Listings 7-6 and 7.7 demonstrates static importing.

Example 7.6. Defining the statics: AreaUtil.fx

public def PI: Number = 3.14;
public function getAreaOfCircle(radius: Number) {
    PI * radius * radius;
}
protected function getCircumferenceOfCircle(radius: Number) {
    2 * PI * radius;
}
package function getSurfaceAreaOfSphere(radius: Number) {
    4 * PI * radius * radius;
}

Example 7.7. Importing the statics from AreaUtil: ShapeBuilder.fx

import AreaUtil.*;

class Circle {
    var radius: Number;
    var area = bind getAreaOfCircle(radius);
    var circumference = bind getCircumferenceOfCircle(radius);
}
class Sphere {
    var radius: Number;
var surfaceArea: Number = bind getSurfaceAreaOfSphere(radius);
}
var circle = Circle {
    radius: 25
}
var sphere = Sphere {
    radius: 20
}
println("PI value: {PI}");
println("Circle Area: {circle.area}");
println("Circle Circumference: {circle.circumference}");
println("Sphere SurfaceArea: {sphere.surfaceArea}");

Run ShapeBuilder.fx to see the following result.

Output

PI value: 3.14
Circle Area: 1962.5
Circle Circumference: 157.0
Sphere SurfaceArea: 5024.0

In Listings 7-6 and 7-7, all the variables and functions declared in AreaUtil.fx are imported into ShapeBuilder.fx and thus can be accessed within ShapeBuilder.fx as if they were defined there. But the variables declared in this example are either public, protected, or package and not script-private, and hence they are accessible. No script-private variables (var statements with no access specifiers mentioned) would be imported into another script. Likewise, a package-access variable will not be imported when the FX script is imported into a different package. Here no package is specified in either of the scripts, and so both of them belong to the same default package that the compiler assigns. This example is a typical use of statics, in which utility variables and functions that are shared across the whole application are grouped into a single .fx script, which would be imported into other scripts wherever these utilities are needed.

The package Access Specifier

Now that you've seen the concept of packages and the need for them, we can explore the package access specifier. A class or a member of the class or the script having the access specifier defined as package would be accessible anywhere within the same package and not in other packages or subpackages. This is the next wider access level beyond the default script-private access. Listings 7-8 through 7-10 demonstrate package access.

Note

Java's default access is package (or friendly), which is applied when you don't explicitly specify any access specifier. But in JavaFX Script, you explicitly specify package access, and the default specifier is script-private.

Example 7.8. Implementation that offers package access function: CookDessert.fx

package com.foo.dessert;
package var bakeTime: Number;
package var coolingTime: Number;

package function cook(item: String) {
    println("Baking item: {item} for {bakeTime} min");
    println("Cooling item: {item} for {coolingTime} min");
    println("Item {item} is ready!!");
}

Example 7.9. Public interface for accessing package level function in CookDessert: Desserts.fx

package com.foo.dessert;
public var item: String;

public function prepare() {
    if (item == "Cake") {
        var cake = CookDessert {};
        cake.bakeTime = 25;
        cake.coolingTime = 10;
        cake.cook ("Cake");
    } else if (item == "Pudding") {
        var pudding = CookDessert {};
        pudding.bakeTime = 10;
        pudding.coolingTime = 5;
        pudding.cook("Pudding");
    } else {
        println("Sorry!! Recipe not available yet!");
    }
}

Example 7.10. Application that accesses the package level functionality in CookDessert through the public interface in Desserts: Dinner.fx

package com.foo.meal;
import com.foo.dessert.*;
var dessert = Desserts {};
dessert.item = "Pudding";
dessert.prepare();

Compile all the files and run Dinner.fx to see the following result.

Output

Baking item: Pudding for 10.0 min
Cooling item: Pudding for 5.0 min
Item Pudding is ready!!

In Listings 7-8, 7-9, and 7-10, CookDessert.fx and Desserts.fx belong to a package named com.foo.dessert. CookDessert contains the parameters and recipe for cooking desserts that Desserts.fx script makes use of. The members, such as bakeTime and coolingTime, are specific to the com.foo.dessert package and hence have been declared with the package access specifier so that they are accessible only within the package. As a result, the API user cannot access the members of CookDessert.fx from within Dinner.fx, since Dinner.fx belongs to a different package. Hence there is another script—Desserts.fx that offers a public interface to the API user and internally calls the members of CookDessert.fx. Desserts.fx is able to access the members of CookDesserts.fx directly since they belong to the same package, com.foo.dessert.

Note that CookDesserts.fx creates an instance of the Desserts script inside its prepare() function; this can be avoided if you import CookDessert.* into Desserts.fx. We can rewrite Listing 7-9 as follows -

package com.foo.dessert;
import CookDessert.*;
public var item: String;
public function prepare() {
    if (item == "Cake") {
        bakeTime = 25;
        coolingTime = 10;
        cook ("Cake");
    } else if (item == "Pudding") {
        ...
    // Same as listing 7.9

Here the static variables (script variables) declared in one file are imported into the other so that you don't have to create an instance of CookDessert.fx in Desserts.fx. This will yield the same output as that of Listing 7.8 to 7.10.

Note

Only permitted members of a script are imported into another, and the access specifier of each member of the script is validated at the time of import. For example, if there is a script-private variable declared in CookDessert, it will not be imported into Desserts. And if this import is to a different package (for example, if CookDessert is imported into Dinner.fx), the package access variables will not be imported, and hence the compiler will give an error appropriately when you try to access any of the CookDessert variables from within Dinner.fx.

Fig 7-2 demonstrates package access for members of script1 and summarizes the behavior of the package access specifier that you have seen so far.

Package Access

Figure 7.2. Package Access

As you see in the diagram, the package members of script1 can be accessed within script1 and script2 since both scripts belong to the same package—Package B. However, no access is granted to package A.

Trying to access the members of script1 or script2 from within any of the scripts in package B would cause a compilation error.

Package Access with Class Members

The package access specifier behaves the same way when used within the class definitions as with script variables. The access specifiers for the class definitions as such will be discussed later in this chapter; for now, let us assume all the classes are public and hence can be accessed anywhere. Here you will see package access specified for class members such as variable definitions and functions. Listings 7-11 and 7-13 convert the same example you have seen previously to use class definitions.

Example 7.11. Class definitions with package level access: CookDessert.fx

package com.foo.dessert;

package class CookDessert {
    package var bakeTime: Number;
    package var coolingTime: Number;
    package function cook(item: String) {
        println("Baking item: {item} for {bakeTime} min");
        println("Cooling item: {item} for {coolingTime} min");
        println("Item {item} is ready!!");
    }
        }

Example 7.12. Public interface to package-level functionality in CookDessert: Desserts.fx

package com.foo.dessert;

public class Desserts {
    public var item: String;
    var cookDessert:CookDessert;

    public function prepare() {
        if (item == "Cake") {
            cookDessert = CookDessert {
                bakeTime: 25
                coolingTime: 10
            };
            cookDessert.cook ("Cake");
        } else if (item == "Pudding") {
            cookDessert = CookDessert {
                bakeTime: 10
                coolingTime: 5
            };
            cookDessert.cook("Pudding");
        } else {
            println("Sorry!! Recipe not available yet!");
        }
    }
}

Example 7.13. Application accessing package-level functionality through a public interface in Dessert.fx: Dinner.fx

package com.foo.meal;
import com.foo.dessert.*;
var dessert = Desserts {
    item: "Pudding"
};
dessert.prepare();
//var cookDessert = CookDessert{}; - ERROR: Cannot be accessed

Output

Baking item: Pudding for 10.0 min
Cooling item: Pudding for 5.0 min
Item Pudding is ready!!

The code in this example is self-explanatory. The same code that was previously defined within the script has been moved into a class definition, and the only change you will probably notice is in the way objects are created for the class CookDessert within Desserts; this time, the attributes are initialized directly in the object literal. Apart from that, there is no change with the behavior.

Another change you should note is that the class CookDessert itself has been defined with a package access specifier. This would prevent anyone from creating an instance of the class from a class or script that is outside the package. That's exactly what the commented line in Dinner.fx demonstrates. If the line were uncommented, it would cause the compiler to throw an error, because Dinner.fx belongs to a different package and hence does not have access to the CookDessert class.

Honoring Access Specifiers for Java Classes

Access specifiers specified within the Java classes accessed within a JavaFX script must also be honored in the same way as JavaFX Script or JavaFX classes. Listings 7-14 and 7-15 demonstrate this behavior.

Example 7.14. A sample Java class: JavaImpl.java

package com.foo;
public class JavaImpl {
    int x = 10;
    String technology = "JavaFX";
    public JavaImpl() {
        System.out.println("Constructor called");
    }
}

Example 7.15. JavaFX script accessing the Java class - Main.fx

package com.foo;
var jimpl = new JavaImpl();
println("X value: {jimpl.x}");
println("Technology: {jimpl.technology}");

Output

Constructor called
X value: 10
Technology: JavaFX

As you see in Listing 7-14, the JavaImpl java class members do not specify any access specifiers, and in Java, the default access specifier is package. Hence, these class members must be accessible within the same package—com.foo. An instance of JavaImpl has been created from within the JavaFX script, Main.fx (Listing 7-15), and that script also belongs to the same com.foo package. Hence, the class members of JavaImpl are accessible within Main.fx.

This example indicates that the access specifiers, whether specified in Java or JavaFX Script, are appropriately honored and even the default access specifier type of Java is preserved when accessed from within JavaFX Script, even though the default access type is different than in Java compared to JavaFX.

Similarly, access is also revoked appropriately wherever required. For example, if we change the package in Main.fx from com to jfx, you will see a compilation error.

Now that you have learned how the package access specifier works under different circumstances, we can move on to the protected access specifier, which is closely tied to class definitions and inheritance.

The protected Access Specifier

The next wider access is provided by the protected access specifier. Members of the class that are protected are accessible within the same package, just like package-level members, and also accessible outside the package if it is accessed from within a subclass. The concept of base class (super class) and derived class (sub class) is explained in detail in Chapter 8, "Inheritance," but here is a brief introduction focusing on the use of the protected access specifier in the context of inheritance.

Inheritance is about taking an existing class and adding more functionality to it. The existing class is called the base class and the new class that extends the existing class is called the derived class. In addition to adding new functionality, the derived class can also choose to change the behavior of the existing members within its implementation.

Syntax

class derived class extendsbase class {..}

Example

class Car extends Vehicle {
}

(The rest of this class is pretty much the same as any other class definition.)

Here, when the Car and Vehicle classes belong to different packages, the only members of Vehicle that you will have access to from within the Car are the public members. But declaring everything as public would leave everything too open to control. There are cases where you would want to expose certain functionality only to those who extend your class and not to all the users of the class. This is where protected comes in handy.

Protected members of the class are accessible within the same package, just like package members, and they are accessible to classes that reside in other packages provided those classes extend this class.

Figure 7-3 demonstrates the protected access specifier.

Protected access for class X members

Figure 7.3. Protected access for class X members

As you see in the diagram, class Z and class Y are subclasses of class X, but they are implemented in different packages. When we looked at package access earlier, class Z was granted access but not class Y. With protected access specified for members of class X, all the classes extending class X will also have access to the protected member, regardless of the package where they are implemented. The protected level also preserves the package access, so that all the scripts and classes in the same package will have access to the protected member of class X.

The next example clearly shows the difference between protected and the other access specifiers you have seen so far. Listing 7-16 shows the Vehicle.fx file and Listing 7-17 shows Car.fx.

Example 7.16. A generic vehicle superclass: Vehicle.fx

package com.automobile;

public class Vehicle {
    package var yearOfManufacture: Integer;
    protected var noOfWheels: Integer;
    protected var noOfDoors: Integer;
    var make: String;
    protected var inspectionDone: Boolean;
    protected function checkQuality(): Boolean { return false; };

    public function getYearOfManufacture() {
        yearOfManufacture;
    }
    init {
        yearOfManufacture = 2009;
        make = "Toyota";
    }
    public function getMake() {
        make;
    }
    public function getNoOfDoors() {
        noOfDoors;
    }
    public function getNoOfWheels() {
        noOfWheels;
    }
}

Example 7.17. A car implementation that extends Vehicle: Car.fx

package com.automobile.fourwheelers;
import com.automobile.*;

class Car extends Vehicle {
    var noOfSeats: Integer;
    var hatchBack: Boolean;
    protected override function checkQuality(): Boolean {
        // Check engine
        // Check Interiors
// Check Painting
        return true;
    }
    init {
        noOfSeats = 4;
        hatchBack = true;
        noOfWheels = 4;
        noOfDoors = 4;
        inspectionDone = true;
        // make = "Honda"; // COMPILER ERROR: Not allowed
        // YearOfManufacture = 2008; // COMPILER ERROR: Not allowed
    }
}
var corolla = Car {};
println("Year Of Manufacture: {corolla.getYearOfManufacture()}");
println("Make: {corolla.getMake()}");
println("No Of Wheels: {corolla.getNoOfWheels()}");
println("No Of Doors: {corolla.getNoOfDoors()}");

Compile the two scripts and run Car.fx to see the following result.

Output

Year Of Manufacture: 2009
Make: Toyota
No Of Wheels: 4
No Of Doors: 4

In this example, you can see that the data members of the Vehicle class are declared as protected and there are equivalent public get methods. So the API user will only be able to use the get methods, and the attributes cannot be set directly from the customer code. The Car class, on the other hand, has access to these protected variables and can set the appropriate values, since the Car class extends from Vehicle. Nevertheless, there are some script-private and package data members, such as make and yearOfManufacture, that the Car class cannot access. Trying to access those variables would cause a compilation error. You can uncomment the commented lines in the Car class to see for yourself the compilation error you get.

Also notice that although the Vehicle and Car classes belong to two different packages, Car is able to access the protected members of the Vehicle class. This is different from the package access specifier, where we were not allowed to access the package data members of a class in one package from another package. Hence, protected members have wider access than package members and can be accessed across packages provided they extend the base class.

The following example, in Listings 7-18 and 7-19, shows a protected member accessed freely within the same package without any inheritance.

Example 7.18. Class definition having protected members: Cup.fx

package com.cutlery;

class Cup {
    protected var material: String;
    protected var purpose: String;
}

Example 7.19. Another class definition in the same package accessing protected members of Cup.fx: Saucer.fx

package com.cutlery;
class Saucer {
    init {
        var c = Cup{};
        c.material = "porcelain";
        c.purpose = "tea";
        println(c.material);
        println(c.purpose);
    }
}
var s = Saucer {};

Compile the two scripts and run Saucer.fx to see the following result.

Output

porcelain
tea

In this example, the protected members of the Cup class can be accessed from within the Saucer class because both of them belong to the same package. So to summarize, the protected access specifier has all the access that package access specifier provides, plus additional access for inherited classes.

The public Access Specifier

The public access specifier has the widest access in JavaFX Script. Public members of a class or the script can be initialized, overridden, read, assigned, or bound from anywhere. Figure 7-4 demonstrates public access for members of script1 in Package B.

Public access for script1 members

Figure 7.4. Public access for script1 members

As you see in the diagram, the public members of script1 are accessible everywhere. The example in Listings 7-20 and 7-21 demonstrates public access.

Example 7.20. A rectangle implementation: Rectangle.fx

package com.foo;
public class Rectangle {
    public var x: Number;
    public var y: Number;
    public var width: Number;
    public var height: Number;
    var area: Number;

    public function draw() {
        computeArea();
        drawRectangle();
    }
    function computeArea() {
        area = width * height;
    }
    function drawRectangle() {
        println("Initializing the rect peer");
        println("Creating graphics surface");
        println("Rectangle Drawn");
    }
}

Example 7.21. An application using the Rectangle API: UIBuilder.fx

package com.foo.uibuilder;
import com.foo.*;
var borderRect = Rectangle {
    x: 0
    y: 0
    width: 100
    height: 100
}

borderRect.draw();
// borderRect.computeArea(); - ERROR: Will not work

Compile the two scripts and run UIBuilder.fx to see the following result.

Output

Initializing the rect peer
Creating graphics surface
Rectangle Drawn

Note that there are two source files here, in two different packages. The public variables declared in Rectangle.fx can be accessed in UIBuilder.fx. In UIBuilder, we are creating an instance of Rectangle and accessing Rectangle's public function draw using the member operator. However, if you try to access a nonpublic member of Rectangle (say computeArea()), the compiler will throw an error. Public members of a class or script are accessible everywhere and thus have the widest possible access in JavaFX Script.

Note

Although the public access modifier is simple, you should use it with caution, because it limits your ability to make later changes to the API. Such changes will have to be done in a compatible manner so as not to break any existing applications that are using the API, and hence you are limited in what you can change once you get your first version of the API out. So it is wise to keep the access narrower initially wherever possible and expose as public only those APIs that are really needed. You can always widen the access later on without breaking compatibility.

The Enforced run() Function Requirement

When you introduce a public member in your Java FX script, or in a class within the script, and the script is going to be the entry point for your application (that is, its name will be the argument to the javafx executable), it is mandatory to implement a run method for this script where you have declared the public member. The compiler generates this automatically as long as you don't have a public member, but the moment you introduce a public member, the compiler expects you to take care of implementing this. Failing to do this will result in a compilation error. Listing 7-22 is an example that demonstrates the error.

Example 7.22. A script with public members but no run() function: ExampleWithoutRun.fx

// CAUTION: This example will not compile
    public var pi : Number = 3.142;
    public var radius : Number = 6;
    function area_of_circle( ) {
               pi * radius * radius;
    }
    println("area of circle = {area_of_circle()}");

Attempt to compile the script, and you'll see the following output.

Output

ExampleWithoutRun.fx:8: Loose expressions are not permitted in scripts with exported ('pu
blic', etc) members.
Any expressions to be run on script invocation should be moved into run().
println("area of circle = {area_of_circle()}");
^

Let's correct the previous example so that it works. Listing 7-23 shows the revised code.

Example 7.23. A script that tightens loose expressions: ExampleWithRun.fx

public var pi : Number = 3.142;
    public var radius : Number = 6;
    function area_of_circle( ) {
        pi * radius * radius;
    }
    function run() {
        println("pi = {pi}  ,  radius = {radius}");
        println("area of circle = {area_of_circle()}");
    }

Compile and run the script to see its output.

Output

pi = 3.142 , radius = 6.0
area of circle = 113.112

As you see in Listing 7-23, once you implement the run() method, everything works fine. This run() method must be included directly in the script and not within any class as such. The run() method can also take arguments, typically the arguments that you pass to your application through the command line.

JavaFX Secondary Access Specifiers

As mentioned in the chapter introduction, JavaFX Script offers two more access specifiers, which are applicable only to variables. They are

public-init
public-read

These access specifiers cannot be used for classes and functions.

Because of the central use of object literals in JavaFX Script, instance variables tend to be externally visible. To provide more control over the var declarations, JavaFX Script introduced these access specifiers that offer more-refined access. We will refer to these access specifiers as secondary specifiers throughout this chapter. The other access specifiers you have learned so far can be considered primary specifiers.

These access specifiers are additive and can be combined with primary access specifiers, such as script-private (no specifier), public, protected, and package.

public-read

As its name suggests, the public-read access specifier allows a variable to be read from anywhere. However, the ability to change the variable value depends on other access specifiers mentioned in the var declaration.

Syntax

public-read [primary access specifier] var var name[:data type] [= value];

where not specifying the primary specifier would default to script-private. The data type is optional, since the compiler can infer the type automatically from the value assigned.

Example

public-read var x = 10.0;

Variable x can be read from anywhere—within the script, outside the script, within the package, outside the package, and so on. But let us see who has the privilege to modify this variable.

The primary access specifier is skipped for this variable declaration, which means this variable can be modified only within the script. Hence, within the script where this is declared, one can modify, bind, or assign the value of this variable.

Listings 7-24 and 7-25 present a simple example of a simulated media player.

Example 7.24. Mock Media Player implementation: MediaPlayer.fx

public class MediaPlayer {
            public var url: String;
            public-read var playing: Boolean = false;
            public function play() {
                // play media here
            println("Currently Playing {url}");
playing = true;
        }
        public function stop() {
            // stop the playback
            playing = false;
        }
    }

Example 7.25. A juke box application that uses MediaPlayer: MediaBox.fx

var player = MediaPlayer {
    url: "http://www.javafx.com/javafx_launch.wmv"
}
player.play();
println("Is Playing? {player.playing}");
player.stop();
println("Is Playing? {player.playing}");

Compile the two scripts and run MediaBox.fx to see their output.

Output

Currently Playing http://www.javafx.com/javafx_launch.wmv
Is Playing? true
Is Playing? false

This example demonstrates a simple media player application that defines a URL and play()/stop() functions. The MediaPlayer class also exposes a public-read attribute, playing, that is intended for only the applications to read. The MediaBox application creates an instance of the player and plays the media. Once it starts playing, the application wants to query whether the media is still being played and hence it queries the playing attribute's value. However, no application can set this value except the MediaPlayer class itself, which would typically set this value depending on various conditions such as the user stopping the media, end of media reached, the URL changed while media was played, and so on.

For example if the MediaBox tried to set the value of playing, that would cause the compiler to throw a compilation error since it has been declared as public-read.

Also note that there is no primary access specifier mentioned in the declaration; this means the write-access defaults to script-private. So the value of playing can only be modified within the MediaPlayer.fx script. Another way of allowing write access externally (if needed) is to declare a public function setPlaying(val: Boolean) within the MediaPlayer, and applications that would like to modify the playing value can call this function.

Now let us see some examples that combine primary and secondary access specifiers to offer a varied write-access to the variable.

public-read package var x: String = "JavaFX";

In this statement, the variable x has its primary access specified as package—it can be assigned, bound or initialized within the same package and the value can be read anywhere.

public-read protected var x: String = "JavaFX";

Here , the variable x has its primary access specified as protected—it can be assigned, bound, or initialized within the same package or from derived classes in other packages.

public-read public var x: String = "JavaFX";

The variable x has its primary access specified as public. This combination is useless, because the variable is publicly modifiable and hence public-read does not make sense anymore. You can remove the -public-read- specifier and it will give the same effect. Since this is a potentially useless combination, the compiler will give a warning message if you try to compile this code.

Also note that a public-read variable can be unidirectionally bound to another variable from anywhere, but the public-read variable must be on the right side of the bind expression. In such a binding, the value of the public-read variable is consumed by some other variable without causing any change to the public-read variable as such. However, bidirectional binding will cause a compilation error. The example in Listings 7-26 and 7-27 demonstrates this binding.

Example 7.26. Script with a single public-read attribute: PublicReadBindExample1.fx

package com.foo;
public-read var x = 100.0;
public function setX(val: Number) {
    x = val;
}

Example 7.27. Public-read attributes in bind expressions: PublicReadBindExample2.fx

package com.jfx;
import com.foo.*;
var obj = PublicReadBindExample1 {};
var y = bind obj.x;
println("Y Value: {y}");
obj.setX(200);
println("Y Value: {y}");

To see the output, compile and run PublicReadBindExample2.fx.

Output ()

Y Value: 100.0
Y Value: 200.0

As you see in the output, changing the x value in this example through the setX() method changes the y value as well, since y is bound to x.

With the help of public-read, you can expose any variable declaration publicly for read access without worrying about the variable being modified inadvertently by the public code, which still retains the write access as you wish, be it protected, package, or script-only.

public-init

A variable defined with the public-init access specifier can be read or initialized anywhere but cannot be bound publicly. Notice that I said "initialized" and not assigned—it can be initialized in an object literal but cannot be changed or assigned or bound from anywhere. Nevertheless, these variables can be modified either within the same script, within the package, or in inherited classes across the package depending on the primary access specified. In other words, once initialization is over, public-init would pretty much be treated the same way as public-read throughout the life-cycle of that class instance created.

Note

Another important difference between public-read and public-init is that public-init usage is limited to class member variables and is not allowed for script variables. Using it for script variables will throw a compilation error.

Syntax

public-init [primary access specifier] var var name[:data type] [= value];

Not specifying the primary specifier would default to script-private. Specifying a data type is optional because the compiler can infer the type automatically from the value assigned.

The example in Listings 7-28 and 7-29 demonstrates the usage of public-init.

Example 7.28. Defining public-int vars: PublicInitExample1.fx

public class PublicInitExample1 {
    public-init var y = 20;
    public-init var x = bind (2 * y);
}

Example 7.29. Initializing public-init vars: PublicInitMain.fx

var pre1 = PublicInitExample1 {
    y: 100
    x: 20
}
println("X Value: {pre1.x}");
println("Y Value: {pre1.y}");
println("------------");
var pre2 = PublicInitExample1 {
    y: 100
}
println("X Value: {pre2.x}");
println("Y Value: {pre2.y}");

Compile the two scripts and run PublicInitMain.fx to see the following output.

Output

X Value: 20
Y Value: 100
------------
X Value: 200
Y Value: 100

In Listing 7-28, there are two public-init variables defined within the PublicInitExample1 class. In a different FX script, you create an instance of the class (pre1) and you are initializing x, y within the object literal at the time of creation. According to the definition of public-init, this is allowed. Hence, printing those values show the correct values you have assigned (20, 100). Note that the bind defined for variable x has been overridden in the object literal and hence will have no effect on the pre1 instance of the class.

In the second instance of the PublicInitExample1 class, pre2, you are initializing only the y value and not the x value. So the x value will use the original bind expression defined within PublicInitExample1 class for pre2, and the last two lines of output indicate that the bind defined on variable x has been exercised.

Now with the same example, after the initialization let us try to see if the value of y can be changed outside the object literal block. Listings 7-30 and 7-31 show the code.

Example 7.30. A script with public-init vars defined: PublicInitExample1.fx

public class PublicInitExample1 {
    public-init var y = 20;
    public-init var x = bind (2 * y);
}

Example 7.31. Accessing public init outside the object literal: PublicInitMain.fx

// WARNING: This code will not compile
var pre1 = PublicInitExample1 {
    y: 100
    x: 20
}
println(pre1.x);
println(pre1.y);
pre1.y = pre1.y + 100;

Compile and run the scripts to see the following output.

Output

PublicInitMain.fx: y has script only (default) write access in PublicInitExample1
pre1.y = pre1.y + 100;
  ^
1 error

This example demonstrates the difference between initialization and assignment. Initialization within the object literal is permitted here, but trying to assign or bind to x/y causes a compilation error.

Now let us see some combinations of public-init and different primary access modifiers to offer varied access to the variable.

public-init var y: String = "JavaFX";

Here, the variable y can be read from anywhere, and it can be publicly initialized within an object literal (as seen in the examples given before) from anywhere, but it cannot be assigned or bound publicly. Because the primary access specifier is omitted, it defaults to script-only, allowing y to be modified (assigned or bound) only within the script.

public-init package var y: String = "JavaFX";

This time the variable y can be read and initialized from anywhere, but it can be assigned and bound only within the same package.

public-init protected var y: String = "Cool";

Here the variable y can be read and initialized from anywhere, and it can be modified, assigned, or bound anywhere within the same package. It can be modified, assigned, and bound from any class in other packages, provided the class extends from the base class where y is declared.

public-init public var y = "JavaFX";

This combination would be useless, because you have already made the variable public by defining the primary access specifier as public. It is fully open to the public, so it makes no sense to declare it as public-init. The compiler in this case would throw a warning to indicate that this is a meaningless combination of access specifiers.

Secondary Specifiers and def

So far, you have seen the usage of secondary access specifiers only with var declarations. Technically, it is possible to use def in place of var. However, the real-world use of such a combination is very limited. A def once defined cannot be changed in an object literal, it cannot be overridden by a derived class, nor can it be assigned a different definition or value regardless of what the access permissions are. Hence the usage of access specifiers with def is pretty much confined to specifying whether the value of the def should be publicly readable or not.

Access Specifiers for Class Definitions

So far, you have only seen access specifiers applied to class members and not to classes themselves. In most of the examples you have seen so far, the widest access, public, has been assigned to the classes. In fact, most of the access specifiers that we have discussed so far are applicable to classes as well as class members. In this section, you will see the applicability of each access specifier with respect to class definitions.

The access specified on the class normally will be wider, with appropriate access restrictions enforced on the class members. Having a too-restrictive access specifier on the class itself can potentially make the class unusable.

Script-private Classes

A class definition that does not have any access specifier mentioned explicitly would default to script-private, meaning that the class can be instantiated and used only within that script. Trying to instantiate or refer to the class in some other script would cause a compilation error. Listing 7-32 shows an example.

Example 7.32. Script-private class definition: Cup.fx

class Cup {
    public var material: String;
}
var c = Cup { material: "ceramic" };
println ("From same script: {c.material}");

Output

From same script: ceramic

In Listing 7-32, the Cup class is script-private, and you don't see any issues using it within the script. However, when you try to use it in some other script, the compiler will throw an error, saying the Cup class has script-only access.

The same error will occur even when the members of the class are public. So normally it does not make sense to have access for the class members that is wider than the class itself.

Note

Having script-only access at the class level is normally too restrictive to be useful, and you should keep the class at a higher access level and restrict the class member's access instead.

Package-accessible Classes

As with class members, you can choose package access for the class itself, which will make the class accessible only within the package and not exposed to the public. It is common to apply such access to implementation classes whose interface and implementation are consumed only within the respective package. Listings 7-33 and 7-34 demonstrate this access.

Example 7.33. A class definition restricted to package-only use: Cup.fx

package class Cup {
    package var material: String;
}

Example 7.34. Using a package-only class: Cup1.fx

var c1 = Cup { material: "plastic" };
println("From another script {c1.material}");

Output

From another script plastic

In this example, Cup.fx and Cup1.fx both reside within the same default unnamed package, so the Cup class is accessible from within Cup1.fx. However, if you move Cup and Cup1.fx into separate packages, there will be a compilation error, because Cup has only package access and cannot be accessed from a different package.

Protected Classes

Classes specified as protected are different from protected class members. A protected member of a public class (A) can be accessed from within another class that extends A, even if the derived class is in a different package. However, this definition will not work if the class itself is protected, because a protected class cannot be extended by another class in a different package. The compiler will complain that the protected class is being accessed outside of its scope because the usage to extend the class comes directly within the script. Go through the example in Listings 7-35 and 7-36 and you will understand this clearly.

Example 7.35. A protected class definition: Cup.fx

package com.cutlery;
protected class Cup {
    protected var material: String;
}

Example 7.36. Accessing a protected class definition: Cup1.fx

//WARNING: This script will not compile
package com.jfxcutlery;
import com.cutlery.*;
class FXCup extends Cup {
    public override var material = "Ceramic";
}
var c2 = FXCup{};
println(c2.material);

Compile and run the code to see the following output.

Output

Cup1.fx:4: com.cutlery.Cup has protected access in com.cutlery
class FXCup extends Cup {
          ^
Cup1.fx:8: cannot find symbol
symbol : variable material
location: class com.jfxcutlery.Cup1.fxCup
println(c2.material);
     ^
Cup1.fx:5: cannot find symbol
symbol : variable material
location: class com.jfxcutlery.Cup1.fxCup
    public override var material = "Ceramic";
              ^
3 errors

As shown in the output, the compiler sees an inappropriate use of the protected class where it has been used within a script directly from a different package. As per the definition, this is wrong because a protected entity is not being used within a derived class. So having a protected class does not bring in any of the advantages of the protected access specifier, and the class can be used only within the package. Hence there will be no difference between protected and package for class definitions.

So in this case, the class has to be defined as public, and if you want to have restrictions in place, keep the class members as protected wherever appropriate.

Public Classes

Public classes do not need much explanation. Just like any other public members of the class or script, they can be accessed from anywhere and can be instantiated or extended from anywhere. You have already seen many such examples in this chapter.

Summary

In this chapter, you have learned about data encapsulation, or data hiding, and the advantages of having appropriate restrictions on data members. In JavaFX Script, data encapsulation can be achieved through access specifiers (also called access modifiers) such as public, protected, package and the default, in decreasing order of access territory. Public members can be read, written, bound and so on from anywhere. Protected members can be accessed within the same package or from classes in other packages that derive from the class where the protected member is defined. Package members can be accessed from within the same package. If no access specifier is specified, it defaults to script-private, where the member would be accessible only within that script.

JavaFX also offers two new secondary access specifiers, public-read and public-init. Members having public-read access can be read from anywhere. Members having public-init can be initialized (within an object literal) or read from anywhere. However, the write access for these members would be decided by the primary modifiers specified along with public-init/read. If primary modifiers are omitted, the member will have write access only within the script where it is defined.

In the next chapter, you will learn more about inheritance in object-oriented programming. You'll learn how it can be achieved in JavaFX Script and how its implementation differs from that in Java. You will also learn more about protected access, with real inheritance examples, and you'll learn how to implement Java Interfaces within JavaFX.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset