Hoi! In de les van vandaag gaan we verder met het bestuderen van generieke geneesmiddelen. Toevallig is dit een groot onderwerp, maar je kunt er niet omheen — het is een buitengewoon belangrijk onderdeel van de taal :) Wanneer je de Oracle-documentatie over generieke geneesmiddelen bestudeert of online tutorials leest, kom je de termen niet-reifieerbare typen en verifieerbare typen . Een herifieerbaar type is een type waarvoor informatie tijdens runtime volledig beschikbaar is. In Java omvatten dergelijke typen primitieven, onbewerkte typen en niet-generieke typen. Niet-reifieerbare typen daarentegen zijn typen waarvan de informatie wordt gewist en tijdens runtime ontoegankelijk wordt. Toevallig zijn dit generieke geneesmiddelen -
List<String>
, List<Integer>
, etc.
Trouwens, weet je nog wat varargs is?
Voor het geval je het vergeten bent, dit is een argument met een variabele lengte. Ze zijn handig in situaties waarin we niet weten hoeveel argumenten aan onze methode kunnen worden doorgegeven. Als we bijvoorbeeld een rekenmachineklasse hebben die eensum
methode heeft. De sum()
methode kan 2 nummers ontvangen, of 3, of 5, of zoveel als je wilt. Het zou heel vreemd zijn om de sum()
methode te overbelasten voor elk mogelijk aantal argumenten. In plaats daarvan kunnen we dit doen:
public class SimpleCalculator {
public static int sum(int...numbers) {
int result = 0;
for(int i : numbers) {
result += i;
}
return result;
}
public static void main(String[] args) {
System.out.println(sum(1,2,3,4,5));
System.out.println(sum(2,9));
}
}
Console-uitvoer:
15
11
Dit laat ons zien dat er enkele belangrijke kenmerken zijn bij het gebruik van varargs in combinatie met generieke geneesmiddelen. Laten we naar de volgende code kijken:
import javafx.util.Pair;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
public static void main(String[] args) {
addAll(new ArrayList<String>(), // This is okay
"Leonardo da Vinci",
"Vasco de Gama"
);
// but here we get a warning
addAll(new ArrayList<Pair<String, String>>(),
new Pair<String, String>("Leonardo", "da Vinci"),
new Pair<String, String>("Vasco", "de Gama")
);
}
}
De addAll()
methode neemt als invoer a List<E>
en een willekeurig aantal E
objecten, en voegt vervolgens al deze objecten toe aan de lijst. In de main()
methode noemen we onze addAll()
methode twee keer. In het eerste geval voegen we twee gewone strings toe aan de List
. Alles is hier in orde. In het tweede geval voegen we twee Pair<String, String>
objecten toe aan het List
. Maar hier krijgen we onverwachts een waarschuwing:
Unchecked generics array creation for varargs parameter
Wat betekent dat? Waarom krijgen we een waarschuwing en waarom is er sprake van een array
? Onze code heeft immers geen array
! Laten we beginnen met het tweede geval. De waarschuwing vermeldt een array omdat de compiler het argument met variabele lengte (varargs) converteert naar een array. Met andere woorden, de handtekening van onze addAll()
methode is:
public static <E> void addAll(List<E> list, E... array)
Het ziet er eigenlijk zo uit:
public static <E> void addAll(List<E> list, E[] array)
Dat wil zeggen, in de main()
methode converteert de compiler onze code hierin:
public static void main(String[] args) {
addAll(new ArrayList<String>(),
new String[] {
"Leonardo da Vinci",
"Vasco de Gama"
}
);
addAll(new ArrayList<Pair<String,String>>(),
new Pair<String,String>[] {
new Pair<String,String>("Leonardo","da Vinci"),
new Pair<String,String>("Vasco","de Gama")
}
);
}
Een String
array is prima. Maar een Pair<String, String>
array is dat niet. Het probleem is dat het Pair<String, String>
een niet-verifieerbaar type is. Tijdens het compileren wordt alle informatie over typeargumenten (<String, String>) gewist. Het maken van arrays van een niet-herifieerbaar type is niet toegestaan in Java . U kunt dit zien als u handmatig een paar<String, String>-array probeert te maken
public static void main(String[] args) {
// Compilation error Generic array creation
Pair<String, String>[] array = new Pair<String, String>[10];
}
De reden ligt voor de hand: type veiligheid. Zoals u zich zult herinneren, moet u bij het maken van een array zeker specificeren welke objecten (of primitieven) de array zal opslaan.
int array[] = new int[10];
In een van onze vorige lessen hebben we het wissen van letters in detail onderzocht. In dit geval zorgt het wissen van typen ervoor dat we de informatie verliezen die de Pair
objectenparen opslaan <String, String>
. Het maken van de array zou onveilig zijn. Als u methoden gebruikt waarbij varargs en generieke geneesmiddelen betrokken zijn, vergeet dan niet om het wissen van typen en hoe het werkt te onthouden. Als je absoluut zeker bent van de code die je hebt geschreven en weet dat deze geen problemen zal veroorzaken, kun je de varargs-gerelateerde waarschuwingen uitschakelen met behulp van de @SafeVarargs
annotaties.
@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
Als u deze annotatie aan uw methode toevoegt, wordt de waarschuwing die we eerder tegenkwamen niet weergegeven. Een ander probleem dat kan optreden bij het gebruik van varargs met generieke geneesmiddelen is hoopvervuiling. Heapverontreiniging kan optreden in de volgende situatie:
import java.util.ArrayList;
import java.util.List;
public class Main {
static List<String> polluteHeap() {
List numbers = new ArrayList<Number>();
numbers.add(1);
List<String> strings = numbers;
strings.add("");
return strings;
}
public static void main(String[] args) {
List<String> stringsWithHeapPollution = polluteHeap();
System.out.println(stringsWithHeapPollution.get(0));
}
}
Console-uitvoer:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
In eenvoudige bewoordingen is hoopvervuiling wanneer objecten van het type A
zich op de hoop zouden moeten bevinden, maar objecten van het type B
daar terechtkomen vanwege fouten met betrekking tot typeveiligheid. In ons voorbeeld is dit precies wat er gebeurt. Eerst hebben we de onbewerkte numbers
variabele gemaakt en ArrayList<Number>
er een generieke verzameling ( ) aan toegewezen. Daarna hebben we het nummer toegevoegd 1
aan de collectie.
List<String> strings = numbers;
Op deze regel probeerde de compiler ons te waarschuwen voor mogelijke fouten door de waarschuwing " Ongecontroleerde toewijzing... " te geven, maar we negeerden deze. We eindigen met een generieke variabele van het type List<String>
die verwijst naar een generieke verzameling van het type ArrayList<Number>
. Het is duidelijk dat deze situatie tot problemen kan leiden! En dat doet het ook. Met behulp van onze nieuwe variabele voegen we een string toe aan de verzameling. We hebben nu hoopvervuiling - we hebben een nummer en vervolgens een string toegevoegd aan de geparametriseerde verzameling. De compiler waarschuwde ons, maar we negeerden de waarschuwing. Als gevolg hiervan krijgen we een ClassCastException
alleen terwijl het programma wordt uitgevoerd. Dus wat heeft dit te maken met varargs? Het gebruik van varargs met generieke geneesmiddelen kan gemakkelijk leiden tot hoopvervuiling. Hier is een eenvoudig voorbeeld:
import java.util.Arrays;
import java.util.List;
public class Main {
static void polluteHeap(List<String>... stringsLists) {
Object[] array = stringsLists;
List<Integer> numbersList = Arrays.asList(66,22,44,12);
array[0] = numbersList;
String str = stringsLists[0].get(0);
}
public static void main(String[] args) {
List<String> cars1 = Arrays.asList("Ford", "Fiat", "Kia");
List<String> cars2 = Arrays.asList("Ferrari", "Bugatti", "Zaporozhets");
polluteHeap(cars1, cars2);
}
}
Wat is hier aan de hand? Vanwege typeverwijdering, ons argument met variabele lengte
List<String>...stringsLists
wordt een array van lijsten, dwz List[]
, van objecten van een onbekend type (vergeet niet dat varargs verandert in een gewone array tijdens compilatie). Hierdoor kunnen we het eenvoudig toewijzen aan de Object[] array
variabele in de eerste regel van de methode — het type objecten in onze lijsten is gewist! En nu hebben we een Object[]
variabele waar we helemaal niets aan kunnen toevoegen, aangezien alle objecten in Java erven Object
! In eerste instantie hebben we alleen een reeks lijsten met tekenreeksen. Maar dankzij typeverwijdering en ons gebruik van varargs kunnen we eenvoudig een lijst met nummers toevoegen, wat we ook doen. Als gevolg hiervan vervuilen we de hoop door verschillende soorten objecten te mengen. Het resultaat zal er nog een zijn ClassCastException
als we proberen een string uit de array te lezen. Console-uitvoer:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Dergelijke onverwachte gevolgen kunnen worden veroorzaakt door gebruik te maken van varargs, een ogenschijnlijk eenvoudig mechanisme :) En daarmee komt de les van vandaag tot een einde. Vergeet niet een aantal taken op te lossen, en als je tijd en energie hebt, lees dan wat bij. " Effectieve Java " leest zichzelf niet! :) Tot de volgende keer!
GO TO FULL VERSION