Ví dụ: giả sử bạn có hai lớp:
public class TestA {}
public class TestB extends TestA{}
Tôi có một phương thức trả về a List<TestA>
và tôi muốn truyền tất cả các đối tượng trong danh sách TestB
đó để tôi kết thúc bằng a List<TestB>
.
Ví dụ: giả sử bạn có hai lớp:
public class TestA {}
public class TestB extends TestA{}
Tôi có một phương thức trả về a List<TestA>
và tôi muốn truyền tất cả các đối tượng trong danh sách TestB
đó để tôi kết thúc bằng a List<TestB>
.
Câu trả lời:
Đơn giản chỉ cần đúc để List<TestB>
gần như làm việc; nhưng nó không hoạt động vì bạn không thể truyền loại chung của một tham số này sang tham số khác. Tuy nhiên, bạn có thể truyền qua một loại ký tự đại diện trung gian và nó sẽ được cho phép (vì bạn có thể truyền đến và từ các loại ký tự đại diện, chỉ với một cảnh báo không được kiểm tra):
List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;
không thể sử dụng phép tạo tướng, nhưng nếu bạn xác định danh sách theo cách khác thì có thể lưu trữ TestB trong đó:
List<? extends TestA> myList = new ArrayList<TestA>();
Bạn vẫn phải kiểm tra kiểu khi bạn đang sử dụng các đối tượng trong danh sách.
Bạn thực sự không thể *:
Ví dụ được lấy từ hướng dẫn Java này
Giả sử có hai loại A
và B
như vậy B extends A
. Sau đó, mã sau đây là chính xác:
B b = new B();
A a = b;
Mã trước đó là hợp lệ vì B
là một lớp con của A
. Bây giờ, những gì xảy ra với List<A>
và List<B>
?
Hóa ra đó List<B>
không phải là một lớp con List<A>
do đó chúng ta không thể viết
List<B> b = new ArrayList<>();
List<A> a = b; // error, List<B> is not of type List<A>
Hơn nữa, chúng tôi thậm chí không thể viết
List<B> b = new ArrayList<>();
List<A> a = (List<A>)b; // error, List<B> is not of type List<A>
*: Để thực hiện việc truyền có thể, chúng ta cần một cha mẹ chung cho cả hai List<A>
và List<B>
: List<?>
ví dụ. Sau đây là hợp lệ:
List<B> b = new ArrayList<>();
List<?> t = (List<B>)b;
List<A> a = (List<A>)t;
Tuy nhiên, bạn sẽ nhận được một cảnh báo. Bạn có thể ngăn chặn nó bằng cách thêm @SuppressWarnings("unchecked")
vào phương thức của bạn.
Với Java 8, bạn thực sự có thể
List<TestB> variable = collectionOfListA
.stream()
.map(e -> (TestB) e)
.collect(Collectors.toList());
new List<TestB>
, một vòng lặp và một diễn viên?
Vì đây là một câu hỏi được tham khảo rộng rãi và các câu trả lời hiện tại chủ yếu giải thích lý do tại sao nó không hoạt động (hoặc đề xuất các giải pháp nguy hiểm, hacky mà tôi không bao giờ muốn thấy trong mã sản xuất), tôi nghĩ rằng nó phù hợp để thêm một câu trả lời khác, hiển thị những cạm bẫy, và một giải pháp có thể.
Lý do tại sao điều này không hoạt động nói chung đã được chỉ ra trong các câu trả lời khác: Việc chuyển đổi có thực sự hợp lệ hay không phụ thuộc vào loại đối tượng được chứa trong danh sách ban đầu. Khi có các đối tượng trong danh sách có loại không phải là loại TestB
, nhưng thuộc một lớp con khác TestA
, thì diễn viên không hợp lệ.
Tất nhiên, các diễn viên có thể hợp lệ. Đôi khi bạn có thông tin về các loại không có sẵn cho trình biên dịch. Trong những trường hợp này, có thể bỏ danh sách, nhưng nói chung, không nên :
Người ta có thể ...
Ý nghĩa của cách tiếp cận đầu tiên (tương ứng với câu trả lời hiện được chấp nhận) là tinh tế. Nó có vẻ hoạt động đúng ngay từ cái nhìn đầu tiên. Nhưng nếu có các loại sai trong danh sách đầu vào, thì ClassCastException
sẽ bị ném, có thể tại một vị trí hoàn toàn khác trong mã và có thể khó gỡ lỗi này và tìm ra phần tử sai nào lọt vào danh sách. Vấn đề tồi tệ nhất là ai đó thậm chí có thể thêm các yếu tố không hợp lệ sau khi danh sách đã được bỏ, khiến việc gỡ lỗi thậm chí còn khó khăn hơn.
Vấn đề gỡ lỗi các giả mạo này ClassCastExceptions
có thể được giảm bớt với Collections#checkedCollection
họ các phương thức.
Một cách an toàn hơn để chuyển đổi từ a List<Supertype>
sang a List<Subtype>
là thực sự lọc danh sách và tạo một danh sách mới chỉ chứa các thành phần có loại nhất định. Có một số mức độ tự do để thực hiện một phương pháp như vậy (ví dụ về việc xử lý các null
mục), nhưng một cách thực hiện có thể có thể như sau:
/**
* Filter the given list, and create a new list that only contains
* the elements that are (subtypes) of the class c
*
* @param listA The input list
* @param c The class to filter for
* @return The filtered list
*/
private static <T> List<T> filter(List<?> listA, Class<T> c)
{
List<T> listB = new ArrayList<T>();
for (Object a : listA)
{
if (c.isInstance(a))
{
listB.add(c.cast(a));
}
}
return listB;
}
Phương pháp này có thể được sử dụng để lọc các danh sách tùy ý (không chỉ với mối quan hệ Subtype-Supertype đã cho về các tham số loại), như trong ví dụ này:
// A list of type "List<Number>" that actually
// contains Integer, Double and Float values
List<Number> mixedNumbers =
new ArrayList<Number>(Arrays.asList(12, 3.4, 5.6f, 78));
// Filter the list, and create a list that contains
// only the Integer values:
List<Integer> integers = filter(mixedNumbers, Integer.class);
System.out.println(integers); // Prints [12, 78]
Bạn không thể cast List<TestB>
đến List<TestA>
như Steve Kuo đề cập NHƯNG bạn có thể đổ nội dung của List<TestA>
thành List<TestB>
. Hãy thử như sau:
List<TestA> result = new List<TestA>();
List<TestB> data = new List<TestB>();
result.addAll(data);
Tôi đã không thử mã này nên có thể có lỗi nhưng ý tưởng là nó nên lặp qua đối tượng dữ liệu thêm các phần tử (đối tượng TestB) vào Danh sách. Tôi hi vọng nó làm việc cho bạn.
Cách an toàn tốt nhất là triển khai một AbstractList
và các mục trong thực hiện. Tôi đã tạo ListUtil
lớp người trợ giúp:
public class ListUtil
{
public static <TCastTo, TCastFrom extends TCastTo> List<TCastTo> convert(final List<TCastFrom> list)
{
return new AbstractList<TCastTo>() {
@Override
public TCastTo get(int i)
{
return list.get(i);
}
@Override
public int size()
{
return list.size();
}
};
}
public static <TCastTo, TCastFrom> List<TCastTo> cast(final List<TCastFrom> list)
{
return new AbstractList<TCastTo>() {
@Override
public TCastTo get(int i)
{
return (TCastTo)list.get(i);
}
@Override
public int size()
{
return list.size();
}
};
}
}
Bạn có thể sử dụng cast
phương pháp để truyền một cách mù quáng các đối tượng trong danh sách và convert
phương thức để truyền an toàn. Thí dụ:
void test(List<TestA> listA, List<TestB> listB)
{
List<TestB> castedB = ListUtil.cast(listA); // all items are blindly casted
List<TestB> convertedB = ListUtil.<TestB, TestA>convert(listA); // wrong cause TestA does not extend TestB
List<TestA> convertedA = ListUtil.<TestA, TestB>convert(listB); // OK all items are safely casted
}
Cách duy nhất tôi biết là bằng cách sao chép:
List<TestB> list = new ArrayList<TestB> (
Arrays.asList (
testAList.toArray(new TestB[0])
)
);
Khi bạn truyền tham chiếu đối tượng, bạn chỉ truyền kiểu tham chiếu, không phải kiểu đối tượng. đúc sẽ không thay đổi loại thực tế của đối tượng.
Java không có các quy tắc ngầm để chuyển đổi các loại Đối tượng. (Không giống như người nguyên thủy)
Thay vào đó, bạn cần cung cấp cách chuyển đổi loại này sang loại khác và gọi nó bằng tay.
public class TestA {}
public class TestB extends TestA{
TestB(TestA testA) {
// build a TestB from a TestA
}
}
List<TestA> result = ....
List<TestB> data = new List<TestB>();
for(TestA testA : result) {
data.add(new TestB(testA));
}
Điều này dài dòng hơn trong một ngôn ngữ có hỗ trợ trực tiếp, nhưng nó hoạt động và bạn không cần phải làm điều này rất thường xuyên.
nếu bạn có một đối tượng của lớp TestA
, bạn không thể sử dụng nó TestB
. mỗi TestB
là một TestA
, nhưng không phải là cách khác.
trong đoạn mã sau:
TestA a = new TestA();
TestB b = (TestB) a;
dòng thứ hai sẽ ném a ClassCastException
.
bạn chỉ có thể truyền TestA
tham chiếu nếu chính đối tượng đó là TestB
. ví dụ:
TestA a = new TestB();
TestB b = (TestB) a;
vì vậy, không phải lúc nào bạn cũng có thể đưa danh sách TestA
vào danh sách TestB
.
Bạn có thể sử dụng selectInstances
phương thức trong Bộ sưu tập Eclipse . Điều này sẽ liên quan đến việc tạo ra một bộ sưu tập mới, tuy nhiên sẽ không hiệu quả như giải pháp được chấp nhận sử dụng đúc.
List<CharSequence> parent =
Arrays.asList("1","2","3", new StringBuffer("4"));
List<String> strings =
Lists.adapt(parent).selectInstancesOf(String.class);
Assert.assertEquals(Arrays.asList("1","2","3"), strings);
Tôi đã đưa StringBuffer
vào ví dụ để chỉ ra rằng selectInstances
không chỉ truyền tải loại mà còn lọc nếu bộ sưu tập có chứa các loại hỗn hợp.
Lưu ý: Tôi là người đi làm cho Bộ sưu tập Eclipse.
Điều này có thể là do loại tẩy. Bạn sẽ thấy rằng
List<TestA> x = new ArrayList<TestA>();
List<TestB> y = new ArrayList<TestB>();
x.getClass().equals(y.getClass()); // true
Trong nội bộ cả hai danh sách là loại List<Object>
. Vì lý do đó, bạn không thể bỏ cái này sang cái khác - không có gì để đúc.
Integer j = 42; Integer i = (Integer) j;
hoạt động tốt, cả hai đều là số nguyên, cả hai đều có cùng một lớp, vì vậy "không có gì để đúc".
Vấn đề là phương thức của bạn KHÔNG trả về danh sách TestA nếu nó chứa TestB, vậy nếu nó được gõ chính xác thì sao? Sau đó, diễn viên này:
class TestA{};
class TestB extends TestA{};
List<? extends TestA> listA;
List<TestB> listB = (List<TestB>) listA;
hoạt động tốt như bạn có thể hy vọng (Eclipse cảnh báo bạn về một dàn diễn viên không được kiểm soát, đó chính xác là những gì bạn đang làm, vì vậy meh). Vì vậy, bạn có thể sử dụng điều này để giải quyết vấn đề của bạn? Trên thực tế bạn có thể vì điều này:
List<TestA> badlist = null; // Actually contains TestBs, as specified
List<? extends TestA> talist = badlist; // Umm, works
List<TextB> tblist = (List<TestB>)talist; // TADA!
Chính xác những gì bạn yêu cầu, phải không? hoặc để thực sự chính xác:
List<TestB> tblist = (List<TestB>)(List<? extends TestA>) badlist;
dường như biên dịch tốt cho tôi
class MyClass {
String field;
MyClass(String field) {
this.field = field;
}
}
@Test
public void testTypeCast() {
List<Object> objectList = Arrays.asList(new MyClass("1"), new MyClass("2"));
Class<MyClass> clazz = MyClass.class;
List<MyClass> myClassList = objectList.stream()
.map(clazz::cast)
.collect(Collectors.toList());
assertEquals(objectList.size(), myClassList.size());
assertEquals(objectList, myClassList);
}
Xét nghiệm này cho thấy cách để cast List<Object>
để List<MyClass>
. Nhưng bạn cần chú ý rằng objectList
phải chứa các thể hiện cùng loại với MyClass
. Và ví dụ này có thể được xem xét khi List<T>
được sử dụng. Đối với mục đích này, nhận trường Class<T> clazz
trong constructor và sử dụng nó thay vì MyClass.class
.
Khá kỳ lạ là việc truyền danh sách theo cách thủ công vẫn không được cung cấp bởi một số hộp công cụ triển khai một cái gì đó như:
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends E, E> List<T> cast(List<E> list) {
return (List) list;
}
Tất nhiên, điều này sẽ không kiểm tra từng mục một, nhưng đó chính xác là những gì chúng tôi muốn tránh ở đây, nếu chúng tôi biết rằng việc triển khai của chúng tôi chỉ cung cấp loại phụ.