泛型类中的静态方法?

Posted

技术标签:

【中文标题】泛型类中的静态方法?【英文标题】:Static method in a generic class? 【发布时间】:2010-10-30 11:58:51 【问题描述】:

在 Java 中,我想要一些东西:

class Clazz<T> 
  static void doIt(T object) 
    // ...
  

但我明白了

无法对非静态类型 T 进行静态引用

除了基本用途之外,我不了解泛型,因此无法理解这一点。我无法在互联网上找到有关该主题的大量信息,这无济于事。

有人能以类似的方式澄清这种用途是否可能吗?另外,为什么我最初的尝试没有成功?

【问题讨论】:

【参考方案1】:

您不能在静态方法或静态字段中使用类的泛型类型参数。类的类型参数仅在实例方法和实例字段的范围内。对于静态字段和静态方法,它们在类的所有实例之间共享,即使是不同类型参数的实例,所以它们显然不能依赖于特定的类型参数。

您的问题似乎不需要使用类的类型参数。如果您更详细地描述您正在尝试做的事情,也许我们可以帮助您找到更好的方法。

【讨论】:

赞成,这个答案实际上解释了发布者的问题,而不仅仅是提供解决方法。 @Andre:你的直觉并非毫无根据; C# 确实以这种方式对待泛型。 "对于静态字段和静态方法,它们在类的所有实例之间共享,甚至是不同类型参数的实例......" 哎哟!再次被类型擦除踢到了疯狂! 如果你看一下泛型类/方法在编译后的样子,你会看到泛型属性被删除了。编译后的 List 看起来像“List”。所以 List 和 List 编译后没有区别 - 都变成了 List. 我认为他很好地解释了他正在努力做的事情。很明显,他正试图动摇这个战利品!哈哈【参考方案2】:

在您实例化一个类型之前,Java 不知道 T 是什么。

也许你可以通过调用Clazz&lt;T&gt;.doit(something) 来执行静态方法,但听起来你不能。

另一种处理方式是将类型参数放在方法本身中:

static <U> void doIt(U object)

这不会让你对 U 有正确的限制,但总比没有好......

【讨论】:

然后我会调用该方法而不指定任何约束,即 Clazz.doIt(object) 而不是 Clazz.doIt(object),对吗?你觉得这样好吗? 第二种语法更准确,如果编译器无法从调用方法的上下文中推断返回值的类型,则需要使用它。换句话说,如果编译器允许 Clazz.doIt(object),那么就这样做。 我试过 Clazz.doIt(object) 并得到一个编译时错误! “令牌的语法错误,错误的构造”。 Clazz.doIt(object) 工作正常,甚至没有警告。 使用 Clazz.doIt(object)。与 C++ 的 Clazz::doIt(1) 相比,我认为这是一种奇怪的语法 你为什么说它没有让你对 U 有正确的限制?【参考方案3】:

我遇到了同样的问题。我通过在 java 框架中下载Collections.sort 的源代码找到了答案。我使用的答案是将&lt;T&gt; 泛型放在方法中,而不是在类定义中。

这样就成功了:

public class QuickSortArray  
    public static <T extends Comparable> void quickSort(T[] array, int bottom, int top)
//do it



当然,在阅读了上面的答案后,我意识到如果不使用泛型类,这将是一个可以接受的替代方案:

public static void quickSort(Comparable[] array, int bottom, int top)
//do it

【讨论】:

虽然接受的答案在技术上是正确的:这实际上是我在谷歌引导我提出这个问题时所寻找的。​​span> Props 提供一个包含T extends XXX 语法的示例。 @Chris 因为无论如何你都在使用泛型,所以你最好一直使用它们——即不要使用像Comparable 这样的原始类型。请改用&lt;T extends Comparable&lt;? super T&gt;&gt;【参考方案4】:

我认为这个语法还没有被提及(如果你想要一个没有参数的方法):

class Clazz 
  static <T> T doIt() 
    // shake that booty
  

然后打电话:

String str = Clazz.<String>doIt();

希望这对某人有所帮助。

【讨论】:

实际上(我不知道 Java 版本),即使没有 &lt;String&gt; 也是可能的,因为它只是推断类型参数,因为您将其分配给变量。如果你不分配它,它只是推断Object 这是一个完美的解决方案。【参考方案5】:

在声明doIt() 方法时,可以使用泛型方法的语法来做你想做的事(注意staticvoid 之间的&lt;T&gt;doIt() 的方法签名中添加) :

class Clazz<T> 
  static <T> void doIt(T object) 
    // shake that booty
  

我让 Eclipse 编辑器接受上面的代码,没有出现Cannot make a static reference to the non-static type T 错误,然后将其扩展为以下工作程序(完成了一些适合年龄的文化参考):

public class Clazz<T> 
  static <T> void doIt(T object) 
    System.out.println("shake that booty '" + object.getClass().toString()
                       + "' !!!");
  

  private static class KC 
  

  private static class SunshineBand 
  

  public static void main(String args[]) 
    KC kc = new KC();
    SunshineBand sunshineBand = new SunshineBand();
    Clazz.doIt(kc);
    Clazz.doIt(sunshineBand);
  

当我运行它时将这些行打印到控制台:

摇晃那个战利品'class com.eclipseoptions.datamanager.Clazz$KC' !!! 动摇那个战利品'class com.eclipseoptions.datamanager.Clazz$SunshineBand'!!!

【讨论】:

这种情况下,第二个会隐藏第一个吗? @AndréNeves,是的。第二个&lt;T&gt; 屏蔽第一个class C int x; C(int x) ... 中的参数x 屏蔽字段x 的方式相同。 如何解释样本的输出:似乎确实涉及两种类型,一种是通过参数T值?如果 T 真的隐藏了类类型,我会期望“摇晃战利品 'class com.eclipseoptions.datamanager.Clazz” 输出两次没有参数。 与遮罩无关。静态方法根本无法被类泛型类型所绑定。不过它可以定义自己的泛型类型和边界。 有没有一种方法可以定义一次静态类型并为多个静态方法重用它?当我想做static &lt;E extends SomeClass&gt;; //static generic type defined only once and reused static E foo(E input)return input; static E bar(E input)return input; //...etc...【参考方案6】:

错误中正确提及:您不能对非静态类型 T 进行静态引用。原因是类型参数 T 可以替换为任何类型参数,例如Clazz&lt;String&gt;Clazz&lt;integer&gt; 等。但静态字段/方法由类的所有非静态对象共享。

以下摘录自doc:

一个类的静态字段是一个类级别的变量,被所有人共享 类的非静态对象。因此,类型的静态字段 不允许使用参数。考虑以下类:

public class MobileDevice<T> 
    private static T os;

    // ...

如果允许类型参数的静态字段,那么下面的代码就会混淆:

MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();

因为静态字段os是phone、pager、pc共享的,那么os的实际类型是什么?它不能是智能手机、寻呼机和 同时平板电脑。因此,您不能创建静态字段 类型参数。

正如克里斯在他的answer 中正确指出的那样,在这种情况下,您需要将类型参数与方法一起使用,而不是与类一起使用。你可以这样写:

static <E> void doIt(E object) 

【讨论】:

【参考方案7】:

类似下面的东西会让你更接近

class Clazz

   public static <U extends Clazz> void doIt(U thing)
   
   

编辑:更详细的更新示例

public abstract class Thingo 


    public static <U extends Thingo> void doIt(U p_thingo)
    
        p_thingo.thing();
    

    protected abstract void thing();



class SubThingoOne extends Thingo

    @Override
    protected void thing() 
    
        System.out.println("SubThingoOne");
    


class SubThingoTwo extends Thingo


    @Override
    protected void thing() 
    
        System.out.println("SuThingoTwo");
    



public class ThingoTest 


    @Test
    public void test() 
    
        Thingo t1 = new SubThingoOne();
        Thingo t2 = new SubThingoTwo();

        Thingo.doIt(t1);
        Thingo.doIt(t2);

        // compile error -->  Thingo.doIt(new Object());
    

【讨论】:

完全按照 Jason S 的建议。 @Andre 之前的建议没有提供 U 必须扩展 Clazz 的限制 我明白了,但除了约束之外,它没有添加任何有用的东西。在我原来的帖子中,我希望能够在不将 U 作为参数传递的情况下做到这一点。 @Andre 请参阅上面的更新。泛型只在方法定义中定义,调用方法时不必传入 @ekj 谢谢,我现在明白了。对不起,我在三年前提出这个问题,当时我每天都在做 Java。我明白了你的想法,尽管我认为你确实明白这并不是我最初的意图。不过,我喜欢你的回答。【参考方案8】:

因为静态变量由类的所有实例共享。例如,如果您有以下代码

class Class<T> 
  static void doIt(T object) 
    // using T here 
  

T 仅在创建实例后可用。但是静态方法甚至可以在实例可用之前使用。所以,泛型类型参数不能在静态方法和变量中被引用

【讨论】:

【参考方案9】:

当你为你的类指定一个泛型类型时,JVM 只知道它有你的类的一个实例,而不是定义。每个定义只有参数化类型。

泛型的工作方式类似于 C++ 中的模板,因此您应该首先实例化您的类,然后使用指定类型的函数。

【讨论】:

Java 泛型与 C++ 模板有很大不同。泛型类是自己编译的。事实上,C++ 中的等效代码可以工作(调用代码看起来像 Clazz&lt;int&gt;::doIt( 5 ) 我比接受的答案更喜欢这个答案...除了 C++ 模板参考之外,关于泛型类型的评论仅适用于类的一个实例而不是整个类。接受的答案没有解释这一点,只是提供了一个解决方法,它与为什么不能在静态方法中使用类的泛型类型没有任何关系。【参考方案10】:

简单来说,它的发生是因为泛型的“擦除”属性。这意味着虽然我们定义了 ArrayList&lt;Integer&gt;ArrayList&lt;String&gt; ,但在编译时它仍然是两种不同的具体类型,但是在运行时,JVM 会删除泛型类型并只创建一个 ArrayList 类而不是两个类。因此,当我们为泛型定义静态类型方法或任何东西时,它由该泛型的所有实例共享,在我的示例中,它由 ArrayList&lt;Integer&gt;ArrayList&lt;String&gt; 共享。这就是你得到错误的原因。泛型类型静态上下文中不允许使用类的参数!

【讨论】:

【参考方案11】:

@BD at Rivenhill:自从这个老问题去年重新引起人们的注意,让我们继续讨论一下,只是为了讨论。 doIt 方法的主体根本不做任何T 特定的事情。这里是:

public class Clazz<T> 
  static <T> void doIt(T object) 
    System.out.println("shake that booty '" + object.getClass().toString()
                       + "' !!!");
  
// ...

因此您可以完全删除所有类型变量而只需编写代码

public class Clazz 
  static void doIt(Object object) 
    System.out.println("shake that booty '" + object.getClass().toString()
                       + "' !!!");
  
// ...

好的。但是让我们回到更接近最初的问题。类声明中的第一个类型变量是多余的。只需要方法中的第二个。我们又来了,但这还不是最终的答案:

public class Clazz  
  static <T extends Saying> void doIt(T object) 
    System.out.println("shake that booty "+ object.say());
  

  public static void main(String args[]) 
    Clazz.doIt(new KC());
    Clazz.doIt(new SunshineBand());
  

// Output:
// KC
// Sunshine

interface Saying 
      public String say();


class KC implements Saying 
      public String say() 
          return "KC";
      


class SunshineBand implements Saying 
      public String say() 
          return "Sunshine";
      

但是,这没什么大惊小怪的,因为以下版本的工作方式相同。它所需要的只是方法参数上的接口类型。任何地方都看不到类型变量。这真的是最初的问题吗?

public class Clazz  
  static void doIt(Saying object) 
    System.out.println("shake that booty "+ object.say());
  

  public static void main(String args[]) 
    Clazz.doIt(new KC());
    Clazz.doIt(new SunshineBand());
  


interface Saying 
      public String say();


class KC implements Saying 
      public String say() 
          return "KC";
      


class SunshineBand implements Saying 
      public String say() 
          return "Sunshine";
      

【讨论】:

【参考方案12】:

T 不在静态方法的范围内,因此您不能在静态方法中使用 T。您需要为静态方法定义不同的类型参数。我会这样写:

class Clazz<T> 

  static <U> void doIt(U object) 
    // ...
  


例如:

public class Tuple<T> 

    private T[] elements;

    public static <E> Tuple<E> of(E ...args)
        if (args.length == 0) 
             return new Tuple<E>();
        return new Tuple<E>(args);
    

    //other methods

【讨论】:

以上是关于泛型类中的静态方法?的主要内容,如果未能解决你的问题,请参考以下文章

Java 泛型泛型简介 ( 泛型类 | 泛型方法 | 静态方法的泛型 | 泛型类与泛型方法完整示例 )

Java泛型详解

Java泛型详解

泛型方法或泛型类中的方法是内部调用PInvoke 或是在 COM 导入类中定义的。

从非泛型类调用抽象泛型类中定义的方法的最佳方法是啥

反射获取泛型类泛型方法