Language basics, Part II |
Classes are CENTRAL to Java. To recap, a class contains related data (members) and functionality/behavior (methods). Creating a class is simple:
class A {
}// A
Once we have a class, we can instantiate (create objects of) it:
A anAObject = new A();
EVERY object in Java, in a running program, has a unique 'hash code' (internal ID) assigned to it by the JVM, it can be accessed (and printed) as someObject.hashCode().
Let's instantiate our simple (empty!) 'A' class and look at its hashCode:
public class A {
public static void main(String[] args) {
A a = new A();
System.out.println(a.hashCode());
}// main
}// A
The static main() function is the place to create objects, including an object of its own containing class! This is how a Java program is BOOTSTRAPPED - main() is where an object (or a set of objects) is/are created, and object methods invoked to start things off.
A class can have a constructor, or several constructors - to help create and initialize objects:
public class A2 {
// constructor (NO need, ever, for destructors!)
A2() {
System.out.println("A2's constructor");
}
public static void main(String[] args) {
A2 a2 = new A2();
}
}// A2
A constructor function has the same name as its class, NO return type (not even 'void'), and NO 'return' statement - just like in C++.
Let's create a simple 'Circle' class:
public class Circle {
int x=100;
int y=200;
int radius=25;
Circle() {
}
// if we implement public String toString() in our (any) class,
// we can simply say System.out.println(<ourobjectname>) ,
// eg. System.out.println(myCoolObject),
// to have our object printed out, in the manner we specify
// below!
public String toString() {
String prt = "Circle: ";
prt += "\tx=" + x;
prt += "\ty=" + y;
prt += "\tradius=" + radius;
return prt;
} // toString()
public static void main(String[] args) {
Circle c = new Circle();
System.out.println(c); // this is when .toString() gets called
// note that c.toString() also would work, in the above call - but
// we don't need to specify the extra .toString() method
}
}// Circle
This version of the Circle class has a constructor we can use to initialize x,y,radius.
Circle(int x, int y, int r) {
this.x = x;
this.y = y;
this.radius = r;
}
Wouldn't it be cool if we can SEE these circles, ie. draw them? It would be :) We can draw them into an applet's 'Graphics' canvas. We can implement a 'paint' method to do so (which an applet's own paint method can call).
So here is how we can make our circles paintable in an applet:
// we paint ourselves as an 'oval' centered at (x,y),
// given a graphics 'context'
public void paint(Graphics g) {
// Graphics has a drawOval(), we simply use it
g.drawOval(x-radius, y-radius, 2*radius, 2*radius);
}// paint()
Along with the Circle class, we also need this applet class where we create Circle objects (two of them) and call those objects' paint():
// applet to construct and paint circles
import java.awt.Graphics;
import java.applet.Applet;
public class CircleApplet extends Applet {
Circle c,c2;
public void init() {
c = new Circle();
c2 = new Circle(50,50,5);
}// init()
public void paint(Graphics g) {
c.paint(g);
c2.paint(g);
}// paint()
}// CircleApplet
Be sure to compile and run the above two classes (Circle, CircleApplet) together.
Note that an applet MUST have an init() method, and a paint() method - this is in contrast to a standalone ("regular") program that needs to have at least one 'psvm'. Why? Because an applet is NOT directly executed by the JVM, instead, an applet is executed by an 'applet runner', which knows to first call init() once, then call paint() repeatedly.
So inside CircleApplet, we did c = new Circle(); (likewise for c2). At run time, the JVM first loads and runs the CircleApplet class (because we ask it to start with this), then looks for a Circle.class (which needs to be in the same dir as CircleApplet.class) and loads and executes it - THAT is how we 'tie' various classes (in various files) together!
Unlike C++, we do NOT need to include one class' file inside another file etc (in C++ we would include a class' .h file inside another class' .cpp file)!! Instead, we can write and compile (and even test, via its own 'psvm') each .java file SEPARATELY - we can 'tie together' separate classes, eg. link classes A and B, by doing something like B b = new B( ) inside a function in class A. ALSO, this means we can have a psvm in as many classes as we want, and can specify one of them to run (eg. 'java A' will run A.class, 'java B' will run B.class - if each has its own psvm, that is what will get executed).
Think about this - make sure you understand how this can keep things cleanly separated, yet combinable when necessary at run time.
Real world applications can involve hundreds of classes, that start out as 'loose' .class files and get 'compressed' into .jar (which is actually .zip) files. As long as the JVM knows the PATH/location (called CLASSPATH) to these .jar or .class files, the corresponding classes inside those .jar or .class files can be dynamically (at run time) located, loaded and executed.
The 'extends' keyword is all is needed (similar to : in C++) to subclass an existing class. As an example, let us extend our Circle class, to create a FilledCircle class, where we can specify a 'fill color' to fill the circle's interior:
public class FilledCircle extends Circle {
java.awt.Color fillColor = java.awt.Color.red;
// ....
}// FilledCircle
Correspondingly, in our paint() method, we use fillOval():
public void paint(Graphics g) {
// we use our own fillColor below, and
// the INHERITED x,y,radius members
g.setColor(fillColor);
g.fillOval(x-radius, y-radius, 2*radius, 2*radius);
}// paint()
This is FilledCircleApplet.java where we create FilledCircles (and Circles, too) - be sure to run this along with FilledCircle.java and Circle.java.
Remember 'pure virtual' base classes in C++, where a base class simply names function protos and equates them to 0s, thereby 'forcing' its children to implement them? In Java, such a class would be an 'abstract' class, with abstract function declarations.
Eg. we can create an abstract 'Paintable' class, place paint() in it, and have Circle extend it:
// every abstract method declared below, MUST be implemented
// by Paintable's children
abstract class Paintable {
abstract void paint(Graphics g);
}// Paintable
public class Circle extends Paintable {
// MUST implement paint()
// ...
}
Here is the new version of Circle, and the same CircleApplet as before. Note that FilledCircle does NOT also need to extend Paintable, because its parent already has.
What if we want to implement abstract functions from multiple abstract classes, eg.
// anti pattern
abstract class A {
abstract void a();
}// A
abstract class B {
abstract void b();
}// B
class X extends A,B {
void a(){}
void b(){}
}// X
Try to compile such code, see what happens. Can't :( But we do NEED to be able to specify abstract function groupings (sets of function calls that together achieve a purpose, eg. database connectivity, printability..), for subclasses to implement. Solution: interfaces.
An interface simply collects together a 'bundle' of method calls, in the form of prototypes (NO function body). If some class 'implements' an interface, that class NEEDS to supply the implementations, for not some, but for ALL the interfaces' methods.
Rather than 'abstract class', we can use 'interface' to declare functions that a class would need to 'implement', should such a class want the functionality.
A class can implement MULTIPLE interfaces - THAT is how we obtain the benefits of multiple 'inheritance' in Java. On the other hand, a class can extend (inherit from) just ONE class.
Here is the working version of the 'anti pattern' above (do try compiling and running this):
interface A {
public void a();
}// A
interface B {
public void b();
}// B
class X implements A,B {
public void a(){}
public void b(){}
}// X
Here is paint() again, this time, declared in an interface (that the implementing Circle class should define):
interface Paintable {
public void paint(Graphics g);
}// Paintable
// or, public class Circle implements Paintable, Printable, DBStorage..
public class Circle implements Paintable {
// MUST implement paint()
// ...
Interface-implementing Circle and the 'driver' CircleApplet show this in action.
Note that Circle can define paint(), "on its own" [like we did at first], without using (implementing) the Paintable interface; likewise, it can also implement many more functions that might be found in other interfaces, without actually using them via the 'implements' keyword - so why does Java even provide an 'interface' mechanism?
Answer: the advantage of using an interface is this - it is a 100% guarantee that every method in the interface (which was placed there because it is important, and necessary, for some specific functionality that would result) MUST BE defined by its implementing class (eg. Circle), not just a subset of the methods, for the code to compile! In other words, it is to make sure that no methods are left out during implementation by accident - it is an organizational device, to help us write good code [it's a "checklist" of sorts!]. Think of an interface as 'subclassing without actually subclassing'.
Let's start with a basic Point class, and add supporting code to create a point array, fill it with points that are 'repelled' from a given point, and draw those points into a screen buffer (display).
To make it easy to get/set x,y, we make them public (not good O-O practice, but it is an expedient choice).
public class Point {
public double x;
public double y;
...
Next, let's imagine we have an interface called 'Proximity' that we need to implement, to create a withinRadius() method - this method will let us know (true/false) if another given point is close to us or not. Note that we create a private distance() method, to call from withinRadius().
interface Proximity {
// is point 'p' within distance 'd' of the implementor?
public boolean withinRadius(Point p, double d);
}// Proximity
// A class to represent points in the Euclidean plane
public class Point implements Proximity {
...
Next, let's generate points as before, and STORE (collect) the subset of points that are in proximity to a given point (generate point, test for proximity, store if 'far away'), into a growable (dynamic) point array.
To do so, we use the templated ArrayList class, which, fyi, extends AbstractList and also implements the List interface.
...
ArrayList<Point> pointList = new ArrayList(); // templated!
...
Point p = new Point(100);
...
pointList.add(p); // STORE the point
...
System.out.println("Generated " + pointList.size() + " points..");
...
Time to plot! In CanvasPlot.java, we set up the drawing surface, then create a new PointArray object, and ask it ("send a message" to it) to generatePoints(). We then loop through the returned pointArray (which is an ArrayList<Point> type object), and 'plot' each point using a small white circle.
Note that we are using classes from three source files: Point.java, PointArray.java, CanvasPlot.java.
The big picture: a CanvasPlot object contains a .view member, which is actually an 'image label' (containing pixel data); in our main(), we create a frame, and add this image label to it.
public CanvasPlot()
{
surface = new BufferedImage(400,400,BufferedImage.TYPE_INT_RGB);
view = new JLabel(new ImageIcon(surface));
Graphics g = surface.getGraphics();
g.setColor(Color.BLUE);
g.fillRect(0,0,400,400);
g.setColor(Color.BLACK);
PointArray pA = new PointArray();
ArrayList<Point> pL = pA.generatePoints(100,400,new Point(200,200),200);
...
This is an example of combining our own code (Point, PointArray, repulsion-based inclusion) with Java's built-in classes that are in CanvasPlot.java (eg. BufferedImage, Graphics..).
Exercise: modify/add to the above code (in PointArray.java), to calculate and print out π(!!) Hint: "Monte Carlo" (or "Macao") :)