Bit Fields

A bit field is a memory-saving tool that was developed early in the history of computer science. It saves memory by combining several true-false options into one byte. For example, a car may have several options that are either present or not present. These true or false options can be grouped into a bit field rather than using an entire byte for each of the options, which would be the case if you used booleans. The options in the bit field are represented as set or unset according to the status of a specific bit in the byte. These options can then be checked and compared using logical operators. One implementation of the hypothetical Car class is shown in Example 7-3.

Example 7-3. A car class that uses bit field constants
package oracle.hcj.constants;
public class Car {
 public final static int POWER_WINDOWS = 1;
 public final static int POWER_LOCKS = 2;
 public final static int SNOW_TIRES = 4;
 public final static int STANDARD_TIRES = 8;
 public final static int CRUISE_CONTROL = 16;
 public final static int CD_PLAYER = 32;
 public final static int AIR_CONDITIONING = 64;
 private int options;
 public Car( ) {
 }
 public void setOptions(final int options) {
 this.options = options;
 }
 public int getOptions( ) {
 return this.options;
 }
}


In this example, a car can have several options, such as power windows and air conditioning. Each of the options are combined into a bit field and stored in the integer variable options. The bit field constants, such as POWER_WINDOWS, define which bit, within the options variable, represents that particular option. Since bits in a byte ascend in powers of two, these bit field constants will always be in powers of two as well. This is one easy way to distinguish bit fields from other types of constants. If you want to determine whether the car has power windows, all that matters is the zero bit in the options integer variable. In Screenshot-1, a bit field is mapped out in memory.

Screenshot-1. A memory map of a bit field
Java figs/HCJ_0701.gif

The zero bit in the field corresponds to the option POWER_WINDOWS. When that bit is set, the car has power windows. On the other hand, when the zero bit isn't set, the car does not have power windows. The situation is similar for each of the successive bits in the bit field. The car shown in Screenshot-1 has cruise control, standard tires, and power door locks.

Altering Bit Fields

To change the options on your car, use logical operators:

package oracle.hcj.constants;
public class BitFieldDemo {
 public void setBitFields( ) {
 this.myCar = new Car( );
  myCar.setOptions(Car.CRUISE_CONTROL | 
 Car.STANDARD_TIRES | 
 Car.POWER_LOCKS);
 System.out.println(myCar.getOptions( ));
 }
}


The result of the combination of options shown here will produce the bit field mapped in Screenshot-1. To create the bit field, use a logical OR (the | operator) operator to combine the various bits into a single number and then set this number in the options variable. The options variable will then contain the number 26, (CRUISE_CONTROL + STANDARD_TIRES + POWER_LOCKS, or 16 + 8 + 2). This encoding is the primary reason why bit field constants can't be sequential—since bit fields are binary, a value of 3 would mean that the zero and one bits are both set. This would lead to ambiguous results. If you want to remove standard tires from your hypothetical car, you can use an exclusive OR (XOR—the ^ operator) operator:

package oracle.hcj.constants;
public class BitFieldDemo {
 public void clearStandardTires( ) {
 myCar.setOptions(myCar.getOptions( ) ^ Car.STANDARD_TIRES);
 System.out.println(myCar.getOptions( )); }
}


This piece of code removes the standard tires from the bit field. By applying an XOR against the field, you clear the set bit. However, realize that if the bit hadn't been set, you would have just set it. The only way to clear an entire bit, whether the bit is set or not, is to create a bit field mask of all bits except for the one you want to clear. After you create the mask, you must use a logical AND operator to combine the mask and the target bit field:

myCar.setOptions(myCar.getOptions( ) & (Car.POWER_WINDOWS |
 Car.POWER_LOCKS |
 Car.SNOW_TIRES |
 Car.CRUISE_CONTROL |
 Car.CD_PLAYER |
 Car.AIR_CONDITIONING));


Since the only bit left unset in the call was STANDARD_TIRES, that bit was zero. When you use a logical AND against the current setting of the bit fields, you can be sure that the bit was removed. Setting a bit is simply a matter of using the OR operator again, as when the bit field was created (see Screenshot-1).

Comparing Bit Fields

Comparing bit fields can also be done with logical operators. For example, if you want to know whether a car has power windows and power locks, you can use the code in Example 7-4.

Example 7-4. Examining bit fields
package oracle.hcj.constants;
public class BitFieldDemo {
public void printCarOptions( ) {
 System.out.println("-- Options --");
 if ((myCar.getOptions( ) & Car.POWER_WINDOWS) > 0) {
 System.out.println("Power Windows");
 }
 if ((myCar.getOptions( ) & Car.POWER_LOCKS) > 0) {
 System.out.println("Power Locks");
 }
 }
}


This code uses logical AND operators to compare the bit field against the bit field constants. Since the bit field constant contains 0 for all bits except its target bit, the AND operation will result in a 1 or a 0. In this case, the car has power locks only, so the method would output the following:

-- Options --
Power Locks


Problems with Bit Fields

Although bit fields are an interesting concept, you may wonder what relevance they have to Java. The fact is, bit fields perform several different functions inside the JDK. One of their most prominent functions occurs in the various reflection methods, in which they are used to store the modifiers for a field, class, constructor, or method. There is a bit for abstract, a bit for final, and so on. However, the modifiers bit field reveals a glaring problem with the bit field concept itself.

Mutual exclusivity

In the Car class, there is a bit for standard tires and another for snow tires. However, a car can have only one or the other. If someone set the bits for both types of tires, the data would be corrupted. In fact, this is a common misuse of the bit field concept. If you think the previous example is contrived, consider the modifiers with regards to reflection. Example 7-5[1] is a snippet from the JDK that shows the definitions of these modifiers.

[1] From the JDK source, © 2002 by Oracle.

Example 7-5. Modifier snippet from the JDK
package java.lang;
public class Modifier {
 public static final int PUBLIC = 0x00000001;
 public static final int PRIVATE = 0x00000002;
 public static final int PROTECTED = 0x00000004;
}


In this case, the JDK assigns different bits for each of the visibility specifiers. The problem with this is that the options represented by these bits are mutually exclusive. A method, class, constructor, or attribute cannot be both private and protected at the same time. Fortunately, in the case of the JDK, it's impossible to set the modifiers programmatically on a method, class, constructor, or attribute because the virtual machine creates these bit fields and doesn't let you change them. However, the Car class is not so fortunate. In fact, determining the type of tires on the car would have been easier if it was done with constants and properties. You could use various tire constants to name the possible tire configurations, and then use a property, tireType, to hold one of these constants. Although this would fix the mutual exclusivity problem, there would still be a bigger problem that you haven't fixed yet.

Limited expandability

The bit field may work for now, but what if the car company comes along and tells you that it has added a tape player to the list of options for the car. In this case, you can use bit seven for a TAPE_PLAYER constant. However, if all of the department heads call a meeting the next day in which they decide to add 75 more options to the car, you will have a problem. Since an int doesn't have 82 bits, there is no more space left in the bit field. Suddenly, you have a large refactoring job on your hands. Not only may you have to change hundreds of lines of code, you may also have to tell the very unhappy departments that they need to wait six months for their new options. The problem with bit fields is that they have limited space, while companies have no limit on the number of things they can change in your program. Since you must expect companies to change your requirements, using bit fields is not an optimal solution. Even if there were no problems in changing code, bit fields would still not be a good idea because they violate the basic object-oriented concept of encapsulation.

Improper encapsulation

Object-oriented engineering is based on the concept of encapsulation—that is, whatever is in an object should be hidden from the class's user. Bit fields are an example of poor encapsulation because they expose the internal implementation of the storage of options to the user. They also combine multiple attributes into one, which muddles the interface. When a user wants to know whether a class has power door locks, he shouldn't have to decode the information from several other options. A class has a certain set of attributes and behaviors. Combining multiple attributes into one is just bad design. If, instead, you modeled your car options differently, you would be able to preserve the concepts of object-oriented engineering and have an unlimited amount of options. You could, for example, make a set in the car class and use that set to store non-mutually exclusive options for your car. Bit fields are just far too cryptic and constraining to be of much use in Java apps. The only time you should use them is when you truly have no other choice. However, they are something that you will have to deal with frequently in legacy code. Whenever you get the chance to convert them to option constants, I highly advise you do so.

      
Comments