rulururu

post Basic design patterns and a book recommendation

February 23rd, 2008

Filed under: General Programming, Java — Kai @ 3:52 pm

About a year ago I got interested in the complexety of design patterns for better solving problems in software development you are circularly confronted with. When reading “Head First Design Pattern” by Eric Freeman, Elisabeth Freeman and Kathy Sierra I soon mentioned that I use the common ones of them without having recognized it before.

designpatternsI’m not as surprised about that as you might think ’cause there a several problems in software development you can just solve that way or you chose kinda “dirty” way. Even thougth the book is exhausting to read ’cause it’s not written the way most book for programmers are - it’s really abstract which means they’re almost never writing about problems that occur the development of a software product but all-time about compareable situations in real life. None the less I can without any doubt recommend it to you as a book every programmer should have read once or maybe twice.

To give you a short preview I’d like to tell you about real basic patterns you might already know very-well.

All code examples are written in Java, not because I do like Java that much, rather ’cause Java is qualified to be readed easily particularly concerning object orientated programming techniques. It doesn’t have much overhead in syntax that would distract you from the things I really want to you see. Of course patterns are used in every object orientated language they just differ form each other by the way there’re syntactical written and some programming languages have features that help to implement them easier.

  • The Factory Method
  • It’s its intention to separatly construct a complex object so that the same construction process can create different representations.

    It provides a simple decision making class which returns one of several possible subclasses of an abstract base class depending on data it is provided.
    If there’s more than one way to created an object factory methods are right choice. Especially when the class that calls a factory method delivers the required information for creating an instance.

    The factory method pattern should be used in those cases:

    * The class that calls the factory method doesn’t know exactly what instance has to be created.
    * The calling class is not pretty sure howto created the needed instance.
    * The calling class gives information to the factory method that helps/is required to created the needed instance of an object.
    * The class that provides a factory method to gives permission of its creation to its subclasses.

    Factory methods are common in toolkits and frameworks where library code needs to create objects of types which may be subclassed by applications using the framework.

    This example of a facotory method creates an instance of an ImageReader. Each time the program reads an image it needs to create a reader of the appropriate type based on some information in the file.

    public class ImageReaderFactory 
    {
        public static ImageReader getImageReader( InputStream is ) 
        {
            int imageType = figureOutImageType( is );
     
            switch( imageType ) 
            {
                case ImageReaderFactory.GIF:
                    return new GifReader( is );
                case ImageReaderFactory.JPEG:
                    return new JpegReader( is );
                // etc.
            }
        }
    }
  • The Abstract Factory Method
  • It provides an interface to create and return one of several families of related objects. It encapsulated a group of individual factories that have a common theme.

    Abstract factory methods work like classic factory methods but they’re a bit enhanced. Using them is recommented when:

    * A class should work totally independent from the creation of the instances of objects it works with.
    * A group of objects shall be created ’cause they’d be used together
    * A class library should offer a whole bunch of implementations.

    Typically you find it in GUI libraries that offer different themes for their controls.

    At first we design a abstract factory that looks very clear:

    public abstract class AbstractGUI 
    {
         public Button CreateButton()
         {
            return new Button();
         }
    }

    Afterwards two concret class get designed that extends the abstract class and its methods override the deliverd ones.

    public class XPStyle extends AbstractGUI 
    {
       @Override
       public Button CreateButton()
       {
          return new XP_Button();
       }
    }
     
    public class GnomeStyle extends AbstractGUI 
    {
      @Override
       public Button CreateButton()
       {
          return new Gnome_Button();
       }
    }

    The GUI class has a method for the name of a control:

    public class AbstractGUI 
    {
    private String name;
     
      public String getName() 
      {
        return this.name;
      }
     
      public void setName(String name) 
      {
        this.name = name;
      }
    }

    This is the most important part, that is used when a particular button is created:

    public class XP_Button extends Button
    {
      public XP_Button()
      {
        super();
        setName(”XP Button”);
      }
    }

    Eventually the great advantage of this is that you don’t have to worry about what kind of objects you’re building the factory does ease your workload.

  • The Builder Pattern
  • It can be used to clearify a complex building progress of an big object.

    This should explain how a abstract builder works:

    abstract class ButtonBuilder
    {
        protected Button button;
     
        public Button getButton()
        { 
            return button; 
        }
        public void BuildNewButton()
        { 
            button = new Button(); 
        }
     
        public abstract void buildStyle();
    }

    In this class the concret building is done:

    class MySuperButtonBuilder extends ButtonBuilder
    {
        public void buildStyle()
        { 
            button.RoundBorders(1);
            button.FancyLook(1);
        }
    }

    It separates the construction of a complex object from its representation, so that several different representations can be created depending on the needs of the program.

  • The Prototype Pattern
  • The prototype means making a clone. This implies cloning of an object to avoid creation. If the cost of creating a new object is large and creation is resource intensive, we clone the object. We use the interface Cloneable and call its method clone() to clone the object.

    public class Data implements Cloneable 
    {
        public Object clone()
        {
            try{
                return this.getClass().newInstance();
            }
            catch(InstantiationException e)
            {
               e.printStackTrace();
               return null;
            }
        }
    }
     
    public class DataCreator
    {
       private Data d;
     
         public DataCreator(Data d)
         { 
             this.d = d; 
         } 
         public Data GetData() 
         { 
           return (Data)data.clone(); 
         } 
         public Object clone()
         { } 
    }
     
    public class SecretData extends Data 
    {
    //concrete prototypes to clone
    }

    Working with that can be done in that way:

    Data dataprotoype = new SecretData(); 
    Data mydata = new SecretData(dataprotoype); 
     
    for(int i=0; i<100; i++) 
       tmpData = mydata.GetData();

    It starts with an initialized and instantiated class and copies or clones it to make new instances rather than creating new instances.

  • The Singleton Pattern
  • Is the one that provides a class of which there can be no more than instance, and provides a single global point of access to that instance.
    Sometimes it’s appropriate to have exactly one instance of a class: e.g. for window managers, print spoolers etc.
    The most common usage is a logging class that does something with alle logs and exceptions that are thrown.

    If an object is needed in the whole application a singleton is better than a real global object. But you’d also be careful when implementing lots of singletons ’cause you surly don’t want
    give up object orientation and have everything globally just for easier usage.

    A singleton should give you those possibilies:

    * Create the only existing object of a class
    * Provide a global point of access to the object (getInstance(); ).

    The first thing you’ve to do when avoiding the creation of multible objects from one class is to set the constructor to private access.
    Then you’ve provide your own method for creation of an object that checks if another object exists before creating. Easy but effective!

    private static Logger log;
     
    private Logger() 
    {
    //private constructor
    }
     
    public static Logger getInstance() 
    {
     if (Logger.log == null) 
     {
       Logger.log = new Logger();
     }
        return Logger.log;
    }

    To improve this solution we’d make it thread-save. Java provides a synchronized keyword that ensures the synchronized access. Java makes it really simple, C# needs a variable that is volatile (indicates that a field can be modified in the program by something such as the operating system, the hardware, or a concurrently executing thread.) which is check before creating a new object.

    public static synchronized Logger getInstance () 
    {
        if (Logger.log == null) {
          Logger.log = new Singleton ();
        }
        return Logger.log;
      }

Design patterns are an extensive topic you could write about for hours. My aim was just the give you a thorough insight into that issue without going to deep and making it more complex than necessary.

To have basic ability in using those patterns is also important ’cause when discussing with other programmers you don’t want to discuss the whole logic you’ll write you just want to discuss about a certail technical term and if it matches to solve the given problem.

Here you can buy Head First Design Patterns published by O’Reilly on Amazon.com

post I do have namesake that is a compiler

February 22nd, 2008

Filed under: C++, Internet — Kai @ 3:23 pm

When writing the article about performance improvments I originally plant to write a bit more about different compilers and perhaps do create kinda statistics. Finally I didn’t do it ’cause it would have been an overkill for the post.

During preparatory research about different compiler I came by KAI C++ compiler, its name amused me a lot :D

The project-page says:

The powerful features of KAI C++ will make programmers more efficient. The advanced optimizations allow programmers to take full advantage of object-oriented design and software reuse without worrying about performance, because KAI C++ makes objects almost as efficient as hand-coded C. Programmers will spend less time trying to correct performance problems, and instead deliver code that is intuitive and easy to maintain.

Afterwards I tried to collect some information about KAI:

  • According to the copyright (says 1996-1999) the compiler seems to be an older one.
  • Besides the project page and user’s guide it has a website www.kai.com that forwards to Intel website.
  • It’s based on linux standards and owns a manage.
  • It’s not very famous ’cause I wasn’t able to collect as much information as I’d liked to.
  • It’s shareware - donno what a licence costs but they promise you comprehensive and extremely responsive customer support

In conclusion I can say that I’m definitely not interested in KAI C++ compiler. I’m not pretty sure how famous, professional or handy it is, additionally it being shareware makes it totally charmless for me to test it.

post Improve C++ Compile-Time and Run-Time Performance

February 21st, 2008

Filed under: C++ — Kai @ 6:51 pm

Organizing your code as good as possible for great compile time and also, much more important, better run-time performance is still a major point of software development.

An application delivering results faster doesn’t always mean that you’ve to re-design all of the code and does not in any case depend on damn-fast libraries you use. Often things can be made better much easier. In some cases it’s not important what you’re using - even more how you’re using it.

I’d like to show you a number of techniques for improving performance in C++. Non of those “tricks” is a difficult programming trick or something really new. They’re just common things that can make your application a little bit more high-performance.

  • To achieve the fastest build times, compile without debug info and without optimization.
  • GCC parameter -g produces debugging information in the operating system’s native format. You don’t have to produce it every time building your code - it’s just necessary when debugging the project.
    -O3 is often used to generate smaller executables - if you need a small binary you’d use it but when being focused on compile time you’d better use the default -O0.

  • What libraries to use?
  • Most C++ libraries are designed for good performance over a wide range of uses. C++ standard library gives you several good algorithms with a significant number of services. Don’t write every algorithm yourself but sometimes it might happen that the standart C++ library doesn’t provide a perfect solution for your project. Before reproducing an insufficient algorithm have a look at other libraries such as BOOST Library or Apache C++ Standard Library. Especially for painting or threading issues there’s a whole bunch of libraries.

  • Include files
  • Reducing the number of included files by better organisating the project can make compilation more efficient.

    Most programmers add something like this to their headers to avoid including a header more than one time.

    #ifndef MYCLASS_H
      #define MYCLASS_H
      class MyClass 
      { 
       ... 
      };
    #endif

    If the file has not been previously read, the class is defined, otherwise, the file is essentially empty. We can avoid the unnecessary read of the file by testing the guard on the outside of the include as well.

    #ifndef MYCLASS_H
      #include "myclass.h"
    #endif

    This technique is most effective when the compiler is hitting the limits of available main memory and as a result the file cache is ineffective.

  • Usage of inline functions
  • Inline function expansion can significantly increase run time, but the cost is significantly increased compilation time. That means you’d know what has more importance to you - compile time or run time…

    In a previous post I described inlining in detail:
    Inlining - on February 10th, 2008

  • Bit-fields
  • Both C and C++ allow integer members to be stored into memory spaces smaller than the compiler would ordinarily allow.

    Convert boolean and small integer values into bit-fields, and then place these fields adjacent to each other. This technique can substantially reduce data size - this should only be a way if your applications uses too much of memory capacity.

    This can bring benefit to your application when reading external file formats - non-standard file formats for example a 9 bit integers or something like that.

    Mostly bitfields are declared in structures:

    struct A
    {
     unsigned int f1: 4;
     unsigned int f2: 4; 
    };

    It’s more common to use bit fields to store a set of boolean datatype flags compactly. Bit fields are kinda tricky way of space optimization.

  • Converter methods
  • In general you cast an objects that inherits from another into it.
    The usage of dynamic cast is very general, and consequently is more expensive than most specific needs warrant.

    car* pCar = (car*)pVehicle;
    car* cCar = dynamic_cast<car*>( pVehicle );

    You can achieve a lot of useful functionality by providing dynamic converter methods:

    car* pCar = pVehicle->to_car()
     
    class vehicle
    {
        virtual car* to_car() { return (car*)0; }
    };
    class car : vehicle 
    {
        virtual car* to_car() { return this; }
    };

    In most cases this is not just clearer it’s also faster.

  • Default operators
  • Use the default operators. If a class definition does not declare a parameterless constructor, a copy constructor, a copy assignment operator, or a destructor, the compiler will implicitly declare them.

    When the compiler builds a default operator, it knows a great deal about the work that needs to be done and can produce very good code. This code is often much faster than user-written code because the compiler can take advantage of assembly-level facilities.

    Default operators are inline functions, so do not use default operators when inline functions are inappropriate.

  • Passing reference paramters
  • Most programmers, including myself, often pass references to functions ’cause it’s said to faster than a call by value. Sounds logical because for a call be value a copy is needed which allocates storage.

    However, value parameters may be more efficient, and even when they are not directly more efficient, the compiler knows that a value parameter cannot be aliased, and so can better optimize access to the parameter.

  • Temporary objects can be avoided
  • In order to avoid lots of temporary objects which make longer compile time and also reduce performace of the application because more memory is needed you’d consider the following:

    T x = a + b;

    Produces a temporary object for the sum of a and b and the passes it into x of class T.

    T x(a); x += b;

    Above solution doesn’t need a temporary object.

  • Write member variables into local variables
  • Accessing member variables is a common operation in C++ member functions.
    Due the fact that you need to read/write members the compiler must often load member variables from memory through the this pointer.

    This pointer might not always be valid which forces the application to reload the member every time again whenever it’s needed. If you pass your member at the top of a function into a local variable it’s just accessed one time and in the rest of the function the local varible can be used.

    That way you can avoid unnecessary memory reloads.

    Another great advantage that improves performace is that the values can reside in registers, as is the case with primitive types.

  • Too many function calls
  • You often see things like that:

    if(App->MyClass->GetInstance())
    {
    App->MyClass->GetInstance()->GetAll()->Select(...);
    MyClass->GetInstance()->GetAll()->SelectTop();
    }

    You don’t have to do that. Often this is a result of copy and paste lines as everybody does lots of times.

    Nevertheless a more readable and also cached memory reducing solution is:

    MyClass* inst = App->MyClass->GetInstance();
    if(inst)
    {
    inst->GetAll()->Select(...);
    inst->GetAll()->SelectTop();
    }
  • const does not improve run performace
  • Many programmers use const reference parameters for functions with the intent to inform the compiler that the parameter is read-only. The const keyword says that the storage may not be modified through the given name.

    What it does not say is that the storage cannot be modified through some other name. Only variables directly declared as const are really constant.

    Basically it’s an ineffective way for improving run-time performance. It does, however, catch errors in the programming process.

  • Deallocating memory
  • Once unnecessary allocations are eliminated, the next most effective technique for improving performance is to deallocate memory when it is no longer needed. This is best accomplished with explicit calls to delete.

    However, applications may lose control of their memory, and a conservative garbage collector can sometimes be a critical tool. The C++ compiler provides a conservative garbage collector with the option -library=gc.

  • Loop optimizations
  • The most simple thing to remove some spare loops which can simply be written outright. Consider this:

    for(int i=0; i<4; i++) 
    array[i] = i;

    this is logically the same as

    array[0] = 0; 
    array[1] = 1, 
    array[2] = 2; 
    array[3] = 3;

    Of course it’s nonsense doing this in on or two parts of your projects. But if you have thirty or more of those parts it might give you back some performance.

    Do as less checking in a loop as possible, try just giving the filtered data objects, those that really should be passed through, to a loop.

  • Different compiler versions
  • It’s interesting to know that GCC 2.95.3 does faster compile code than version 3 compilers.
    This shouldn’t be the determining factor ’cause using the newest (or a newer) version of GCC is always recommented. A downgrade to a lower version can just be necessary because of an application getting no longer compiled by the newer one.

Delay optimization as much as possible, and don’t do it if you can avoid it. Optimizing too early or too often is not a good approach to engineering. Better to have a program that runs than a fast program that crashes. Better rethink your concept twice before starting the programming work instead of correting half of the project afterwards.

post Photoshop soon available on Linux?

February 20th, 2008

Filed under: Linux, Software — Kai @ 10:27 pm

According to a survey implemented by Novell Adobe Photoshop is the most missed software product for Linux users. I can definitely approve that ’cause I don’t often need Windows but for Photoshop CS I have to switch on my Windows PC.

Indeed Gimp is a great graphics editor but not sufficiant. Maybe it has the same features and you can do almost the same stuff with it like with Photoshop but if you’ve worked for years with Photoshop, like I did, you’re kinda familiar with its usage. I utterly would spend more time on looking for a similar function in Gimp than just using Photoshop on Windows.

Google is funding work to ensure the Windows version of Adobe Systems’ Photoshop and other Creative Suite software can run on Linux computers. For working efficiently on that project Google hired developers from Codeweavers. The very well-known open-source project Wine is produced and supported by Codeweavers.

Some time ago also Adobe itself tried to make a running Linux solution of Photoshop - but unfortunately without success. According to my opinion it’s great that specialist make sure that Photoshop will run perfectly under wine in future.

Codeweavers

In marked contrast to other people on the Internet I can’t find something bad concerning the fact Google will fund for improve the Wine/Photoshop combination. Althougth it’s no act of charity by Google - they’re just trying to invest into the right projects. From my point of view projects being open source getting supported by Google is rather good than bad.

post Frequently done wrong - don’t also do it!

February 19th, 2008

Filed under: C++, MFC — Kai @ 4:39 pm

I’d like to tell you about tiny mistakes I often mention when reading code. All of them are not dramatic but they stay in your code for years and one time something goes wrong they’re hard to discover. There are many things that can quickly be done wrong - that’s the reason why I restrict my post to C++ / MFC related mistakes.

  • A widespread misbelief is that this usage of _tprintf and CString is correct:
  • CString strWord = _T("another");
    _tprintf(_T("One string placed in %s"), strWord);

    There’s only one reason why this doesn’t report an error:
    CString has size of 4 byte and points on a TCHAR array.

    This will fail when inheriting an own string class from CString class in which we override a virtual method from CString class. You’ll get problems when CString grows up from 4 to 8 bytes because it’s now holding two pointers: One to a vtable and one to the TCHAR array. _tprintf doesn’t know what to with the object on the stack and might crash or just report nonsense.

    As you can see a small change in your project can lead to lots of errors which are difficult to reproduce.

    How to do it right? There are three solutions - it’s just a manner of taste which one you prever:

    CString strWord = _T("another");
    _tprintf(_T("One string placed in %s"), static_cast<LPCTSTR>(strWord));

    or

    _tprintf(_T("One string placed in %s"), strWord.GetString());

    or

    _tprintf(_T("One string placed in %s"), strWord.GetBuffer(0));
    strWord.ReleaseBuffer();

    First solution is a static cast to an LPCTSTR (which is simply a char*).

    Second is the usage of CString::GetString() method which returns a PCTSTR.

    Third is save for unicode: CString::GetBuffer() returns a TCHAR* or for unicode WCHAR*
    Forgetting to call CString::ReleaseBuffer() can cause problems very difficult to debug as it releases the lock on CString’s inner buffer.

  • CString.Format & printf
  • When using CString.Format with Unicode strings, please be aware that if you are using %s the class expects the parameter to be UNICODE, this means this does not work -

    CString strString;
    strString.Format (_T("Output: %s"), myClass.GetOutput(var, VARIABLE)) ;

    For correct programming your options are:

    CString strString;
    strString.Format (_T("Output: %s"), CString(myClass.GetOutput(var, VARIABLE))) ;

    or for non-MFC usage of A2T makro:

    CString strString;
    USES_CONVERSION;
    strString.Format (_T("Output: %s"), A2T(myClass.GetOutput(var, VARIABLE))) ;

    Again be careful to use the correct character set with printf.

  • Wrong cast from void*
  • You often see a unsave cast from void* to another type that is often not just done that way because of lazyness but caused by nescience of probably occuring errors.

    char c = 0;
    char* p = &c;
    void* q = p;
    int* pp = q;

    This works but is dangerous because when decrementing *pp memory will get overriden even thougth pp is an integer.

    *pp--; //dangerous!

    Doing a c-style or C++ cast avoids big problems ’cause it explecit tells the compiler what type will be written into the other.

    int* pp = (int*)q;
    int* pp = static_cast<int*>(q);

    It’s very common to assign the result of a cast to a suitable pointer:
    In C++, use the typesafe new instead of malloc() operator because it can’t alloc the wrong size of memory:

    typedef std::complex<double> d;
     
    d* q = new d(1,2); // will throw bad_alloc if memory is exhausted
    if (*q == 1) { 
    // go on... 
    }

    new throws a bad_alloc exception if a memory error occurs.

  • Errors due gets()
  • Using the gets() function is bad! Nevertheless you often come accross it’s usage…Fortunatelly it just concerns console apps.

    You’d just use this function in exceptional cases - actually it should no more get used apart from having a really good reason to do the opposite. It does not know how many characters can be safely stored in the string passed to it. Thus, if too many are read, memory will be corrupted. Many security bugs that have been exploited on the Internet use this fact! Use the fgets() function instead (and read from stdin).

All those mistake are not just things beginners do. Also lots of other programmers, even experienced ones, make rather large error in their reasoning. Those mistakes are for the reason that a compiler can’t detect them difficult to detect. They may run in your project for years without complaining.

Of course there are many more things that can be do wrong without any knowledge of it but having heard of some frequently done wrong things might help to prevent them. At least I hope so!

post Don’t compare objects mindless

February 18th, 2008

Filed under: .NET, Java — Kai @ 11:27 pm

When comparing objects in .Net I sometimes have to think about what way is the right one for my current case.

Imagine we got two objects called a and b that hold some data. There are several ways to compare them to each other - but it fact it’s not as confusing as I first thought.

Basically we can compare two different things of an object:

  • Identity (reference equality): Two objects are identical if they actually are the same object in memory. If the point to the same reference in memory they are equal.
  • Equivalence (value equality): Two objects are equivalent if the value or values they contain are the same.
int a = 3; 
int b = 3;

In most cases you’d regard them as equal ’cause their values are the same. Compared by identitiy they’re not equal.
This is the first you’d always keep in mind.

if(a.Equals(b))
{ 
Console.WriteLine("a equals b...");
}

Equals() is a virtual method on System.Object. It’s intended to test for identity or equivalence as appropriate. If you have an own type definition you’d like to be compareable you can override it in your class. Althougth it’s not necessary because there’s a default override of .Equals() in the base class. System.ValueType which will work for any structs you set up. Before overwriting it you’d consider that it uses reflection, which is slow, and involves a certain amount of boxing.

Value types get refleced over their internal fields to see if they are all equal.
For reference types, the situation is different. In general I’d expect Equals() for reference types to do an identity comparison but certain reference types aren’t lightweight enough to work as value types, but nevertheless have value semantics. That means most reference types are compared by reference but some like System.String are compared by value.

if(object.Equals(a, b))
{ 
Console.WriteLine("a equals b...");
}


object.Equals(object, object)
is a static method on the object class. This method was designed to reduce effort for programmers to check if a is null before calling a.Equals(b).
Generally it’s about the same as above mentioned a.Equals(b) but it also returns true if both objects are null ’cause they point to the same reference.

if(object.ReferenceEquals(a, b))
{ 
Console.WriteLine("a has same reference as b...");
}

object.ReferenceEquals(object, object) compares the addresses on the memory where the objects are.
Normally whether value types occupy the we don’t care about the memory addresses of them when comparing. It isn’t relevant for anything we’d want to normally use them for.

The difficulty comes from the fact that ReferenceEquals expects two System.Objects as parameters. This means that our value types will get boxed onto the heap as they are passed in to this routine. Normally, because of the way the boxing process works, they will get boxed separately to different memory addresses on the heap.

String a = "test", b="test";
if(a == b)
{ 
Console.WriteLine("a is equal to b...");
}

== is propably the most used operator for equality testing.
For value types within the .NET Framework, == is implemented as you would expect, and will test for equivalence (value equality).

If you use the == operator with reference types without thinking, bad things can happen. For example if you compare data structures which are not the same but contain the same value they’ll be compared as objects which means the comparison is done for reference and the result will be false even though the values are the same.

If you’d like to compare e.g. as System.String by reference you can do that as well by casting them to object.

String a = "test", b="test";
if ((object) a == (object) b)
{
Console.WriteLine("a and b point have same reference");
}

a is b checks the type of the the objects it is given. It’s not the comparison you might expect by the name of the operator.

int a = 3, b=4;
if(a is b)
{ 
Console.WriteLine("a and b are both integer");
}

In Java the meaning of those methods is about the same but when using the == operator you’d be careful because it does always a reference comparison - that’s why most people when programming Java for the first time don’t get the expected results. When comparing Strings in Java you’d always use equals().

static String s1 = "test";
static String s2 = new String ("test");
public static void main (String [] args)
{
  System.out.println ((s1 == s2) ? "same reference" : "not same reference");
  System.out.println ((s1.equals (s2)) ? "same value" : "not same value");
}

C# makes comparing strings easy even for a novice programmer and without sacrificing flexibility. The compiler is smart enough to realize that the == operator, when used with Strings, compares values rather than references. The option to compare references, of course, is still open to the programmer.

Always make sure that you know what you want to compare before using one of those methods/opertors without thinking.

ruldrurd
« Previous PageNext Page »
Powered by WordPress, Content and Design by Kai Bellmann
Entries (RSS) and Comments (RSS)