[3381] in java-interest
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