JavaData & JavaTop Ten Untrodden Areas of Java

Top Ten Untrodden Areas of Java

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

From its inception, the diverse feature of the Java API has grown into a multifaceted giant. It’s almost impossible for one to tread every route Java has taken into consideration. There was a time when one could say “I know every aspect/library of the C/C++ language.” No doubt, C/C++ has grown to a great extent now, but, it’s still a child from the perspective of the diverse features and API library of Java. In this article, I shall try to get into ten (in my opinion) of those lesser treaded alleys of Java and give a little hint about what those are for, with some relevant examples.

1. Generics

Tthe Generics APIs were added to Java from version 1.5. Generics is basically a general type cast of a collection where we can recast into a specific object and tell the compiler what type of objects are permitted in each collection. The compiler then inserts the cast automatically. Because the compiler knows the cast now, it can give an error message if we try to insert a wrong type of object into the collection at compile time. This makes both our code safe and clean. Each generic type defines a raw type, such as List<E>. Here, E corresponds to a raw type that we can recast into, say, List<String>.

The following code is a bad idea because we accidentally added a date object into a list of strings. The compiler does not complain because it does not have a hint of the object cast in the list.

List list=new ArrayList<>();
list.add("Sunday");
list.add("Monday");
list.add(new Date());
Iterator iter=list.iterator();
while(iter.hasNext()){
   System.out.println(iter.next());
}

This is a the right way. The compiler now gives the error message.

List<String> list=new ArrayList<>();
list.add("Sunday");
list.add("Monday");
list.add(new Date());   //this gives the error message
Iterator<String> iter=list.iterator();
while(iter.hasNext()){
   System.out.println(iter.next());
}

2. Threads

A thread simply defines the single execution life cycle of a program. In fact, even the simple “hello world” program in Java is a thread in its execution. Each thread has certain states where we can intervene programatically during execution. The states are new, when the program starts a thread; runnable, where we can intervene with await; and lock calls to a state called waiting. The waiting state regains a runnable state with unlock and signal calls. There is also another waiting state called timed waiting. Timed waiting regains a runnable state after a given time interval. The final state of a thread is terminated, where the thread completes its task. Now, there can be multiple threads at work synchronously; this is called multithreaded programming. In Java, there are two ways to create a thread: either implement the Runnable interface or extend the Thread class.

public class MyTask implements Runnable{
   private String threadName;
   private int sleepTime;
   private static Random rand=new Random();

   public MyTask(String threadName){
      this.threadName=threadName;
      sleepTime=rand.nextInt(1000);
    }

   @Override
   public void run() {
      try{
         System.out.printf("%s will sleep "
         + "for %d msecn", threadName, sleepTime);
         Thread.sleep(sleepTime);
      }catch(InterruptedException ex){
         ex.printStackTrace();
      }
      System.out.println(threadName+" woke up");
   }
}

Or, we can write the same code by extending the Thread class, as follows:

public class MyTask extends Thread{
   ...
}

Execute above code from main with

public static void main(String[] args) {
   MyTask t1=new MyTask("thread 1");
   ...
   MyTask t4=new MyTask("thread 4");
   ExecutorService te=Executors.newFixedThreadPool(4);
   te.execute(t1);
   ...
   te.execute(t4);
   te.shutdown();
}

3. Concurrency

Threads allow multiple activities to proceed concurrently. Concurrent programming is the most logical outcome. In fact, the power of threads is grossly underestimated until you give it a vent through concurrency. Concurrent programming is much harder than single threaded programming because every mistake you commit here has a ripple effect. Also, failures can be very hard to reproduce. Additionally, programs and their design are also quite complicated. Even though you may not like it, you cannot evade the need of the hour. Processors are racing with multiple cores. To take advantage of these multi-core processors from the Java language point of view, concurrent programming is the only answer. There are lot of concepts involved with concurrent programming. I won’t go into much detail for now, let’s try an example of a Java concurrency lock.

public class Main implements Runnable {

   private Person person;
   private Lock lock = new ReentrantLock();

   public Main(Person person) {
      this.person = person;
   }

   public static void main(String[] args) {
      Person p1=new Person();
      Person p2=new Person();
      Main m1=new Main(p1);
      Main m2=new Main(p2);
      ExecutorService te=Executors.newFixedThreadPool(2);
      te.execute(m1);
      te.execute(m2);
      te.shutdown();
   }

   @Override
   public void run() {
      try {
         if (lock.tryLock(15, TimeUnit.SECONDS)) {
            person.printHello();
         }
      } catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         lock.unlock();
      }
      person.startLogging();

   }
}

4. Reflection

The core reflection features are from java.lang.reflect. Reflection provides the facility of programmatic access to loaded class information. That means, given a Class object, we can obtain information about the constructor, methods, and fields of the class represented by the Class instance. Remember, the drop-down list appears in your favorite IDE, providing information about the list of member functions and constructor whenever you type the dot(.) operator after any object. This feature is possible because the programmer who created the IDE has used the language’s reflection features. Also, reflection allows one class to use another, even though the latter class may not exist when the former class was compiled.

Method[] methods = MyTask.class.getMethods();
for (Method m : methods)
   System.out.println(m.getName());

Constructor[] con = MyTask.class.getConstructors();
for (Constructor c : con)
   System.out.println(c.getName());

5. Serialization

Serialization is a mechanism to encode an object as a bytestream. This bytestream then can be transmitted across different virtual machines or stored on a disk for later deserialization. Serialization is ideal for remote communication and object persistence. Serialization is like breaking an object into a stream of bytes. Deserializarion is its reverse; we can convert a stream of bytes into an object.

public class Name implements Serializable{
   public String firstName;
   public String lastName;

}

public class SerializeAndDeserializeDemo {
   public static void main(String[] args) throws ClassNotFoundException {
      Name name = new Name();
      name.firstName = "Ken";
      name.lastName = "Thompson";

      // Serializing name object as bytestream to a file
      try {
         FileOutputStream fout = new
         FileOutputStream("/home/temp/name");
         ObjectOutputStream objout = new ObjectOutputStream(fout);
         objout.writeObject(name);
         objout.close();
         fout.close();
      } catch (IOException ex) {
         ex.printStackTrace();
      }

      // Deserializing bytestream from a file to name object
      try {
         FileInputStream fin = new
         FileInputStream("/home/temp/name");
         ObjectInputStream objin = new ObjectInputStream(fin);
         Name n = (Name) objin.readObject();
         objin.close();
         fin.close();
         System.out.println("Name Deserialized: "
         +n.firstName+" "+n.lastName);
      } catch (IOException ex) {
         ex.printStackTrace();
      }
   }
}

6. Regular Expression

With a regular expression, we can define a set of strings with the help of a sequence of characters and symbols. Regular expressions are particularly helpful in validating input and ensuring that data is in a particular format.

public class Main {

   public static final String EMAILPATTERN = "^[_A-Za-z0-9-]+"
      + "(.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(."
      + "[A-Za-z0-9]+)*(.[A-Za-z]{2,})$";

   public static void main(String[] args) {
      String email1 = "www.developer.com";
      String email2 = "someone@somemail.com";

      System.out.println(validateEmail(email1));
      System.out.println(validateEmail(email2));

   }

   public static boolean validateEmail(String email) {
      return email.matches(EMAILPATTERN);
   }
}

7. Enumeration

Enumeration was introduced in Java version 5.0. It is a special kind of class defined by the keyword enum and a type name. In a comma-separated enumeration constant, each represents a unique value such as the seasons of the year, the planets in the solar system, or the suits in a deck of playing cards. Before enumeration was introduced, common patterns of constants were declared as a group of named int constants. Enumeration in Java is quite powerful and can be used as follows:

public enum Calculate {
   PLUS("+") {
      int operate(int x, int y) {
         return x + y;
      }
   },
   MINUS("-") {
      int operate(int x, int y) {
         return x - y;
      }
   };
   private final String op;
   Calculate(String op) {
      this.op = op;
   }
   abstract int operate(int x, int y);
}

public class Main {
   public static void main(String[] args) {
      for(Calculate p:Calculate.values()){
         System.out.println(p.operate(10, 20));
      }
   }

8. Anonymous Inner Class

The anonymous inner class is a special form of the inner class; it has no name and generally is defined within a method declaration. Typically, this class can access only the instance variables and methods of the top-level class object that declared it. It also can access only the final local variables of the method. These types of classes have some special uses. Let’s try an example to illustrate the idea where it may be used.

public class Main extends JFrame {
   private JComboBox<String> comboBox;
   private JLabel label;
   private String[] names={"Mercury", "Venus", "Earth",
      "Moon", "Mars", "Jupiter", "Saturn"};
   private String[] desc={"4222.6", "2802.0","24.0",
      "708.7","24.7","9.9","10.7"};

   public Main(){
      super("Inner Anonymous Class Demo");
      setLayout(new FlowLayout());
      comboBox=new JComboBox<>(names);

      // Itemlistener is the anonymous inner class

      comboBox.addItemListener(new ItemListener() {

         @Override
         public void itemStateChanged(ItemEvent e) {
            if(e.getStateChange()==ItemEvent.SELECTED)
               label.setText("Length of day is "
            +desc[comboBox.getSelectedIndex()]+" hrs.");
         }
      });
      add(comboBox);
      label=new JLabel();
      add(label);
   }

   public static void main(String[] args) {
      Main m=new Main();
      m.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      m.setSize(350, 150);
      m.setVisible(true);
   }
}

9. Garbage Collection

Every object we create uses various resources, such as memory. There should be some systematic way to give resources back to the system when they are no longer needed, to avoid resource leaks. It is the programmer’s responsibility in C/C++ to reclaim dynamically allocated memory explicitly. But, in Java, the JVM performs automatic garbage collection. In fact, a thread of the garbage collector runs simultaneously with any program run in JVM to reclaim memory that is no longer needed and mark any object for garbage collection when there are no more references to that object. That’s because every class in Java has the methods of the Object class and therefore contains the finalize method. This method is called by the garbage collector to perform termination housekeeping on an object just before the garbage collector reclaims the object’s memory. Rarely, such a situation occurs where you may need to call the finalize method explicitly. Also, it is discouraged to overload or use the finalize method. There is a problem, though; one cannot be sure when garbage collector will execute. In fact, it may never execute before a program terminates. Let’s try an example by explicitly calling the garbage collector to execute.

public class Person {
   private static int count=0;
   public Person(){
      count++;
   }
   protected void finalize(){
      count--;
   }

   public static int getCount(){
      return count;
   }
}

public class Main {

   public static void main(String[] args) {

      System.out.println(Person.getCount());
      Person p1=new Person();
      Person p2=new Person();
      Person p3=new Person();
      System.out.println(Person.getCount());

      p1=null;
      p2=null;
      System.out.println(Person.getCount());

      /* You may not get the result as expected
       * because the garbage collector may execute
       * after main has been terminated although
       * we have explicitly called gc() here.
       */
      System.gc();

      System.out.println(Person.getCount());

   }
}

10. Bit Manipulation

For programmers who stoop to the bits and bytes level, Java provides extensive bit manipulation capabilities. System-level communication with the operating system, hardware, or networking software often require direct bit manipulation techniques. Java provides seven bitwise operators to work on bits and bytes. They are: bitwise AND(&), bitwise inclusive OR (|), bitwise exclusive OR (^), left shift (<<), signed right shift (>>), unsigned right shift (>>>), and bitwise complement (~). Java provides a class called BitSet that makes bit manipulation easy (refer to javadoc for more info on BitSet). Here is an example to demonstrate the Sieve of Erastosthenes with with the help of a BitSet class.

public class Main {
   public static void main(String[] args) {
      int input=223;

      BitSet bitSet = new BitSet(1024);
      for (int i = 2; i < bitSet.size(); i++) {
         bitSet.set(i);
      }

      int erastos = (int) Math.sqrt(bitSet.size());
      for (int i = 2; i < erastos; i++) {
         if (bitSet.get(i)) {
            for (int j = 2 * i; j < bitSet.size(); j += i) {
               bitSet.clear(j);
            }
         }
      }
      int c = 0;
      for (int i = 2; i < bitSet.size(); i++) {
         if (bitSet.get(i)) {
            System.out.print(String.valueOf(i));
            System.out.print(++c % 10 == 0 ? "n" : "t");
         }
      }
      if(bitSet.get(input))
         System.out.println("n"+input+" is a prime number");
      else
         System.out.println("n"+input+" is not a prime number");
   }
}

Conclusion

While enroute to some of these alleys of Java, I realized there are many things that need to be elaborated and many other concepts to touch upon. These are basic concepts any Java programmer must have experienced. Java has some amazing features deep within its rabbit hole. It surprises me every time I explore them. If you think there are more points to cover, do not forget to comment. I’ll keep writing.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories