Title: OOP
Course: CPS 161: An Intro to Programming with Java - DL MM MASTER

OOP: Object Oriented Programming

Simple Shape Class

- The following Shape class will be used to demonstrate subclasses. Another words used other than subclasses is the word derived classes, or child classes.
- This class has 2 constructors. Every time the new operator is called, a constructor is called. The constructor is chosen based on the calling sequence of the actual new call.
- All classes that you create are derived from some class. If no explicit mention is made, then the class is derived from the Object class. In the following class, we could equally replace the first line with:
class Shape extends Object
- Take a look at the Object class to see all that comes with it. We have encountered the equals method and the toString methods.
- This class has no toString method. Therefore, when you try to print the class, you end up getting the default toString method in the Object class.



class Shape
{
   String color;

   Shape()
   {
     System.out.println("Shape default Constructor");
     color = "black"; // default color
   }

   Shape(String c)
   {
        color = c;
        System.out.println("Shape Constructor(String)");
   }

   double getPerimeter()
   {
        return -1.0; // Hard to get the perimeter of a shape
   }

   double getArea()
   {
        return -1.0; // Hard to get the area of a shape
   }

   double getAPRatio()
   {
           return getArea()/getPerimeter();
   }

}

class Test
{
   public static void main(String[] args)
   {
    Shape s1 = new Shape(); // call default constructor
    Shape s2 = new Shape("red"); // call constructor which takes 1 parameter
    System.out.println("s1=" + s1 + " s2="+s2);

   }
}



Extending Classes

Notes:

- In Rectangle, we are overriding the getPerimeter and getArea methods.
- Note that in the Rectangle constructor, we will first go through a Shape constructor. You can pick the constructor by issuing a command like:
super(color);
- If you don't explicity call super as the first executable statement in a Rectangle constructor, you have the equivalent of a default call of:
super();
- Constructors call parent Constructors or other methods in the parent class with the keyword super.
- You could decide that you don't want to replicate code, and have one constructor in a class call another constructor in the same class. For example, in the Square class below, you could replace the following call:
super("black", 2.0, 2.0);
with:
this("black", 2.0, 2.0);
In this case we would be calling the other Square class constructor
- Note the Rectangle constructor with parameters. Note that the usage of the "this" keyword is to avoid the ambiguity of the a statement like:
width = width;
height = height;
By using this.width, we are definitely accessing the member variable width instead of the formal parameter width. So we get:
this.width = width;
this.height = height;
- Note that a derived class can redefine methods in a parent class by just repeating the definition of the method from the parent class. See the setWidth method in the Square class. Note that when you redefine a method in a parent class, you must use exactly the same calling sequence.
- Also a derived class can add new methods ... see setSide in the Square class.
- So the Parentage of the Square class is:
Object --> Shape --> Rectangle --> Square
- See if you can figure out where the following methods are defined for the Square class:
  - getPerimeter
  - getArea
  - setSide
  - setWidth
  - equals
  - toString
  - getAPRatio
- Check out the Test program below to see how these classes can be used.



class Shape
{
   String color;

   Shape()
   {
     System.out.println("Shape default Constructor");
     color = "black"; // default color
   }

   Shape(String c)
   {
        color = c;
        System.out.println("Shape Constructor(String)");
   }

   double getPerimeter()
   {
        return -1.0; // Hard to get the perimeter of a shape
   }

   double getArea()
   {
        return -1.0; // Hard to get the area of a shape
   }

   double getAPRatio()
   {
           return getArea()/getPerimeter();
   }

}

class Rectangle extends Shape
{
   double width=1.0;
   double height=2.0;

   Rectangle()
   {
    System.out.println("Rectangle default Constructor");
   }

   Rectangle(String color, double height, double width)
   {
    super(color);
    this.width = width;
    this.height = height;
    System.out.println("Rectangle Constructor(String, double, double)");
   }

   double getPerimeter()
   {
    return 2*(width+height);
   }

   double getArea()
   {
    return width*height;
   }

   void setHeight(double h)
   {
    height =h;
   }

   void setWidth(double w)
   {
    width = w;
   }
   public String toString()
   {
        return "Rect: w="+width + " h="+ height + " color=" + color;
   }
}

class Circle extends Shape
{
   double radius;

   Circle()
   {
    System.out.println("Circle default Constructor");
   }

   Circle(String color, double r)
   {
    super(color);
    radius = r;
    System.out.println("Circle Constructor(String, double)");
   }

   double getPerimeter()
   {
    return 2.0 * Math.PI * radius;
   }

   double getArea()
   {
    return Math.PI * radius * radius;
   }
}

class Square extends Rectangle
{

   Square()
   {
    super("black", 2.0, 2.0);
    System.out.println("Square default Constructor");
   }

   Square(String color, double side)
   {
    super(color, side, side);
    System.out.println("Square Constructor(color, double)");
   }

   void setHeight(double h)
   {
    super.setHeight(h);
    super.setWidth(h);
   }

   void setWidth(double w)
   {
    setHeight(w);
   }

   void setSide(double w)
   {
    setHeight(w);
   }

}

class Test
{
   public static void main(String[] args)
   {
    Shape s1 = new Shape();
    Shape s2 = new Shape("red");
    System.out.println("s1=" + s1 + " s2="+s2); System.out.println();

    Rectangle r1 = new Rectangle();
    Rectangle r2 = new Rectangle("red", 1.5, 4.7);
    System.out.println("r1=" + r1 + " r2="+r2);  System.out.println();

    Circle c1 = new Circle();
    Circle c2 = new Circle("red", 1.5);
    System.out.println("c1=" + c1 + " c2="+c2); System.out.println();

    Square sq1 = new Square();
    Square sq2 = new Square("red", 1.5);
    System.out.println("sq1=" + sq1 + " sq2="+sq2); System.out.println();

   }
}


Polymorphism in Action

The magic of Polymorphism:

When faced with a decision of which method to call in the following:

Shape shp=new Square("red", 8.9);
double x = shp.getArea();

Java will know that shp is defined as a Shape, but it is actually a Square. Consequently, shp will have access to all of the methods available to a Square. In effect Java makes its choices at Run Time, not at Compile time. This is sometimes called Late binding as opposed to early binding. Late binding implies that the Run time system is storing information with each object at Run time to allow it to make its decisions. Using late binding makes Java object Polymorphic. It allows some interesting properties:


import java.util.*;
class Test2
{
    static void printStuff(Object[] sarr)
    {
         // Note that we don't need to know what kind of Object we have
         // in this array of Objects:


         for (int i=0; i < sarr.length; i++)
            System.out.println("sarr["+i + "]=" + sarr[i]);
    }
    static void printStuff(Shape[] sarr)
    {
         // Note that we don't need to know what kind of Object we have
         // in this array of Shapes:

         for (int i=0; i < sarr.length; i++)
            System.out.println("APRatio = " + sarr[i].getAPRatio());

         for (int i=0; i < sarr.length; i++)
            System.out.println("sarr["+i + "]=" + sarr[i]);
    }

    public static void main(String[] args)
    {
        Shape[] sarr = new Shape[8];
        sarr[0] = new Shape();
        sarr[1] = new Shape("red");
        sarr[2] = new Rectangle();
        sarr[3] = new Rectangle("blue", 1.2, 7.9);
        sarr[4] = new Circle();
        sarr[5] = new Circle("tan", 6.5);
        sarr[6] = new Square();
        sarr[7] = new Square("green", 4.5);

        printStuff(sarr);

        System.out.println("******************************");

        Object[] sarr2 = new Object[8];
        sarr2[0] = new String("USA");
        sarr2[1] = new Shape("red");
        sarr2[2] = new Random();
        sarr2[3] = new Rectangle("blue", 1.2, 7.9);
        sarr2[4] = new Circle();
        sarr2[5] = new Circle("tan", 6.5);
        sarr2[6] = new Square();
        sarr2[7] = new Square("green", 4.5);

        printStuff(sarr2);

    }
}



Abstract Classes and Methods:

Note that it's tough to compute a Perimeter or an Area for a Shape class, because a Shape has not form to it. However, it is useful to have the Shape class as a template for all of the Shapes that can be derived from it (i.e Rectangle, Square, Circle). For this reason, Java invented the idea of an abstract class.

An abstract class can have 0 or more abstract methods.

You cannot construct an abstract class, but you can construct a class that is derived from the abstract class and implements all of the abstract methods.



abstract class Shape
{
   String color;

   Shape()
   {
     System.out.println("Shape default Constructor");
     color = "black"; // default color
   }

   Shape(String c)
   {
        color = c;
        System.out.println("Shape Constructor(String)");
   }

   abstract double getPerimeter();

   abstract double getArea();

   double getAPRatio()
   {
           return getArea()/getPerimeter();
   }

}


All of the previous classes can stay the same (Rectangle, Square, and Circle). These classes are useable, because they all implement the abstract methods getPerimeter and getArea. Note that the Square class doesn't implement these methods directly, however, through the Rectangle implementation of getArea and getPerimeter, the Square class does have an inherited implementation of the required abstract methods.

If shape is defined as an abstract class, you can issue the following statement:
Shape[] sarr = new Shape[8];

However, you can't issue any of the following statements:
Shape s = new Shape();
sarr[0] = new Shape("red");

A new Test program might look like the following:



import java.util.*;
class Test2
{
    static void printStuff(Object[] sarr)
    {
         // Note that we don't need to know what kind of Object we have
         // in this array of Objects:


         for (int i=0; i < sarr.length; i++)
            System.out.println("sarr["+i + "]=" + sarr[i]);
    }

    public static void main(String[] args)
    {	
		
        Shape[] sarr = new Shape[8];
       // Because Shape is an Abstract class:
       // Can't do:  sarr[0] = new Shape();
       // Can't do:  sarr[1] = new Shape("red");
        sarr[0] = new Square();
        sarr[1] = new Circle();
        sarr[2] = new Rectangle();
        sarr[3] = new Rectangle("blue", 1.2, 7.9);
        sarr[4] = new Circle();
        sarr[5] = new Circle("tan", 6.5);
        sarr[6] = new Square();
        sarr[7] = new Square("green", 4.5);

        printStuff(sarr);

        System.out.println("******************************");

        Object[] sarr2 = new Object[8];
        sarr2[0] = new String("USA");
        // Because Shape is an Abstract class:
        // Can't do: sarr2[1] = new Shape("red");
        sarr2[1]= new Random();
        sarr2[2] = new Random();
        sarr2[3] = new Rectangle("blue", 1.2, 7.9);
        sarr2[4] = new Circle();
        sarr2[5] = new Circle("tan", 6.5);
        sarr2[6] = new Square();
        sarr2[7] = new Square("green", 4.5);

        printStuff(sarr2);

    }
}


Using the Interface Approach:

An interface can be thought of as a contract. A class which implements the interface is required to provide all of the methods defined in the interface.

- Note that in Test2.main routine below we can create an array of classes which implement the interface with a statement like:
Shape [] sarr = new Shape[8];
- We can use the array of Shape without knowing the real classes. The only thing we really need to know about this array is that every class in the array will implement the interface and consequently have all of the methods defined in the interface.
- In C++, there is a concept called multiple inheritance where a class can be derived from 2 or more other classes. This concept is useful but quite messy in C++. The interface concept provides Java with a concept that is like multiple inheritance, but inherently much cleaner.
- Because of interfaces, Java only needs to allow for classes to have a single parent class.
- Many key modules in Java are defined as interfaces. For example JDBC (Java Data Base Control) is an interface which defines a standard set of methods required for Java to communicate with a any Data Base. The JDBC interface avoids the layered approach used in previous attempts to provide a common interface to all databases (ODBC).
- Most of the Java data structures are defined as interfaces.
- Interface example:




interface Shape
{
   double getPerimeter();
   double getArea();
   double getAPRatio();
}


class Rectangle implements Shape
{
   double width=1.0;
   double height=2.0;

   Rectangle()
   {
    System.out.println("Rectangle default Constructor");
   }
   Rectangle(double w, double h)
   {
     width = w;
     height = h;
   }

   public double getAPRatio()
   {
      return getArea() / getPerimeter();
   }

   public double getPerimeter()
   {
    return 2*(width+height);
   }

   public double getArea()
   {
    return width*height;
   }

   public String toString()
   {
        return "Rect: w="+width + " h="+ height;
   }
}

class Circle implements Shape
{
   double radius;

   Circle()
   {
    System.out.println("Circle default Constructor");
    radius = 1.0;
   }
   Circle(double r)
   {
      radius = r;
   }

   public double getPerimeter()
   {
    return 2.0 * Math.PI * radius;
   }

   public double getArea()
   {
    return Math.PI * radius * radius;
   }
   public double getAPRatio()
   {
      return getArea() / getPerimeter();
   }
}

import java.util.*;
class Test2
{
    static void printStuff(Shape[] sarr)
    {
         // Note that we don't need to know what kind of Object we have
         // in this array of Objects:

         for (int i=0; i < sarr.length; i++)
            System.out.println("APRatio = " + sarr[i].getAPRatio());

         for (int i=0; i < sarr.length; i++)
            System.out.println("sarr["+i + "].getAPRatio=" + sarr[i].getAPRatio());
    }

    public static void main(String[] args)
    {	
		
        Shape[] sarr = new Shape[8];
       // Because Shape is an Interface:
       // Can't do:  sarr[0] = new Shape();
       // Can't do:  sarr[1] = new Shape();
        sarr[0] = new Rectangle(2.4, 6.7);
        sarr[1] = new Circle(7.3);
        sarr[2] = new Rectangle();
        sarr[3] = new Rectangle();
        sarr[4] = new Circle();
        sarr[5] = new Circle(2.1);
        sarr[6] = new Rectangle();
        sarr[7] = new Rectangle(5.5, 2.3);

        printStuff(sarr);

    }
}


Adapter Classes

I'm not going to create an Adapter class here, but I just want to mention the concept. Sometimes the number of methods in the Interface is quite large and we may only be interested in some of the methods related to the interface. An Adapter class is convenience class. An Adapter class will implement a particular interface and provide dummy routines for all of the methods in the interface. This allows us to derive a class from the adapter class and implement only the methods of interest. When we get into Java graphics we will see the usefulness of this.





Another Example to demonstrate creating subclasses, abstract classes, and interfaces

IS A Versus Has A

The extends keyword should only be used in "Is A" relationships. Java will let you use the extends keyword even in situations where it doesn't make sense. In the previous examples, we could clearly make the statements:
a Square is a Rectangle
a Rectangle is a Shape
a Circle is a Shape
a Shape is an Object

Ask yourself two questions when determining if the relationship between classes is ï¿½is aï¿½ or ï¿½has a.ï¿½ First ask: ï¿½Is the first class (for example, a circle) a type of the latter class (for example, a shape)?ï¿½ If so, then the relationship is ï¿½is a.ï¿½ You also may ask: ï¿½Does the first class (for example, a building) contain one or more of the second classï¿½ type (for example, rooms)?ï¿½ If so, then the relationship is ï¿½has a.ï¿½

What is the object-oriented relationship between a oak and an tree?
An is a relationship

What is the object-oriented relationship between a city and a road?
A has a relationship.

Consider the following:

car - engine
Mustang - Car
Blue Jay - Bird

What is wrong with the following?


public class Building {
   long length;
   long width;

   public Building(long len, long wid) {
      length = len;
      width = wid;
   }

   public long area() {
      return length * width;
   }
}
// The following implies that an OfficeRoom is a Building ????

public class OfficeRoom extends Building {
   public OfficeRoom(long len, wid) {
      super(len, wid);
   }
}




Is this better?


public class Building {
   long length;
   long width;

   // The building has a number of office rooms:
   OfficeRoom [] officeRooms;

   public Building(long len, long wid, OfficeRoom [] ors) {
      length = len;
      width = wid;
      officeRooms = ors;
   }

   public long area() {
      return length * width;
   }
}
public class OfficeRoom {
   long length;
   long width;

   public OfficeRoom(long len, wid) {
   }

   public long area() {
      return length * width;
   }
}



Constructors -- Summary of rules

Similar principles apply for overloaded Constructors as for methods.

this and super rules

1. Inside of a constructor you can have your first statement be either a this or super (not both).
2. The this pointer calls another constructor within your same class. Which constructor gets called is based on the arguments passed in the this(xx, yy) call using the overloading rules discussed earlier.
3. If the super(xx, yy) call is made, you are calling a constructor in your parent class.
4. If you have neither this or super, there is an implied call to:
super() - the default constructor(i.e. no parameter constructor) of your parent class. If your parent class has no default constructor then you get a compilation error.
5. If a class has no explicit constructors, then the compiler provides you a default constructor.
6. If you write one constructor, then the compiler does not provide any default constructor.

With the above rules in place, explain what is happening in the following:


public class MyClass {
   String title;
   int age=10;


   public MyClass() {
      this("Default Title");

      System.out.println("MyClass()");
   }

   public MyClass(String t) {
      this(t, 50);
      System.out.println("MyClass(String t)");
   }

   public MyClass(String t, int age)
   {
      title = t;
      this.age = age;
      System.out.println("MyClass(String t, int age)");
   }

   public static void main(String[] args)
   {
		// What output would be expected with each of the following.
        // What are the resultant values of title and age
		MyClass m = new MyClass();
        MyClass m2 = new MyClass("George");
        MyClass m3 = new MyClass("Fred", 55);
   }

}




What is happening in the following:


public class MySuper {
   //first constructor
   public MySuper() {
      System.out.println("Super1");
   }

   //second constructor
   public MySuper( String title) {
      System.out.println("Super2");
   }
}

public class MySub extends MySuper {
   //first constructor
   public MySub() {
      super("the title");

      System.out.println("Sub1");
   }

   //second constructor
   // Which constructor in MySuper will be called here?
   public MySub(String title) {
      System.out.println("Sub2");
   }
   public static void main(String[] args)
   {
   	MySub m1 = new MySub();
	MySub m2 = new MySub("abc");
   }
}



What is wrong with the following?? -- How do you fix it??:


public class A {
   public A( String title) {
      System.out.println("A");
   }
}

public class B extends A {
   public B( String title) {
      System.out.println("B");
   }
}




Miscellaneous Issues

method parameter considerations:

Primitive datatypes are passed by-value which means that no matter what the called method does, when the method returns, the passed parameters are unchanged. The called method is in effect only passed a copy of the primitive parameter.


class Parameters
{
    static void modifyPrimitive(boolean boo, char c, byte b,
                        short s, int iValue, long lValue,
                        float f, double d)
    {
        boo = true;
        c ='b';
        b = 13;
        s = 13;
        iValue = 13;
        lValue = 13;
        f = 13.0F;
        d = 13.0;
    }
    public static void main(String[] args)
    {
        boolean myBoo = false;
        char    myChar = 'a';
        byte    myB = 1;
        short   myS = 1;
        int     myI = 1;
        long    myL = 1;
        float   myF = 1.0F;
        double  myD = 1.0;

        modifyPrimitive(myBoo, myChar, myB, myS, myI, myL, myF, myD);

//None of the original values have changed
        System.out.println("myBoo = " + myBoo);
        System.out.println("myChar = " + myChar);
        System.out.println("myB = " + myB);
        System.out.println("myS = " + myS);
        System.out.println("myI = " + myI);
        System.out.println("myL = " + myL);
        System.out.println("myF = " + myF);
        System.out.println("myD = " + myD);
    }
}


Passing Class Instances and Arrays

Now that we have determined what happens when Primitives are passed to a method, its time to look at the more complex objects.

Classes that contain Individual Data -

Consider the following example --- We have a Rectangle class with 2 ints in it. A handle is passed to our modifyObjects method. This method can change the contents of the 2 ints or any other data that is modifiable inside the object instance. The modifyObjects method cannot change the handle itself. In otherwords, the value of myR in the calling program won't change, but the values of data inside the class can change.

Arrays -

In this situation, Arrays are somewhat like Object Instances. A method can't change the handle myArr, but the contents of the array elements can be changed.

Immutable Objects -

We will discuss this more, but there are some classes that can't be changed (they are immutable). The following classes are immutable( String, Integer, Float, Double, etc.). When you attempt to change an immutable object you are creating a new instance of the class. Note that myStr is not changed when you return from modifyObjects.


class Rectangle
{
    int width = 1;
    int height =2;
}
class ObjParameters
{
    static void modifyObjects(Rectangle r, int[] arr, String str)
    {
        r.width = 10;
        r.height = 20;

        for (int i =0; i < arr.length; i ++)
            arr[i] = 10;

        str = "Modified in modifyObjects";
    }
    public static void main(String[] args)
    {
       Rectangle myR = new Rectangle();
       int [] myArr = {1,2,3};
       String myStr ="Original Value";

       modifyObjects(myR, myArr, myStr);

       // Rectangle gets changed
       System.out.println("myR width = " + myR.width + "  height =" + myR.height);

      // Array gets changed.
       System.out.print("myArr = ");
       for (int i = 0; i < myArr.length; i++)
            System.out.print(myArr[i] + ", ");
       System.out.println();

	// Strings don't get changed, because they are Immutable
       System.out.println("myStr = " + myStr);
    }
}


Reference Games

In this example, we are going to set one object equal to another, modify the new object and then see if the original object was changed. We are effectively doing the same thing as we did in the previous example when we passed parameters and we will get the same results. However, we are doing this without making procedure calls.

Make sure you understand what is going on in this example --- ask questions if the following example makes no sense to you.

class Rectangle
{
    int width = 1;
    int height =2;
}
class ObjReferences
{
    public static void main(String[] args)
    {
       Rectangle myR = new Rectangle();
       int [] myArr = {1,2,3};
       String myStr ="Original Value";

       Rectangle r;
       int [] a;
       String s;

       r = myR;
       r.width = 10;
       r.height = 20;
       a = myArr;
       for (int i=0; i < a.length; i++)
            a[i] = 10;

       s = myStr;
       s = "Modified version of String";

    // Rectangle contents are changed
       System.out.println("myR width = " + myR.width + "  height =" + myR.height);

    // Array contents are changed
       System.out.print("myArr = ");
       for (int i = 0; i < myArr.length; i++)
            System.out.print(myArr[i] + ", ");
       System.out.println();

     // String is not changed
       System.out.println("myStr = " + myStr);
    }
}


equals method and Overriding methods in the Object Class

All classes are derived from the Object class. We need to take a look at the key Object class key methods called toString and equals. Most of the classes that Java provides have overriden the default characteristics of these 2 methods.

toString() -

This method is called whenever Java needs to convert your class into a String. For example, in the following example, the toString method available to Rectangle1 will be called:
Rectangle1 r1 = new Rectangle(1,2);
System.out.println("r1 = " + r1);

If you don't override the default toString method in the Object class, your class won't be dumped in a very meaningful way. In the example below, the Rectangle1 class demonstrates the default toString, while the Rectangle2 class uses an overriden toString routine.

= = Operator

When you compare 2 classes with the = = Operator, you are asking whether the handles (pointers) for the two objects point to the same object instance. If the 2 objects have the same contents, but happen to be in different object instances than the = = Operator will return false.

equals method

By default the equals method provided in the Object class works the same as the = = Operator. Traditionally, equals should return true if 2 separate object instances have the same content. For your class to have this behavior, you must override the equals method in your class to provide this functionality. Note that this was done in the Rectangle2 class below:


class Rectangle1
{
    int width = 1;
    int height =2;
    Rectangle1 (int w, int h)
    {
        width = w;
        height = h;
    }
}
class Rectangle2
{
    int width = 1;
    int height =2;
    Rectangle2 (int w, int h)
    {
        width = w;
        height = h;
    }
    public boolean equals(Object obj)
    {
       Rectangle2 r2;

       if (obj != null && obj instanceof Rectangle2)
       {
            r2 = (Rectangle2)obj;
            if (r2.width == width && r2.height == height)
                return true;
            else
                return false;
        }
        else
            return false;
    }
    public String toString()
    {
        return "Rectangle2: width(" + width +
                    ") height(" + height + ")";
    }
}

class ObjectMethods
{
    public static void main(String[] args)
    {
       Rectangle1 r1= new Rectangle1(2,3);
       Rectangle1 r2= new Rectangle1(2,3);

       System.out.println("default r2.toString()  " + r2);

       if  (r1 == r2)
          System.out.println("r1 == r2");
        else
          System.out.println("r1 != r2");

       if (r1.equals(r2))
          System.out.println("r1 equals r2");
        else
          System.out.println("r1 does not equal r2");

      // *******************************************

       Rectangle2 r3= new Rectangle2(2,3);
       Rectangle2 r4= new Rectangle2(2,3);

       System.out.println("MY r3.toString()  " + r3);

       if  (r3 == r4)
          System.out.println("r3 == r4");
        else
          System.out.println("r3 != r4");

       if (r3.equals(r4))
          System.out.println("r3 equals r4");
        else
          System.out.println("r3 does not equal r4");

      // *******************************************

    }
}


String.equals

One of the more common mistakes that is made by beginning Java students is the use of the
= = Operator when the equals method should have been used.

Often it is desired to compare 2 strings. The == operator will tell you whether we are pointing to the same object. However, we usually are only interested in whether the 2 instances have the same content. When we want to know whether we have the same content, we should always use the equals method.

Unfortunately, the == Operator will sometimes seem to work. In the following example, note that comparing
s1 == s3 does return a true. However, comparing
s4 == s5 will not return true. Using the equals method will always give you the correct answer if you want to compare contents. The reason
s1 == s3 has to do with the efficiency of the Java compiler. The Java compiler determines that the literal string used to initialize s1 is the same as s3. So the Java compiler only allocates one hunk of memory for the 2 instances of the same literal string.


class StringEquals
{
    public static void main(String[] args)
    {
       String s1 = "12";
       String s2 = "34";
       String s3 = "12";
       String s4, s5;

       s4 = s1 + s2;
       s5 = s1 + s2;

     // this statement generates a true
       System.out.println("s1 == s3 " + (s1==s3));

     // this statement generates a false
       System.out.println("s4 == s5 " + (s4 == s5));

     // this statement generates a true
       System.out.println("s4.equals(s5) " + s4.equals(s5)  );




    }
}


Exercises:

1. Consider creating classes Animal, Mammal, Bird, Cow, BlueJay, Horse, Reptile, Snake, etc. Imagine methods and properties of each of these classes. For example:
  - The Animal class can have properties of name, weight, age
  - The Animal class can have methods of birth, death, make_noise
  - Mammals can have methods of nurse_children
  - Birds can have methods of laying_eggs
Create an array of Animals and do things like:
  - Compute the total weight
  - Make all of the noises in the array of animals
  - Make sure that your array contains instances of the following classes: Animal, Mammal, Horse and Snake.
2. Modify the above problem so that the Animal class declares the make_noise method as abstract. After making the make_noise method in the Animal class, what were you unable to do?
3. Create an interface called Living which has methods of grow and death. Now consider creating a similar structure of classes related to plant life. Note that now we can create an array of Living things. Suppose we want to have a class called Country that implements the Living interface. Does this make any sense (Consider the Roman Empire)?
4. Create a Person class. Perhaps you want the Person class to extend your Mammal class above.
5. Now create a class called Chess_Master which extends Person, but implements an interface called Chess_Playing.
6. Create a class called Computer which also implements the Chess_Playing interface.

Last Modified: March 12, 2008 9:59 AM