所处章节:第二章 Programming Ruby
页数范围:38~71
Java/Ruby语法特性对比:
Java divides the world into primitive types and objects. The primitive
types represent numeric values of various ranges and precision:
int i;
primitive types can use infix math, for example:
1+1;
To represent arbitrarily large integers, Java uses the BigInteger class:
BigInteger twobil = new BigInteger("2000000000");
BigInteger cannot use infix mathematical notation you have
to say:
a.multiply(b)
Ruby transparently uses
the appropriate type as needed, Notice that x smoothly shifts from Fixnum to Bignum as necessary:
irb(main):004:0> x = 10**9 => 1000000000 irb(main):005:0> x.class => Fixnum irb(main):006:0> x *= 100 => 100000000000 irb(main):007:0> x.class => Bignum irb(main):018:0> 1.0.finite? => true irb(main):019:0> (1.0/0.0).finite? => false
"?" by convention it is used for methods that return a
boolean
print(String.format("Hello, %s", name.toUpperCase()));
irb(main):005:0> "Hello, #{name.upcase}" => "Hello, READER"
In Java, you can use backslash escapes to represent characters:
print(String.format("Hello, \"%s\"\nWelcome to Java", name));
Ruby also uses backslash escapes:
irb(main):008:0> puts "Hello, \"#{name}\"\nWelcome to Ruby" Hello, "Reader" Welcome to Ruby
with %Q you can get rid of all these backslash escapes:
irb(main):011:0> puts %Q{"One", "Two", and "Three" are strings"} "One", "Two", and "Three" are strings"
assertEquals('H', "Hello".charAt(0));
irb(main):015:0> ?A => 65 irb(main):019:0> ?H == "Hello".slice(0) => true irb(main):022:0> ?o == "Hello"[-1] => true irb(main):025:0> "Hello"[1,4] => "ello"
To turn off string interpolation,use a single-quoted string instead of a double-quoted one:
irb(main):028:0> "Hello, #{name.upcase}" => "Hello, READER" irb(main):029:0> 'Hello, #{name.upcase}' => "Hello, \#{name.upcase}"
Java:
print("one\ntwo\nthree");
Ruby provides an explicit syntax for multiline strings called a here
document, or heredoc.
irb(main):035:0> puts <
Java:
public static String bleep(String input) { return input.replaceAll("\\b\\w{4}\\b", "(bleep)"); }
Ruby use regular expressions, delimiting them with //:
irb(main):041:0> 'Are four letter words mean?'.gsub(/\b\w{4}\b/, "(bleep)") => "Are (bleep) letter words (bleep)?"
print("HELLO".toLowerCase());
irb(main):047:0> "HELLO".downcase() => "hello" irb(main):048:0> "HELLO".downcase => "hello"
print(Math.cos(0));
irb(main):051:0> Math.cos(0) => 1.0 irb(main):051:0> Math.cos 0 => 1.0
In Java, objects are type-safe. Objects know what they are capable of, type-safe and you cannot ask them to perform methods they do not have:
print("hello".cos(0));
Ruby objects are also type-safe:
irb(main):057:0> "hello".cos 0 NoMethodError: undefined method ‘cos' for "hello":String from (irb):57
Type Safety Does Not Ensure Correctness!!!
public class PrintArgs { public static void main(String[] args) { for (int n=0; n
ARGV.each {|x| puts x} or ARGV.each do |x| puts x end $ ruby samples/print_args.rb one two three one two three irb(main):009:0> ['C', 'Java', 'Ruby'].each {|lang| puts "#{lang} is fun!"} C is fun! Java is fun! Ruby is fun! => ["C", "Java", "Ruby"] irb(main):002:0> ['C', 'Java', 'Ruby'] == %w{C Java Ruby} => true irb(main):002:0> [1,2] + [3] => [1, 2, 3] irb(main):003:0> [1,2,3] * 2 => [1, 2, 3, 1, 2, 3] irb(main):004:0> [1,2,1] - [2] => [1, 1] irb(main):005:0> [1,2] / 2 NoMethodError: undefined method ‘/' for [1, 2]:Array from (irb):5 irb(main):006:0> skills = ['C', 'Java'] => ["C", "Java"] irb(main):007:0> skills.push 'Ruby' => ["C", "Java", "Ruby"] irb(main):008:0> skills.pop => "Ruby"
import java.util.*; public class PrintEnv { public static void main(String[] args) { Map map = System.getenv(); for (Iterator it = map.entrySet().iterator(); it.hasNext();) { Map.Entry e = (Map.Entry) it.next(); System.out.println(String.format("%s: %s", e.getKey(), e.getValue())); } } }
ENV.each {|k,v| puts "#{k}: #{v}"} irb(main):037:0> dict = {:do => "a deer", :re => "a drop of golden sun"} => {:do=>"a deer", :re=>"a drop of golden sun"} irb(main):013:0> dict.fetch(:do) => "a deer" irb(main):014:0> dict.store(:so, "a needle pulling thread") => "a needle pulling thread" irb(main):015:0> dict[:so] => "a needle pulling thread" irb(main):016:0> dict[:dos] = "a beer" => "a beer"
import java.util.Map; public class ForEach { public static void main(String[] args) { for (String arg: args) { System.out.println(arg); } for (Map.Entry entry: System.getenv().entrySet()) { System.out.println( String.format("%s : %s", entry.getKey(), entry.getValue())); } } }
irb(main):017:0> [1,2,3,4,5].collect {|x| x**2} => [1, 4, 9, 16, 25] irb(main):021:0> [1,2,3,4,5].find_all {|x| x%2==0} => [2, 4]
We want to make two important points from these examples:
• Languages evolve and improve over time. Usually improvement
comes not from thin air but from ideas that have already been
explored elsewhere. Java’s For-Each syntax was inspired by other
languages that have similar features. The programmers who facilitate
this kind of cross-pollination are those who become fluent in
multiple programming languages.
• Languages evolve at many levels. Runtimes can change, language
syntax can change, and libraries can change. In Java, iteration
changes like the addition of For-Each are language changes. Similar
changes in Ruby are library changes, since each et. al. are
method calls. Library changes are easier to make than language
changes. (Many developers write libraries, few developers write
languages, and language evolution tends to be retarded by standards
bodies, backward compatibility, and so on.) Merely pointing
out that Java and Ruby enable different approaches to change is
not a value judgment. However, it may lead to value judgments
in a specific context (which you must provide). What parts of your
system need to change? On what timeline, and under whose direction?
Conversely, what parts need to stay rock-solid stable and be
guaranteed to work in the same fashion across different projects
over time?
if (n > 5) { print("big"); } else { print("little"); }
if n>5 puts "big" else puts "little" end or puts(if (n>5) "big" else "little" end)
if (o != null) { print("got o"); }
o = nil puts o ? true : false if lines > 1; puts "you have more than one line"; end puts "you have more than one line" if lines > 1 puts "you've got lines" if lines != 0 puts "you've got lines" unless lines == 0 The following program runs an input loop, shouting back everything passed via stdin: line = "" puts "Shouting back #{line.upcase}" while line=gets The ugliness here comes from using the boolean operator || to shoehorn two statements into one to conform to the requirements of the statement modifier form. irb(main):026:0> i=1; puts(i*i) || i+=1 while i<=5 1 4 9 16 25 (1..5).each {|x| puts x*x} irb(main):014:0> (1..10).max => 10 irb(main):015:0> (1...10).max => 9 irb(main):016:0> (1..10).exclude_end? => false irb(main):017:0> (1...10).exclude_end? => true ("A".."C").each {|x| puts x*5} AAAAA BBBBB CCCCC irb(main):003:0> ('A'..'I').step(2) {|x| print x} ACEGI
public static int numberGrade(char letter) { switch(letter) { case 'a': return 100; case 'b': return 90; case 'c': return 80; case 'd': return 70; case 'e': case 'f': return 0; } return 0; }
def number_grade(letter) case letter when 'A': 100 when 'B': 90 when 'C': 80 when 'D': 70 else 0 end end def letter_grade(x) case x when 90..100: 'A' when 80..90: 'B' when 70..80: 'C' when 60..70: 'D' when Integer: 'F' when /[A-F]/: x else raise "not a grade: #{x}" end end irb(main):018:0> case('John') irb(main):019:1> when('John'): 'a name' irb(main):020:1> when(String): 'a word' irb(main):021:1> end => "a name" You can also invoke the case equality operator directly; it is written as a triple equals (===): irb(main):002:0> (90..100) === 95 => true irb(main):003:0> (90..100) === 95.5 => true irb(main):004:0> (90..100) === 0 => false irb(main):005:0> Integer === 95 => true irb(main):006:0> Integer === 95.5 => false
public class Person { private String firstName; private String lastName;
class Person
That’s it. There is no need to declare instance variables (the Ruby equivalent
of fields) because they come into existence automatically when
they are used.Ruby instance variables are
implicitly private, so this designation is unneeded as well.
Defining Constructors
In Java, Notice that the constructor
arguments share the same names as the private fields: firstName and
lastName. To disambiguate, you explicitly prefix the instance variables
with this.
public Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; }
In Ruby, instance
variables must begin with a @, so there is no danger of a name collision
with method parameters.
def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end
public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getFullName() { return String.format("%s %s", firstName, lastName); }
attr_accessor :first_name, :last_name def full_name "#{first_name} #{last_name}" end Ruby also provides attr_reader and attr_writer if you want read-only or write-only properties. p.first_name = 'Justin' p.last_name = 'Gehtland' puts "Hello from #{p.first_name}"
To a Java eye, this looks like direct access to the fields first_name and
last_name, but in Ruby these are method calls. Even the punctuation (=)
is part of a method name. To make this clearer, here is a hand-coded
version of the accessors:
# don't do this--use attr_accessor! def first_name @first_name end def first_name=(new_name) @first_name = new_name end
public void marry(Person other) { String newLastName = String.format("%s-%s", getLastName(), other.getLastName()); setLastName(newLastName); other.setLastName(newLastName); }
def marry(other) other.last_name = self.last_name = "#{self.last_name}-#{other.last_name}" end If you forget to prefix a setter with self, you may create hard-to-find bugs. Java does not suffer from this ambiguity, so be careful.
Sometimes methods apply to a class as a whole, instead of to any particular
instance of a class. In Java these are called static methods:
public static String getSpecies() { return "Homo sapiens"; }
In Ruby, these methods are called class methods:
def self.species "Homo sapiens" end
public class Person { private String firstName; private String lastName; public Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getFullName() { return String.format("%s %s", firstName, lastName); } public void marry(Person other) { String newLastName = String.format("%s-%s", getLastName(), other.getLastName()); setLastName(newLastName); other.setLastName(newLastName); } public static String getSpecies() { return "Homo sapiens"; } }
class Person def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end attr_accessor :first_name, :last_name def full_name "#{first_name} #{last_name}" end def marry(other) other.last_name = self.last_name = "#{self.last_name}-#{other.last_name}" end def self.species "Homo sapiens" end end
The == operator tests for identity. Strings s1 and s2 are == because they
point to the same object. Strings3 is not== to the others. It contains the
same characters, but it is at a different location in memory.
The equals method tests equality, which Java strings define to mean
“containing the same characters.” Thus, string s3.equals the others.
public void testEquals() { String s1 = "Java rocks!"; String s2 = s1; String s3 = new String("Java rocks!"); assertTrue(s1 == s2); assertFalse(s1 == s3); assertTrue(s1.equals(s3)); }
Ruby also distinguishes between identity and equality. Each unique
object has an object_id. Two objects are identical if they have the same
object_id, and the equal? method tests for this:
irb(main):001:0> s1 = s2 = "Ruby rocks!" => "Ruby rocks!" irb(main):002:0> s1.object_id => 190400 irb(main):003:0> s2.object_id => 190400 irb(main):004:0> s2.equal? s1 => true irb(main):006:0> s3 = "Ruby rocks!" => "Ruby rocks!" irb(main):007:0> s4 = "Ruby rocks!" => "Ruby rocks!" irb(main):008:0> s3==s4 => true irb(main):009:0> s3.eql? s4 => true irb(main):010:0> s3.equal? s4 => false Java’s == tests for identity, while Ruby’s == usually tests for equality
import java.util.*; public class AccessMe { private String name; private List stuff; public AccessMe(String name) { this.name = name; stuff = new ArrayList(); } public String getName() { return name; } protected List getStuff() { return stuff; } private void clear() { name = null; stuff = null; } }
Ruby does not have any equivalent for package private but supports
public, protected, and private:
class AccessMe def initialize(name) @name = name @stuff = [] end attr_accessor :name protected attr_accessor :stuff private def clear @name = @stuff = nil end end
Although access control specifiers set a general rule for how you can
use a class, the general rule may need to bend in some circumstances.
For example, an object serializer may bypass protection modifiers to
access all of an object’s state. In Ruby, you can bypass access control
specifiers with send:
a = AccessMe.new("test") a.send :stuff=, 'some stuff' puts a.send(:stuff)
You can invoke
the access control methods more than once for the same symbol. At first
glance this may seem silly—why would a class want to have different
levels of access control at different times? One possibility is temporarily
setting methods public so that unit tests can test them:
def test_clear AccessMe.send :public, :clear, :stuff a = AccessMe.new("test") a.clear assert_nil a.stuff end
This sample uses only techniques we have covered thus far. You can
use cleaner ways to set methods public for the duration of a test. One
approach is to use the extend method, described in Section 3.8, Mixins,
on page 90.
try { methodThatOpensFile(); } catch (FileNotFoundException fnfe) { System.out.println("File not found " + fnfe); } catch (Exception e) { System.out.println("Caught " + e); }
begin File.read 'nonexistent' rescue SystemCallError => e puts 'system call failed' rescue Exception => e puts 'generic failure of some kind' else puts 'nothing failed' ensure puts 'this always executes' end begin 1/0 rescue Exception => e puts "Message " + e.message puts "Backtrace " + e.backtrace.join("\n") end