http://www.developer.com/

Back to article

Primitives and Object Wrappers


June 6, 2006

This series, The Object-Oriented Thought Process, is intended for someone just learning an object-oriented language and who wants to understand the basic concepts before jumping into the code, or someone who wants to understand the infrastructure behind an object-oriented language he or she is already using. These concepts are part of the foundation that any programmer will need to make the paradigm shift from procedural programming to object-oriented programming.

Click here to start at the beginning of the series.

In keeping with the code examples used in the previous articles, Java will be the language used to implement the concepts in code. One of the reasons that I like to use Java is because you can download the Java compiler for personal use at the Sun Microsystems Web site http://java.sun.com/. You can download the standard edition, J2SE 5.0, at http://java.sun.com/j2se/1.5.0/download.jsp to compile and execute these applications. I often reference the Java J2SE 5.0 API documentation and I recommend that you explore the Java API further. Code Listings are provided for all examples in this article as well as Figures and output (when appropriate). See the first article in this series for detailed descriptions for compiling and running all the code examples.

In previous columns, you covered the concept of object wrappers. This is actually one of the most important, and interesting, object-oriented design methodologies. Perhaps the most elegant aspect of object-oriented design is when it comes to integrating with other technologies, specifically older, more established technologies. In fact, one of the hottest job opportunities in Information Technologies is that of integrating legacy applications with object-oriented technologies.

For example, because much of today's business data is kept on mainframes, and it will most likely be staying put, the ability to use a Web front end to interface with this legacy data is very important. Wrapping mainframe technologies in objects is a powerful mechanism form combining the two methodologies. Likewise, as you saw with the client-server systems of earlier articles in this series, using object wrappers to hide the hardware implementation provides a great advantage.

Interestingly, one of the best examples of object wrappers is also one of the simplest. Because the main purpose of this series is to explore the underlying concepts of object-oriented technologies, it is quite helpful to explore the relationship between primitives and objects.

Primitives

When experienced programmers begin to learn object-oriented techniques, one of the first stumbling bocks encountered is the concept that everything is an object—well almost everything. In some languages, such as Java, the universe is really divided into two camps, objects and primitives (in other o-o language architectures, although the primitives are there, the programmer must access them via object wrappers).

Sun Microsystems's Java tutorial defines a primitive as: A variable of primitive type contains a single value of the appropriate size and format for its type: a number, a character, or a boolean value. For example, an integer value is 32 bits of data in a format known as two's complement, the value of a char is 16 bits of data formatted as a Unicode character, and so on.

The primitives are called built-in types, and there are eight of them:

  • boolean
  • int
  • long
  • byte
  • short
  • float
  • double
  • char

One of the problems that C programmers had was that the byte size of the primitive types differed among the various platforms. This led to a great amount of grief, or at least a lot of work, for a programmer wanting to port a program from one machine to another.

For example, there were times when I was porting C/C++ programs to different machines that had integer sizes of 16, 32, and 64 bits. This led to a lot of conditional compiling and made the code more vulnerable to bugs. In short, it was the programmer's responsibility to adjust to the variety of the platforms. This was not the optimal solution. The Java architecture allows the programmer to treat all primitives the same on all platforms. Table 1 shows the storage requirements for the various primitives.

boolean undefined
byte signed 8-bit integer
short signed 16-bit integer
int signed 32-bit integer
long signed 64-bit integer
float signed 32-bit floating-point
double signed 64-bit floating-point
char 16-bit Unicode 2.0 character

Table 1

These primitives fall into three basic categories, the boolean type, the common numeric types (byte, short, int, long, float, and double) and the char type. I like to categorize them in groupings because the boolean and char types require a bit more explanation.

boolean

In the case of the boolean type, the explanation is really due to the fact that the boolean is not really what it appears to be. Because a boolean only has two possible values, true and false, it may seem obvious that a single bit is all that is required to implement the boolean. Yet, as most C programmers know, there was never an actual boolean type in the original C specification.

Yet, the concept of a boolean type was quite common in C programs. When a C programmer needed a boolean type, the programmer simply used an integer to do the job. To mimic the functionality of the boolean values true and false, a simple coding trick was performed:

#define TRUE 1
#define FALSE 0

In effect, this code defines TRUE and FALSE to the values 1 and 0 respectively. With this in place, the programmer can now use traditional Boolean logic.

if (flag == TRUE) {
   ...do something
}

Although this workaround provides the functionality that we need, it has one drawback—it wastes space. This may well be a trivial problem, but the fact remains that you are using at minimum 8 bits, where only a single bit is required (this assumes that the compiler is implementing the boolean as a byte).

In fact, Java uses the same approach, although behind the scenes. There are efficiency reasons why a single bit is not used to implement a boolean. The compiler/virtual machine/operating systems are not designed to access individual bits. Thus, while you might be seeing bits, you are actually getting bytes simulating bits. As you can see in the code in Listing 1, you actually define a boolean type.

public class Primitives {

   public static void main(String[] args) {

      boolean flag = true;

      if (flag = = true)System.out.println("flag is true");

   }

}

Listing 1

char

The char type is the only non-numeric primitive, and is stored as a 16-bit unsigned value. Despite the fact that a char represents a single character, each char is stored as a numeric code that represents a specific character. Java represents characters in a 16-bit Unicode whereas earlier languages have used 8-bit codes such as ASCII. Unicode allows programming languages to handle a wider character set and support several languages using various alphabets. You can get a great idea of the various alphabet choices, as well as their definitions, by visiting: http://www.unicode.org/charts/.

The code in Listing 2 is quite interesting.

public class Primitives {

   public static void main(String[] args) {

      char c = 'a';

      short num = 97;

      System.out.println("c = " + (short)c);
      System.out.println("num = " + (char)num);

   }
}

Listing 2

There are two primitives declared:

char c = 'a';
int num = 97;

The numeric code for the character 'a' is 97. When you cast the char to an int and print it, you get the value 97.

System.out.println("c = " + (int)c);

Conversely, you can assign an int to the value of 97 and then cast it to a char. When you print this char, you do indeed get the character 'a'.

System.out.println("num = " + (char)num);

When executed, the output produced by the code in Listing 2 is displayed in Figure 1.

Figure 1

Another interesting feature with the codes is the relationship between lowercase and uppercase letters. Take a look at Listing 3.

public class Primitives {

   public static void main(String[] args) {

      char c1 = 'a';
      char c2 = 'A';

      char c3 = 'b';
      char c4 = 'B';

      System.out.println("a = " + (short)c1);
      System.out.println("A = " + (short)c2);

      System.out.println("b = " + (short)c3);
      System.out.println("B = " + (short)c4);

      System.out.println("a diff = " + ((short)c1 - (short)c2));
      System.out.println("b diff = " + ((short)c3 - (short)c4));

   }

}

Listing 3

In this example, there are two sets of chars defined. One set is a lowercase 'a' and an uppercase 'A'. The second is a lowercase 'b' and an uppercase 'B'. Not surprisingly, the lowercase and uppercase chars have different numeric codes. Of even greater interest is that the difference between the lowercase and uppercase values of both the 'a' set and the 'b' set are the same (the value 32). The way the code tables are set up, this ration is set for all of the lowercase and uppercase letters.

When the code is executed, you get the results in Figure 2.

Figure 2

Although there are only a limited number of characters on the keyboard, there is a much richer set available to the programmer. For example, if you look at the code table you will find that the value for PI symbol is 227. You then can print this out programmatically, with the code in Listing 4.

public class Primitives {

   public static void main(String[] args) {

      char c = 'a';

      short num = 227;    // The code for PI

      System.out.println("c = " + (short)c);
      System.out.println("num = " + (char)num);

   }
}

Listing 4

When you run the code, as seen in Figure 3, the actual π symbol is displayed. Thus, you do have a wide variety of symbols that can be used.

Figure 3

Characters also can be escape sequences. The original octal escape sequences look like the familiar newline escape sequence that harkens back to the days of the C Programming Language:

char c2 = 'n';    \ ASCII escape sequence for newline

These types of escape sequences are supported, as well as the Unicode escape sequences that are in the following format:

char c1 = 'u0022';    \ Unicode escape sequence for double quote

Listing 5 shows how these escape sequences are used within the code.

public class Primitives {

   public static void main(String[] args) {

      char c = 'a';

      short num = 227;    // The code for PI

      System.out.println("c = " + (short)c);
      System.out.println("num = " + (char)num);

   }
}

Listing 5

The output from the code is shown in Figure 4. Notice that a double quote is displayed and that there is an extra line before the last line of output.

Figure 4

Strings

The Java primitives that you just covered are not objects. By definition, objects have attributes and behaviors, which the primitive data types don't have. Other than these primitives, everything else in Java is an object. Whereas a character represents a single Unicode value, you can treat a string as an object that contains a collection of characters.

In fact, to obtain the functionality of a string, the C Programming Language used a character array to contain the individual units of the string.

char string[100];

You would literally manipulate the string as a collection of characters.

C++ provided some extensions, specifically a string class (using the The C++ Standard Template Library (STL)) that effectively wrapped the functionality of the character array into a nice package.

#include <string>
using std::string;

string s = "abc def abc";

Java has a class called String that was part of the original Java specification. As I continue the discussion on wrappers, you can think of a String class as a wrapper. It wraps the functionality of the character array that I mentioned earlier in a class. You then can create objects from this String class.

String s = "abc";

This String class provides the attributes and various methods that provide certain string manipulation functionality. The String class effectively wraps most of the functionality that you would need to process a string. For example, Listing 6 shows how the String method toUpperCase() works to manipulate the String object created.

public class Primitives {

   public static void main(String[] args) {

      String s1 = "abc";

      String s2 = s1.toUpperCase();

      System.out.println("s1 = " + s1);
      System.out.println("s2 = " + s2);

   }
}

Listing 6

The output from the code is shown in Figure 5. In this example, there are actually two strings created, s1 and s2. The following code does not really change the original string; it uses s1 to create a new string using the toUpperCase()method.

String s2 = s1.toUpperCase();

Figure 5

You would get the same behavior by simply reusing the original string, as seen in Listing 7. Even in this case, there actually are two strings being created. Rather than transform the original string, this code creates a second string in memory, despite the fact that this is not apparent by examining the code. Once the second string is created, the reference s1 is literally re-pointed to the new string in memory. At this point, the memory for the original string is essentially an orphan. Once this happens, the garbage collector then reclaims this orphaned memory.

public class Primitives {

   public static void main(String[] args) {

      String s1 = "abc";

      s1 = s1.toUpperCase();

      System.out.println("s1 = " + s1);

   }
}

Listing 7

In the same vein, strings are not processed at the character level, but with methods provided by the String class. One of the most common behaviors is that of comparing strings. The example in Listing 8 shows one of the ways to use the equals() method of the String class.

public class Primitives {

   public static void main(String[] args) {

      String s1 = "abc";
      String s2 = "abd";

      if (s1.equals(s2)) {
         System.out.println("strings are the same");
      }else {
         System.out.println("strings are not the same");
      }

   }
}

Listing 8

The output in Figure 6 shows what happens when you execute this code. If the two strings are equal, the binary value of true is returned. In this example, the strings are not identical, so the binary value of false is returned.

Figure 6

Primitive Wrappers

As was mentioned earlier, the Java primitives are not objects. Although Java is considered an object-oriented language, obviously not everything is an object, as is the case in some other object-oriented languages. However, all the primitive types have a corresponding wrapper class. Many of these classes are spelled the same; however, the first character in the name of the wrapper class is always capitalized. The key is to realize that these wrappers are all classes.

There are several reasons why you would want to use a wrapper class; however, the most important may be that there are some situations where you must treat everything as an object. Because an int, for example, is not an object, the next best thing is to wrap it inside an object. The Integer class is used as this wrapper.

When I was first learning Java, use of the Integer class helped me understand the concept of a wrapper. I encountered this the first time I wanted to convert an argument from the command line. In C/C++, I could process the character string in the argument list directly by using the atoi() function.

void main( int argc, char *argv[] ) {

   cout<<"Convert to an integer: "<<atoi(argv[0]);

}

In Java, an approach like this won't work. Because everything, in effect, is treated like an object, you can't process a primitive in this way. To convert a string to an int, you must first create an Integer object wrapper and then use the behaviors of this class to do the conversion as shown in Listing 9.

public class Primitives {

   public static void main(String[] args) {

      Integer i = new Integer(0);

      int val = Integer.valueOf(args[0]);

      System.out.println("val = " + val);

   }
}

Listing 9

The output from this code is presented in Figure 7. Notice that the string "100" is converted to an int 100 and then added to the constant value 2.

Figure 7

In reality, if all you need is to convert the number, and not create a whole new object, you can use the class in static fashion. In short, you can statically use the functionality of the Integer class by specifying the Integer class instead of an actual object. Listing 10 shows how this is accomplished.

public class Primitives {

   public static void main(String[] args) {

      int val = Integer.valueOf("100");

      System.out.println("val = " + (val + 2) );

   }
}

Listing 10

The Java specification at http://java.sun.com/j2se/1.3/docs/api/java/lang/Integer.html shows that the valueOf() method is declared as static; this allows a programmer to access the method at the class level. This is true for many methods of the wrapper classes.

static Integer valueOf(String s)
          Returns a new Integer object initialized to the value of the specified String.

When you execute the code in Listing 10, you must provide an argument, as shown in Figure 8.

Figure 8

JDK 1.5 Enhancements

In the JDK 1.5 release, the compiler "recognizes the relationship between primitive variables and objects of their corresponding wrapper type". [1] By using techniques called autoboxing and unboxing, you can deal with wrappers more intuitively.

Autoboxing

Rather than having to explicitly create an Integer wrapper as in earlier versions of the SDK,

Integer wrapper = new Integer(0);

you now can let the compiler deal it, as you can see in Listing 10.

public class Primitives {

   public static void main(String[] args) {

      Integer wrapper = 33;

      System.out.println("wrapper = " + wrapper );

   }
}

Listing 10

Unboxing

Unboxing is the complimentary action to autoboxing. Again, rather than explicitly declaring the wrapper,

Integer wrapper = new Integer(0);

you can let the compiler deal with the details. In Listing 11, you create an Integer wrapper and then directly set an int to the wrapper itself.

public class Primitives {

   public static void main(String[] args) {

      Integer wrapper = 33;

      int i = wrapper;

      System.out.println("i = " + i );

   }
}

Listing 11

Although autoboxing and unboxing are very convenient, be aware that in both cases, the compiler actually will generate similar bytecodes to the pre JDK 1.5 syntax.

Conclusion

In this article, you explored the various relationships among Java primitives, objects, and object wrappers. These are important concepts because the use of object wrappers is a very powerful technique and used in many applications. One of the more common uses of object wrappers is to wrap operating system functionality such as interfaces with hardware. The use of Sockets that you saw in an earlier article is a good example of how specific functionality is wrapped, and thus transparent, to the Java programmer.

The topic of wrappers also is a very good way to understand what object concepts are all about. Because these wrappers contain attributes and behaviors, they a very good example of how objects are packaged.

References


[1] Just Java 2, 6th Edition. Peter van der Linden. Page 55.

About the Author

Matt Weisfeld is a faculty member at Cuyahoga Community College (Tri-C) in Cleveland, Ohio. Matt is a member of the Information Technology department, teaching programming languages such as C++, Java, and C# .NET as well as various web technologies. Prior to joining Tri-C, Matt spent 20 years in the information technology industry gaining experience in software development, project management, business development, corporate training, and part-time teaching. Matt holds an MS in computer science and an MBA in project management. Besides The Object-Oriented Thought Process
, which is now in it's second edition, Matt has published two other computer books, and more than a dozen articles in magazines and journals such as Dr. Dobb's Journal, The C/C++ Users Journal, Software Development Magazine, Java Report, and the international journal Project Management. Matt has presented at conferences throughout the United States and Canada.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date