java中使用点运算符访问通用列表的奇数方法调用

Posted

技术标签:

【中文标题】java中使用点运算符访问通用列表的奇数方法调用【英文标题】:Odd method call in java using a dot operator to access a generic list 【发布时间】:2014-02-13 09:35:01 【问题描述】:

我遇到了一些高级 java 代码(对我来说是高级的:))我需要帮助理解。

在一个类中有一个嵌套类如下:

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);
    

稍后在外部类中,创建了一个可调用类的实例。 我不知道这是什么:

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

在我看来,它不像 java 语法。您能否详细说明这种神秘的语法发生了什么?您可以在下面的代码摘录中看到它被使用。

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

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

已编辑 添加了 ConnectionHelper 类。

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;
    

【问题讨论】:

你熟悉generics吗? 我是,但我也发现语法混乱。 你能说明ConnectionHelper的定义吗? 查看泛型方法部分:ntu.edu.sg/home/ehchua/programming/java/JavaGeneric.html 我添加了 connectionHelper 类 【参考方案1】:

您通常认为类是通用的,但方法也可以是通用的。一个常见的例子是Arrays.asList

大多数情况下,您不必使用带尖括号 &lt;...&gt; 的语法,即使在调用泛型方法时也是如此,因为这是 Java 编译器真正能够做到的地方某些情况下的基本类型推断。例如,Arrays.asList 文档中给出的 sn-p 省略了类型:

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

但它相当于这个版本,其中明确给出了泛型类型:

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

【讨论】:

但是空间为什么会起作用?应该没有空格吧? 这只是约定俗成的问题。如果您愿意,可以在很多地方插入多余的空格。你也可以写List &lt; String &gt; stooges = Arrays . &lt; String &gt; asList ( "Larry" , "Moe" , "Curly" ) ;。您甚至可以在任何这些地方插入换行符。 ` 谢谢。我忘记了空白是偏好问题【参考方案2】:

来自Java Generics and Collections

List<Integer> ints = Lists.<Integer>toList(); // first example
List<Object> objs = Lists.<Object>toList(1, "two"); // second example
    In the first example,没有类型参数,信息太少 Sun 的编译器用来推断正确类型的类型推断算法。它推断 toList 的参数是任意泛型类型的空数组,而不是 一个空的整数数组,这会触发前面描述的未经检查的警告。 (Eclipse 编译器使用不同的推理算法,并编译同一行 在没有显式参数的情况下正确。) In the second example,没有类型参数太多了 类型推断算法推断正确的信息 类型。你可能认为 Object 是唯一的整数类型 和字符串有共同点,但实际上它们也都实现了 Serializable 和 Comparable 接口。类型推断 算法无法选择这三个中的哪一个是正确的类型。

一般来说,以下经验法则就足够了:

在调用泛型方法时,如果有 是对应于类型参数的一个或多个实参,它们都具有 相同类型,则可以推断类型参数;如果没有论据 对应于类型参数或实参属于不同的子类型 预期类型,则必须明确给出类型参数。

传递类型参数的几点注意事项

当一个类型参数被传递给一个泛型方法调用时,它以角度出现 左边的括号,就像在方法声明中一样。

Java 语法要求类型参数只能出现在使用点分形式的方法调用中。甚至 如果 toList 方法定义在调用代码的同一个类中,我们不能 缩短如下:

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

这是非法的,因为它会混淆解析器。

【讨论】:

【参考方案3】:

这是因为,在 Java 7 之前,泛型并不完全支持目标类型,因此您需要在 ConnectionHelper.&lt;List&lt;ClientCoverageCRU&gt;&gt; 中使用所谓的类型见证来帮助编译器。

但请注意,Java 8 significantly improves target typing 和您的具体示例中的类型见证在 Java 8 中不是必需的。

【讨论】:

不知道它被称为“类型见证”+1 所以,这将是正确的,除了该方法实际上将泛型类型作为参数,因此它不需要从目标推断 什么是目标类型?什么是类型见证? 类型见证是当您需要在方法调用中显式添加泛型类型时,如您的示例所示。目标类型是编译器能够根据分配给结果的变量的类型来确定方法调用的泛型类型。 tutorial on type inference有更详细的解释。【参考方案4】:

这很丑陋,但有效。

无论ConnectionHelper 是什么,它都有一个需要推断泛型类型的静态tryExecute 方法。

类似:

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

从更新后的问题编辑:Java 具有针对泛型类型的类型推断。方法签名中的第一个&lt;T&gt; 表示将在调用方法时推断类型。

在您更新的帖子中,您显示 tryExecute() 定义为采用通用参数:

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

这实际上意味着使用该语法是完全多余和不必要的; T(类型)是从传入的 command 推断出来的,其中必须实现 Callable&lt;T&gt;。该方法被定义为返回推断类型 T 的内容。

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

在您的示例中,coverageCRUDaoCallable 必须实现 Callable&lt;List&lt;ClientCoverageCRU&gt;&gt;,因为该方法返回 List&lt;ClientCoverageCRU&gt;

在我上面的示例中,您将 必须 使用您所询问的语法,因为没有传入任何内容来推断类型。 T 必须通过使用 ConnectionHelper.&lt;List&lt;ClientCoverageCRU&gt;&gt;tryExecute() 显式提供

【讨论】:

我不明白。你能详细说明一下吗?我添加了 ConnectionHelper 类供您参考。谢谢。【参考方案5】:

所以基本上,ConnectionHelper 中的tryExecute() 方法使用泛型。这允许您在“点运算符”之后的方法调用之前向其提供类型推断。这实际上直接显示在 Oracle Java 泛型教程中,尽管我认为它在生产环境中是不好的做法。

你可以看到它的官方例子here。

正如您在修改后的帖子中看到的,tryExecute() 定义是:

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

通过这样称呼它 (&lt;List&lt;ClientCoverageCRU&gt;&gt; tryExcute),您将强制 T 成为 List&lt;ClientCoverageCRU&gt;。但是,一般来说,更好的做法是让这从方法中的实际参数中推断出来。该类型也可以从 Callable&lt;T&gt; 中推断出来,因此向其提供 Callable&lt;List&lt;ClientCoverageCRU&gt;&gt; 作为参数将消除这种混淆用法的需要。

在JLS 4.11 - Where Types Are Used中查看其用法:

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

... 以及为什么在 JLS 15.12 - Method Invocation Expressions 中的方法调用中允许这样做的正式定义。您可以跳到 15.12.2.7 和 15.12.2.8 了解更多细节。 15.12.2.8 - Inferring Unresolved Type Arguments 解释了这个函数的形式逻辑。

【讨论】:

如果明确提供,就不再是真正的推理了。 @NateC-K 哈哈,关于英语的好点。尽管如此,仍然考虑 Java 的类型推断,即使它没有意义(如您在 Oracle 教程中所见)。 :)

以上是关于java中使用点运算符访问通用列表的奇数方法调用的主要内容,如果未能解决你的问题,请参考以下文章

0524泰山版java开发手册

Java并发概念-1

Java动态代理

1.23 Java基础总结 方法的声明和调用

Java 中类似 Python 的列表推导

方法的声明和调用——java