Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Tuesday, December 11, 2007

Java is Pass-By-Value, Dammit!

Introduction

http://javadude.com/articles/passbyvalue.htm


I finally decided to write up a little something about Java's parameter passing. I'm really tired of hearing folks (incorrectly) state "primitives are passed by value, objects are passed by reference".

I'm a compiler guy at heart. The terms "pass-by-value" semantics and "pass-by-reference" semantics have very precise definitions, and they're often horribly abused when folks talk about Java. I want to correct that... The following is how I'd describe these

Pass-by-value
The actual parameter (or argument expression) is fully evaluated and the resulting value is copied into a location being used to hold the formal parameter's value during method/function execution. That location is typically a chunk of memory on the runtime stack for the application (which is how Java handles it), but other languages could choose parameter storage differently.
Pass-by-reference
The formal parameter merely acts as an alias for the actual parameter. Anytime the method/function uses the formal parameter (for reading or writing), it is actually using the actual parameter.

Java is strictly pass-by-value, exactly as in C. Read the Java Language Specification (JLS). It's spelled out, and it's correct. (See http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#37472)

In short: Java has pointers and is strictly pass-by-value. There's no funky rules. It's simple, clean, and clear. (Well, as clear as the evil C++-like syntax will allow ;)

Note: See the note at the end of this article for the semantics of remote method invocation (RMI). What is typically called "pass by reference" for remote objects is actually incredibly bad semantics.


The Litmus Test

There's a simple "litmus test" for whether a language supports pass-by-reference semantics:

Can you write a traditional swap(a,b) method/function in the language?

A traditional swap method or function takes two arguments and swaps them. Its basic structure looks like

// NON-JAVA!
swap(Type arg1, Type arg2) {
Type temp = arg1;
arg1 = arg2;
arg2 = temp;
}

If you can write such a method/function in your language such that calling

// NON-JAVA
Type var1 = ...;
Type var2 = ...;
swap(var1,var2);

actually switches the values of the variables var1 and var2, the language supports pass-by-reference semantics.

For example, in Pascal, you can write

{ Pascal }
procedure swap(var arg1, arg2: SomeType);
var
temp : SomeType;
begin
temp := arg1;
arg1 := arg2;
arg2 := temp;
end;

...
{ in some other procedure/function/program }
var
var1, var2 : SomeType;
begin
var1 := ...;
var2 := ...;
swap(var1, var2);
end;

or in C++ you could write

// C++
void swap(SomeType& arg1, Sometype& arg2) {
SomeType temp = arg1;
arg1 = arg2;
arg2 = temp;
}

...

SomeType var1 = ...;
SomeType var2 = ...;
swap(var1, var2); // swaps their values!

(Please let me know if my Pascal or C++ has lapsed and I've messed up the syntax...)

But you cannot do this in Java!


Now the details...

The problem we're facing here is statements like

In Java, Objects are passed by reference, and primitives are passed by value.

This is half incorrect. Everyone can easily agree that primitives are passed by value; there's no such thing in Java as a pointer/reference to a primitive.

However, Objects are not passed by reference. A correct statement would be Object references are passed by value.

This may seem like splitting hairs, bit it is far from it. There is a world of difference in meaning. The following examples should help make the distinction.

In Java, take the case of

  public void foo(Dog d) {
d = new Dog("Fifi");
}

Dog aDog = new Dog("Max");
foo(aDog);

the variable passed in (aDog) is not modified! After calling foo, aDog still points to the "Max" Dog!

Many people mistakenly think/state that something like

  public void foo(Dog d) {
d.setName("Fifi");
}

shows that Java does in fact pass objects by reference.

The mistake they make is in the definition of

  Dog d;

itself. When you write

  Dog d;

you are defining a pointer to a Dog object, not a Dog object itself.

Calling

  foo(d);

passes the value of d to foo; it does not pass the object that d points to!

The value of the pointer being passed is similar to a memory address. Under the covers it's a tad different, but you can think of it in exactly the same way. The value uniquely identifies some object on the heap.

The use of the word "reference" in Java was an incredibly poor choice (in my not-so-humble opinion...) Java has pointers, plain and simple. The designers of Java wanted to try to make a distinction between C/C++ pointers and Java pointers, so they picked another term. Under the covers, pointers are implemented very differently in Java and C/C++, and Java protects the pointer values, disallowing operations such as pointer arithmetic and invalid runtime casting.

However, it makes no difference how pointers are implemented under the covers. You program with them exactly the same way in Java as you would in C or C++. The syntax is just slightly different.

In Java,

  Dog d;   // Java

is exactly like C or C++'s

  Dog *d;  // C++

And using

  d.setName("Fifi");  // Java

is exactly like C++'s

  d->setName("Fifi"); // C++

To sum up: Java has pointers, and the value of the pointer is passed in. There's no way to actually pass an object itself as a parameter. You can only pass a pointer to an object.

Keep in mind, when you call

  foo(d);

you're not passing an object; you're passing a pointer to the object.

For a slightly different (but still correct) take on this issue, please see http://www-106.ibm.com/developerworks/library/j-praxis/pr1.html. It's from Peter Haggar's excellent book, Practical Java.)


A Note on Remote Method Invocation (RMI)

When passing parameters to remote methods, things get a bit more complex. First, we're (usually) dealing with passing data between two independent virtual machines, which might be on separate physical machines as well. Passing the value of a pointer wouldn't do any good, as the target virtual machine doesn't have access to the caller's heap.

You'll often hear "pass by value" and "pass by reference" used with respect to RMI. These terms have more of a "logical" meaning, and really aren't correct for the intended use.

Here's what is usually meant by these phrases with regard to RMI. Note that this is not proper usage of "pass by value" and "pass by reference" semantics:

RMI Pass-by-value
The actual parameter is serialized and passed using a network protocol to the target remote object. Serialization essentially "squeezes" the data out of an object/primitive. On the receiving end, that data is used to build a "clone" of the original object or primitive. Note that this process can be rather expensive if the actual parameters point to large objects (or large graphs of objects).
This isn't quite the right use of "pass-by-value"; I think it should really be called something like "pass-by-memento". (See "Design Patterns" by Gamma et al for a description of the Memento pattern).
RMI Pass-by-reference
The actual parameter, which is itself a remote object, is represented by a proxy. The proxy keeps track of where the actual parameter lives, and anytime the target method uses the formal parameter, another remote method invocation occurs to "call back" to the actual parameter. This can be useful if the actual parameter points to a large object (or graph of objects) and there are few call backs.
This isn't quite the right use of "pass-by-reference" (again, you cannot change the actual parameter itself). I think it should be called something like "pass-by-proxy". (Again, see "Design Patterns" for descriptions of the Proxy pattern).

Thursday, November 15, 2007

STL: vector vs. list

They solve different problems, so it depends on the "shape" of your data, and which is more important to you, inserting, deleting or accessing quickly.

Lists are double-linked lists, so insertion at either end, or in the middle (providing you know an existing element in the middle) is equally fast. Also, splicing is fast (eg. putting a list into the middle of another) - all that does is re-arrange the end pointers. All of those operations would be slower with vectors, because you would need to copy everything.

eg. Inserting into the middle of a vector of 1000 items would involve moving 500 items up one. Similarly with deleting. In fact, vectors are really only good for insertion and deletion at the end.

For things that require insertion and deletion, but only at *each* end (like a queue) then there is a dequeue (double-ended queue) which is really a collection of vectors. This is more efficient.

However, vectors shine in random access - since they are organised sequentially in memory, whilst this is slow for insertion at anywhere other than the end, you can easily find, say, element 500, you simply index into the vector. Thus, vectors can be sorted, shuffled, and accessed randomly.

What STL ever done for us?

"Well, yes, vectors, I mean obviously the vectors are good ..."
"Don't forget queues Reg, I mean, where would we be without properly organized queues and iterators for any data type ?" General murmurs of agreement
"Yes, alright, apart from vectors and queues ..."
"Sorts Reg - I hated having to code up a new sort routine for every class." Here, here, etc.
"Right. So apart from vectors, queues and associated containers classes, iterators, various useful algorithms and functions, what has the STL ever done for us ?"
"Memory allocators Reg. We can allocate container memory using any scheme we like, and change it without rewriting all the code. It keeps things in order" Reg loses his temper
"Order ? Order ? Oh shut up!"

At the end of this section, the waffle above should start making sense to you, but is unlikely to become more humorous as a result of your studies.

There are three types of sequence containers in the STL. These, as their name suggests, store data in linear sequence. They are the vector, deque and list:

  • vector
  • deque
  • list

To choose a container, decide what sort of operations you will most frequently perform on your data, then use the following table to help you.

Time overhead of operations on sequence containers
Operation Vector Deque List
Access 1st Element Constant Constant Constant
Access last Element Constant Constant Constant
Access "random" element Constant Constant Linear
Add/Delete at Beginning Linear Constant Constant
Add/Delete at End Constant Constant Constant
Add/Delete at "random" Linear Linear Constant

Each container has attributes suited to particular applications. The subsections and code samples below should further clarify when and how to use each type of sequence container.

full version: http://www.pottsoft.com/home/stl/stl.htmlx


The Daubechies D4 Wavelet Transform in C++ and Java

http://www.bearcave.com/software/java/wavelets/daubechies/index.html

I recommend using the "save as" feature of your browser to save the C++ and Java source files (I'm not sure how to reliably suppress viewing and force download with all browsers).

  • The Daubechies D4 algorithm as a C++ class

  • Doxygen generated documentation for the C++ version of the Daubechies D4 algorithm. This documentation was generated with this Doxygen configuration file.

  • The Daubechies D4 algorithm as a Java class


Daubechies D4 wavelet transform (D4 denotes four coefficients)

I have to confess up front that the comment here does not even come close to describing wavelet algorithms and the Daubechies D4 algorithm in particular. I don't think that it can be described in anything less than a journal article or perhaps a book. I even have to apologize for the notation I use to describe the algorithm, which is barely adequate. But explaining the correct notation would take a fair amount of space as well. This comment really represents some notes that I wrote up as I implemented the code. If you are unfamiliar with wavelets I suggest that you look at the bearcave.com web pages and at the wavelet literature. I have yet to see a really good reference on wavelets for the software developer. The best book I can recommend is Ripples in Mathematics by Jensen and Cour-Harbo.

All wavelet algorithms have two components, a wavelet function and a scaling function. These are sometime also referred to as high pass and low pass filters respectively.

The wavelet function is passed two or more samples and calculates a wavelet coefficient. In the case of the Haar wavelet this is

  coefi = oddi - eveni
or
coefi = 0.5 * (oddi - eveni)

depending on the version of the Haar algorithm used.

The scaling function produces a smoother version of the original data. In the case of the Haar wavelet algorithm this is an average of two adjacent elements.

The Daubechies D4 wavelet algorithm also has a wavelet and a scaling function. The coefficients for the scaling function are denoted as hi and the wavelet coefficients are gi.

Mathematicians like to talk about wavelets in terms of a wavelet algorithm applied to an infinite data set. In this case one step of the forward transform can be expressed as the infinite matrix of wavelet coefficients represented below multiplied by the infinite signal vector.

     ai = ...h0,h1,h2,h3, 0, 0, 0, 0, 0, 0, 0, ...   si
ci = ...g0,g1,g2,g3, 0, 0, 0, 0, 0, 0, 0, ... si+1
ai+1 = ...0, 0, h0,h1,h2,h3, 0, 0, 0, 0, 0, ... si+2
ci+1 = ...0, 0, g0,g1,g2,g3, 0, 0, 0, 0, 0, ... si+3
ai+2 = ...0, 0, 0, 0, h0,h1,h2,h3, 0, 0, 0, ... si+4
ci+2 = ...0, 0, 0, 0, g0,g1,g2,g3, 0, 0, 0, ... si+5
ai+3 = ...0, 0, 0, 0, 0, 0, h0,h1,h2,h3, 0, ... si+6
ci+3 = ...0, 0, 0, 0, 0, 0, g0,g1,g2,g3, 0, ... si+7

The dot product (inner product) of the infinite vector and a row of the matrix produces either a smoother version of the signal (ai) or a wavelet coefficient (ci).

In an ordered wavelet transform, the smoothed (ai) are stored in the first half of an n element array region. The wavelet coefficients (ci) are stored in the second half the n element region. The algorithm is recursive. The smoothed values become the input to the next step.

The transpose of the forward transform matrix above is used to calculate an inverse transform step. Here the dot product is formed from the result of the forward transform and an inverse transform matrix row.

      si = ...h2,g2,h0,g0, 0, 0, 0, 0, 0, 0, 0, ...  ai
si+1 = ...h3,g3,h1,g1, 0, 0, 0, 0, 0, 0, 0, ... ci
si+2 = ...0, 0, h2,g2,h0,g0, 0, 0, 0, 0, 0, ... ai+1
si+3 = ...0, 0, h3,g3,h1,g1, 0, 0, 0, 0, 0, ... ci+1
si+4 = ...0, 0, 0, 0, h2,g2,h0,g0, 0, 0, 0, ... ai+2
si+5 = ...0, 0, 0, 0, h3,g3,h1,g1, 0, 0, 0, ... ci+2
si+6 = ...0, 0, 0, 0, 0, 0, h2,g2,h0,g0, 0, ... ai+3
si+7 = ...0, 0, 0, 0, 0, 0, h3,g3,h1,g1, 0, ... ci+3

Using a standard dot product is grossly inefficient since most of the operands are zero. In practice the wavelet coefficient values are moved along the signal vector and a four element dot product is calculated. Expressed in terms of arrays, for the forward transform this would be:

  ai = s[i]*h0 + s[i+1]*h1 + s[i+2]*h2 + s[i+3]*h3
ci = s[i]*g0 + s[i+1]*g1 + s[i+2]*g2 + s[i+3]*g3

This works fine if we have an infinite data set, since we don't have to worry about shifting the coefficients "off the end" of the signal.

I sometimes joke that I left my infinite data set in my other bear suit. The only problem with the algorithm described so far is that we don't have an infinite signal. The signal is finite. In fact not only must the signal be finite, but it must have a power of two number of elements.

If i=N-1, the i+2 and i+3 elements will be beyond the end of the array. There are a number of methods for handling the wavelet edge problem. This version of the algorithm acts like the data is periodic, where the data at the start of the signal wraps around to the end.

This algorithm uses a temporary array. A Lifting Scheme version of the Daubechies D4 algorithm does not require a temporary. The matrix discussion above is based on material from Ripples in Mathematics, by Jensen and Cour-Harbo. Any error are mine.

Author: Ian Kaplan
Use: You may use this software for any purpose as long as I cannot be held liable for the result. Please credit me with authorship if use use this source code.

This comment is formatted for the doxygen documentation generator