MIJava


Welcome to the home of MIJava.


Introduction

MIJava provides multiple inheritance in Java. The Java language itself does not provide multiple inheritance for classes, and for valid reasons. (See: No More Multiple Inheritance)

Instead of using multiple inheritance in classes, it is recommended that developers use a combination of interfaces and delegation to solve the problem. For example, to mimic the behavior of a Sub class defined like this:

// This IS NOT valid Java
public class Sub extends Super1, Super2 {
  // This Sub class autmatically inherits both 
  // Super1.printName() and Super2.getName()
}
class Super1 {
  public void printName() {
    System.out.println("Super1");
  }
}
class Super2 {
  public String getName() {
    return "Super2";
  }
}
You should use the following code:

// This IS valid Java
public interface Sub extends Super1, Super2 {
}
interface Super1 {
  public void printName();
}
interface Super2 {
  public void printHello();
}
public class SubI implements Sub {
  private Super1 super1 = new Super1I();
  private Super2 super2 = new Super2I();
  public void printName() {
    super1.printName();
  }
  public String getName() {
    return super2.getName();
  }
}
class Super1I implements Super1 {
  public void printName() {
    System.out.println("Super1");
  }
}
class Super2I implments Super2 {
  public void printHello() {
    System.out.println("Hello");
  }
}

This works okay for a small amount of inheritance, but quickly becomes tedious and error prone as the number of inherited methods and classes increases. (And some might argue that this kind of code generates more problems than multiple inheritance would.) This is where MIJava comes in. MIJava is a preprocessor which takes source code that is structured like the former example and emits source code that is structured like the latter example. The MIJava application works under the [perhaps generous] assumption that a programmatic process will make fewer mistakes than a developer would.


Getting MIJava

Currently, the MIJava application is in beta. To download a copy, click here. Please try it out and provide feedback on the project page. (The source code for the project is available on the project page as well.)

If you are running in a Unix environment, you can extract a script from the .jar file, which you then invoke to compile miclasses:


     $ jar xf MIJava.jar mijava
     $ chmod +x mijava

Using MIJava

First, you need to create a miclass. A miclass is just like a standard Java class, except that it can extend more than one class. (More specifically, it can extend more than one miclass. See Restrictions and Limitations below.) Example:

// This is valid MIJava
public miclass Sub extends Super1, Super2 {
  // This Sub miclass autmatically inherits both 
  // Super1.printName() and Super2.getName()
}
miclass Super1 {
  public void printName() {
    System.out.println("Super1");
  }
}
miclass Super2 {
  public String getName() {
    return "Super2";
  }
}
The standard convention is to put a miclass definition in a file named .mijava. For example, the Sub miclass defined above would be saved to a file named Sub.mijava. However, this isn't strictly necessary.

Next, you need to compile your miclass. If you are running in a Unix environment, you can simply invoke the mijava script just as you would execute the javac compiler:

     $ mijava <args>
If you are running on Windows, or some other environment, you would invoke the MIJava compiler like this:
     > java -jar MIJava.jar <args>
Where <args> are the same arguments that you would pass to the javac compiler.


Restrictions and Limitations

There are a few restrictions to what exactly can be done with MIJava. Many of these "restrictions" are simply features that are intentionally left out of the 1.0 release criteria for MIJava, since they are issues that do not cause great problems for most users, and would be extra difficult to resolve. They will be added in to later releases of MIJava, based on popular demand.

· All miclass fields must be private.
Any fields that are declared within a miclass must be declared "private". This is due to the fact that other classes will be accessing the miclass via an interface, and interfaces do not allow fields. (See Technical Details below.)
· Must use the Sun Java 2 SDK version 1.4.
This application was developed using the Java 1.4 SDK, which provides a regular expression package, among other things. It also depends on the Sun javac compiler to compile the miclasses after they have been transformed.
· You cannot have inner classes with miclasses.
A miclass may not contain any inner classes, nor may a standard class contain an inner miclass.
· An @argfiles parameter may not be passed to the mijava script.
Does anyone actually use the @argfiles parameter?
· A miclass may only extend other miclasses.
All classes that are extended by a miclass must also be miclasses. It is not possible to extend a standard class with a miclass.
· A miclass may not be declared "abstract".
This is because an abstract class may not be directly instantiated, which causes problems. (See Technical Details below.)
· The javac compiler must be located somewhere within the $PATH.
The javac compiler must be located in one of the directories that is listed in the $PATH environment variable. MIJava will not be able to compile the miclasses unless it is able to find the javac compiler.
· You may not use a fully qualified name in order to reference a miclass type.
For example, you may not use something like "net.sourceforge.miclass.SomeMIClass someObject = null;". Instead, you would have to "import net.sourceforge.miclass.SomeMIClass;" and then have "SomeMIClass someObject = null;".

Release Criteria

Prior to releasing the version 1.0 of MIJava, the following items must be addressed:

Future versions of MIJava will likely add support for other Java compilers and eliminate some of the restrictions mentioned above. These improvements will be made according to popular demand.


Technical Details

In order to illustrate the transformations that are performed by MIJava, lets take a look at some expanded example miclasses:

public miclass Sub extends Super1, Super2 {
  // Overrides Super1.getSecret()
  public int getSecret() {
    return 5;
  }
  // Inherits Super1.addSecret(), 
  // Super1.printSecret() and Super2.getName()
}
miclass Super1 {
  public int getSecret() {
    return 3;
  }
  public int addSecret(int x) {
    return x + getSecret();
  }
  public void printSecret() {
    System.out.println(getSecret());
  }
}
miclass Super2 {
  public String getName() {
    return "Super2";
  }
}

A straightforward transformation of this code, like that shown in the above introduction, would result in this:

public interface Sub extends Super1, Super2 {
  public int getSecret();
}
interface Super1 {
  public int getSecret();
  public int addSecret(int x);
  public void printSecret();
}
interface Super2 {
  public String getName();
}
public class SubI implements Sub {
  private Super1 super1 = new Super1I();
  private Super2 super2 = new Super2I();
  public int getSecret() {
    return 5;
  }
  public int addSecret(int x) {
    return super1.addSecret(x);
  }
  public void printSecret() {
    super1.printSecret();
  }
  public String getName() {
    return super2.getName();
  }
}
class Super1I implements Super1 {
  public int getSecret() {
    return 3;
  }
  public int addSecret(int x) {
    return x + getSecret();
  }
  public void printSecret() {
    System.out.println(getSecret());
  }
}
class Super2I implement Super2 {
  public String getName() {
    return "Super2";
  }
}

However, there are a few problems with that transformation. One problem is in the output of this code:

Sub sub = new SubI();
int added = sub.addToSecret(5);
System.out.print(added);
System.out.print(" ");
sub.printSecret();
This code prints the string "8 3". The problem is that according to the definition of the Sub miclass, we would have expected the code to print the string "10 5". The problem is that the SubI class correctly delegates the addToSecret() call to Super1I, but then the Super1I class invokes its own version of getSecret(), not SubI.getSecret(). This is a break from the polymorphism that we would have expected. The same problem arises in the call to printSecret(). This problem is resolved by having Super1I and Super2I maintain a reference to any subclass that is delegating its calls.

Another small problem is that it isn't safe to simply tack an "I" to the end of a class name, willy-nilly, and expect it to work out okay. There might be another class defined in the package that is named "SubI", which would cause a name conflict. So instead, we will prepend a "$" to the class name, which should work better, since the '$' character is reserved for use in mechanically generated source code, which is what we have here.

So after correcting these problems, and applying the same systematic changes to Sub, Super1, and Super2, we come out with this transformation:

public interface Sub extends Super1, Super2 {
  public int getSecret();
}
interface Super1 {
  public int getSecret();
  public int addSecret(int x);
  public void printSecret();
}
interface Super2 {
  public String getName();
}
public class $Sub implements Sub {
  private $Super1 $super1;
  private $Super2 $super2;
  private Sub $delegator = null;
  public $Sub() {
    $super1 = new $Super1();
    $super1.set$delegator(this);
    $super2 = new $Super2();
    $super2.set$delegator(this);
  }
  public set$delegator(Sub sub) {
    $delegator = sub;
    $super1.set$delegator(sub);
    $super2.set$delegator(sub);
  }
  public int getSecret() {
    if ($delegator == null)
      return $getSecret();
    else
      return $delegator.getSecret();
  }
  public int $getSecret() {
    return 5;
  }
  public int addSecret(int x) {
    if ($delegator == null)
      return $super1.$addSecret(x);
    else
      return $delegator.addSecret(x);
  }
  public void printSecret() {
    if ($delegator == null)
      $super1.$printSecret();
    else
      $delegator.printSecret();
  }
  public String getName() {
    if ($delegator == null)
      return $super2.$getName();
    else
      return $delegator.getName();
  }
}
class $Super1 implements $Super1 {
  private Super1 $delegator = null;
  public $Super1() {
  }
  public set$delegator(Super1 super1) {
    $delegator = super1;
  }
  public int getSecret() {
    if ($delegator == null)
      return $getSecret();
    else
      return $delegator.getSecret();
  }
  public int $getSecret() {
    return 3;
  }
  public int addSecret(int x) {
    if ($delegator == null)
      return $addSecret(x);
    else
      return $delegator.addSecret(x);
  }
  public int $addSecret(int x) {
    return x + getSecret();
  }
  public void printSecret() {
    if ($delegator == null)
      $printSecret();
    else
      $delegator.printSecret();
  }
  public void $printSecret() {
    System.out.println(getSecret());
  }
}
class $Super2 implements Super2 {
  private Super2 $delegator = null;
  public $Super2() {
  }
  public set$delegator(Super2 super2) {
    $delegator = super2;
  }
  public String getName() {
    if ($delegator == null)
      return $getName();
    else
      return $delegator.getName();
  }
  public String $getName() {
    return "Super2";
  }
}

This code would produce the correct "10 5" output for test above. The main point of difference in this code is that the $Super1.getSecret() method first checks to see if there is a $delegator subclass. If there is a subclass, then $Super1.getSecret() calls that class's version of getSecret() rather than its own.

This code is quite a bit more convoluted, but lets trace a call to $Sub.addSecret(5) to see how it works. First, $Sub.addSecret() checks to see if $delegator == null. We'll assume for this example that this instance of $Sub does not have a subclass that is delegating method calls to it, so the "if" is true and we then call $Super1.$addSecret(5). $Super1.$addSecret() calls getSecret(), which is actually $Super1.getSecret(). $Super1.getSecret() first checks to see if $delegator is set. In this case, $delegator is set (to $Sub), so it then calls the getSecret() of the $delegator, which is $Sub.getSecret(). Since $Sub does not have a delegator, $Sub.getSecret() calls $Sub.$getSecret(), which returns 5.


SourceForge Logo































































Copyright © 2002 Michael Hobbs
$Id: MIJava.html,v 1.7 2002/04/08 16:56:50 hobb0001 Exp $