views:

135

answers:

5

Hi, I have a Java class B with an inner class C. Some methods of B accept an instance of C as parameter but I only want to accept C instances created by the proper instance of B. Is there a way to do this validation at compile time?


Example

C c1 = new C();
B foo = c1.getB(); // foo was created by instance c1
C c2 = new C();
c2.method(foo); // I want a compiler error here.


My case

Have a class names Map which hold a matrix of instances of the inner class MapArea. The nice thing about this scheme is that I can validate the xPos, and yPos fields at the constructor so no invalid Areas for a given map are built. The map as a method distanceFrom(MapArea startingPos, MapArea toLocation, MapArea... otherLocations) and I was trying to avoid to validate the map area arguments again.

A: 

If you make the constructor of the inner class (C) private, I believe the enclosing class (B) can still instantiate it while other classes cannot. This ensures that only B and C can instantiate C.

Edit: I've verified that with a small mockup. Make the inner class constructor private, and then only the inner class (C) or the enclosing class (B) can instantiate it.

See http://tns-www.lcs.mit.edu/manuals/java-1.1.1/guide/innerclasses/spec/innerclasses.doc6.html for more. In particular: "Access protection never prevents a class from using any member of another class, as long as one encloses the other, or they are enclosed by a third class.".

Andrew
I already have a private constructor, you might want to check the example I added.
Pedro Daniel
+2  A: 

There's no way (AFAIK) of doing this at compile time.

At runtime you can do it by having the outer instance's factory method pass a reference to itself to the inner instance's constructor.

The inner class would need to store that reference, such that the outer class can check whether it created that instance or not:

public class C {

    public class B {
        private C parent;
        private B(C parent) {
            this.parent = parent;
        }
        public C getParent() {
            return parent;
        }
    }

    public B getB() {
        return new B(this);
    }

    public void method(B b) {
        assert(this == b.getParent());
    }
}

Actually, as Kip's concurrent answer shows, B can access C.this to get the parent object so there's no need to store the parent reference. However the method above would be necessary if C wasn't actually an inner class.

Alnitak
A: 

A compile error won't work, but you can at least throw an exception:

public class C
{
  public static void main (String [] args)
  {
    C c1 = new C();
    B b = c1.getB();
    c1.useB(b); //OK
    C c2 = new C();
    c2.useB(b); //throws IllegalArgumentException
  }

  public B getB() { return new B(); }

  public void useB(B b) {
    if(b.getC() != this)
      throw new IllegalArgumentException();
    //...
  }

  private class B
  {
    public C getC() { return C.this; }
    //...
  }
}
Kip
A: 

There's no compile-time way to guard against instance-specific usage. Your best bet is probably throwing an Exception when the usage in incorrect. Another option you have is to have the parent class to have a Map of instances of the inner class, and to have other classes tell the outer class to operate on the inner class not by the instance but by some other references. This will work with other classes don't need to do anything directly with the inner class.

Eddie
+2  A: 

If this is really the behavior you want, method() should really be defined in the inner class.

In other words, instead of:

public class C {
  //...
  public void method(B b) {
    this.x = b.y;
    //...
  }
  //...
  public class B {
    //...
  }
  //...
}

It should be:

public class C {
  //...
  public class B {
    //...
    public void method() {
      C c = this.C;
      c.x = this.y;
      //...
    }
  //...
  }
  //...
}

Of course, this wouldn't solve the problem if, for example, you wanted public void method(B b1, B b2, B b3), where all three instances of B are enclosed by the same instance of C.

Kip
+1. This is the best answer I've seen so far to this. If the method is on the inner class, then it is not possible to invoke the wrong outer class.
Eddie