Using Multiple Classes

In this section we will start off explaining nested classes before moving on to creating separate classes altogether. In our opinion, you should sit back and glance over the "Nested Classes" section and not get too involved with the code. Of much more interest and importance is creating individual classes and objects, which will come later in this chapter.

Nested Classes

A nested class is simply a class that is defined inside of another class, meaning that a nested class is a class that is a member of another class. A class that contains a nested class is known as the enclosing class of the nested class. Nested classes are used when their existence is dependent on another class to which there is a solid parent-child relationship. The use of nested classes can be split into two important areas: inner classes (non-static) and static nested classes.

Inner Classes

An inner class is a nested class that only exists within an instance of the enclosing class, which means by definition an inner class is a non-static nested class. To explain this effectively, we shall look at a case example. Let's say we wanted to make a program in which you created objects of type Human as well as objects of type Alien that were members of an instance of an enclosing class called Creatures. The idea is that an instance of the Creatures class is created, and in turn it will create instances of each of its nested classes. The following example, Creatures.java, is how we may implement this:

public class Creatures {     public Creatures()     {         myAlien = new Alien("Dak-Dak-DaDakDakDak");         myHuman = new Human("Hello, Bonjour, Hola");     }         public class Alien     {         public Alien(String greeting)         {             this.greeting = greeting;         }                 String greeting;     }         public class Human     {         public Human(String greeting)         {             this.greeting = greeting;         }                 String greeting;     }           public static void main(String[] args)     {         Creatures myCreatures = new Creatures();                 System.out.println("Alien says: " +             myCreatures.myAlien.greeting);         System.out.println("Human says: " +             myCreatures.myHuman.greeting);     }         Alien myAlien;     Human myHuman; }

When you compile and run this code, you should get output similar to the following screen shot.

click to expand
Figure 4-2:

In this example, we have created two inner classes: Alien and Human. These inner classes are non-static members of the Creatures class, which means that instances of these inner classes can only be created in association with an instance of Creatures. We create instances of the inner classes Alien and Human in the constructor of Creatures.

public Creatures() {     myAlien = new Alien("Dak-Dak-DaDakDakDak");     myHuman = new Human("Hello, Bonjour, Hola"); }

This is perfectly valid because we are inside the object (an instance of the Creatures class) in its constructor and are therefore able to access non-static instance members of Creatures, like myAlien and myHuman. The inner classes have complete access to other non-static members of the enclosing class Creatures. For example, in the inner class Human, you could make a method to check if the variable myAlien references an instance of the inner class Alien, as follows:

public class Human {     public Human(String greeting)     {         this.greeting = greeting;     }       public boolean hasFriend()     {         if(myAlien==null)             return false;         else             return true;     }           String greeting; }

Then in main, the following code could be added to check if myAlien references an object.

if(myCreatures.myHuman.hasFriend())     System.out.println("We have visitors, set your weapons to stun"); else     System.out.println("Of course, earth is the center of the        universe");

Note we could also just use the following if statement in main to achieve the same result.

if(myCreatures.myAlien==null)

If we wanted to create an instance of, say, the Alien class from outside an instance of Creatures (for example, in the main method), it would need to be created in association with an instance of Creatures. We could do this, as follows, in main:

public static void main(String[] args) {     Creatures myCreatures = new Creatures();     Creatures.Alien martian =          myCreatures.new Alien("DakDakDak"); }

The code myCreatures.new Alien is the same as using the code new Alien from inside an instance of Creatures like myCreatures, so we need to do this when we are in the static method main. However, the new Alien object referenced by martian still maintains a reference to its enclosing class (i.e., the object referenced by myCreatures, as it can still access non-static members of its enclosing class). This means that if we later set myCreatures to null, hence losing this reference to that object, the inner class instance referenced by martian will still maintain a reference to it, meaning the garbage collector cannot yet destroy this data, and the data still exists in memory for the inner class to access. Garbage collection is discussed in Chapter 12.

If we want to create instances of the Alien and Human classes from outside an instance of Creatures irrespective of the existence of a Creatures object, declare the nested classes to be static.

Static Nested Classes

Nested classes are generally made static when they have a strong relationship with the enclosing class, but their existence is independent of an instance of the enclosing class. Note that this means static nested classes cannot access non-static members of their enclosing class. Note that a static nested class does not mean that the members of the class are all static.

We might say that, for example, the Alien and Human classes are strongly related to the Creatures class, but it should also be possible to treat them independently from the Creatures class. This would mean that, from outside the Creatures class when using the Alien and Human classes, the word Creatures would act almost as a namespace for the Alien and Human classes. A namespace is simply a keyword that is used for grouping data, although in Java the best technique for grouping classes is using packages, which we will discuss in the next chapter.

The following code is an adapted version of the previous example, Creatures.java, where we define the nested classes Alien and Human as static. This means that we are able to create instances of these classes irrespective of an existing instance of the Creatures class.

public class Creatures {     public static class Alien     {         public Alien(String greeting)         {             this.greeting = greeting;         }                 String greeting;     }         public static class Human     {         public Human(String greeting)         {             this.greeting = greeting;         }                 String greeting;     }           public static void main(String[] args)     {         Alien myAlien = new Alien("Dak-Dak-DaDakDakDak");         Human myHuman = new Human("Hello, Bonjour, Hola");                 System.out.println("Alien says: " + myAlien.greeting);         System.out.println("Human says: " + myHuman.greeting);     } }

This code will create the same output as the previous example seen in Figure 4-2.

Here we create a new instance of each of the nested classes, Alien and Human, which are now static and accessible without creating an instance of Creatures. However, it is important to note that we are creating instances of these nested classes from main, which is still a member of Creatures, static or not. If the Creatures class were a separate class from the main class of which main is a member, we would access the nested classes as members of the Creatures class, as follows.

//   when inside the Creatures class Alien myAlien = new Alien("Dak-Dak-DaDakDakDak");     //   when outside the creatures class Creatures.Alien myAlien =      new Creatures.Alien("Dak-Dak-DaDakDakDak");

The use of nested classes is a design issue for the structure and reusability of your code, which is not that important if you are just making a simple game but is important if you are building up a games library of related classes.

You should also be aware that a nested class could itself contain a nested class.

class TopLevelClass {     class NestedClass     {         class NestedNestedClass         {         }     } } 

A class that contains a nested class but itself is not nested inside another class is known as the top-level class. We're getting a little complex now and should move on, as you will no doubt want to make classes that are independent of the main class.

Multiple Classes

When programming in Java, all completely independent classes should be entered into separate source files similar to how we have been entering the main class's source code so far in this book—into files of the same name as the class itself with a ".java" file extension. If you are a procedural programmer, this may seem quite strange, but give it time, you will soon see how neat and tidy it makes your code. Gone are the days of bunging up one main file with relatively unrelated methods that would be better placed in libraries for further use.

Using separate files for classes makes your project easier to design, as with object-oriented programming in general. Splitting a project up into smaller modules and then dealing with these modules is more efficient, especially for larger projects, and it is also easier for handling the code in a programming team.

So let's take the Alien class and put it into its own source file. We will remove the Human class completely for the time being to keep it as simple as possible, although we will bring it back when we look at inheritance later. In the upcoming example we will also change the name of our main class to MainApp and include another class named Universe as a more appropriate container for storing references to instances of Alien than Creatures in order to save confusion later when we look at inheritance and polymorphism with a Creature class. You should add all of the source files into the same directory as the main class.

The following example contains three classes—MainApp, Universe, and Alien—to be entered into the source files MainApp.java, Universe.java, and Alien.java, respectively. Simply copy these, compile, and run the main application, just like you have been doing. You need to compile the main file MainApp.java and then the compiler will compile the rest of the files that are used in the project automatically and produce a .class file for each of your classes used from there. You can then run the main class as you have been doing so far with the single class projects so far in the book. The other .class files used should also be loaded. Here is the code.

Code Listing 4-1: Alien.java

start example
public class Alien {     public Alien(String greeting)     {         setGreeting(greeting);     }         public void setGreeting(String greeting)     {         this.greeting = greeting;     }         public String getGreeting()     {         return greeting;     }          public String toString()     {         return greeting;     }            private String greeting; }
end example

The Alien class represents an Alien object and is used to store a string value for a greeting from an Alien object. Here we are introduced to two important aspects of OOP in Java: the keyword private and the toString method. We will discuss these new features after the rest of the code listings for this example.

Code Listing 4-2: Universe.java

start example
public class Universe {     public Universe(int alienTotal)     {         alienList = new Alien[alienTotal];            }          public boolean setAlien(int index, Alien alien)     {         if(index>=0 && index < alienList.length)         {             alienList[index] = alien;             return true;         }         else return false;     }         public Alien getAlien(int index)     {         if(index >=0 && index < alienList.length)             return alienList[index];         else return null;     }          public String toString()     {         String str = "Total aliens: " + alienList.length + "\n";         for(int i=0; i< alienList.length; i++)             str += "Alien " + i + ": " + alienList[i] + "\n";         return str;     }         private Alien[] alienList; } 
end example

An instance of the Universe class is used to store an array of a specified number of Alien objects. This number is passed as an argument to the constructor where a new array of type Alien is created of the specified length. Again, this class includes the keyword private and also the toString method, which will be discussed in a moment.

Code Listing 4-3: MainApp.java

start example
public class MainApp {     public static void main(String[] args)     {         Universe universe = new Universe(4);         universe.setAlien(0, new Alien("Dak-Dak-DaDakDakDak"));         universe.setAlien(1, new Alien("Hi I'm from Pluto"));         universe.setAlien(2, new Alien("But I'm from Pluto, I've             never seen you there"));         universe.setAlien(3, new Alien("Well I'm from Jupiter, I'll             eat you all :)"));                 System.out.println(universe);     } }
end example

When you compile MainApp.java and then run MainApp.class, you should get output similar to the following screen shot.

click to expand
Figure 4-3:

The main class MainApp first creates an instance of Universe, referenced by universe, passing the value 4 to the Universe constructor, which will in turn create an array of type Alien and length 4 inside the universe object, as you can see in the Universe class. We then create four new Alien objects, setting each of them to a specific array element index using the method setAlien of the universe object, which takes the index and Alien object reference as its parameters. This is an example of using the Alien class completely separated from the Universe class; we could simply make Alien objects in MainApp that have nothing to do with the Universe class whatsoever if we desired. We could have a SolarSystem class instead of a Universe class and create more than one SolarSystem object, to which Alien objects could be moved from one to another, make some Planet objects inside each SolarSystem object, and so on.

Anyway, the last bit of code in MainApp may seem quite strange, where we pass the reference universe to System.out.println, and then all of the relative information about the data in our program is printed to the screen. This will be explained in due time after we first look at the importance of the keyword private.

Controlling Data Access (Public, Protected, Private)

In the previous example, we used the keyword private for the very first time, both in the Universe class for the array variable alienList and in the Alien class for the variable greeting. The keyword private in this instance was used to restrict access from outside the respective classes. This allows you to add security to members of your classes, with any access to the data being handled using public methods in the class, which you make yourself. We have been using the keyword public throughout the book so far, which specifies that access be allowed from any other classes. On the other end of the scale, the keyword private restricts access from any other classes to that specific data, even from subclasses (subclasses are explained later in the "Inheritance" section). The keyword protected is somewhere between public and private access attributes.

There are actually four access attributes in Java: public, private, protected, and the default access attribute that is used when no access attribute keyword is specified. The default access attribute is very similar to being public but not identical, as we shall see below.

The following diagram shows the members of class A in package A that are accessible from a variety of other classes in various situations in respect to class A, based on the access attribute of a member of class A. You may not yet be familiar with some of these circumstances, such as packages and subclasses, but do not worry about this for now, as these will become familiar later in this chapter and in the next chapter. This diagram can act as a reference in the future when choosing your access attributes.

click to expand
Figure 4-4:

This diagram shows that the public access attribute means that public data is accessible from any class, anywhere. The protected access attribute means that access is allowed from any class inside the same package as class A and any subclasses of class A anywhere. The private access attribute means that access is restricted from anywhere outside of class A. "No attribute" simply means that an access attribute is not specified, which means that access is only allowed from classes in the same package as class A. Note that this diagram shows access levels from various classes in respect to class A members. For example, class A might contain the method doSomething, which has the access attribute protected, as follows:

public class A {     protected void doSomething()     {     } }

This simply means that only classes in the same package as class A can access the method doSomething; any attempts from classes outside of package A where class A is in your code will cause a compiler error.

Why Use Access Attributes?

Access attributes are important for controlling access to data, which is important in many respects, not only for the security of your code but for preserving the efficiency too. An example of where you may want to prevent access to members of your classes is so that someone else using your class cannot manipulate the internal variables that make your class what it is. If you make these variables inaccessible, they can only influence changes in those variables via accessible methods that are safe because you have created them yourself, which means you can control what is done with your classes. This is not only suitable for other people using your classes but for you too. If you use methods to manipulate variables of a class, this is already efficient because it is easy to see where access is coming from, meaning it is easy to update code and chase bugs. It is perfectly fine to ignore the access attributes when you are knuckling down hacking away at your code, but it can vastly neaten your code and make it efficient and friendly.

The Static Block

Imagine the scenario: You declare a static variable member of a class, which needs to be assigned its value straight away when the class is loaded, but the code to create this assignment value cannot be made in an assignment statement alone. For example, the assignment value may require repetitive calculations, or it just might be neater on multiple lines. In this case, you can use a static block, which can be added to your code as follows:

public class MyClass {     public static int[] squares = new int[10];         static     {         //   values assigned         for(int i=0; i<squares.length; i++)             squares[i] = i*i;     } }
Note 

The reason we have declared the static block after the variable declaration of squares is because they are dealt with on a line-by-line basis when the class is first loaded to the Java Virtual Machine. If they were the other way around, the static block would not recognize the variable squares yet.

Here we have a static array of type int and length 10. The array is initialized when the class is loaded. In order to actually define specified values for the elements of this array, we could have used the initialization block using curly brackets as follows:

public static int[] squares = {0, 1, 4, 9, 16, 25};

As you can see, entering many values is unsuitable when a simple algorithm could equally enter the code. This is why we can use a static block to perform this code. We could also have initialized the array in the static block and not at the declaration point.

public class MyClass {     public static int[] squares;         static     {         // initialized         squares = new int[10];             // values assigned         for(int i=0; i<squares.length; i++)             squares[i] = i*i;     } }
Note 

Remember when we discussed the use of final variables in Chapter 2, where we mentioned declaring final variables without a value and then assigning the value in one of the class constructors later on? Well, similarly, a static final value does not need to be assigned immediately at the declaration point but can also be assigned in a static block.



Java 1.4 Game Programming
Java 1.4 Game Programming (Wordware Game and Graphics Library)
ISBN: 1556229631
EAN: 2147483647
Year: 2003
Pages: 237

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net