Sự khác biệt giữa các đối tượng HashMap và Map trong Java là gì?


348

Sự khác biệt giữa các bản đồ sau đây mà tôi tạo ra (trong một câu hỏi khác, mọi người trả lời bằng cách sử dụng chúng dường như có thể thay thế cho nhau và tôi tự hỏi nếu / chúng khác nhau như thế nào):

HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();

Giả sử bạn triển khai bằng HashMap và Mary sử dụng Bản đồ. Nó sẽ biên dịch?
GilbertS

Câu trả lời:


445

Không có sự khác biệt giữa các đối tượng; bạn có một HashMap<String, Object>trong cả hai trường hợp. Có một sự khác biệt trong giao diện bạn có đối tượng. Trong trường hợp đầu tiên, giao diện là HashMap<String, Object>, trong khi đó trong giao diện thứ hai Map<String, Object>. Nhưng đối tượng cơ bản là như nhau.

Lợi thế của việc sử dụng Map<String, Object>là bạn có thể thay đổi đối tượng cơ bản thành một loại bản đồ khác mà không phá vỡ hợp đồng của bạn với bất kỳ mã nào sử dụng nó. Nếu bạn tuyên bố nó là HashMap<String, Object>, bạn phải thay đổi hợp đồng nếu bạn muốn thay đổi việc thực hiện cơ bản.


Ví dụ: Giả sử tôi viết lớp này:

class Foo {
    private HashMap<String, Object> things;
    private HashMap<String, Object> moreThings;

    protected HashMap<String, Object> getThings() {
        return this.things;
    }

    protected HashMap<String, Object> getMoreThings() {
        return this.moreThings;
    }

    public Foo() {
        this.things = new HashMap<String, Object>();
        this.moreThings = new HashMap<String, Object>();
    }

    // ...more...
}

Lớp này có một vài bản đồ bên trong của chuỗi-> đối tượng mà nó chia sẻ (thông qua các phương thức truy cập) với các lớp con. Hãy nói rằng tôi viết nó bằng HashMaps để bắt đầu bởi vì tôi nghĩ đó là cấu trúc phù hợp để sử dụng khi viết lớp.

Sau đó, Mary viết mã phân lớp nó. Cô ấy có một cái gì đó cô ấy cần phải làm với cả hai thingsmoreThings, vì vậy, tự nhiên cô ấy đặt nó trong một phương thức chung, và cô ấy sử dụng cùng loại tôi đã sử dụng getThings/ getMoreThingskhi xác định phương pháp của mình:

class SpecialFoo extends Foo {
    private void doSomething(HashMap<String, Object> t) {
        // ...
    }

    public void whatever() {
        this.doSomething(this.getThings());
        this.doSomething(this.getMoreThings());
    }

    // ...more...
}

Sau đó, tôi quyết định rằng thực sự, sẽ tốt hơn nếu tôi sử dụng TreeMapthay vì HashMapvào Foo. Tôi cập nhật Foo, đổi HashMapthành TreeMap. Bây giờ, SpecialFookhông biên dịch nữa, vì tôi đã phá vỡ hợp đồng: Foođược sử dụng để nói nó cung cấp HashMaps, nhưng bây giờ nó cung cấp TreeMapsthay thế. Vì vậy, chúng tôi phải sửa chữa SpecialFoongay bây giờ (và loại điều này có thể gợn qua một cơ sở mã).

Trừ khi tôi có một lý do thực sự tốt để chia sẻ rằng việc triển khai của tôi đang sử dụng một HashMap(và điều đó xảy ra), những gì tôi nên làm là tuyên bố getThingsgetMoreThingsnhư chỉ trở lại Map<String, Object>mà không cụ thể hơn điều đó. Trong thực tế, việc cấm một lý do chính đáng để làm một việc khác, ngay cả trong Footôi có lẽ nên khai báo thingsmoreThingsnhư Map, không HashMap/ TreeMap:

class Foo {
    private Map<String, Object> things;             // <== Changed
    private Map<String, Object> moreThings;         // <== Changed

    protected Map<String, Object> getThings() {     // <== Changed
        return this.things;
    }

    protected Map<String, Object> getMoreThings() { // <== Changed
        return this.moreThings;
    }

    public Foo() {
        this.things = new HashMap<String, Object>();
        this.moreThings = new HashMap<String, Object>();
    }

    // ...more...
}

Lưu ý cách tôi hiện đang sử dụng Map<String, Object>ở mọi nơi tôi có thể, chỉ cụ thể khi tôi tạo các đối tượng thực tế.

Nếu tôi đã làm điều đó, thì Mary sẽ làm điều này:

class SpecialFoo extends Foo {
    private void doSomething(Map<String, Object> t) { // <== Changed
        // ...
    }

    public void whatever() {
        this.doSomething(this.getThings());
        this.doSomething(this.getMoreThings());
    }
}

... và thay đổi Foosẽ không SpecialFoongừng biên dịch.

Các giao diện (và các lớp cơ sở) cho phép chúng tôi chỉ tiết lộ càng nhiều càng cần thiết , giữ cho sự linh hoạt của chúng tôi dưới vỏ bọc để thay đổi khi thích hợp. Nói chung, chúng tôi muốn có các tài liệu tham khảo của chúng tôi là cơ bản nhất có thể. Nếu chúng ta không cần biết nó là một HashMap, chỉ cần gọi nó là a Map.

Đây không phải là một quy tắc mù quáng, nhưng nói chung, mã hóa cho giao diện chung nhất sẽ ít giòn hơn so với mã hóa thành một cái gì đó cụ thể hơn. Nếu tôi nhớ điều đó, tôi sẽ không tạo ra một FooMary khiến tôi thất bại SpecialFoo. Nếu Mary đã nhớ điều đó, thì mặc dù tôi đã nhắn tin Foo, nhưng cô ấy đã tuyên bố phương thức riêng tư của mình Mapthay vì HashMapFoohợp đồng thay đổi của tôi sẽ không ảnh hưởng đến mã của cô ấy.

Đôi khi bạn không thể làm điều đó, đôi khi bạn phải cụ thể. Nhưng trừ khi bạn có một lý do, lỗi về giao diện ít cụ thể nhất.


56

Bản đồ là một giao diện mà HashMap thực hiện. Sự khác biệt là trong lần triển khai thứ hai, tham chiếu của bạn đến HashMap sẽ chỉ cho phép sử dụng các hàm được xác định trong giao diện Bản đồ, trong khi lần đầu tiên sẽ cho phép sử dụng bất kỳ chức năng công khai nào trong HashMap (bao gồm giao diện Bản đồ).

Nó có thể sẽ có ý nghĩa hơn nếu bạn đọc hướng dẫn giao diện của Sun


Tôi giả sử: first = HashMap <String, Object> map = new HashMap <String, Object> ();
OneWorld

Nó tương tự như tần suất Danh sách được triển khai dưới dạng ArrayList
Gerard

26

nhập mô tả hình ảnh ở đây

Bản đồ có các triển khai sau:

  1. Bản đồ băm Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

  3. Bản đồ cây Map m = new TreeMap();

  4. Bản đồ yếu Map m = new WeakHashMap();

Giả sử bạn đã tạo một phương thức (đây chỉ là mã giả).

public void HashMap getMap(){
   return map;
}

Giả sử yêu cầu dự án của bạn thay đổi:

  1. Phương thức sẽ trả về nội dung bản đồ - Cần trả về HashMap.
  2. Phương thức sẽ trả về khóa bản đồ theo thứ tự chèn - Cần thay đổi kiểu trả về HashMapthành LinkedHashMap.
  3. Phương thức sẽ trả về khóa bản đồ theo thứ tự được sắp xếp - Cần thay đổi kiểu trả về LinkedHashMapthành TreeMap.

Nếu phương thức của bạn trả về các lớp cụ thể thay vì một cái gì đó thực hiện Mapgiao diện, bạn phải thay đổi kiểu trả về của getMap()phương thức mỗi lần.

Nhưng nếu bạn sử dụng tính năng đa hình của Java và thay vì trả về các lớp cụ thể, hãy sử dụng giao diện Map, nó sẽ cải thiện khả năng sử dụng lại mã và giảm tác động của các thay đổi yêu cầu.


17

Tôi chỉ định làm điều này như một nhận xét về câu trả lời được chấp nhận nhưng nó trở nên quá sôi nổi (tôi ghét không có ngắt dòng)

ah, do đó, sự khác biệt là nói chung, Map có các phương thức nhất định liên quan đến nó. nhưng có nhiều cách khác nhau hoặc tạo bản đồ, chẳng hạn như HashMap và những cách khác nhau này cung cấp các phương thức duy nhất mà không phải bản đồ nào cũng có.

Chính xác - và bạn luôn muốn sử dụng giao diện chung nhất có thể. Xem xét ArrayList vs LinkedList. Sự khác biệt lớn về cách bạn sử dụng chúng, nhưng nếu bạn sử dụng "Danh sách", bạn có thể dễ dàng chuyển đổi giữa chúng.

Trong thực tế, bạn có thể thay thế phía bên phải của trình khởi tạo bằng một câu lệnh động hơn. Còn những thứ như thế này thì sao:

List collection;
if(keepSorted)
    collection=new LinkedList();
else
    collection=new ArrayList();

Bằng cách này, nếu bạn định điền vào bộ sưu tập với một loại chèn, bạn sẽ sử dụng một danh sách được liên kết (một loại chèn vào danh sách mảng là hình sự.) Nhưng nếu bạn không cần phải sắp xếp nó và chỉ đang nối thêm, bạn sử dụng một ArrayList (Hiệu quả hơn cho các hoạt động khác).

Đây là một sự kéo dài khá lớn ở đây vì các bộ sưu tập không phải là ví dụ tốt nhất, nhưng trong thiết kế OO, một trong những khái niệm quan trọng nhất là sử dụng mặt tiền giao diện để truy cập các đối tượng khác nhau với cùng một mã.

Chỉnh sửa trả lời bình luận:

Đối với nhận xét bản đồ của bạn bên dưới, Có, sử dụng giao diện "Bản đồ" chỉ giới hạn bạn với các phương thức đó trừ khi bạn chuyển bộ sưu tập từ Map sang HashMap (HOÀN TOÀN đánh bại mục đích).

Thông thường những gì bạn sẽ làm là tạo một đối tượng và điền vào nó bằng cách sử dụng loại cụ thể của nó (HashMap), trong một loại phương thức "tạo" hoặc "khởi tạo", nhưng phương thức đó sẽ trả về một "Bản đồ" không cần phải có thao túng như một HashMap nữa.

Nếu bạn phải sử dụng theo cách này, có lẽ bạn đang sử dụng giao diện sai hoặc mã của bạn không đủ cấu trúc. Lưu ý rằng có thể chấp nhận được một phần mã của bạn coi nó là "HashMap" trong khi phần còn lại coi nó là "Bản đồ", nhưng điều này sẽ chảy "xuống". để bạn không bao giờ được đúc.

Cũng lưu ý khía cạnh bán gọn gàng của các vai trò được chỉ định bởi các giao diện. LinkedList tạo ra một ngăn xếp hoặc hàng đợi tốt, một ArrayList tạo ra một ngăn xếp tốt nhưng một hàng đợi khủng khiếp (một lần nữa, việc xóa sẽ gây ra sự thay đổi của toàn bộ danh sách) vì vậy LinkedList thực hiện giao diện Queue, ArrayList thì không.


nhưng trong ví dụ này, tôi chỉ nhận được các phương thức từ lớp List chung, phải không? bất kể tôi biến nó thành LinkedList () hay ArrayList ()? chỉ là nếu tôi sử dụng sắp xếp chèn (mà tôi tưởng tượng phải là một phương thức cho Danh sách mà LinkedList và ArrayList có được nhờ kế thừa) thì nó hoạt động nhanh hơn trên LinkedList?
Tony Stark

Tôi đoán những gì tôi đang tìm kiếm là có hay không khi tôi nói Map <string, string> m = new HashMap <string, string> () my Map m có thể sử dụng các phương thức cụ thể cho HashMap hay không. Tôi đang nghĩ nó không thể?
Tony Stark

à, đợi đã, không, Bản đồ m của tôi từ trên phải có các phương thức từ HashMap.
Tony Stark

về cơ bản, lợi ích duy nhất của việc sử dụng Bản đồ theo 'ý nghĩa giao diện' là nếu tôi có một phương pháp yêu cầu bản đồ, tôi đảm bảo mọi loại bản đồ sẽ hoạt động theo phương pháp này. nhưng nếu tôi đã sử dụng hashmap, tôi đang nói phương thức này chỉ hoạt động với hashmap. hoặc, nói cách khác, phương thức của tôi chỉ sử dụng các phương thức được xác định trong lớp Map nhưng được kế thừa bởi các Class khác mở rộng Map.
Tony Stark

ngoài các lợi ích bạn đã đề cập ở trên, khi sử dụng Danh sách có nghĩa là tôi không cần phải quyết định loại Danh sách nào tôi muốn cho đến khi chạy, trong khi nếu giao diện không tồn tại, tôi phải chọn một trước khi biên dịch và chạy
Tony Stark

12

Theo ghi nhận của TJ Crowder và Adamski, một tài liệu tham khảo là một giao diện, còn lại là một triển khai cụ thể của giao diện. Theo Joshua Block, bạn phải luôn cố gắng mã hóa các giao diện, để cho phép bạn xử lý tốt hơn các thay đổi đối với triển khai cơ bản - tức là nếu HashMap đột nhiên không lý tưởng cho giải pháp của bạn và bạn cần thay đổi triển khai bản đồ, bạn vẫn có thể sử dụng Bản đồ giao diện và thay đổi kiểu khởi tạo.


8

Trong ví dụ thứ hai của bạn, tham chiếu "bản đồ" có kiểu Map, là giao diện được triển khai bởi HashMap(và các loại khác Map). Giao diện này là một hợp đồng nói rằng các đối tượng bản đồ chìa khóa để các giá trị và hỗ trợ hoạt động khác nhau (ví dụ put, get). Nó nói gì về việc thực hiện của Map(trong trường hợp này một HashMap).

Cách tiếp cận thứ hai thường được ưa thích vì bạn thường không muốn đưa ra cách triển khai bản đồ cụ thể cho các phương thức sử dụng Maphoặc thông qua định nghĩa API.


8

Bản đồ là loại tĩnh bản đồ , trong khi HashMap là loại bản đồ động. Điều này có nghĩa là trình biên dịch sẽ coi đối tượng bản đồ của bạn là một trong những loại Bản đồ, mặc dù trong thời gian chạy, nó có thể trỏ đến bất kỳ kiểu con nào của nó.

Cách thực hành lập trình này dựa trên các giao diện thay vì triển khai có thêm lợi ích của tính linh hoạt còn lại: Chẳng hạn, bạn có thể thay thế loại bản đồ động khi chạy, miễn là nó là một kiểu con của Bản đồ (ví dụ LinkedHashMap) và thay đổi hành vi của bản đồ trên con ruồi.

Một nguyên tắc tốt là duy trì tính trừu tượng nhất có thể ở cấp API: Nếu ví dụ, một phương thức bạn đang lập trình phải hoạt động trên bản đồ, thì đủ để khai báo một tham số là Map thay vì loại HashMap nghiêm ngặt hơn (vì ít trừu tượng hơn) . Theo cách đó, người tiêu dùng API của bạn có thể linh hoạt về loại triển khai Bản đồ mà họ muốn chuyển sang phương thức của bạn.


4

Thêm vào câu trả lời được bình chọn hàng đầu và nhiều câu trả lời trên nhấn mạnh "chung chung hơn, tốt hơn", tôi muốn đào sâu thêm một chút.

Map là hợp đồng cấu trúc trong khi HashMap là một triển khai cung cấp các phương thức riêng để giải quyết các vấn đề thực tế khác nhau: cách tính chỉ số, năng lực là gì và làm thế nào để tăng nó, cách chèn, làm thế nào để giữ chỉ số duy nhất, v.v.

Hãy xem mã nguồn:

Trong Mapchúng tôi có phương pháp containsKey(Object key):

boolean containsKey(Object key);

JavaDoc:

boolean java.util.Map.containsValue (Giá trị đối tượng)

Trả về true nếu bản đồ này ánh xạ một hoặc nhiều khóa tới giá trị đã chỉ định. Chính thức hơn, trả về true nếu và chỉ khi bản đồ này chứa ít nhất một ánh xạ tới một giá trị vsao cho (value==null ? v==null : value.equals(v)). Hoạt động này có thể sẽ yêu cầu tuyến tính thời gian trong kích thước bản đồ cho hầu hết các triển khai của giao diện Bản đồ.

Thông số: giá trị

giá trị mà sự hiện diện trong bản đồ này là để phản bội

Trả về: đúng

nếu bản đồ này ánh xạ một hoặc nhiều khóa vào chỉ định

giá trị:

ClassCastException - nếu giá trị thuộc loại không phù hợp cho bản đồ này (tùy chọn)

NullPulumException - nếu giá trị được chỉ định là null và bản đồ này không cho phép giá trị null (tùy chọn)

Nó đòi hỏi các triển khai của nó để thực hiện nó, nhưng "làm thế nào" là tự do, chỉ để đảm bảo nó trả về đúng.

Trong HashMap:

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

Hóa ra là HashMapsử dụng mã băm để kiểm tra xem bản đồ này có chứa khóa hay không. Vì vậy, nó có lợi ích của thuật toán băm.


3

Bạn tạo các bản đồ tương tự.

Nhưng bạn có thể điền vào sự khác biệt khi bạn sẽ sử dụng nó. Với trường hợp đầu tiên, bạn sẽ có thể sử dụng các phương thức HashMap đặc biệt (nhưng tôi không nhớ ai thực sự hữu ích) và bạn sẽ có thể chuyển nó dưới dạng tham số HashMap:

public void foo (HashMap<String, Object) { ... }

...

HashMap<String, Object> m1 = ...;
Map<String, Object> m2 = ...;

foo (m1);
foo ((HashMap<String, Object>)m2); 

3

Bản đồ là giao diện và Hashmap là lớp thực hiện Giao diện bản đồ


1

Bản đồ là Giao diện và Hashmap là lớp thực hiện điều đó.

Vì vậy, trong việc thực hiện này, bạn tạo ra các đối tượng tương tự


0

HashMap là một triển khai của Map nên nó khá giống nhau nhưng có phương thức "clone ()" như tôi thấy trong hướng dẫn tham khảo))


0
HashMap<String, Object> map1 = new HashMap<String, Object>();
Map<String, Object> map2 = new HashMap<String, Object>();  

Trước hết Maplà một giao diện nó có thực hiện khác nhau như - HashMap, TreeHashMap, LinkedHashMap, vv Giao diện hoạt động giống như một lớp siêu cho lớp thực hiện. Vì vậy, theo quy tắc của OOP, bất kỳ lớp cụ thể nào thực hiện Mapcũng là một Map. Điều đó có nghĩa là chúng ta có thể gán / đặt bất kỳ HashMapbiến loại nào cho một Mapbiến loại mà không cần bất kỳ kiểu truyền nào.

Trong trường hợp này, chúng ta có thể gán map1cho map2mà không cần bất kỳ đúc hoặc bất kỳ thua của dữ liệu -

map2 = map1
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.