What Operations can be performed on a Reference in Java?

In Java it’s important to know what references are and how they are used. So it’s important to know what operations can be performed on references.

The Question

To make the question clearer:

  • Operation: anything that the language offers (operators, statements etc)
  • Reference: Any expression that has a reference type (not primitive)

Some operations use reference type expressions, but only allow certain types.

I think this is helpful if you study how Java really works and how references are different from primitives.

Note that I write this in 2015 and we don’t have value types yet. A future version of Java might introduce them. When type values are introduced I’ll update this post (if I don’t forget).

The Answer

I use foo like a variable name and it always represents a reference type expression. So foo could be replaced with anything that is a reference.
For example a String literal: "foo"
or a method call: bla.toString()

Operations on all Reference Types

Some of them depend on the context of code. In Java all types are checked (except some generic type information). But these operations are not restricted by anything other than regular type checking. If the type is Object, then everything goes.

  • The “.” Operator (member access):

    foo.member;
    foo.method();
    This includes .new, if foo is an outer object:
    foo.new NestedClass()
    And you can call static members:
    foo.staticMethod()

  • The “::” Operator (method reference):

    Supplier s = foo::getBar;

  • The “=” Operator (assignment)

    Foo foo = new Foo();
    Foo foo2 = foo;

  • The “+” Operator (concatenation):

    String s = "foo: " + foo;
    This really uses foo.toString(). So it implicitly uses the dot operator. And it only works if at least one expression is a String.

  • The “==”/”!=” Operators (equality):

    if (foo == null) { ... }
    if (foo != null) { ... }

  • Type Casting:

    (Bar) foo
    This just tells the compiler that foo is expected to be an instance of Bar, but doesn’t really do anything at runtime. You still get an exception if foo is not an instance of Bar.

  • Type Comparison Operator:

    if (foo instanceof Bar) { ... }
    Update: Since Java 19 we have record patterns:
    if (foo instanceof Pair(var left, var right)) { ... }
    left
    and right are then two new references.

  • Concurrency:

    synchronize(foo) { ... }

  • Construction:

    This doesn’t do anything with a reference, but it creates one.
    new Foo()

  • Pass-By-Value:

    xyz.method(foo);
    new Bla(foo);

  • Return Statement:

    return foo;

Operations on certain Reference Types

The restrictions are defined by the Java Language Specification.

  • Array Access Expression (array types):

    foo[i]

  • Array Initializer and Creation Expression (array types):

    Like constructor calls, they create a new object and therefore a reference:
    Object[] a1 = { f, o, o };
    Foo[] a2 = new Foo[42];

  • Lambda (functional interface):

    The lambda itself is an expression. So this creates a new reference to a single abstract method type:
    (param) -> param.bla()
    An even simpler example is this, which could be used as a Runnable, that doesn’t do anything:
    () -> {}

  • String Literals (String):

    They also create a reference to the cached String.
    "foo"

  • Auto Unboxing (only if wrapper type):

    int i = foo;
    foo > 8
    foo++
    etc.

  • Switch Statement (Enums and String):

    switch(foo) { ... }
    Update: Since Java 12 we have “Enhanced Switch Expressions”. And they kept improving on the new ways of using switch. But it still takes one value (possibly a reference and maybe even null).

  • Case (String or enum type):

    “bla” is of reference type (String):
    case "bla": ...
    the enum constant FOO is of ref. type:
    case MyEnum.FOO:
    Update: Since Java 12 we have “Enhanced Switch Expressions”. But that doesn’t really add more operations. There are just more things you can do in a “case”, such as case String s && s.length()>3 -> ... and  case null -> ...

  • For-Each (Iterable):

    for(Type e : foo) { ... }

  • Try-With-Resource (AutoCloseable):

    The expression must be a variable declaration with initialization.
    try(Foo foo = new Foo()) { ... }

  • Throwing (Throwable)

    This also depends on the throws clause.
    throw foo;

  • Catching (Throwable):

    This also depends on the throws clauses of called methods.
    try { ... } catch (Foo foo) { ... }

Miscellaneous

  • this / super Keywords

    In Java you can use this and super for different things. They are references (unless used for constructor chaining) and behave like variables/parameters. You can even add this as a parameter so you can add annotations to it (the so called receiver parameter).

  • class Keyword

    This is used like a static field of each class to get a reference to that class. Use Foo.class to get the same as foo.getClass() but without needing a reference to an instance of Foo. This even works on primitives (int.class).

One thought on “What Operations can be performed on a Reference in Java?”

Leave a Reply

Your email address will not be published. Required fields are marked *