Java Scoped Access or Access Scoping
Jakob Jenkov |
Scoped access, or access scoping, is a technique that is similar to method chaining, meaning it enables you to express more functionality with less code. However, scoped access looks a bit different and does not have the same limitations as method chaining.
If you prefer video, I have a video version of this scoped access tutorial, here:
Let's start with the same code example as in the tutorial about method chaining:
public class Node {
protected List<Node< children = new ArrayList<Node>();
public void addChild(Node child) {
children.add(child);
}
}
Node root = new Node(); Node child1 = new Node(); Node child2 = new Node(); Node child3 = new Node(); root.addChild(child1); root.addChild(child2); root.addChild(child3);
The example above does not use neither method chaining nor scoped access - but it makes it clear how scoped access could express the same functionality, somewhat simpler.
The Access Scope Method
Since access scoping is not built into the Java language at this time, we need to create a method to support it. I will call that method "as()" - which is both short for "access scope", and sounds a bit like the "with" operator that some programming languages have (but not Java).
public class AccessScope {
public static <T> T as(T target, Consumer<T> accessor) {
block.apply(target);
return target;
}
}
The as() method takes two parameters. The first parameter is the object to be accessed using
scoped access, or access scoping.
The second parameter is a Consumer interface implementation. Inside the consume() method
of this Consumer implementation, we will access the target object.
Here is an example of using the as() method:
Node root = AccessScope.as( new Node(), n -> {
n.addChild( new Node() );
n.addChild( new Node() );
n.addChild( new Node() );
} );
The difference of this example from the first example is not big. After all, we could have reduced the first example by 3 lines, if we had written it like this:
Node root = new Node(); root.addChild(new Node()); root.addChild(new Node()); root.addChild(new Node()););
The difference becomes more visible when we want to add children to the children too. Here is how that would look without access scoping:
Node root = new Node(); Node child1 = new Node(); Node child2 = new Node(); Node child3 = new Node(); root.addChild(child1); root.addChild(child2); root.addChild(child3); child1.addChild(new Node()); child2.addChild(new Node()); child3.addChild(new Node());
Here is how the same code would look using scoped access:
import static x.y.z.AccessScope.as;
...
Node root = as( new Node(), n -> {
n.addChild( as( new Node(), n2 -> {
n2.addChild( new Node() );
} ) );
n.addChild( as( new Node(), n2 -> {
n2.addChild( new Node() );
} ) );
n.addChild( as( new Node(), n2 -> {
n2.addChild( new Node() );
} ) );
} );
The observant reader will notice that the code example that does not use scoped access only has 10 non-blank code lines. The code example that is using scoped access has 11 non-blank code lines. However, 4 of these lines only consists of " } ); " .
One advantage the scoped access construct has is, that the hierarchical structure of the object graph is reflected in the code. The first example does not reflect the hierarchical structure of the object graph it creates.
You could also abbreviate the scoped access code example a bit, like this:
Node root = new Node();
root.addChild( as( new Node(), n2 -> {
n2.addChild( new Node() );
} ) );
root.addChild( as( new Node(), n2 -> {
n2.addChild( new Node() );
} ) );
root.addChild( as( new Node(), n2 -> {
n2.addChild( new Node() );
} ) );
Now this example is down to 10 lines too, while still reflecting the hierarchical structure of the object graph being created.
Scoped Access vs. Method Chaining
Access scoping is a bit more verbose than method chaining but does not have the same limitations as method chaining. Method chaining lacks up-reach and self-reach outside the call chain. Scoped access does not have this limitation.
Let's first see the previous example using method chaining. Here is first the Node class, changed to support method chaining:
public class Node {
protected List<Node< children = new ArrayList<Node>();
public Node addChild(Node child) {
children.add(child);
return this;
}
}
Now let's see the earlier object graph building example using method chaining:
Node root = new Node()
.addChild( new Node()
.addChild( new Node() )
)
.addChild( new Node()
.addChild( new Node() )
)
.addChild( new Node()
.addChild( new Node() )
) ;
Imagine we expand the requirements that nodes also need a reference up to their parent. First, we change the Node class to support this:
public class Node {
protected Node parent = null;
protected List<Node< children = new ArrayList<Node>();
public Node setParent(Node parent) {
this.parent = parent;
return this;
}
public Node addChild(Node child) {
children.add(child);
return this;
}
}
All of a sudden, method chaining is no longer feasible because the nodes created and added in the example using method chaining have no way to reference its parent at the time the Node instance is created. We would pretty much have to create one node at a time, set the parent on it, and then add its children.
This is not a problem with scoped access. Here is how the expanded requirements would look using access scoping:
Node root = new Node();
root.addChild( as( new Node(), n2 -> {
n2.addChild( as( new Node(), n3 -> {
n3.setParent( n2 );
} );
n2.setParent(root);
} ) );
root.addChild( as( new Node(), n2 -> {
n2.addChild( as( new Node(), n3 -> {
n3.setParent( n2 );
} );
n2.setParent(root);
} ) );
root.addChild( as( new Node(), n2 -> {
n2.addChild( as( new Node(), n3 -> {
n3.setParent( n2 );
} );
n2.setParent(root);
} ) );
Using scoped access, a Node could also reach itself off the call chain. Here is an example:
Node root = new Node();
root.addChild( as( new Node(), n2 -> {
// Node can actually reference itself inside call to setParent()
n2.setParent( n2 );
} );
A Scoped Access Syntax Suggestion
It would be nice if we had built-in syntax support for scoped access in Java. Here is a scoped access syntax suggestion:
Node node = new Node():{
.addChild( new Node():{
.addChild( new Node() );
}
}
It is the :{ ... } syntax that marks the scoped access block.
A scoped access block is an expression that is evaluated to the object before the : that starts the block.
| Tweet | |
Jakob Jenkov | |











