10
MAR2006
Java a Bit on the Wordy Side
I was helping a friend of mine with a Java problem yesterday and couldn't help but notice this totally normal (for Java) file in his project:
import java.io.Serializable;
public class Contact implements Serializable
{
private String firstName;
private String lastName;
private String email;
private String phone;
public Contact()
{
this("", "", "", ""); // call four-argument constructor
} // end no-argument Contact constructor
// initialize a record
public Contact(String first, String last, String eml, String phn)
{
setFirstName(first);
setLastName(last);
setEmail(eml);
setPhone(phn);
} // end four-argument Contact constructor
// set first name
public void setFirstName(String first)
{
firstName = first;
} // end method setFirstName
// get first name
public String getFirstName()
{
return firstName;
} // end method getFirstName
// set last name
public void setLastName(String last)
{
lastName = last;
} // end method setLastName
// get last name
public String getLastName()
{
return lastName;
} // end method getLastName
// set email address
public void setEmail(String eml)
{
email = eml;
} // end method setEmail
// get email address
public String getEmail()
{
return email;
} // end method getEmail
// set phone number
public void setPhone(String phn)
{
phone = phn;
} // end method setPhone
// get phone
public String getPhone()
{
return phone;
} // end method getPhone
} // end class Contacts
Of course, I just had to rewrite that in Ruby to compare:
Contact = Struct.new(:first_name, :last_name, :email, :phone)
I'm not joking there, they are truly equivalent. The Ruby version has the private instance data, setters, and getters:
>> Contact = Struct.new(:first_name, :last_name, :email, :phone)
=> Contact
>> james = Contact.new("James", "Gray",
"james@grayproductions.net", "(405) 285-0536")
=> #<struct Contact first_name="James", last_name="Gray",
email="james@grayproductions.net", phone="(405) 285-0536">
>> james.last_name
=> "Gray"
>> james.phone
=> "(405) 285-0536"
>> james.phone = "405-285-0536"
=> "405-285-0536"
>> james.phone
=> "405-285-0536"
In fact, you could argue that the Ruby version is superior. Watch this:
>> james.each_pair do |var, val|
?> puts "#{var.to_s.capitalize}: #{val}"
>> end
First_name: James
Last_name: Gray
Email: james@grayproductions.net
Phone: 405-285-0536
=> #<struct Contact first_name="James", last_name="Gray",
email="james@grayproductions.net", phone="405-285-0536">
I can even reopen the class, if I need to add new methods:
>> class Contact
>> def full_name
>> "#{first_name} #{last_name}"
>> end
>> end
=> nil
>> james.full_name
=> "James Gray"
I now know why Java guys fear losing all their IDE goodies. I wouldn't want to type all of that code either!
Comments (12)
-
Tim Morgan March 17th, 2006 Reply Link
This is one example that really makes me happy to not have a job in the Java world.
Struct
is pretty darn handy, and being able to open the class back up and add more methods, properties, etc. is a real turn-on (can't do that in Python :-).-
You can't do that in python
class Parrot: pass p = Parrot() p.spam() >>> AttributeError: Parrot instance has no attribute 'spam' def spam(self): return "Spam comes from %s" % self Parrot.spam = spam p.spam() >>> Spam comes from <__main__.Parrot instance at 0xb7b2766c>
:-)
-
-
yeah, but your ruby version does not have any comments. i think in 2-3 years time, if you returned to ruby code listing, you would have no idea what that was about and why
-
wx: I can't think of a very meaningful comment to adorn the utterly straight-forward one liner. ;)
-
-
Can you also overwrite the setters to do meaningful things like validation and other checks?
Could we have
james.first_name = ""
check that the name is not empty?
-
Sure, setters are just methods:
def first_name=(value) if value.length != "" self[:first_name] = value end end
-
Thanks, so you can change the way class members are accessed without the outside world having to change.
-
-
-
Honestly, the code is about equally readable. One is just more verbose.
That said, you can point to a shortcoming in Java because of a design bug that appears to be there.
The
Contact()
constructor appears to exist in the form it does to ensure that the fields inContact
are nevernull
(I say let them benull
, but sometimes people want to go another way). The problem is that neither the setters nor theContact(String first, String last, String eml, String phn)
constructor enforce this logic. So you can very much end up withnull
fields.Either the
Contact()
constructor should be changed to construct the object withnull
s, or the setters and the other constructor need to be changed. -
How a reasonable Java programmer would write the class. Still verbose, yet with retarded comments removed and using the Sun preferred whitespace standard, quite clear.
import java.io.Serializable; public class Contact implements Serializable { private String firstName; private String lastName; private String email; private String phone; public Contact(){ } public Contact( String first, String last, String eml, String phn ) { firstName = first; lastName = last; email = eml; phone = phn; } public void setFirstName( String first ) { firstName = first; } public String getFirstName() { return firstName; } public void setLastName( String last ) { lastName = last; } public String getLastName() { return lastName; } public void setEmail( String eml ) { email = eml; } public String getEmail(){ return email; } public void setPhone( String phn ){ phone = phn; } public String getPhone(){ return phone; } }
The real crime is the JavaBean standard which requires all those getters and setters. The above class in functionally equivalent to
public class Contact implements Serializable { public String firstName; public String lastName; public String email; public String phone; public Contact(){ } public Contact( String first, String last, String eml, String phn ) { firdtName = first; lastName = last; email = eml; phone = phn; } }
and unless you have a need for JavaBeanieness, that's how you should code it. Don't get me wrong—I love the Ruby, but the code posted was a bit of a straw man.
-
Jim's admittedly much cleaner rewrite has a serious drawback: the members are now accessed as instance variables rather than via accessor methods. The syntax and byte code for the two are different. That means that you could never compatibly introduce a method by that name to do something more complex than simply set the variable. In general I think folks will want to write accessors to reserve the right to introduce other behavior.
-
AmigoNico, you are wrong.
James code is better than Java's.
Albeit it looks like he is accessing attributes, he is not.
He is actually accessing getter/setter methods. However, unlike other languages (Python, Java, C++, etc) that force you to use a different syntax for getter/setters, Ruby does not.In Ruby there is NO distinction between getter/setter functions and attributes from a syntax pov (It is the ONLY language I know that is smart enough to do that). It is one of the reasons I fell in love with the language.
As a matter of fact, Ruby does not allow attributes to be public (unlike Python), keeping the class encapsulation.
A simple example:
class A def initialize @x = 0 end end a = A.new puts a.x # NoMethodError: undefined method `x' # for #<A:0x2b484690e7f0 @x=10> from (irb):10 ## Attribute x is private. ## Create a r/w accessor for it. class A attr_accessor :x end puts a.x 0 # # Now, change the reader to calculate # something # class A def x @x - 20 end end puts a.x -20 a.x = 5 puts a.x -15
-
-
-
I'm sure you could write a class in Java to behave the same way as a Ruby
Struct
. However, even if you did manage to get the Java down to one line through subclassing, Java is still full of punctuation that Ruby is able to drop (which makes it more readable to me). The other strength of Ruby in my mind is the ease and integrated use of metaprogramming.