泛型类中的静态方法?


197

在Java中,我希望具有以下内容:

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

但是我明白了

无法静态引用非静态类型T

我不了解泛型的基本用途,因此没有太多意义。我无法在互联网上找到有关该主题的很多信息,这无济于事。

有人可以通过类似的方式澄清这种使用是否可能吗?另外,为什么我最初的尝试失败了?

Answers:


270

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

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


9
经认可,此答案实际上是在解释发布者的问题,而不仅仅是提供解决方法。
Jorn)

7
@安德烈:你的直觉不是没有根据的;C#确实确实以这种方式对待泛型。
jyoungdev

32
“对于静态字段和静态方法,它们在类的所有实例之间共享,甚至具有不同类型参数的实例也可以共享……” 再次按类型擦除插入螺母!
BD,里文希尔,2011年

2
如果您看一下编译后通用类/方法的外观,您会看到通用属性已删除。编译后的List <Integer>看起来像“ List”。因此,编译后List <Integer>和List <Long>之间没有区别-都成为List。
戴尼厄斯(Dainius)

3
我认为他解释了他试图做的相当不错的事情。很明显,他正在努力摆脱dat战利品!哈
astryk

140

T直到实例化类型,Java才知道是什么。

也许您可以通过调用来执行静态方法,Clazz<T>.doit(something)但这听起来似乎不行。

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

static <U> void doIt(U object)

并没有对U施加正确的限制,但是总比没有好....


然后,我将在不指定任何约束的情况下调用该方法,即使用Clazz.doIt(object)而不是Clazz <Object> .doIt(object),对吗?您认为还可以吗?
安德烈(AndréChalella)2009年

第二种语法是更精确的语法,如果编译器无法从调用该方法的contaxt推断返回值的类型,则需要使用该语法。换句话说,如果编译器允许使用Clazz.doIt(object),则可以这样做。
skaffman's

1
我尝试了Clazz <Object> .doIt(object)并遇到了编译时错误!“令牌语法错误,构造放置错误”。Clazz.doIt(object)工作正常,甚至没有警告。
2009年

8
使用Clazz。<Object> doIt(object)。与C ++的Clazz <int> :: doIt(1)相比,我认为这是一种怪异的语法
Crend King 2012年

你为什么说这没有给你正确的限制?
亚当·伯利

46

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

所以这工作:

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
}

8
尽管从技术上来说,公认的答案是正确的:这实际上是Google引导我提出这个问题时所要寻找的。
杰里米·

提供包含T extends XXX语法的示例的道具。
Ogre Psalm33 2014年

3
@Chris因为无论如何都使用泛型,所以不妨一直使用它们---即不要使用像一样的原始类型Comparable。试试吧<T extends Comparable<? super T>>
easoncxz 2015年

15

在声明您的方法时,可以通过使用通用方法的语法来做您想做的事情doIt()(请注意,<T>static和之间添加void了方法签名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);
  }
}

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

摇晃战利品“类com.eclipseoptions.datamanager.Clazz $ KC”!
摇晃战利品“类com.eclipseoptions.datamanager.Clazz $ SunshineBand”!


在这种情况下,第二个<T>是否隐藏第一个?
安德烈Chalella

1
@AndréNeves,是的。第二个参数<T>屏蔽第一个class C { int x; C(int x) { ... } }参数的方式与参数x屏蔽字段的方式相同x
Mike Samuel

如何解释样本的输出:似乎确实涉及两种类型,一种是通过参数T值?如果T确实隐藏了类类型,那么我期望“摇晃战利品'class com.eclipseoptions.datamanager.Clazz”两次输出而没有参数。
Pragmateek

1
它与掩膜无关。静态方法根本不受类泛型类型的约束。但是,它可以定义自己的通用类型和边界。
mike

有没有一种方法可以一次定义静态类型,然后将其重用于几种静态方法?我不是这样做的粉丝static <E extends SomeClass> E foo(E input){return input;}为每一个方法的时候,我想这样做static <E extends SomeClass>; //static generic type defined only once and reused static E foo(E input){return input;} static E bar(E input){return input;} //...etc...
HesNotTheStig

14

我认为还没有提到此语法(如果您想要一个没有参数的方法):

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

并致电:

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

希望这对某人有帮助。


1
实际上(我不知道Java版本),即使没有也是可能的,<String>因为它只是推断类型参数,因为您将其分配给变量。如果您不分配它,它只是推断Object
Adowrath

这是一个完美的解决方案。
Prabhat Ranjan

6

错误中已正确提及:您不能对非静态类型T进行静态引用。原因是T可以用任何类型参数(例如Clazz<String>或)替换类型参数。Clazz<integer>等等)。但是静态字段/方法由所有非静态共享类的-static对象。

以下摘录摘自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由电话,寻呼机和pc共享,所以os的实际类型是什么?它不能同时是Smartphone,Pager和TabletPC。因此,您不能创建类型参数的静态字段。

正如chris在他的答案中正确指出的那样,在这种情况下,您需要在方法中使用类型参数,而不是对类使用。您可以这样写:

static <E> void doIt(E object) 

3

类似以下内容将使您更接近

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建议的。
安德烈·查拉利亚


我知道了,但是除了约束之外,它没有添加任何有用的东西。在我的原始帖子中,我希望能够做到这一点而无需将U作为参数传递。
安德烈Chalella

@Andre参见上面的更新。泛型类型仅在方法定义中定义,并且在调用方法时不必传递
ekj 2012年

@ekj谢谢,我现在明白了。抱歉,三年前我每天做Java时,我提出了这个问题。我理解了您的想法,即使我认为您确实知道这与我最初的意图也不完全相同。但是,我喜欢你的回答。
安德烈Chalella

2

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

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


2
Java泛型与C ++模板完全不同。通用类是由其自身编译的。事实上,在C ++中的等价代码将工作(调用代码看起来像Clazz<int>::doIt( 5 )
大卫·罗德里格斯- dribeas

我喜欢这个答案胜于接受的答案...除了C ++模板参考,关于泛型类型的注释仅适用于该类的一个实例,而不是整个类。可接受的答案并不能解释这一点,而只是提供了一种变通方法,它与为什么您不能在静态方法中使用类的泛型类型没有任何关系。
乔恩

1

简而言之,它的发生是由于泛型的“ Erasure”属性所引起的。这意味着,尽管我们定义了ArrayList<Integer>ArrayList<String>,但在编译时它仍然是两种不同的具体类型,但在运行时,JVM会擦除泛型类型,并且仅创建一个ArrayList类,而不是两个类。因此,当我们定义一个静态类型的方法或任何一个通用的,它是由通用的所有实例共享,在我的例子,同时由共享ArrayList<Integer>ArrayList<String>。那就是为什么你得到一个类不是的error.A泛型类型参数在静态上下文中允许!


1

@BD in 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";
      }
}

0

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

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

T仅在创建实例后可用。但是,即使在实例可用之前也可以使用静态方法。因此,无法在静态方法和变量内引用泛型类型参数

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.