John Squirrels
Nivå
San Francisco

Skriv sletting

Publisert i gruppen
Hei! Vi fortsetter vår serie med leksjoner om generiske legemidler. Vi har tidligere fått en generell idé om hva de er og hvorfor de trengs. I dag skal vi lære mer om noen av funksjonene til generiske legemidler og om å jobbe med dem. La oss gå! Type sletting - 1I forrige leksjon snakket vi om forskjellen mellom generiske typer og råtyper . En råtype er en generisk klasse hvis type er fjernet.
List list = new ArrayList();
Her er et eksempel. Her angir vi ikke hvilken type gjenstander som skal plasseres i vår List. Hvis vi prøver å lage en slik Listog legge til noen objekter til den, vil vi se en advarsel i IDEA:
"Unchecked call to add(E) as a member of raw type of java.util.List".
Men vi snakket også om det faktum at generikk bare dukket opp i Java 5. Da denne versjonen ble utgitt, hadde programmerere allerede skrevet en haug med kode ved å bruke råtyper, så denne funksjonen i språket kunne ikke slutte å fungere, og muligheten til å lage råtyper i Java ble bevart. Problemet viste seg imidlertid å være mer utbredt. Som du vet, konverteres Java-kode til et spesielt kompilert format kalt bytecode, som deretter kjøres av den virtuelle Java-maskinen. Men hvis vi legger informasjon om typeparametere i bytekoden under konverteringsprosessen, ville den bryte all den tidligere skrevne koden, fordi det ikke var noen typeparametere før Java 5! Når du arbeider med generiske medisiner, er det ett veldig viktig konsept du må huske. Det kalles type sletting. Det betyr at en klasse ikke inneholder informasjon om en typeparameter. Denne informasjonen er kun tilgjengelig under kompilering og slettes (blir utilgjengelig) før kjøretid. Hvis du prøver å sette feil type objekt i List<String>, vil kompilatoren generere en feil. Dette er nøyaktig hva språkets skapere ønsker å oppnå når de opprettet generiske artikler: kompileringstidssjekker. Men når all Java-koden din blir til bytekode, inneholder den ikke lenger informasjon om typeparametere. I bytecode List<Cat>er kattelisten din ikke annerledes enn List<String>strenger. I bytekode er det ingenting som sier at det catser en liste over Catobjekter. Slik informasjon slettes under kompilering — bare det at du har en List<Object> catsliste vil havne i programmets bytekode. La oss se hvordan dette fungerer:
public class TestClass<T> {

   private T value1;
   private T value2;

   public void printValues() {
       System.out.println(value1);
       System.out.println(value2);
   }

   public static <T> TestClass<T> createAndAdd2Values(Object o1, Object o2) {
       TestClass<T> result = new TestClass<>();
       result.value1 = (T) o1;
       result.value2 = (T) o2;
       return result;
   }

   public static void main(String[] args) {
       Double d = 22.111;
       String s = "Test String";
       TestClass<Integer> test = createAndAdd2Values(d, s);
       test.printValues();
   }
}
Vi opprettet vår egen generiske TestClassklasse. Det er ganske enkelt: det er faktisk en liten "samling" av 2 objekter, som lagres umiddelbart når objektet er opprettet. Den har 2 Tfelt. Når createAndAdd2Values()metoden er utført, må de to passerte objektene ( Object aog Object bcastes til Ttypen og deretter legges til objektet TestClass. I main()metoden lager vi en TestClass<Integer>, dvs. Integertypeargumentet erstatter Integertypeparameteren. Vi sender også a Doubleog a Stringtil metoden createAndAdd2Values(). Tror du programmet vårt vil fungere? Vi spesifiserte tross alt Integersom typeargumentet, men en Stringkan definitivt ikke castes til en Integer! La oss kjøremain()metode og sjekk. Konsoll utgang:
22.111
Test String
Det var uventet! Hvorfor skjedde dette? Det er et resultat av type sletting. Informasjon om Integertypeargumentet som ble brukt til å instansiere objektet vårt TestClass<Integer> testble slettet da koden ble kompilert. Feltet blir TestClass<Object> test. Våre Doubleog Stringargumenter ble enkelt konvertert til Objectobjekter (de konverteres ikke til Integerobjekter slik vi forventet!) og stille lagt til TestClass. Her er et annet enkelt, men veldig avslørende eksempel på type sletting:
import java.util.ArrayList;
import java.util.List;

public class Main {

   private class Cat {

   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       List<Integer> numbers = new ArrayList<>();
       List<Cat> cats = new ArrayList<>();

       System.out.println(strings.getClass() == numbers.getClass());
       System.out.println(numbers.getClass() == cats.getClass());

   }
}
Konsoll utgang:
true
true
Det ser ut til at vi har laget samlinger med tre forskjellige typer argumenter - String, Integer, og vår egen Catklasse. Men under konverteringen til bytekode blir alle tre listene List<Object>, så når programmet kjører forteller det oss at vi bruker samme klasse i alle tre tilfellene.

Skriv sletting når du arbeider med matriser og generiske artikler

Det er et veldig viktig poeng som må forstås tydelig når du arbeider med matriser og generiske klasser (som f.eks. List). Du bør også ta det i betraktning når du velger datastrukturer for programmet ditt. Generiske er gjenstand for typesletting. Informasjon om typeparametere er ikke tilgjengelig under kjøring. Derimot vet arrays om og kan bruke informasjon om deres datatype når programmet kjører. Forsøk på å sette en ugyldig type inn i en matrise vil føre til at et unntak blir kastet:
public class Main2 {

   public static void main(String[] args) {

       Object x[] = new String[3];
       x[0] = new Integer(222);
   }
}
Konsoll utgang:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
Fordi det er så stor forskjell mellom arrays og generiske, kan de ha kompatibilitetsproblemer. Fremfor alt kan du ikke lage en rekke generiske objekter eller bare en parameterisert matrise. Høres det litt forvirrende ut? La oss ta en titt. Du kan for eksempel ikke gjøre noe av dette i Java:
new List<T>[]
new List<String>[]
new T[]
Hvis vi prøver å lage en rekke List<String>objekter, får vi en kompileringsfeil som klager over generisk matriseoppretting:
import java.util.List;

public class Main2 {

   public static void main(String[] args) {

       // Compilation error! Generic array creation
       List<String>[] stringLists = new List<String>[1];
   }
}
Men hvorfor gjøres dette? Hvorfor er det ikke tillatt å lage slike arrays? Dette er alt for å gi typesikkerhet. Hvis kompilatoren lar oss lage slike arrays av generiske objekter, kan vi lage massevis av problemer for oss selv. Her er et enkelt eksempel fra Joshua Blochs bok "Effective Java":
public static void main(String[] args) {

   List<String>[] stringLists = new List<String>[1];  //  (1)
   List<Integer> intList = Arrays.asList(42, 65, 44);  //  (2)
   Object[] objects = stringLists;  //  (3)
   objects[0] = intList;  //  (4)
   String s = stringLists[0].get(0);  //  (5)
}
La oss forestille oss at List<String>[] stringListsdet er tillatt å lage en array like og ikke vil generere en kompileringsfeil. Hvis dette var sant, her er noen ting vi kan gjøre: I linje 1 lager vi en rekke lister: List<String>[] stringLists. Arrayet vårt inneholder en List<String>. På linje 2 lager vi en liste med tall: List<Integer>. I linje 3 tilordner vi vår List<String>[]til en Object[] objectsvariabel. Java-språket tillater dette: en rekke Xobjekter kan lagre Xobjekter og objekter av alle underklasser X. Følgelig kan du sette hva som helst i en Objectmatrise. I linje 4 erstatter vi det eneste elementet i matrisen objects()(a List<String>) med en List<Integer>. Dermed la vi a List<Integer>i en matrise som kun var ment å lagreList<String>gjenstander! Vi vil støte på en feil bare når vi kjører linje 5. A ClassCastExceptionvil bli kastet under kjøring. Følgelig ble et forbud mot å lage slike arrays lagt til Java. Dette lar oss unngå slike situasjoner.

Hvordan kan jeg komme meg rundt type sletting?

Vel, vi lærte om type sletting. La oss prøve å lure systemet! :) Oppgave: Vi har en generisk TestClass<T>klasse. Vi ønsker å skrive en createNewT()metode for denne klassen som vil opprette og returnere et nytt Tobjekt. Men dette er umulig, ikke sant? All informasjon om Ttypen slettes under kompilering, og under kjøring kan vi ikke bestemme hvilken type objekt vi må lage. Det er faktisk én vanskelig måte å gjøre dette på. Du husker sikkert at Java har en Classklasse. Vi kan bruke den til å bestemme klassen til noen av objektene våre:
public class Main2 {

   public static void main(String[] args) {

       Class classInt = Integer.class;
       Class classString = String.class;

       System.out.println(classInt);
       System.out.println(classString);
   }
}
Konsoll utgang:
class java.lang.Integer
class java.lang.String
Men her er ett aspekt vi ikke har snakket om. I Oracle-dokumentasjonen vil du se at klasseklassen er generisk! Type sletting - 3

https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html

Dokumentasjonen sier: "T - typen klasse som er modellert av dette klasseobjektet." Når vi oversetter dette fra dokumentasjonsspråket til vanlig tale, forstår vi at klassen til objektet Integer.classikke bare er Class, men snarere Class<Integer>. Objekttypen String.classer ikke bare Class, men heller Class<String>, osv. Hvis det fortsatt ikke er klart, kan du prøve å legge til en type-parameter i forrige eksempel:
public class Main2 {

   public static void main(String[] args) {

       Class<Integer> classInt = Integer.class;
       // Compilation error!
       Class<String> classInt2 = Integer.class;


       Class<String> classString = String.class;
       // Compilation error!
       Class<Double> classString2 = String.class;
   }
}
Og nå, ved å bruke denne kunnskapen, kan vi omgå type sletting og utføre oppgaven vår! La oss prøve å få informasjon om en typeparameter. Typeargumentet vårt vil være MySecretClass:
public class MySecretClass {

   public MySecretClass() {

       System.out.println("A MySecretClass object was created successfully!");
   }
}
Og her er hvordan vi bruker løsningen vår i praksis:
public class TestClass<T> {

   Class<T> typeParameterClass;

   public TestClass(Class<T> typeParameterClass) {
       this.typeParameterClass = typeParameterClass;
   }

   public T createNewT() throws IllegalAccessException, InstantiationException {
       T t = typeParameterClass.newInstance();
       return t;
   }

   public static void main(String[] args) throws InstantiationException, IllegalAccessException {

       TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
       MySecretClass secret = testString.createNewT();

   }
}
Konsoll utgang:
A MySecretClass object was created successfully!
Vi ga nettopp det nødvendige klasseargumentet til konstruktøren av vår generiske klasse:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Dette tillot oss å lagre informasjonen om typeargumentet, og forhindret at den ble fullstendig slettet. Som et resultat klarte vi å lage enTgjenstand! :) Med det går dagens leksjon mot slutten. Du må alltid huske type sletting når du arbeider med generiske medisiner. Denne løsningen ser ikke veldig praktisk ut, men du bør forstå at generiske medisiner ikke var en del av Java-språket da det ble opprettet. Denne funksjonen, som hjelper oss med å lage parameteriserte samlinger og fange opp feil under kompilering, ble tatt på senere. På noen andre språk som inkluderte generikk fra den første versjonen, er det ingen typesletting (for eksempel i C#). Forresten, vi er ikke ferdige med å studere generikk! I neste leksjon vil du bli kjent med noen flere funksjoner ved generiske legemidler. Foreløpig ville det vært greit å løse et par oppgaver! :)
Kommentarer
  • Populær
  • Ny
  • Gammel
Du må være pålogget for å legge igjen en kommentar
Denne siden har ingen kommentarer ennå