Loại an toàn: Bỏ chọn diễn viên


266

Trong tệp ngữ cảnh ứng dụng mùa xuân của tôi, tôi có một cái gì đó như:

<util:map id="someMap" map-class="java.util.HashMap" key-type="java.lang.String" value-type="java.lang.String">
    <entry key="some_key" value="some value" />
    <entry key="some_key_2" value="some value" />   
</util:map>

Trong lớp java, việc thực hiện trông như sau:

private Map<String, String> someMap = new HashMap<String, String>();
someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");

Trong Eclipse, tôi thấy một cảnh báo có nội dung:

Loại an toàn: Bỏ chọn truyền từ Object sang HashMap

Tôi đã làm gì sai? Làm thế nào để tôi giải quyết vấn đề?


Tôi đã đưa ra một thói quen để thực sự kiểm tra diễn viên với HashMap được tham số hóa, loại bỏ cảnh báo truyền không được kiểm tra: link Tôi nói đây là giải pháp "chính xác", nhưng liệu nó có đáng để tranh cãi hay không. :)
Skiphoppy


Câu trả lời:


249

Chà, trước hết, bạn đang lãng phí bộ nhớ với HashMapcuộc gọi tạo mới . Dòng thứ hai của bạn hoàn toàn bỏ qua tham chiếu đến hàm băm được tạo này, sau đó nó có sẵn cho trình thu gom rác. Vì vậy, đừng làm điều đó, sử dụng:

private Map<String, String> someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");

Thứ hai, trình biên dịch đang phàn nàn rằng bạn truyền đối tượng đến a HashMapmà không kiểm tra xem nó có phải là a không HashMap. Nhưng, ngay cả khi bạn đã làm:

if(getApplicationContext().getBean("someMap") instanceof HashMap) {
    private Map<String, String> someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");
}

Bạn có thể vẫn sẽ nhận được cảnh báo này. Vấn đề là, getBeantrả về Object, vì vậy không biết loại này là gì. Chuyển đổi nó thành HashMaptrực tiếp sẽ không gây ra vấn đề với trường hợp thứ hai (và có lẽ sẽ không có cảnh báo nào trong trường hợp đầu tiên, tôi không chắc trình biên dịch Java có phạm vi như thế nào với các cảnh báo cho Java 5). Tuy nhiên, bạn đang chuyển đổi nó thành a HashMap<String, String>.

HashMaps thực sự là các bản đồ lấy một đối tượng làm khóa và có một đối tượng làm giá trị, HashMap<Object, Object>nếu bạn muốn. Vì vậy, không có gì đảm bảo rằng khi bạn nhận được bean của mình thì nó có thể được biểu diễn là HashMap<String, String>vì bạn có thể có HashMap<Date, Calendar>vì đại diện không chung chung được trả về có thể có bất kỳ đối tượng nào.

Nếu mã biên dịch và bạn có thể thực thi String value = map.get("thisString");mà không có bất kỳ lỗi nào, đừng lo lắng về cảnh báo này. Nhưng nếu bản đồ không hoàn toàn từ các khóa chuỗi thành các giá trị chuỗi, bạn sẽ nhận được một ClassCastExceptionthời gian chạy, vì các tổng quát không thể ngăn điều này xảy ra trong trường hợp này.


12
Điều này đã được một thời gian trước đây, nhưng tôi đã tìm kiếm một câu trả lời về loại kiểm tra Set <CustomClass> trước khi sử dụng và bạn không thể ví dụ về một cái chung được tham số hóa. ví dụ: if (event.getTarget instanceof Đặt <CustomClass>) Bạn chỉ có thể nhập kiểm tra chung chung với một? và điều đó sẽ không loại bỏ cảnh báo diễn viên. ví dụ: if (event.getTarget instanceof Set <?>)
tỏiman

315

Vấn đề là dàn diễn viên là một kiểm tra thời gian chạy - nhưng do xóa kiểu, trong thời gian chạy thực sự không có sự khác biệt giữa một HashMap<String,String>HashMap<Foo,Bar>cho bất kỳ khác FooBar.

Sử dụng @SuppressWarnings("unchecked")và giữ mũi của bạn. Ồ, và chiến dịch cho các thế hệ thống nhất trong Java :)


14
Tôi sẽ đưa các tổng quát thống nhất của Java qua NSMutableWhthing, mà cảm giác như một bước nhảy vọt mười năm, bất kỳ ngày nào trong tuần. Ít nhất Java đang cố gắng.
Dan Rosenstark

12
Chính xác. Nếu bạn khăng khăng kiểm tra loại, chỉ có thể được thực hiện với HashMap <?,?> Và điều đó sẽ không xóa cảnh báo vì nó giống như không kiểm tra các loại chung. Đó không phải là ngày tận thế, nhưng thật khó chịu khi bạn bị bắt hoặc đang đàn áp cảnh báo hoặc sống với nó.
tỏiman

5
@JonSkeet Thế nào là chung chung?
SasQ

89

Như các thông báo trên chỉ ra, Danh sách không thể được phân biệt giữa a List<Object>và a List<String>hoặc List<Integer>.

Tôi đã giải quyết thông báo lỗi này cho một vấn đề tương tự:

List<String> strList = (List<String>) someFunction();
String s = strList.get(0);

với những điều sau đây:

List<?> strList = (List<?>) someFunction();
String s = (String) strList.get(0);

Giải thích: Chuyển đổi loại đầu tiên xác minh rằng đối tượng là Danh sách mà không quan tâm đến các loại được giữ trong (vì chúng tôi không thể xác minh các loại nội bộ ở cấp Danh sách). Chuyển đổi thứ hai bây giờ là bắt buộc vì trình biên dịch chỉ biết Danh sách chứa một số loại đối tượng. Điều này xác minh loại của từng đối tượng trong Danh sách khi nó được truy cập.


3
bạn nói đúng đấy bạn ạ Thay vì truyền danh sách, chỉ cần lặp lại nó và bỏ từng phần tử, cảnh báo sẽ không xuất hiện, thật tuyệt vời.
juan Isaza

2
Điều này đã loại bỏ cảnh báo nhưng tôi vẫn không tự tin: P
mumair

1
Có cảm giác như bịt mắt trình biên dịch nhưng không chạy: D Vì vậy, tôi không thấy bất kỳ sự khác biệt nào giữa cái này và @SuppressWarnings ("không được kiểm tra")
Gordae

1
Điều đó thật tuyệt vời! Sự khác biệt chính của việc sử dụng @SupressWarning là việc sử dụng chú thích sẽ loại bỏ cảnh báo khỏi IDE và các công cụ phân tích mã của bạn, nhưng nếu bạn đang sử dụng cờ -Werror, bạn sẽ bị lỗi. Sử dụng phương pháp này cả hai cảnh báo đều cố định.
Edu Costa

30

Một cảnh báo chỉ có vậy. Một lời cảnh báo. Đôi khi các cảnh báo là không liên quan, đôi khi chúng không. Chúng được sử dụng để gọi sự chú ý của bạn đến một cái gì đó mà trình biên dịch nghĩ có thể là một vấn đề, nhưng có thể không.

Trong trường hợp diễn viên, nó sẽ luôn đưa ra cảnh báo trong trường hợp này. Nếu bạn hoàn toàn chắc chắn rằng một dàn diễn viên cụ thể sẽ an toàn, thì bạn nên xem xét việc thêm một chú thích như thế này (tôi không chắc về cú pháp) ngay trước dòng:

@SuppressWarnings (value="unchecked")

14
-1: một cảnh báo không bao giờ nên được chấp nhận. Hoặc loại bỏ các loại cảnh báo hoặc sửa chữa nó. Sẽ đến lúc bạn sẽ phải đưa ra nhiều cảnh báo và bạn sẽ không thấy điều đó có liên quan một lần.
ezdazuzena

10
Bạn thực sự không thể tránh các cảnh báo phân loại lớp khi truyền các khái quát được tham số hóa, ví dụ như Bản đồ, vì vậy đây là câu trả lời tốt nhất cho câu hỏi ban đầu.
muttonUp

9

Bạn đang nhận được thông báo này vì getBean trả về một tham chiếu Object và bạn đang chuyển nó thành đúng loại. Java 1.5 cung cấp cho bạn một cảnh báo. Đó là bản chất của việc sử dụng Java 1.5 hoặc tốt hơn với mã hoạt động như thế này. Mùa xuân có phiên bản an toàn

someMap=getApplicationContext().getBean<HashMap<String, String>>("someMap");

trong danh sách việc cần làm của nó.


6

Nếu bạn thực sự muốn thoát khỏi các cảnh báo, một điều bạn có thể làm là tạo một lớp mở rộng từ lớp chung.

Ví dụ: nếu bạn đang cố gắng sử dụng

private Map<String, String> someMap = new HashMap<String, String>();

Bạn có thể tạo một lớp mới như vậy

public class StringMap extends HashMap<String, String>()
{
    // Override constructors
}

Sau đó khi bạn sử dụng

someMap = (StringMap) getApplicationContext().getBean("someMap");

Trình biên dịch DOES biết các loại (không còn chung chung) là gì và sẽ không có cảnh báo. Đây có thể không phải lúc nào cũng là giải pháp hoàn hảo, một số người có thể cho rằng loại này đánh bại mục đích của các lớp chung, nhưng bạn vẫn đang sử dụng lại tất cả các mã giống nhau từ lớp chung, bạn chỉ khai báo vào thời gian biên dịch kiểu gì bạn muốn sử dụng


3

Giải pháp để tránh cảnh báo không được kiểm tra:

class MyMap extends HashMap<String, String> {};
someMap = (MyMap)getApplicationContext().getBean("someMap");

Hình như hack không phải là giải pháp.
Malwinder Singh

1
- Lớp myMap serializable không khai báo một lĩnh vực serialVersionUID thức tĩnh của loại dài: {
thầm kín

1

Một giải pháp khác, nếu bạn thấy mình truyền cùng một đối tượng rất nhiều và bạn không muốn xả rác mã của mình @SupressWarnings("unchecked"), sẽ là tạo một phương thức với chú thích. Bằng cách này, bạn tập trung vào dàn diễn viên và hy vọng giảm khả năng xảy ra lỗi.

@SuppressWarnings("unchecked")
public static List<String> getFooStrings(Map<String, List<String>> ctx) {
    return (List<String>) ctx.get("foos");
}

1

Mã dưới đây gây ra Cảnh báo an toàn loại

Map<String, Object> myInput = (Map<String, Object>) myRequest.get();

Giải pháp thay thế

Tạo Đối tượng Bản đồ mới mà không đề cập đến các tham số vì loại đối tượng được giữ trong danh sách không được xác minh.

Bước 1: Tạo Bản đồ tạm thời mới

Map<?, ?> tempMap = (Map<?, ?>) myRequest.get();

Bước 2: Khởi tạo bản đồ chính

Map<String, Object> myInput=new HashMap<>(myInputObj.size());

Bước 3: Lặp lại Bản đồ tạm thời và đặt các giá trị vào Bản đồ chính

 for(Map.Entry<?, ?> entry :myInputObj.entrySet()){
        myInput.put((String)entry.getKey(),entry.getValue()); 
    }

0

Tôi đã làm gì sai? Làm thế nào để tôi giải quyết vấn đề?

Đây :

Map<String,String> someMap = (Map<String,String>)getApplicationContext().getBean("someMap");

Bạn sử dụng một phương pháp cũ mà chúng tôi thường không muốn sử dụng vì nó trả về Object:

Object getBean(String name) throws BeansException;

Phương pháp để ưu tiên lấy (cho singleton) / tạo (cho nguyên mẫu) một hạt từ nhà máy đậu là:

<T> T getBean(String name, Class<T> requiredType) throws BeansException;

Sử dụng nó như:

Map<String,String> someMap = app.getBean(Map.class,"someMap");

sẽ biên dịch nhưng vẫn có cảnh báo chuyển đổi không được kiểm tra vì tất cả Mapcác đối tượng không nhất thiết là Map<String, String>các đối tượng.

Nhưng <T> T getBean(String name, Class<T> requiredType) throws BeansException;không đủ trong các lớp chung chung như các bộ sưu tập chung vì điều đó đòi hỏi phải chỉ định nhiều hơn một lớp làm tham số: loại bộ sưu tập và (các) loại chung của nó.

Trong loại kịch bản này và nói chung, cách tiếp cận tốt hơn là không sử dụng trực tiếp BeanFactory các phương pháp mà để cho khung để tiêm đậu.

Khai báo đậu:

@Configuration
public class MyConfiguration{

    @Bean
    public Map<String, String> someMap() {
        Map<String, String> someMap = new HashMap();
        someMap.put("some_key", "some value");
        someMap.put("some_key_2", "some value");
        return someMap;
    }
}

Thuốc tiêm đậu:

@Autowired
@Qualifier("someMap")
Map<String, String> someMap;
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.