Làm thế nào để tôi thực hiện kiểu trả về chung chung?


588

Xem xét ví dụ này (điển hình trong sách OOP):

Tôi có một Animallớp học, nơi mỗi người Animalcó thể có nhiều bạn bè.
Và các lớp con thích Dog, Duck, Mousevv mà thêm hành vi cụ thể như bark(), quack()vv

Đây là Animallớp học:

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

Và đây là một số đoạn mã với nhiều kiểu chữ:

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

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

Có cách nào tôi có thể sử dụng thuốc generic cho kiểu trả về để loại bỏ kiểu chữ không, để tôi có thể nói

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

Đây là một số mã ban đầu với kiểu trả về được truyền đến phương thức dưới dạng tham số không bao giờ được sử dụng.

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

Có cách nào để tìm ra kiểu trả về trong thời gian chạy mà không sử dụng tham số phụ instanceofkhông? Hoặc ít nhất bằng cách vượt qua một lớp của loại thay vì một ví dụ giả.
Tôi hiểu rằng generic là để kiểm tra kiểu thời gian biên dịch, nhưng có một cách giải quyết cho việc này không?

Câu trả lời:


903

Bạn có thể định nghĩa callFriendtheo cách này:

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

Sau đó gọi nó như vậy:

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

Mã này có lợi ích là không tạo ra bất kỳ cảnh báo trình biên dịch nào. Tất nhiên, đây thực sự chỉ là một phiên bản đúc được cập nhật từ những ngày trước chung chung và không thêm bất kỳ sự an toàn nào.


29
... nhưng vẫn không có kiểu kiểm tra thời gian biên dịch giữa các tham số của lệnh gọi callFriend ().
David Schmitt

2
Đây là câu trả lời tốt nhất cho đến nay - nhưng bạn nên thay đổi addFriend theo cùng một cách. Nó làm cho việc viết lỗi trở nên khó khăn hơn vì bạn cần lớp đó theo đúng nghĩa đen ở cả hai nơi.
Craig P. Motlin

@Jaider, không hoàn toàn giống nhau nhưng điều này sẽ hoạt động: // Lớp động vật công khai T CallFriend <T> (tên chuỗi) trong đó T: Animal {return friends [name] as T; } // Lớp gọi jerry.CallFriend <Dog> ("spike"). Bark (); jerry.CallFriend <Duck> ("quacker"). Quack ();
Nestor Ledon

124

Không. Trình biên dịch không thể biết kiểu jerry.callFriend("spike")nào sẽ trả về. Ngoài ra, việc triển khai của bạn chỉ ẩn các diễn viên trong phương thức mà không có bất kỳ loại an toàn bổ sung nào. Xem xét điều này:

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

Trong trường hợp cụ thể này, việc tạo một talk()phương thức trừu tượng và ghi đè nó một cách thích hợp trong các lớp con sẽ phục vụ bạn tốt hơn nhiều:

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

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

10
Mặc dù phương pháp mmyer có thể hoạt động, tôi nghĩ phương pháp này là lập trình OO tốt hơn và sẽ giúp bạn tiết kiệm một số rắc rối trong tương lai.
James McMahon

1
Đây là cách chính xác để đạt được kết quả tương tự. Lưu ý mục tiêu là để có được hành vi cụ thể của lớp dẫn xuất trong thời gian chạy mà không cần tự viết mã rõ ràng để thực hiện kiểm tra và đúc kiểu xấu xí. Phương pháp được đề xuất bởi @laz hoạt động, nhưng ném loại an toàn ra khỏi cửa sổ. Phương thức này yêu cầu ít dòng mã hơn (vì việc triển khai phương thức bị ràng buộc muộn và dù sao cũng phải tìm kiếm thời gian chạy) nhưng bạn vẫn dễ dàng xác định hành vi duy nhất cho từng lớp con của Động vật.
dcow

Nhưng câu hỏi ban đầu không hỏi về loại an toàn. Cách tôi đọc nó, người hỏi chỉ muốn biết liệu có cách nào để tận dụng thuốc generic để tránh phải bỏ vai.
laz

2
@laz: có, câu hỏi ban đầu - như được đặt ra - không phải là về loại an toàn. Điều đó không thay đổi thực tế rằng có một cách an toàn để thực hiện điều đó, loại bỏ các thất bại trong lớp. Xem thêm weblogs.asp.net/alex_papadimoulis/archive/2005/05/25/ Khăn
David Schmitt

3
Tôi không đồng ý về điều đó, nhưng chúng tôi đang xử lý Java và tất cả các quyết định / quyết định thiết kế của nó. Tôi thấy câu hỏi này chỉ là cố gắng tìm hiểu những gì có thể có trong các tổng quát Java, chứ không phải là một xyprobols ( meta.stackexchange.com/questions/66377/what-is-the-xy-probols ) cần được thiết kế lại. Giống như bất kỳ mô hình hoặc cách tiếp cận nào, có những lúc mã tôi cung cấp là phù hợp và có những lúc cần một cái gì đó hoàn toàn khác (như những gì bạn đã đề xuất trong câu trả lời này).
laz

114

Bạn có thể thực hiện nó như thế này:

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

(Có, đây là mã hợp pháp; xem Java Generics: Loại chung được định nghĩa chỉ là loại trả về .)

Kiểu trả về sẽ được suy ra từ người gọi. Tuy nhiên, lưu ý @SuppressWarningschú thích: cho bạn biết rằng mã này không an toàn . Bạn phải tự xác minh nó, hoặc bạn có thể nhận được ClassCastExceptionstrong thời gian chạy.

Thật không may, cách bạn đang sử dụng nó (mà không gán giá trị trả về cho một biến tạm thời), cách duy nhất để làm cho trình biên dịch hài lòng là gọi nó như thế này:

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

Mặc dù điều này có thể đẹp hơn một chút so với việc đúc, nhưng có lẽ tốt hơn là bạn nên cho Animallớp một talk()phương pháp trừu tượng , như David Schmitt nói.


Phương pháp xâu chuỗi không thực sự là một ý định. tôi không phiền khi gán giá trị cho biến Subtyped và sử dụng nó. Cảm ơn giải pháp.
Sathish

Điều này hoạt động hoàn hảo khi thực hiện phương thức gọi chuỗi!
Hartmut P.

Tôi thực sự thích cú pháp này. Tôi nghĩ rằng trong C # đó là jerry.CallFriend<Dog>(...điều mà tôi nghĩ có vẻ tốt hơn.
andho

Điều thú vị là java.util.Collections.emptyList()chức năng riêng của JRE được triển khai chính xác như thế này và javadoc tự quảng cáo là an toàn.
Ti Strga

31

Câu hỏi này rất giống với Mục 29 trong Java hiệu quả - "Hãy xem xét các thùng chứa không đồng nhất về loại an toàn". Câu trả lời của Laz là gần nhất với giải pháp của Bloch. Tuy nhiên, cả đặt và nhận nên sử dụng Class theo nghĩa đen cho an toàn. Các chữ ký sẽ trở thành:

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

Bên trong cả hai phương pháp, bạn nên kiểm tra xem các tham số có lành mạnh không. Xem Java hiệu quả và lớp javadoc để biết thêm thông tin.


17

Ngoài ra, bạn có thể yêu cầu phương thức trả về giá trị theo kiểu đã cho theo cách này

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

Thêm ví dụ ở đây tại tài liệu Java Java


17

Đây là phiên bản đơn giản hơn:

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
}

Mã làm việc đầy đủ:

    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. Ý tôi là nếu nó được chăm sóc đúng cách trong thời gian chạy thì "thực hành tốt" nghĩa là gì?
Farid

Tôi có nghĩa là để nói đúc rõ ràng (T) không cần thiết kể từ khi kiểu trả declartion <T> trên tờ khai phương pháp nên là đủ đủ
webjockey

9

Như bạn đã nói vượt qua một lớp sẽ ổn, bạn có thể viết điều này:

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

Và sau đó sử dụng nó như thế này:

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

Không hoàn hảo, nhưng điều này là khá nhiều như bạn có được với Java genericics. Có một cách để triển khai Container không đồng nhất Typeafe (THC) bằng cách sử dụng Token siêu loại , nhưng điều đó lại có vấn đề riêng.


Tôi xin lỗi nhưng đây là câu trả lời chính xác giống như laz, vì vậy bạn đang sao chép anh ta hoặc anh ta đang sao chép bạn.
James McMahon

Đó là một cách gọn gàng để vượt qua Loại. Nhưng nó vẫn không phải là loại an toàn như Schmitt nói. Tôi vẫn có thể vượt qua một lớp khác và việc đánh máy sẽ đánh bom. mmyer Trả lời lần 2 để đặt loại Trả về có vẻ tốt hơn
Sathish

4
Nemo, nếu bạn kiểm tra thời gian đăng bài, bạn sẽ thấy chúng tôi đã đăng chúng khá chính xác cùng một lúc. Ngoài ra, chúng không hoàn toàn giống nhau, chỉ có hai dòng.
Fabian Steeg

@Fabian Tôi đã đăng một câu trả lời tương tự, nhưng có một sự khác biệt quan trọng giữa các slide của Bloch và những gì được xuất bản trong Java hiệu quả. Anh ta sử dụng Class <T> thay vì TypeRef <T>. Nhưng đây vẫn là một câu trả lời tuyệt vời.
Craig P. Motlin

8

Dựa trên ý tưởng tương tự như Mã thông báo siêu loại, bạn có thể tạo id được nhập để sử dụng thay vì chuỗi:

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

Nhưng tôi nghĩ điều này có thể đánh bại mục đích, vì bây giờ bạn cần tạo các đối tượng id mới cho mỗi chuỗi và giữ chúng (hoặc tái cấu trúc chúng với thông tin loại chính xác).

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

Nhưng bây giờ bạn có thể sử dụng lớp theo cách bạn muốn ban đầu, mà không cần phôi.

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

Đây chỉ là ẩn tham số loại bên trong id, mặc dù điều đó có nghĩa là bạn có thể truy xuất loại từ định danh sau nếu muốn.

Bạn cũng cần phải thực hiện các phương pháp so sánh và băm của TypedID nếu bạn muốn có thể so sánh hai trường hợp giống nhau của một id.


8

"Có cách nào để tìm ra kiểu trả về trong thời gian chạy mà không có tham số phụ sử dụng instanceof không?"

Là một giải pháp thay thế, bạn có thể sử dụng mẫu Khách truy cập như thế này. Làm cho động vật trừu tượng và làm cho nó thực hiện có thể truy cập:

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

Có thể truy cập chỉ có nghĩa là việc triển khai Động vật sẵn sàng chấp nhận khách truy cập:

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

Và một triển khai khách truy cập có thể truy cập tất cả các lớp con của động vật:

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

Vì vậy, ví dụ một triển khai Dog sẽ như thế này:

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

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

Mẹo ở đây là Dog biết nó thuộc loại nào, nó có thể kích hoạt phương thức truy cập quá tải có liên quan của khách truy cập v bằng cách chuyển "this" làm tham số. Các lớp con khác sẽ triển khai accept () chính xác theo cùng một cách.

Sau đó, lớp muốn gọi các phương thức cụ thể của lớp con phải triển khai giao diện Khách truy cập như thế này:

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

Tôi biết đó là nhiều giao diện và phương thức hơn bạn mặc cả, nhưng đó là một cách tiêu chuẩn để xử lý mọi kiểu con cụ thể với các kiểm tra thể hiện chính xác bằng 0 và các kiểu không. Và tất cả đều được thực hiện theo kiểu bất khả tri ngôn ngữ tiêu chuẩn, vì vậy nó không chỉ dành cho Java mà bất kỳ ngôn ngữ OO nào cũng sẽ hoạt động như nhau.


6

Không thể. Làm thế nào để Bản đồ biết được lớp con Thú nào sẽ được nhận, chỉ được cung cấp khóa Chuỗi?

Cách duy nhất có thể là nếu mỗi Thú chỉ chấp nhận một loại bạn bè (thì đó có thể là tham số của lớp Thú) hoặc phương thức callFriend () có tham số loại. Nhưng có vẻ như bạn đang thiếu điểm thừa kế: đó là bạn chỉ có thể xử lý các lớp con một cách thống nhất khi sử dụng riêng các phương thức siêu lớp.


5

Tôi đã viết một bài viết có chứa bằng chứng về khái niệm, các lớp hỗ trợ và một lớp kiểm tra để chứng minh làm thế nào các loại Token có thể được lấy ra bởi các lớp của bạn khi chạy. Tóm lại, nó cho phép bạn ủy quyền cho các triển khai thay thế tùy thuộc vào các tham số chung thực tế được truyền bởi người gọi. Thí dụ:

  • TimeSeries<Double> đại biểu cho một lớp bên trong tư nhân sử dụng double[]
  • TimeSeries<OHLC> đại biểu cho một lớp bên trong tư nhân sử dụng ArrayList<OHLC>

Xem: Sử dụng TypeTokens để truy xuất các tham số chung

Cảm ơn

Richard Gomes - Blog


Thật vậy, cảm ơn bạn đã chia sẻ cái nhìn sâu sắc của bạn, bài viết của bạn thực sự giải thích tất cả!
Yann-Gaël Guéhéneuc

3

Có rất nhiều câu trả lời tuyệt vời ở đây, nhưng đây là cách tiếp cận tôi đã thực hiện cho bài kiểm tra Appium trong đó hành động trên một yếu tố duy nhất có thể dẫn đến các trạng thái ứng dụng khác nhau dựa trên cài đặt của người dùng. Mặc dù nó không tuân theo các quy ước của ví dụ của OP, tôi hy vọng nó sẽ giúp được ai đó.

public <T extends MobilePage> T tapSignInButton(Class<T> type) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    //signInButton.click();
    return type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
}
  • MobilePage là siêu hạng mà loại mở rộng có nghĩa là bạn có thể sử dụng bất kỳ đứa con nào của nó (duh)
  • type.getConstructor (Param. class, v.v.) cho phép bạn tương tác với hàm tạo của loại. Hàm tạo này phải giống nhau giữa tất cả các lớp dự kiến.
  • newInstance lấy một biến được khai báo mà bạn muốn truyền cho hàm tạo đối tượng mới

Nếu bạn không muốn ném lỗi, bạn có thể bắt chúng như vậy:

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

Theo hiểu biết của tôi, đây là cách tốt nhất và thanh lịch nhất để sử dụng Generics.
cbaldan

2

Không thực sự, vì như bạn nói, trình biên dịch chỉ biết rằng callFriend () đang trả lại một Animal, không phải là Dog hay Duck.

Bạn có thể không thêm một phương thức makeNaty () trừu tượng vào Animal mà sẽ được triển khai như một vỏ cây hoặc quạc bởi các lớp con của nó không?


1
Điều gì xảy ra nếu các động vật có nhiều phương pháp thậm chí không thuộc một hành động chung có thể được trừu tượng hóa? Tôi cần điều này để liên lạc giữa các lớp con với các hành động khác nhau trong đó tôi không sao khi chuyển Loại, không phải là một thể hiện.
Sathish

2
Bạn thực sự vừa trả lời câu hỏi của riêng bạn - nếu một con vật có hành động độc đáo, thì bạn phải ném vào con vật cụ thể đó. Nếu một con vật có một hành động có thể được nhóm với các con vật khác, thì bạn có thể định nghĩa một phương thức trừu tượng hoặc ảo trong một lớp cơ sở và sử dụng nó.
Matt Jordan

2

Những gì bạn đang tìm kiếm ở đây là trừu tượng. Mã chống lại các giao diện nhiều hơn và bạn sẽ phải thực hiện đúc ít hơn.

Ví dụ dưới đây là trong C # nhưng khái niệm vẫn giữ nguyên.

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

Câu hỏi này có liên quan đến mã hóa chứ không phải khái niệm.

2

Tôi đã làm như sau trong lib kontraktor của tôi:

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

phân lớp:

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

ít nhất điều này hoạt động bên trong lớp hiện tại và khi có một tham chiếu gõ mạnh. Nhiều kế thừa hoạt động, nhưng sau đó thực sự khó khăn :)


1

Tôi biết đây là một điều hoàn toàn khác mà người ta đã hỏi. Một cách khác để giải quyết điều này sẽ là sự phản ánh. Ý tôi là, điều này không mang lại lợi ích từ Generics, nhưng nó cho phép bạn mô phỏng, theo một cách nào đó, hành vi bạn muốn thực hiện (tạo ra một con chó sủa, tạo ra một con vịt, v.v.) mà không cần quan tâm đến việc đúc kiểu:

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

Thế còn

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

Có một cách tiếp cận khác, bạn có thể thu hẹp kiểu trả về khi bạn ghi đè một phương thức. Trong mỗi lớp con, bạn sẽ phải ghi đè callFriend để trả về lớp con đó. Chi phí sẽ là nhiều khai báo của callFriend, nhưng bạn có thể cô lập các phần chung cho một phương thức được gọi là nội bộ. Điều này có vẻ đơn giản hơn nhiều so với các giải pháp được đề cập ở trên và không cần thêm đối số để xác định loại trả về.


Tôi không chắc ý của bạn là gì khi "thu hẹp kiểu trả về". Afaik, Java và hầu hết các ngôn ngữ được gõ không quá tải các phương thức hoặc hàm dựa trên kiểu trả về. Ví dụ, public int getValue(String name){}không thể phân biệt từ public boolean getValue(String name){}quan điểm của trình biên dịch. Bạn sẽ cần phải thay đổi loại tham số hoặc thêm / xóa tham số để quá tải được nhận ra. Có lẽ tôi chỉ hiểu lầm bạn thôi ...
The One True Colter

trong java, bạn có thể ghi đè một phương thức trong một lớp con và chỉ định kiểu trả về "hẹp" (nghĩa là cụ thể hơn). Xem stackoverflow.com/questions/14694852/ cấp .
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;
}

Bạn có thể trả lại bất kỳ loại và nhận trực tiếp như thế nào. Đừng cần đánh máy.

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

Điều này là tốt nhất nếu bạn muốn tùy chỉnh bất kỳ loại hồ sơ khác thay vì con trỏ thực sự.


2
Bạn nói rằng "không cần typecast", nhưng rõ ràng có liên quan đến typecasting: (Cursor) cursorví dụ.
Sami Laine

Đây là một cách sử dụng hoàn toàn không phù hợp của thuốc generic. Mã này yêu cầu cursorphải là một Cursorvà sẽ luôn trả về một Person(hoặc null). Sử dụng generic sẽ loại bỏ việc kiểm tra các ràng buộc này, làm cho mã không an toàn.
Andy Turner
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.