CodeGym /Java blog /Tilfældig /Type sletning
John Squirrels
Niveau
San Francisco

Type sletning

Udgivet i gruppen
Hej! Vi fortsætter vores serie af lektioner om generiske lægemidler. Vi fik tidligere en generel idé om, hvad de er, og hvorfor de er nødvendige. I dag lærer vi mere om nogle af funktionerne ved generiske lægemidler og om at arbejde med dem. Lad os gå! Type sletning - 1I sidste lektion talte vi om forskellen mellem generiske typer og råtyper . En rå type er en generisk klasse, hvis type er blevet fjernet.

List list = new ArrayList();
Her er et eksempel. Her angiver vi ikke, hvilken type objekter der vil blive placeret i vores List. Hvis vi forsøger at oprette sådan en Listog tilføje nogle 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 talte også om det faktum, at generika kun dukkede op i Java 5. Da denne version blev frigivet, havde programmører allerede skrevet en masse kode ved hjælp af råtyper, så denne funktion i sproget kunne ikke stoppe med at fungere, og evnen til at oprette råtyper i Java blev bevaret. Problemet viste sig dog at være mere udbredt. Som du ved, konverteres Java-kode til et særligt kompileret format kaldet bytecode, som derefter udføres af den virtuelle Java-maskine. Men hvis vi lægger information om typeparametre i bytekoden under konverteringsprocessen, ville det bryde al den tidligere skrevne kode, fordi der ikke var nogen typeparametre før Java 5! Når du arbejder med generiske lægemidler, er der et meget vigtigt koncept, som du skal huske. Det kaldes type sletning. Det betyder, at en klasse ikke indeholder information om en typeparameter. Disse oplysninger er kun tilgængelige under kompilering og slettes (bliver utilgængelige) før runtime. Hvis du forsøger at sætte den forkerte type objekt i din List<String>, vil compileren generere en fejl. Det er præcis, hvad sprogets skabere ønsker at opnå, når de oprettede generiske artikler: kompileringstidstjek. Men når al din Java-kode bliver til bytekode, indeholder den ikke længere information om typeparametre. I bytecode List<Cat>er din katteliste ikke anderledes end List<String>strenge. I bytecode siger intet, at det catser en liste over Catobjekter. Sådanne oplysninger slettes under kompileringen — kun det faktum, at du har en List<Object> catsliste, ender i programmets bytekode. Lad os se, hvordan dette virker:

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 skabte vores egen generiske TestClassklasse. Det er ganske enkelt: det er faktisk en lille "samling" af 2 objekter, som gemmes med det samme, når objektet er oprettet. Den har 2 Tfelter. Når createAndAdd2Values()metoden udføres, skal de to beståede objekter ( Object aog Object bcastes til Ttypen og derefter tilføjes til TestClassobjektet. I main()metoden opretter vi et TestClass<Integer>, dvs. Integertypeargumentet erstatter Integertypeparameteren. Vi sender også a Doubleog a Stringtil metoden createAndAdd2Values(). Tror du, vores program vil fungere? Vi har jo angivet Integersom typeargumentet, men en Stringkan absolut ikke castes til en Integer! Lad os køremain()metode og check. Konsoludgang:

22.111 
Test String
Det var uventet! Hvorfor skete dette? Det er resultatet af type sletning. Oplysninger om Integertypeargumentet, der blev brugt til at instantiere vores TestClass<Integer> testobjekt, blev slettet, da koden blev kompileret. Feltet bliver TestClass<Object> test. Vores Doubleog Stringargumenter blev nemt konverteret til Objectobjekter (de konverteres ikke til Integerobjekter, som vi forventede!) og stille og roligt tilføjet til TestClass. Her er et andet simpelt, men meget afslørende eksempel på typesletning:

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());

   }
}
Konsoludgang:

true 
true
Det ser ud til, at vi har lavet samlinger med tre forskellige typer argumenter - String, Integer, og vores helt egen Catklasse. Men under konverteringen til bytecode bliver alle tre lister List<Object>, så når programmet kører fortæller det os, at vi bruger den samme klasse i alle tre tilfælde.

Skriv sletning, når du arbejder med arrays og generiske artikler

Der er en meget vigtig pointe, som skal forstås klart, når man arbejder med arrays og generiske klasser (såsom List). Du bør også tage det i betragtning, når du vælger datastrukturer til dit program. Generiske stoffer er underlagt typesletning. Oplysninger om typeparametre er ikke tilgængelige under kørsel. Derimod kender arrays til og kan bruge information om deres datatype, når programmet kører. Forsøg på at indsætte en ugyldig type i et array vil medføre, at en undtagelse bliver kastet:

public class Main2 {

   public static void main(String[] args) {

       Object x[] = new String[3];
       x[0] = new Integer(222);
   }
}
Konsoludgang:

Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
Fordi der er så stor forskel mellem arrays og generiske stoffer, kan de have kompatibilitetsproblemer. Frem for alt kan du ikke oprette et array af generiske objekter eller bare en parameteriseret array. Lyder det lidt forvirrende? Lad os se. For eksempel kan du ikke gøre noget af dette i Java:

new List<T>[]
new List<String>[]
new T[]
Hvis vi forsøger at oprette et array af List<String>objekter, får vi en kompileringsfejl, der klager over generisk array-oprettelse:

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 gøres dette? Hvorfor er oprettelse af sådanne arrays ikke tilladt? Dette er alt sammen for at give typesikkerhed. Hvis compileren lader os skabe sådanne arrays af generiske objekter, kunne vi lave et væld af problemer for os selv. Her er et simpelt eksempel fra Joshua Blochs bog "Effektiv 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)
}
Lad os forestille os, at oprettelse af et array-like List<String>[] stringListser tilladt og ikke vil generere en kompileringsfejl. Hvis dette var sandt, er her nogle ting, vi kunne gøre: I linje 1 opretter vi en række lister: List<String>[] stringLists. Vores array indeholder en List<String>. I linje 2 opretter vi en liste med tal: List<Integer>. I linje 3 tildeler vi vores List<String>[]til en Object[] objectsvariabel. Java-sproget tillader dette: en række Xobjekter kan gemme Xobjekter og objekter af alle underklasser X. Derfor kan du overhovedet sætte hvad som helst i et Objectarray. I linje 4 erstatter vi det eneste element i objects()arrayet (a List<String>) med en List<Integer>. Således satte vi a List<Integer>i et array, der kun var beregnet til at gemmeList<String>genstande! Vi vil kun støde på en fejl, når vi udfører linje 5. A ClassCastExceptionvil blive kastet under kørsel. Derfor blev et forbud mod oprettelse af sådanne arrays tilføjet til Java. Dette lader os undgå sådanne situationer.

Hvordan kommer jeg uden om typesletning?

Nå, vi lærte om tekstsletning. Lad os prøve at narre systemet! :) Opgave: Vi har en generisk TestClass<T>klasse. Vi ønsker at skrive en createNewT()metode til denne klasse, der vil skabe og returnere et nyt Tobjekt. Men det er umuligt, ikke? Al information om Ttypen slettes under kompileringen, og ved kørsel kan vi ikke bestemme, hvilken type objekt vi skal oprette. Der er faktisk én vanskelig måde at gøre dette på. Du husker sikkert, at Java har en Classklasse. Vi kan bruge det til at bestemme klassen for ethvert af vores objekter:

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);
   }
}
Konsoludgang:

class java.lang.Integer 
class java.lang.String
Men her er et aspekt, som vi ikke har talt om. I Oracle-dokumentationen kan du se, at klasseklassen er generisk! Type sletning - 3

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

Dokumentationen siger, "T - typen af ​​klassen, der er modelleret af dette klasseobjekt." Når vi oversætter dette fra dokumentationssproget til almindelig tale, forstår vi, at objektets klasse Integer.classikke bare er Class, men snarere Class<Integer>. Objektets type String.classer ikke kun Class, men snarere Class<String>osv. Hvis det stadig ikke er klart, kan du prøve at tilføje en type-parameter til det 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 nu, ved at bruge denne viden, kan vi omgå typesletning og udføre vores opgave! Lad os prøve at få information om en typeparameter. Vores type argument vil være MySecretClass:

public class MySecretClass {

   public MySecretClass() {

       System.out.println("A MySecretClass object was created successfully!");
   }
}
Og her er hvordan vi bruger vores løsning 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();

   }
}
Konsoludgang:

A MySecretClass object was created successfully!
Vi har lige givet det påkrævede klasseargument til konstruktøren af ​​vores generiske klasse:

TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Dette gjorde det muligt for os at gemme informationen om typeargumentet, hvilket forhindrede det i at blive helt slettet. Som et resultat var vi i stand til at skabe enTobjekt! :) Dermed slutter dagens lektion. Du skal altid huske typesletning, når du arbejder med generiske lægemidler. Denne løsning ser ikke særlig praktisk ud, men du bør forstå, at generiske lægemidler ikke var en del af Java-sproget, da det blev oprettet. Denne funktion, som hjælper os med at skabe parametriserede samlinger og fange fejl under kompilering, blev taget på senere. På nogle andre sprog, der inkluderede generiske artikler fra den første version, er der ingen type sletning (f.eks. i C#). I øvrigt er vi ikke færdige med at studere generika! I den næste lektion vil du stifte bekendtskab med et par flere funktioner i generiske lægemidler. Indtil videre ville det være godt at løse et par opgaver! :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION