Java >> Java tutorial >  >> Tag >> HashMap

Forskellen mellem kort og HashMap i Java

1. Oversigt

Forskellen mellem Kort  og HashMap er, at den første er en grænseflade, og den anden er en implementering . Men i denne artikel vil vi grave lidt dybere og forklare, hvorfor grænseflader er nyttige. Vi lærer også, hvordan man gør kode mere fleksibel med grænseflader, og hvorfor vi har forskellige implementeringer til den samme grænseflade.

2. Formål med grænseflader

En grænseflade er en kontrakt, der kun definerer adfærd. Hver klasse, der implementerer en bestemt grænseflade, skal opfylde denne kontrakt. For at forstå det bedre kan vi tage et eksempel fra det virkelige liv. Forestil dig en bil. Hver person vil have et andet billede i deres sind. Udtrykket bil indebærer nogle kvaliteter og adfærd. Ethvert objekt, der har disse kvaliteter, kan kaldes en bil. Det er derfor, vi alle forestillede os en anden bil.

Grænseflader fungerer på samme måde. Kort  er en abstraktion, der definerer bestemte kvaliteter og adfærd. Kun den klasse, der har alle disse kvaliteter, kan være et Kort.

3. Forskellige implementeringer

Vi har forskellige implementeringer af kortet interface af samme grund, som vi har forskellige bilmodeller. Alle implementeringer tjener forskellige formål. Det er umuligt at finde den bedste implementering generelt. Der er kun den bedste implementering til et eller andet formål. Selvom en sportsvogn er hurtig og ser cool ud, er den ikke det bedste valg til en familie-picnic eller tur til en møbelbutik.

HashMap er den enkleste implementering af Kort interface og giver den grundlæggende funktionalitet. For det meste dækker denne implementering alle behov. To andre udbredte implementeringer er TreeMap , og LinkedHashMap giver yderligere funktioner.

Her er et mere detaljeret, men ikke fuldstændigt hierarki:

4. Programmering til implementeringer

Forestil dig, at vi gerne vil udskrive nøglerne og værdierne for et HashMap  i konsollen:

public class HashMapPrinter {

    public void printMap(final HashMap<?, ?> map) {
        for (final Entry<?, ?> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " " + entry.getValue());
        }
    }
}

Dette er en lille klasse, der klarer opgaven. Den indeholder dog et problem. Det vil kun kunne fungere med HashMap. Derfor ethvert forsøg på at gå ind i metoden TreeMap  eller endda HashMap , refereret af Kort  vil resultere i en kompileringsfejl:

public class Main {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        HashMap<String, String> hashMap = new HashMap<>();
        TreeMap<String, String> treeMap = new TreeMap<>();

        HashMapPrinter hashMapPrinter = new HashMapPrinter();
        hashMapPrinter.printMap(hashMap);
//        hashMapPrinter.printMap(treeMap); Compile time error
//        hashMapPrinter.printMap(map); Compile time error
    }
}

Lad os prøve at forstå, hvorfor det sker. I begge disse tilfælde kan compileren ikke være sikker på, at der i denne metode ikke vil være nogen påkaldelser på HashMap   specifikke metoder.

TreeMap  er på en anden gren af kortet  implementering (ingen ordspil beregnet), så det kan mangle nogle metoder, der er defineret i HashMap.

I det andet tilfælde, på trods af det virkelige underliggende objekt af typen HashMap, det refereres til af kortet interface. Derfor vil dette objekt kun være i stand til at afsløre metoder defineret i kortet og ikke i HashMap.

Så selvom vores HashMapPrinter er en ret simpel klasse, den er for specifik . Med denne tilgang ville det kræve, at vi oprettede en specifik printer for hvert kort  implementering.

5. Programmering til grænseflader

Ofte bliver begyndere forvirrede over betydningen af ​​udtrykket "program til grænseflader" eller "kode mod grænseflader". Lad os overveje følgende eksempel, som vil gøre det lidt klarere. Vi ændrer typen af ​​argumentet til den mest generelle mulige type, som er Kort:

public class MapPrinter {
    
    public void printMap(final Map<?, ?> map) {
        for (final Entry<?, ?> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " " + entry.getValue());
        }
    }
}

Som vi kan se, forblev den faktiske implementering den samme, mens den eneste ændring er typen af ​​argument. Dette viser, at metoden ikke brugte nogen specifikke metoder i HashMap . Al den nødvendige funktionalitet var allerede defineret i kortet grænseflade, nemlig metoden entrySet() .

Som et resultat skabte denne mindre ændring en enorm forskel. Nu kan denne klasse arbejde med ethvert kort implementering:

public class Main {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        HashMap<String, String> hashMap = new HashMap<>();
        TreeMap<String, String> treeMap = new TreeMap<>();

        MapPrinter mapPrinter = new MapPrinter();
        mapPrinter.printMap(hashMap);
        mapPrinter.printMap(treeMap);
        mapPrinter.printMap(map);
    }
}

Kodning til grænseflade hjalp os med at skabe en alsidig klasse, der kan fungere med enhver implementering af kortet grænseflade. Denne tilgang kan eliminere kodeduplikering og sikre, at vores klasser og metoder har et veldefineret formål.

6. Hvor skal man bruge grænseflader

Generelt bør argumenter være af den mest generelle type. Vi så i et tidligere eksempel, hvordan blot en simpel ændring i en signatur af en metode kunne forbedre vores kode. Et andet sted, hvor vi bør have samme tilgang, er en konstruktør:

public class MapReporter {

    private final Map<?, ?> map;

    public MapReporter(final Map<?, ?> map) {
        this.map = map;
    }

    public void printMap() {
        for (final Entry<?, ?> entry : this.map.entrySet()) {
            System.out.println(entry.getKey() + " " + entry.getValue());
        }
    }
}

Denne klasse kan arbejde med enhver implementering af Kort,  bare fordi vi brugte den rigtige type i konstruktøren.

7. Konklusion

For at opsummere diskuterede vi i denne tutorial, hvorfor grænseflader er et godt middel til abstraktion og definition af en kontrakt. Brug af den mest generelle type som muligt vil gøre koden let at genbruge og let at læse. Samtidig reducerer denne tilgang mængden af ​​kode, hvilket altid er en god måde at forenkle kodebasen på.

Som altid er koden tilgængelig på GitHub.


Java tag