Everything is an object, Part 1

Learn to write your first Java program with these Java basics

Although it is based on C++, Java is more of a "pure" object-oriented language. Both C++ and Java are hybrid languages, but in Java the designers felt that the hybridization was not as important as it was in C++. A hybrid language allows multiple programming styles; the reason C++ is hybrid is to support backward compatibility with the C language. Because C++ is a superset of the C language, it includes many of that language's undesirable features, which can make some aspects of C++ overly complicated.

TEXTBOX: TEXTBOX_HEAD: Everything is an object: Read the whole series!

The Java language assumes that you want to do only object-oriented programming. This means that before you can begin you must shift your mindset into an object-oriented world (unless it's already there). The benefit of this initial effort is the ability to program in a language that is simpler to learn and to use than many other OOP languages. In this [two-part article] we'll see the basic components of a Java program and we'll learn that everything in Java is an object, even a Java program.

You manipulate objects with references

Each programming language has its own means of manipulating data. Sometimes the programmer must be constantly aware of what type of manipulation is going on. Are you manipulating the object directly, or are you dealing with some kind of indirect representation (a pointer in C or C++) that must be treated with a special syntax?

All this is simplified in Java. You treat everything as an object, so there is a single consistent syntax that you use everywhere. Although you treat everything as an object, the identifier you manipulate is actually a "reference" to an object. You might imagine this scene as a television (the object) with your remote control (the reference). As long as you're holding this reference, you have a connection to the television, but when someone says "change the channel" or "lower the volume," what you're manipulating is the reference, which in turn modifies the object. If you want to move around the room and still control the television, you take the remote/reference with you, not the television.

Also, the remote control can stand on its own, with no television. That is, just because you have a reference doesn't mean there's necessarily an object connected to it. So if you want to hold a word or sentence, you create a String reference:

String s;

But here you've created only the reference, not an object. If you decided to send a message to s at this point, you'll get an error (at run-time) because s isn't actually attached to anything (there's no television). A safer practice, then, is always to initialize a reference when you create it:

String s = "asdf";

However, this uses a special Java feature: strings can be initialized with quoted text. Normally, you must use a more general type of initialization for objects.

You must create all the objects

When you create a reference, you want to connect it with a new object. You do so, in general, with the new keyword. new says, "Make me a new one of these objects." So in the above example, you can say:

String s = new String("asdf");

Not only does this mean "Make me a new String," but it also gives information about how to make the String by supplying an initial character string.

Of course, String is not the only type that exists. Java comes with a plethora of ready-made types. What's more important is that you can create your own types. In fact, that's the fundamental activity in Java programming, and it's what you'll be learning about in the rest of this [article].

Where storage lives

It's useful to visualize some aspects of how things are laid out while the program is running, in particular how memory is arranged. There are six different places to store data:

  1. Registers. This is the fastest storage because it exists in a place different from that of other storage: inside the processor. However, the number of registers is severely limited, so registers are allocated by the compiler according to its needs. You don't have direct control, nor do you see any evidence in your programs that registers even exist.
  2. The stack. This lives in the general RAM (random-access memory) area, but has direct support from the processor via its stack pointer. The stack pointer is moved down to create new memory and moved up to release that memory. This is an extremely fast and efficient way to allocate storage, second only to registers. The Java compiler must know, while it is creating the program, the exact size and lifetime of all the data that is stored on the stack, because it must generate the code to move the stack pointer up and down. This constraint places limits on the flexibility of your programs, so while some Java storage exists on the stack-in particular, object references -- Java objects themselves are not placed on the stack.
  3. The heap. This is a general-purpose pool of memory (also in the RAM area) where all Java objects live. The nice thing about the heap is that, unlike the stack, the compiler doesn't need to know how much storage it needs to allocate from the heap or how long that storage must stay on the heap. Thus, there's a great deal of flexibility in using storage on the heap. Whenever you need to create an object, you simply write the code to create it using new, and the storage is allocated on the heap when that code is executed. Of course there's a price you pay for this flexibility: it takes more time to allocate heap storage than it does to allocate stack storage (that is, if you even could create objects on the stack in Java, as you can in C++).
  4. Static storage. "Static" is used here in the sense of "in a fixed location" (although it's also in RAM). Static storage contains data that is available for the entire time a program is running. You can use the static keyword to specify that a particular element of an object is static, but Java objects themselves are never placed in static storage.
  5. Constant storage. Constant values are often placed directly in the program code, which is safe since they can never change. Sometimes constants are cordoned off by themselves so that they can be optionally placed in read-only memory (ROM).
  6. Non-RAM storage. If data lives completely outside a program it can exist while the program is not running, outside the control of the program. The two primary examples of this are streamed objects, in which objects are turned into streams of bytes, generally to be sent to another machine, and persistent objects, in which the objects are placed on disk so they will hold their state even when the program is terminated. The trick with these types of storage is turning the objects into something that can exist on the other medium, and yet can be resurrected into a regular RAM-based object when necessary. Java provides support for lightweight persistence, and future versions of Java might provide more complete solutions for persistence.

Special case: primitive types

There is a group of types that gets special treatment; you can think of these as "primitive" types that you use quite often in your programming. The reason for the special treatment is that to create an object with new --especially a small, simple variable -- isn't very efficient because new places objects on the heap. For these types Java falls back on the approach taken by C and C++. That is, instead of creating the variable using new, an "automatic" variable is created that is not a reference. The variable holds the value, and it's placed on the stack so it's much more efficient.

Java determines the size of each primitive type. These sizes don't change from one machine architecture to another as they do in most languages. This size invariance is one reason Java programs are so portable.

Primitive types
Primitive typeSizeMinimumMaximumWrapper type
boolean---Boolean
char16-bitUnicode 0Unicode 216- 1Character
byte8-bit-128+127Byte
short16-bit-215+215-1Short
int32-bit-231+231-1Integer
long64-bit-263+263-1Long
float32-bitIEEE754IEEE754Float
double64-bitIEEE754IEEE754Double
void---Void

All numeric types are signed, so don't go looking for unsigned types. The size of the boolean type is not explicitly defined; it is only specified to be able to take the literal values true or false.

The primitive data types also have "wrapper" classes for them. That means that if you want to make a nonprimitive object on the heap to represent that primitive type, you use the associated wrapper. For example:

char c = 'x';
Character C = new Character(c);

Or you could also use:

Character C = new Character('x');

The reasons for doing this [are shown in the book].

High-precision numbers

Java includes two classes for performing high-precision arithmetic: BigInteger and BigDecimal. Although these approximately fit into the same category as the "wrapper" classes, neither one has a primitive analogue.

Both classes have methods that provide analogues for the operations that you perform on primitive types. That is, you can do anything with a BigInteger or BigDecimal that you can with an int or float, it's just that you must use method calls instead of operators. Also, since there's more involved, the operations will be slower. You're exchanging speed for accuracy.

BigInteger supports arbitrary-precision integers. This means that you can accurately represent integral values of any size without losing any information during operations.

BigDecimal is for arbitrary-precision fixed-point numbers; you can use these for accurate monetary calculations, for example.

Consult your online documentation for details about the constructors and methods you can call for these two classes.

Arrays in Java

Virtually all programming languages support arrays. Using arrays in C and C++ is perilous because those arrays are only blocks of memory. If a program accesses the array outside of its memory block or uses the memory before initialization (common programming errors) there will be unpredictable results.

One of the primary goals of Java is safety, so many of the problems that plague programmers in C and C++ are not repeated in Java. A Java array is guaranteed to be initialized and cannot be accessed outside of its range. The range checking comes at the price of having a small amount of memory overhead on each array as well as verifying the index at run-time, but the assumption is that the safety and increased productivity is worth the expense.

When you create an array of objects, you are really creating an array of references, and each of those references is automatically initialized to a special value with its own keyword: null. When Java sees null, it recognizes that the reference in question isn't pointing to an object. You must assign an object to each reference before you use it, and if you try to use a reference that's still null, the problem will be reported at run-time. Thus, typical array errors are prevented in Java.

You can also create an array of primitives. Again, the compiler guarantees initialization because it zeroes the memory for that array.

Arrays are covered in detail [in the book].

You never need to destroy an object

In most programming languages, the concept of the lifetime of a variable occupies a significant portion of the programming effort. How long does the variable last? If you are supposed to destroy it, when should you? Confusion over variable lifetimes can lead to a lot of bugs, and this section shows how Java greatly simplifies the issue by doing all the cleanup work for you.

Scoping

Most procedural languages have the concept of

scope

. This determines both the visibility and lifetime of the names defined within that scope. In C, C++, and Java, scope is determined by the placement of curly braces

{}

. So for example:

{
  int x = 12;
  /* only x available */
  {
    int q = 96;
    /* both x & q available */
  }
  /* only x available */
  /* q "out of scope" */
}

A variable defined within a scope is available only to the end of that scope.

Indentation makes Java code easier to read. Since Java is a free-form language, the extra spaces, tabs, and carriage returns do not affect the resulting program.

Note that you cannot do the following, even though it is legal in C and C++:

{
  int x = 12;
  {
    int x = 96; /* illegal */
  }
}

The compiler will announce that the variable x has already been defined. Thus the C and C++ ability to "hide" a variable in a larger scope is not allowed because the Java designers thought that it led to confusing programs.

Scope of objects

Java objects do not have the same lifetimes as primitives. When you create a Java object using

new

, it hangs around past the end of the scope. Thus if you use:

{
  String s = new String("a string");
} /* end of scope */

the reference s vanishes at the end of the scope. However, the String object that s was pointing to is still occupying memory. In this bit of code, there is no way to access the object because the only reference to it is out of scope. In [the book] you'll see how the reference to the object can be passed around and duplicated during the course of a program.

Related:
1 2 Page 1
Page 1 of 2