ColdFusion is built on top of Java. This is huge part of why ColdFusion is such a sexy programming language. Because of this relationship, all of the ColdFusion data types (String, Query, Struct, Array, etc.) are really just custom Java classes that are built on top of things like strings, record sets, hash tables, collections, etc.. While this is an undocumented feature, this structure gives our beloved ColdFusion objects dot-style invocable methods that hook directly into the underlying Java objects.
For example, ColdFusion strings can be used in such a way:
<cfset strValue = "This is my ColdFusion string value." /> <cfset strValue = strValue.Trim() /> <cfset strValue = strValue.ReplaceAll( ... ) /> <cfset strValue = strValue.ReplaceFirst( ... ) /> <cfset arrParts = strValue.Split( ... ) />
Now, this makes way for some hugely powerful functionality (such as the use of Java's regular expressions which are much more powerful that ColdFusion's regular expressions), but again, I stress that this is totally undocumented.
At CFUNITED-07, I attended the final FAQ session and asked the speakers if Adobe was planning to document these beautiful, underlying Java methods that can be called directly on the ColdFusion objects. Ray Camden stepped up here and pretty much smacked my inquiry down. He did not do this in a negative way (Ray's the man, we all know that), he was merely pointing out the fact that if you use undocumented features and then ColdFusion's implementation changes, you might screw over your clients.
I can respect this answer and I went on my way, a bit disheartened with my tail between my legs.
But then, I had an epiphany! Not only can you access these underlying Java methods on the String object, ColdFusion forces you to! It wants you to! It pulls you close against her and whispers softly, "Go ahead, play with my underlying Java methods".
Here's what I realized; let's say you wanted to be good and not invoke the underlying Java methods of the ColdFusion string. To get around this, you could use ColdFusion's Create Object() method to create a real Java String object and initialize it with a ColdFusion string. This would give you access to all the Java String methods:
<!--- Since we don't want to use the Java methods on the string, let's create an actual Java string and then use the methods on that. ---> <cfset objString = CreateObject( "java", "java.lang.String" ).Init( JavaCast( "string", "I am a ColdFusion string in Java" ) ) /> <!--- At this point, objString is now a Java String and we can use it's object methods to manipulate the inner value. ---> <cfset objString = objString.ReplaceFirst( JavaCast( "string", "ColdFusion" ), JavaCast( "string", "Sexy ColdfFusion" ) ) /> <!--- Output the new string value. Convert the Java string to a ColdFusion string using the ToString() method. ---> #ToString( objString )#
As you can see, we are creating a real Java string object so that we can use the .Replace First() method. We are then converting that Java String object to a ColdFusion string and outputting the value. Running the above code, we get the following output:
I am a Sexy ColdFusion string in Java
Ok, now here's the twist - when you create a Java String object using Create Object(), ColdFusion doesn't allow you to keep the Java object. Instead, it takes that object and immediately converts it into a ColdFusion string object (ColdFusion strings really are Java strings, so there is no conversion, but I am trying to make a point). To prove this, let's create two Java objects using Create Object() and then test what kind of values they are:
<!--- Create one Java String object that holds a Java String instance with explicit value. ---> <cfset objJavaString = CreateObject( "java", "java.lang.String" ).Init( JavaCast( "string", "Hello" ) ) /> <!--- Create ont Java StringBuffer object that is empty. ---> <cfset objJavaBuffer = CreateObject( "java", "java.lang.StringBuffer" ).Init() /> <!--- Check to see if these JAVA OBJECT are considered simple values or complext objects. They are both created in the same way and should act accordingly. ---> IsSimpleValue: #IsSimpleValue( objJavaString )#<br /> IsObject: #IsObject( objJavaString )#<br /> <br /> IsSimpleValue: #IsSimpleValue( objJavaBuffer )#<br /> IsObject: #IsObject( objJavaBuffer )#<br />
Here, we are creating two Java Objects, one of type String and one of type StringBuffer. Running the above code we get the following output:
As you can see, ColdFusion is telling us that the Java String is NOT an object, but rather a ColdFusion simple value. Furthermore, it is telling us that the Java StringBuffer IS an object. Both of these were created in the same way, so why do they act differently? Simple - ColdFusion will not allow you to keep a Java String object - it will take that object and immediately convert it into a ColdFusion string (again, I'm trying to make a point).
So what does this all mean? Basically, it means that ColdFusion is forcing you to use ColdFusion strings even when accessing the underlying Java methods. While this cannot necessarily be extended to include all ColdFusion data types, I think this makes it very clear that ColdFusion wants you access the underlying Java String methods directly on ColdFusion string values; it's forcing you to.
personally i think with cf9 adobe should finally recognize and embrace the underline java methods.
it would be great to use them instead of all of the outside functions.
why do we need a trim function when we have the underline java method:
trim(mystring) <- ugly as hell!
mystring.trim() <- much nicer!
Hopefully adobe does this soon.
BTW, would know of a site that has a reference to all the underline java methods for the each coldfusion type?
he was merely pointing out the fact that if you use undocumented features and then ColdFusion's implementation changes, you might screw over your clients.
Uh, is CF going to be written in anything other than Java anytime soon? :| Not sure I follow with this logic. I understand that it is undocumented (yet, people still use it) and furthermore, Adobe has been really really really good about trying to keep stuff backwards compatible even some of the undocumented features (which, sometimes eventually become documented).
I wrote a method, GetJavaClassMethods(), which expose this information:
That will show you the Java methods as well as the ColdFusion-specific ones. Not all of them are available (I think some are protected methods???). Other than that, I just check the Java 2 documentation:
Is CF going to be written in anything else soon? I doubt it, but I guess it's a different kind of gamble when you are dealing with Joe Shmo's dog groomers as opposed to JP Morgan Change bank transaction system... when is "not likely" to change, not likely enough??
I love using the underlying java methods on occasion as they are very useful and often times do things that the default ColdFusion methods don't do.
I try not to use them all over the place in place of methods that cf already has however, partly for compatibility and partly because it keeps people not so familiar with Java from having to keep the Java docs and CF docs open at all times hehe.
As for CF being written in another language, I wouldn't make any bets. While Adobe hasn't done it, BlueDragon has, so know your client and their needs. Using a bunch of Java methods for the heck of it is probably not the greatest reason no matter how unlikely the change. The other side is that ColdFusion could in a future version wrap Strings with a different Java object implementation that doesn't implement all methods of the java.lang.String class. Again, pretty rare, but reason to not get overzealous.
Thanks for another article Ben. You do a great job of taking a small topic and really exploring it for those of all experience levels. Rock On.
Always glad to explore things from new angles. Even if ColdFusion were to change the implementation of the String object, that would be a backwards compatibility issue, not a programming choice issue. Basically ColdFusion is saying that Java Strings and ColdFusoin strings are interchangeable and that is a very subtle but also very powerful contract to make.
I agree, and we can figure this out just by looking at the ColdFusion documentation.
The ColdFusion language defines a primitive immutable string type. The string type is passed by value. The string type is considered a "simple value".
The ColdFusion language defines a function createObject(java) that provides a way to create a Java object (either Class or instance) and invoke methods on it using dot notation.
The documentation for createObject(java) states:
"if strings are received as return values, ColdFusion does no conversion."
Now we can see that that strings are represented as java.lang.String in the CF engine (and obviously in regular Java code). The documentation also states that a Java object is the return result of createObject(java) and that you may invoke methods on it using dot notation.
Therefore using the Java methods on CF strings is not bad practice because they are documented as being java.lang.String's, and the methods of java.lang.String are documented:
The main issue I see with relying on this behavior has nothing to do with Adobe changing the CF engine, but rather with moving your code between servers. Now whether you value the capacity to move from Java to .NET (BlueDragon) or some other hypothetical future CF engine is entirely your own decision, but if you're only taking the the Adobe CF engine into account there's no reason I can see to avoid using these methods.
Awesome detective work! I don't think I have actually looked that thoroughly into the CreateObject() documentation before. This is good stuff to know.
As far as portability from system to system, that is a whole other discussion. I think that is something that has to be decided on a project by project basis. Personally, I don't mind committing to a given language, but again a lot can go into that discussion.
Actually, I tried in CF7 to use the java .indexOf() & .charAt() to implement the Base32 algorithm. However, it was not stable enough and I had to write my own charAt() using mid() and indexOf() using findNoCase().
I couldn't remember the test case that failed. I believe it was charAt() which failed on a string more than 1 char long... forgot.
Although I know calling java method on a string can be done, I was not sure if it is safe to call the underlying java method. Since this is supposed to be an undocumented feature... What if one day in CF11 that it doens't build on top of JAVA anymore?
.. just a thought.
Things might change in CF11. But think about it this way. Right now, let's say you have a function that can accept either a Java String object or a ColdFusion component instance. Then, to determine which one you have, you might have something like this:
<cfif IsSimpleValue( ARGUMENTS.Target )>
... do some conditional code ...
Right now, that would fire TRUE for a passed in Java String object and false for a CFC. If CF11 changed the way they implemented the Java String, then this code would potentially no longer work.
Now, I am not saying that is a bad thing. All I am saying is that you can't use forward-compatibility as a reason to not use something that is FORCED on you by the language. They could change anything they want. That's what happens from version to version. In this version, they are forcing you to use the underlying java methods on a string.
First of all, many thanks for a very interesting article, Ben.
There is one issue I found when I tried running your examples on BlueDragon.NET and it's about the case of the Java methods.
When I used your code:
<cfset objString=CreateObject("java", "java.lang.String").Init(JavaCast("string", "I am a ColdFusion string in Java") )/>
I got a "Method Init could not be found. Check you have correct method name, the method name casing matches that of the Java class and you've provided the correct number of arguments" runtime error. And after I've changed the "Init" method to lowercase all started working fine.
That's odd. I just tried running your exact code and it works fine. What version of ColdFusion are you running?
I haven't tested it on a ColdFusion server, it was run on a Windows box with BlueDragon.NET 7.0 instead. This server uses .net as an underlying platform that, to my best knowledge, is bridged with Java via a technology called J#. I wouldn't be surprised if the case sensitivity issue was caused by this element of the game.
Also, I am not sure what differences there are between ColdFusion and Blue Dragon. That might be where the issue lies.