Programování

Jak používat generiky Java, aby se zabránilo ClassCastExceptions

Java 5 přinesla generika do jazyka Java. V tomto článku vás seznámím s generikami a pojednám o generických typech, obecných metodách, generikách a odvození typů, kontroverznosti generik a znečištění generiky a haldy.

stáhnout Získat kód Stáhněte si zdrojový kód pro příklady v tomto výukovém programu Java 101. Vytvořil Jeff Friesen pro JavaWorld.

Co jsou to generika?

Generika jsou kolekce souvisejících jazykových funkcí, které umožňují typům nebo metodám pracovat na objektech různých typů a zároveň poskytují bezpečnost typu při kompilaci. Obecné vlastnosti řeší problém java.lang.ClassCastExceptions je vyvolána za běhu, což je výsledek kódu, který není typově bezpečný (tj. casting objektů z jejich aktuálních typů na nekompatibilní typy).

Generics and the Java Collections Framework

Generika jsou široce používána v prostředí Java Collections Framework (formálně představeno v budoucnu) Java 101 články), ale nejsou pro ni exkluzivní. Generika se také používají v jiných částech standardní knihovny tříd Java, včetně java.lang.Class, java.lang. Srovnatelné, java.lang.ThreadLocal, a java.lang.ref.WeakReference.

Zvažte následující fragment kódu, který demonstruje nedostatek bezpečnosti typu (v kontextu rámce Java Collections Framework java.util.LinkedList třída), která byla v kódu Java běžná před zavedením generik:

Seznam doubleList = nový LinkedList (); doubleList.add (nový Double (3.5)); Double d = (Double) doubleList.iterator (). Next ();

I když cílem výše uvedeného programu je pouze ukládání java.lang.Double objekty v seznamu, nic nebrání tomu, aby byly uloženy jiné druhy objektů. Můžete například určit doubleList.add ("Dobrý den"); přidat a řetězec java.lang objekt. Při ukládání jiného druhu objektu je však poslední řádek (Dvojnásobek) vyvolá operátor obsazení ClassCastException být hozen při konfrontaci sDvojnásobek objekt.

Protože tento nedostatek zabezpečení typu se nezjistí až za běhu, vývojář si nemusí být vědom problému a ponechat jej na klientovi (místo kompilátoru), aby jej objevil. Generika pomáhají kompilátoru upozornit vývojáře na problém s ukládáním objektu pomocíDvojnásobek zadejte do seznamu povolením vývojáři označit seznam jako pouze obsahující Dvojnásobek předměty. Tato pomoc je ukázána níže:

Seznam doubleList = nový LinkedList (); doubleList.add (nový Double (3.5)); Double d = doubleList.iterator (). Next ();

Seznam nyní zní „Seznam z Dvojnásobek.” Seznam je obecné rozhraní vyjádřené jako Seznam, to trvá a Dvojnásobek argument typu, který je také určen při vytváření skutečného objektu. Kompilátor nyní může vynutit správnost typu při přidávání objektu do seznamu - seznam by se například mohl uložit Dvojnásobek pouze hodnoty. Toto vynucování odstraňuje potřebu (Dvojnásobek) obsazení.

Objevování obecných typů

A obecný typ je třída nebo rozhraní, které zavádí sadu parametrizovaných typů pomocí a seznam parametrů formálního typu, což je čárkami oddělený seznam názvů parametrů typu mezi dvojicí hranatých závorek. Obecné typy dodržují následující syntaxi:

třída identifikátor<formalTypeParameterList> Rozhraní {// class body} identifikátor<formalTypeParameterList> {// tělo rozhraní}

Rámec Java Collections Framework nabízí mnoho příkladů obecných typů a jejich seznamů parametrů (a v tomto článku na ně odkazuji). Například, sada java.util je obecný typ, je jeho formální seznam parametrů typu a E je parametr osamělého typu seznamu. Dalším příkladem jejava.util.Map.

Konvence pojmenování parametrů typu Java

Konvence programování v jazyce Java diktuje, že názvy parametrů typu musí být velká velká písmena, například E pro prvek, K. pro klíč, PROTI pro hodnotu a T pro typ. Pokud je to možné, nepoužívejte nesmyslné jméno jako Pjava.util.List znamená seznam prvků, ale co byste tím mohli myslet Seznam

A parametrizovaný typ je instance obecného typu, kde jsou parametry typu obecného typu nahrazeny argumenty skutečného typu (názvy typů). Například, Soubor je parametrizovaný typ kde Tětiva je skutečný argument typu nahrazující parametr typu E.

Jazyk Java podporuje následující druhy argumentů skutečného typu:

  • Typ betonu: Název parametru třídy nebo jiného referenčního typu je předán parametru typu. Například v Seznam, Zvíře je předán E.
  • Typ parametrizovaného betonu: Název parametrizovaného typu je předán parametru typu. Například v Soubor, Seznam je předán E.
  • Typ pole: Pole je předáno parametru typu. Například v Mapa, Tětiva je předán K. a Tětiva[] je předán PROTI.
  • Parametr typu: Parametr typu je předán parametru typu. Například v třída Kontejner {Nastavit prvky; }, E je předán E.
  • Divoká karta: Otazník (?) je předán parametru typu. Například v Třída, ? je předán T.

Každý generický typ implikuje existenci a surový typ, což je obecný typ bez formálního seznamu parametrů typu. Například, Třída je surový typ pro Třída. Na rozdíl od obecných typů lze surové typy použít s jakýmkoli druhem objektu.

Deklarace a používání obecných typů v Javě

Deklarování obecného typu zahrnuje určení seznamu parametrů formálního typu a přístup k těmto parametrům typu během jeho implementace. Použití obecného typu zahrnuje předání argumentů skutečného typu do jeho parametrů typu při vytváření instance obecného typu. Viz Výpis 1.

Výpis 1:GenDemo.java (verze 1)

class Container {private E [] elements; soukromý int index; Kontejner (velikost int) {elements = (E []) nový objekt [velikost]; index = 0; } void add (prvek E) {prvky [index ++] = prvek; } E get (int index) {návratové prvky [index]; } int size () {návratový index; }} public class GenDemo {public static void main (String [] args) {Container con = new Container (5); con.add ("sever"); con.add ("jih"); con.add ("východ"); con.add ("Západ"); for (int i = 0; i <con.size (); i ++) System.out.println (con.get (i)); }}

Výpis 1 ukazuje generickou deklaraci typu a použití v kontextu jednoduchého typu kontejneru, který ukládá objekty příslušného typu argumentu. Kvůli zjednodušení kódu jsem vynechal kontrolu chyb.

The Kontejner třída se deklaruje jako obecný typ zadáním seznam parametrů formálního typu. Zadejte parametr E se používá k identifikaci typu uložených prvků, prvku, který má být přidán do interního pole, a návratového typu při načítání prvku.

The Kontejner (velikost int) konstruktor vytvoří pole pomocí elements = (E []) nový objekt [velikost];. Pokud se divíte, proč jsem to nespecifikoval elements = new E [size];, důvodem je, že to není možné. Mohlo by to vést k a ClassCastException.

Zkompilovat výpis 1 (javac GenDemo.java). The (E[]) cast způsobí, že kompilátor vydá varování, že obsazení není zaškrtnuto. Označí to možnost, že odlidnění z Objekt[] na E[] může porušovat bezpečnost typu, protože Objekt[] lze uložit jakýkoli typ objektu.

Všimněte si však, že v tomto příkladu neexistuje způsob, jak porušit bezpečnost typu. Jednoduše není možné uložitE objekt v interním poli. Předpona Kontejner (velikost int) konstruktor s @SuppressWarnings („nezaškrtnuto“) by potlačil tuto varovnou zprávu.

Vykonat java GenDemo pro spuštění této aplikace. Měli byste dodržovat následující výstup:

sever jih východ západ

Parametry vázaného typu v Javě

The E v Soubor je příkladem parametr neomezeného typu protože můžete předat jakýkoli argument skutečného typu E. Můžete například určit Soubor, Soubornebo Soubor.

Někdy budete chtít omezit typy argumentů skutečného typu, které lze předat parametru typu. Například možná chcete omezit parametr typu tak, aby přijímal pouze Zaměstnanec a jeho podtřídy.

Parametr typu můžete omezit zadáním parametru horní hranice, což je typ, který slouží jako horní limit pro typy, které lze předat jako argumenty skutečného typu. Určete horní mez pomocí vyhrazeného slova rozšiřuje následuje název typu horní meze.

Například, zaměstnanci třídy omezuje typy, které lze předat Zaměstnanci na Zaměstnanec nebo podtřída (např. Účetní). Upřesnění noví zaměstnanci by bylo legální, zatímco noví zaměstnanci by bylo nezákonné.

K parametru typu můžete přiřadit více než jednu horní hranici. První vazba však musí být vždy třída a další hranice musí být vždy rozhraní. Každá vazba je oddělena od svého předchůdce znakem ampersand (&). Podívejte se na výpis 2.

Výpis 2: GenDemo.java (verze 2)

import java.math.BigDecimal; import java.util.Arrays; abstraktní třída Zaměstnanec {private BigDecimal hourlySalary; soukromé jméno řetězce; Zaměstnanec (název řetězce, BigDecimal hourlySalary) {this.name = name; this.hourlySalary = hourlySalary; } public BigDecimal getHourlySalary () {návrat hourlySalary; } public String getName () {zpáteční jméno; } public String toString () {návratové jméno + ":" + hourlySalary.toString (); }} třída Účetní rozšiřuje Zaměstnanec implementuje Srovnatelné {Účetní (název řetězce, BigDecimal hourlySalary) {super (name, hourlySalary); } public int compareTo (Accountant acct) {return getHourlySalary (). compareTo (acct.getHourlySalary ()); }} třída SortedEmployees {private E [] zaměstnanci; soukromý int index; @SuppressWarnings ("nezaškrtnuto") SortedEmployees (int size) {zaměstnanci = (E []) nový zaměstnanec [velikost]; int index = 0; } void add (E emp) {zaměstnanci [index ++] = emp; Arrays.sort (zaměstnanci, 0, index); } E get (int index) {návrat zaměstnanců [index]; } int size () {návratový index; }} veřejná třída GenDemo {public static void main (String [] args) {SortedEmployees se = new SortedEmployees (10); se.add (nový účetní („John Doe“, nový BigDecimal („35,40“))); se.add (nový účetní („George Smith“, nový BigDecimal („15.20“))); se.add (nový účetní („Jane Jones“, nový BigDecimal („25,60“))); for (int i = 0; i <se.size (); i ++) System.out.println (se.get (i)); }}

Výpis 2 Zaměstnanec třída abstrahuje koncept zaměstnance, který pobírá hodinovou mzdu. Tato třída je podtřída Účetní, který také implementuje Srovnatelný to naznačit ÚčetníS lze porovnat podle jejich přirozeného řádu, což je v tomto příkladu hodinová mzda.

The java.lang. Srovnatelné rozhraní je deklarováno jako obecný typ s názvem jediného parametru typu T. Toto rozhraní poskytuje int compareTo (To) metoda, která porovnává aktuální objekt s argumentem (typu T), vrací záporné celé číslo, nulu nebo kladné celé číslo, protože tento objekt je menší než, rovný nebo větší než zadaný objekt.

The Seřazení zaměstnanci třída vám umožní ukládat Zaměstnanec instance podtřídy, které se implementují Srovnatelný v interním poli. Toto pole je tříděno (přes java.util.Arrays třídy void sort (Object [] a, int fromIndex, int toIndex) třídní metoda) vzestupně podle hodinové mzdy po an Zaměstnanec instance podtřídy je přidána.

Zkompilovat výpis 2 (javac GenDemo.java) a spusťte aplikaci (java GenDemo). Měli byste dodržovat následující výstup:

George Smith: 15,20 Jane Jones: 25,60 John Doe: 35,40

Dolní hranice a parametry obecného typu

Pro parametr obecného typu nemůžete určit dolní mez. Abychom pochopili, proč doporučuji přečíst si Časté dotazy Java Generics o Angelice Langerové na téma dolních mezí, které podle ní „by byly matoucí a nijak zvlášť užitečné.“

Vzhledem k zástupným znakům

Řekněme, že chcete vytisknout seznam objektů bez ohledu na to, zda se jedná o řetězce, zaměstnance, tvary nebo nějaký jiný typ. Váš první pokus může vypadat jako to, co je uvedeno v seznamu 3.

Výpis 3: GenDemo.java (verze 3)

import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class GenDemo {public static void main (String [] args) {List direction = new ArrayList (); direction.add ("sever"); direction.add ("jih"); direction.add ("východ"); direction.add ("západ"); printList (pokyny); List grades = new ArrayList (); grades.add (nové celé číslo (98)); grades.add (nové celé číslo (63)); grades.add (nové celé číslo (87)); printList (stupně); } static void printList (List list) {Iterator iter = list.iterator (); while (iter.hasNext ()) System.out.println (iter.next ()); }}

Zdá se logické, že seznam řetězců nebo seznam celých čísel je podtypem seznamu objektů, přesto si kompilátor stěžuje, když se pokusíte zkompilovat tento výpis. Konkrétně vám řekne, že seznam řetězců nelze převést na seznam objektů a podobně pro seznam celých čísel.

Chybová zpráva, kterou jste obdrželi, souvisí se základním pravidlem generik:

Copyright cs.verticalshadows.com 2022