TEAM-ADA Archives

Team Ada: Ada Programming Language Advocacy

TEAM-ADA@LISTSERV.ACM.ORG

Options: Use Classic View

Use Proportional Font
Show Text Part by Default
Condense Mail Headers

Topic: [<< First] [< Prev] [Next >] [Last >>]

Print Reply
Sender: "Team Ada: Ada Advocacy Issues (83 & 95)" <[log in to unmask]>
Date: Wed, 5 Sep 2001 11:45:50 +1000
MIME-version: 1.0
Content-type: text/plain; charset="iso-8859-1"
Parts/Attachments: text/plain (352 lines)
This is a long post, but illustrates some of the Pros and Cons of Ada vs Java in a practical, industrial computing sense.

Executive Summary:
An example is shown of how something trivially easy in Ada can be a real bear in Java.
The same example shows the extensive technical support for Java that is available to overcome these difficulties, and should make all readers on this list ponder the fact that there's no Ada equivalent. (Typical example: How to parse XML into Ada, How to generate HTML in Ada, How to implement a TCP/IP stack in Ada etc)

I wrote in a previous post:
> Now I like Java. After Ada, it's my favourite language, and I often find myself wishing "gee, I wish Ada could do X as quickly and easily as I can with Java 1.whatever". (Of course just as often, I wish that Java of any variety could do Y at all, when Ada-95 does it trivially, and Java suffers from fatal flaws due to its C ancestry, and .... but that's beside the point).

Example of the type of thing I mean:

Java Solution -

From Latest JDC Tech Tips:
--------QUOTE START -------------------------

MAKING DEFENSIVE COPIES OF OBJECTS

Suppose that you are doing some graphical programming using the
Rectangle class in the AWT. In your program, a Rectangle object
is passed to a constructor for a class. The constructor checks
the width and height of the Rectangle to make sure that the
values are positive. If width and height are positive, then the
volume of the rectangle (width * height) is guaranteed to be
positive. In the program, you also take other steps to ensure
that objects of your class reference only valid Rectangles. For
example, you make the class final. In this way, no subclass can
invalidate the error checking you do in the program.

The code looks like this:

import java.awt.Rectangle;

final class TestRect {
private final Rectangle rect;

public TestRect(Rectangle r) {

// check for width/height at least 1

if (r.width < 1 || r.height < 1) {
throw new IllegalArgumentException();
}

rect = r;
}

public int getVolume() {
return rect.width * rect.height;
}
}

public class CopyDemo1 {
public static void main(String args[]) {

// create a Rectangle and a TestRect

Rectangle r = new Rectangle(0, 0, 5, 10);
TestRect t = new TestRect(r);

// set width to an invalid value and
// compute volume

//r.width = -59;
System.out.println("Volume = " +
t.getVolume());
}
}

So is this code foolproof? Can a bad Rectangle object somehow get
through the checks and be referenced from within TestRect code?
The answer is yes. If you uncomment the line in the CopyDemo1
program that reads "r.width = -59", and then run the program, the
result is:

Volume = -590

which is probably not what you had in mind. The problem is that
when you pass an object as an argument to a method, you're really
passing a reference (pointer) to the object. This means that a
single object can be shared across various parts of a program.
See the December 5, 2000 Tech Tip "Returning Multiple Values From
a Method" for a further explanation of how arguments are passed
to a method.

How can you fix the problem illustrated above? One way is to make
a copy of the Rectangle passed to TestRect:

import java.awt.Rectangle;

final class TestRect {
private final Rectangle rect;

public TestRect(Rectangle r) {

// use copy constructor to copy Rectangle
// object

rect = new Rectangle(r);

// check for valid width/height

if (rect.width < 1 || rect.height < 1) {
throw new IllegalArgumentException();
}
}

public int getVolume() {
return rect.width * rect.height;
}
}

public class CopyDemo2 {
public static void main(String args[]) {

// create Rectangle and TestRect objects

Rectangle r = new Rectangle(0, 0, 5, 10);
TestRect t = new TestRect(r);

// set width to an invalid value

r.width = -59;

// compute volume

System.out.println("Volume = " +
t.getVolume());
}
}

In this approach, you make a copy using the copy constructor for
Rectangle, the constructor that takes a Rectangle argument and
creates a new object with the same values. You make the copy
before validity checking. The validity checking is done on the
copy to avoid subtle problems in multi-threaded programs. One of
these subtle problems is the possibility that another thread
could change the passed-in argument after it has been checked but
before it has been copied.

If you run the CopyDemo2 program, the result is:

Volume = 50

Because you made a copy of the argument within the constructor,
changing the width to -59 outside of the constructor has no
effect on the result. Note that making copies in this way has
some cost in speed and space, especially if you copy big objects.

Another way to apparently solve this problem is to use a clone()
method. But this approach doesn't always work, as the following
example illustrates:

import java.awt.Rectangle;

final class TestRect {
private final Rectangle rect;

public TestRect(Rectangle r) {

// clone the Rectangle object

rect = (Rectangle)r.clone();

// check for valid width/height

if (rect.width < 1 || rect.height < 1) {
throw new IllegalArgumentException();
}
}

public int getVolume() {
return rect.width * rect.height;
}
}

// subclass of Rectangle with bogus clone() method

class MyRectangle extends Rectangle {
public MyRectangle(int x, int y, int w, int h) {
super(x, y, w, h);
}

public Object clone() {
return this;
}
}

public class CopyDemo3 {
public static void main(String args[]) {

// create MyRectangle and TestRect objects

Rectangle r = new MyRectangle(0, 0, 5, 10);
TestRect t = new TestRect(r);

// set width to an invalid value

r.width = -59;

// compute volume

System.out.println("Volume = " +
t.getVolume());
}
}

Rectangle is not a final class, so it can be subclassed. In this
example, the class MyRectangle is defined with a degenerate clone
method, one that simply returns a reference to the object being
cloned. A clone method like this could either be malicious or
just poorly written. In either case, you don't get the desired
result. The output of the CopyDemo3 program is the same as the
CopyDemo1 program:

Volume = -590

The problem of shared access to objects can show up in other
contexts. Suppose you add an accessor method to TestRect
that returns the Rectangle reference stored within the object.
A user can then change the object in an arbitrary way, and the
assumptions you made about the object's validity no longer hold.

Another way of fixing the problem is to pass the width and height
values as integers, and not pass in a Rectangle object reference.
In general, you can also fix the problem by making a class
immutable, that is, objects of the class cannot be changed after
creation. But Rectangle is mutable, and so you need to find
another way to solve the problem.

Another situation where you might want to make a defensive copy
is illustrated in the following program:

class TestNames {
public static final String names[] =
{"red", "green", "blue"};
}

public class CopyDemo4 {
public static void main(String args[]) {
TestNames.names[0] = "purple";
System.out.println(TestNames.names[0]);
}
}

The fact that the names array is final means that you can't
assign to it. But you can still change the value of a particular
array slot. When you run this program, you get the result:

purple

In other words, you have effectively overwritten what is supposed
to be a read-only array.

How do you solve this problem? One way is to copy the array using
clone, another is to create a read-only view of the array using
the Collections Framework. Here's an illustration that shows both
approaches:

import java.util.*;

final class TestNames {
private static final String names[] =
{"red", "green", "blue"};

// return a copy of the names array

public static String[] getNames() {
//return names;
return (String[])names.clone();
}

// return a read-only List view of the array

public static List getNamesList() {
return Collections.unmodifiableList(
Arrays.asList(names));
}
}

public class CopyDemo5 {
public static void main(String args[]) {

// attempt to modify names[0] and then
// print its value

TestNames.getNames()[0] = "purple";
System.out.println(TestNames.getNames()[0]);

// get names array as a read-only list and
// print the value in the first slot

List list = TestNames.getNamesList();
System.out.println(list.get(0));

// attempt to modify the read-only list

//list.set(0, "purple");
}
}

Notice that this program makes the names array private, and
defines an accessor method for it, getNames(). However, simply
going this far does not solve the problem. You can still change
the first slot in the array. So the program clones the array.
After the cloning, if you change the first slot of the array, it
only affects the copy. Of course, cloning a large array has
a speed and space cost attached to it.

Notice too the Collections Framework mechanisms used in the
program. Arrays.asList() creates a list backed by an array.
Collections.unmodifiableList then creates a read-only view of the
list. The array is not copied in this case, but instead a
read-only List view is built on top of it.

These solutions involve either a shallow copy or no copy of the
underlying array. If the array contains mutable objects, then
there is still a problem because errant user code can change the
value of an individual object within the array. In the CopyDemo5
program above, an array of strings is used, and String objects
are immutable.

The need for defensive copying also shows up in other contexts,
such as after an object has been deserialized, or when an object
is added to a Set or Map. For example, one of the properties of
a set is that it contains no duplicate elements. What if a
mutable object is added to a set, and then it later changes so
that equals() is true between that object and another one in the
set? At this point, the set is corrupt. To avoid this problem,
a defensive copy should have been made.

For more information about making defensive copies of objects,
see item 24 "Make defensive copies when needed" in "Effective
Java Programming Language Guide" by Joshua Bloch
(http://java.sun.com/docs/books/effective/).

----- QUOTE END -----------------

Ada Solution:

Use a non-access-type parameter of mode "in".

Thus:

TestRect(R : in Rectangle);

Discussion:

Parameters of mode "in" may be read, but not written to. They can be treated in most respects as constants. If the parameter is an access type, then the contents of the object being accessed may be changed by other tasks, but the access itself can't be, it still references the same object.

------------------------------------------------------

ATOM RSS1 RSS2