![]() | Monkeys at Keyboards: The Javanomicon © Michael James Heron | ||||
| Topic: Java Programming Level: 2 Version: delta | |||||
32 - Odds and Ends | |||||
| Previous | Table of Contents | Next |
| Forum |
| Chapter Objectives |
By the end of this chapter, the reader will be able to:
|
We're coming to the end of our study of the Java programming language in this book - we've really gone as far as we can without beginning to plumb the depths of some very complex subjects, and those areas are worthy of investigation in their own right rather than as an aside in this particular text. In this chapter, we'll look at pulling everything together and presenting the fruits of our labour as a finished product. After all, outside of some open source projects, you rarely obtain a piece of software purely as a collection of source files. You expect documentation, and you expect to be able to run the program with a click of the mouse. It should be the same with our Java programs. Sometimes however, we are not producing software as a finished application - sometimes we are developing class libraries to be used in other applications. In this case, there are also some expectations regarding documentation, and we should ensure that any code we are providing for consumption by others is presented in the best possible light. In this chapter, we're going to look at packages, and how we can use them to group together conceptually related classes for easy access. We are going to look at the jar archive format, and how we can use it to provide Java applications as executable products, and we are going to look at javadoc to see how we can use Java's powerful commenting tools to develop useful documentation in the same style as that provided by the Java API library itself. We're also going to look at a few tools that didn't fit into the text of any of the previous chapters, but are still important enough to warrant a discussion.
First of all, we'll look at providing our code as a package for other developers. We've made use of packages all the way through our development as Java programmers - this is what the import statement at the top of our code does. Packages are Java's way of linking together groups of classes that have some architectural or conceptual relationship. So for example, all of the classes that deal file with File IO are located in a package called java.io, which we must import before we have access to the necessary classes. When we've written multi-class applications in previous chapters, Java has always looked in the working directory for the appropriate classes being used within our code - as long as the compiled class files were all stored in the same directory, our code would compile quite happily without prompting a 'class not found' error. But consider some of the applications we've written up until now - many of them have been written with the idea of reusability in mind. Our password applet involved making use of many separate classes that encapsulated each of the units of functionality. It's perfectly possible that we'd like to make use of any one of these classes in another application we write, to save us implementing all the code again. Likewise, our recursive formula parser is a powerful piece of code, and one that we may want to make use of in another application - imagine writing a piece of software that allowed the user to define their own formula, for example. In such a scenario, we could make good use of our parser to provide answers to complex, user-defined equations. As things currently stand, to make use of these classes in other projects we need to compile their class files into the working directory for a project. This is very wasteful and undermines the idea of reusability. If we have ten projects using SomeClass.class, then if someone comes along and improves the code for SomeClass, then we need to track down all the projects using it and replace their old class file with the updated version: very tiresome. Packages allow us to solve this problem by indicating that a particular class belongs to a certain package. We can then import that package into our own project ensuring that any changes in the classes we use are automatically reflected in our applications and applets - the class files are stored centrally, and so when the code is recompiled all of the projects making use of that package class will have an updated version available automatically. The first step of this process is to indicate membership of a particular class within a given package. We use the package keyword to do this. The convention is that packages are indicated in all lower case letters:
In order for a class to be made use of within a package, it must be declared as public, so any internal classes (those declared as private or protected) are not going to be accessible to external projects, even if they are part of an imported package. Once we have declared membership of a package, we need to ensure that Java knows where to find the relevant class files. We must create a folder that matches the name of the package - there is a direct mapping between package name and folder in the same way there is a direct mapping between the code filename and class name - Java uses the package name to locate class files within a directory structure. It doesn't matter where we create this directory, we'll look at how we can point out its location to Java in a few moments. Once we've created the directory, we compile our java code into it to create the class file. It is only the class file that Java uses for the packages, so that is all that has to be present - you don't need to include the .java file in the directory, although for maintainability reasons it can be good practise to do so. Finally, we need to point our new directory out to Java, so that it knows where on our system it has to look. How this is done will vary from system to system - in this book we will only look at how we can do this within the JCreator IDE. JCreator maintains a list of directories that are of interest to it for the purposes of finding particular classes and packages. We can add an entry to the list of directories by navigating to configure -> options -> JDK Profiles, which brings up the following dialog box. Depending on the exact platform, what you see may be slightly different: In order for a class to be made use of within a package, it must be declared as public, so any internal classes (those declared as private or protected) are not going to be accessible to external projects, even if they are part of an imported package. Once we have declared membership of a package, we need to ensure that Java knows where to find the relevant class files. We must create a folder that matches the name of the package - there is a direct mapping between package name and folder in the same way there is a direct mapping between the code filename and class name - Java uses the package name to locate class files within a directory structure. It doesn't matter where we create this directory, we'll look at how we can point out its location to Java in a few moments. Once we've created the directory, we compile our java code into it to create the class file. It is only the class file that Java uses for the packages, so that is all that has to be present - you don't need to include the .java file in the directory, although for maintainability reasons it can be good practise to do so. Finally, we need to point our new directory out to Java, so that it knows where on our system it has to look. How this is done will vary from system to system - in this book we will only look at how we can do this within the JCreator IDE. JCreator maintains a list of directories that are of interest to it for the purposes of finding particular classes and packages. We can add an entry to the list of directories by navigating to configure -> options -> JDK Profiles, which brings up the following dialog box. Depending on the exact platform, what you see may be slightly different:
The window on the right will display all the installed versions of the Java development kit that are recognised by JCreator. Typically there will be only one - if there is more you will need to determine which of these is being used predominantly by your compiler. Click on the main JDK profile and then press the edit button to bring up another window. Click on add and add path, and then navigate to the directory that contains the directory you created for your packages:
Now, you can import your new package into your code the same way that you can import the standard Java packages. For a package in a directory called myexamples:
This will be a package available to any Java program you write in JCreator from this point onwards, and any class you add into the directory will be automatically available provided it defines itself as belonging to the appropriate package. There are a range of naming conventions concerning exactly how you define the name and directory structure of a particular package - the interested student is directed to the further reading section of this chapter for the official Sun standard. For more complex directory structures, we use the dot notation to indicate subdirectories - we don't need to add new directories to JCreator as indicated above. The directories we add to JCreator are base directories, within which JCreator looks for packages. When it finds a dot in the package name, it interprets it as a subdirectory:
We would compile this class into a subdirectory of the myexamples package directory - as indicated in the package name, this subdirectory would be named extended. We can then import this subpackage with the following statement:
Every dot indicates a further subdirectory - we can continue to extend these indefinitely.
Now that we've seen how to provide our classes as part of a package, we should look at how we can make them look as professional as the standard Java classes - a large part of the 'sparkle' of the Java programming language comes down to the documentation that goes along with the class. Many programming languages provide little or no internal documentation, which makes it difficult for programmers to find solutions to syntax problems, or to investigate the tools available within a particular language framework. The default library of classes provided by the Java programming language is known as its API, which is an acronym for 'Application Programming Interface'. The Java API documentation is contained within a series of HTML documents that include all the methods of a class, its parents, and its attributes. Each piece of information comes with a description and other useful titbits. You don't need to actually write your own HTML files to provide this information for your own classes - Java provides a framework called javadoc that allows you to define the documentation for a class in comments that are stored along with its code. We can then make use of an automated HTML generator (provided as part of the Java development kit) that goes over each file and picks up the comments before converting them into the accompanying HTML files complete with all the peripheral infrastructure (for example, an index file) for browsing. There really is no excuse for not commenting your classes and provided accompanying documentation when Sun Microsystems have gone to all this trouble for you! We'll look at writing comments for individual methods first. Javadoc requires that comments be written in a very particular format, but this is not especially different from the standard comments we've seen up until this point. A Javadoc comment starts with the following symbol on a line by itself:
Every subsequent line of the comment must begin with a *. In later versions of the JDK, this is not the case - but it is good practise to indicate the commenting format you are making use of, so we'll pretend like it is an incontrovertible commandment of the Java language. The comments themselves are going to be rendered into HTML by the javadoc generation tool, so you can make use of HTML control codes for presentation reasons if required:
Or you can just use plain text:
We can continue extending the comment as far as we like - this is the wording that will form the bulk of the descriptive text for the comment in the HTML file for the class. Once we're done with describing what the class does, we can make use of some special javadoc tags to indicate particular kinds of information:
Then, when we're done writing our comment and we've included all the relevant tags, we close off the comment with the following symbol on a line of its own:
So, for example - a javadoc comment for a trivial method:
Having gone to all this effort to write useful and complete comments, we can then make use of Java's HTML generator to turn our hard-work into a useful HTML document. The tool to do this is called javadoc which must be invoked from the command line, like so:
Invoking the javadoc command will create a number of different HTML files, the most important of which is index.html. Opening this up in a browser will bring up a familiar looking display - it's the same one that is used in the standard Java API documentation. We can then provide these HTML files along with our class for a fully searchable documentation package. Each individual method should be documented, as well as the class itself. The standard for documenting the class is the same as for individual methods, but it should also declare an @author tag (or more than one) so that the person responsible for the development can be identified (or blamed):
The first sentence of each comment is used as the summary - a sentence is indicated by a period, exclamation mark or question mark. The rest of the descriptive text is used as the full text for the documentation of that particular class or method:
Above we looked at using packages as a way of linking together groups of contextually related classes. However, this doesn't help us distribute our code to anyone who doesn't have direct access to our hard-drive. It is however possible to develop a package structure that does make it simple for people to make use of our classes, but we won't look at how that can be done in this book. Instead, we'll look at how we can make use of the JAR archive format to aid in distribution of our classes. The JAR (Java Archive) format allows you to bundle together multiple files into a single compressed entity. Usually a JAR file contains a number of class files, along with any resources that are required for the classes to function. JAR is really just a specialisation of the standard zip format, and works through a tool provided as part of the Java SDK - this tool is called (funnily enough) jar, and is accessed from the command line the same way that we access the javadoc tool. Creating a JAR file is a simple case of using the jar tool to specify an archive name and a selection of files to be added. The jar tool is command line and allows for a number of flags to be specified. The most common and useful of these are listed below:
So for example, to create a named jar file from all of the files that match a particular wildcard:
We can pass any number of arguments for the files to be added:
This then creates a single entity (myArchive.jar) that contains all of the input files. Later we can extract the files using a slightly different format of the command:
However, using JAR as a simple substitute for ZIP misses out on some of its major benefits - one of these is that we can JAR up a number of files associated with a package, and then use that JAR file as a package location through JCreator (or any other IDE). We can do this using the same process as we went through for adding a class directory, with a small different. Rather than add path when we choose a new directory for the JDK profile, we choose add file and navigate to the JAR file. From that point on, the package is available just as if it were stored in a directory on our hard drive, and we can happily import as necessary. So for distributing a package of our own to other developers, we can define all the classes as belonging to a particular package and then jar everything up into a single file. We can then send this on to our colleagues, and they can incorporate our jar file into their projects (using whatever method is appropriate if they are not making use of JCreator). A simple import statement from that point on will make the classes of package available to them. We can even run an Applet from a JAR file, although this requires a slightly different syntax for our HTML page. We need to indicate the archive file to be used when searching for the applet code, like so:
As mentioned in the last chapter, making use of the JAR format is an excellent way of reducing the size of your applets - by providing all the code in JAR format, users make use of the compressed format rather than download each of the uncompressed class files. This greatly decreases the amount of time that a user needs to spend downloading.
This is all very useful from a developer's perspective, but if we're providing an application that people should be able to use outside of a development environment, we're going to have to do something a little extra. You wouldn't want to have your grandmother learn how to use JCreator purely in order to use the recipe generator that you wrote for her, after all. Luckily, JAR files allow for us to indicate that their contents should be executable... in effect, they can work exactly the same way as a standard exe file. This works on the principle of a manifest file that indicates which of the files contained with the jar file contains the main method for the application. A manifest file is simply a text file that follows a certain layout. In its simplest format, all it requires is the name of the main class (just the name, no .class extension), given in the following format:
We create an executable JAR file by providing a manifest file when we create the archive. We also have to use the flag m, which specifies that we will be making use of the manifest file for this particular jar operation. If we have the manifest file stored as manifest.txt, we'd use the following syntax:
Let's look at this idea in practise by making our Maths Tester (from the last case study) into an executable archive. Our Math Tester has three classes... we don't need the Java files, we only need these .class files:
Of these three, only the MathsTester class has a main method, and so that is the Main-Class for our manifest:
We create this as a text file called manifest.txt, then, we use the jar tool to create our executable archive:
The tool works for a few seconds and then produces a file called MathsTesterApplication.jar. We can double click on this jar file, and we get a fully working version of our application without having to open it up in a development environment. Huzzah! There is much more than this that can be done with the manifest file - we can do more than simply indicate a main class for the purposes of creating an executable. The interested reader is directed to Sun's website (which is listed in the further reading section).
Although javadoc provides a very useful way of providing API information regarding the classes you have written, it is only one of the pieces of documentation you should provide if you wish to make your efforts as appreciated as they can be by other developers. The actual documentation you provide will vary depending on what license under which you release your code - this is a dense legal topic that requires considerable thought before you commit. At the moment it is unlikely to be an issue unless you have a genuinely substantive piece of work that you are offering for real world applications... but there's no reason to believe that won't be the case. You now have all the tools necessary to begin developing complex and sophisticated programs and class libraries. As a general rule, unless you are planning to make money from your efforts, there is extra documentation you can provide that makes it easier for fellow developers to use your code. If you are making a commercial release, the dictates of the market will put other constraints on what documentation you provide. For example, you don't get a copy of the doc format specification when you buy a copy of Microsoft Word. You should however consider including some (or indeed, all) of the following pieces of documentation:
Even if you are simply putting the finishing touches to code for your own personal use, it is an exceptionally good idea to document all of this. In a year's time when you come back to try and change your code, will you really remember the format in which your random access files are stored? Sure, you could read through the code and work it out - but why should you? Simply noting the type, size and order of elements in a record will be enough to provide valuable 'at a glance' information to anyone who needs to understand your data format for the purposes of expansion or modification. Within your file specification, give the type of data, the size, and what that data represents. For example:
Obviously this is something you need to do only for data formats you define yourself. If you are making use of an already existing format, you should simply indicate where the information regarding that standard may be found (a book reference or URL). Remember that documentation is an aid to understanding, not an end in and of itself. Any documentation that you feel will aid people (or yourself) in understanding the code you have written should be included along with the application itself. For complex applications, you may even think about writing a user/developer manual and tutorial. However, remember that more documentation doesn't necessarily mean more understanding. Including the right documentation is far more important than simply providing lots of documentation. In fact, it is often counter productive to provide too much documentation if it makes it more difficult to find those pieces of information in which people are likely to be interested.
There are many structured development philosophies that explain how software can be written - things such as the waterfall method define, in exhaustive detail, the process that any formally developed piece of software will go through. As individual developers working on our own, we tend to adopt an ad-hoc approach to writing programs. We change and revise as we write - we alter sections of code on a whim and then spend hours fixing the things that we broke in the process. We leave the documentation until the last minute and thus make it a massive chore that takes up all available time simply because we really don't have the enthusiasm to do it at all. Eventually, there comes a point when you think to yourself: 'Ah, my hard labour is done, and now I can recline on my throne of greatness whilst scantily clad servants feed me peeled grapes and drip beads of sweet nectar down my throat as I slumber'. The question is really 'when is my code completed?'. The answer is somewhat depressing - a piece of code is never complete. The best you can hope for is complete enough. Before you get to the point where you are considering an application to be completed, check it off each of the criteria on the following checklist. Only if you've completed each of these stages to a satisfactory degree should you think about considering your back-breaking labour to be at an end:
If you can't tick off all of these requirements, you still have some work to do before you can consider your job done - even if the code you have written meets all other requirements and works perfectly! Remember, what you can get away with as a beginning developer is not what you'll be able to get away with in the workplace. It's never too early to adhere to good practise.
Way back in chapter two we looked at some of the operators provided by Java. We ignored a whole family of operators because of their specialised functionality and relative complexity. These are known as bitwise operators and work on the individual bits within a variable. Remember the code snippet from the last chapter:
That's not a typo - the operator used there has only one ampersand symbol. This is a bitwise and operator. The operators we're used to are called logical operators. This piece of code is an example of 'being too clever for your own good'. It sets up an integer variable of the value 16, which is represented as the following in binary:
It then applies a bitwise comparison to each of the bits in the integer. It then returns whatever the result of the bitwise comparison was. In this case, it sets a bit to 1 if and only if the bits in both binary representations are one at a particular location. So, for the above comparsion:
The result of the bitwise operation is 0. If the comparison was instead :
Then we'd see:
The result for this would be 16. We have a similar bitwise operator: |. This is an or operation. The above two comparisons using this operator would be:
The bitwise or comparison of 16 and one gives a total of 17. The bitwise or comparison of 16 and 16 gives 16:
The third of these is the exclusive or, and that will set a bit to one if the first bit or the second bit are one, but not if both of them are. The symbol for this is ^. So for 16 ^ 1:
The exclusive or comparison of 16 and 1 gives 17, just like the or operator. The exclusive or comparison of 16 and 16 on the other hand gives 0:
You might be asking yourself at this point 'Well, why should I care?'. That's a fair question considering the obscure nature of the operators. The reasons you should care are twofold. For one, you might very well encounter these operators in the code written by others, and you should be aware of what they do. For another, they are a useful way to compress a series of boolean values down into a very compact representation... this is something that makes compression of large two state data-sets into a breeze. The example we gave in the last chapter, whilst not easy to read, is a very compressed way of representing the state of a whole integer's worth of boolean variables. Usually we want to be able to limit how many such variables we want to send - there's no point storing a whole integer worth of binary bits if all we need are four. Usually such operations are performed on primitive byte data types. Bytes, as their name suggests, are only eight binary bits long. They can hold seven binary numbers to represent their contents - the eighth is used to indicate the sign of the number contained. The number contained in the 128 column represents the sign of the number - a byte can therefore store numbers in the range of -127 to 127. That's not much use for storing large numbers, but it's just perfect for storing the state of seven boolean variables if the compactness of the representation is an issue.
And so our discussion of Java as a programming language ends. We're not done with the book yet - we're going to look at another example of building a Java application amongst other things, but we've covered all the syntax and concepts that relate specifically to Java. You've come a very long way since I met you as fresh faced young novices, eager to sup at the fountain of Java's goodness. The range of applications you are now equipped to write is simply mind-boggling. Although there are some specialised topics that require further study (such as networking, client-server programming, multi-threading and certain aspects of Java's object library), you have all the tools you need to write a vast range of programs - a plethora of cornucopias are yours for the taking! The only difficulty that now faces you is gaining experience - not just so that you can become better coders, but so that you can actually appreciate the startling diversity of applications you are capable writing. From this point on, the problem isn't with knowledge - the problem is with confidence. Now is the time to start being adventurous. Don't shirk back and say 'that sounds like something that is too hard for me to code'... instead, embrace it as a challenge and consider how you can apply your expertise to meet that challenge. Appendix 2 (Code Challenges) lists some difficult problems that you are now well equipped to solve, even if you don't believe that to begin with. You are also now well equipped to start producing Java projects that can be used as actual applications, as well as class libraries that can be used by other developers. Getting together with a few interested friends and working together is a great way of refining your skills and learning some interesting tricks along the way... or even passing some of your own knowledge along to others! Further ReadingThe following table details further reading on the topic in this chapter, and also any external resources that you may find useful.
|
| Previous | Table of Contents | Next |
© 2004-2006 Michael James Heron