Showing posts with label Language Design. Show all posts
Showing posts with label Language Design. Show all posts

Monday, May 7, 2012

Java generic type erasure advantages and disadvantages

Java's type erasure on its generics allows the components produced on generics-using JVM be instantly available on non-generics JVM


An example, let's say we have a Greetable interface

interface Greetable<T> {
   String hello(T another);
}

On non-generics-capable Java, e.g. developers using JVM 1.4 or earlier, shall see Greetable's hello method's parameter as:



Type erasure has some problems though. Let's say you have two classes Geek and Hipster that implements the same interface, yet they can be friendly only among their own kind, this is where type erasure can be a problem. Read on.


class Hipster implements Greetable<Hipster>
{
    String _name;

    Hipster(String name) {
        _name = name;
    }

    @Override
    public String toString() {
        return _name;
    }

    @Override
    public String hello(Hipster another) {
        return "Hi I'm " + _name + ", hola mi amigo " + another + "!";
    }

}

class Geek implements  Greetable<Geek>
{
    String _name;

    Geek(String name) {
        _name = name;
    }

    @Override
    public String toString() {
        return _name;
    }

    @Override
    public String hello(Geek another) {
        return "Hi I'm " + _name + ", charie " + another + "!";
    }
}

You can see that both of them implements method hello, yet their accepted parameter is confined only to their own kind. This is where generics shine, error(type mismatches) can be caught during compile-time, not when it is costly to fix an error(during runtime)

Given this:

Hipster george = new Hipster("George");
Geek paul = new Geek("Paul");


Then you do this:
george.hello(paul); // compile error

That will not be allowed by the compiler, that will result to compile-time error. You use generics if you want to enforce types compatibility. Compiler can catch this error

// this is allowed. Hipster is derived from Greetable
Greetable<hipster> george = new Hipster("Pete"); 


Geek paul = new Geek("Paul");


// but this will not be allowed.
george.hello(paul); 


george.hello method signature is hello(Hipster another);. Paul is not a Hipster, he's a Geek, hence the compiler can catch that Paul doesn't matched George's friend preference of Hipster.


With type erasure, type mismatches are not caught by the compiler:


// this is allowed. Hipster is derived from Greetable
Greetable george = new Hipster("George"); 

george.hello(paul); // this will be allowed by the compiler. yet this will throw an exception during runtime

With type erasure, the type on george's hello method became an untyped one, it has method signature of hello(Object another). This has the consequence of the compiler not being able to catch type mismatches for you, hence the type incompatibility just arises during runtime only, the runtime will throw an exception; which is bad, it's better to fix errors earlier.

Saturday, April 14, 2012

Java should not aid inconsistencies

One of some leaky abstractions of Java: unboxing and comparison

class Main
{
  public static void main (String[] args) throws java.lang.Exception
  {
      Integer a = 7;
      Integer b = 7;
 
      Integer c = 200;
      Integer d = 200;
      
      // java should not aid comparing variable to literal, 
      // java language designer should make this a compilation error...
      System.out.println(c == 200); // true 
      System.out.println(d == 200); // true
 
      // ...because it gives the wrong impression that you could likewise 
      // do the same comparison on two variables:
      System.out.println(c == d); // false. what!? leaky abstraction that is!
      
 
      System.out.println(a == 7); // true
      System.out.println(b == 7); // true
 
      // this should be false. but it ain't, all numbers from -128 to 127 are cached(aka interning),
      // hence a and b points to the same memory location
      System.out.println(a == b); // true. what!? leaky abstraction that is!
 
           

      // this is way better. all the output are true
      System.out.println("\nBetter\n");

      System.out.println(a.equals(7));
      System.out.println(a.equals(b));
      System.out.println(c.equals(200)); // now we're talking
      System.out.println(c.equals(d));

       
      // and this comparison is infinitely better. 
      // when you want to compare if a is less than or equal to b, just change the '==' operator to '<='
    
      /*

      System.out.println(a.compareTo(7) == 0);
      System.out.println(a.compareTo(b) == 0);
      System.out.println(c.compareTo(200) == 0); // now we're talking
      System.out.println(c.compareTo(d) == 0);

      */
  }
}
Output:
true
true
false
true
true
true

Better

true
true
true
true
Leaky abstraction: http://www.joelonsoftware.com/articles/LeakyAbstractions.html

Sunday, April 8, 2012

Java and C# varargs difference need a mindset shift

Whereas in C#, this will output integer:

using System;
public class Test
{
        public static void Main()
        {
                int a = 7;
                DoStuff(a);
        }
        
        static void DoStuff(object x)
        {       
                Console.WriteLine("object");
        }
        
        static void DoStuff(params int[] x)
        {
                Console.WriteLine("integer");
        }
}

In Java, this will output object:

public class Main
{
        public static void main(String[] args)
        {
                int a = 7;
                DoStuff(a);
        }
        
        static void DoStuff(Object x)
        {
                System.out.println("object");
        }
        
        static void DoStuff(int... x)
        {
                System.out.println("integer");
        }
}

Thursday, April 5, 2012

int and short compatibility problem, not a versioning problem. it's a bad design decision

On my last post about int compatibility problem, I have an impression that the problem lies in the versioning problem.

On C# and C++, following would compile, but not in Java:

class Alien
{
 String invade(short ships) { return "int"; }
}


public class Invader
{
 public static void main(String[] args)
 {
  System.out.println(new Alien().invade(7));
 }
}


If it really is about versioning problem, why Java allows mapping numbers to float?


Java would compile this:

class Alien
{
        String invade(float ships) { return "float"; }
}

public class Invader
{
        public static void main(String[] args)
        {
                System.out.println(new Alien().invade(7));
        }
}

Now if you put Alien class and Invader class to their own file, the mappability of number to float shall have a versioning problem:

Alien.java:
class Alien
{
 String invade(float ships) { return "float"; }
}

Invader.java:
public class Invader
{
    public static void main(String[] args)
    {
        System.out.println(new Alien().invade(7));
    }
}

Compile both files, then run it, output will be:
float

Now try to modify the Alien.java and then re-compile it (note: don't re-compile Invade.java):

class Alien
{
 String invade(float ships) { return "float"; }
 String invade(int ships) { return "int"; }
}

Then re-run Invader (don't recompile it), output will be:
float

Now that's weird, if you read Invade.java source code, you can see that the number 7 could be mapped to overloaded method with int parameter, but since the burned instruction on the call site is bound to call the method with float parameter, the code would still continue to call the method with float parameter, regardless of the component(Alient.class) having a more compatible method, one that accepts int. To coerce the caller(Invader.class) to sync to the more fitting overloaded method, re-compile the Invader class. Then the output will be:

int


Seeing that a number is compatible to a method that accepts float, why not make it compatible to short then too?

Saturday, March 24, 2012

Explicit public declaration of interface methods. Not trusting your users' intelligence enough

Whereas C# doesn't allow this, Java does:

interface Device 
{
 public void doIt();
}



interface methods are always public and there's no reason to declare it explicitly so. Allowing that sort of thing reminds me of purported Hungarian notation:

int intCount;
int iCount;
string stringName;
string sName;


You can forgive the compiler for not enforcing compilation error on bad misuse of that Hungarian notation, but for the language being lenient on catching that redundant public thing is sort of not trusting the language's users intelligence enough. Think about it, you want your language users to always remember interface methods are public, then who are the audience of explicit public declaration of interface methods? Its non-users? :-)


Another redundancy:
http://stackoverflow.com/questions/4380796/what-is-public-abstract-interface-in-java