0

In Java, I can write something like this:

public class Brother {
   private final Parent parent;
   
   public Brother(Parent parent) {
      this.parent = parent;
   }

   public void annoySister() {
       parent.getSister().annoy();
   }

}

public class Sister {
   private final Parent;
   
   public Sister(Parent parent) {
      this.parent = parent;
   }

   public void annoy() {
      System.out.println("I am annoyed");
   }

   public void parentsName() {
      parent.getName();
   }
}
    
public class Parent {
   private final Brother brother;
   private final Sister sister;
   private final String name;

   public Parent() {
      this.name = "Johnny";
      this.brother = new Brother(this);
      this.sister = new Sister(this);
   }
   // getters
}

This way all the objects created in Parent's class are available to each other. For example, in the code above, the Brother can access Sister methods and vice versa in addition to methods provided by Parent class (essentially accessing its state).

public class Main {
    
    public static void main(String args) {
       // somehow this seems to be like a container for all objects; ApplicationContext?
       Parent parentInstance = new Parent();

       parentInstance.getBrother().annoySister();
       parentInstance.getSister().parentsName();
    }
}

Q. How can this be passed to other classes, when Parent is being created as its constructor is being executed?

8
  • 3
    Be careful leaking this in the constructor, see stackoverflow.com/questions/9851813/… and all related questions. Commented Sep 14 at 18:15
  • Q1 - well - I'm afraid we'd have to ask a spec designer, probbably of 1.0, why. The JVM will let you - not just to your own subclass but to pretty much anything you can call. Even if the JMM reas its ugly evil coin, all fields will have (one of) the valid values at the time, which - if you haven't written that field yet - is null/0/0.0/false Commented Sep 14 at 18:18
  • 3
    "How can this be passed to sub-classes ..." - 1) please note there is no sub-class in posted code, Parent, Brother and Sister are only subclasses of Object, but neither is a subclass of the others; 2) also be aware that the constructor, despite its name, is not really constructing this, it is only initializing the instance - this is created by the new statement before executing the constructor (basically a pointer/reference to some memory location) Commented Sep 14 at 18:19
  • @user85421 Thanks, I fixed the post after your comment. this is created by the new statement, then how this reference is present when new Brother(this) is being executed in the constructor which is not complete yet to provide this to the newstatement. Commented Sep 14 at 18:33
  • 1
    Please edit your post to focus on only one precise question. You are currently asking two questions. Commented Sep 14 at 18:49

1 Answer 1

1

How can this be passed to other classes, when Parent is being created as it's constructor is being executed?

The execution of new X(), in pseudocode:

void createInstance {
  int totalSize = SIZE_OF_TYPE_REF + SIZE_OF_IDENTITY;
  for (Field f : type.allInstanceFieldsOfThisAndSuperTypes()) {
    totalSize += f.sizeInBytes();
  }
  ptr = malloc(totalSize);
  ptr[0-7] = type.ref;
  ptr[8-15] = randomId();
  invokeConstructor(makeObjectOutOf(ptr));
}

But this takes some liberties; the spec is not nearly as specific as that.

The point is - the answer to your question is rather simple: The JVM makes the object first and then treats the constructor as a method that gets to toy with it first. At the JVM level, it's incorrect to think that the end result of a constructor is the creation of a single instance. No, thats how they start. The end result of a constructor is that the instance already created when it started is now in a valid state.

Yes, the logical conclusion you seem to sort of draw (given the somewhat incredulous tone of your question) is indeed the bizarre but correct conclusion:

Instances exist as of the start of the constructor but aren't actually semantically guaranteed in valid state until the end; nevertheless they exist and this can be passed around. Invalid state and all. It is entirely up to the programmer to take this foot gun and handle it with great care.

This is fairly well known. Various linting tools will outright tell you that you are 'leaking this', and tell you to cut it out. The JVM spec itself is starting to have an opinion, scans for leaking your this, and has to downshift to less efficient paths, in order to fulfill certain guarantees the Java Memory Model makes about the visibility of updates to final fields.

We can trivially take this footgun and deal some serious damage, your example is in that sense barely cracking the top of this particular iceberg, so to speak:

class Person {
  private final String name;

  Person(String name) {
    Greeter.greet(this);
    this.name = name;
  }

  public String getName() { return this.name; }
}

class Greeter {
  static void greet(Person p) {
    System.out.println("Hello, " + p.getName() + "!");
  }

  public static void main(String[] args) {
    Person p = new Person("Aizaz");
    Greeter.greet(p);
  }
}

Toss that in a file, compile it, and then run java Greeter and out comes:

Hello, null!
Hello, Aizaz!

What the heck? We have a final field and only one instance, thus we could only possibly observe that field having one value. Except.. we see two of em. a null and a value. When compiling we got no warnings at all!

That's the foot gun. It went off.

Fortunately it's easy to know when this happens: Anytime that you leak 'this'. This is done either if you refer to this in a constructor which you should not do except for this.fieldName = shadowedFieldName;), or if you invoke an instance method in your constructor which you should never do. If you want your constructor to have helpers, make em static, which avoids this messy situation. As mentioned, various linter tools can help you out and will flag any such code with a warning, because both of these situations are things that basic rules-based code analysis can trivially find with a 100% accuracy rate (no false positives and no false negatives).

Q2 Is this some kind of design pattern?

Sure, well, we'd have to get into philosophy. What is a 'design pattern'?

It's somewhat common. It's also a bad idea. You should not write your code like this, because of the footgun nature of it. It's easy enough to spot this leakage in a constructor. It's not at all easy to intentionally do so, put the onus on whatever you leak the this to not to touch the foot gun aspects of it, and then attempt to put rules into place such as with linter tools to police that. Generally you use the linter tool to lint the easy, 100% accurately lintable thing and deal with the fallout.

But this gets to the underlying problem

The abstract structure that you have created here is the doubly linked tree structure. You have nodes that have refs to their children, and you want the children to have refs back to their parents.

Whilst this is a commonly cooked up solution to various designs, you should reconsider. For example, javac itself does not do this; its parser turns java source files into tree structures (CompilationUnit, which contains TypeDefinition objects, those contain TypeMember objects (which can be FieldDeclaration, MethodDeclaration, ConstructorDeclaration, and TypeDefinitions, amongst other things), those contain Parameter objects and a Body which in turns contains a bunch of Statement objects - hopefully it is not difficult to envision how source files are turned into complicated tree structures.

In the javac source code, nodes do not have a ref to their parents.

Whatever job requires you to 'travel' "up" the tree, find another way to do it. For example, invert the control structure. Alternatively, have a separate object that represents the entire tree as a whole, and it has a ref to the root node. It can be asked to travel up (so a child instance does not know its parent, but you can ask the tree as a whole 'give me the parent of child child) and it does know.

Alternatively, have the concept of parent be a settable thing. It's not a final field, and post construction a child starts off as parent-less. This means your object goes through multiple phases of construction; after phase 1 different rules hold than after phase 2 (phase 1 is the constructor, phase 2 is the thing where the parent 'marks' itself as parent of that object). You can attempt to then document these lifecycle processes and in that way attempt to ensure (with little help from the compiler) that e.g. child objects are never leaked to beyond the package until they hit some final phase. If that's not possible you can even make 'which phase are we in' part of the public API - have the class def have a 'getState()' method that returns an enum representing where we're at, and then document that various methods cannot be called unless certain states have been reached.

This is not a particularly nice design and runs afoul of various philosophical treatises on how OO 'should be', but in the end, how it 'should be' is scientifically speaking mostly drivel: IT has very little rigorous scientifically tested hypotheses, it's truly the weakest of all the 'sciences'. If it works, it works. If it works but the maintenance headache seems unduly ridiculous given the scope of the project and teaching new team members the ropes takes ages, and your tests are convoluted messes - that might be a sign your design is insufficient. I'd put more stock in that than abstract philosophical 'rules' such as Liskov's substitution principle or "SOLID design" ^1.

Point is, 'how to represent doubly-linked tree structures in OO / java' is a complicated question that is beyond the scope of SO for a complete treatise; it's more the domain of 40-page papers and veers far too much into opinion to actually 'answer'; hence I have merely provided the context.


[1] Dear reader, you may be thinking: What heresy is this?? Liskov and SOLID are fantastic; in some distant past I did not apply them properly and my code was a mess, since I learned, I apply it rigorously and my code is much easier to maintain! Ah, but therein lies the rub: You, dear reader, have experience. From my experience, attempting to apply these principles when you have no idea what they truly mean due to lack of experience - that's just a formula for truly brain-twisting lunacy. Cargo culting writ large. So please do not read this paragraph a 'Liskov and SOLID are lies'. It's more 'Liskov and SOLID are not useful tools to teach. They are useful tools only to describe and neatly file away the lessons you already learned the hard way'.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for the detailed explanation, I really appreciate it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.