Chapter:Classes I

From Juneday education
Jump to: navigation, search

THIS PAGE IS REPLACED BY:

Meta information about this chapter

Expand using link to the right to see the full content.

Introduction

Classes are of course a very central topic in a book on Java and Java programming. It is, in fact, one of the larger topics and very heavy on syntax and semantics. We want to cover the topic on classes in its entirety but will will focus on the basics and just touch upon the heavier and more advanced stuff like nested and inner classes.

The focus of this chapter is to teach how to write your own classes and we start off by talking about the syntactic layout of class source code files. After this, we zoom in on stuff like variables (instance variables and class variables), constructors and methods (instance and class methods).

The idea is to give the students an understanding of how to declare a class, what are the different parts of a class, i.e. what declarations can exist in the class block, and an introduction to the declaration and function of those parts.

Purpose

The purpose of including this chapter is to give the students the basic knowledge needed to construct classes of their own. This is so essential to Java programming, that without this knowledge, very little can be done. After learning how to use someone else's classes to construct objects and put those objects to work, it is now time to provide the tools for knowing how to create classes from scratch (for use by the student's own programs or for use by other people).

Goal

The student shall after completing this chapter be able to:

  • Understand the basic building blocks of classes:
    • variables (instance and static)
    • constructors
    • methods (instance and static)
  • Construct basic classes containing the basic building blocks
  • Recognize the most basic Java keywords and understand the contexts where they can occur
  • Understand the concept of access modifiers (at least private and public)
  • Declare and use:
    • Variables (instance and static) and constants
    • Constructors
    • Methods (instance and class)
  • Understand the next chapters about inheritance and interfaces

Instructions to the teacher

Common problems

It's important that the student understands the difference between classes and objects. Compiled java source code files are called class files, which can be confusing unless this is addressed. Talking about the source code for a class and the class as seen by the JVM in runtime can be very confusing, since it is the class code the students are used to reading and writing, while a runtime class acts both as a type and API towards objects. Also, it is important to remind the student that a reference variable may refer to an object of some class or null. We want to stress once again that what we say has a great impact on what the students understand. Even if it becomes tedious and bureaucratic to say stuf like "toString() returns a reference to a new String with a String representation of the ojbect", this is in our experience something which the students (and sometimes even more experienced programmers) need to hear over an over again. It doesn't hurt to remind the students of this relation from time to time.

Another thing to think about is that an object is the same as an instance of a class. When alternating by calling the object "an object" and "an instance", it is not wrong to remind the students that objects and instances are synonyms in Java.

One big issue when learning programming and Java is to mentally switch between what happens in compile time and what happens in runtime. A problem for us teachers, is that we often show source code and ask questions like: "What happens when this line of code executes?". Even though we know that the Java source code resides in a class in a source code file which will be compiled to byte code in a class file, and only at runtime will that class eventually be loaded by the JVM, which in turn at some time will execute the code we were talking about, this is something we sometimes forget to point out to the students. The danger with this, of course, is that the students may subconsciously get the idea that the source code will be interpreted as it is. This, in turn, may lead to confusion about the difference between compile time (which is where we are when we look at source code) and runtime, because the code looks the same when the teacher is talking about it!

An additional potential problem with understanding all this, is polymorphism in the sense that a reference variable has the compile time type "Reference to some class" when the actual type of the object referred to at some point during runtime may have the type of a subclass to the reference type. This will be addressed in the chapters about inheritance and interfaces, but it is worth thinking about even in earlier chapters, because it is in early chapters we have the chance to lay the foundation for understanding compile time, runtime, compile time type, runtime type. The point here is that if you don't understand the difference between compile time and runtime (or maybe even also design time), it will be very hard to grasp the concept of polymorphism when it's time for that.

Understanding the difference between static members and instance members is often hard for students. First it is hard to understand what the semantic difference is, and the difference in runtime. Second, once the students understand the implication of a variable or method being static, they are faced with knowing when to choose "instance or static". It isn't obvious for new students (or sometimes even experienced programmers) to motivate when in particular a method should be static and when it shouldn't. It doesn't help that many books (for more or less natural reasons) start with the main method as a self-contained program, only to move on to refactoring the program by factoring out parts of the code to static methods in the same (and only) class as the main method.

One way of explaining how to choose between static or instance context for a method is to focus on the data which the method operates on. Methods that only operate on data provided as arguments (and that have no side effects) are good candidates for being static methods. Methods whose operations depend on the state of some object are more likely to be instance methods. Now, the latter depends of course on an understanding on grouping data and behavior together as objects. So we could rephrase the second rule about instance method candidates to, "Methods that constitute the operation you can perform on an object, the messages you can send to an object, are candidates for being instance methods, because they often need access to the state of the object in order to do their work". Another possible rephrasing could be "Methods declared in the same class as the type of the object, and whose operations depend on the state of that object, must be instance methods".

Another, perhaps more simplistic, way of explaining when to use a static method is to say "when you don't need to have access to any specific object in order to run a method, this method must be static". But this is just a rephrasing of the previous statement. The central part to understand is that method which change or use the data of a specific object, as part of this objects behavior, must be an instance method. Instance methods are tied to instances, as implied by the name. They offer a way to use or change an instance's state.

Note, though, that passing a reference to an object as a parameter to a static method, which in turn uses the state of the object, could also be described as the method "depending on the state of an object". This was of course not the intent of the rule. The rule only applies to methods "declared in the same class as the type of the object".

Once the students understand that you can have many objects of the same class, and that those objects can have different state, it is easier to explain that instance methods are applied to the instances even if they are declared in the class. Static methods, on the other hand, can be called regardless of even the existence of instances, so they can't depend on instance variables or references to instances.

A particular surprising but telling example is the classic quiz question What will be the result of compiling and running the following program:

 1 public class Example{
 2   public static void main(String[] args){
 3     Example ex = null;
 4     System.out.println( aMethod(ex) );
 5   }
 6   static String aMethod(Example e){
 7     return e.aNotherMethod();
 8   }
 9   static String aNotherMethod(){
10     return "Awooga";
11   }
12 }

Many would jump to the conclusion that there will be a NullPointerException on line 7, because the parameter e is null. But static methods can be called even via Null references, since the static method doesn't need or care about dereferencing the reference variable e - it needs only to know the type of e.

We have tried to sneak in a style in previous chapters and examples which lets the main method create objects of some class, and let the objects do the work. We actually believe that merely avoiding a lot of calls to static methods from the main method helps giving the students a better preparation for understanding the difference between static methods and instance methods. It is our belief that instance methods are more common than static methods(this certainly seems to be the case, looking at the Java API) and perhaps should be merited more attention for this reason alone.

When showing calls to static methods, such as mathematical functions in java.lang.Math, it is good to make a point about those methods being static and elaborate on that. For instance one can mention that it would be tedious to have to create a Math object just to get access to the mathematical functions (which are pure functions and don't depend on any state and don't have any side effects). One can also elaborate on what it would mean if it were possible to create Math objects - could there be more than one instance of "Math"? What would that mean? This is also a good time to show that it is not possible to create Math instances. Ask the students if they can guess how the class is defined in order to prevent instantiation (the answer is that it has a private constructor of course). Later, when talking about inheritance, one could ask the students if one may extend Math. If not, how is this achieved? Repeat the meaning of finalwhen used in a class declaration.

Chapter Videos

See below for individual links to the videos.

Description

Class declaration

In the previous chapter we introduced objects and classes so we should all have a basic idea of what objects and classes are.

The chapters on classes deals with the components of a class and the source code layout for a class declaration. What parts constitutes a class? Before we answer that, we start by looking at what are the parts of a class as defined in a source code file. A Java class definition in a source code file can contain the following elements:

  • A package declaration - declares logical association expressed as a package (which corresponds to the path to the class file)
  • import statements (if needed)
  • The actual class declaration with the class name and the start of the block of code for the class
  • The variables needed to keep the state of objects created from this class (instance variables)
  • The variables common (shared, will exist in only one copy) to all instances created from this class (static variables)
  • The constructor(s) providing means to create instances from this class
  • The methods of instances of this class - what objects of this class can do
  • The methods which exist independently from instances of this class (methods which don't involve instances of this class)
  • Nested and inner classes - a class definition can actually contain other classes - this is outside of the scope for this book
  • Initializers - you can actually put blocks of code directly in the class body - this is outside of the scope for this book

We'll discuss these components one by one, in the chapters on Classes. Note that the last two components are only included here in order for us to be more complete. We don't want to hide these components for you, but we will not go into any details about the use of them in the scope of this book.

Package declaration

Video

Description

Imagine there are two classes with the same name and you want to use them both. Actually this is the case already in Java with a class called Date. In Java this is solved by adding an extra name to the class, we use the package construction. The two classes are java.util.Date and java.sql.Date and by using packages we can use them both.

Also imagine if you have a project with some thousand classes. Keeping all the corresponding Java files (the source code) in one directory would make it very hard to get an overview of the source code. Instead we split the project into smaller parts, each responsible for a part of the complete system. To achieve this we use the same construction as above.

We dare say that you will not see a program, in use by real users, that don't use packages. Not using packages is unfortunately something you will see in many books and education material. Since packages are easy to use we're going to use packages for everything we all write in the future - be it examples, exercises, assignments and exams.

A class can contain zero or one package declaration, which has to be the first statement in the source code file for the class. If the class source code doesn't have a package declaration, the class will implicitly belong to a "nameless" package, which could be thought of as "current directory". Packages correspond to the relative location of the compiled source code file. The source code is also saved in a directory structure matching the package name.

A class in the "nameless" package can only be found by the java command (actually by the JVM's so called class loader) if the command is executed in the same directory as the class file (the compiled source code file). A class in an explicit package must be compiled into a directory matching the package name.

A package normally name consists of a series of lower case parts, separated by '.' (dots) leading up to the actual class name, as in org.progund.Game, where Game is the name of the class. Each part leading up to the class name corresponds to physical directories of the same name and structure. If we look at org.progund.Game again, the source code for Game.java should be located in a directory structure as shown below:

.
`-- org
    `-- progund
        `-- Game.java

Note the dot at the top of the directory structure. This is the starting point of the relative path to Game.java. A dot signifies "current directory" in most terminals and shells. This is to say, that if we are in some directory and want to compile Game.java, and Game.java belongs to the package org.progund, we need to make sure that the class file from compiling Game.java ends up in a directory structure according to the package name. Here, the first part of the package is org. So we need to have a directory called "org". The next part is progund, which means that inside "org" we need to have a directory "progund". This means that the dots correspond to the file separator character in a path! On UNIX-like systems (like cygwin), the file separator character is "/" (forward slash). So, the compiled file for org.progund.Game must be located in the relative location org/progund/Game.class.

Now, if the source code file Game.java is in org/progund/Game.java and we are in the current directory as shown above, if we compile the file using the relative path to it:

$ javac org/progund/Game.java

Then, the compiler (if the compilation is successful of course) will put the class file in the same directory as the source code. We will then have a directory structure as shown here:

.
`-- org
    `-- progund
        |-- Game.class
        `-- Game.java

Now, let's pretend that the Game class has a main method, so that we can run it as a Java program. If we want to run it using the java command, we will actually give java the full class name (the qualified name including the package):

$ java org.progund.Game

We saw that the class file which is loaded and run by the JVM invoked by java was located in the relative path org/progund/Game.class but the argument to java was org.progund.Game. This should make it obvious that the dots in the class name corresponds completely to the directory separators!

It is OK if you keep things as described above where the class files end up in the same directory as the source code files. But in the real world, it is actually common to keep two parallel file structures; one for the source code and one for the class files:

.
|-- bin
|   `-- org
|       `-- progund
|           `-- Game.class
`-- src
    `-- org
        `-- progund
            `-- Game.java

Now we've added to extra levels; the bin directory, and the src directory. Note that these are not part of the Java world! They are just there so that we can have two identical directory structures side by side!

If we'd want to run the program using Game.class, we'd have to cd down to the bin directory, so that org.progund.Game still would translate to a valid relative path! If we cd to the bin directory, when the package name is translated to a relative path, the path would still be correct to the file org/progund/Game.class. But if we'd stayed in the directory annotated as "." in the layout above (the directory where both bin and src are located), running java org.progund.Game would yield a "Class not found" error from the java command. This is only natural, because the JVM would translate the qualified name org.progund.Game to a path for the class files org/progund/Game.class. Trying to resolve that path would fail miserably, because there was no org directory in the current directory! Only a bin directory, and a src directory.

Now, there is actually a solution to this too! Java comes with a concept called "class path". If we were in the directory above bin and src, we could actually help java find the org.progund.Game class, by giving it a hint on where to look for the org directory:

$ javac -d bin src/progund/Game.java 
$ java -cp bin org.progund.Game
Game over!
$ ls
bin  src
$ ls bin/org/progund/
Game.class
$ ls src/org/progund/
Game.java

The trick was to provide the class path using the flag -cp bin. That meant "run org.progund.Game but look in bin in order to find the class!".

Videos

Exercises - package declaration

Create the following directory structure:

.
|-- bin
|   `-- org
|       `-- progund
`-- src
    `-- org
        `-- progund

You should remain in the directory signified by "." in above. Download the Game.java source code into src/org/progund (when we say "download a file into a directory", we actually mean that you should download a file and make sure you move it to that directory). Open the file and look at the package declaration. Remember that if there is a package declaration, it must be on the first line of the source code file.

  1. What is the package name?
  2. What is the full (qualified) name of the Game class?
    Use cd to change directory down to src. Compile Game.java from this directory (you need to give javac the full path to the file Game.java!). Still in the src directory, list the contents of the relative path org/progund/.
  3. How many files is in that directory?
  4. What is the names of those files?
    Still in the src directory, run the program Game (Game has a main method!):
    $ java org.progund.Game
    
  5. What is printed to the terminal?
    Delete the class file you just created:
    $ rm org/progund/Game.class
    
    Change directory up one level:
    $ cd ..
    
    Now try this:
    $ javac -d bin src/org/progund/Game.java
    
    Note the flag -d bin! It means that javac should no longer put the class file in the same directory as the source code, but use the bin directory structure instead. Using the package declaration, javac knows that it should put the class file not only somewhere under bin but exactly in bin/org/progund.
  6. How many files are now inside the src/org/progund directory? (use ls to find out)
  7. How many files are now inside the bin/org/progund/ directory?
    Run the program inside bin/org/progund/Game.class For this, you need to use the class path flag -cp bin (since you haven't changed directory down to bin, java would never find the org directory without the -cp bin!).
  8. Did it work?

Solutions to - package declaration

Expand using link to the right to see the full content.

  1. org.progund
  2. org.progund.Game
  3. src/org/progund/ now contains two files.
  4. src/org/progund/Game.java and src/org/progund/Game.class
  5. Game over!
  6. src/org/progund/ now contains one file, Game.java. You just deleted the other one, and you told javac to put the class file in the bin directory structure instead, using the flag -d which stands for "destination".
  7. bin/org/progund/ now contains one file, Game.class. You told javac to put the class file in the bin directory structure, using the flag -d which stands for "destination". So this is the one file!
  8. Of course it worked? If not, please consult with your class mates and/or tutors.

Starting point of a program

Exercises

The starting point for a class is the main method, which has to be declared exactly as Java requires (with the exception that you may chose the name of the parameter).

We have written some exercises to let you played around with the main method and with two main methods.

  1. Write a simple class, called Game, in the package org.gamefactory.textgame. This means that you should have a directory structure as follows:
    `-- org
        `-- gamefactory
            `-- textgame
                `-- Game.java
    
    The class should have no main method.
  2. Compile and execute the class. Did it work? Why?
  3. Add a main method to the class. The main method could simply output the class' name, using println. The code looks something like this:
      public static void main(String[] args) {
        System.out.println("Game");
      }
    
  4. Compile and execute the class. Did it work? Why?
  5. Write one more simple class, called Gamer, in the package org.gamefactory.textgame. This means that you should have a directory structure as follows:
    `-- org
        `-- gamefactory
            `-- textgame
                |-- Game.java
                `-- Gamer.java
    
  6. Add a main method to the new class (Gamer). The main method could simply output the class' name, using println.
  7. Compile and execute the Gamer class. Did it work? Why?
  8. Execute the Game class (the first class you wrote). Did it work? Why?
  9. Change the name of the method main to maine in the Game class. Compile and execute. Did it work? Why?
  10. Change the name of the method back to main. Change the main methods's access modifier public to private in the Game class. Compile and execute. Did it work? Why?
  11. Make the main method, in the Game class, public again. Compile and execute. Did it work? Why?
  12. Ok, one more time. Let's rename the method main to maine in the Game class. In the main method of the Gamer class we want to invoke the maine method in the Game class. You can do it like this:
     
    package org.gamefactory.textgame;
    
    public class Gamer{
    
      public static void main(String[] args) {
          Game.maine(null); // using a class in the same package works without import!
          System.out.println("Gamer");
      }
            
    }
    
  13. Change the name of the maine back to main. Compile both classes and execute the Gamer class. Did it work? Why?

Solutions to Starting point of a program

Expand using link to the right to see the full content.

  1. The class could look something like this:
    package org.gamefactory.textgame;
    
    public class Game{
    
    }
    
  2. It compiles but will not execute. It compiles because it is a valid Java class but fails to execute since the class does not contain a valid main method.
  3. The class could now look something like this:
    package org.gamefactory.textgame;
    
    public class Game{
    
      public static void main(String[] args) {
          System.out.println("Game");
      }
    
    }
    
  4. It both compiles and executes. It can be executed since it has a valid main method.
  5. The class could look something like this:
    package org.gamefactory.textgame;
    
    public class Gamer{
    
    }
    
  6. The class could now look something like this:
    package org.gamefactory.textgame;
    
    public class Gamer{
    
      public static void main(String[] args) {
          System.out.println("Gamer");
      }
    
    }
    
  7. It both compiles and executes. It can be executed since it has a valid main method.
  8. It worked now as it worked before. Nothing has changed so why shouldn't it work.
  9. Changing the name to maine does not satisfy the requirements Java sets on a main method so, although the class compiles fine, the class can't be executed.
  10. Changing the access modifier to private does not satisfy the requirements Java sets on a main method so, although the class compiles fine, the class can't be executed.
  11. Now it compiles and can be executed again, since the class is valid Java and the main method satisfies the requirements.
  12. The Game and Gamer classes compile. Game class executes fine. We can see that the Gamer class can invoke the maine method in the Game class.
  13. The Game class compiles and executes fine. The Gamer class will not compile since it invokes a method (maine) that does not exist (any more) in the Game class.


Comments and documentation

Imagine going back to classes you wrote some years ago. Or image reading someone else's classes. It is sometimes hard to understand what the class does, or at least intends to do. Java provides an easy way to write comments, which is text not considered as code and therefore ignored by the compiler. There are two ways to write comments in your code.

There is a format way you can use when writing comments with which you can create manuals for classes and packages. See Javadoc.

single line

You start a single line comment like this.

 
// This is a single line comment

Using "//" makes the compiler ignore everything after (and including) the "//". So the comment "This is a single line comment" is ignored.

Let's illustrate this in a class:

 
package org.commenteers;

public class Example{ // Compier ignores this                                   

    // main method, starting point                                              
    public static void main(String[] args) {
        // Print a welcome message                                              
        System.out.println("Welcome to the example class ... ");
    }

}

multi line

You start a multi line comment like this.

 
/*

and end it like this:

 
*/

A multi line comment looks like this:

 
/* 
  This is a multi line comment
   ... bla bla
*/

Using "/*" and "*/" makes the compiler ignore everything between (and including). So the comment "This is a multi line comment ... bla bla" is ignored by the compiler.

Let's illustrate this in a class:

 
package org.commenteers;

/*                                                                              
  This class is just a simple example                                           
  on how to comment code in Java                                                
                                                                                
 */

public class Example{ // Compier ignores this                                   

    // main method, starting point                                              
    public static void main(String[] args) {
        // Print a welcome message                                              
        System.out.println("Welcome to the example class ... ");
    }

}


Exercises

  1. Create a file like the one below (note that the file is in a package and you need to crate (and use) a corresponding directory structure).
     
    package org.commenteers;
    
    public class Simple{
    
      public static void main(String[] args) {
            System.out.println("Simple class to test comments");
    
            for (int i=0; i<10; i++) {
                System.out.println("  i: " + i);
            }
      }
    
    }
    
  2. Write a comment before the main method saying "This is the main method".
  3. Change the class so that the first println statement is ignored by the compiler. Use single line comment to achieve this.
  4. Write an introduction text to the class using multi line comments.
  5. Change the class so that the for loop is not executed (enclose it in a multi line comment)

Solutions to - Comments and documentation

Expand using link to the right to see the full content.

  1. Just create it in the correct directory structure!
  2. Suggestion:
     
    package org.commenteers;
    
    public class Simple{
    
      // This is the main method
      public static void main(String[] args) {
        System.out.println("Simple class to test comments");
    
        for (int i=0; i<10; i++) {
          System.out.println("  i: " + i);
        }
      }
    }
    
  3. Suggestion:
     
    package org.commenteers;
    
    public class Simple{
    
      // This is the main method
      public static void main(String[] args) {
        //System.out.println("Simple class to test comments");
    
        for (int i=0; i<10; i++) {
          System.out.println("  i: " + i);
        }
      }
    }
    
  4. Suggestion:
     
    package org.commenteers;
    
    /*
     *
     * This class is something the authors forced us to work with
     * The class really does nothing useful and all we'll ever get
     * out of it is how to comment things... but, hey that's kind of useful!
    */
    
    public class Simple{
    
      // This is the main method
      public static void main(String[] args) {
        //System.out.println("Simple class to test comments");
    
        for (int i=0; i<10; i++) {
          System.out.println("  i: " + i);
        }
      }
    }
    
  5. Suggestion:
     
    package org.commenteers;
    
    /*
     *
     * This class is something the authors forced us to work with
     * The class really does nothing useful and all we'll ever get
     * out of it is how to comment things... but, hey that's kind of useful!
    */
    
    public class Simple{
    
      // This is the main method
      public static void main(String[] args) {
        //System.out.println("Simple class to test comments");
    
        /*
        for (int i=0; i<10; i++) {
          System.out.println("  i: " + i);
        }
        */
      }
    }
    

Chapter Links

External links

Books this chapter is a part of

Programming with Java book

Book TOC | previous chapter | next chapter