Cách hiệu quả nhất để chuyển Danh sách <SubClass> sang Danh sách <BaseClass>


134

Tôi có một List<SubClass>cái mà tôi muốn coi là a List<BaseClass>. Có vẻ như đó không phải là vấn đề vì SubClassviệc chuyển sang một cái BaseClassrất nhanh, nhưng trình biên dịch của tôi phàn nàn rằng việc chọn diễn viên là không thể.

Vậy, cách tốt nhất để có được một tham chiếu đến các đối tượng giống như là List<BaseClass>gì?

Ngay bây giờ tôi chỉ tạo một danh sách mới và sao chép danh sách cũ:

List<BaseClass> convertedList = new ArrayList<BaseClass>(listOfSubClass)

Nhưng theo tôi hiểu thì phải tạo ra một danh sách hoàn toàn mới. Tôi muốn tham khảo danh sách ban đầu, nếu có thể!


3
câu trả lời bạn có ở đây: stackoverflow.com/questions/662508/ từ
lukastymo

Câu trả lời:


175

Cú pháp cho loại bài tập này sử dụng ký tự đại diện:

List<SubClass> subs = ...;
List<? extends BaseClass> bases = subs;

Điều quan trọng là nhận ra rằng một List<SubClass>không thể thay thế với một List<BaseClass>. Mã giữ lại một tham chiếu đến List<SubClass>sẽ mong muốn mọi mục trong danh sách là a SubClass. Nếu một phần khác của mã được gọi là danh sách List<BaseClass>, trình biên dịch sẽ không khiếu nại khi a BaseClasshoặc AnotherSubClassđược chèn. Nhưng điều này sẽ gây ra một ClassCastExceptionđoạn mã đầu tiên, giả định rằng mọi thứ trong danh sách là a SubClass.

Các bộ sưu tập chung không hoạt động giống như các mảng trong Java. Mảng là covariant; đó là, nó được phép làm điều này:

SubClass[] subs = ...;
BaseClass[] bases = subs;

Điều này được cho phép, bởi vì mảng "biết" loại phần tử của nó. Nếu ai đó cố lưu trữ thứ gì đó không phải là một thể hiện SubClasstrong mảng (thông qua basestham chiếu), một ngoại lệ thời gian chạy sẽ bị ném.

Bộ sưu tập chung không "biết" loại thành phần của chúng; thông tin này bị "xóa" tại thời điểm biên dịch. Do đó, họ không thể tăng ngoại lệ thời gian chạy khi cửa hàng không hợp lệ xảy ra. Thay vào đó, a ClassCastExceptionsẽ được nâng lên ở một điểm xa, khó liên kết trong mã khi một giá trị được đọc từ bộ sưu tập. Nếu bạn chú ý đến trình biên dịch về an toàn kiểu, bạn sẽ tránh được các lỗi loại này khi chạy.


Cần lưu ý rằng với Mảng, thay vì ClassCastException khi truy xuất một đối tượng không thuộc loại SubClass (hoặc dẫn xuất), bạn sẽ nhận được ArrayStoreException khi chèn.
Axel

Đặc biệt cảm ơn vì lời giải thích tại sao hai danh sách không thể được coi là giống nhau.
Riley Lark

Hệ thống kiểu Java giả vờ rằng các mảng là covariant, nhưng chúng không thực sự thay thế được, được chứng minh bởi ArrayStoreException.
Paŭlo Ebermann

38

erickson đã giải thích lý do tại sao bạn không thể làm điều này, nhưng đây là một số giải pháp:

Nếu bạn chỉ muốn đưa các phần tử ra khỏi danh sách cơ sở của mình, về nguyên tắc phương thức nhận của bạn nên được khai báo là lấy a List<? extends BaseClass>.

Nhưng nếu không và bạn không thể thay đổi nó, bạn có thể bọc danh sách Collections.unmodifiableList(...), cho phép trả về Danh sách siêu kiểu của tham số. (Nó tránh được vấn đề an toàn kiểu bằng cách ném UnsupportedOperationException khi thử chèn.)


Tuyệt quá! không biết điều đó.
keuleJ

Cảm ơn rât nhiều!
Woland

14

Như @erickson đã giải thích, nếu bạn thực sự muốn tham chiếu đến danh sách ban đầu, hãy đảm bảo không có mã nào chèn bất cứ thứ gì vào danh sách đó nếu bạn muốn sử dụng lại theo khai báo ban đầu. Cách đơn giản nhất để có được nó là chỉ đưa nó vào một danh sách không rõ ràng cũ:

List<BaseClass> baseList = (List)new ArrayList<SubClass>();

Tôi sẽ không đề xuất điều này nếu bạn không biết điều gì xảy ra với Danh sách và sẽ đề nghị bạn thay đổi bất kỳ mã nào cần Danh sách để chấp nhận Danh sách bạn có.


3

Dưới đây là một đoạn hữu ích hoạt động. Nó xây dựng một danh sách mảng mới nhưng việc tạo đối tượng JVM qua đầu là không đáng kể.

Tôi thấy các câu trả lời khác là không nhất thiết phức tạp.

List<BaseClass> baselist = new ArrayList<>(sublist);

1
Cảm ơn bạn vì đoạn mã này, có thể cung cấp một số trợ giúp ngay lập tức. Một lời giải thích phù hợp sẽ cải thiện đáng kể giá trị giáo dục của nó bằng cách chỉ ra lý do tại sao đây là một giải pháp tốt cho vấn đề và sẽ giúp nó hữu ích hơn cho những độc giả tương lai với những câu hỏi tương tự, nhưng không giống nhau. Vui lòng chỉnh sửa câu trả lời của bạn để thêm giải thích và đưa ra dấu hiệu về những hạn chế và giả định được áp dụng. Cụ thể, điều này khác với mã trong câu hỏi tạo ra một danh sách mới như thế nào?
Toby Speight

Chi phí không phải là không đáng kể, vì nó cũng phải sao chép (nông) mỗi tham chiếu. Vì vậy, nó chia tỷ lệ với kích thước của danh sách, vì vậy nó là một hoạt động O (n).
john16384

2

Tôi đã bỏ lỡ câu trả lời khi bạn chỉ chọn danh sách ban đầu, sử dụng một nhóm kép. Vì vậy, đây là cho sự hoàn chỉnh:

List<BaseClass> baseList = (List<BaseClass>)(List<?>)subList;

Không có gì được sao chép, và hoạt động nhanh chóng. Tuy nhiên, bạn đang lừa trình biên dịch ở đây, do đó bạn phải chắc chắn không sửa đổi danh sách theo cách subListbắt đầu chứa các mục thuộc loại phụ khác. Khi xử lý các danh sách bất biến, điều này thường không phải là một vấn đề.


1

List<BaseClass> convertedList = Collections.checkedList(listOfSubClass, BaseClass.class)


5
Điều này không nên hoạt động, vì đối với phương thức này, tất cả các đối số và kết quả đều có cùng tham số loại.
Paŭlo Ebermann

lẻ, bạn nói đúng vì một số lý do, tôi đã có ấn tượng rằng phương pháp này rất hữu ích để làm nổi bật một bộ sưu tập chung chung. vẫn có thể được sử dụng theo cách này và nó sẽ cung cấp một bộ sưu tập "an toàn" nếu bạn muốn sử dụng các chế độ triệt tiêu.
jtahlborn

1

Những gì bạn đang cố gắng làm là rất hữu ích và tôi thấy rằng tôi cần phải làm điều đó rất thường xuyên trong mã mà tôi viết. Một trường hợp sử dụng ví dụ:

Giả sử chúng tôi có một giao diện Foovà chúng tôi có một zorkinggói có ZorkingFooManagertạo và quản lý các phiên bản của gói riêng tư ZorkingFoo implements Foo. (Một kịch bản rất phổ biến.)

Vì vậy, ZorkingFooManagercần phải chứa một private Collection<ZorkingFoo> zorkingFoosnhưng nó cần phải phơi bày a public Collection<Foo> getAllFoos().

Hầu hết các lập trình viên java sẽ không nghĩ hai lần trước khi thực hiện getAllFoos()như phân bổ một cái mới ArrayList<Foo>, điền vào nó với tất cả các yếu tố từ zorkingFoosvà trả lại nó. Tôi thích giải trí với suy nghĩ rằng khoảng 30% tất cả các chu kỳ đồng hồ được sử dụng bởi mã java chạy trên hàng triệu máy trên khắp hành tinh sẽ không làm được gì ngoài việc tạo ra các bản sao ArrayLists vô dụng như vậy được thu gom rác sau vài giây.

Tất nhiên, giải pháp cho vấn đề này là xuống bộ sưu tập. Đây là cách tốt nhất để làm điều đó:

static <T,U extends T> List<T> downCastList( List<U> list )
{
    return castList( list );
}

Điều này đưa chúng ta đến castList()chức năng:

static <T,E> List<T> castList( List<E> list )
{
    @SuppressWarnings( "unchecked" )
    List<T> result = (List<T>)list;
    return result;
}

Biến trung gian resultlà cần thiết do sự sai lệch của ngôn ngữ java:

  • return (List<T>)list;tạo ra một ngoại lệ "diễn viên không được kiểm tra"; càng xa càng tốt; nhưng sau đó:

  • @SuppressWarnings( "unchecked" ) return (List<T>)list; là việc sử dụng bất hợp pháp các chú thích cảnh báo đàn áp.

Vì vậy, mặc dù nó không được sử dụng nhiều hơn @SuppressWarningstrên một returncâu lệnh, nhưng rõ ràng vẫn ổn khi sử dụng nó trên một bài tập, vì vậy biến "kết quả" bổ sung sẽ giải quyết vấn đề này. (Nó nên được tối ưu hóa đi bằng trình biên dịch hoặc bằng JIT.)


0

Một cái gì đó như thế này cũng hoạt động:

public static <T> List<T> convertListWithExtendableClasses(
    final List< ? extends T> originalList,
    final Class<T> clazz )
{
    final List<T> newList = new ArrayList<>();
    for ( final T item : originalList )
    {
        newList.add( item );
    }// for
    return newList;
}

Không thực sự biết tại sao clazz lại cần thiết trong Eclipse ..


0

Làm thế nào về việc đúc tất cả các yếu tố. Nó sẽ tạo một danh sách mới, nhưng sẽ tham chiếu các đối tượng ban đầu từ danh sách cũ.

List<BaseClass> convertedList = listOfSubClass.map(x -> (BaseClass)x).collect(Collectors.toList());

Đây là đoạn mã hoàn chỉnh hoạt động để chuyển danh sách lớp con thành siêu lớp.
kanaparthikiran

0

Đây là đoạn mã làm việc hoàn chỉnh bằng Generics, để chuyển danh sách lớp con thành siêu lớp.

Phương thức người gọi vượt qua loại lớp con

List<SubClass> subClassParam = new ArrayList<>();    
getElementDefinitionStatuses(subClassParam);

Phương thức Callee chấp nhận bất kỳ kiểu con nào của lớp cơ sở

private static List<String> getElementDefinitionStatuses(List<? extends 
    BaseClass> baseClassVariableName) {
     return allElementStatuses;
    }
}
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.