วิธีการคงที่ในชั้นเรียนทั่วไป?


197

ใน Java ฉันต้องการมีสิ่งต่อไปนี้:

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

แต่ฉันได้

ไม่สามารถทำการอ้างอิงแบบคงที่กับประเภทที่ไม่ใช่แบบคงที่ T

ฉันไม่เข้าใจข้อมูลทั่วไปนอกเหนือจากการใช้งานพื้นฐานดังนั้นจึงไม่สามารถเข้าใจได้ มันไม่ได้ช่วยให้ฉันไม่สามารถหาข้อมูลจำนวนมากบนอินเทอร์เน็ตเกี่ยวกับเรื่องนี้

บางคนสามารถอธิบายได้ว่าการใช้ดังกล่าวเป็นไปได้ด้วยวิธีเดียวกันหรือไม่? ทำไมความพยายามเริ่มแรกของฉันถึงไม่สำเร็จ?

คำตอบ:


271

คุณไม่สามารถใช้พารามิเตอร์ประเภททั่วไปของคลาสในวิธีการแบบคงที่หรือเขตข้อมูลแบบคงที่ พารามิเตอร์ชนิดของคลาสอยู่ในขอบเขตสำหรับวิธีการอินสแตนซ์และเขตข้อมูลอินสแตนซ์เท่านั้น สำหรับเขตข้อมูลแบบคงที่และวิธีการแบบคงที่พวกเขาจะใช้ร่วมกันระหว่างอินสแตนซ์ทั้งหมดของคลาสแม้แต่อินสแตนซ์ของพารามิเตอร์ชนิดต่าง ๆ ดังนั้นแน่นอนพวกเขาไม่สามารถขึ้นอยู่กับพารามิเตอร์ชนิดเฉพาะ

ดูเหมือนว่าปัญหาของคุณจะต้องใช้พารามิเตอร์ประเภทของคลาส หากคุณอธิบายสิ่งที่คุณพยายามทำในรายละเอียดเพิ่มเติมเราอาจช่วยคุณหาวิธีที่ดีกว่าในการทำ


9
คำตอบนี้อธิบายถึงปัญหาของโปสเตอร์แทนการให้วิธีแก้ปัญหา
Jorn

7
@ อังเดร: สัญชาตญาณของคุณไม่ได้ไม่มีมูลความจริง C # ปฏิบัติต่อยาชื่อสามัญอย่างนี้
jyoungdev

33
"สำหรับฟิลด์สแตติกและเมธอดสแตติกพวกเขาจะใช้ร่วมกันระหว่างอินสแตนซ์ทั้งหมดของคลาสแม้แต่อินสแตนซ์ของพารามิเตอร์ชนิดต่าง ๆ ... " เตะในถั่วโดยลบประเภทอีกครั้ง!
BD ที่ Rivenhill

2
หากคุณจะดูว่าคลาส / วิธีทั่วไปดูแลการรวบรวมคุณจะเห็นว่าแอตทริบิวต์ทั่วไปจะถูกลบออก และรายการ <Integer> หลังจากรวบรวมดูเหมือน "รายการ" ดังนั้นจึงไม่มีความแตกต่างระหว่าง List <Integer> และ List <Long> หลังจากการรวบรวม - ทั้งคู่กลายเป็น List
Dainius

3
ฉันคิดว่าเขาอธิบายสิ่งที่เขาพยายามทำค่อนข้างดี เห็นได้ชัดว่าเขากำลังพยายามที่จะเขย่าโจร! hah
astryk

139

Java ไม่ทราบว่าTมีอะไรจนกว่าคุณจะยกตัวอย่างประเภท

บางทีคุณสามารถดำเนินการวิธีการคงที่โดยการโทรClazz<T>.doit(something)แต่ดูเหมือนว่าคุณไม่สามารถ

วิธีอื่น ๆ ในการจัดการสิ่งต่าง ๆ คือการใส่พารามิเตอร์ type ลงในเมธอดเอง:

static <U> void doIt(U object)

ซึ่งคุณไม่ได้รับการ จำกัด ที่ถูกต้องใน U แต่มันดีกว่าไม่มีอะไรเลย ....


แล้วฉันจะเรียกวิธีการนี้โดยไม่ระบุข้อ จำกัด ใด ๆ เช่น Clazz.doIt (object) แทน Clazz <Object> .doIt (object) ใช่ไหม? คุณคิดว่าตกลงหรือไม่
André Chalella

ไวยากรณ์ที่สองมีความถูกต้องมากขึ้นซึ่งคุณต้องการหากคอมไพเลอร์ไม่สามารถอนุมานชนิดของค่าส่งคืนจาก contaxt ซึ่งวิธีการนี้เรียกว่า กล่าวอีกนัยหนึ่งถ้าคอมไพเลอร์อนุญาตให้ใช้ Clazz.doIt (วัตถุ) ให้ทำเช่นนั้น
skaffman

1
ฉันลองใช้ Clazz <Object> .doIt (object) แล้วและมีข้อผิดพลาดในการรวบรวมเวลา! "ข้อผิดพลาดทางไวยากรณ์ของโทเค็น, การสร้างผิดตำแหน่ง". Clazz.doIt (วัตถุ) ทำงานได้ดีแม้ว่าจะไม่ใช่คำเตือนก็ตาม
André Chalella

8
ใช้ Clazz <Object> doIt (object) ฉันคิดว่ามันเป็นไวยากรณ์ที่แปลกประหลาดเมื่อเทียบกับ Clazz ของ C ++ <int> :: doIt (1)
Crend King

ทำไมคุณถึงบอกว่ามันไม่ทำให้คุณถูก จำกัด ใน U?
Adam Burley

46

ฉันพบปัญหาเดียวกันนี้ ฉันพบคำตอบของฉันด้วยการดาวน์โหลดซอร์สโค้ดสำหรับCollections.sortในกรอบงานจาวา คำตอบที่ฉันใช้คือใส่<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

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 editor เพื่อยอมรับโค้ดข้างต้นโดยไม่มี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> อันที่สองซ่อนอันแรกหรือไม่
André 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
มันไม่มีส่วนเกี่ยวข้องกับการปิดบัง วิธีการแบบคงที่ก็ไม่สามารถจะผูกพันตามชั้นเรียนประเภททั่วไป อย่างไรก็ตามมันสามารถกำหนดประเภทและขอบเขตทั่วไปของตนเองได้
ไมค์

มีวิธีการกำหนดประเภทคงที่หนึ่งครั้งและนำมาใช้ใหม่สำหรับวิธีคงที่หลายวิธีหรือไม่ ฉันไม่ใช่แฟนตัวยงของการทำ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>อื่น ๆ แต่ฟิลด์ / วิธีแบบคงที่ วัตถุคงที่ของชั้นเรียน

ข้อความที่ตัดตอนมาต่อไปนี้นำมาจากเอกสาร :

ฟิลด์สแตติกของคลาสเป็นตัวแปรระดับคลาสที่แบ่งใช้โดยอ็อบเจ็กต์ที่ไม่ใช่สแตติกทั้งหมดของคลาส ดังนั้นไม่อนุญาตให้ใช้ฟิลด์แบบคงที่ของพารามิเตอร์ประเภท พิจารณาคลาสต่อไปนี้:

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

    // ...
}

หากอนุญาตให้ใช้ฟิลด์แบบคงที่ของพารามิเตอร์ประเภทดังนั้นรหัสต่อไปนี้จะสับสน:

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

เนื่องจากฟิลด์สแตติก 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 แนะนำ
André Chalella

@Andre ข้อเสนอแนะก่อนหน้านี้ไม่ได้ให้ข้อ จำกัด ที่ U ต้องขยาย Clazz
EKJ

ฉันเห็น แต่มันไม่เพิ่มสิ่งใดที่มีประโยชน์ยกเว้นข้อ จำกัด ในโพสต์ต้นฉบับของฉันฉันต้องการจะทำเช่นนี้โดยไม่ผ่าน U เป็นพารามิเตอร์
André Chalella

@Andre ดูการอัปเดตของฉันด้านบน ประเภททั่วไปมีการกำหนดไว้เฉพาะในการกำหนดวิธีการและไม่ต้องถูกส่งผ่านเมื่อเรียกวิธีการ
ekj

@ekj ขอบคุณฉันเข้าใจแล้ว ขออภัยฉันโพสต์คำถามนี้เมื่อสามปีที่แล้วเมื่อฉันทำ Java ทุกวัน ฉันได้ความคิดของคุณแม้ว่าฉันคิดว่าคุณเข้าใจว่านี่ไม่ใช่สิ่งที่ฉันตั้งใจไว้ อย่างไรก็ตามฉันชอบคำตอบของคุณ
André Chalella

2

เมื่อคุณระบุประเภททั่วไปสำหรับคลาสของคุณ JVM รู้ว่ามีเพียงอินสแตนซ์ของคลาสของคุณเท่านั้นไม่ใช่นิยาม แต่ละคำจำกัดความมีประเภท parametrized เท่านั้น

Generics ใช้งานได้เช่นเทมเพลตใน C ++ ดังนั้นคุณควรสร้างอินสแตนซ์ของคลาสก่อนจากนั้นใช้ฟังก์ชันที่มีประเภทที่ระบุ


2
Java generics ค่อนข้างแตกต่างจากเทมเพลต C ++ คลาสทั่วไปถูกคอมไพล์ด้วยตัวเอง ในความเป็นจริงรหัสเทียบเท่าใน C ++ จะทำงาน (รหัสโทรจะมีลักษณะClazz<int>::doIt( 5 ))
เดวิดRodríguez - dribeas

ฉันชอบคำตอบนี้ดีกว่าคำตอบที่ยอมรับ ... นอกเหนือจากการอ้างอิงเทมเพลต C ++ ความคิดเห็นเกี่ยวกับประเภททั่วไปจะเป็นเพียงหนึ่งอินสแตนซ์ของคลาสแทนที่จะเป็นคลาสทั้งหมด คำตอบที่ได้รับการยอมรับไม่ได้อธิบายสิ่งนี้และให้วิธีแก้ปัญหาที่ไม่มีส่วนเกี่ยวข้องกับสาเหตุที่คุณไม่สามารถใช้ชนิดทั่วไปของคลาสในวิธีการคงที่
Jorn

1

นอกจากนี้ในการใส่คำง่ายๆมันเกิดขึ้นเพราะคุณสมบัติ "ลบ" ของข้อมูลทั่วไปซึ่งหมายความว่าถึงแม้ว่าเราจะกำหนดArrayList<Integer>และArrayList<String>ในเวลาคอมไพล์มันยังคงเป็นคอนกรีตสองประเภทที่แตกต่างกัน แต่ที่รันไทม์ JVM จะลบประเภททั่วไปและ สร้างคลาส ArrayList เพียงคลาสเดียวแทนที่จะเป็นสองคลาส ดังนั้นเมื่อเรากำหนดวิธีการประเภทแบบคงที่หรืออะไรก็ได้สำหรับทั่วไปมันจะถูกใช้ร่วมกันโดยอินสแตนซ์ทั้งหมดของทั่วไปนั้นในตัวอย่างของฉันมันถูกแชร์โดยทั้งสองArrayList<Integer>และArrayList<String>นั่นคือสาเหตุที่คุณได้รับข้อผิดพลาดพารามิเตอร์ประเภททั่วไปของ Class ไม่ใช่ อนุญาตในบริบทแบบคงที่!


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";
      }
}

อย่างไรก็ตามมันไม่ได้เกี่ยวอะไรเลยมากนักเนื่องจากเวอร์ชั่นต่อไปนี้ทำงานในลักษณะเดียวกัน สิ่งที่ต้องการคือประเภทอินเตอร์เฟสของพารามิเตอร์ method ไม่มีตัวแปรประเภทที่มองเห็นได้ทุกที่ นั่นเป็นปัญหาดั้งเดิมจริงๆเหรอ?

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 สามารถใช้ได้หลังจากสร้างอินสแตนซ์แล้วเท่านั้น แต่วิธีการแบบคงที่สามารถใช้งานได้ก่อนที่อินสแตนซ์จะพร้อมใช้งาน ดังนั้นพารามิเตอร์ประเภททั่วไปไม่สามารถอ้างอิงภายในวิธีการคงที่และตัวแปร

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.