Статический метод в универсальном классе?

197

В Java я хотел бы иметь что-то вроде:

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

Но я получаю

Невозможно сделать статическую ссылку на нестатический тип T

Я не понимаю дженерики за пределами базового использования и поэтому не могу понять этого. Это не помогает, что я не смог найти много информации в Интернете по этому вопросу.

Может ли кто-нибудь уточнить, возможно ли такое использование подобным образом? Кроме того, почему моя первоначальная попытка оказалась неудачной?

Андре Шалелла
источник

Ответы:

271

Вы не можете использовать параметры общего типа класса в статических методах или статических полях. Параметры типа класса находятся только в области видимости для методов экземпляра и полей экземпляра. Для статических полей и статических методов они являются общими для всех экземпляров класса, даже для экземпляров параметров другого типа, поэтому очевидно, что они не могут зависеть от конкретного параметра типа.

Похоже, что ваша проблема не требует использования параметра типа класса. Если вы опишите, что вы пытаетесь сделать более подробно, возможно, мы поможем вам найти лучший способ сделать это.

newacct
источник
9
Этот ответ на самом деле объясняет проблему автора, а не просто предлагает обходной путь.
Jorn
7
@Andre: Ваша интуиция не беспочвенна; C # действительно так относится к дженерикам.
jyoungdev
33
«Для статических полей и статических методов они являются общими для всех экземпляров класса, даже для экземпляров параметров другого типа ...» Ой! Пинали в гайки по типу стирания снова!
BD в Ривенхилле
2
если вы посмотрите, как выглядит обобщенный класс / методы после компиляции, вы увидите, что универсальный атрибут удален. И List <Integer> после компиляции выглядит как «Список». Таким образом, после компиляции нет разницы между List <Integer> и List <Long> - оба стали List.
Дайний
3
Я думаю, что он объяснил, что он пытается сделать довольно хорошо. Понятно, что он пытается потрясти добычу! хах
астрик
139

Java не знает, что Tесть, пока вы не создадите экземпляр типа.

Может быть, вы можете выполнять статические методы путем вызова, Clazz<T>.doit(something)но это звучит так, как будто вы не можете.

Другой способ справиться с этим - поместить параметр типа в сам метод:

static <U> void doIt(U object)

который не дает вам правильное ограничение на U, но это лучше, чем ничего ....

Джейсон С
источник
Тогда я бы вызвал метод без указания каких-либо ограничений, т.е. Clazz.doIt (объект) вместо Clazz <Object> .doIt (объект), верно? Считаете ли вы, что все в порядке?
Андре Шалелья
Второй синтаксис - более точный, который вам нужен, если компилятор не может определить тип возвращаемого значения из контекста, в котором вызывается метод. Другими словами, если компилятор разрешает Clazz.doIt (объект), то сделайте это.
Скаффман
1
Я попробовал Clazz <Object> .doIt (object) и получил ошибку во время компиляции! Msgstr "Синтаксическая ошибка на токене (ах), неуместной конструкции (ях)". Clazz.doIt (объект) работает нормально, хотя, даже не предупреждение.
Андре Шалелла
9
Используйте Clazz. <Object> doIt (объект). Я думаю, что это странный синтаксис, по сравнению с C ++ Clazz <int> :: doIt (1)
Crend King
Почему вы говорите, что это не дает вам правильное ограничение на U?
Адам Берли
46

Я столкнулся с этой же проблемой. Я нашел свой ответ, загрузив исходный код для Collections.sortв рамках Java. Ответ, который я использовал, заключался в том, чтобы поместить <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синтаксис.
Огрский псалом33
3
@Chris Так как вы в любом случае используете дженерики, вы можете использовать их полностью, а именно - не использовать необработанные типы, такие как Comparable. Попробуй <T extends Comparable<? super T>>вместо этого.
easoncxz
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' !!!

BD в Ривенхилле
источник
В этом случае второй <T> скрывает первый?
Андре Шалелья
1
@ AndréNeves, да. Вторая <T>маскирует первую так же, как в class C { int x; C(int x) { ... } }параметре xмаскирует поле x.
Майк Сэмюэл
Как объяснить вывод выборки: кажется, что действительно задействованы два типа, один по значению параметра T? Если бы T действительно скрывал тип класса, я бы ожидал, что класс «потряси эту добычу» com.eclipseoptions.datamanager.Clazz выводится дважды без параметров.
Pragmateek
1
Это не имеет ничего общего с маскировкой. Статический метод просто не может быть связан универсальным типом классов . Тем не менее он может определять свои собственные общие типы и границы.
Майк
Есть ли способ определить статический тип один раз и повторно использовать его для нескольких статических методов? Я не фанат 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
Это идеальное решение.
Прабхат Ранджан
6

Это правильно упоминается в ошибке: вы не можете сделать статическую ссылку на нестатический тип T. Причина в том, что параметр типа Tможет быть заменен любым аргументом типа, например, Clazz<String>и Clazz<integer>т. Д. Но статические поля / методы являются общими для всех не предметы класса.

Следующая выдержка взята из документа :

Статическое поле класса - это переменная уровня класса, общая для всех нестатических объектов класса. Следовательно, статические поля параметров типа не допускаются. Рассмотрим следующий класс:

public class MobileDevice<T> {
    private static T os;

    // ...
}

Если бы были разрешены статические поля параметров типа, то следующий код был бы запутан:

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

Поскольку статическое поле os совместно используется телефоном, пейджером и компьютером, каков действительный тип os? Это не может быть смартфон, пейджер и планшетный ПК одновременно. Поэтому нельзя создавать статические поля параметров типа.

Как правильно указал Крис в своем ответе, вам нужно использовать параметр типа с методом, а не с классом в этом случае. Вы можете написать это так:

static <E> void doIt(E object) 
akhil_mittal
источник
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());
    }
}
EKJ
источник
Точно так, как предложил Джейсон С.
Андре Шалелла
@ И предыдущее предложение не содержало ограничения на то, что U должен продлить
Clazz
Я вижу, но это не добавляет ничего полезного, кроме ограничения. В моем оригинальном посте я хотел бы иметь возможность сделать это без передачи U в качестве параметра.
Андре Шалелла
@Andre Смотрите мое обновление выше. Универсальный тип определяется только в определении метода и не должен передаваться при вызове метода
ekj
@ekj Спасибо, теперь я понимаю. Извините, я задал этот вопрос три года назад, когда я ежедневно занимался Java. Я понял вашу идею, хотя я думаю, что вы понимаете, что это не совсем то, что я изначально задумал. Однако мне понравился твой ответ.
Андре Шалелла
2

Когда вы указываете универсальный тип для своего класса, JVM знает, что он имеет только экземпляр вашего класса, а не определения. Каждое определение имеет только параметризованный тип.

Обобщения работают как шаблоны в C ++, поэтому вы должны сначала создать экземпляр своего класса, а затем использовать функцию с указанным типом.

Марцин Цилке
источник
2
Обобщения Java весьма отличаются от шаблонов C ++. Универсальный класс компилируется сам по себе. На самом деле эквивалентный код в C ++ будет работать (вызов кода будет выглядеть Clazz<int>::doIt( 5 ))
Дэвид Родригес - dribeas
Мне нравится этот ответ лучше, чем принятый ... кроме ссылки на шаблон C ++, замечание о родовом типе относится только к одному экземпляру класса, а не ко всему классу. Принятый ответ не объясняет этого, а лишь предоставляет обходной путь, который не имеет никакого отношения к тому, почему вы не можете использовать универсальный тип класса в статическом методе.
Jorn
1

Кроме того, проще говоря, это происходит из-за свойства Erasure для generics. Это означает, что хотя мы определяем ArrayList<Integer>и ArrayList<String>во время компиляции он остается как два разных конкретных типа, но во время выполнения JVM стирает универсальные типы и создает только один класс ArrayList вместо двух классов. Поэтому, когда мы определяем метод статического типа или что-то еще для универсального объекта, он используется всеми экземплярами этого универсального шаблона , в моем примере он используется совместно обоими ArrayList<Integer>и. ArrayList<String>Вот почему вы получаете ошибку. Параметр универсального типа класса не является Разрешено в статическом контексте!


источник
1

@BD в Ривенхилле: Так как этот старый вопрос привлек к себе новое внимание в прошлом году, давайте немного поговорим ради обсуждения. Тело вашего 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 доступен только после создания экземпляра. Но статические методы можно использовать даже до того, как будут доступны экземпляры. Таким образом, на параметры общего типа нельзя ссылаться внутри статических методов и переменных.

Бхарат
источник