[3381] in java-interest

home help back first fref pref prev next nref lref last post

Re: JAVA: What are ABSTRACT CLASSES etc.? (long)

daemon@ATHENA.MIT.EDU (Gary Aitken)
Thu Nov 9 23:22:23 1995

Date: Thu, 9 Nov 1995 17:34:47 -0700
From: garya@village.org (Gary Aitken)
To: michael@w3media.com (Michael Mehrle), java-interest@java.sun.com

>1) What the hell are "abstract classes"? The book describes it as "classes,
>which are made up of methods that have not been completed. It's up to the
>subclasses of the abstract class to override the method." I've no idea what
>that means. I understand the basic "extending" of classes by creating
>subclasses which override certain methods. So far so good. But I've no idea
>what "abstract classes do...

An abstract class is a class which by itself does not constitute a
complete implementation of the member functions which it advertises.
Because of this, the language will not allow you to try to create an
object of this class, since it would be missing some functionality.
For example:

A class "shape", which is supposed to have a "circumference" function.
One would design this class to have several derived classes, 
such as rectangle, elipse, etc.  The class shape would define the
general capabilities of all varieties of shapes; it would declare 
the circumference function, but would not implement it, because it is 
different for every derived class.

To force every derived class to implement this function, the function
is declared as an abstract function (i.e. a function interface declaration
only, with no body):

public abstract class Shape {
				Shape()	{}
public abstract	float	circumference(); // declaration only, no body
};

public class Elipse extends Shape {
private	float	ax1;	// length of first axis
private	float	ax2;	// length of second axis
public		Elipse(float a1, float a2) { super(); ax1=a1; ax2=a2; }
public	float	axis1()	    { return( ax1 ); }
public	float	axis2()	    { return( ax2 ); }
public	float	circumference() { 
		return((float) (2. * 3.14 * Math.sqrt((ax1*ax1 + 
ax2*ax2)/2.)));
		};
};

When a derived class redefines the meaning of a function declared in one
of its base classes, the derived class function is said to "override"
the base class function.  In the above case, Elipse.circumference
overrides the abstract function Shape.circumference.  A derived class 
may override a base class function which is not abstract, as well as 
one which is abstract.

For example, we could further derive a class Circle:

public class Circle extends Elipse {
			Circle(float d) { super(d,d); }
public float	circumference() { return((float) (3.14 * axis1())); }
};

Here, Circle.circumference overrides Elipse.circumference, which is
not abstract.  In this particular case, it is not *necessary* for
Circle.circumference to exist, since the formula for an Elipse would work.
However, one might well want to override it because it is more efficient.
In other situations it is necessary.

If we also define a rectangle and a square:

public class Rectangle extends Shape {
private	float	wid;
private	float	ht;
public		Rectangle(float w, float h) { super(); wid=w; ht=h; }
public	float	circumference()	{ return( wid + wid + ht + ht ); }
};

public class Square extends Rectangle() {
public		Square(float s)	{ super(s,s); }
};

In this case Square does not override Rectangle.circumference.  If you
invoke the circumference() function for an object of type Square, it
will use the nearest base class's function, in this case, Rectangle's.

If an arbitrary shape, be it a circle or an Elipse, has a method
invoked, the most-derived method of that name is called, where 
"most-derived" means the most-derived subclass for the object in 
question for which that method is defined:

	Shape  s1 = new Circle(2.);
	Elipse s2 = new Circle(2.);
	Circle s3 = new Circle(2.);
	Shape  s4 = new Elipse(2., 3.);
	Elipse s5 = new Elipse(2., 3.);
	Shape  s5 = new Square(2.);
	float c;
	c = s1.circumference();	// invokes Circle.circumference()
	c = s2.circumference();	// invokes Circle.circumference()
	c = s3.circumference();	// invokes Circle.circumference()
	c = s4.circumference();	// invokes Elipse.circumference()
	c = s5.circumference();	// invokes Elipse.circumference()
	c = s6.circumference();	// invokes Rectangle.circumference()

>2) Which brings me to "interfaces" which don't make sense to me either.
>What is the major difference between an "interface" and a "superclass"? The
>book says the advantage is that you can define the protocols for an
>abstract class without worrying about the specific implementation, leaving
>that until later. WHY?

An interface *defines* a "standard" set of functions which are used 
to implement a general api, but does not implement any of them.
Note that the definition of an interface does not include a constructor.
This is because other classes *implement* interfaces, and their constructors
may take various different types of arguments.  
A class *implements* an interface.  By specifying that it implements
an interface, it logically includes the interface specification in its
declaration; the class is then required to implement the interface
(see example below)

When writing a function which manipulates objects of a particular type,
one must use objects of the appropriate type.  Those objects may
be passed in as an argument, be held as actual data members of
the class to which the function belongs, or be obtained via some
function call.  In any case, they must all be of the same type --
of the same class or interface on which the manipulating function operates.  

Consider a class ShapeCollection, which manipulates a group of objects
of different shapes.  If a ShapeCollection needs to use data from a number
of different sources, there is a high probability that they will use 
different units.
(Lots of similar examples exist -- different graphics representations,
different toolkit interfaces, etc).  
Let's assume we have normal, well-behaved shapes, of class Shape as 
defined above, which deal in units of meters.  Let's also assume we 
have some BrainDeadShapes which deal in English units.  There could 
be huge differences in the actual implementation of these two classes 
of objects.  We can use an interface to abstract out the basic principles 
we are interested in, and then deal with all of these different objects 
in a uniform manner with one set of code in ShapeCollection.

For example, we can define an interface for SimpleShape, which
deals in units of meters:

public interface SimpleShape{
	float	circumference();	// circumference in meters
}

Our ShapeCollection class will deal with SimpleShapes only:

public class ShapeCollection {
	private	int		n_pt;				// number of 
parts
	private	SimpleShape	parts[];

	public	ShapeCollection() {
			n_pt = 0;
			parts = null;
	};

	public 	float		totalPerimeter( ) {	// total perimeter in 
meters
			float		p = 0;

		for ( int i=0 ; i<n_pt; i++)
			p += parts[i].circumference();
		return( p );
	};

	public	void		addShape( SimpleShape s) {	// Add a part 
to the ShapeCollection
		SimpleShape	p[] = new SimpleShape[n_pt+1];
		for ( int i=0 ; i<n_pt ; i++ )
			p[i] = parts[i];
		p[n_pt] = s;
		parts = p;
		n_pt++;
		return;
	};
};


In order for our ShapeCollection class to be able to manipulate both Shapes
and BrainDeadShapes, the only requirement is that each of them must implement 
the interface SimpleShape.  We can get this by making sure that
Shape and BrainDeadShape each implement the SimpleShape interface.

We modify Shape (slightly) to get:

public abstract class Shape implements SimpleShape {
			Shape()	{}
public abstract	float	circumference();
}

With the above, we don't have much work to do, since the name 
Shape.circumference
matches the interface definition in both meaning (units returned) and signature
(type of return value and arguments).

But consider a BrainDeadShape, which is based on a description of points in
a 2d plane, and English distance units, and which has an existing set of 
subclasses which compute the circumference in feet, via a function called 
perimeter:

public class Point {
private	int	x_val;	// township
private	int	y_val;	// range
public		Point( int xx, int yy ) { x_val=xx; y_val=yy };
public	int	x()	{ return( x_val ); };
public	int	y()	{ return( y_val ); };
};

public abstract class BrainDeadShape {
private		int		n_pts;	// number of points defining boundary
private		Point		pts[];	// the actual points
					BrainDeadShape()	{ n_pts=0; }
			void		setPoints( int np, Point sp[] )	{ 
n_pts=np; pts=sp; }
public abstract	float		perimeter();
public		Point		point( int i )	{ return( pts[i] ); }
};

public class BrainDeadRect extends BrainDeadShape {
public		BrainDeadRect( int x1, int y1, int x2, int y2 ) {
				Point pt[] = new Point[2];
				pt[0] = new Point(x1,y1);
				pt[1] = new Point(x2,y2);
				setPoints( 2, pt );
			};
public		float		perimeter()	{ 
				return( 2*(point(1).x()-point(0).x()) + 
					2*(point(1).y()-point(0).y()) ); 
			};
};

We can adapt this BrainDeadShape to work with our ShapeCollection as follows:

public abstract class BrainDeadShape implements SimpleShape {
					...
public 		float		circumference()	{ 
					return( (float) (perimeter() / 0.3048) 
);
			};
};

Now, we can create a ShapeCollection and add both Shapes and BrainDeadShapes,
and manipulate them using the same code:

ShapeCollection	c = new ShapeCollection();
Shape			s1 = new Circle( (float) 2. );
BrainDeadShape	s2 = new BrainDeadRect( 1, 3, 10, 7 );

	c.addShape( s1 );
	c.addShape( s2 );
	System.out.println( c.totalPerimeter() );

The main point -- the interface has allowed two object hierarchies with 
different
implementations, and different, unrelated class hierarchies, to implement the 
same
set of primitives, such that other objects may deal with them without the need 
to
differentiate between them. The implementation of the primitives does not 
disrupt
the original class hierarchies in any manner, preserving compatibility with all
existing code.

>3) Casting Classes. Allright, casting integers to longs - no problemo - I
>even understand the typwrapping
>function of the Boolean or Integer classes. But casting classes? The book
>(again..) says: "You can cast from a superclass to a subclass implicitly,
>without an explicit call for conversion, although explicit casts to
>subclasses are not an error." Daaah! <8^P

I believe this is in error; at least the Beta compiler complains.  
You may cast from a subclass to a superclass implicitly, but not vice-versa.
Using the above shape example:

	Shape  s1;
	Circle c1;
	Shape  s2;

	c1 = new Circle((float)2.);
	s1 = c1;	// ok, cast from subclass to superclass
	c1 = s1;	// invalid, cast from superclass to subclass
	c1 = (Circle) s1;	// valid, will be runtime checked for validity

>4)And finally - yes 'threading'.
> There are two different ways that you can provide a customized run()
>method for a Java thread:
>
>      1.Subclass the Thread class defined in the java.lang package and
>        override the run() method.
>      2.Provide a class that implements the Runnable interface, also defined
>        in the java.lang package. Now, when you instantiate a thread (either
>        directly from the Thread class, or from a subclass of Thread), give
>        the new thread a handle to an instance of your Runnable class. This
>        Runnable object provides the run() method to the thread.
>
>I don't understand the second method. (Maybe cause I don't know so much
>about applets yet and - see above I have no idea what interfaces are ).
>Anybody knows?

I think if you understand the above example of interface, you will 
understand this.

Hope this helps and isn't too long winded.
If you want a complete test (the above is almost complete, but
is missing all the package stmts, subdirectories, etc to make
it actually work), let me know and I will email you something.
Gary Aitken		garya@village.org
-
This message was sent to the java-interest mailing list
Info: send 'help' to java-interest-request@java.sun.com

home help back first fref pref prev next nref lref last post