Two Opposite Trends in Java Programming: Which Should You Go With?
Java is a static typing language, meaning you have to define a type before you can use it and the compiler checks the types for you. Some people like the static typing and others don’t. People like it would like even more into the language. Some others would prefer less typing. The rest don’t have strong opinions and are OK with both.
In the last several years, we actually see two opposite trends in Java programming: stronger typing and weaker typing. This blog analyzes in depth why these two trends happened and what do they mean for you.
Generics for Stronger Typing
Before 1.5, Java has a little gap in the typing. For example, the Java collection library could have type leaks. Let’s take a look at two methods of the type List, which is an interface:
public boolean add(Object o); public Object get(int index);
The add() method takes an object of Object type, meaning you can put in anything as you want because in Java every object is type or subtype (directly or indirectly) of Object. When you get an item out from the list, you have an object typed as Object. In most cases, you cast it to a specific type like:
String str = (String) list.get(0);
Now, if the first object is not of String type or its subtype, you get ClassCastException.How to prevent this from happening?
In Java 1.5, generics feature was added into the language. It allows you to specify the type of the items in the container. In the above example, you would do like this:
List<String> list = new ArrayList<String>(); String str = list.get(0);
You don’t need to do conversation any more. If you get the typing wrong, the compiler would flag you with errors. Everything is just safer than before. The whole collection library was re-written to support Generics.
The problem is that you have to learn how to use the generics. And the code using generics doesn’t look as clean as before. More about the generics can be found here.
Dependency Injection (DI) for Weaker Typing
In a strong typing language like Java, once you put in code that involves the implementation types, then you establish the dependency on these implementation classes. The compiler would demand you to have them ready before you can compile the code.
This enforces the type safety, but at the same time limits the flexibility. In some cases like a framework, it doesn’t know what implementations will be there in the future.
That is where DI comes to play.
The DI is really for removing dependencies on specific implementations of interfaces so that you can switch among them on the fly without re-compiling the code. Note that, before this technique was called DI or Inverse of Control (IoC), it’s used in some tools like Eclipse for extensibility. The foundation for the DI is the Java reflection API.
There are several DI frameworks, the most famous of which is the Spring framework. While using Spring, you define an XML file, say “appcontext.xml,” which includes a bean definition like this:
<bean id="message" <strong>class</strong>="org.doublecloud.MessageImpl" lazy-init="false" init-method="printSelf"> <property name="content" ref="stringmessage" /> </bean>
In your code, you write something like that:
FileSystemXmlApplicationContext factory = <strong>new</strong> FileSystemXmlApplicationContext("applicationcontext.xml"); Message stm = (Message) factory.getBean("message");
Now you code compile without the presence of the MessageImpl class, and you can change the class in the XML file to whatever implementation class that implements the Message interface. The framework then just picks it up and makes it work.
All look great, right?
Coming along with the benefits are some drawbacks:
- If you miss type something especially the class name, you won’t know it until running it.
- If your MessageImpl does not implement the Message interface, you won’t know it neither until running it. From the xml file itself, you cannot tell it’s a requirement.
Combined together, typing is getting weaker with DI. You could have the IDE to do a little extra for you to have above checked for you. But still, it needs extra tooling and a different way to work on the issue.
Besides the above drawbacks, there are also extra problem with code browsing and debugging. When trying to look into the implementation class, you will need extra effort. First, you will find out the XML file in which the bean is defined. It’s straight forward in this small sample, but could be quite troublesome if the implementation class is “injected” somewhere else. Secondly, you need to find out the name of implementation class in the XML file. Lastly, you can search for the implementation class.
If you turn on debugger, you may be able to get the real type using inspector and jump to the step 3 directly. Still, it’s not as straight forward as normal.
Given these two opposite trends, what are you going to do with them?
In general, strong typing is a good thing. It can help you to catch problems early at development time. It becomes more important when your system scope becomes bigger. Although you can write unit test code, you should leverage the language features like generics as your first defense. The effort for using the Generics is mostly minimal.
To develop generic enabled libraries is another story – you will need extra efforts there. Overall, the chance for you to get there is pretty small. Before you ever think about that, you should ask yourself whether you can use something in the standard Java libraries. If you do, forget about inventing your own wheel.
For the DI, it all depends on what you want to do.
If you just design a typical application, I would suggest you to go straight forward without DI. But if you are working on a framework or application that requires extensibilities, DI is your friend; otherwise, it could complicate your application without clear benefits.
Like many other technologies, there is no strictly “good” or “bad” with stronger or weaker typing. It’s all about when and how you use them on what.