tags:

views:

5295

answers:

16

I'm a C++ guy learning Java. I'm reading Effective Java and something confused me. It says never to write code like this:

String s = new String("silly");

Because it creates unnecessary String objects. But instead it should be written like this:

String s = "No longer silly";

Ok fine so far...However, given this class:

public final class CaseInsensitiveString{
    private String s;
    public CaseInsensitiveString(String s){
     if (s == null) {
      throw new NullPointerException();
        }
     this.s = s;
    }
    :
    :
}

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
  1. Why is the first statement ok? Shouldn't it be

    CaseInsensitiveString cis = "Polish";

  2. How do i make CaseInsensitiveString behave like String so the above statement is ok (with and w/out extending String)? What is it about String that makes it ok to just be able to pass it a literal like that? From my understanding there is no "copy constructor" concept in Java right?

+6  A: 

Strings are special in Java - they're immutable, and string constants are automatically turned into String objects.

There's no way for your SomeStringClass cis = "value" example to apply to any other class.

Nor can you extend String, because it's declared as final, meaning no sub-classing is allowed.

Alnitak
+5  A: 

You can't. Things in double-quotes in Java are specially recognised by the compiler as Strings, and unfortunately you can't override this (or extend java.lang.String - it's declared final).

Dan Vinton
final is a red herring here. Even if String wasn't final, extending String wouldn't help him in this case.
Darron
I think you mean fortunately you can't override this.:)
Craig P. Motlin
@Motlin: Ha! you may well be right. I think I read somewhere that Java was designed to stop anyone ever doing anything stupid by deliberately excluding anything interesting like this...
Dan Vinton
+50  A: 

String is a special built-in class of the language. It is for the String class only in which you should avoid saying

String s = new String("Polish");

Because the literal "Polish" is already of type String, and you're creating an extra unnecessary object. For any other class, saying

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

is the correct (and only, in this case) thing to do.

Adam Rosenfield
a second point is that the compiler can to fancy internig/propagating-stuff with string literals which are not necessarily possible with such a weird functioncall like "String(literal)"
Tetha
+1  A: 

CaseInsensitiveString and String are different objects. You can't do:

CaseInsensitiveString cis = "Polish";

because "Polish" is a String, not a CaseInsensitiveString. If String extended CaseInsensitiveString String then you'd be OK, but obviously it doesn't.

And don't worry about the construction here, you won't be making unecessary objects. If you look at the code of the constructor, all it's doing is storing a reference to the string you passed in. Nothing extra is being created.

In the String s = new String("foobar") case it's doing something different. You are first creating the literal string "foobar", then creating a copy of it by constructing a new string out of it. There's no need to create that copy.

Herms
Even if you extended String this wouldn't work. You'd need String to extend CaseInsensitiveString.
Darron
Ah, right. Guess I needed more coffee
Herms
in either case its impossible, either because string is built in or because its declared final
luke
A: 

In Java the syntax "text" creates an instance of class java.lang.String. The assignment:

String foo = "text";

is a simple assignment, with no copy constructor necessary.

MyString bar = "text";

Is illegal whatever you do because the MyString class isn't either java.lang.String or a superclass of java.lang.String.

Darron
A: 

First, you can't make a class that extends from String, because String is a final class. And java manage Strings differently from other classes so only with String you can do

String s = "Polish";

But whit your class you have to invoke the constructor. So, that code is fine.

unkiwii
+19  A: 

I believe the main benefit of using the literal form (ie, "foo" rather than new String("foo")) is that all String literals are 'interned' by the VM. In other words it is added to a pool such that any other code that creates the same string will use the pooled String rather than creating a new instance.

To illustrate, the following code will print true for the first line, but false for the second:

System.out.println("foo" == "foo");
System.out.println(new String("bar") == new String("bar"));
Leigh
Similarly, that's why FindBugs tells you to replace "new Integer(N)" with "Integer.valueOf(N)" - because of that interning.
Paul Tomblin
You should also add "foo" == new String("foo").intern()
James Schek
A correction: String literals are made to point to the same reference by the compiler, not the VM. The VM may intern String objects at runtime, so the second line may return true or false!
Craig P. Motlin
@Motlin: I'm not sure that's correct. The javadoc for the String class mandates that "All literal strings and string-valued constant expressions are interned". So we can rely on literals being interned, meaning that "foo" == "foo" should always return true.
Leigh
@Leigh Yes, literals are interned, but by the compiler and not the VM. What Motlin is getting at is that the VM may additionally intern strings, thus, whether or not new String("bar") == new String("bar") --> false is implementation Dependant.
Aaron Maenpaa
+2  A: 

In your first example, you are creating a String, "silly" and then passing it as a parameter to another String's copy constructor, which makes a second String which is identical to the first. Since Java Strings are immutable (something that frequently stings people who are used to C strings), this is a needless waste of resources. You should instead use the second example because it skips several needless steps.

However, the String literal is not a CaseInsensitiveString so there you cannot do what you want in your last example. Furthermore, there is no way to overload a casting operator like you can in C++ so there is literally no way to do what you want. You must instead pass it in as a parameter to your class's constructor. Of course, I'd probably just use String.toLowerCase() and be done with it.

Also, your CaseInsensitiveString should implement the CharSequence interface as well as probably the Serializable and Comparable interfaces. Of course, if you implement Comparable, you should override equals() and hashCode() as well.

James
+10  A: 

Strings are treated a bit specially in java, they're immutable so it's safe for them to be handled by reference counting.

If you write

String s = "Polish";
String t = "Polish";

then s and t actually refer to the same object, and s==t will return true, for "==" for objects read "is the same object" (or can, anyway, I"m not sure if this is part of the actual language spec or simply a detail of the compiler implementation-so maybe it's not safe to rely on this) .

If you write

String s = new String("Polish");
String t = new String("Polish");

then s!=t (because you've explicitly created a new string) although s.equals(t) will return true (because string adds this behavior to equals).

The thing you want to write,

CaseInsensitiveString cis = "Polish";

can't work because you're thinking that the quotations are some sort of short-circuit constructor for your object, when in fact this only works for plain old java.lang.Strings.

Steve B.
A: 

In most versions of the JDK the two versions will be the same:

String s = new String("silly");

String s = "No longer silly";

Because strings are immutable the compiler maintains a list of string constants and if you try to make a new one will first check to see if the string is already defined. If it is then a reference to the existing immutable string is returned.

To clarify - when you say "String s = " you are defining a new variable which takes up space on the stack - then whether you say "No longer silly" or new String("silly") exactly the same thing happens - a new constant string is compiled into your application and the reference points to that.

I dont see the distinction here. However for your own class, which is not immutable, this behaviour is irrelevant and you must call your constructor.

UPDATE: I was wrong! Based on a down vote and comment attached I tested this and realise that my understanding is wrong - new String("Silly") does indeed create a new string rather than reuse the existing one. I am unclear why this would be (what is the benefit?) but code speaks louder than words!

Ewan Makepeace
This is incorrect -- see Leigh's answer.
titaniumdecoy
A: 

I would just add that Java has Copy constructors...

Well, that's an ordinary constructor with an object of same type as argument.

PhiLho
It's a design pattern, not a language construct. With Java there are very few uses where a copy constructor would be interesting since everything is always "by reference", and each object just has one copy. In fact, making copies would really cause a LOT of problems.
Bill K
+2  A: 

Java strings are interesting. It looks like the responses have covered some of the interesting points. Here are my two cents.

strings are immutable (you can never change them)

string x = "x";

x = "Y";

  • The first line will create a variable x which will contain the string value "x". The JVM will look in its pool of string values and see if "x" exists, if it does, it will point the variable x to it, if it does not exist, it will create it and then do the assignment
  • The second line will remove the reference to "x" and see if "Y" exists in the pool of string values. If it does exist, it will assign it, if it does not, it will create it first then assignment. As the string values are used or not, the memory space in the pool of string values will be reclaimed.

string comparisons are contingent on what you are comparing

string a1 = new string("A");

string a2 = new string("A");

  • a1 does not equal a2
  • a1 and a2 are object references
  • When string is explicitly declared, new instances are created and their references will not be the same.

I think you're on the wrong path with trying to use the caseinsensitive class. Leave the strings alone. What you really care about is how you display or compare the values. Use another class to format the string or to make comparisons.

i.e. TextUtility.compare(string 1, string 2) TextUtility.compareIgnoreCase(string 1, string 2) TextUtility.camelHump(string 1)

Since you are making up the class, you can make the compares do what you want - compare the text values.

mson
+2  A: 

- How do i make CaseInsensitiveString behave like String so the above statement is ok (with and w/out extending String)? What is it about String that makes it ok to just be able to pass it a literal like that? From my understanding there is no "copy constructor" concept in Java right?

Enough has been said from the first point. "Polish" is an string literal and cannot be assigned to the CaseInsentiviveString class.

Now about the second point

Although you can't create new literals, you can follow the first item of that book for a "similar" approach so the following statements are true:

    // Lets test the insensitiveness
    CaseInsensitiveString cis5 = CaseInsensitiveString.valueOf("sOmEtHiNg");
    CaseInsensitiveString cis6 = CaseInsensitiveString.valueOf("SoMeThInG");

    assert cis5 == cis6;
    assert cis5.equals(cis6);

Here's the code.

C:\oreyes\samples\java\insensitive>type CaseInsensitiveString.java
import java.util.Map;
import java.util.HashMap;

public final class CaseInsensitiveString  {


    private static final Map<String,CaseInsensitiveString> innerPool 
                                = new HashMap<String,CaseInsensitiveString>();

    private final String s;


    // Effective Java Item 1: Consider providing static factory methods instead of constructors
    public static CaseInsensitiveString valueOf( String s ) {

        if ( s == null ) {
            return null;
        }
        String value = s.toLowerCase();

        if ( !CaseInsensitiveString.innerPool.containsKey( value ) ) {
             CaseInsensitiveString.innerPool.put( value , new CaseInsensitiveString( value ) );
         }

         return CaseInsensitiveString.innerPool.get( value );   
    }

    // Class constructor: This creates a new instance each time it is invoked.
    public CaseInsensitiveString(String s){
        if (s == null) {
            throw new NullPointerException();
         }         
         this.s = s.toLowerCase();
    }

    public boolean equals( Object other ) {
         if ( other instanceof CaseInsensitiveString ) {
              CaseInsensitiveString otherInstance = ( CaseInsensitiveString ) other;
             return this.s.equals( otherInstance.s );
         }

         return false;
    }


    public int hashCode(){
         return this.s.hashCode();
    }

// Test the class using the "assert" keyword

    public static void main( String [] args ) {

        // Creating two different objects as in new String("Polish") == new String("Polish") is false
        CaseInsensitiveString cis1 = new CaseInsensitiveString("Polish");
        CaseInsensitiveString cis2 = new CaseInsensitiveString("Polish");

        // references cis1 and cis2 points to differents objects.
        // so the following is true
        assert cis1 !=  cis2;      // Yes they're different
        assert cis1.equals(cis2);  // Yes they're equals thanks to the equals method

        // Now let's try the valueOf idiom
        CaseInsensitiveString cis3 = CaseInsensitiveString.valueOf("Polish");
        CaseInsensitiveString cis4 = CaseInsensitiveString.valueOf("Polish");

        // References cis3 and cis4 points to same  object.
        // so the following is true
        assert cis3 == cis4;      // Yes they point to the same object
        assert cis3.equals(cis4); // and still equals.

        // Lets test the insensitiveness
        CaseInsensitiveString cis5 = CaseInsensitiveString.valueOf("sOmEtHiNg");
        CaseInsensitiveString cis6 = CaseInsensitiveString.valueOf("SoMeThInG");

        assert cis5 == cis6;
        assert cis5.equals(cis6);

        // Futhermore
        CaseInsensitiveString cis7 = CaseInsensitiveString.valueOf("SomethinG");
        CaseInsensitiveString cis8 = CaseInsensitiveString.valueOf("someThing");

        assert cis8 == cis5 && cis7 == cis6;
        assert cis7.equals(cis5) && cis6.equals(cis8);
    }

}

C:\oreyes\samples\java\insensitive>javac CaseInsensitiveString.java


C:\oreyes\samples\java\insensitive>java -ea CaseInsensitiveString

C:\oreyes\samples\java\insensitive>

That is, create an internal pool of CaseInsensitiveString objects, and return the corrensponding instance from there.

This way the "==" operator returns true for two objects references representing the same value.

This is useful when similar objects are used very frequently and creating cost is expensive.

The string class documentation states that the class uses an internal pool

The class is not complete, some interesting issues arises when we try to walk the contents of the object at implementing the CharSequence interface, but this code is good enough to show how that item in the Book could be applied.

It is important to notice that by using the internalPool object, the references are not released and thus not garbage collectible, and that may become an issue if a lot of objects are created.

It works for the String class because it is used intensively and the pool is constituted of "interned" object only.

It works well for the Boolean class too, because there are only two possible values.

And finally that's also the reason why valueOf(int) in class Integer is limited to -128 to 127 int values.

OscarRyz
A: 

String is one of the special classes in which you can create them without the new Sring part

it's the same as

int x = y;

or

char c;

Wonder
+1  A: 

Just because you have the word String in your class, does not mean you get all the special features of the built-in String class.

javaguy
A: 

how string class makes two objects? plz tell me on my email id "[email protected]"

lokesh Gupta