Odd method call in java using a dot operator to ac

2019-01-08 18:36发布

问题:

I came across some advanced java code (advanced for me :) ) I need help understanding.

In a class there is a nested class as below:

private final class CoverageCRUDaoCallable implements
        Callable<List<ClientCoverageCRU>>
{
    private final long oid;
    private final long sourceContextId;

    private CoverageCRUDaoCallable(long oid, long sourceContextId)
    {
        this.oid = oid;
        this.sourceContextId = sourceContextId;
    }

    @Override
    public List<ClientCoverageCRU> call() throws Exception
    {
        return coverageCRUDao.getCoverageCRUData(oid, sourceContextId);
    }
}

Later in the outer class, there is an instance of the callable class being created. I have no idea what this is:

ConnectionHelper.<List<ClientCoverageCRU>> tryExecute(coverageCRUDaoCallable);

It doesn't look like java syntax to me. Could you please elaborate what's going on in this cryptic syntax? You can see it being used below in the code excerpt.

CoverageCRUDaoCallable coverageCRUDaoCallable = new CoverageCRUDaoCallable(
        dalClient.getOid(), sourceContextId);

// use Connection helper to make coverageCRUDao call.
List<ClientCoverageCRU> coverageCRUList = ConnectionHelper
        .<List<ClientCoverageCRU>> tryExecute(coverageCRUDaoCallable);

EDITED added the ConnectionHelper class.

public class ConnectionHelper<T>
{
    private static final Logger logger =
        LoggerFactory.getLogger(ConnectionHelper.class);

    private static final int    CONNECTION_RETRIES = 3;

    private static final int    MIN_TIMEOUT        = 100;

    public static <T> T tryExecute(Callable<T> command)
    { 
        T returnValue = null;
        long delay = 0;

        for (int retry = 0; retry < CONNECTION_RETRIES; retry++)
        { 
            try
            { 
                // Sleep before retry
                Thread.sleep(delay);

                if (retry != 0)
                {
                    logger.info("Connection retry #"+ retry);
                }

                // make the actual connection call
                returnValue = command.call();
                break;

            } 
            catch (Exception e)
            {
                Throwable cause = e.getCause();
                if (retry == CONNECTION_RETRIES - 1)
                {
                    logger.info("Connection retries have exhausted. Not trying "                        
                            + "to connect any more.");

                    throw new RuntimeException(cause);
                }

                // Delay increased exponentially with every retry.
                delay = (long) (MIN_TIMEOUT * Math.pow(2, retry));

                String origCause = ExceptionUtils.getRootCauseMessage(e);

                logger.info("Connection retry #" + (retry + 1)
                        + " scheduled in " + delay + " msec due to "
                        + origCause);
                        + origCause);
            }
        }
        return returnValue;
    }

回答1:

You more often think of classes as being generic, but methods can be generic too. A common example is Arrays.asList.

Most of the time, you don't have to use the syntax with angle brackets <...>, even when you're invoking a generic method, because this is the one place in which the Java compiler is actually capable of doing basic type inference in some circumstances. For example, the snippet given in the Arrays.asList documentation omits the type:

List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");

But it's equivalent to this version in which the generic type is given explicitly:

List<String> stooges = Arrays.<String>asList("Larry", "Moe", "Curly");


回答2:

That is because, until Java 7, generics do not fully support target typing, so you need to help the compiler a little with what is called a type witness like in ConnectionHelper.<List<ClientCoverageCRU>>.

Note however that Java 8 significantly improves target typing and in your specific example the type witness is not required in Java 8.



回答3:

It's ugly, but valid.

Whatever ConnectionHelper is, it has a static tryExecute method that needs to infer a generic type.

Something like:

public static <T> T tryExecute() { ... }

Edit from updated Question: Java has type inference for generic types. The first <T> in the method signature signifies the type will be inferred when the method is called.

In your updated post you show tryExecute() defined to take a generic argument:

public static <T> T tryExecute(Callable<T> command)

This actually means the use of that syntax is completely redundant and unnecessary; T (the type) is inferred from the command being passed in which has to implement Callable<T>. The method is defined to return something of the inferred type T.

             Infer a type
               |
               v
public static <T> T tryExecute(Callable<T> command)
                  ^                     ^
                  |                     |
  <-return type--------------------------                           

In your example, coverageCRUDaoCallable has to be implementing Callable<List<ClientCoverageCRU>> because the method is returning List<ClientCoverageCRU>

In my example above you'd have to use the syntax you were asking about because nothing is being passed in from which to infer the type. T has to be explicitly provided via using ConnectionHelper.<List<ClientCoverageCRU>>tryExecute()



回答4:

So basically, the tryExecute() method in the ConnectionHelper uses generics. This allows you to feed the type inference to it prior to the method call after the "dot operator". This is actually shown directly in the Oracle Java tutorials for Generics, even though I'd consider it bad practice in a production environment.

You can see an official example of it here.

As you can see in your modified post, the tryExecute() definition is:

public static <T> T tryExecute(Callable<T> command)

By calling it as such (<List<ClientCoverageCRU>> tryExcute), you are forcing T to be a List<ClientCoverageCRU>. A better practice in general, though, would be to let this be inferred from an actual argument in the method. The type can also be inferred from the Callable<T>, so supplying it a Callable<List<ClientCoverageCRU>> as an argument would eliminate the need for this confusing usage.

See its usage in the JLS 4.11 - Where Types Are Used:

<S> void loop(S s) { this.<S>loop(s); }  

... and the formal definition of why this is allowed in method invocation in JLS 15.12 - Method Invocation Expressions. You can skip down to 15.12.2.7 and 15.12.2.8 for still more specifics. 15.12.2.8 - Inferring Unresolved Type Arguments explains the formal logic by which this functions.



回答5:

From Java Generics and Collections,

List<Integer> ints = Lists.<Integer>toList(); // first example
List<Object> objs = Lists.<Object>toList(1, "two"); // second example
  1. In the first example, without the type parameter there is too little information for the type inference algorithm used by Sun's compiler to infer the correct type. It infers that the argument to toList is an empty array of an arbitrary generic type rather than an empty array of integers, and this triggers the unchecked warning described earlier. (The Eclipse compiler uses a different inference algorithm, and compiles the same line correctly without the explicit parameter.)
  2. In the second example, without the type parameter there is too much information for the type inference algorithm to infer the correct type. You might think that Object is the only type that an integer and a string have in common, but in fact they also both implement the interfaces Serializable and Comparable. The type inference algorithm cannot choose which of these three is the correct type.

In general, the following rule of thumb suffices:

In a call to a generic method, if there are one or more arguments that correspond to a type parameter and they all have the same type then the type parameter may be inferred; if there are no arguments that correspond to the type parameter or the arguments belong to different subtypes of the intended type then the type parameter must be given explicitly.

Some points for passing type parameter

When a type parameter is passed to a generic method invocation, it appears in angle brackets to the left, just as in the method declaration.

The Java grammar requires that type parameters may appear only in method invocations that use a dotted form. Even if the method toList is defined in the same class that invokes the code, we cannot shorten it as follows:

List<Integer> ints = <Integer>toList(); // compile-time error

This is illegal because it will confuse the parser.