Passing Arrays By Reference In ColdFusion - SWEEET!
Posted September 19, 2006 at 8:18 AM
I happened upon this discovery last night and I am very excited about it. Yes, learning IS fun and this goes to show you have important messing around with code is. For those of you who have been programming in ColdFusion for a while, you know that complex structures such as structures, components, and Java objects are passed around by reference and simple values like strings, dates, numbers, and even arrays are passed by value. For values that are passed by value, you know that if you pass them to a function for alteration, you MUST past the new value back otherwise the changes will be lost.
Let's explore this idea. First, I am going to create an array of girls:
Launch code in new window » Download code as text file »
- <!--- Define the array. --->
- <cfset arrGirls = ArrayNew( 1 ) />
-
- <!--- Set some values. --->
- <cfset arrGirls[ 1 ] = "Sarah" />
- <cfset arrGirls[ 2 ] = "Libby" />
- <cfset arrGirls[ 3 ] = "Mary-Kate" />
- <cfset arrGirls[ 4 ] = "Ashley" />
Now that we have that set up, I am going to define a ColdFusion function that will take that array and alter it by adding some girls to it:
Launch code in new window » Download code as text file »
- <cffunction
- name="AddGirls"
- returntype="void"
- output="false"
- hint="Adds some girls to the passed in array.">
-
- <!--- Define arguments. --->
- <cfargument name="Array" type="array" required="true" />
-
- <!--- Add some girls to the array. --->
- <cfset ArrayAppend( ARGUMENTS.Array, "Heather" ) />
- <cfset ArrayAppend( ARGUMENTS.Array, "Azure" ) />
- <cfset ArrayAppend( ARGUMENTS.Array, "Myriam" ) />
- <cfset ArrayAppend( ARGUMENTS.Array, "Lori" ) />
-
- <!--- Return out. --->
- <cfreturn />
- </cffunction>
Now, if I CFDump out the initial array, you will see that it has the first 4 girls:
| | | | ||
| | ![]() | | ||
| | | |
Now, I am going to pass the array to the altering function:
Launch code in new window » Download code as text file »
- <!--- Add some girls! --->
- <cfset AddGirls( arrGirls ) />
One might hope that when this array gets passed to the function, AddGirls(), that it is passed by reference. If that were the case, you would see the four additional girls in the next CFDump:
| | | | ||
| | ![]() | | ||
| | | |
As you can see, however, the only girls that show up in this last CFDump were the original four girls. That is because the array is passed by value. In the function, a complete copy of the array was altered and the original was never changed.
But, it's time to rock ... your ... world, baby-oh-baby!
First, let's take a look at the function, AddGirls(). Look at how it actually adds girls to the given array. It uses the ArrayAppend() ColdFusion function. Look closely. That function is NOT returning any value. That means that for that function call, the array IS getting passed by value. This might seem insignificant, but it is the clue needed to solve the "pass by reference" mystery.
If you look at what the ArrayAppend() method takes, it takes two arguments. At first glance, you might think it takes an array as the first argument and the value as the second argument. This is incorrect! If you look at the Java implementation of the method, the first argument is NOT an array. It is, in fact, an instance of java.util.List. If you look at what a List is in Java 2, you will see that it is an interface definition for a family of collection objects. This is "base class" of object, if you will. What that means is that ArrayAppend() is expecting ANY object that implements the interface java.util.List.
Ok, so how did I make that jump? Well, for one, that is part of what interfaces are used for (polymorphism??). But, if you just look at what a ColdFusion array is underneath the surface, you will see that it is in fact a Java object of type coldfusion.runtime.Array. Now, how does an object of type coldfusion.runtime.Array pass for an object of type java.util.List? Because coldfusion.runtime.Array implements the java.util.List interface.
Ladies and gentlemen, this is HUGE. It basically means that we can treat anything that implements java.util.List AS AN ARRAY! What? Get out of here with that! No - it's true.
Let's explore...
If you look at the classes the implement the java.util.List interface, the first one that pops out (at least it did for me) is the class java.util.ArrayList. It just feels right, doesn't it? Let's recreate our first example, except instead of declaring a standard ColdFusion array, let's create a Java ArrayList:
Launch code in new window » Download code as text file »
- <!--- Define the array AS AN ARRAY LIST. --->
- <cfset arrJavaGirls = CreateObject(
- "java",
- "java.util.ArrayList"
- ).Init() />
-
- <!--- Set some values. --->
- <cfset arrJavaGirls[ 1 ] = "Sarah" />
- <cfset arrJavaGirls[ 2 ] = "Libby" />
- <cfset arrJavaGirls[ 3 ] = "Mary-Kate" />
- <cfset arrJavaGirls[ 4 ] = "Ashley" />
I am renaming the array "arrJavaGirls" so you don't think I am trying to fool you somehow. When we dump out this object, you will see that in fact, it IS acting just like any ColdFusion array would:
| | | | ||
| | ![]() | | ||
| | | |
Notice that I can use the java.util.ArrayList just as if it were a regular indexed array. I set values using array notation. In fact, all ColdFusion array manipulation functions will work on it. Here are a few to demonstrate:
Launch code in new window » Download code as text file »
- <!--- Append a value. --->
- <cfset ArrayAppend( arrJavaGirls, "Kimmie" ) />
-
- <!--- Prepend a value. --->
- <cfset ArrayPrepend( arrJavaGirls, "Christina" ) />
-
- <!--- Delete a value. --->
- <cfset ArrayDeleteAt( arrJavaGirls, 4 ) />
-
- <!--- Insert a value. --->
- <cfset ArrayInsertAt( arrJavaGirls, 4, "Jo" ) />
CFDumping out the array you will see that it acts exactly like a standard ColdFusion array.
| | | | ||
| | ![]() | | ||
| | | |
Do you believe me? Implementing the java.util.List interface is all we needed. While I do not show it here, I have also testing things like ArrayAvg(), ArraySum(), ArrayMin(), and ArrayMax() and they ALL work. Now, how does this work with our function above, AddGirls()? Let's give it a go.
Launch code in new window » Download code as text file »
- <!--- Add some girls! --->
- <cfset AddGirls( arrJavaGirls ) />
Notice that I am NOT expecting any return value. I am passing the array as the only action and hoping that it is used by reference. And, CFDumping the array value now:
| | | | ||
| | ![]() | | ||
| | | |
... YOU WILL SEE THAT THE ORIGINAL ARRAY WAS UPDATED! Yes, that means the array WAS passed by reference and NOT by value. Not only that, look at the argument in the AddGirls() method:
Launch code in new window » Download code as text file »
- <!--- Define arguments. --->
- <cfargument name="Array" type="array" required="true" />
It is expecting a object of type "array." This just goes to show that our java.util.ArrayList is a VALID ColdFusion array. If it looks like an array, smells like an array, and walks like an array - it's an array. Just to drive the point home, let's perform perhaps the most popular action on an array: Looping over it and outputting values:
Launch code in new window » Download code as text file »
- <!--- Loop over array. --->
- <cfloop
- index="intI"
- from="1"
- to="#ArrayLen( arrJavaGirls )#"
- step="1">
-
- <!--- Output indexed value. --->
- #intI# : #arrJavaGirls[ intI ]#<br />
-
- </cfloop>
You will see that this gives us the following list, just as we would expect an array to do:
1 : Christina
2 : Sarah
3 : Libby
4 : Jo
5 : Ashley
6 : Kimmie
7 : Heather
8 : Azure
9 : Myriam
10 : Lori
The only reason that you know that this is not a standard ColdFusion array is because of the array declaration. Everything else acts exactly the same.... well, not exactly the same. This object IS PASSED BY REFERENCE.
Now, just as a test, I wonder how this affects performance? My gut feeling is that passing an array by reference is going to be faster than constantly duplicating array values. But, on the other hand, there is an overhead to instantiating and passing around a component. Let's see it in action.
For this test, we have to define another function that returns an array. Since ColdFusion arrays are inherently passed by value, we need a function that will update the passed-in array and then return the updated array:
Launch code in new window » Download code as text file »
- <cffunction
- name="AlterGirls"
- returntype="array"
- output="false"
- hint="Adds some girls to the passed in array and pass it back.">
-
- <!--- Define arguments. --->
- <cfargument name="Array" type="array" required="true" />
-
- <!--- Add some girls to the array. --->
- <cfset ArrayAppend( ARGUMENTS.Array, "Heather" ) />
- <cfset ArrayAppend( ARGUMENTS.Array, "Azure" ) />
- <cfset ArrayAppend( ARGUMENTS.Array, "Myriam" ) />
- <cfset ArrayAppend( ARGUMENTS.Array, "Lori" ) />
-
- <!--- Return altered array. --->
- <cfreturn ARGUMENTS.Array/>
- </cffunction>
As you can see, this one is basically the same as our first function. The only differences are that this one has a return type of "array" and actually returns the altered array. Now, for ColdFusion arrays, those are duplicates being passed around. For the java.util.ArrayList, those are references being passed around. Now, onto the testing:
Launch code in new window » Download code as text file »
- <!--- Set up the iterations. --->
- <cfset intIterations = 10000 />
-
- <!--- Test the standard array calls. --->
- <cftimer label="Test ColdFusion Array" type="outline">
-
- <!--- Loop for speed testing. --->
- <cfloop index="intI" from="1" to="#intIterations#" step="1">
-
- <!--- Alter the array. --->
- <cfset arrGirls = AlterGirls( arrGirls ) />
-
- </cfloop>
-
- <!--- Output final size. --->
- Final Size: #ArrayLen( arrGirls )#
-
- </cftimer>
-
-
- <!--- Test the java.util.ArrayList calls. --->
- <cftimer label="Test java.util.ArrayList" type="outline">
-
- <!--- Loop for speed testing. --->
- <cfloop index="intI" from="1" to="#intIterations#" step="1">
-
- <!--- Alter the array. --->
- <cfset AlterGirls( arrJavaGirls ) />
-
- </cfloop>
-
- <!--- Output final size. --->
- Final Size: #ArrayLen( arrJavaGirls )#
-
- </cftimer>
Holy crap! The test was insane! For 10,000 iterations, the ColdFusion array test took a draw-dropping 292,087 ms. The java.util.ArrayList test took a sleek, sexy 281 ms. Grabbing the calculator, I see that this means that the java.util.ArrayList performs in .00096 percent of the time that the ColdFusion array took. I don't think I have ever seen such a HUGE performance difference in all my testing in ColdFusion.
So, to sum up:
Test ColdFusion Array
Time: 292,087 ms
Final Size: 40,004 items
Test java.util.ArrayList
Time: 281 ms
Final Size: 40,010 items
I hope this has rocked your world the way it has rocked mine.
Download Code Snippet ZIP File
Post Comment | Ask Ben | Permalink | Other Searches | Print Page
Newer Post
Project HUGE : Getting A Baseline For Legs
Older Post
Java Exploration in ColdFusion: java.io.LineNumberReader
Reader Comments
Just to let you know, Ben, you could try mucking around with Struct as well - anything that implements the java.util.Map interface can be treated as a Struct.
It makes sense that a list (which is a String) would be considerable slower than Java array. Concatenating strings in Java is very slow when the string gets large in size.
I wonder if Adobe will eventually change the way the Array works. I wonder if this has anything to do w/Arrays in Java starting at zero (0) and Arrays in CF starting at one (1.)
Ashwin,
That is good to now. Structs are pretty darn cool already... but a little bit of experimentation never hurts :)
Dan,
I am not sure if the indexing is going to matter. When you attempt to use the java object as a ColdFusion array, it seems to translate the indexes for you. Likewise with a string. In Java, you can get the first character using string.CharAt( 0 )... however, when you treat the same object like a ColdFusion string, the first letter is at index 1: Mid( string, 1, 1 ).
oops, "that is good to know".
That IS pretty sweet.
How come one of the arrays has 6 more items than the other at the end of the test? Did they not start off empty?
I'm curious how this test scales. In other words, if you did 10,000 manipulations of a small array, if the difference would be as big. I assume that the normal array method really bogs down once your array grows in size like your test.
Brad,
Yeah, for the testing, I used the arrays that were created earlier in the example. So, one had the original 4 items I think and the other had a few more. But, once the time-testing is underway, they are being manipulated similarly.
Excellent question about the manipulation of small arrays. I will try that at lunch :)
Try and run this code (hope the tags will not get killed):
<cfset javaArray = CreateObject("java", "java.util.ArrayList").Init()/>
<cfset regArray = arrayNew(1)>
<cfset arrayResize(regArray,100000)>
<!--- <cfset arrayResize(javaArray,100000)> --->
<cftimer label="Test Java Array" type="outline">
<cfloop index="m" from="1" to="100000" step="1">
<cfset javaArray[m] = m>
</cfloop>
</cftimer>
<cftimer label="Test ColdFusion Array" type="outline">
<cfloop index="k" from="1" to="100000" step="1">
<cfset regArray[k] = k>
</cfloop>
</cftimer>
Tom,
Good comment. It demonstrates that for straight up setting of cell values, the ColdFusion array trends towards slightly better performance. Sometimes the Java array was faster, but mostly the ColdFusion one was faster by like 30-50 ms over 10,000 iterations.
At lunch, though, I want to test how a similar test performs when the array is passed as an argument to a method.
For those of you not on CF-Talk, Tom just posted an excellent catch. The java array does NOT support ArrayResize(). That means that you can't just pass it around like it was any old array.
I got so excited about this feature that perhaps I assumed too much. I will go back to the drawing board and see what I can figure out as a fix to this.
Dangy, and I thought this was so freakin' cool!
One thing you MUST be aware of when using ArrayLists and various other collections.
They are NOT thread safe by design (and may account for some of the times you are getting on performance).
If you are going to use them in CFCs that are singletons (i.e. placed in a shared scope), you will have to create syncronised versions of the Collections.
This can be done through java.util.Collections class -
http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html
Mark,
When you say it's not thread safe (cause I am not that used to the non-thread safe world), do you mean, if I call:
ArrayList::Add()
followed by
ArrayList::Size()
.. the size() method might not return the appropriate size of array. Meaning, does the processing of the page continue before Add() is completed processing.
Also, is this something would throw an error? Or just something that might present "corrupt" data?
To copy and paste from the javadocs on ArrayList:
----8<----
Note that this implementation is not synchronized. If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally.
----8<----
So if two people hit a bean that has an Arraylist inside it at the same time, and try to change that data at the same time, you are going to encounter some strange error.
Ugg... I wish I knew more about this stuff. There are errors that I am willing to take on and those that I am not. For intance, I am ok with one person getting a wrong SIZE() value... however, if you people add at the same time, I am NOT willing to have one item lost.
Well, that is why you have the ability to create synchronised collections.
It's all there in the Javadocs -
Again, to copy and paste:
-----8<-----------
This is typically accomplished by synchronizing on some object that naturally encapsulates the list. If no such object exists, the list should be "wrapped" using the Collections.synchronizedList method. This is best done at creation time, to prevent accidental unsynchronized access to the list:
List list = Collections.synchronizedList(new ArrayList(...));
-----8<-----------
There you go... a syncrhonised list.
Mark,
Thanks a lot for the heads up on this kind of situation and the good solution. I am a ColdFusion programmer venturing into Java, so I am a bit slow to wrap my head around it.
They are doing work on the server at the moment, so I can't test it, but I will definately try it.
I was thinking about this on the way home -
A Coldfusion array extends java.util.Vector (which implements List, and is syncronised), so I wonder if you create a java.util.Vector, will the reference trick work or not...
Figured I'd leave it to you to work out for us ;o)
Mark,
I think I tried with the Vector class and no luck. But, I will try again, because I am possibly confused. I think it had the same type casting issues that the ArrayList had.
Thanks for taking the time to think about it though. And thanks again for the thread-safe tips.
Great article, but a little long. In summary, "To pass arrays by reference in Coldfusion, don't use Coldfusion arrays" ;)
Thanks for your tutorials!
Hmm... in ColdFusion 8.01, I get this error when I try to do an ArrayLen() on it:
Error Messages: Object of type class coldfusion.runtime.java.JavaProxy cannot be used as an array.
Any ideas?
@Josh,
No sure, sorry.
ArraySort also reorders the results of these java obj's









