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


197

У Java я хотів би мати щось таке:

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

Але я отримую

Неможливо зробити статичну посилання на нестатичний тип T

Я не розумію дженерики за межами основних цілей, і тому не можу багато чого розуміти. Це не допомагає, що я не зміг знайти в Інтернеті багато інформації про цю тему.

Чи може хтось уточнити, чи можливе таке використання аналогічним чином? Крім того, чому моя первісна спроба виявилася невдалою?

Відповіді:


271

Не можна використовувати параметри загального типу класу в статичних методах або статичних полях. Параметри типу класу є лише в області застосування, наприклад, для методів і полів екземпляра. Для статичних полів та статичних методів вони поділяються між усіма екземплярами класу, навіть екземплярами різних параметрів типу, тому очевидно, що вони не можуть залежати від конкретного параметра типу.

Схоже, для вашої проблеми не потрібно використовувати параметр типу класу. Якщо ви опишете, що ви намагаєтеся зробити більш детально, можливо, ми можемо допомогти вам знайти кращий спосіб зробити це.


9
Ця відповідь, наголошена, справді пояснює проблему постера, а не просто вирішення проблеми.
Джорн

7
@Andre: Ваша інтуїція не необгрунтована; C # дійсно так ставиться до дженериків.
jyoungdev

33
"Для статичних полів та статичних методів вони поділяються між усіма екземплярами класу, навіть екземплярами параметрів різного типу ..." Так! Збивали горіхи за типом стирання знову!
BD у Rivenhill

2
якщо ви подивитесь, як виглядає загальний клас / методи після компіляції, ви побачите, що загальний атрибут видалений. І Список <Integer> після компіляції виглядає як "Список". Тому між списком <Integer> та списком <Long> після компіляції немає різниці - обидва стали List.
Дайній

3
Я думаю, що він пояснив, що намагається зробити досить добре. Зрозуміло, що він намагається похитнути датську здобич! hah
astryk

139

Java не знає, що T таке, поки ви не інстанціюєте тип.

Можливо, ви можете виконати статичні методи, зателефонувавши Clazz<T>.doit(something) але це здається, що ви не можете.

Інший спосіб вирішити речі - це ввести параметр типу в сам метод:

static <U> void doIt(U object)

що не дає вам правильного обмеження на U, але це краще, ніж нічого ...


Тоді я б закликав метод, не вказуючи жодних обмежень, тобто Clazz.doIt (об'єкт) замість Clazz <Object> .doIt (object), правда? Чи вважаєте ви це нормальним?
Андре Шалелла

Другий синтаксис є більш точним, який вам потрібен, якщо компілятор не може вивести тип повернутого значення з контакту, в якому викликається метод. Іншими словами, якщо компілятор дозволяє Clazz.doIt (об’єкт), то зробіть це.
skaffman

1
Я спробував Clazz <Object> .doIt (object) і отримав помилку під час компіляції! Msgstr "Помилка синтаксису в лексемах, неправильних конструкціях. Clazz.doIt (об'єкт) працює чудово, хоча навіть не попереджає.
Андре Шалелла

8
Використовуйте Clazz. <Object> doIt (object). Я думаю, що це дивний синтаксис у порівнянні з C ++ 'Clazz <int> :: doIt (1)
Crend King

Чому ви кажете, що це не дає вам правильного обмеження на U?
Адам Берлі

46

Я зіткнувся з цією ж проблемою. Я знайшов свою відповідь, завантаживши вихідний код Collections.sortу java Framework. Відповідь, яку я використав, полягала в тому, щоб ввести <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синтаксис.
Псалом Огре3333

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' !!!


У цьому випадку другий <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

Це ідеальне рішення.
Prabhat

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) 

3

Щось подібне, зблизить вас

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

EDIT: Оновлений приклад з більш детальною інформацією

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

Саме так, як запропонував Джейсон С.
Андре Шалелла

@Andre попередня пропозиція не передбачала обмеження, що U має продовжити Клац
ekj

Я бачу, але це не додає нічого корисного, крім обмеження. У своєму початковому дописі я хотів би це зробити, не передаючи U як параметр.
Андре Шалелла

@Andre Дивіться моє оновлення вище. Загальний тип визначений лише у визначенні методу, і його не потрібно передавати при виклику методу
ekj

@ekj Дякую, зараз я розумію. Вибачте, я поставив це питання три роки тому, коли я займався Java щодня. Я зрозумів вашу думку, хоча я думаю, ви розумієте, що це не зовсім те, що я задумав спочатку. Однак мені сподобалась ваша відповідь.
Андре Шалелла

2

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

Дженерики працюють як шаблони на C ++, тому спочатку слід інстанціювати свій клас, а потім використовувати функцію із заданим типом.


2
Java-дженерики сильно відрізняються від шаблонів C ++. Родовий клас складається сам. Насправді еквівалентний код у C ++ спрацював би (код виклику виглядатиме так Clazz<int>::doIt( 5 ))
David Rodríguez - dribeas

Мені подобається ця відповідь краще, ніж прийнята ... окрім посилань на шаблони C ++, коментар про загальний тип є лише для одного примірника класу замість усього класу. Прийнята відповідь не пояснює це, а просто дає вирішення, яке не має нічого спільного з тим, чому ви не можете використовувати загальний тип класу в статичному методі.
Джорн

1

Простіше кажучи, це трапляється через властивість "Erasure" у генериці. Що означає, що, хоча ми визначаємо ArrayList<Integer>і ArrayList<String>під час компіляції, він залишається як два різних конкретні типи, але під час виконання JVM стирає загальні типи та створює лише один клас ArrayList замість двох класів. Отже, коли ми визначаємо метод статичного типу або що-небудь для загального, він ділиться всіма примірниками цього загального, у моєму прикладі він поділяється на обидва ArrayList<Integer>і ArrayList<String>. Тому ми отримуємо помилку. Параметр загального типу класу - це не Дозволено в статичному контексті!


1

@BD в 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 доступний лише після створення екземпляра. Але статичні методи можна використовувати навіть до появи екземплярів. Отже, параметри загального типу не можна посилатись у статичні методи та змінні

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.