Learn how to store data in objects: The journey from Java wanna-be to Java developer continues
on
Get link
Facebook
X
Pinterest
Email
Other Apps
In the previous Java 101 column, I discussed Java as
an interpreted language and explained Java byte code and the Java
Virtual Machine. You learned how to set up Java on a system using the
Java 2 SDK, downloadable from Sun, and created your first simple Java
program. I then covered the basics of object-oriented programming, and
how it is centered around types and objects. Finally, I introduced the
class construct in Java, and you created a class that looks like this:
If you didn't read the last column, you'll probably want to review it before tackling this one.
Variables and primitive types
Though the snooze button is probably the most commonly used button on an alarm clock, the simple AlarmClock
class you've created so far is still missing some important
requirements. For instance, you have no way of manipulating how long the
alarm clock will stay in snooze mode. However, before you can do that,
you must take a more detailed look at how Java controls data.
Developers
use variables in Java to hold data, with all variables having a data
type and a name. The data type determines the values that a variable can
hold. You will see in the examples below how integral types hold whole
numbers, floating point types hold real numbers, and string types hold
character strings.
Called primitive types, integral and floating
point are the simplest data types that Java uses. The following program
illustrates the integral type, which can hold both positive and negative
whole numbers. This program also illustrates comments, which document
your code but don't affect the program in any way.
/*
* This is also a comment. The compiler ignores everything from
* the first /* until a "star slash" which ends the comment.
*
* Here's the "star slash" that ends the comment.
*/publicclassIntegerTest{publicstaticvoid main(String[] args){// Here's the declaration of an int variable called anInteger,// which you give an initial value of 100.int anInteger =100;// Declare and initialize anIntegerSystem.out.println(anInteger);// Outputs 100// You can also do arithmetic with primitive types, using the// standard arithmetic operators.
anInteger =100+100;System.out.println(anInteger);// Outputs 200}}
Java also uses floating point types, which can hold real numbers (numbers that include a decimal place).
Here is an example program:
publicclassDoubleTest{publicstaticvoid main(String[] args){// Here's the declaration of a double variable called aDouble.// You also give aDouble an initial value of 5.76.double aDouble =5.76;// Declare and initialize aDoubleSystem.out.println(aDouble);// Outputs 5.76// You can also do arithmetic with floating point types.
aDouble =5.76+1.45;System.out.println(aDouble);// Outputs 7.21}}
Try running the programs above. Remember, you have to compile them before you can run them:
javac *.java
java IntegerTest
java DoubleTest
Java uses four integral types and two floating point types, which
both hold different ranges of numbers and take up varying amounts of
storage space. The following table lists them, along with some of their
properties:
Integral types
Type
Size (Bits)
Range
byte
8
-128 to 127
short
16
-32,768 to 32,767
int
32
-2,147,483,648 to 2,147,483,647
long
64
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
Integral and floating point types
Floating point types
Type
Size (Bits)
Range
float
32
Single precision floating point (IEEE 754 conforming)
A string type holds strings, and handles them differently from the
way integral and floating point types handle numbers. The Java language
includes a String class to represent strings. You declare a string using the type String,
and initialize it with a quoted string, a sequence of characters
contained within double quotes, as shown below. You can also combine two
strings using the + operator.
// Code fragment// Declaration of variable s of type String,// and initialization with quoted string "Hello."String s ="Hello";// Concatenation of string in s with quoted string " World"String t = s +" World";System.out.println(t);// Outputs Hello World
Variable scope
In addition to type, scope is
also an important characteristic of a variable. Scope establishes when a
variable is created and destroyed and where a developer can access the
variable within a program. The place in your program where you declare
the variable determines its scope.
So far, I've discussed local variables,
which hold temporary data that you use within a method. You declare
local variables inside methods, and you can access them only from within
those methods. This means that you can retrieve only local variables anInteger, which you used in IntegerTest, and aDouble, which you used in DoubleTest, from the main method in which they were declared and nowhere else.
You can declare local variables within any method. The example code below declares a local variable in the AlarmClock snooze() method:
publicclassAlarmClock{publicvoid snooze(){// Snooze time in millisecond = 5 secslong snoozeInterval =5000;System.out.println("ZZZZZ for: "+ snoozeInterval);}}
You can get to snoozeInterval only from the snooze() method, which is where you declared snoozeInterval. Refer to the example below:
publicclassAlarmClockTest{publicstaticvoid main(String[] args){AlarmClock aClock =newAlarmClock();
aClock.snooze();// This is still fine.// The next line of code is an ERROR.// You can't access snoozeInterval outside the snooze method.
snoozeInterval =10000;}}
Method parameters
A
method parameter, which has a scope similar to a local variable, is
another type of variable. Method parameters pass arguments into methods.
When you declare the method, you specify its arguments in a parameter
list. You pass the arguments when you call the method. Method parameters
function similarly to local variables in that they lie within the scope
of the method to which they are linked, and can be used throughout the
method. However, unlike local variables, method parameters obtain a
value from the caller when it calls a method. Here's a modification of
the alarm clock that allows you to pass in the snoozeInterval.
publicclassAlarmClockTest{publicstaticvoid main(String[] args){AlarmClock aClock =newAlarmClock();// Pass in the snooze interval when you call the method.
aClock.snooze(10000);// Snooze for 10000 msecs.}}
Member variables -- how objects store data
Local
variables are useful, but because they provide only temporary storage,
their value is limited. Since their lifetimes span the length of the
method in which they are declared, local variables compare to a notepad
that appears every time you receive a telephone call, but disappears
once you hang up the phone. That setup can be useful for jotting down
notes, but you often want something a little more permanent. What's a
programmer to do? Enter member variables.
Member variables -- of which there are two, instance and static -- make up part of a class. I will consider instance variables now, and return to static variables in a later article.
Developers
implement instance variables to contain data useful to a class. An
instance variable differs from a local variable in the nature of its
scope and its lifetime. The entire class makes up the scope of an
instance variable, not the method in which it was declared. In other
words, developers can access instance variables anywhere in the class.
In addition, the lifetime of an instance variable does not depend on any
particular method of the class; that is, its lifetime is the lifetime
of the instance that contains it.
Remember
instances from the previous article? Instances are the actual objects
that you create from the blueprint you design in the class definition.
You declare instance variables in the class definition, affecting each
instance you create from the blueprint. Each instance contains those
instance variables, and data held within the variables can vary from
instance to instance.
Consider the AlarmClock class. Passing the snoozeInterval into the snooze()
method isn't a great design. Imagine having to type in a snooze
interval on your alarm clock each time you fumbled for the snooze
button. Instead, just give the whole alarm clock a snoozeInterval. You complete this with an instance variable in the AlarmClock class, as shown below:
publicclassAlarmClock{// You declare snoozeInterval here. This makes it an instance variable.// You also initialize it here.long m_snoozeInterval =5000;// Snooze time in millisecond = 5 secs.publicvoid snooze(){// You can still get to m_snoozeInterval in an AlarmClock method// because you are within the scope of the class.System.out.println("ZZZZZ for: "+ m_snoozeInterval);}}
You can access instance
variables almost anywhere within the class that declares them. To be
technical about it, you declare the instance variable within the class scope,
and you can retrieve it from almost anywhere within that scope.
Practically speaking, you can access the variable anywhere between the
first curly bracket that starts the class and the closing bracket. Since
you also declare methods within the class scope, they too can access
the instance variables.
You
can also access instance variables from outside the class, as long as
an instance exists, and you have a variable that references the
instance. To retrieve an instance variable through an instance, you use
the dot operator together with the instance. That may not be
the ideal way to access the variable, but for now, complete it this way
for illustrative purposes:
publicclassAlarmClockTest{publicstaticvoid main(String[] args){// Create two clocks. Each has its own m_snoozeIntervalAlarmClock aClock1 =newAlarmClock();AlarmClock aClock2 =newAlarmClock();// Change aClock2// You'll soon see that there are much better ways to do this.
aClock2.m_snoozeInterval =10000;
aClock1.snooze();// Snooze with aClock1's interval
aClock2.snooze();// Snooze with aClock2's interval}}
Try this program out, and you'll see that aClock1 still has its interval of 5,000 while aClock2 has an interval of 10,000. Again, each instance has its own instance data.
Don't
forget, the class definition is only a blueprint, so the instance
variables don't actually exist until you create instances from the
blueprint. Each instance of a class has its own copy of the instance
variables, and the blueprint defines what those
Encapsulation
Encapsulation
remains as one of the foundations of object-oriented programming. When
using encapsulation, the user interacts with the type through the
exposed behavior, not directly with the internal implementation. Through
encapsulation, you hide the details of a type's implementation. In
Java, encapsulation basically translates to this simple guideline:
"Don't access your object's data directly; use its methods."
That
is an elementary idea, but it eases our lives as programmers. Imagine,
for example, that you wanted to instruct a "person" object to stand up.
Without encapsulation, your commands could go something like this:
"Well, I guess you'd need to tighten this muscle here at the front of
the leg, loosen this muscle here at the back of the leg. Hmmm -- need to
bend at the waist too. Which muscles spark that movement? Need to
tighten these, loosen those. Whoops! Forgot the other leg. Darn. Watch
it -- don't tip over ..." You get the idea. With encapsulation, you
would just need to invoke the standUp() method. Pretty easy, yes?
Some advantages to encapsulation:
Abstraction of detail: The user interacts with a type at a higher level. If you use the standUp() method, you no longer need to know all the muscles required to initiate that motion.
Isolation from changes
:
Changes in internal implementation don't affect the users. If a person
sprains an ankle, and depends on a cane for a while, the users still
invoke only the standUp()
method. Correctness
:
Users can't arbitrarily change the insides of an object. They can only
complete what you allow them to do in the methods you write.
Here is a short example in which encapsulation clearly helps in a program's accuracy:
// Bad -- doesn't use encapsulationpublicclassPerson{int m_age;}publicclassPersonTest{publicstaticvoid main(String[] args){Person p =newPerson();
p.m_age =-5;// Hey -- how can someone be minus 5 years old?}}// Better - uses encapsulationpublicclassPerson{int m_age;publicvoid setAge(int age){// Check to make sure age is greater than 0. I'll talk more about// if statements at another time.if(age >0){
m_age = age;}}}publicclassPersonTest{publicstaticvoid main(String[] args){Person p =newPerson();
p.setAge(-5);// Won't have any effect now.}}
Even that simple program shows
how you can slip into trouble if you directly access the internal data
of classes. The larger and more complex the program, the more important
encapsulation becomes. And remember, many programs start out small and
then grow to last indefinitely, so design them correctly, right from the
beginning. To apply encapsulation to AlarmClock, you can just create methods to manipulate the snooze interval.
Before
I proceed, I should discuss methods in more detail. Methods can return
values that the caller uses. To return a value, declare a nonvoid return
type, and use a return statement. The getSnoozeInterval() method shown in the example below illustrates this.
Write the program
Okay
-- you're ready to manipulate the snooze interval. You do this by
adding get and set methods for the snooze interval. When you have an
instance variable like snoozeInterval, you will regularly call the get and set methods getSnoozeInterval() and setSnoozeInterval().
publicclassAlarmClock{long m_snoozeInterval =5000;// Snooze time in millisecond// Set method for m_snoozeInterval.publicvoid setSnoozeInterval(long snoozeInterval){
m_snoozeInterval = snoozeInterval;}// Get method for m_snoozeInterval.// Note that you are returning a value of type long here.publiclong getSnoozeInterval(){// Here's the line that returns the value.return m_snoozeInterval;}publicvoid snooze(){// You can still get to m_snoozeInterval in an AlarmClock method// because you are within the scope of the class.System.out.println("ZZZZZ for: "+ m_snoozeInterval);}}
publicclassAlarmClockTest{publicstaticvoid main(String[] args){// Create two clocks. Each has its own m_snoozeInterval.AlarmClock aClock1 =newAlarmClock();AlarmClock aClock2 =newAlarmClock();// Change aClock2. You use the set method.
aClock2.setSnoozeInterval(10000);
aClock1.snooze();// Snooze with aClock1's interval.
aClock2.snooze();// Snooze with aClock2's interval.}}
Defined now are two methods to
manipulate the snooze interval. One is used to get the snooze interval,
and the other is used to set it. That may seem trivial, but then, AlarmClock is a trivial class. In future columns, the class will grow in functionality and complexity.
Conclusion
You've covered a great deal of new ground. You looked at how to manipulate primitive types like int and double.
You examined local variables, method parameters, and variable scope.
You learned how to add data to classes using instance variables, and how
that data is contained in each instance. Finally, you explored
encapsulation and how it leads to better code.
Next time, you'll see some more of the control structures in Java, such as if and while statements, and learn how to enforce encapsulation with Java's access modifiers. You'll also study some of Java's built-in functionality, which can manipulate time, and use it to add more features to the AlarmClock class. To accomplish that, you'll delve more deeply into how one object uses another to complete its work.
double
64
Double precision floating point (IEEE 754 conforming)
Jacob Weintraub is founder and president of LearningPatterns.com
(LPc). Jacob has been working in object technologies since 1989,
and teaching Java since 1995. He authored LPc's Java for
Programmers, as well as many of its advanced courses, such as
those on OOAD and EJB.
Comments
Post a Comment