Classes are an integral part of Object Orientation, encapsulating state and behavior for each component in a larger system, thereby allowing us to express our software in terms of the structures and relationships which link its autonomous component parts. Object Orientation has become an incredibly popular way of constructing software in recent years: both Java, and its underlying Java Virtual Machine environment, are heavily object centric.
It should come as no surprise, then, that JavaFX Script is also Object Orientated!
JFX’s support for OO is pretty rich. While you may not be shocked to see such stalwarts as access modifiers included in its list of features, you may be surprised to learn JFX permits multiple inheritance (something which many popular modern OO languages, like Java and C#, do not.)
|This article is based on JavaFX in Action, published November, 2009. It is being reproduced here by permission from Manning Publications. Manning early access books and ebooks are sold exclusively through Manning. Visit the book’s page for more information.
Developer Tutorial readers can get 30% off any version (ebook or print book) of JavaFX In Action. Simply use the code “devtut30” at checkout.
In the coming sub-sections we’ll explore the ins and outs of classes, and how to define, create, inherit, control and manipulate them so we can employ OO thinking to write robust, maintainable and reusable code.
Object Orientated what..?
This book does not seek to be an introduction to Object Orientation. Such a topic would demand at least a book to itself, and possibly several if we included all the various methodologies, patterns and best practice guides. Given the prevalence of Object Orientation languages in modern software development, it’s unlikely anyone would have made it to JavaFX without encountering at least one Object Orientated language along the way, therefore this book assumes the reader has at least a passing familiarity with basic OO ideas like inheritance and objects.
How crucial is understanding objects to learning JFX? Well, you don’t need a solid grasp of OO to proceed with the material in this book, but it would definitely help. Certainly, you’re only seeing the picture in black and white if you don’t have at least a working knowledge of the ideas behind classes, objects, inheritance, etc.
However, an in-depth encyclopedic awareness of UML (Unified Modeling Language) and all the various OO patterns and design methodologies is not required. Don’t panic too much if you’re a fairly recent convert to classes and the like! Remember, JavaFX Script is targeted at quite a broad audience, not necessarily all of them have programming as their primary skill. Indeed, you might like to see JFX as an excellent opportunity to practice and enhance those basic OO skills you do posses.
Classes and source files
Before we begin our discussion of classes proper, there’s one itch which desperately needs scratching – indeed I suspect many seasoned Java programmers will have been wondering when I was planning on getting around to mentioning it, especially if they’ve been building and running the sample code for themselves.
One thing you learn very early on in Java is that there’s a close bond between a Java class and the name of the source code file which houses it. If a class is called Fred, its source file will be Fred.java . It’s a simple naming relationship, but one which has served the Java compiler well over the years.
However, this isn’t always the case with JavaFX Script. JFX has a looser coupling between code and the name of a source file. You’ve already witnessed countless JavaFX Script examples thus far in which the code didn’t live inside any class, but roamed quite freely, unattached to any object. This is a radical departure from the way Java does things, where everything has to be associated with a class.
Although the rules are a little looser, we still need to stick to the naming convention for any class we wish to refer to outside its source file. So, for example, if we want to refer to Fred from outside the source file it lives in, we should put it in a source file called Fred.java – that way the compiler can find (and compile, if necessary) the class when it sees any reference to it.
Classes which are only referred to within the source file they are defined in can get away with not needing to be in a separate class-named file. This is handy for small utility classes (like a data structure, for example) which are only used locally to help implement a given algorithm, as it mean they don’t need to be created in separate source code files.
But what about all that free roaming code which doesn’t belong to any class, where does that end up? Behind the scenes, of course, everything end up inside a class. This is because the Java Virtual Machine requires it to be that way. The free roaming code is bundled up and added to a bytecode class file under the current source file name, even if a class of that name doesn’t formally exist in the JFX program.
A good way to think of this, from a Java programmers perspective, is to picture code within a given FX source file not belonging to a JavaFX class as inside the main() method of a Java class. The code can then be invoked by using its fully qualified class name with the javafx runtime. So free roaming code inside the source file Fred.fx will end up in a class file Fred.class, runnable as if it lives in said class’s main() method.
Class definition (class, attribute, function, this)
Creating new classes is an important part of any Object Orientated language, and true to form the JavaFX Script syntax boasts its trademark brevity. In this section we’ll forgo mention of inheritance (for now), concentrating instead on the basic format of a class: its data and behavior.
Variables in a JavaFX Script class are referred to as attributes, and the behavior is implemented by way of functions. Let’s have a look at an example:
The class above defines a music track, with three attributes and two functions.
You’ll note JavaFX Script uses the same naming conventions as Java: class names begin with a capital letter, function and attribute/variable names do not. Both use camel case to form names from multiple words. You don’t have to stick to these rules, they are only conventions after all, but it helps make your code more readable to other programmers.
The title, artist and time attributes are the variables which objects of type Track will have. Aside from the attribute keyword substituting for var, attributes share pretty much the same syntax as variables. They can also be assigned initial values by adding an equals symbol and said value before the closing semi-colon.
Functions in JFX classes are defined using the keyword function, followed by the function’s name, a list of parameters and their type in parenthesis, a colon and the return type (where Void means no return.) There are two functions above, the first accepts another Track and checks if it references the same song as the current object, the second constructs a String to represent this song.
The keyword this can be used to refer to the current object, although (as the toString() function demonstrates) it’s use is optional.
You’ll note how the attributes and functions of a class are enclosed within the classes curly braces? This is how the compiler knows they belong to the class. If you want a function or a variable (attribute) to belong to a class you must define it inside the class’s body, otherwise it will float free, independent of any class association.
This practice of constraining the variables and code of a given class within the class definition body is common practice in many other programming languages, including Java. It may seem obvious to readers with experience of said languages – who probably make up a high proportion of this book’s audience – but I mention it here only because early interpreted versions of JavaFX Script were a little unconventional in this regard. For example, the body of a function (the part with the code in) was typically written outside of the class body in early experimental versions of the JavaFX Script language.
So, if you see any example source code on the internet written that way, don’t be surprised if it doesn’t compile
Once we’ve defined our class we can create objects from it. Attributes of the new object can be set by listing their name, a semi-colon, then the desired values, inside the curly braces following the class name. In the example, song is created as an object of type Track. In case you’re wondering, we didn’t strictly need to specify the type after song (duck typing would work) but as this is your first class example I thought it wouldn’t hurt to go that extra mile.
Before we move on, there’s a couple of things which need some extra attention. Take a look at the source code below:
A couple of things to note in the above code. Firstly the doesNotReturn() function fails to declare its return type. It seems explicitly declaring the return type is unnecessary when the function doesn’t return anything â€“ Void is assumed.
Secondly, as far more importantly, the doesReturn() function looks rather bizarre – shouldn’t it have a return keyword in there somewhere? Well, yes, it could have – but that’s also optional, thanks to the way JavaFX Script works.
JavaFX Script is what’s referred to as an expression language, meaning all executable bits of code are interpreted as expressions, accepting zero or more parameters as input, and giving out none or one return value. And when I say all, I mean all â€“ even loops and conditions will work on the right hand side of an assignment.
For now, we’ll push on with our look at classes and objects, but keep the idea that all code statements are expressions in the back of your mind as we proceed.
Object declaration (new) and static class members (static)
We saw an example of creating an object from a class in the previous section. Many other Object Orientated languages call a constructor, often via a keyword such as new, but JavaFX Script objects have no constructors, preferring its trademark declarative syntax instead.
For those situations in which we absolutely must have some sort of constructor, the JavaFX documentation suggests we use static functions instead. This is why we’re also dealing with static functions and data in this section (in case you thought it was a rather odd pairing of topics.)
In this section we’ll see more examples of declaring objects using the JFX syntax, and we’ll also explore an alternative Java-style syntax for working with uncooperative classes in existing Java APIs. Plus the aforementioned static syntax.
The recommended way to create an object is, as you may have gathered, using the declarative syntax. We simply state the class name of the object we wish to create, open our curly braces, then list each attribute we want to give an initial value to, using its name and value separated by a colon. A matching closing ‘curly’ is needed, obviously, and an optional (it seems) semi-colon to round the whole thing off.
Let’s have a look at an example:
We create three objects, the first two using the JavaFX Script syntax, and the final one using the Java syntax. You’ll note how JFX uses the colon notation, allowing us to set any available attribute on the object as part of its creation.
The second example merely demonstrates a slightly more compact layout, with two attribute assignments on the same line, separated by a semi-colon. JavaFX Script actually seems surprisingly liberal about when it requires a semi-colon.
The third object demands some explanation. It looks more like the way we might create a new object instance in Java, and indeed that’s intentional. There may be times when we are forced to instantiate a Java object using a specific constructor to make it function the way we desire. The new syntax allows us to do just that. As demonstrated above, new can be used on any class, including JavaFX Script classes, although we should resist the temptation (the example above is strictly speaking bad practice) as it should only be employed for Java objects which cannot be created any other way.
Finally we use the static syntax to create an object by calling a static function, specifically written for the purpose, on the class. This returns an initialized object of the class type.
What are static functions? They are functions which ‘belong’ the to class rather than objects of that class. This means they can be referenced without creating an object first, by using only the class name, as demonstrated above. This make them ideal for constructor (or should that be factory?) style functions.
We can also have static attributes: pieces of data which belong to a class, outside of any single instance of that class. This makes them good for constants, like in the drive example above.
One final thing to note: both static functions and attributes can be referenced via object instances of the class type, so as well as SpaceShip.spaceShip(“X-Wing”) and SpaceShip.WARP_DRIVE we could also have used ship1.spaceShip(“X-Wing”) and ship4.WARP_DRIVE instead, or any of the other objects. Be warned though, if a static attribute is changed, it changes for all class objects – while it may appear visible from each object, there is only ever one copy of the data, shared.
Object declaration and sequences
We saw an example of creating an object from a class in the previous section. Many other Object Orientated languages call a constructor, often via a keyword such as new, but JavaFX Script objects have no constructors, preferring its trademark declarative syntax instead.
There’s actually nothing special about the example we’re about to see. It’s worth an example on its own, however, as this mixing of objects and sequences crops up frequently in JavaFX programming.
The code below creates two Track objects inside a Track sequence. Note: to run this code you need the Track class we saw a short while back.
Class inheritance (extends)
Part one is a simple Animal class with just a life gage and a date of birth, plus three functions: born() for when the object is just created, getName() to get the animal type as a String, and toString() for getting a printable description of the object.
This is the base class onto which we’ll build in the following parts of the code, so eye it over a second time and make sure you’re familiar with what it’s doing.
While writing the code for this section I accidentally missed off the return type off the getName() functions – and strangely the code compiled and ran! I’m not sure if this was a bug in the preview release compiler, or an intended feature of the language. Either way, I wouldn’t recommend you leave return types off functions; if nothing else they help make your code more readable.
Our second chunk of code inherits the first, by using the extends keyword after its name followed by the parent class, in this case Animal. As you may expect, this makes Mammal a type of Animal. It overrides the getName() method to provide it’s own answer, and adds a new function for giving birth to a new Mammal. The new function creates a fresh Mammal, setting the initial life value, then calling born(), a function it inherited from its parent class, Animal. (It also inherits the toString() function, of course.)
So far so good, how another another Animal subclass?
Again we have a subclass of Animal, this time called Reptile, with its own overridden implementation of getName() and its own new function. The new function in question creates and returns a fourth type of object, Egg, housing a new Reptile (I’ve missed off the return keyword completely from layEgg(), merely stated the e variable on its own. Remember, JavaFX Script is an expression language.)
The most interesting part of layEgg() is the use of JFX’s declarative syntax to create the inner Reptile object. The code which creates the Reptile is actually written inside the code which creates the Egg. We could have done it long hand (the Java way) by creating a Reptile object first, then creating the Egg, then adding the former into the latter, but instead we combined the creation of the whole structure (both new objects) into one single expression.
Now finally some code to test our new objects.
We create two Mammal objects, but here’s the clever part: we store one of them as an Animal. We print both of them (Java’s System.out.println() calls toString() on objects passed into it, and this goes for JavaFX objects too), then we explicitly call the getName() function of the object which has been typed as an Animal. The getName() function exists in both Mammal, and its parent super class, Animal, recall. So what will it return, “Mammal”, which is the type it truly is, or “Animal”, the type it’s being stored as?
The answer is â€œMammalâ€, because JavaFX functions are virtual, meaning the ‘later’ overriding redefinition of getName() in the subclass replaces the original definition in the parent class, even when the object in question is being referenced by way of its parent type.
By the way, before we move on I do want to acknowledge to any women reading that I fully acknowledge childbirth is not as painless as creating a new object and calling a function on it! Likewise, similar sentiments to any reptiles who happen to be reading.
At the sharp end of Object Orientated software everything tends to boil down to types. What type is an object? For example, is it a plane, a train, or an automobile? Of course, all three are types of vehicle, and share common properties and functionality. They all move, and therefore have a speedometer, an odometer (mileage) and consume power. They all carry passengers as well. They all need some form of engine to drive them forward, and a braking system to slow them down. But, of course, they also have a lot of differences. Trains cannot arbitrarily turn left or right, because they are bound by the constraints of a track â€“ or at least they shouldn’t be able to under normal operating conditions. Cars cannot fly through the air like a plane (again, under normal operating conditions!) but they can reverse, which is something a plane in flight cannot do (or can it? See later.)
We build up Object Orientated software by modeling these relationships. If we were building a transport simulator we might start with a vehicle class which contained all the data and functionality we know is common to all vehicles in our system. The odometer, for example, could be included in this top level class, because all vehicles have a mileage. We could also define a few functions, perhaps one to speed up and one to slow down, because both of these are common to all vehicles. Yet each vehicle accelerates and decelerates in a different way, so these functions can only be stubs which will be populated by the specific vehicles themselves.
We could then start to define different classes of vehicle (excuse the pun) based on our top level class. We might define a plane class, which adds an altitude attribute. We might also define an automobile class, which adds turn left and turn right functions. And so on… The process of creating a more specific class in terms of a more general one is known as subclassing. We say that (in terms of our simulator) a plane is a subclass of vehicle â€“ in other words it is a type of vehicle, and can be treated in our simulation either specifically as a plane, or generically as a vehicle.
When the plane subclasses vehicle it can fill out the stub functions for accelerating and decelerating with some code which makes sense to how planes work. When it does so we say the plane has overridden those functions – in other words it has replaced the ones in vehicle with its own more specialized implementations. Later on we might define a specific class for a Harrier Jump Jet, which has very different movement capabilities to other planes (it can hover on the spot), and so may need to override the plane’s accelerate and decelerate functions again, with even more Harrier-specialized versions.
An object on our simulation may be created as a type of Harrier Jump Jet, which in turn is a type of plane, which in turn is a type of vehicle. Because we know the Harrier inherited all the functionality of the plane (even if it did override some of it with its own implementation), and by proxy inherited all the functionality of vehicle (again, even if it replaced some of it), the Harrier can be treated as either a Harrier, a plane, or a vehicle. This means we can add the Harrier object to a vehicle array containing stream trains, Ford Model T’s and Apollo space capsules – all of which will ultimately inherit from our top level vehicle class in the simulation.
When we treat the Harrier as a vehicle we can only reference those attributes and functions which are available to vehicles – so checking the odometer is fine because it is part of the vehicle class, but checking the altimeter will result in a compilation error. The altimeter is specific to the place class, and its subclasses. To use it we need to cast the generic vehicle object into a more-specific plane or Harrier Jump Jet object. This ability to treat an object by way of its super types (its parents in the class hierarchy) is known as polymorphism.
Multiple inheritance: compound and plain classes
JavaFX Script permits multiple inheritance, as alluded to in previous sections. Multiple inheritance is the ability to acquire attributes and functions from more than one class at a time, through subclassing.
Java permits only single inheritance, but by way of a compromise supports interfaces, in which a class can inherit multiple polymorphic forms in one go (the necessary details required for the class to be treated as other types) but no actual functionality. As such the interfaces a Java class inherits are not part of any class hierarchy, but merely a way of cutting across the hierarchy to allow polymorphism in places where it would otherwise to awkward to achieve using the hierarchy alone.
So much for Java, what about JavaFX?
This ability of JFX’s to inherit from more than one class causes a little bit of a rift between itself and its big cousin, Java. The Java Virtual Machine expects single inheritance, and the JavaFX Script compiler therefore employs a cunning combination of classes and interfaces to make its multiple inheritance work. This is fine when dealing with pure JavaFX classes, but what happens when we start adding Java classes into the mix? What happens, for example, when a JavaFX class extends a Java class?
We’ll look at that in a moment, but for now let’s familiarize ourselves with what multiple inheritance looks like in JavaFX Script:
The Robot class is perhaps something we’d use in a video game. Here we see it inherits the capability to both move and fire a weapon. In our game there may be many types of object which can fire but not move (fixed cannons, for example), or move but not fire (a reconnaissance probe, for example), so we’ve defined movement and firing as separate inheritable types. You’ll note in the example I couldn’t be bothered to implement the full artificial intelligence needed to make our robot work, so I’ve added a few comments to describe what the code might do, and you can fill the rest in with your own imaginations.
As the example demonstrates, the extends keyword specifying the class being derived from can actually take a comma separated list of classes. This is the secret to inheriting multiple classes in JavaFX. And that’s really all there is to it – well, apart from one small detail…
In order to co-operate with Java, JavaFX Script makes a distinction between classes it refers to as being compound and those it refers to as being plain. So what do these terms mean?
- A plain class is any class which extends, directly or indirectly, a regular Java class. When we say a regular class we mean one which was written in Java – or any language which compiles to JVM compatible bytecode, other than JavaFX itself.
- A compound class is any class entirely constructed from within JavaFX. Both itself and every class it inherits from, directly and indirectly, must be JavaFX classes.
So a plain class is one which either is a Java class itself, or inherits from a Java class. And a compound class is one which is a pure JavaFX Script class, with no Java classes in its inheritance. Now that we understand the terminology, here’s the rules which define how the two types work together:
- All JavaFX classes are compound by default.
- A JavaFX class may directly inherit any number of compound classes, but only one plain class.
- Any JavaFX class which inherits a plain class, becomes a plain class.
So classes which live entirely within JavaFX can work happily with multiple inheritance, but classes which depend upon ‘outside’ (non-JFX) classes are bound by Java’s constraints of single inheritance. Whew, hopefully that’s all clear!
Right, now for something completely different…
Function types in JavaFX are incredibly useful. Not only do they provide a really neat way of creating event handlers – even neater when combined with anonymous functions, which we’ll look at in the next section – but they allow us to plug bits of bespoke code into existing software algorithms.
Let me explain: functions in JavaFX Script are first class objects, which means we can have variables and attributes of function type, and can even pass a function into another function as a parameter. This is best explained by way of an example:
The code centers around a function called testFunc(), which accepts a String and returns a Boolean. The function itself is at end of the code snippet, it’s a pretty unremarkable piece of code, but the first part is where the action is as far as we’re concerned.
Firstly we define a new variable, func, with a very unusual type. The variable will hold a reference to our function, so its type reflects it the function signature: function(s:String):Boolean – a function which accepts a String and returns a Boolean. We can assign to this variable any function which matches that signature, and indeed in the very next line we do just that, when we assign testFunc (our function) to func (our variable) using what looks like a standard variable assignment.
We can now call testFunc() by way of our variable reference to it, which the code does twice, just to prove it works. This demonstrates assigning a function to a variable, or class attribute, but what about passing functions into other functions?
Unsurprisingly the code works along similar lines: the receiving function uses a function signature for its parameters, just like the variable above. Let’s have a look at some code:
Let’s take this example nice and slow, because there’s a lot of complicated code in there. The first function, manip(), accepts two parameters and returns Void. The first parameter is of type String, and the second is of type function(s:String):String, which in plain English translates as a function which accepts a String and returns a String. Fortunately we happen have two functions to hand matching that very signature, m1() and m2(), both of which accept and return a String, performing basic manipulations in between.
At the end of the code we call manip() twice, passing in a String and one of our functions each time. The manip() function invokes the parameter function with said String and prints the result.
A simple example, perhaps, yet one which adequately demonstrates the effect.
Being able to pass functions around like this is quite a power feature. For example, imagine being able to control which items get shown in a list by pointing a class attribute towards a function which sorted and/or filtered the entries.
But this isn’t the end of our discussion. In the next section we continue to look at functions, this time with an exciting twist.
We’ve just seen how we can pass functions as parameters into other functions, and even assign them to variables, but what applications does this functionality have? The most obvious one is callbacks – or event handlers as they’re more commonly known in the Java world.
In a GUI environment we frequently need to respond to events originating from the user. When they click a button or slide a scrollbar, we need to know so we can take action, for example. The method we use to act on an external event is to register a piece of code with the class which originates the event, to be called when said event happens. Function types, with their ability to point at code, fit the bill perfectly. All any class which can generate events needs to do is provide an attribute of function type, complete with the necessary signature, then call whichever function gets assigned to that attribute once the event is fired.
We’ve already seen how we might do this in the previous section. But having to write a separate function for each event handler we might need is a pain, especially as in most cases we only refer to them the once. If only there was a quick and easy syntax for one-time function creation. Well, unsurprisingly, there is…
The class FileSystemWalker has three attributes and two functions. One of the attributes is a function type, called action, which can point to functions of type function(file:File):Void – or, in plain English, any function which accepts a java.io.File object and returns nothing.
The most important function is walk(), which recursively walks a directory tree looking for files which end with the desired extension, calling whichever function has been assigned to action for each match, and passing said file in as a parameter. The other function, go(), merely acts as a convenience to kick-start the recursive process from a given root directory.
So far, nothing new! But it starts to get interesting when we see how walker, the object of type FileSystemWalker, is constructed.
In its declaration walker assigns the root directory to the first parameter passed in on the command line – so when you run the code make sure you nominate a directory! (The __ARGS__ sequence is where you’ll find all the command line arguments, by the way.) The extension is set to PNG files, so walk() will only act on filenames with that ending. But look at the way action is assigned. Rather than pointing to a function elsewhere, the code merely defines a nameless (anonymous) function of the required type right there as part of the assignment.
This is an anonymous function, a define-and-forget piece of code assigned to a variable of function type. It allows us to plug short snippets of code into existing classes without the inconvenience of having to fill up our source code file with lots of fully fleshed out functions
Anonymous functions are ideal for event handling, and as GUIs rely heavily on event callbacks you can perhaps imagine just how useful they can be.
We’ve spent a lot of time dealing with the nitty-gritty of functions over the last few sections. Time for a shift in focus, I think. In the next section we’ll zoom out to a higher level, looking at how multiple inheritance works and learning about that caveat which was mentioned a few pages back.
Access modifiers (public, protected, private)
To round off our look at classes we’re going to examine how to keep secrets.
Classes enable us to encapsulate related attributes and functions into self contained objects. But what makes an object truly self contained is when it can lock out third parties from its implementation detail. By making some attributes and functions public, and withholding some as private, we can effectively create a programming interface: a method of working with our class which is manageable and free from abuse.
Consider the following scenario: as part of a larger system we constructed a class which dealt with dates, but for some reason we only bothered to record the last two digits of the year. So 2005 is stored as only 05. This isn’t a problem, because the class supports a getYear() function which adds on 2000 before it returns a result. Then our boss comes to see us and explains the system is being expanded to deal with data from as far back as 1995 – time to change our class to store dates as four digits. But as soon as we publish the change a fellow programmer from another part of the team complains we’re making his code break! Extensively throughout his code he was reading the year directly, not bothering with our getYear() function, and so what we assumed would be a minor change is now a major headache.
What we need is some way to lock other programmers out of the implementation detail of our code, to effectively mark parts of the code as â€œno goâ€ areas, and force everyone to use a class the way we intended it to be used. Access modifiers provide just such a mechanism.
The class above is only the first part of our example code; it acts as the basis for testing the various access modifiers, which we’ll do in further examples below. We can see quite clearly there are three new keywords in play: private, protected and public – these are the modifiers, but what do they mean?
- At one extreme end of the visibility scale we have public. Any function or attribute defined with a public prefix (access modifier) is visible to the whole world. Anyone can call said function, and anyone read or write said attribute. In the example above, attr4 and func4() have no access restrictions, because they are public.
- The next level up is protected, which affords some degree of privacy. Only code from the same package, or subclasses, can see functions or attributes prefixed as protected. In our example attr3 and func3() can only be seen by other code inside the package jfxia.chapter3, or by any class from any package which subclasses ClassA itself.
- The next level is the default level, sometimes called â€œpackage-privateâ€, which requires no keyword prefix. This is the same as protected, except subclasses are not included – only code from the same package can view attributes and functions with default visibility. In our example, attr2 and func2() can only be referenced by other code in the package jfxia.chapter3.
- The final, and tightest, level of visibility is private. Any attribute or function prefixed with private can only be seen by code within the same class â€“ including free roaming code which lives outside the class body itself, but in the same source file. In the above example attr1 and func1() can only be seen within the ClassA.fx source file.
All the code in the example just given would end up as part of ClassA, including the free floating stuff which is outside the actual body of the class itself. Therefore the attempts we see, underneath the class body, to access attributes and functions within ClassA all work without a hitch. But now let’s look at what happens when we try to access the attributes and functions of ClassA from different contexts. First, another class from within the same package:
This second set of code lives in the same package as ClassA, yet not all of it would compile successfully. The failing lines have been commented out, and above each line is the compiler error which caused the failure.
We can see the compiler had no problem with public, protected and default visible attributes and functions, but borks with the private class members. This is what we’d expect, as private members should not be available outside of the class itself.
Now let’s look at another bit of code, exhibiting the errors created by a different context:
Here we see what looks on the surface to be almost identical code to our second part. But there is one crucial difference: you must keep in mind this code is being compiled into an entirely different package (note the â€œbâ€ on the end of the name) so we can expect a different set of results to the previous example. Once again the failing lines have been commented out, and the error messages presented above in comments.
The code fails on everything except the public attributes and functions. The code isn’t inside ClassA, so private access is certain to fail, and it isn’t inside the same package, so protected and default access will also fail. Public, of course, will always work.
So remember the rules: public is for everyone, protected is for the package and any subclass, default (no keyword) is for the package only, and private is for the class only. By using these modifiers on our attributes and function we can create effective components which allow other programmers to interact with them through clearly defined means, and protect their inner implementation detail.
I had trouble testing access modifiers with regard to subclasses outside of their parent’s package. Trying to access attributes in the parent class, both unqualified and via the super keyword, resulted in some strange behavior. Leave the super qualifier off, and the compiler quite happily built code which referenced private and package-private fields. Put the super on, and the compiler complained of a missing symbol for the parent class. Chalk this up to preview release quirkiness, for now.
If we’d used access modifiers in the example we began with, our fellow programmer wouldn’t have been able to directly access the details of how we recorded the year in our class. Plus we’d feel confident in fixing bugs and upgrading the class internals, because we can be sure which parts of our code others can see, and which parts are under our total control.