CodeGym /Java blogg /Slumpmässig /Generika i Java
John Squirrels
Nivå
San Francisco

Generika i Java

Publicerad i gruppen
Hej! Vi kommer att prata om Java Generics. Jag måste säga att du kommer att lära dig mycket! Inte bara den här lektionen, utan även de kommande lektionerna, kommer att ägnas åt generika. Så om du är intresserad av generika, idag har du tur: du kommer att lära dig mycket om funktionerna hos generika. Och om inte, avgå själv och slappna av! :) Detta är ett mycket viktigt ämne, och du måste känna till det. Låt oss börja med det enkla: "vad" och "varför".

Vad är Java Generics?

Generika är typer som har en parameter. När du skapar en generisk typ anger du inte bara en typ, utan även vilken datatyp den ska fungera med. Jag antar att det mest uppenbara exemplet redan har kommit till dig: ArrayList! Så här skapar vi vanligtvis en i ett program:

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<String> myList1 = new ArrayList<>();
       myList1.add("Test String 1");
       myList1.add("Test String 2");
   }
}
Som du kanske gissar är en funktion i den här listan att vi inte kan stoppa in allt i den: den fungerar uteslutande med String -objekt. Låt oss nu ta en liten utvikning i Javas historia och försöka svara på frågan "varför?" För att göra detta kommer vi att skriva vår egen förenklade version av ArrayList-klassen. Vår lista vet bara hur man lägger till data till och hämtar data från en intern array:

public class MyListClass {

   private Object[] data;
   private int count;

   public MyListClass() {
       this.data = new Object[10];
       this.count = 0;
   }

   public void add(Object o) {
       this.data[count] = o;
       count++;
   }

   public Object[] getData() {
       return data;
   }
}
Anta att vi vill att vår lista endast ska lagra heltal . Vi använder inte en generisk typ. Vi vill inte inkludera en explicit "instanceof Integer "-kontroll i add()- metoden. Om vi ​​gjorde det skulle hela vår klass bara vara lämplig för Integer , och vi skulle behöva skriva en liknande klass för alla andra datatyper i världen! Vi förlitar oss på våra programmerare och lämnar bara en kommentar i koden för att säkerställa att de inte lägger till något vi inte vill ha:

// Use this class ONLY with the Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
En av programmerarna missade den här kommentaren och lade oavsiktligt flera strängar i en lista med siffror och beräknade sedan deras summa:

public class Main {

   public static void main(String[] args) {

       MyListClass list = new MyListClass();
       list.add(100);
       list.add(200);
       list.add("Lolkek");
       list.add("Shalala");

       Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
       System.out.println(sum1);

       Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
       System.out.println(sum2);
   }
}
Konsolutgång:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
      at Main.main (Main.java:14)
Vad är det värsta med den här situationen? Absolut inte programmerarens slarv. Det värsta är att felaktig kod hamnade på en viktig plats i vårt program och kompilerades framgångsrikt. Nu kommer vi att stöta på buggen inte när vi skriver kod, utan bara under testning (och detta är det bästa scenariot!). Att fixa buggar i senare utvecklingsstadier kostar mycket mer – både i form av pengar och tid. Det är just här generika gynnar oss: en generisk klass låter den olyckliga programmeraren upptäcka felet direkt. Programmet kommer helt enkelt inte att kompilera!

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Integer> myList1 = new ArrayList<>();
      
       myList1.add(100);
       myList1.add(100);
       myList1.add ("Lolkek"); // Error!
       myList1.add("Shalala"); // Error!
   }
}
Programmeraren inser omedelbart sitt misstag och blir omedelbart bättre. Förresten, vi behövde inte skapa vår egen List- klass för att se den här typen av fel. Ta helt enkelt bort vinkelparenteserna och skriv ( <Integer> ) från en vanlig ArrayList!

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add(100);
      list.add(200);
      list.add("Lolkek");
      list.add("Shalala");

       System.out.println((Integer) list.get(0) + (Integer) list.get(1));
       System.out.println((Integer) list.get(2) + (Integer) list.get(3));
   }
}
Konsolutgång:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
     at Main.main(Main.java:16)
Med andra ord, även om vi använder Javas "inbyggda" mekanismer, kan vi göra den här typen av misstag och skapa en osäker samling. Men om vi klistrar in den här koden i en IDE får vi en varning: "Okontrollerat anrop till add(E) som medlem av råtyp av java.util.List" Vi får veta att något kan gå fel när man lägger till ett objekt till en samling som saknar en generisk typ. Men vad betyder frasen "råtyp"? En råtyp är en generisk klass vars typ har tagits bort. Med andra ord, List myList1 är en råtyp . Motsatsen till en råtyp är en generisk typ — en generisk klass med en indikation på den eller de parametriserade typen/typerna . Till exempel, List<String> myList1. Du kanske frågar varför språket tillåter användning av råa typer ? Anledningen är enkel. Javas skapare lämnade stöd för råtyper i språket för att undvika att skapa kompatibilitetsproblem. När Java 5.0 släpptes (generics dök upp först i den här versionen) hade mycket kod redan skrivits med råtyper . Som ett resultat stöds denna mekanism fortfarande idag. Vi har flera gånger nämnt Joshua Blochs klassiska bok "Effektiv Java" på lektionerna. Som en av skaparna av språket hoppade han inte över råa typer och generiska typer i sin bok. Vad är generika i Java?  - 2Kapitel 23 i boken har en mycket vältalig titel: "Använd inte råtyper i ny kod" Detta är vad du behöver komma ihåg. När du använder generiska klasser, förvandla aldrig en generisk typ till en råtyp .

Generiska metoder

Java låter dig parametrisera enskilda metoder genom att skapa så kallade generiska metoder. Hur är sådana metoder användbara? Framför allt är de till hjälp genom att de låter dig arbeta med olika typer av metodparametrar. Om samma logik säkert kan tillämpas på olika typer, kan en generisk metod vara en bra lösning. Se detta som ett mycket enkelt exempel: Anta att vi har en lista som heter myList1 . Vi vill ta bort alla värden från listan och fylla alla tomma utrymmen med nya värden. Så här ser vår klass ut med en generisk metod:

public class TestClass {

   public static <T> void fill(List<T> list, T val) {
       for (int i = 0; i < list.size(); i++)
           list.set(i, val);
   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       strings.add("Old String 1");
       strings.add("Old String 2");
       strings.add("Old String 3");

       fill(strings, "New String");

       System.out.println(strings);

       List<Integer> numbers = new ArrayList<>();
       numbers.add(1);
       numbers.add(2);
       numbers.add(3);

       fill(numbers, 888);
       System.out.println(numbers);
   }
}
Var uppmärksam på syntaxen. Det ser lite ovanligt ut:

public static <T> void fill(List<T> list, T val)
Vi skriver <T> före returtypen. Detta indikerar att vi har att göra med en generisk metod. I det här fallet accepterar metoden 2 parametrar som indata: en lista med T-objekt och ett annat separat T-objekt. Genom att använda <T> parametriserar vi metodens parametertyper: vi kan inte skicka in en lista med strängar och ett heltal. En lista med strängar och en sträng, en lista med heltal och ett heltal, en lista över våra egna Cat- objekt och ett annat Cat- objekt — det är vad vi behöver göra. Main ()- metoden illustrerar hur fill() -metoden enkelt kan användas för att arbeta med olika typer av data. Först använder vi metoden med en lista med strängar och en sträng som indata, och sedan med en lista med heltal och ett heltal. Konsolutgång:

[New String, New String, New String] [888, 888, 888]
Tänk om vi inte hade generiska metoder och behövde logiken i fill()- metoden för 30 olika klasser. Vi skulle behöva skriva samma metod 30 gånger för olika datatyper! Men tack vare generiska metoder kan vi återanvända vår kod! :)

Generiska klasser

Du är inte begränsad till de generiska klasserna som finns i standard Java-biblioteken – du kan skapa dina egna! Här är ett enkelt exempel:

public class Box<T> {

   private T t;

   public void set(T t) {
       this.t = t;
   }

   public T get() {
       return t;
   }

   public static void main(String[] args) {

       Box<String> stringBox = new Box<>();

       stringBox.set("Old String");
       System.out.println(stringBox.get());
       stringBox.set("New String");

       System.out.println(stringBox.get());
      
       stringBox.set(12345); // Compilation error!
   }
}
Vår Box<T> -klass är en generisk klass. När vi väl tilldelar en datatyp ( <T> ) under skapandet kan vi inte längre placera objekt av andra typer i den. Detta kan ses i exemplet. När vi skapade vårt objekt angav vi att det skulle fungera med Strings:

Box<String> stringBox = new Box<>();
Och i den sista kodraden, när vi försöker lägga numret 12345 i rutan, får vi ett kompileringsfel! Så enkelt är det! Vi har skapat vår egen generiska klass! :) Med det tar dagens lektion sitt slut. Men vi säger inte hejdå till generika! I nästa lektion kommer vi att prata om mer avancerade funktioner, så gå inte iväg! ) För att förstärka det du lärde dig föreslår vi att du tittar på en videolektion från vår Java-kurs
Lycka till med dina studier! :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION