|
|
What is Java? Before we can really answer that, we have to answer another question: What is the Object-Oriented Paradigm?The Object-Oriented Paradigm, or OOP, is a method of coding which results in better, more reliable and more reusable code. "The fundamental idea behind object-oriented languages is to combine into a single unit both data and the functions that operate on that data. Such a unit is called an object." [LaFore, 1995] By using objects, we can model the real world in a more natural fashion, and we also achieve some other useful side effects. An example of OOP v/s Structured programming:Let's say we have to write code that will model a store. We have to keep track of customers, how much money the customers have, and how much the customers have bought. We have to keep track of the sales clerks, where they are and when their shifts are. And we have to keep track of the merchadise, how much we have and where it is. First we'll look at a Structural method. To keep track of the customers and their money and purchases, we'll need an array of structures. Same for the clerks, and same for the merchandise. That pretty much takes care of the data.
Now, let's look at an OOP implementation. We'll define three objects, Customer, Clerk, and Merchandise. The Customer object will have a Money variable and a Purchases array. The Customer object will also have a Spend method and a Buy method to update the money and purchases, respectively. The Clerk object will have a Location variable and Shift variable. It will have a Move method and a CheckShift method. The Merchandise object will have a Price variable and a Location variable. It will have Move and Sell methods. We will have to have three arrays, one for each object type. The methods will be visable to the control structure, but the data will be hidden, or encapsulated, within each object. So, what's the difference? They both have pretty much the same stuff; it's just arranged a little differently. Well, the different arrangement is what makes OOP more powerful. The first obvious advantage is that the data is hidden. You cannot accidentally alter the data for a customer while fiddling with something else. You can only access one of the object's data by directly going through that object. Another advantage is reusablilty of the code. Say, for instance, we now have to simulate an assembly line. One of the things we'll need to keep track of is where the workers are, and who's currently working. We can just borrow the Clerk object from our current model and plop it in our new project without any modification! It already knows how to keep track of where it is, and when it's shifts negin and end. That's all we need for an assembly-line worker. Try doing that with the Structured code! You'd have to copy out the code for the structure, the code for each of the relevant functions, and modify the functions to fit the new array name, along with some other house keeping. OOP allows you to bundle together related data and functions so that they can be easily moved around and reused. Yet another advantage is in maintenance. Say we want to be more specific now. We want to model a grocery store. And, we don't want to keep track of just "merchandise." We want to keep track of meat, fruits and vegatebles, and dry goods.
With OOP we could take advantage of a property called inheritance. Inheritance allows
an object to inherit code from another object, saving the time of writing all new code. Then, we could further specifiy each new object. For example, for Meat, we could add a
SellDate variable and a Spoiled method so that we can measure losses from spoilage. Now, the last real advantage we'll talk about is called polymorphism. The best way to
explain this will be by example. When we create a new Clerk, we want to be able to initialize his or her (Gender variable, anyone?) starting position and shifts. So, when we create a Clerk, we can do this: Clerk new_clerk = new Clerk(); new_clerk.Location = "aisle 9"; new_clerk.Shift = "12pm-12am"; But, this is rather long-winded and clumbsy. We can define something called a constructor, which we'll discuss later, which will allow us to do the same thing like this: Clerk new_clerk = new Clerk("aisle 9", "12pm-12am"); Now, isn't that better? But, the code for initializing the object is in the constructor, so we haven't really changed anything. Here is where the power of polymorphism comes in. What if we don't want to always assign both the shift and the location at the same time? What if we want to be able to just assign the location and leave the shift until later? We can do that without any problem. We can define another constructor so that we can do this: Clerk new_clerk = new Clerk("aisle 9"); Now, look at the two initializations by constructor. they both call Clerk() (which is
the name of the constructor). How can they both be named the same, but use different
arguements? The answer is polymorphism! In OOP you can declare a method or constructor
more than once, each with a different set of arguements, but the same name. When you call
the method, the compiler knows from the arguement list, called the signature, which
version of the method you mean. This may not be a major code-saving feature, but it reduces confusion by 100%. Instead of add_int(), add_float(), add_short(), etc., you can just have an add() which will know by the arguements which one you need. Well, now that we understand OOP, we can answer our original question: What is Java? Java is a "simple, object-oriented, distributed, interpreted, robust, secure, architecture neutral, portable, high-performance, multithreaded, and dynamic language." [Flanagan, 1996] What does all that mean? Simple: Java was made to look and act much like C and C++, since so many people are already familiar with those languages. However, to simplify matters, several features common to C and C++ were not included. Among those are pointers, header files, and goto's. Object-Oriented: We just finished explaining that. Java is not 100% object-oriented like SmallTalk, but it is much more so that C++. Distributed: Java was designed with today's distributed networks in mind. It is capable of network communication and handling socket calls. Interpreted: Java code is compiled into byte-code, which is somewhat similar to a binary executable. Instead of being run by your operating system, though, the byte-code is run by the Java Virtual Machine, a byte-code interpreter. The advantage is that the Java code doesn't have to be directly translated into the system-specific machine code. Instead, the Java Virtual Machine acts as an intermediary which will be the same on every system. So, the end result is code that acts the same on every computer. Robust: Java is a strongly typed language, and so will catch most errors long before run-time. Also, since Java doesn't support pointers, code written in Java is much more reliable. Another feature adding to Java's robustness is its exception handling. When an error occurs, the code is able to deal with it on the fly, allowing for a high rate of recovery. Secure: Java was designed with the internet in mind. As such, it has to be secure to be usable at all. Java applets are not allowed to do any file I/O. Also, the lack of pointers prevents a large amount of potential problems.The internet is a dangerous place. There is really no way to be competely secure without being useless. Java has tried to find a middle ground between being unprotected and being unusable. Portable: Because Java runs on the Java Virtual Machine, and not the individual system itself, Java code is easily portable to any system capable of running the virtual machine. Performance: Java is approxiamtely 20 times slower than C. However, it is more than fast enough for most of its more commons uses such as applets and multimedia. Java does allow you, though, to import native C code for the times when speed is essential. Multithreaded: Java can run several simulateous process threads. This allows the user to easily code programs with can do more than one thing at once. Such multitasking capability is invaluable for use in building GUI's and other such applications. Dynamic: Java was built to be able to adapt to the continually evolving environment. It can dynamically load in classes when they are needed, and it can even got out over the 'Net to find them.
References: We've already talked a little about objects, and you should have some idea what an object is. Well, classes are were objects come from. A class is to an object, as a data type is to a variable. (And you thought you didn't need English to program!) In geek speak that is, "An object is an instantiation of a class." O.K., so you don't remember what an object is really. Let's go back over them briefly.
Objects are the basic building block of an object-oriented program. Each object contains
some data and the functions (called methods) which act on that data. For instance, say we
have an object that is a stack. It will have an array to store the data in and push() and
pop() methods for accessing the array. The power of objects comes from the fact that the
data array can only be accessed through push() and pop(). What I have done is create a Stack class. It has an array for data, a variable to keep
track of what the top of the stack is, and push() and pop() methods. Notice that the pop()
method will return -1 if the stack is empty. Let's look a little more closely. First of all, what do all the public and privates
mean? Public means anyone can see the variable or use the method or instantiate the class.
Private means only methods within the class can use the variable or use the method. Very
seldom will you see a private class. In the Stack class, data_array and current are private. That means only the push() and pop() methods can use them. So, the only way you can access the contents of the stack is through those methods. The methods themselves are public, meaning anyone can use them, just as Test does. Second, notice how PushNPop() called the push() and pop() methods. When you call a methods, you must specify which object's methods you're calling. That is done with the "." operator. So, my_stack.pop() says to call the pop() method of the object my_stack. Notice also that pop() does return a value. Why is it worth doing all that work for just a stack? Well, look at it this way: When PushNPop() calls pop(), does it have to worry about whether the stack is empty or not? Does PushNPop() even know how the stack is stored? No. We could completely reformat the Stack class, and so long as it still has a pop() method that returns an interger, PushNPop() will never know the difference. For another advantage, let's look at another piece of code: Here we also see why we must use the "." when calling a method. Because we can have more than one object of the same class, we must specify which object's method we're calling. By the way, in case you're wondering, after executing PushNPop(), stack1 will contain nothing, and stack2 will contain {5,1}. (From this point onward, I will assume you're sold on the idea of objects. I'll concentrate instead on selling you on Java.) One of the great things about Java is it's huge class library. Java has a very large library of predefined classes that you can use. For instance, our Stack class is not necessary since it is already defined in Java's class library. A very important class in the Java class library is the Applet class. You've seen a little of the Applet class in the previous lesson. The Applet class is the class from which all applets are derived. Every applet must inherit from java.applet.Applet somewhere along the line. Inheritance is a way for a class to gain all of the properties and abilities of another class. In Java, the extends keyword is used to declare inheritance for a class. The Applet class has the ability to interface with Netscape and other browsers. The Applet class has four important methods: init(), start(), paint(), and stop(). These four methods are the ones called by Netscape when it wants to run the applet. The first time it tries to run the applet, Netscape will call the init() method. The init() method is used to do variable initializations and other things that need to be done before the applet runs, and that only need to be done once. Next, Netscape calls start(). The start() method is the once that gets things going. It starts the applet doing whatever it does. When Netscape needs the applet to display itself, it calls the paint() method. The paint() methods should draw everything on the screen that needs to be drawn. Finally, when the user goes to another page, Netscape calls the stop() method. stop() should kill all of the applet's active processes and generally gets things cleaned up and ready to quit. Let's look at some examples. This is the same example we saw earlier. First, you see that this HelloWorldApplet
extends Applet. By inheriting from Applet, HelloWorldApplet gains the ability to interface
with web browsers. Second, notice that this class only has a paint() method. Where are the other methods like init() and start()? Well, the answer has to do with inheritance. Since HelloWorldApplet is a child of Applet, it inherits all of Applet's non-private variables and methods. So, HelloWorldApplet has the same init(), paint(), start(), etc. as Applet. Then why does HelloWorldApplet define a paint() method if it already has one it inherited from Applet? Well, the paint() method inherited from Applet is a generic paint() method that really doesn't do anything. So, HelloWorldApplet has to override Applet's paint() method with its own. That way, HelloWorldApplet has a paint() method which does specifically what it needs. Any time a class redefines one of it's parent's methods or vartiables, it's called overriding. Now we changed things a little. We've added a private variable and an init() method.
Why? No reason really. I just wanted to make the example a little more complex. Let's look
at the differences between the last class and this one. In the last two lessons we've talked several times about inheritance, but we've never really fully explained how it works. We're going to do that now before we go any further. Also, we'll explore constructors, the finalize() method, static and constant variables, and we'll revisit our HelloWorldApplet. Let's pretend we have a class called Person. Person has methods such as walk(), talk(), breathe() (a private method), and eatAndDrink(), and variables such as height, weight, eye_color, and hair_color. Now, you'll notice that our Person class is very general. It has only the most basic elements. It does not even have gender. O.K. We definitely need Male and Female classes. So, we'll inherit Male from Person. Males don't really do much of anything special, so we don't need to add any variables or methods. Now, to remain biblically correct, we'll inherit Female from Male. Now Female needs a new method, haveBaby(). The haveBaby() method will return a Baby object, so we need to define a Baby class. Baby will inherit from person since a baby isn't really a man or a woman. Baby will need a new method, cry(), and a new boolean variable, diapers_dirty. (A boolean variable is true or false.) The Baby class will also have to have new eatAndDrink() and walk() methods since babies don't do that quite the same as adults. What we'll have to do is override the eatAndDrink() and walk() methods from Person by declaring an eatAndDrink() method and a walk() method in Baby. Alright, so what we have is a Male class with walk(), talk(), breathe(), eatAndDrink(), height, weight, eye_color, and hair_color. We also have a Female class with walk(), talk(), breathe(), eatAndDrink(), haveBaby(), height, weight, eye_color, and hair_color. And we have a Baby class with walk(), talk(), breathe(), eatAndDrink(), cry(), height, weight, eye_color, hair_color, and diapers_dirty. We also still have our original Person class. The pseudo-code follows. I have left the methods empty since the internals are not important right now. We now can declare a whole family! But, there's more to life than family. We can't forget work. So, let's define some working classes. Let's start with Actress. Actress should inherit from Female. Actress will need an act() method and a salary variable. Let's also define a Salesman. Salesman will inherit from Male, and will need a sell() method, a salary variable, and a stock variable. Let's also define a Mother class. Mother will inherit from Female. All mother needs is a Baby. Here's the code: Now that's more like it! The Vector object in Salesman is from the Java library. It
provides an easy way to keep track of several of objects at once. Now for the part that makes inheritance really powerful. Mother is a Female is a Person. If we need to, we can treat any object as one of it's parents. For example, if our Male class had a marry() method which took a Female as a parameter, we could pass it an Actress because Actress can act as a Female. To clarify, let's look at some pseudo-code. Here you can see that the marry() method which expects a Female will accept either a Female or an Actress. It would also accept a Mother. The marry() method will accept any Female or any class inherited from Female. If we inherited a new class, AerobicsInstructor, from Actress, marry() would also accept that. The point I'm trying to make here is that marry() will accept any object that is somehow a decendant of Female. This property, called polymorphism, allows you to do some really neato stuff. You could, for instance, declare an array of Persons. In that array, you could put, Males, Females, Salesmans, and AerobicInstructors because they are all descendants of Person. We may see more of this in later lessons. One other thing polymorphism allows you to do is to call a method from the parent, even if it has been overridden. For example: We just redefined our Baby class to add a little surprise for Mommy and Daddy. We added two methods, adultEatAndDrink() and adultWalk(). Both methods just call a method of the parent class by using the super keyword. Here's what this will do: When adultWalk() is called in a Baby, it calls the walk() method of Baby's parent, Person. The walk() method of Person is the way adults walk. So, what will happen is that Baby will get up off his hands and knees and start strutting like Travolta. Or something to that effect. adultEatAndDrink() will have a similar effect. It will call Parent's eatAndDrink() method, and Baby will be downing cheeseburgers and coffee. In Java, a class can only inherit from one other class. There is no mulitple inheritance as C++ programmers are used to. Instead, Java offers something called interfaces. A class can implement any number of interfaces. An interface is like a skeletal class. It can only contain methods, no variables, and its methods must be abstract. Abstract methods contain no code. They simply state that the method exists. So, in effect, an interface is kind of like a set of parameters for a class to follow. It states what methods the class needs to implement. Again, polymorphism applies to interfaces. I know that didn't make much sense, so let's look at some more code. This time, we're going to come up with a more sensible way of defining working classes. What we have now is an interface called Law. Any class that implements Law is required to have everything needed to be a lawyer, doLawyerStuff() and chargeHugeFee(). We also defined two kinds of lawyers, MaleLawyer and FemaleLawyer. Both define the above two methods. MaleLawyer also adds a variable to keep track of the number of wins so he can boast to his buddies. The FemaleLawyer class is a Female and a Person, and quailifies at Law. So, FemaleLawyer can act as a Person, a Female, or a Law. I know that sounds a little odd, but it works. We can declare an array of type Law, and we can put FemaleLawyers and MaleLawyers in it. We cannot, however, put Laws in it since they don't really exist; Law is an interface, not a class. Again, we will see more of this in later examples. Let's now look at another part of the mechanics of a class, constructors. Constructors are used to create an instance of a class. For instance, when we declared george as a Man a little while back, we used a constructor for class Man. However, since we hadn't declared any constructors for Man, we ended up using the default constructor. Anytime there are no constructors defined, a default constructor is included by the compiler. Let's start with an example. Now we've extended our Man class to contain a constructor. Constructors are always named after the class, and never declare a return value, as you can see above. What this constructor does is set the wife variable to null when a Man is created, meaning that he has no wife. What if we wanted to allow a Man to already be married when he is created? We can take advantage of another application of polymorphism. Methods can be polymorphic too. What that means is that two methods can have the same name, as long they have different parameters. We can now create a Man with or without a wife. Notice that both constructors have the name Man, but on tkaes no parameters, and the other takes a Female as a parameter. Here's how we would use this class. Love is so complicated! Notice that Frank was created with a wife, Gisela, whom he later divorces, and George was created without a wife but later marries the recently divorced Gisela. To be perfectly PC, and include alternative lifestyles, we could redefine Man as follows, using the polymorphics property: There are many different levels of polymorphism at work here. First, wife is now a person, so it can accept anything inherited from Person, including Actress and MaleLawyer. There are now two marry () methods. One accepts a Female, and the other a Male. There is no difference in the way they are called. The difference is in what kind of object you pass it. If you pass a Female, it knows to use the first marry() method. If you pass a Male, it knows to use the second marry() method. If you try to pass a Baby, you will get an error, because there is no marry() method which expects a Baby or just a Person. Besides, that's illegal! Now, one more thing about constructors. A constructor in a child class can call the constructor of its parent class. In fact, it does this automatically, whether you want it to or not. But, it is always better to explicitly declare, just so that nothing is going on behind the scenes. To call the parent's constructor, use the super() method. The call to super() has to be the first statement in the constructor. Let's see another example. What we have created is a father class. We can create our Father with no wife, with a wife, or with a wife and a child. Notice that we use the wife variable from Man, so we don't have to declare one in Father. We also take advantage of the constructors in Man. The first one in Father, the empty constructor, would be supplied by the compiler anyway, but it's always a good idea to declare it explicitly. On the other side of constructors is the finalize() method. The finalize() method is used to clean things up before an object is destroyed. Proper use of this method can result in faster clean up time and better performance. However, for most basic application, it's not necessary. Here we've added a finalize() method. The finalize() method always has a return value of void, and takes no parameters. This finalize() method just assigns null to the wife variable so that the automatic garbage clean up will get to it faster. You can only have one finalize method per class. We will be seeing much more of constructors and a little more of the finalize() method in the following lessons. Alright, let's talk a little about variables. There are many modifiers we can add to a variable declaration to make subtle changes to the way the variable works. Right now we're going to look at two of them, static and final. Final makes the variable a constant. After it is declared, the variable can no longer be modified. It's very similar to the const statement in C++. Static does something a little odd with the variable it modifies. It makes it into a "class variable." Class variables exists whether there are any instances of the class or not. Only variables declared outside of the classes methods can be declared static. Let's look at an example.
Methods can also be declared final and static. A final method cannot be overriden in a subclass. A static method acts rather like a static variable. It is a class method, and can be called even if no objects have been instantiated from the class. A good example of a static final variable is Math.PI. It is declared in java.lang.Math,
but it's usable from anywhere without instantiating a Math object. Now, on a completely different note, let's go back and look at our HelloWorldApplet again. The thing everyone wants to do with Java is to add pictures, sound, and animation. In the remainder of this lesson, we'll cover sound and pictures. Animation will come later. First I'll show you the code to add an image to an applet, then I'll explain it. First, this HelloWorldApplet draws a filled rectangle that is slightly larger than the image we're going to use. The effect is that the rectangle becomes a frame for the image. I did that just to make the image stand out against the background.
We put the image load in the start() because it only needs to be done once every time you view the page. The paint() method gets called several times while you look at a page. In general, you only want things necessary for redrawing the applet to be in the paint() method. That's all there is to it. You can draw any number of images in this way. Just be sure to import java.awt.*, because that's where the Image class is located. Sound works in a very similar way. The only real difference between loading an image and loading a sound is that the sound plays immediately; you don't have to display it or tell it to play. Again we've put the sound in the start() method. This means that the soud will only be played when the page is reloaded, which is a much better alternantive than putting it in paint() and having called several times in rapid succession. |