ฉันจะทำให้วิธีคืนชนิดทั่วไปได้อย่างไร


588

ลองพิจารณาตัวอย่างนี้ (โดยทั่วไปในหนังสือ OOP):

ฉันมีAnimalชั้นเรียนที่แต่ละคนAnimalสามารถมีเพื่อนมากมาย
และ subclasses ชอบDog, Duck, Mouseฯลฯ ที่เพิ่มพฤติกรรมที่เฉพาะเจาะจงเช่นbark(), quack()ฯลฯ

นี่คือAnimalคลาส:

public class Animal {
    private Map<String,Animal> friends = new HashMap<>();

    public void addFriend(String name, Animal animal){
        friends.put(name,animal);
    }

    public Animal callFriend(String name){
        return friends.get(name);
    }
}

และนี่คือตัวอย่างข้อมูลบางส่วนที่มีการพิมพ์ดีดจำนวนมาก:

Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

((Dog) jerry.callFriend("spike")).bark();
((Duck) jerry.callFriend("quacker")).quack();

มีวิธีใดบ้างที่ฉันสามารถใช้ generics สำหรับประเภทคืนเพื่อกำจัด typecasting เพื่อที่ฉันจะได้พูด

jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();

นี่คือรหัสเริ่มต้นที่มีประเภทส่งคืนที่ถ่ายทอดไปยังเมธอดเนื่องจากพารามิเตอร์ที่ไม่เคยใช้

public<T extends Animal> T callFriend(String name, T unusedTypeObj){
    return (T)friends.get(name);        
}

มีวิธีคิดประเภทคืนที่รันไทม์โดยไม่ต้องใช้พารามิเตอร์พิเศษinstanceofหรือไม่ หรืออย่างน้อยก็ผ่านคลาสประเภทแทนที่จะเป็นตัวอย่างแบบจำลอง
ฉันเข้าใจว่าข้อมูลทั่วไปใช้สำหรับการตรวจสอบเวลาแบบคอมไพล์ แต่มีวิธีแก้ปัญหาสำหรับเรื่องนี้หรือไม่?

คำตอบ:


903

คุณสามารถกำหนดcallFriendวิธีนี้:

public <T extends Animal> T callFriend(String name, Class<T> type) {
    return type.cast(friends.get(name));
}

จากนั้นเรียกมันว่า:

jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();

รหัสนี้มีประโยชน์ในการไม่สร้างคำเตือนของคอมไพเลอร์ แน่นอนว่านี่เป็นเพียงการอัปเดตของการคัดเลือกนักแสดงในยุคก่อนวันธรรมดาและไม่เพิ่มความปลอดภัยใด ๆ เพิ่มเติม


29
... แต่ยังไม่มีการตรวจสอบประเภทเวลารวบรวมระหว่างพารามิเตอร์ของการโทร callFriend ()
David Schmitt

2
นี่คือคำตอบที่ดีที่สุด - แต่คุณควรเปลี่ยน addFriend ด้วยวิธีเดียวกัน มันทำให้การเขียนบั๊กยากขึ้นเนื่องจากคุณต้องการตัวอักษรระดับนั้นทั้งสองแห่ง
Craig P. Motlin

@Jaider ไม่เหมือนกัน แต่จะใช้ได้: // Animal Class public T CallFriend <T> (ชื่อสตริง) โดยที่ T: สัตว์ {ส่งคืนเพื่อน [ชื่อ] เป็น T; } // Calling Class jerry.CallFriend <Dog> ("spike"). Bark (); jerry.CallFriend <เป็ด> ( "Quacker") กำมะลอ ().
Nestor Ledon

124

ไม่คอมไพเลอร์ไม่ทราบว่าjerry.callFriend("spike")จะส่งคืนประเภทใด นอกจากนี้การนำไปใช้ของคุณจะซ่อนตัวบล็อกในวิธีที่ไม่มีความปลอดภัยเพิ่มเติม พิจารณาสิ่งนี้:

jerry.addFriend("quaker", new Duck());
jerry.callFriend("quaker", /* unused */ new Dog()); // dies with illegal cast

ในกรณีเฉพาะนี้การสร้างtalk()วิธีนามธรรมและการเอาชนะอย่างเหมาะสมในคลาสย่อยจะช่วยให้คุณดีขึ้นมาก:

Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

jerry.callFriend("spike").talk();
jerry.callFriend("quacker").talk();

10
ในขณะที่วิธีการ mmyers อาจใช้งานได้ฉันคิดว่าวิธีนี้เป็นวิธีการเขียนโปรแกรม OO ที่ดีกว่าและจะช่วยคุณประหยัดปัญหาในอนาคต
James McMahon

1
นี่เป็นวิธีที่ถูกต้องเพื่อให้ได้ผลลัพธ์เดียวกัน ขอให้สังเกตว่าเป้าหมายคือการได้รับพฤติกรรมที่เฉพาะเจาะจงของคลาสที่รันไทม์โดยไม่ต้องเขียนโค้ดตัวเองอย่างชัดเจนเพื่อทำการตรวจสอบและการพิมพ์ที่น่าเกลียด วิธีที่เสนอโดย @laz ทำงานได้ แต่พ่นความปลอดภัยของประเภทออกไปนอกหน้าต่าง วิธีนี้ต้องใช้โค้ดน้อยกว่า (เนื่องจากการใช้วิธีนั้นล่าช้าและค้นหารันไทม์อยู่แล้ว) แต่ก็ยังให้คุณกำหนดพฤติกรรมที่ไม่ซ้ำสำหรับแต่ละคลาสย่อยของ Animal ได้อย่างง่ายดาย
dcow

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

2
@laz: ใช่คำถามเดิม - ถูกวาง - ไม่เกี่ยวกับความปลอดภัยของประเภท แต่นั่นไม่ได้เปลี่ยนความจริงที่ว่ามีวิธีที่ปลอดภัยในการดำเนินการดังกล่าวโดยกำจัดความล้มเหลวในการส่งระดับ ดูเพิ่มเติมที่weblogs.asp.net/alex_papadimoulis/archive/2005/05/25/…
David Schmitt

3
ฉันไม่เห็นด้วยเกี่ยวกับสิ่งนั้น แต่เรากำลังจัดการกับ Java และการตัดสินใจ / การออกแบบทั้งหมดของมัน ฉันเห็นคำถามนี้ว่าเป็นเพียงการพยายามเรียนรู้สิ่งที่เป็นไปได้ใน Java generics ไม่ใช่ xyproblem ( meta.stackexchange.com/questions/66377/what-is-the-xy-problem ) ที่ต้องได้รับการออกแบบใหม่ เช่นเดียวกับรูปแบบหรือวิธีการอื่น ๆ มีบางครั้งที่จำเป็นต้องใช้รหัสที่ฉันให้และเวลาที่บางสิ่งที่แตกต่างออกไปโดยสิ้นเชิง (เช่นที่คุณแนะนำในคำตอบนี้)
laz

114

คุณสามารถใช้มันได้เช่นนี้

@SuppressWarnings("unchecked")
public <T extends Animal> T callFriend(String name) {
    return (T)friends.get(name);
}

(ใช่นี่คือรหัสทางกฎหมายดูที่Java Generics: ประเภททั่วไปกำหนดไว้เป็นประเภทส่งคืนเท่านั้น )

ประเภทการคืนจะถูกอนุมานจากผู้โทร อย่างไรก็ตามโปรดสังเกต@SuppressWarningsคำอธิบายประกอบ: ที่บอกคุณว่ารหัสนี้ไม่ได้เป็นประเภทที่ปลอดภัย คุณต้องยืนยันด้วยตัวคุณเองหรือคุณอาจใช้ClassCastExceptionsเวลารันไทม์

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

jerry.<Dog>callFriend("spike").bark();

ในขณะที่สิ่งนี้อาจจะดีกว่าการคัดเลือกนักแสดงเล็กน้อย แต่คุณน่าจะดีกว่าที่จะให้วิธีการที่Animalเป็นนามธรรมแก่ชั้นเรียนtalk()ดังที่ David Schmitt กล่าว


การผูกมัดวิธีไม่ใช่ความตั้งใจจริงๆ ฉันไม่รังเกียจที่จะกำหนดค่าให้กับตัวแปรย่อยและใช้มัน ขอบคุณสำหรับการแก้ปัญหา
Sathish

นี้ทำงานได้อย่างสมบูรณ์แบบเมื่อทำวิธีการเรียกสาย!
Hartmut P.

ฉันชอบไวยากรณ์นี้มาก ฉันคิดว่าใน C # มันเป็นjerry.CallFriend<Dog>(...สิ่งที่ฉันคิดว่าดูดีขึ้น
andho

น่าสนใจว่าjava.util.Collections.emptyList()ฟังก์ชั่นของ JRE นั้นถูกนำไปใช้อย่างเช่นนี้และ javadoc นั้นก็ประกาศตัวเองว่าเป็นประเภทที่ปลอดภัย
Ti Strga

31

คำถามนี้คล้ายกับข้อ 29 ในจาวาที่มีประสิทธิภาพ - "พิจารณาประเภทที่แตกต่างกันของ containerafe" คำตอบของ Laz นั้นใกล้เคียงที่สุดกับโซลูชันของ Bloch อย่างไรก็ตามทั้งการย้ายและรับควรใช้คลาสตัวอักษรเพื่อความปลอดภัย ลายเซ็นจะกลายเป็น:

public <T extends Animal> void addFriend(String name, Class<T> type, T animal);
public <T extends Animal> T callFriend(String name, Class<T> type);

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


17

นอกจากนี้คุณสามารถขอให้วิธีการคืนค่าในประเภทที่กำหนดด้วยวิธีนี้

<T> T methodName(Class<T> var);

ตัวอย่างเพิ่มเติมที่นี่ที่เอกสารคู่มือ Oracle Java


17

นี่คือเวอร์ชั่นที่ง่ายกว่า:

public <T> T callFriend(String name) {
    return (T) friends.get(name); //Casting to T not needed in this case but its a good practice to do
}

รหัสการทำงานอย่างสมบูรณ์:

    public class Test {
        public static class Animal {
            private Map<String,Animal> friends = new HashMap<>();

            public void addFriend(String name, Animal animal){
                friends.put(name,animal);
            }

            public <T> T callFriend(String name){
                return (T) friends.get(name);
            }
        }

        public static class Dog extends Animal {

            public void bark() {
                System.out.println("i am dog");
            }
        }

        public static class Duck extends Animal {

            public void quack() {
                System.out.println("i am duck");
            }
        }

        public static void main(String [] args) {
            Animal animals = new Animal();
            animals.addFriend("dog", new Dog());
            animals.addFriend("duck", new Duck());

            Dog dog = animals.callFriend("dog");
            dog.bark();

            Duck duck = animals.callFriend("duck");
            duck.quack();

        }
    }

1
Casting to T not needed in this case but it's a good practice to doอะไร ฉันหมายถึงถ้าได้รับการดูแลอย่างถูกต้องระหว่างรันไทม์ "แนวปฏิบัติที่ดี" หมายถึงอะไร
Farid

ฉันหมายถึงว่าไม่จำเป็นต้องระบุแคสต์ (T) อย่างชัดเจนเนื่องจากการประกาศชนิดส่งคืน <T> ในการประกาศวิธีการควรเพียงพอ
webjockey

9

ดังที่คุณกล่าวว่าผ่านชั้นเรียนจะเป็นไรคุณสามารถเขียนสิ่งนี้:

public <T extends Animal> T callFriend(String name, Class<T> clazz) {
   return (T) friends.get(name);
}

แล้วใช้มันเช่นนี้

jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();

ไม่สมบูรณ์แบบ แต่นี่ก็ใกล้เคียงกับที่คุณได้รับจาก Java generics มีวิธีการใช้Typesafe Heterogenous Containers (THC) โดยใช้ Super Type Tokensแต่มันมีปัญหาของตัวเองอีกครั้ง


ฉันขอโทษ แต่นี่เป็นคำตอบเดียวกับลาซาดังนั้นคุณจึงกำลังคัดลอกเขาหรือเขากำลังคัดลอกคุณ
James McMahon

เป็นวิธีที่เรียบร้อยในการผ่านประเภท แต่ก็ยังไม่ปลอดภัยชนิดเช่น Schmitt กล่าว ฉันยังสามารถผ่านชั้นเรียนที่แตกต่างกันและ typecasting จะระเบิด mmyers ตอบกลับที่ 2 เพื่อตั้งค่า Type in Return ดูเหมือนจะดีขึ้น
4171 Sathish

4
นีโม่ถ้าคุณตรวจสอบเวลาการโพสต์คุณจะเห็นว่าเราโพสต์พวกเขาในเวลาเดียวกัน นอกจากนี้พวกเขาจะไม่เหมือนกันเพียงสองบรรทัด
Fabian Steeg

@Fabian ฉันโพสต์คำตอบที่คล้ายกัน แต่มีความแตกต่างที่สำคัญระหว่างสไลด์ของ Bloch และสิ่งที่เผยแพร่ใน Java ที่มีประสิทธิภาพ เขาใช้ Class <T> แทน TypeRef <T> แต่นี่ยังเป็นคำตอบที่ดี
Craig P. Motlin

8

จากแนวคิดเดียวกันกับโทเค็นชนิดพิเศษคุณสามารถสร้างรหัสที่พิมพ์เพื่อใช้แทนสตริง:

public abstract class TypedID<T extends Animal> {
  public final Type type;
  public final String id;

  protected TypedID(String id) {
    this.id = id;
    Type superclass = getClass().getGenericSuperclass();
    if (superclass instanceof Class) {
      throw new RuntimeException("Missing type parameter.");
    }
    this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
  }
}

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

Mouse jerry = new Mouse();
TypedID<Dog> spike = new TypedID<Dog>("spike") {};
TypedID<Duck> quacker = new TypedID<Duck>("quacker") {};

jerry.addFriend(spike, new Dog());
jerry.addFriend(quacker, new Duck());

แต่ตอนนี้คุณสามารถใช้คลาสได้ตามที่คุณต้องการโดยไม่ต้องปลดเปลื้อง

jerry.callFriend(spike).bark();
jerry.callFriend(quacker).quack();

นี่เป็นเพียงการซ่อนพารามิเตอร์ประเภทภายใน id แม้ว่ามันหมายความว่าคุณสามารถดึงประเภทจากตัวระบุในภายหลังได้หากคุณต้องการ

คุณจะต้องใช้วิธีการเปรียบเทียบและการแฮชของ TypedID ด้วยเช่นกันหากคุณต้องการเปรียบเทียบอินสแตนซ์ที่เหมือนกันสองรายการของ id


8

"มีวิธีที่จะคิดออกประเภทคืนที่รันไทม์โดยไม่ต้องใช้พารามิเตอร์เสริมใช้ instanceof?"

เป็นอีกทางเลือกหนึ่งที่คุณสามารถใช้รูปแบบของผู้เข้าชมเช่นนี้ ทำให้สัตว์นามธรรมและทำให้มันใช้ Visitable:

abstract public class Animal implements Visitable {
  private Map<String,Animal> friends = new HashMap<String,Animal>();

  public void addFriend(String name, Animal animal){
      friends.put(name,animal);
  }

  public Animal callFriend(String name){
      return friends.get(name);
  }
}

Visitable เพียงหมายความว่าการใช้งานสัตว์ยินดีที่จะยอมรับผู้เข้าชม:

public interface Visitable {
    void accept(Visitor v);
}

และการใช้งานของผู้เยี่ยมชมสามารถเยี่ยมชม subclasses ทั้งหมดของสัตว์:

public interface Visitor {
    void visit(Dog d);
    void visit(Duck d);
    void visit(Mouse m);
}

ตัวอย่างเช่นการนำ Dog มาใช้จะมีลักษณะเช่นนี้:

public class Dog extends Animal {
    public void bark() {}

    @Override
    public void accept(Visitor v) { v.visit(this); }
}

เคล็ดลับที่นี่ก็คือเมื่อสุนัขรู้ว่ามันเป็นประเภทใดมันสามารถทำให้เกิดการเยี่ยมชมที่เกี่ยวข้องมากเกินไปวิธีการของผู้เข้าชม v โดยการส่งผ่าน "นี้" เป็นพารามิเตอร์ คลาสย่อยอื่น ๆ จะใช้ accept () เหมือนกันทุกประการ

คลาสที่ต้องการเรียกเมธอดเฉพาะคลาสย่อยต้องใช้อินเตอร์เฟสผู้เยี่ยมชมดังนี้:

public class Example implements Visitor {

    public void main() {
        Mouse jerry = new Mouse();
        jerry.addFriend("spike", new Dog());
        jerry.addFriend("quacker", new Duck());

        // Used to be: ((Dog) jerry.callFriend("spike")).bark();
        jerry.callFriend("spike").accept(this);

        // Used to be: ((Duck) jerry.callFriend("quacker")).quack();
        jerry.callFriend("quacker").accept(this);
    }

    // This would fire on callFriend("spike").accept(this)
    @Override
    public void visit(Dog d) { d.bark(); }

    // This would fire on callFriend("quacker").accept(this)
    @Override
    public void visit(Duck d) { d.quack(); }

    @Override
    public void visit(Mouse m) { m.squeak(); }
}

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


6

เป็นไปไม่ได้. แผนที่ควรจะรู้ได้อย่างไรว่า subclass ของสัตว์ชนิดใดที่จะได้รับจากการได้รับคีย์ String เท่านั้น

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


5

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

  • TimeSeries<Double> มอบหมายให้กับชั้นในส่วนตัวที่ใช้ double[]
  • TimeSeries<OHLC> มอบหมายให้กับชั้นในส่วนตัวที่ใช้ ArrayList<OHLC>

โปรดดู: การใช้ TypeTokens เพื่อดึงพารามิเตอร์ทั่วไป

ขอบคุณ

Richard Gomes - บล็อก


แน่นอนขอบคุณสำหรับการแบ่งปันข้อมูลเชิงลึกของคุณบทความของคุณอธิบายทุกอย่างจริงๆ!
Yann-GaëlGuéhéneuc

3

มีคำตอบที่ดีมากมายที่นี่ แต่นี่เป็นวิธีที่ฉันใช้สำหรับการทดสอบ Appium ที่การแสดงองค์ประกอบเดียวอาจส่งผลให้ไปสู่สถานะแอปพลิเคชันที่แตกต่างกันตามการตั้งค่าของผู้ใช้ แม้ว่ามันจะไม่ปฏิบัติตามตัวอย่างของ OP แต่ฉันหวังว่ามันจะช่วยใครบางคน

public <T extends MobilePage> T tapSignInButton(Class<T> type) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    //signInButton.click();
    return type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
}
  • MobilePage เป็นสุดยอดประเภทที่ขยายความหมายที่คุณสามารถใช้ลูก ๆ ของมันได้ (duh)
  • type.getConstructor (Param.class ฯลฯ ) ช่วยให้คุณสามารถโต้ตอบกับนวกรรมิกของประเภท ตัวสร้างนี้ควรจะเหมือนกันระหว่างคลาสที่คาดหวังทั้งหมด
  • newInstance นำตัวแปรที่ประกาศแล้วซึ่งคุณต้องการส่งต่อไปยังตัวสร้างออบเจ็กต์ใหม่

หากคุณไม่ต้องการที่จะโยนข้อผิดพลาดคุณสามารถจับพวกเขาเช่น:

public <T extends MobilePage> T tapSignInButton(Class<T> type) {
    // signInButton.click();
    T returnValue = null;
    try {
       returnValue = type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return returnValue;
}

เพื่อความเข้าใจของฉันนี่เป็นวิธีที่ดีที่สุดและหรูหราที่สุดในการใช้ยาสามัญ
cbaldan

2

ไม่อย่างนั้นเพราะอย่างที่คุณพูดคอมไพเลอร์รู้แค่ว่า callFriend () กำลังส่งคืนสัตว์ไม่ใช่สุนัขหรือเป็ด

คุณไม่สามารถเพิ่มเมธอด makeNoise () ที่เป็นนามธรรมลงใน Animal ที่จะนำไปใช้เป็นเปลือกหรือต้มตุ๋นโดย subclasses ของมันได้หรือไม่?


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

2
คุณเพิ่งตอบคำถามของคุณเอง - ถ้าสัตว์มีการกระทำที่ไม่เหมือนใครคุณต้องโยนให้กับสัตว์นั้น หากสัตว์มีการกระทำที่สามารถจัดกลุ่มกับสัตว์อื่น ๆ กว่าที่คุณสามารถกำหนดวิธีนามธรรมหรือเสมือนในชั้นฐานและใช้ที่
Matt Jordan

2

สิ่งที่คุณกำลังมองหาที่นี่คือสิ่งที่เป็นนามธรรม รหัสกับส่วนต่อประสานที่มากขึ้นและคุณควรจะทำการแคสติ้งน้อย

ตัวอย่างด้านล่างเป็น C # แต่แนวคิดยังคงเหมือนเดิม

using System;
using System.Collections.Generic;
using System.Reflection;

namespace GenericsTest
{
class MainClass
{
    public static void Main (string[] args)
    {
        _HasFriends jerry = new Mouse();
        jerry.AddFriend("spike", new Dog());
        jerry.AddFriend("quacker", new Duck());

        jerry.CallFriend<_Animal>("spike").Speak();
        jerry.CallFriend<_Animal>("quacker").Speak();
    }
}

interface _HasFriends
{
    void AddFriend(string name, _Animal animal);

    T CallFriend<T>(string name) where T : _Animal;
}

interface _Animal
{
    void Speak();
}

abstract class AnimalBase : _Animal, _HasFriends
{
    private Dictionary<string, _Animal> friends = new Dictionary<string, _Animal>();


    public abstract void Speak();

    public void AddFriend(string name, _Animal animal)
    {
        friends.Add(name, animal);
    }   

    public T CallFriend<T>(string name) where T : _Animal
    {
        return (T) friends[name];
    }
}

class Mouse : AnimalBase
{
    public override void Speak() { Squeek(); }

    private void Squeek()
    {
        Console.WriteLine ("Squeek! Squeek!");
    }
}

class Dog : AnimalBase
{
    public override void Speak() { Bark(); }

    private void Bark()
    {
        Console.WriteLine ("Woof!");
    }
}

class Duck : AnimalBase
{
    public override void Speak() { Quack(); }

    private void Quack()
    {
        Console.WriteLine ("Quack! Quack!");
    }
}
}

คำถามนี้เกี่ยวกับการเข้ารหัสไม่ใช่แนวคิด

2

ฉันได้ทำสิ่งต่อไปนี้ในส่วนควบคุม lib ของฉัน:

public class Actor<SELF extends Actor> {
    public SELF self() { return (SELF)_self; }
}

subclassing:

public class MyHttpAppSession extends Actor<MyHttpAppSession> {
   ...
}

อย่างน้อยก็สามารถใช้งานได้ภายในคลาสปัจจุบันและเมื่อมีการอ้างอิงที่ชัดเจน การสืบทอดหลายอย่างได้ผล แต่ก็ยุ่งยากมาก :)


1

ฉันรู้ว่านี่เป็นสิ่งที่แตกต่างอย่างสิ้นเชิงจากที่ถาม อีกวิธีหนึ่งในการแก้ไขปัญหานี้ก็คือการไตร่ตรอง ฉันหมายความว่าสิ่งนี้ไม่ได้รับประโยชน์จาก Generics แต่มันช่วยให้คุณเลียนแบบพฤติกรรมที่คุณต้องการแสดง (ทำเปลือกสุนัขทำเป็ดต้มตุ๋น ฯลฯ ) โดยไม่ต้องดูแลการหล่อแบบ:

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

abstract class AnimalExample {
    private Map<String,Class<?>> friends = new HashMap<String,Class<?>>();
    private Map<String,Object> theFriends = new HashMap<String,Object>();

    public void addFriend(String name, Object friend){
        friends.put(name,friend.getClass());
        theFriends.put(name, friend);
    }

    public void makeMyFriendSpeak(String name){
        try {
            friends.get(name).getMethod("speak").invoke(theFriends.get(name));
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    } 

    public abstract void speak ();
};

class Dog extends Animal {
    public void speak () {
        System.out.println("woof!");
    }
}

class Duck extends Animal {
    public void speak () {
        System.out.println("quack!");
    }
}

class Cat extends Animal {
    public void speak () {
        System.out.println("miauu!");
    }
}

public class AnimalExample {

    public static void main (String [] args) {

        Cat felix = new Cat ();
        felix.addFriend("Spike", new Dog());
        felix.addFriend("Donald", new Duck());
        felix.makeMyFriendSpeak("Spike");
        felix.makeMyFriendSpeak("Donald");

    }

}

1

เกี่ยวกับอะไร

public class Animal {
private Map<String,<T extends Animal>> friends = new HashMap<String,<T extends Animal>>();

public <T extends Animal> void addFriend(String name, T animal){
    friends.put(name,animal);
}

public <T extends Animal> T callFriend(String name){
    return friends.get(name);
}

}


0

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


ฉันไม่แน่ใจว่าคุณหมายถึง "จำกัด ประเภทการส่งคืน" Afaik, Java และภาษาที่พิมพ์ส่วนใหญ่จะไม่โอเวอร์โหลดเมธอดหรือฟังก์ชั่นมากเกินไปตามประเภทการคืน ยกตัวอย่างเช่นpublic int getValue(String name){}แยกไม่ออกจากpublic boolean getValue(String name){}มุมมองของคอมไพเลอร์ คุณจะต้องเปลี่ยนประเภทพารามิเตอร์หรือเพิ่ม / ลบพารามิเตอร์เพื่อให้เกินพิกัดที่จะรับรู้ บางทีฉันอาจจะเข้าใจผิดว่าคุณ ...
The One Colter จริง

ใน java คุณสามารถแทนที่เมธอดในคลาสย่อยและระบุประเภทผลตอบแทน "แคบ" (เช่นเฉพาะเจาะจงมากขึ้น) ดูstackoverflow.com/questions/14694852/...
FeralWhippet

-3
public <X,Y> X nextRow(Y cursor) {
    return (X) getRow(cursor);
}

private <T> Person getRow(T cursor) {
    Cursor c = (Cursor) cursor;
    Person s = null;
    if (!c.moveToNext()) {
        c.close();
    } else {
        String id = c.getString(c.getColumnIndex("id"));
        String name = c.getString(c.getColumnIndex("name"));
        s = new Person();
        s.setId(id);
        s.setName(name);
    }
    return s;
}

คุณสามารถส่งคืนประเภทใดก็ได้และรับโดยตรงเช่น ไม่จำเป็นต้องพิมพ์

Person p = nextRow(cursor); // cursor is real database cursor.

นี่เป็นการดีที่สุดถ้าคุณต้องการกำหนดระเบียนชนิดอื่นแทนเคอร์เซอร์จริง


2
คุณพูดว่า "ไม่จำเป็นต้องพิมพ์ตัวอักษร" แต่เห็นได้ชัดว่ามีตัวเชื่อมโยงตัวพิมพ์: (Cursor) cursorตัวอย่างเช่น
Sami Laine

นี่เป็นการใช้ยาสามัญอย่างไม่เหมาะสมโดยสิ้นเชิง รหัสนี้cursorจะต้องเป็นCursorและจะกลับPerson(หรือ null) การใช้ข้อมูลทั่วไปจะลบการตรวจสอบข้อ จำกัด เหล่านี้ทำให้รหัสไม่ปลอดภัย
Andy Turner
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.