Chapter 2. Classes and Objects

 

First things first, but not necessarily in that order.

 
 --Dr. Who, Meglos

The fundamental programming unit of the Java programming language is the class. Classes provide the structure for objects and the mechanisms to manufacture objects from a class definition. Classes define methods: collections of executable code that are the focus of computation and that manipulate the data stored in objects. Methods provide the behavior of the objects of a class. Although you can compute using only primitive types—integer, floating-point, and so on—almost any interesting program will create and manipulate objects.

Object-oriented programming strictly separates the notion of what is to be done from how it is done. “What” is described as a set of methods (and sometimes publicly available data) and their associated semantics. This combination—methods, data, and semantics—is often described as a contract between the designer of the class and the programmer who uses it because it says what happens when certain methods are invoked on an object. This contract defines a type such that all objects that are instances of that type are known to honor that contract.

A common assumption is that the methods declared in a class are its entire contract. The semantics of those operations are also part of the contract, even though they may be described only in documentation. Two methods may have the same name and parameters and throw the same exceptions, but they are not equivalent if they have different semantics. For example, not every method called print can be assumed to print a copy of the object. Someone might define a print method with the semantics “process interval” or “prioritize nonterminals”—not that we'd recommend this. The contract of the method, both signature and semantics together, defines what it means.

The “how” of an object is defined by its class, which defines the implementation of the methods the object supports. Each object is an instance of a class. When a method is invoked on an object, the class is examined to find the code to be run. An object can use other objects to do its job, but we start with simple classes that implement all their own methods directly.

A Simple Class

Here is a simple class, called Body[1] that could be used to store data about celestial bodies such as comets, asteroids, planets, and stars:

class Body {
    public long idNum;
    public String name;
    public Body orbits;

    public static long nextID = 0;
}

A class is declared using the keyword class, giving the class a name and listing the class members between curly braces. A class declaration creates a type name, so references to objects of that type can be declared with a simple

Body mercury;

This declaration states that mercury is a variable that can hold a reference to an object of type Body. The declaration does not create an object—it declares only a reference that is allowed to refer to a Body object. During its existence, the reference mercury may refer to any number of Body objects. These objects must be explicitly created. In this respect, the Java programming language is different from languages in which objects are created when you declare variables.

This first version of Body is poorly designed. This is intentional: We will demonstrate the value of certain language features as we improve the class in this chapter.

Class Members

A class can have three kinds of members:

  • Fields are the data variables associated with a class and its objects and hold the state of the class or object.

  • Methods contain the executable code of a class and define the behavior of objects.

  • Nested classes and nested interfaces are declarations of classes or interfaces that occur nested within the declaration of another class or interface.

In this chapter we concentrate on the basic members: fields and methods. Nested members are discussed in Chapter 5.

Class Modifiers

A class declaration can be preceded by class modifiers that give the class certain properties:

  • annotations—. Annotations and annotation types are discussed in Chapter 15.

  • public—. A public class is publicly accessible: Anyone can declare references to objects of the class or access its public members. Without a modifier a class is only accessible within its own package. You'll learn about general access control in Section 2.3 on page 47. Packages and related accessibility issues of classes and members are discussed in Chapter 18.

  • abstract—. An abstract class is considered incomplete and no instances of the class may be created. Usually this is because the class contains abstract methods that must be implemented by a subclass. You'll learn about this in “Abstract Classes and Methods” on page 97.

  • final—. A final class cannot be subclassed. Subclassing is discussed in Chapter 3.

  • strict floating point—. A class declared strictfp has all floating-point arithmetic in the class evaluated strictly. See “Strict and Non-Strict Floating-Point Arithmetic” on page 203 for details.

A class cannot be both final and abstract.

A class declaration can be preceded by several modifiers. Modifiers are allowed in any order, but we recommend that you adopt a consistent order to improve the readability of your code. We always use, and recommend, the order listed.

While we won't be concerned about class modifiers in this chapter you need to know a little about public classes. Most Java development tools require that a public class be declared in a file with the same name as the class, which means there can only be one public class declared per file.

Exercise 2.1Write a simple Vehicle class that has fields for (at least) current speed, current direction in degrees, and owner name.

Exercise 2.2Write a LinkedList class that has a field of type Object and a reference to the next LinkedList element in the list.

Fields

A class's variables are called fields; the Body class's name and orbits variables are examples. A field declaration consists of a type name followed by the field name and optionally an initialization clause to give the field an initial value. Every Body object has its own specific instances of three fields: a long that uniquely identifies the body from all others, a String that is its name, and a reference to another Body around which it orbits. Giving each separate object a different instance of the fields means that each object has its own unique state—such fields are known as instance variables. Changing the orbits field in one Body object does not affect the orbits field in any other Body object.

Field declarations can also be preceded by modifiers that control certain properties of the field:

  • annotations—. Annotations and annotation types are discussed in Chapter 15.

  • access modifiers—. These are discussed in Section 2.3 on page 47.

  • static—. This is discussed below.

  • final—. This is discussed below.

  • transient—. This relates to object serialization and is discussed in Section 20.8 on page 549.

  • volatile—. This relates to synchronization and memory model issues and is discussed in Section 14.10 on page 370.

A field cannot be both final and volatile.

When multiple modifiers are applied to the same field declaration, we recommend using the order listed above.

Field Initialization

When a field is declared it can be initialized by assigning it a value of the corresponding type. In the Body example, the nextID field is initialized to the value zero. The initialization expression, or more simply the initializer, need not be a constant, however. It could be another field, a method invocation, or an expression involving all of these. The only requirement is that the initializer be of the right type and, if it invokes a method, no checked exceptions may be thrown because there is no surrounding code to catch the exception. For example, the following are all valid initializers:

double zero = 0.0;             // constant
double sum = 4.5 + 3.7;        // constant expression
double zeroCopy = zero;        // field
double rootTwo = Math.sqrt(2); // method invocation
double someVal = sum + 2*Math.sqrt(rootTwo); // mixed

Although initializers provide a great deal of flexibility in the way fields can be initialized, they are only suitable for simple initialization schemes. For more complex schemes we need additional tools—as we shall soon see.

If a field is not initialized a default initial value is assigned to it depending on its type:

Type

Initial Value

boolean

false

char

'u0000'

byte, short, int, long

0

float, double

+0.0

object reference

null

Static Fields

Sometimes you want only one instance of a field shared by all objects of a class. You create such fields by declaring them static, so they are called static fields or class variables. When you declare a static field in a class only one copy of the field exists, no matter how many instances of the class are created.

In our case, Body has one static field, nextID, which contains the next body identifier to use. The nextID field is initialized to zero when the class is initialized after it is loaded (see “Loading Classes” on page 435). You will see that each newly created Body object will be assigned the current value of nextID as its identifier, and the value of nextID will be incremented. Hence, we only want one copy of the nextID field, to be used when creating all Body instances.

Within its own class a static field can be referred to directly, but when accessed externally it must usually be accessed using the class name. For example, we could print the value of nextID as follows:

System.out.println(Body.nextID);

The use of System.out itself illustrates accessing a static field. When a static member of a class is referenced many times from another class, or when multiple static members of a class are referenced from another class, you can clarify the code, and perhaps save some typing, by using a static import statement to name the class of the static member once. This is discussed in detail in Section 2.9 on page 71.

A static member may also be accessed using a reference to an object of that class, such as

System.out.println(mercury.nextID);

You should avoid this form because it gives the false impression that nextID is a member of the object mercury, not a member of the class Body. It is the type of the reference, not the type of the object it refers to, that determines the class in which to look for the static variable.

In this book when we use the term field, we usually mean the non-static kind. When the context makes it ambiguous, we use the term non-static field to be clear.

Exercise 2.3Add a static field to your Vehicle class to hold the next vehicle identification number, and a non-static field to the Vehicle class to hold each car's ID number.

final Fields

A final variable is one whose value cannot be changed after it has been initialized—any attempt to assign to such a field will produce a compile-time error. We have seen final fields used to define named constants because constants don't change value. In general, a final field is used to define an immutable property of a class or object—a property that doesn't change for the lifetime of the class or object. Fields that are marked final also have special semantics with regard to concurrent access by multiple threads, this is discussed in more detail in Chapter 14.

If a final field does not have an initializer it is termed a blank final. You would use a blank final when simple initialization is not appropriate for a field. Such fields must be initialized once the class has been initialized (in the case of static final fields) or once an object of the class has been fully constructed (for non-static final fields). The compiler will ensure that this is done and refuse to compile a class if it determines that a final field does not get initialized.

A final field of a primitive type, or of String type, that is initialized with a constant expression—that is, an expression whose value can be determined at compile time—is known as a constant variable. Constant variables are special because the compiler treats them as values not fields. If your code refers to a constant variable, the compiler does not generate bytecode to load the value of the field from the object, it simply inserts the value directly into the bytecode. This can be a useful optimization, but it means that if the value of your final field needs to be changed, then every piece of code that refers to that field also has to be recompiled.

Whether a property is immutable is determined by the semantics of the application for which the class was designed. When you decide whether a field should be final, consider three things:

  • Does the field represent an immutable property of the object?

  • Is the value of the field always known at the time the object is created?

  • Is it always practical and appropriate to set the value of the field when the object is created?

If the property is merely infrequently changed rather than truly immutable then a final field is not appropriate. If the value of the field is not known when the object is created then it can't be made final, even if it is logically immutable once known. For example, in a simulation of celestial bodies a comet may become trapped by the gravitational pull of a star and commence to orbit that star. Once trapped the comet orbits the star forever or until it is destroyed. In this situation the orbits field will hold an immutable value once set, but that value is not known when the comet object is created and so the field cannot be final. Finally, if initialization of the field is expensive and the field's value is infrequently needed, then it may be more practical to defer the initialization of the field until its value is needed—this is generally termed lazy initialization—which cannot be done to a final field.

There are additional considerations concerning final fields if your object must be clonable or serializable. These are discussed in “Cloning Objects” on page 101 and “Object Serialization” on page 549, respectively.

Exercise 2.4Consider your solution to Exercise 2.3. Do you think the identification number field should be final?

Access Control

If every member of every class and object were accessible to every other class and object then understanding, debugging, and maintaining programs would be an almost impossible task. The contracts presented by classes could not be relied on because any piece of code could directly access a field and change it in such a way as to violate the contract. One of the strengths of object-oriented programming is its support for encapsulation and data hiding. To achieve these we need a way to control who has access to what members of a class or interface, and even to the class or interface itself. This control is specified with access modifiers on class, interface, and member declarations.

All members of a class are always available to code in the class itself. To control access from other classes, class members have four possible access modifiers:

  • private—. Members declared private are accessible only in the class itself.

  • package—. Members declared with no access modifier are accessible in classes in the same package, as well as in the class itself. We discuss packages and related accessibility issues in Chapter 18.

  • protected—. Members declared protected are accessible in subclasses of the class, in classes in the same package, and in the class itself. Extending classes is covered in Chapter 3.

  • public—. Members declared public are accessible anywhere the class is accessible.

The private and protected access modifiers apply only to members not to the classes or interfaces themselves (unless nested). For a member to be accessible from a section of code in some class, the member's class must first be accessible from that code.

It is important to realize that access control is performed on a per-class (or interface) level not on a per-object level. This means that members of a class are always accessible from all code written in that class regardless of which instance the code is being applied to. We illustrate this with an example later when we look at how methods can also be used to control access; see Section 2.6.6 on page 65.

You should view public and protected members as contractual, because they can be relied on by code you do not control. Changing them can be impossible after that code relies on public or protected functionality. Package and private access are part of your implementation, hidden from outsiders (classes in the same package should be related).

We declared the Body class's fields public because programmers need access to them to do the work the class is designed for. In a later version of the Body class, you will see that such a design is not usually a good idea.

Creating Objects

In this first version of Body, objects that represent particular celestial bodies are created and initialized like this:

Body sun = new Body();
sun.idNum = Body.nextID++;
sun.name = "Sol";
sun.orbits = null; // in solar system, sun is middle

Body earth = new Body();
earth.idNum = Body.nextID++;
earth.name = "Earth";
earth.orbits = sun;

First we declare a reference variable (sun) that can refer to objects of type Body. As mentioned before, such a declaration does not create an object; it only defines a variable that references objects. The object it refers to must be created explicitly.

We create the object sun refers to using new. The new construct is by far the most common way to create objects (we cover the other ways in Chapter 16). When you create an object with new, you specify the type of object you want to create and any arguments for its construction. The runtime system allocates enough space to store the fields of the object and initializes it in ways you will soon see. When initialization is complete, the runtime system returns a reference to the new object.

If the system cannot find enough free space to create the object, it may have to run the garbage collector to try to reclaim space. If the system still cannot find enough free space, new throws an OutOfMemoryError exception.

Having created a new Body object, we initialize its variables. Each Body object needs a unique identifier, which it gets from the static nextID field of Body. The code must increment nextID so that the next Body object created will get a unique identifier.

We then make an earth object in a similar fashion. This example builds a solar system model. In this model, the sun is in the center, and sun's orbits field is null because it doesn't orbit anything. When we create and initialize earth, we set its orbits field to sun. A moon object would have its orbits field set to earth. In a model of the galaxy, the sun would orbit around the black hole presumed to be at the middle of the Milky Way.

You create objects using new, but you never delete them explicitly. The virtual machine manages memory for you using garbage collection, which means that objects that you cannot possibly use any longer can have their space automatically reclaimed by the virtual machine without your intervention. If you no longer need an object you should cease referring to it. With local variables in methods this can be as simple as returning from the method—when the method is no longer executing none of its variables can possibly be used. More durable references, such as fields of objects, can be set to null. Memory management is discussed in more detail in Chapter 17.

Exercise 2.5Write a main method for your Vehicle class that creates a few vehicles and prints their field values.

Exercise 2.6Write a main method for your LinkedList class that creates a few objects of type Vehicle and places them into successive nodes in the list.

Construction and Initialization

A newly created object is given an initial state. Fields can be initialized with a value when they are declared, or you can accept their default value, which is sometimes sufficient to ensure a correct initial state. But often you need more than simple initialization to create the initial state. For example, the creating code may need to supply initial data or perform operations that cannot be expressed as simple assignment.

Constructors

For purposes other than simple initialization, classes can have constructors. Constructors are blocks of statements that can be used to initialize an object before the reference to the object is returned by new. Constructors have the same name as the class they initialize. Like methods, they take zero or more arguments, but constructors are not methods and thus have no return type. Arguments, if any, are provided between the parentheses that follow the type name when the object is created with new. Constructors are invoked after the instance variables of a newly created object of the class have been assigned their default initial values and after their explicit initializers are executed.

This improved version of the Body class uses both constructors and initializers to set up each new object's initial state:

class Body {
    public long idNum;
    public String name = "<unnamed>";
    public Body orbits = null;
    private static long nextID = 0;

    Body() {
        idNum = nextID++;
    }
}

A constructor declaration consists of the class name followed by a (possibly empty) list of parameters within parentheses and a body of statements enclosed in curly braces. Constructors can have any of the same access modifiers as class members, but constructors are not members of a class—a distinction you can usually ignore, except when it comes to inheritance. Constructors can also have annotations applied to them; see Chapter 15.

The constructor for Body takes no arguments, but it performs an important function—assigning a proper idNum to the newly created object. In the original code, a simple programmer error—forgetting to assign the idNum or not incrementing nextID after use—could result in different Body objects with the same idNum. That would create bugs in code that relies on the part of the contract that says “All idNum values are different.”

By moving responsibility for idNum generation inside the Body class, we have prevented errors of this kind. The Body constructor is now the only entity that assigns idNum and is therefore the only entity that needs access to nextID. We can and should make nextID private so that only the Body class can access it. By doing so, we remove a source of error for programmers using the Body class.

We also are now free to change the way idNum values are assigned to Body objects. A future implementation of this class might, for example, look up the name in a database of known astronomical entities and assign a new idNum only if an idNum had not previously been assigned. This change would not affect any existing code, because existing code is not involved at all in the mechanism for idNum allocation.

The initializers for name and orbits set them to reasonable values. Therefore, when the constructor returns from the following invocations, all data fields in the new Body object have been set to some reasonable initial state. You can then set state in the object to the values you want:

Body sun = new Body();   // idNum is 0
sun.name = "Sol";

Body earth = new Body(); // idNum is 1
earth.name = "Earth";
earth.orbits = sun;

The Body constructor is invoked when new creates the object but after name and orbits have been set to their default initial values.

The case shown here—in which you know the name of the body and what it orbits when you create it—is likely to be fairly common. You can provide another constructor that takes both the name and the orbited body as arguments:

Body(String bodyName, Body orbitsAround) {
    this();
    name = bodyName;
    orbits = orbitsAround;
}

As shown here, one constructor can invoke another constructor from the same class by using the this() invocation as its first executable statement. This is called an explicit constructor invocation. If the constructor you want to invoke has arguments, they can be passed to the constructor invocation—the type and number of arguments used determines which constructor gets invoked. Here we use it to invoke the constructor that has no arguments in order to set up the idNum. This means that we don't have to duplicate the idNum initialization code. Now the allocation code is much simpler:

Body sun = new Body("Sol", null);
Body earth = new Body("Earth", sun);

The argument list determines which version of the constructor is invoked as part of the new expression.

If provided, an explicit constructor invocation must be the first statement in the constructor. Any expressions that are used as arguments for the explicit constructor invocation must not refer to any fields or methods of the current object—to all intents and purposes there is no current object at this stage of construction.

For completeness, you could also provide a one-argument constructor for constructing a Body object that doesn't orbit anything. This constructor would be used instead of invoking the two-argument Body constructor with a second argument of null:

Body(String bodyName) {
    this(bodyName, null);
}

and that is exactly what this constructor does, using another explicit constructor invocation.

Some classes always require that the creator supply certain kinds of data. For example, your application might require that all Body objects have a name. To ensure that all statements creating Body objects supply a name, you would define all Body constructors with a name parameter and you wouldn't bother initializing the name field.

Here are some common reasons for providing specialized constructors:

  • Some classes have no reasonable initial state without parameters.

  • Providing an initial state is convenient and reasonable when you're constructing some kinds of objects (the two-argument constructor of Body is an example).

  • Constructing an object can be a potentially expensive operation, so you want objects to have a correct initial state when they're created. For example, if each object of a class had a table, a constructor to specify the initial size would enable the object to create the table with the right size from the beginning instead of creating a table of a default size, which would later be discarded when the method that set the actual size was invoked.

  • A constructor that isn't public restricts who can create objects using it. You could, for example, prevent programmers using your package from creating instances of a class by making all its constructors accessible only inside the package.

Constructors without arguments are so common that there is a term for them: no-arg (for “no arguments”) constructors. If you don't provide any constructors of any kind in a class, the language provides a default no-arg constructor that does nothing. This constructor—called the default constructor—is provided automatically only if no other constructors exist because there are classes for which a no-arg constructor would be incorrect (like the Attr class you will see in the next chapter). If you want both a no-arg constructor and one or more constructors with arguments, you must explicitly provide a no-arg constructor. The default constructor has the same accessibility as the class for which it was defined—if the class is public then the default constructor is public.

Another form of constructor is a copy constructor—this constructor takes an argument of the current object type and constructs the new object to be a copy of the passed in object. Usually this is simply a matter of assigning the same values to all fields, but sometimes the semantics of a class dictate more sophisticated actions. Here is a simple copy constructor for Body:

Body(Body other) {
    idNum = other.idNum;
    name = other.name;
    orbits = other.orbits;
}

Whether this is correct depends on the contract of the class and on what the uniqueness of idNum is supposed to indicate—for this example we won't concern ourselves with that.

This idiom is not used much within the Java class libraries, because the preferred way to make a direct copy of an object is by using the clone method—see “Cloning Objects” on page 101. However, many classes support a more general form of construction that “copies” another object. For example, the StringBuilder and StringBuffer classes (described in Chapter 13) each have a constructor that takes a single CharSequence argument that allows you to copy an existing String, StringBuffer, or StringBuilder object; and the collection classes (described in Chapter 21) each have a constructor that takes another Collection as an argument and so allow one collection to be initialized with the same contents as another (which need not be of exactly the same type). Writing a correct copy constructor requires the same consideration as writing a correct clone method.

Constructors can also be declared to throw checked exceptions. The throws clause comes after the parameter list and before the opening curly brace of the constructor body. If a throws clause exists then any method that invokes this constructor as part of a new expression must either catch the declared exception or itself declare that it throws that exception. Exceptions and throws clauses are discussed in detail in Chapter 12.

Exercise 2.7Add two constructors to Vehicle: a no-arg constructor and one that takes an initial owner's name. Modify the main program so that it generates the same output it did before.

Exercise 2.8What constructors should you add to LinkedList?

Initialization Blocks

Another way to perform more complex initialization of fields is to use an initialization block. An initialization block is a block of statements that appears within the class declaration, outside of any member, or constructor, declaration and that initializes the fields of the object. It is executed as if it were placed at the beginning of every constructor in the class—with multiple blocks being executed in the order they appear in the class. An initialization block can throw a checked exception only if all of the class's constructors are declared to throw that exception.

For illustration, this variant of the Body class replaces the no-arg constructor with an equivalent initialization block:

class Body {
    public long idNum;
    public String name = "<unnamed>";
    public Body orbits = null;

    private static long nextID = 0;

    {
        idNum = nextID++;
    }

    public Body(String bodyName, Body orbitsAround) {
        name = bodyName;
        orbits = orbitsAround;
    }
}

Now the two-argument constructor doesn't need to perform the explicit invocation of the no-arg constructor, but we no longer have a no-arg constructor and so everyone is forced to use the two-argument constructor.

This wasn't a particularly interesting use of an initialization block but it did illustrate the syntax. In practice, initialization blocks are most useful when you are writing anonymous inner classes—see Section 5.4 on page 144—that can't have constructors. An initialization block can also be useful to define a common piece of code that all constructors execute. While this could be done by defining a special initialization method, say init, the difference is that such a method would not be recognized as construction code and could not, for example, assign values to blank final fields. Initialization is the purpose of having initialization blocks, but in practice you can make it do anything—the compiler won't check what it does. Spreading initialization code all through a class is not a good design, and initialization blocks should be used judiciously, when they express something that cannot easily be done by constructors alone.

Static Initialization

The static fields of a class can have initializers as we have already seen. But in addition we can perform more complex static initialization in a static initialization block. A static initialization block is much like a non-static initialization block except it is declared static, can only refer to static members of the class, and cannot throw any checked exceptions. For example, creating a static array and initializing its elements sometimes must be done with executable statements. Here is example code to initialize a small array of prime numbers:

class Primes {
    static int[] knownPrimes = new int[4];

    static {
        knownPrimes[0] = 2;
        for (int i = 1; i < knownPrimes.length; i++)
            knownPrimes[i] = nextPrime();
    }
    // declaration of nextPrime ...
}

The order of initialization within a class is first-to-last—each field initializer or initialization block is executed before the next one, from the beginning of the source to the end. The static initializers are executed after the class is loaded, before it is actually used (see “Preparing a Class for Use” on page 441). With this guarantee, our static block in the example is assured that the knownPrimes array is already created before the initialization code block executes. Similarly, anyone accessing a static field is guaranteed that the field has been initialized.

What if a static initializer in class X invokes a method in Y, but Y's static initializers invoke a method in X to set up its static values? This cyclic static initialization cannot be reliably detected during compilation because the code for Y may not be written when X is compiled. If cycles happen, X's static initializers will have been executed only to the point where Y's method was invoked. When Y, in turn, invokes the X method, that method runs with the rest of the static initializers yet to be executed. Any static fields in X that haven't had their initializers executed will still have their default values (false, '/u0000', zero, or null depending on their type).

Methods

A class's methods typically contain the code that understands and manipulates an object's state. Some classes have public or protected fields for programmers to manipulate directly, but in most cases this isn't a very good idea (see “Designing a Class to Be Extended” on page 108). Many objects have tasks that cannot be represented as a simple value to be read or modified but that require computation.

We have already seen a number of examples of methods in Chapter 1—all of our demonstration programs had a main method that was executed by the Java virtual machine. Here is another main method that creates a Body object and prints out the values of its fields.

class BodyPrint {
    public static void main(String[] args) {
        Body sun = new Body("Sol", null);
        Body earth = new Body("Earth", sun);
        System.out.println("Body " + earth.name + 
                           " orbits " + earth.orbits.name +
                           " and has ID " + earth.idNum);
    }
}

A method declaration consists of two parts: the method header and the method body. The method header consists of an optional set of modifiers, an optional set of type parameters, the method return type, the signature, and an optional throws clause listing the exceptions thrown by the method. The method signature consists of the method name and the (possibly empty) parameter type list enclosed in parentheses. All methods must have a return type and signature. Type parameters are used to declare generic methods and are discussed in Chapter 11. Exceptions and throws clauses are discussed in detail in Chapter 12. The method body consists of statements enclosed between curly braces.

The method modifiers consist of the following:

  • annotations—. Annotations and annotation types are discussed in Chapter 15.

  • access modifiers—. These were discussed on page 47.

  • abstract—. An abstract method is one whose body has not been defined in this class—the body is specified as a semicolon after the parameter list. A subclass is then responsible for providing a body for this method. This is discussed in Section 3.7 on page 97.

  • static—. This is discussed below.

  • final—. A final method cannot be overridden in a subclass. This is discussed in Section 3.6 on page 96.

  • synchronized—. A synchronized method has additional semantics related to the control of concurrent threads within a program. This is discussed in Section 14.3 on page 345.

  • native—. This is discussed in Section 2.11 on page 74.

  • strict floating point—. A method declared strictfp has all floating-point arithmetic evaluated strictly. If a method is declared within a class declared strictfp, then that method is implicitly declared strictfp. See Section 9.1.3 on page 203 for details.

An abstract method cannot be static, final, synchronized, native, or strict. A native method cannot be strict.

When multiple modifiers are applied to the same method declaration, we recommend using the order listed.

Static Methods

A static method is invoked on behalf of an entire class, not on a specific object instantiated from that class. Such methods are also known as class methods. A static method might perform a general task for all objects of the class, such as returning the next available serial number or something of that nature.

A static method can access only static fields and other static methods of the class. This is because non-static members must be accessed through an object reference, and no object reference is available within a static method—there is no this reference.

In this book when we use the term method, we usually mean the non-static kind. When the context makes it ambiguous, we use the term non-static method to be clear.

Exercise 2.9Add a static method to Vehicle that returns the highest identification number used thus far.

Method Invocations

Methods are invoked as operations on objects via references using the dot (.) operator:

reference.method(arguments)

In the BodyPrint example we invoke the println method on the object referred to by the static reference System.out and passing a single String argument formed by concatenating a number of other strings.

Each method is declared to have a specific number of parameters, each of a specific type. The last parameter of a method can be declared as a sequence of parameters of a given type. This is indicated by an ellipse (...) after the type name. For example, String... is a sequence of zero or more String objects. Sequence parameters allow you to invoke the method by passing in a variable number of arguments, some of which will correspond to that sequence. These variable argument methods are described in the next section.

When a method is invoked the caller must provide an argument of the appropriate type for each of the parameters declared by the method. An argument need not be exactly the same type as its corresponding parameter because some automatic conversions are applied. For example, a byte argument can be converted to an int parameter, and wrapper objects can be converted to their primitive values (and vice-versa) as needed—see “Boxing Conversions” on page 198.

Methods also have a return type, either a primitive type or a reference type. If a method does not return any value, the place where a return type would go is filled with a void.

The BodyPrint example illustrates a common situation in which you would like to examine the state of an object. Rather than providing access to all the fields of the object, a class can define a method that returns a string representation of the state of the object. For example, here is a method of the Body class to return a String that describes the particular Body object it is invoked upon:

public String toString() {
    String desc = idNum + " (" + name + ")";
    if (orbits != null)
        desc += " orbits " + orbits.toString();
    return desc;
}

This method uses + and += to concatenate String objects. It first builds a string that describes the identifier and name. If the body orbits another body, we append the string that describes that body by invoking its toString method. This recursion builds a string of bodies orbiting other bodies until the chain ends with an object that doesn't orbit anything. The resulting string is then returned to the caller by the return statement.

The toString method of an object is special—it is invoked to get a String when an object is used in a string concatenation expression using the + operator. Consider these expressions:

System.out.println("Body " + sun);
System.out.println("Body " + earth);

The toString methods of sun and earth are invoked implicitly and produce the following output:

Body 0 (Sol)
Body 1 (Earth) orbits 0 (Sol)

All objects have a toString method whether their class explicitly defines one or not—this is because all classes extend the class Object and it defines the toString method. Class extension and the Object class are discussed in Chapter 3.

Exercise 2.10Add a toString method to Vehicle.

Exercise 2.11Add a toString method to LinkedList.

Methods with Variable Numbers of Arguments

The last parameter in a method (or constructor) parameter list can be declared as a sequence of a given type. To indicate that a parameter is a sequence you write an ellipse (...) after the parameter type, but before its name. For example, in

public static void print(String... messages) {
    // ...
}

the parameter messages is declared to be a sequence of String objects. Sequence parameters allow a method to be invoked with a variable number of arguments (including zero) that form the sequence. Such methods are known as variable-argument, or more commonly varargs methods; methods that don't declare a sequence parameter are known as fixed-argument methods.[2]

The need to process a variable number of arguments can arise in many situations. For example, our Body objects already track which other body they orbit, but it can be useful to know which Body objects they are orbited by. While a given body generally only orbits one other body, it may itself be orbited by many other bodies. To track this we could define an array of Body objects and provide a method to add each orbiting body, one at a time, to that array. That is not an unreasonable solution, but it is much more convenient if we just define a method that can take a sequence of Body objects:

public void setOrbiters(Body... bodies) {
    // ... store the values ...
}

You could then use this method in the following way:

Body sun = new Body("Sol", null);
Body earth = new Body("Earth", sun);
Body moon = new Body("Moon", earth);
Body mars = new Body("Mars", sun);
Body phobos = new Body("Phobos", mars);


Body deimos = new Body("Deimos", mars);

earth.setOrbiters(moon);
mars.setOrbiters(phobos, deimos);

You may be wondering what type the parameter sequence bodies has inside setOrbiters—quite simply it is an array of Body objects. Declaring a parameter as a sequence is nothing more than asking the compiler to construct an array for you and to set its elements to be the arguments actually passed for the sequence. When you invoke a method, the compiler will match each supplied argument to a named parameter. If the compiler finds a sequence parameter then it will bundle all the remaining arguments into an array of the type specified by the sequence parameter—remember a sequence parameter must be the last declared parameter. Of course, those arguments must be of the right type to be stored in the array. If no arguments are left to store in the array then a zero-length array is created and passed as the argument. Knowing this, you could save the compiler the trouble and pass an array yourself:

Body[] marsMoons = { phobos, deimos };
mars.setOrbiters(marsMoons);

But in most cases it is easier to just let the compiler do its job.

Sometimes the compiler will need some help working out what you intended a particular method invocation to mean. For example, suppose you invoke setOrbiters with the single argument null. Does that mean to pass null as the array (and so have no array) or does it mean to have an array of length one with a null entry? The compiler can't be sure so it will give you a warning. To remove the ambiguity you need to cast null to a suitable type—for a sequence parameter of type T, if you cast to T then you get an array of T of length one with a null entry; if you cast to T[] then you get a null array reference. Other ambiguities can arise in the context of method overloading, but we'll get to those later (see “Overloading Methods” on page 69).

You've already seen an example of another varargs method in use, but may not have realized it. The printf method that was introduced on page 23 is declared to take a String and a sequence of Objects. For each format specifier in the format string, the method expects a corresponding object argument to be passed in.

Exercise 2.12Considering your Vehicle and LinkedList classes, can you think of a need for any methods that take a variable number of arguments?

Method Execution and Return

When a method is invoked, the flow of control passes from the calling method into the invoked method and the statements of that method are executed in sequence according to the semantics of those statements. A method completes execution and returns to the caller when one of three things happens: a return statement is executed, the end of the method is reached, or an uncaught exception is thrown.

If a method returns any result, it can only return a single result—either a primitive value or a reference to an object. Methods that need to return more than one result can achieve this effect in several ways: return references to objects that store the results as fields, take one or more parameters that reference objects in which to store the results, or return an array that contains the results. Suppose, for instance, that you want to write a method to return what a particular person can do with a given bank account. Multiple actions are possible (deposit, withdraw, and so on), so you must return multiple permissions. You could create a Permissions class whose objects store boolean values to say whether a particular action is allowed:

public class Permissions {
    public boolean canDeposit,
                   canWithdraw,
                   canClose;
}

Here is a method that fills in the fields to return multiple values:

public class BankAccount {
    private long number;    // account number
    private long balance;   // current balance (in cents)

    public Permissions permissionsFor(Person who) {
        Permissions perm = new Permissions();
        perm.canDeposit = canDeposit(who);
        perm.canWithdraw = canWithdraw(who);
        perm.canClose = canClose(who);
        return perm;
    }

    // ... define canDeposit et al ...
}

In methods that return a value, every path through the method must either return a value that is assignable to a variable of the declared return type or throw an exception. The permissionsFor method could not return, say, a String, because you cannot assign a String object to a variable of type Permissions. But you could declare the return type of permissionsFor as Object without changing the return statement, because you can assign a Permissions object reference to a variable of type Object, since (as you know) all classes extend Object. The notion of being assignable is discussed in detail in Chapter 3.

Parameter Values

All parameters to methods are passed “by value.” In other words, values of parameter variables in a method are copies of the values the invoker specified as arguments. If you pass a double to a method, its parameter is a copy of whatever value was being passed as an argument, and the method can change its parameter's value without affecting values in the code that invoked the method. For example:

class PassByValue {
    public static void main(String[] args) {
        double one = 1.0;

        System.out.println("before: one = " + one);
        halveIt(one);
        System.out.println("after:  one = " + one);
    }

    public static void halveIt(double arg) {
        arg /= 2.0;     // divide arg by two
        System.out.println("halved: arg = " + arg);
    }
}

The following output illustrates that the value of arg inside halveIt is divided by two without affecting the value of the variable one in main:

before: one = 1.0
halved: arg = 0.5
after:  one = 1.0

You should note that when the parameter is an object reference, it is the object reference—not the object itself—that is passed “by value.” Thus, you can change which object a parameter refers to inside the method without affecting the reference that was passed. But if you change any fields of the object or invoke methods that change the object's state, the object is changed for every part of the program that holds a reference to it. Here is an example to show this distinction:

class PassRef {
    public static void main(String[] args) {
        Body sirius = new Body("Sirius", null);

        System.out.println("before: " + sirius);
        commonName(sirius);
        System.out.println("after:  " + sirius);
    }

    public static void commonName(Body bodyRef) {
        bodyRef.name = "Dog Star";
        bodyRef = null;
    }
}

This program produces the following output:

before: 0 (Sirius)
after:  0 (Dog Star)

Notice that the contents of the object have been modified with a name change, while the variable sirius still refers to the Body object even though the method commonName changed the value of its bodyRef parameter variable to null. This requires some explanation.

The following diagram shows the state of the variables just after main invokes commonName:

Parameter Values

At this point, the two variables sirius (in main) and bodyRef (in commonName) both refer to the same underlying object. When commonName changes the field bodyRef.name, the name is changed in the underlying object that the two variables share. When commonName changes the value of bodyRef to null, only the value of the bodyRef variable is changed; the value of sirius remains unchanged because the parameter bodyRef is a pass-by-value copy of sirius. Inside the method commonName, all you are changing is the value in the parameter variable bodyRef, just as all you changed in halveIt was the value in the parameter variable arg. If changing bodyRef affected the value of sirius in main, the “after” line would say “null”. However, the variable bodyRef in commonName and the variable sirius in main both refer to the same underlying object, so the change made inside commonName is visible through the reference sirius.

Some people will say incorrectly that objects are passed “by reference.” In programming language design, the term pass by reference properly means that when an argument is passed to a function, the invoked function gets a reference to the original value, not a copy of its value. If the function modifies its parameter, the value in the calling code will be changed because the argument and parameter use the same slot in memory. If the Java programming language actually had pass-by-reference parameters, there would be a way to declare halveIt so that the above code would modify the value of one, or so that commonName could change the variable sirius to null. This is not possible. The Java programming language does not pass objects by reference; it passes object references by value. Because two copies of the same reference refer to the same actual object, changes made through one reference variable are visible through the other. There is exactly one parameter passing mode—pass by value—and that helps keep things simple.

You can declare method parameters to be final, meaning that the value of the parameter will not change while the method is executing. Had bodyRef been declared final, the compiler would not have allowed you to change its value to null. When you do not intend to change a parameter's value, you can declare it final so the compiler can enforce this expectation. A final declaration can also protect against assigning a value to a parameter when you intended to assign to a field of the same name. The declaration can also help the compiler or virtual machine optimize some expressions using the parameter, because it is known to remain the same. A final modifier on a parameter is an implementation detail that affects only the method's code, not the invoking code, so you can change whether a parameter is final without affecting any invoking code.

Using Methods to Control Access

The Body class with its various constructors is considerably easier to use than its simple data-only form, and we have ensured that the idNum is set both automatically and correctly. But a programmer could still mess up the object by setting its idNum field after construction because the idNum field is public and therefore exposed to change. The idNum should be read-only data. Read-only data in objects is common, but there is no keyword to apply to a field that allows read-only access outside the class while letting the class itself modify the field.

To enforce read-only access, you must either make the field final, which makes it read-only for the lifetime of the object, or you must hide it. You hide the field by making the idNum field private and providing a new method so that code outside the class can read its value using that method:

class Body {
    private long idNum;    // now "private"

    public String name = "<unnamed>";
    public Body orbits = null;

    private static long nextID = 0;

    Body() {
        idNum = nextID++;
    }

    public long getID() {
        return idNum;
    }

    //...
}

Now programmers who want to use the body's identifier will invoke the getID method, which returns the value. There is no longer any way for programmers to modify the identifier—it has effectively become a read-only value outside the class. It can be modified only by the internal methods of the Body class.

Methods that regulate access to internal data are sometimes called accessor methods. Their use promotes encapsulation of the class's data.

Even if an application doesn't require fields to be read-only, making fields private and adding methods to set and fetch them enables you to add actions that may be needed in the future. If programmers can access a class's fields directly, you have no control over the values they will use or what happens when values are changed. Additionally, making a field part of the contract of a class locks in the implementation of that class—you can't change the implementation without forcing all clients to be recompiled. For example, a future version of Body may want to look-up the ID number in a database indexed by the body's name and not actually store the ID number in the object at all. Such a change cannot be made if idNum is accessible to clients. For these reasons, you will see very few public or protected fields in subsequent examples in this book.

Methods to get or set a value in an object's state are sometimes said to define a property of that object. For example, the Body class's getID can be said to define an ID property for Body objects that is retrieved by the getID method, and implemented by the idNum field. Some automatic systems, including those for the JavaBeans component architecture, use these conventions to provide automatic property manipulation systems; see Section 25.3 on page 721. We can and should define the name and orbits fields to be properties by making them private and providing set and get methods for them:

class Body {
    private long idNum;
    private String name = "<unnamed>";
    private Body orbits = null;

    private static long nextID = 0;

    // constructors omitted ...

    public long getID() { return idNum; }
    public String getName() { return name; }
    public void setName(String newName) {
        name = newName;
    }
    public Body getOrbits() { return orbits; }
    public void setOrbits(Body orbitsAround) {
        orbits = orbitsAround;
    }
}

Making a field final can be another way to prevent unwanted modifications to a field, but immutability and accessibility should not be confused. If a field is immutable then it should be declared final regardless of accessibility. Conversely, if you don't want a field to form part of the contract of a class you should hide it behind a method, regardless of whether the field is read-only or modifiable.

Now that we have made all the fields of Body private, we can return to an earlier remark that access control is per-class not per-object. Suppose that a body could be captured by another body and forced to orbit around it, we could define the following method in Body:

public void capture(Body victim) {
    victim.orbits = this;
}

If access control were per-object, then the capture method when invoked on one object would not be able to access the private orbits field of the victim body object to modify it. But because access control is per-class, the code of a method in a class has access to all the fields of all objects of that class—it simply needs a reference to the object, such as via a parameter as above. Some object-oriented languages advocate per-object access control, but the Java programming language is not one of them.

Exercise 2.13Make the fields in your Vehicle class private, and add accessor methods for the fields. Which fields should have methods to change them, and which should not?

Exercise 2.14Make the fields in your LinkedList class private, and add accessor methods for the fields. Which fields should have methods to change them, and which should not?

Exercise 2.15Add a changeSpeed method that changes the current speed of the vehicle to a passed-in value and add a stop method that sets the speed to zero.

Exercise 2.16Add a method to LinkedList to return the number of elements in a list.

this

You have already seen (on page 52) how you can use an explicit constructor invocation to invoke another one of your class's constructors at the beginning of a constructor. You can also use the special object reference this inside a non-static method, where it refers to the current object on which the method was invoked. There is no this reference in a static method because there is no specific object being operated on.

The this reference is most commonly used as a way to pass a reference to the current object as an argument to other methods. Suppose a method requires adding the current object to a list of objects awaiting some service. It might look something like this:

service.add(this);

The capture method in class Body also used this to set the value of the victim's orbits field to the current object.

An explicit this can be added to the beginning of any field access or method invocation in the current object. For example, the assignment to name in the Body class two-argument constructor

name = bodyName;

is equivalent to the following:

this.name = bodyName;

Conventionally, you use this only when it is needed: when the name of the field you need to access is hidden by a local variable or parameter declaration. For example, we could have written the two-argument Body constructor as

public Body(String name, Body orbits) {
    this();
    this.name = name;
    this.orbits = orbits;
}

The name and orbits fields are hidden from the constructor by the parameters of the same name. To access, for example, the name field instead of the name parameter, we prefix it with this to specify that the name is for the field belonging to “this” object. Deliberately hiding identifiers in this manner is considered acceptable programming practice only in this idiomatic use in constructors and “set” methods—some coding guidelines and development environments advocate never hiding identifiers, even in these cases. The way in which names are resolved is discussed in “The Meanings of Names” on page 178.

Overloading Methods

Each method has a signature, which is its name and the number and types of its parameters. Two methods can have the same name if they have different numbers or types of parameters and thus different signatures. This feature is called overloading because the simple name of the method has an overloaded (more than one) meaning. When you invoke a method, the compiler uses the number and types of arguments to find the best match from the available overloads. Here are some orbitsAround methods for our Body class that return true if the current body orbits around the specified body or a body with the specified identifier:

public boolean orbitsAround(Body other) {
    return (orbits == other);
}
public boolean orbitsAround(long id) {
    return (orbits != null && orbits.idNum == id);
}

Both methods declare one parameter, but the type of the parameter differs. If the method orbitsAround is invoked with a Body reference as an argument, the version of the method that declares a Body parameter is invoked—that version compares the passed in reference to the body's own orbits reference. If orbitsAround is invoked with a long argument, the version of the method that declares a long parameter is invoked—that version of the method compares the passed in identification number with the idNum field of the object it orbits. If the invocation matches neither of these signatures, the code will not compile.

For varargs methods, a sequence parameter T... is treated as being a parameter of type T[] for overloading purposes. So if two signatures differ only because one declares a sequence and the other an array, then a compile-time error occurs.

The signature does not include the return type or the list of thrown exceptions, and you cannot overload methods based on these factors. A full discussion of how the language chooses which available overloaded method to invoke for a given invocation can be found in “Finding the Right Method” on page 224.

As you may have realized, constructors can also be overloaded in the same way that methods are.

Overloading is typically used when a method or constructor can accept the same information presented in a different form, as in the orbitsAround example, or when it can use a number of parameters, some of which have default values and need not be supplied. For example, the Body class might have two overloaded constructors: one that takes a name and another Body object that it orbits around (as shown on page 55) and a second that takes only a name, indicating that body does not orbit another body.

You should use overloading judiciously and carefully. While overloading based on the number of arguments is usually quite clear, overloading based on the same number but different types of arguments can be confusing. When you add varargs methods to the mix, even overloading based on different numbers of parameters can become confusing when reading or writing invocations of those methods—does that two-argument invocation match two fixed parameters, or one fixed parameter and a sequence with one element, or just a sequence with two elements? For example, the following shows extremely poor use of overloading:

public static void print(String title) {
    // ...
}
public static void print(String title, String... messages) {
    // ...
}
public static void print(String... messages) {
    // ...
}

Given the invocation

print("Hello"); // which print ?

which print method is to be invoked? While not obvious, this example actually has clearly defined behavior: a fixed-argument method will always be selected over a varargs method—again, see “Finding the Right Method” on page 224 for details. So in this case it will invoke the print method that takes a single String parameter. In contrast, this invocation is ambiguous and results in a compile-time error:

print("Hello", "World");  // INVALID: ambiguous invocation

This could be a string and a one-element sequence, or a two-element sequence. The only way to resolve this ambiguity is to pass actual arrays if you intend to match with the sequence parameters:

print("Hello", new String[] {"World"});
print( new String[] { "Hello", "World" });

The first invocation now clearly matches the two-parameter print method, and the second clearly matches the single-sequence-parameter print method.

You can best avoid such situations by never overloading methods in a way that leads to such ambiguity.

Exercise 2.17Add two turn methods to Vehicle: one that takes a number of degrees to turn and one that takes either of the constants Vehicle.TURN_LEFT or Vehicle.TURN_RIGHT.

Importing Static Member Names

Static members should generally be referenced using the name of the class to which the static member belongs, as in System.out or Math.sqrt. This is usually the best practice because it makes clear what your code is referring to. But using the name of the class every time you reference a static field or method can sometimes make your code cluttered and more difficult to understand. For example, suppose you needed to define the mathematical function to calculate the hyperbolic tangent (tanh).[3] This can be calculated using the Math.exp function:

static double tanh(double x) {
    return (Math.exp(x) - Math.exp(-x)) /
           (Math.exp(x) + Math.exp(-x));
}

The appearance of Math throughout the expression is very distracting and certainly doesn't improve the readability of the code. To alleviate this problem you can tell the compiler that whenever you refer to the method exp, you mean the static method Math.exp. You do this with a static import statement:

import static java.lang.Math.exp;

A static import statement must appear at the start of a source file, before any class or interface declarations. Given the previous static import statement, anywhere we invoke a method exp in our source file it will be taken to mean Math.exp (assuming we don't hide it with another declaration of exp—see “The Meanings of Names” on page 178). Now we can rewrite our example more clearly as:

static double tanh(double x) {
    return (exp(x) - exp(-x)) /
           (exp(x) + exp(-x));
}

A static import statement consists of the keyword phrase importstatic , followed by the fully qualified name of the class or interface you are importing the static member from, a dot and then the static member's name. Like all statements it is terminated by a semicolon.

A static import on demand statement uses an asterisk (*) instead of a member name. This tells the compiler that if it finds names that it doesn't know about, it should look at the type given by the static import on demand statement to see if it has a static member by that name. If so, the compiler will assume that you intended to refer to the static member. For example, if our hyperbolic tangent function were part of a class that defined many mathematical functions and used many of the static methods and constants of the Math class, then we might use a static import on demand statement to import all of those method and constant names:

import static java.lang.Math.*;

When names can be imported in this way it is easier for naming conflicts to occur. There are various rules about how static import and static import on demand work with each other and with the other names used in your program. These are explained in more detail in “The Meanings of Names” on page 178.

The use of static imports can help the readability of your code, but if misused they can also make it harder to understand what your code is doing. When the reader sees Math.exp it is quite evident what is being referred to, but a simple exp is not so clear. If you have multiple static import on demand statements, the reader will have to look up each of the types to see if they have a static member named exp. As a general rule you should use static imports to improve the readability and clarity of your code, not to save yourself some typing.

The main Method

Details of invoking an application vary from system to system, but whatever the details, you must always provide the name of a class that drives the application. When you run a program, the system locates and runs the main method for that class. The main method must be public, static, and void (it returns nothing), and it must accept a single argument of type String[]. Here is an example that prints its arguments:

class Echo {
    public static void main(String[] args) {
        for (int i = 0; i < args.length; i++)
            System.out.print(args[i] + " ");
        System.out.println();
    }
}

The String array passed to main contains the program arguments. They are usually typed by users when they run the program. For example, on a command-line system such as UNIX or a DOS shell, you might run the Echo application this way:

java Echo in here

In this command, java is the Java bytecode interpreter, Echo is the name of the class, and the rest of the words are the program arguments. The java command finds the compiled bytecodes for the class Echo, loads them into a Java virtual machine, and invokes Echo.main with the program arguments contained in strings in the String array. The result is the following output:

in here

The name of the class is not included in the strings passed to main. You already know the name because it is the name of the class in which main is declared.

An application can have any number of main methods because each class in the application can have one. Which class's main method is used is specified each time the program is run, as Echo was.

Exercise 2.18Change Vehicle.main to create cars with owners whose names are specified on the command line, and then print them.

Native Methods

If you need to write a program that will use some existing code that isn't written in the Java programming language, or if you need to manipulate some hardware directly, you can write native methods. A native method lets you implement a method that can be invoked from the Java programming language but is written in a “native” language, usually C or C++. Native methods are declared using the native modifier. Because the method is implemented in another language, the method body is specified as a semicolon. For example, here is a declaration of a native method that queries the operating system for the CPU identifier of the host machine:

public native int getCPUID();

Other than being implemented in native code, native methods are like all other methods: they can be overloaded, overridden, final, static, synchronized, public, protected, or private. A native method cannot, however, be declared abstract or strictfp.

If you use a native method, all portability and safety of the code are lost. You cannot, for instance, use a native method in almost any code you expect to download and run from across a network connection (an applet, for example). The downloading system may or may not be of the same architecture, and even if it is, it might not trust your system well enough to run arbitrary native code.

Native methods are implemented using an API provided by the people who wrote the virtual machine on which the code executes. The standard one for C programmers is called JNI—Java Native Interface. Others are being defined for other native languages. A description of these APIs is beyond the scope of this book.

 

The significant problems we face cannot be solved by the same level of thinking that created them.

 
 --Albert Einstein


[1] Good naming is a key part of class design, and Body should really be called CelestialBody or something of that kind. We use Body for brevity, as we refer to it dozens of time throughout the book.

[2] More formally, the number of arguments that can be passed to a method invocation is known as the arity of the invocation. Varargs methods are variable-arity methods, while fixed-argument methods are fixed-arity methods.

[3] This function is part of the math library so there's no need to actually do this.

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

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