The Source for Java Technology Collaboration
User: Password:



Kirill Grouchnikov's Blog

Programming Archives


Evolving the language

Posted by kirillcool on February 06, 2008 at 10:34 AM | Permalink | Comments (21)

I guess i wasn't very focused in yesterday's entry, paying too much attention to the details of the specific puzzle and not emphasizing my main point (or as Chris defined it the "money quote"). I'll try to rectify it here, bringing together my thoughts on the subject of evolving Java as a language.

First, let's start with the code from Neal's entry. Here is the relevant part:


    static <T,U> List<U> map(List<T> list, 
		{T=>U} transform) {
        List<U> result = new ArrayList<U>(list.size());
        for (T t : list) {
            result.add(transform.invoke(t));
        }
        return result;
    }

    public static void main(String[] args) {
        List<Color> colors = map(Arrays.asList(
		Flavor.values()), { Flavor f => f.color });
        System.out.println(colors.equals(Arrays.asList(Color.values())));
    }

Putting aside the puzzle punchline, here is how i would do the same without closures:


public class Puzzler<T, U> {

   interface Transformer<T, U> {
      U transform(T t);
   }

   List<U> transform(List<T> list, 
          Transformer<T, U> transformer) {
      List<U> result = new ArrayList<U>(list.size());
      for (T t : list) {
         result.add(transformer.transform(t));
      }
      return result;
   }

   public static void main(String[] args) {
      List<Colour> colors = new Puzzler<Flavor, Colour>().transform(Arrays
            .asList(Flavor.values()), new Transformer<Flavor, Colour>() {
         public Colour transform(Flavor f) {
            return f.color;
         }
      });
      System.out.println(colors.equals(Arrays.asList(Colour.values())));
   }
}

And now comes the million dollar question - which one is better? It's a simple question that has two straightforward answers. However, the real answer is "it depends". And based on the subjective background of each one of us, we would go for either the first version or the second version. What does it depend on? Well, quite a few things.

There was an illuminating exchange of opinions in Scala blogosphere about a month ago, ignited by Doug Pardee's post, which lead Reg Braithwaite to quote Jef Raskin's views on the definition of intuitive:

It is clear that a user interface feature is "intuitive" insofar as it resembles or is identical to something the user has already learned. In short, "intuitive" in this context is an almost exact synonym of "familiar."

Let's get back to our two code samples, and to the question which one is better. In my highly subjective opinion, the second one is better, because:

  • it is more readable
  • it is more maintainable
  • it is more verbose

Allow me to address each one of these three points. The second sample is more readable because this is how i'm accustomed to program this sort of requirement. This is what the current language structures allow me to do, and i don't find it too much of a hassle. The code is more maintainable because of the limited variety of currently available options. It ascertains that i and other people who will have to maintain this code must have seen this approach a few times in the existing code base. Getting back to the Jef Raskin quote, it feels more intuitive since it is familiar. Not necessarily optimal, but familiar nonetheless.

The last point on verbosity might be a little controversial, but i'll stand by my words. I've programmed in Ada for almost five years (professionally) and i've never seen it as an over-verbose language. Sure, it might take me an extra 20 seconds to read through the definition of that interface and its invocation, but the extra verbosity makes sure that the code guides me (as its maintainer) along its intended path.

Why would one choose the first example? There are many reasons, all of them undoubtedly valid in the subjective eye of the relevant parties. Some would enjoy the perceived expressiveness (cramming more logic in less characters), some would prefer the abstraction of a method body as first-class language citizen, some would want to know that their language is on par with the hot kid-du-jour that gets all the blogosphere love, some would view it as a refreshing exercise to expand their mind, and some would see the commercial potential that lies in books, consulting and conferences (absolutely no offense meant).

One nagging question remains unanswered - who is the intended target of the new language features? Are the creative minds behind different closures proposals at work just because they are creative, or is there an overwhelming evidence that Java programs (and programmers to a lesser extent) suffer without this language feature. Couple that with the absolute refusal to remove existing language features (like, say, poorly-implemented generics), and you get the following from Bruce Eckel:

But we need to become especially conservative when considering major, fundamental language features like closures which, while they can be very appealing in theory, may have a cost that is too great in practice when they are forced into a language that values backward compatibility over the clarity of its abstractions.

There are real-life scenarios addressed by closures. Anonymous listeners, unnecessary complex try-catch-finally blocks on closing streams and connections, blocks guarded by semaphores, you name it. But if the language is not going to remove (refuse to compile) the existing ways of addressing these scenarios, adding yet another way to do what millions (yes, millions that do not read or write blogs) Java programmers already know how to do is very harmful. Harmful to the code readability (at least in the first few years with the very slow adoption rate for the server-side JDK upgrades) and harmful to the code maintainability.

In my opinion, the language is only a tool. At the end of the day, we still have real people addressing real problems. The technical merits of new language proposals need to be weighed carefully against the potential negative disruption that they bring into the everyday cycle of software development. The puzzlers are only a tip of the iceberg. When i need to maintain and extend existing code, i already have a hard enough time to understand the business logic in it. If you're not going to remove the existing ways to address the limitations imposed by the language, don't add yet another way. It's not going to help me. Not in the everyday world.



And so it begins - the first closures puzzler

Posted by kirillcool on February 05, 2008 at 10:20 AM | Permalink | Comments (41)

Neal Gafter has posted the first closures puzzler. I guess the second edition of Java Puzzlers is in works, and closures will be a hefty addition to the book. If anything, this makes me really sad.

I was very excited to lay my hands on the first edition, but after reading through a few chapters, i skimmed the table of contents, glanced at the visual illusions and never came back to it. If i had to sum this book in one sentence, it would be "great optical illusions and irrelevant content". I know, this is a harsh statement, and i have nothing but respect to both authors (in fact, i consider "Effective Java" to be the book that i would take with me to a deserted island on a condition that it has WiFi access).

I've been using Java for the last 8 years, most of it exclusively (amounting to about 10-12 hours a day, including the work and the side projects). I've written my share of new code, and i most certainly have seen my share of old code that i had to maintain, fix and extend. But never once have i encountered anything even remotely connected to any Java puzzler presented in the book and at the conferences.

Bit-level shifts, integer overflows, overloaded methods, reflection, generics - you name it. I've looked at the examples, i've tried to match them to the real code that i see during the day, and i have found nothing. Of course, i am looking at the tiniest sliver of Java code that exists in the enterprise sector, but somehow i get an impression that i'm not alone. I would even go as far as to say that a feature implemented in a way that results in at least one puzzler is not a feature worth having in the language.

Which brings me to the quote of the day. It comes from an announcement on the end-of-life for Ruby.NET:

As a researcher, my prime interest is not in developing products, but in developing innovative new ideas and having an impact by having those ideas used in the real world.

The generics were added to the language in a very incomplete manner, mainly due to the restrictions on binary compatibility. If we let the researchers in us (even if they are the best researchers) to do the same with closures, i'll pass.



The things that we take for granted

Posted by kirillcool on March 08, 2007 at 11:12 AM | Permalink | Comments (8)

Last weekend i went and bought a new laptop (actually, this is my first laptop). So, as i plugged it in and connected to the net, Vista started downloading updates and installing them. And then i started thinking - what happens if one of these updates will simply crash the OS on the next boot?

The implicit assumption of any update to the operating system is that it at least boots (if everything is OK with the hardware). For the past 8 years that i've been using Windows (from 3.11 to NT, 98, XP, 2003 and Vista) i've seen the blue screen of death only once, and that was because of bad RAM sticks (after our IT department replaced them, it all went back to normal). So you kind of take it for granted that when you switch the power on, the OS will come alive and greet you with the full functioning desktop at your disposal. But just think about myriads of small things that can go wrong, including the boot, startup processes, login, drivers, mouse and keyboard, anti-virus, firewall etc. The operating system simply can't fail.

I don't know what's the testing process inside Microsoft for releasing the automated updates, but the end result is that it simply works. They are downloaded, installed, and on some occasions (which are far less frequent than they used to be) OS reboots. And then the magic happens - it all comes back to life. No exception stack traces to copy and send to the developer. No magic key sequence to bypass a faulted module. No praying that it won't crash. It just works.

And this is a technical feat worthy of praise. Such a gigantic multi-layered beast and zero tolerance for startup errors. Makes me think twice about asking people to send stack traces when something goes wrong...



When you know that a programmer is a Java programmer

Posted by kirillcool on December 04, 2005 at 04:47 AM | Permalink | Comments (21)

A long long time ago in a blog far far away I wrote about JDK collections and how should you choose your data structures. This topic has been on my mind during the past year, and I still haven't reached a definitive conclusion.

First, I should mention that i have interviewed about 40 people this year for the technical positions in our project, which gave me an opportunity to see a large variety of programming backgrounds, styles and approaches. Second, we program in Java, which is quite important for the following discussion. Now that this is said, let's continue.

There are numerous techniques for interviewing candidates for technical positions. They range from writing the code on the paper to open-ended design discussions. Most would argue that the best candidate should be well-versed in many conceptually different languages (such as Java / Perl / Lisp), but doesn't have to be a master of any. This will allow him / her to pick the tool (language) that best suites the task. This is true in theory. In practice, however, there are quite a few formidable obstacles to this.

First, most existing projects have already chosen an implementation language, so it would be quite annoying hearing "Let's do this in Ruby" when you have hundreds of man-years invested in the project under your belt. In addition, it's not only your decision - you have existing deployments at customer sites, when sometimes the customer is the one dictating the language (say, somebody invested in a farm of WebLogic servers and wishes your application to be a part of the eco-system there). Furthermore, what about other developers? Sure, in Ruby you can do it in half the time, if only you have 10 experienced Ruby developers (not all applications are CRUD in the real world, unfortunately). Until we get that many available Ruby developers, curb your enthusiasm :(

And now, for the interesting part. Suppose you have an excellent Perl developer and a good Java developer. Which one should you take to your Java project? Arguably, an excellent non-Java developer can learn Java syntax in 4-5 days. But is syntax everything you need to know to write excellent Java code? How much time will it take until he starts to write Java code that looks like Java code and not like Perl code (don't forget that working on team means that the code is maintained by everybody, and even if somebody leaves, his code stays)? Do big projects really need "stellar" developers, or perhaps a team of good developers with solid Java knowledge does better job in the long run?

Which brings me to something that has been on my mind - what turns a programmer to a Java programmer? In my opinion, it's the correct way of working with collections. Once I see a candidate using iterators and maps, this is a very good sign. Most of the people coming from C or C++ work only with arrays or vectors. They sure look like hammers, but most of the real-world problems are bolts. It takes some time to get used to the syntax, it takes more time to know the available options, and it takes much more time to make the correct and informed choice. During this time, an excellent ex-Perl developer is prone to make your code base slower, dirtier and a mess to maintain.

Any thoughts?

Mylar - a very useful Eclipse plugin

Posted by kirillcool on November 06, 2005 at 01:29 AM | Permalink | Comments (4)

The Mylar plugin for Eclipse (available for versions 3.1 and 3.2M3 only) is, without doubt, one of the most innovative ways to change out interaction with IDEs.

Let's see a typical example of our daily work on Java project. You get a task, which can be an enhancement, new feature or bug fix. Typically, you have your project as a tree on the left-hand side, along with the list of all methods / fields (either as sub-branches of the project tree or as a separate tree). When you need to view or change source code, you either locate the corresponding entry in the project tree, or use one of the many shortcuts (such as Ctrl+Shift+T in Eclipse) to locate that class. Typically, you will need to access a number of classes, with a couple of methods in each one of them. Now, consider what happens in an existing project.

You have hundreds (if not thousands) of classes in tens of packages, each class having quite a few functions (depending on the previous team members, it can get to hundreds). The classes that you need to change for a particular bug fix are most likely under different packages. When you need to go back and forth between these classes, you waste valuable time (and energy) to do so. Wouldn't it be nice to have a context view of your workspace. This view would contain only relevant branches of the project tree, the classes you are working on and the methods that you are changing. Ideally, the IDE itself would track the changes you are making to the codebase, continually updating the context view. Mylar plugin for Eclipse does exactly that.

Here are few screenshots that show the differences (before and after applying Mylar):

Outline of a single class - before Mylar (click to view full-size)

mylar-outline-no-context-small.png

Outline of a single class - after Mylar (click to view full-size)

mylar-outline-context-small.png

Outline of class tree - before Mylar (click to view full-size)

mylar-package-no-context-small.png

Outline of class tree - after Mylar (click to view full-size)

mylar-package-context-small.png

Few things should be noted. First of all, Mylar is very much work in progress, so there are quite a few quirks and exceptions. In addition, if your task spans more than 5-6 classes, and you work on more than one function in each class, the UI gets a little bit overstuffed (however, you can remove the context entries manually):

mylar-too-much-small.png



Developing Java project - a team effort

Posted by kirillcool on October 14, 2005 at 02:04 PM | Permalink | Comments (8)

I was watching one of the Monday night NFL games that i have on tape. It was a Packers vs. Bears from three years ago. Bears had a lousy night with three interceptions (one returned for TD), three sacks on defense, fumbled kick return, missed onside kick and a variety of assorted mishaps. They lost 21-34 and went on to have a 4-12 season. One phrase by Madden got stuck with me (guess i missed it back then). One of the defensive stars of the league is Brian Urlacher who's been with Bears for the last six seasons. Madden called him "the perfect middle line-backer and the perfect football player".

The track record for Bears over these six seasons (Urlacher has missed only 7 games due to injury over these seasons) has been far from perfect though - only one winning season (13-3 in 2001) and 22-46 in other five seasons (including 1-3 during the current one). The reason is obvious - it takes 53 players (11 at any given moment, unless you want to get a "12 men on field" penalty) to play the game. Have one star and ten dead-beats - the star isn't going make a difference.

Over the same six years, four different teams have won the Superbowl, with three victories going to the Patriots. In all cases except the Ravens (and the SB score is a little misleading), the winning teams had a perfect combination on all teams, offensive, defensive and special (2000 Ravens defense gave up an amazing 9.4 points per game, so there was no need for real offense).

Every year, we (the lowly developers) are attacked with a variety of new solutions that promise to cut development cost, time and productivity in half (in worst case). Last year's award undoubtedly goes to AOP (thankfully we got over it before it hit the mainstream). This year, it's AJAX, SOA and (sort of) Web 2.0. This entry caught my eye recently. It outlines an interesting cross-section of technologies that should be mastered by an AJAX developer. And that is only for the GUI interaction with the server. What about the persistence layer, internationalization support, deployment and installation, data structures, flow engine, interoperability with legacy systems? Regard any one of those as minor issue and it will bite you. Hard.

The religious wars over "which IDE is best" kind of ignore a simple problem - if you chose a poor solution for clustering / high availability, the best IDE will get you to the dead end twice as fast. You will just have a little more time to understand that you will not make the deadline. It takes a team of programmers to do the job, but it also takes a team of solutions for a team of problems. Pick the current shining star and forget all the rest? See you on your next project.

How single can your singleton instance be?

Posted by kirillcool on August 24, 2005 at 01:40 AM | Permalink | Comments (12)

We are all familiar with the following straightforward implementation of lazy-loaded Singleton pattern:
    
public class TestSingleton {
  private static TestSingleton instance;

  private TestSingleton() {}

  public static synchronized TestSingleton getInstance() {
    if (instance == null) {
      instance = new TestSingleton();
    }
    return instance;
  }
}
The constructor is private, and a single public function is exposed. This function (which is also synchronized) tests whether there is already an instance and if not, it creates one. Item 57 in Joshua Bloch's "Effective Java" instructs us to add readResolve() function in case the singleton is also Serializable. Another version, which employs eager-loading is:
    
public class TestSingleton {
  private static TestSingleton instance = new TestSingleton();

  private TestSingleton() {}

  public static TestSingleton getInstance() {
    return instance;
  }
}
It's even simpler than the previous one. You pay a one-time penalty of creating an object (even if you never use it), but the getInstance() is no longer synchronized.

The constructor, is of course, private in both cases to prevent accessing it from the outside code. However, as shown before, you can use combination of getDeclaredConstructors and setAccessible to make the private constructor accessible via reflection. You can then create any number of instances of that singleton class. However, the author of the above failed to notice that the created instance may not be fully functional. The publically-accessible getInstance() function can perform additional manipulations with the created object, setting additional properties and attributes. In this case, the object you have may not function properly. Here, you need to call the getInstance() function and have it return a different instance (properly and fully initialized) each time you call it.

The answer lies in Chapter 2 of excellent Component Development for the Java Platform book (which came out in 2001). This chapter describes the way the ClassLoaders work, and how you can have any number of Class object instances for the same class. Let's start with a small (and not real-life) example to show how it works, and then go to a real-life example.

First, we create a new Java class at runtime:
    
public class SingletonMain {
   public static void main(String... args) throws Exception {
      Logger _logger = Logger.getLogger(SingletonMain.class.getName());
      // create first version of class
      String className = "TestSingleton";
      String dir1 = "C:\\temp\\ver1";
      new File(dir1).mkdirs();
      BufferedWriter bw1 = new BufferedWriter(new FileWriter(dir1
            + File.separator + className + ".java"));
      bw1.write("public class " + className + " {");
      bw1.newLine();
      bw1.write("  private static " + className + " instance;");
      bw1.newLine();
      bw1.write("  private " + className + "() {}");
      bw1.newLine();
      bw1.write("  public static synchronized " + className + " getInstance() {");
      bw1.newLine();
      bw1.write("    if (instance == null) {");
      bw1.newLine();
      bw1.write("      instance = new " + className + "();");
      bw1.newLine();
      bw1.write("    }");
      bw1.newLine();
      bw1.write("    return instance;");
      bw1.newLine();
      bw1.write("  }");
      bw1.newLine();
      bw1.write("}");
      bw1.newLine();
      bw1.close();
The class looks like this:
    
public class TestSingleton {
  private static TestSingleton instance;
  private TestSingleton() {}
  public static synchronized TestSingleton getInstance() {
    if (instance == null) {
      instance = new TestSingleton();
    }
    return instance;
  }
}
We now compile it and use URLClassLoader to load the compiled class:
    
      // compile
      int compStatus = com.sun.tools.javac.Main.compile(new String[] { dir1
            + File.separator + className + ".java" });
      _logger.info("Compilation status is " + compStatus);

      // load
      ClassLoader classLoader1 = new URLClassLoader(
            new URL[] { new File(dir1).toURL() });
      Class clazz1 = classLoader1.loadClass(className);
      _logger.info("Loaded " + clazz1.getName() + " [" + clazz1.hashCode()
            + "]");
The output is
    
INFO [11:05:27.553] [SingletonMain.main] Compilation status is 0
INFO [11:05:27.584] [SingletonMain.main] Loaded TestSingleton [24287316]
Now, we use reflection to call the getInstance() function. In addition, we test that no public constructor is defined:
    
      Method getter1 = clazz1.getMethod("getInstance", new Class[0]);
      Constructor[] ctrs1 = clazz1.getConstructors();
      
      _logger.info(ctrs1.length + " constructors");
      Object val10 = getter1.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val10.hashCode());
      Object val11 = getter1.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val11.hashCode());
      Object val12 = getter1.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val12.hashCode());
      
The result here is, as expected, the same instance on all calls to getInstance():
    
INFO [11:05:27.584] [SingletonMain.main] 0 constructors
INFO [11:05:27.584] [SingletonMain.main] Fetched object #7254922
INFO [11:05:27.584] [SingletonMain.main] Fetched object #7254922
INFO [11:05:27.584] [SingletonMain.main] Fetched object #7254922
As mentioned before, you can use getDeclaredConstructors() and setAccessible() on Constructor to make it accessible, but then you will miss the additional logic (empty in our sample case) in getInstance().

In order to create a second instance of TestSingleton class, we need to create additional ClassLoader that does not have our first ClassLoader as its ascendant. As described in the book, the ClassLoader first checks its cache to see if it already loaded the specified class, then asks its parent for the class (recursively until there is no parent), and only then (if parent tells that it doesn't have this class) it loads the class (from the URL in our case). Here, we need to copy the compiled class to another directory and create a second URLClassLoader. Both class loaders will have the same parent (system class loader), but this parent doesn't have the TestSingleton class, hence the second class loader will have its own copy:
    
      // copy the class file to another directory
      String dir2 = "C:\\temp\\ver2";
      new File(dir2).mkdirs();
      InputStream in = new FileInputStream(new File(dir1 + File.separator
            + className + ".class"));
      OutputStream out = new FileOutputStream(new File(dir2 + File.separator
            + className + ".class"));
      byte[] buf = new byte[1024];
      int len;
      while ((len = in.read(buf)) > 0) {
         out.write(buf, 0, len);
      }
      in.close();
      out.close();

      // load
      ClassLoader classLoader2 = new URLClassLoader(
            new URL[] { new File(dir2).toURL() });
      Class clazz2 = classLoader2.loadClass(className);
      _logger.info("Loaded " + clazz2.getName() + " [" + clazz2.hashCode()
            + "]");
The result is:
    
INFO [11:05:27.600] [SingletonMain.main] Loaded TestSingleton [33482492]
As you can see, the hash code of the second class object is different - we have a distinct class. Let's test that it's really different:
    
      Method getter2 = clazz2.getMethod("getInstance", new Class[0]);
      Constructor[] ctrs2 = clazz1.getConstructors();

      _logger.info(ctrs2.length + " constructors");
      Object val20 = getter2.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val20.hashCode());
      Object val21 = getter2.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val21.hashCode());
      Object val22 = getter2.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val22.hashCode());
And the result is:
    
INFO [11:05:27.600] [SingletonMain.main] 0 constructors
INFO [11:05:27.600] [SingletonMain.main] Fetched object #32429958
INFO [11:05:27.600] [SingletonMain.main] Fetched object #32429958
INFO [11:05:27.600] [SingletonMain.main] Fetched object #32429958
So, now we have two instances of (almost) the same class. It is true that the Class objects are different, but the functionality is the same.

Now that we have seen the technique, it can be trivially applied to already existing classes. In order to do this, we scan the class path looking for either the .class on local computer or for jar file that contains the required class. After we find it, we copy the corresponding class to some directory, and load it using the same technique as above (passing null parent). Afterwards we create a second copy in another directory.
    
public class SingletonMain2 {
   public static void main(String... args) throws Exception {
      Logger _logger = Logger.getLogger(SingletonMain2.class.getName());

      String className = "TestSingleton2";
      String classSig = className + ".class";
      File dir1 = new File("C:\\temp\\ver1");
      dir1.mkdirs();
Now we start to scan the classpath. Note that here we show only Windows version. For cross-platform, you need to use ":;" pattern to break the classpath and assemble drive names back when you are under Windows:
    
      String classpath = System.getProperty("java.class.path");
      _logger.info("Classpath : " + classpath);
      String[] pathComps = classpath.split(";");
      boolean wasLocated = false;
For each classpath component, we check whether it is a jar file or a directory. The handling of jar files is left as a (simple) exercise:
    
      for (String pathComp : pathComps) {
         if (wasLocated)
            break;
         if (pathComp.endsWith(".jar")) {
            // scan jar and look for the desired class.
            // Use JarInputStream for reading jar file and
            // JarEntry for checking the current entry.
            // The, getInputStream() on the matching entry
            // and copy it to the temp directory as below.
         } 
In case it's a directory, we scan it for the desired .class file (not that if the class belongs to some package, you'll need to adjust the scan code correspondingly):
    
            // should be directory
            File pathCompDir = new File(pathComp);
            if (pathCompDir.exists() && pathCompDir.isDirectory()) {
               // scan for the class
               File[] allFiles = pathCompDir.listFiles();
               for (File currFile : allFiles) {
                  if (currFile.getName().equals(classSig)) {
                     _logger.info("Located at "
                           + currFile.getAbsolutePath());
                     wasLocated = true;
                     // copy to another directory
                     InputStream in = new FileInputStream(currFile);
                     OutputStream out = new FileOutputStream(new File(
                           dir1, classSig));
                     byte[] buf = new byte[1024];
                     int len;
                     while ((len = in.read(buf)) > 0) {
                        out.write(buf, 0, len);
                     }
                     in.close();
                     out.close();
                     break;
                  }
               }
            }
As shown above, when the class is found, it is copied to a temporary directory. After we are done copying, we create the first URLClassLoader with null parent. This is crucial - the default parent of a new class loader is the system class loader. In this case, the parent class loader has access to the original class (since it's in the classpath), and the copied class will be simply ignored.
    
      if (!wasLocated) {
         _logger.info("Not located - quitting");
      }

      // very important - pass null parent class loader, otherwise
      // the parent class loader will be system class loader and
      // it will find the class in the classpath
      ClassLoader classLoader1 = new URLClassLoader(
            new URL[] { dir1.toURL() }, null);
      Class clazz1 = classLoader1.loadClass(className);
      _logger.info("Loaded " + clazz1.getName() + " [" + clazz1.hashCode()
            + "]");
      _logger.info("Have " + clazz1.getName() + " [" + clazz1.hashCode()
            + "]");
The result is
    
INFO [11:27:39.598] [SingletonMain2.main] Loaded TestSingleton2 [4384790]
INFO [11:27:39.598] [SingletonMain2.main] Have TestSingleton2 [4384790]
Now, as before we call getInstance() a couple of times:
    
      Method getter1 = clazz1.getMethod("getInstance", new Class[0]);
      Constructor[] ctrs1 = clazz1.getConstructors();

      _logger.info(ctrs1.length + " constructors");
      Object val10 = getter1.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val10.hashCode());
      Object val11 = getter1.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val11.hashCode());
      Object val12 = getter1.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val12.hashCode());
And see that the same instance is returned:
    
INFO [11:27:39.598] [SingletonMain2.main] 0 constructors
INFO [11:27:39.598] [SingletonMain2.main] Fetched object #9634993
INFO [11:27:39.598] [SingletonMain2.main] Fetched object #9634993
INFO [11:27:39.598] [SingletonMain2.main] Fetched object #9634993
Now, we copy the same class to another temporary directory
    
      // copy the class file to another directory
      String dir2 = "C:\\temp\\ver2";
      new File(dir2).mkdirs();
      InputStream in = new FileInputStream(new File(dir1 + File.separator
            + className + ".class"));
      OutputStream out = new FileOutputStream(new File(dir2 + File.separator
            + className + ".class"));
      byte[] buf = new byte[1024];
      int len;
      while ((len = in.read(buf)) > 0) {
         out.write(buf, 0, len);
      }
      in.close();
      out.close();
And create additional class loader:
    
      // load
      ClassLoader classLoader2 = new URLClassLoader(
            new URL[] { new File(dir2).toURL() }, null);
      Class clazz2 = classLoader2.loadClass(className);
      _logger.info("Loaded " + clazz2.getName() + " [" + clazz2.hashCode()
            + "]");
The class object is different:
    
INFO [11:27:39.614] [SingletonMain2.main] Loaded TestSingleton2 [14576877]
Call getInstance() a couple of times on the new class:
    
      Method getter2 = clazz2.getMethod("getInstance", new Class[0]);
      Constructor[] ctrs2 = clazz2.getConstructors();

      _logger.info(ctrs2.length + " constructors");
      Object val20 = getter2.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val20.hashCode());
      Object val21 = getter2.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val21.hashCode());
      Object val22 = getter2.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val22.hashCode());
And you get the same (but different from the first) object:
    
INFO [11:27:39.676] [SingletonMain2.main] 0 constructors
INFO [11:27:39.676] [SingletonMain2.main] Fetched object #12677476
INFO [11:27:39.676] [SingletonMain2.main] Fetched object #12677476
INFO [11:27:39.676] [SingletonMain2.main] Fetched object #12677476
And now, the big question - how single can your singleton instance be? See here how to create a true singleton.
Technorati Profile

IDE lockdown - give my Java back

Posted by kirillcool on February 03, 2005 at 01:07 PM | Permalink | Comments (16)

As aptly put in this article, there are two types of programmers, those that go with languages and those that go with tools. Given that we all go with Java, what about the tools? Looking at the market, we have at least five major players, Eclipse, JBuilder, NetBeans, JDeveloper and IntelliJ (which, unfortunately, has no free version). How to choose which one is the best for you? Here there are number of options:
  • at work - most probably dictated by the work-place policy (which on itself isn't bad, considering that you have to work in team environment at different computers, so you'd better have the same environment installed)
  • go with the majority (hoping that the shepherd isn't blind and has no hidden agenda)
  • try out various tools and see which one fits you the best
In this case, the freedom of choice is a bliss. I remember working with JBuilder back in 4-6 versions, and i look back at those days with a shudder. It's unthinkable why even now, after 5 more versions, the GUI still looks awful, but that is a totally different subject. Unfortunately, the longer you work with a particular IDE, the more keyboard shortcuts you learn. Why do i say unfortunately? Because now you are locked on one particular IDE. For better or worse, you are basically stuck. Let's see a quick example.

At work, we use Eclipse. At home, I use IntelliJ. The more i program with both tools, the more i get used to the quick shortcuts (instead of lifting my fat fingers and moving that heavy mouse across the screen to the menus). Looking back, i see that most of the time i use eight different functionalities (in no particular order):
  • rename (variable, member, class)
  • move (class to another package)
  • find declaration (of variable, member, class)
  • refactor string to constant static member
  • generate getters and setters for a class
  • implement methods from interface
  • organize imports
  • reformat code
The first two are a must-have, as pointed out here. The third one is even more important, the rest are nice and very useful. Of course, i use much more than these eight (guess i disagree with this post). Here is the problem:

Function Eclipse 3.1 IntelliJ 4.5 JBuilder 2005 NetBeans 4.0 JDeveloper 10g
Rename Alt+Shift+R Shift+F6 None Alt+Shift+R -
Move Alt+Shift+V F6 None Alt+Shift+V -
Find declaration F3 Ctrl+B Ctrl+Enter Alt+G None
Refactor to const None Ctrl+Alt+C - - -
Organize imports Ctrl+Shift+O Ctrl+Alt+O Ctrl+I Alt+Shift+F None
Format code Ctrl+Shift+F Ctrl+Alt+C - Ctrl+Shift+F -
Generate get/set None Alt+Insert - - None
Implement methods None Ctrl+I None Ctrl+I -
Total shortcuts 165 243 202 82 173


The more i use these features in Eclipse and IntelliJ, the more i get confused and use wrong combination in wrong IDE. It gets worse at the beginning of the programming session, but it doesn't get much better after that, when i try to tell myself to use the right combinations. I was thinking right now about the natural language analogy and why babies seem to easily pick two different languages at the same time. The analogy is out of place here: although the shortcuts refer to the same functionality, they are in the same Ctrl+Shift+Alt+Key language. It gets even worse in debugging:

Function Eclipse 3.1 IntelliJ 4.5 JBuilder 2005 NetBeans 4.0 JDeveloper 10g
Step in F5 F7 F7 F7 F7
Step over F6 F8 F8 F8 F8
Step out F7 Shift+F8 - Alt+Shift+F7 Shift+F7
Resume F8 F9 - Ctrl+F5 F9


So what, you may say, just stick with one IDE and you are set. When something better comes up, just move to it and forget about the old one. The problem here is that not only you present yourself (say in CV) as Java programmer, but now you are tempted to mention that you are expert in a particular IDE. The current trend of hiring J2EE programmers that only have experience in a particular application server may well project into IDEs. Although tying language and working environment may prove fruitful in the short run, you are restricting your options and the mindset in the long run.

I vote with both hands for IDE. They are indespensable for debugging, refactoring, integration with third part extensions. I won't go back to emacs or pico. But each vendor implementing 200+ shortcuts that agree only on Ctrl+S being Save? Maybe we need a JSR for the key bindings. I won't even go into creating my own keymaps or choosing one of the predefined keymaps that simulate the rival IDEs. If they are there, why won't you just stick with them? The people will not choose the IDE because of the keymaps, they will only be more than happy to know that they can revert to your IDE without the need to learn new keymap set.



Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds