Java >> Java tutorial >  >> Tag >> extends

cirkulær generisk :grænseflade IFoo <T udvider IFoo <T>>

Dette er et velkendt formsprog i Java.

Nej. Det er ikke nyttigt og bør ikke bruges. Med undtagelse af Enum (hvilket er et særligt tilfælde, fordi enum typer er compiler-genererede), er dette mønster aldrig set nogen steder i de officielle Java-biblioteker eller nogen steder i nogen officiel Java-dokumentation.

Formålet med generiske grænser på en typevariabel er at opsætte en relation, så kode inde i den generiske klasse eller generiske metode kan bruge denne garanti til at udføre nogle operationer uden casts, som er potentielt usikre. Generiske grænser bør være så ubegrænsende som muligt, mens de stadig forhindrer casts i koden.

Derfor er det eneste legitime formål med en bundet interface IComparable<T extends IComparable<T>> ville være, hvis 1) det faktisk er en klasse, ikke en grænseflade (grænseflader har ikke kode, og der er derfor ingen casts at undgå), og 2) kode i klassen skal udføre følgende operation:brug en metode til at få T ud af sig selv, og derefter kalde på det en metode, der kræver IComparable<T> . Derefter en garanti for, at T udvider IComparable<T> ville tillade dette at ske uden afstøbninger.

Jeg har set mange anvendelser af dette mønster, og i 99% af tilfældene behøver de ikke at udføre ovennævnte operation. Tværtimod, 99 % af tiden, er det, folk ønsker at gøre, på en eller anden måde at udtrykke det T "er lig med" IComparable<T> , hvilket ovenstående mønster ikke garanterer (og som er umuligt at udtrykke i Java). Det garanterer kun, at T udvider IComparable<T> , men ikke den IComparable<T> udvider T .

Det meste af tiden, når folk bruger dette mønster, bruger de det til en klasse, hvori de gør (T)this . Det faktum, at der skal være en rollebesætning, viser, at dette er potentielt usikkert; at dens typesikkerhed ikke er garanteret af grænserne (der er ingen garanti for, at this (af typen IComparable<T> ) udvider T ). Det er faktisk usikkert; et velkendt eksempel er class Foo extends IComparable<Foo> , og derefter class Bar extends IComparable<Foo> , altså this (af typen Bar ) udvider ikke T (Foo ).

Så hvis du nogensinde ser den kode, er den næsten helt sikkert skrevet med en eller anden misforståelse om, hvad den gør, og den bør næsten altid ændres til dette:

public interface IComparable<T> {
    public int compare(T item);
}

Som en øvelse udfordrer jeg dig til at finde et uddrag, hvor interface IComparable<T extends IComparable<T>> virker, og hvor interface IComparable<T> virker ikke.


Faktisk - du behøver ikke den rekursive definition i dit tilfælde.

public interface IComparable<T> {
  public int compare(T item);
}

public class Foo implements IComparable<Foo> {
  @Override
  public int compare(Foo o) {
    return 0;
  }
}

Er ganske tilstrækkeligt til at definere, hvad du forsøger.

Et almindeligt sted for rekursive definitioner er, når du arbejder med enum . Du vil ofte se E extends Enum<E> og i denne sammenhæng giver det faktisk meget mening, fordi Enum klasse er defineret med en generisk type E at den opregner.

Som en fortælling til @NPE's indlæg om den refererede diskussion:

public interface ResultItem<T extends ResultItem<T>> {
    public int getConfidence();
    public boolean equals(T item);
    public T cloneWithConfidence(int newConfidence);
}

Det, der sker her, er, at i stedet for at definere din klasse i form af en specifik generisk klasse T du angiver, at T skal forlænges ResultItem , dvs. det er en hvilken som helst underklasse af ResultItem . Selvfølgelig siden ResultItem er en generisk klasse, du skal give dens generiske parameter, som i dette tilfælde er T sig selv. Således T extends ResultItem<T> . Dette er ikke en rekursiv definition, det er en subclass capture definition.


Først om dine tvivl om gentagelsen:

Det er en form for gentagelse (som X bliver defineret af noget relateret til X), men selvom det er det, er det kun ét niveau. Forestil dig, at compileren har en funktion define som definerer noget, den ikke allerede kender ved at læse og kompilere kildefilen, og en hjælpefunktion get_list_of_methods . Så i tilfældet med ResultItem vi har:

1) define(interface ResultItem<T extends ResultItem<T>>)

Til dette, en definition af den indre ResultItem<T> er nødvendig. Men vi behøver ikke at vide alt om ResultItem<T> , vi vil kun vide, hvad det betyder, at noget udvider ResultItem<T> , derfor i første omgang:

2) get_list_of_methods(ResultItem<T>)

Dette parser kildefilen og får listen over alle metoderne for ResultItem<T> - vi behøver ikke at kompilere dem nu.

3) opret generisk begrænsning, der beskytter T vil have alle metoderne ResultItem<T>

4) Fortsættelse af trin 1) - parse kildefilen for at få definitionerne af alle metoderne for ResultItem<T> . At vide, hvilke metoder der implementeres af T fra trin 2 kan vi kompilere metoderne.

Sandsynligvis i virkeligheden er dette mere kompliceret, men det hjælper til at se, at der ikke er behov for en gentagelse for at fortolke dette. Så det er ikke mere tilbagevendende end class X at have en metode, der accepterer X . Det kræver bare mere end et pas. På ældre sprog såsom lookahead definitioner, hvor det ikke er muligt. Selv i C++ skal du nu fremsende erklæringer:

class X;

class Y
{
private:
  X* x;
};

class X
{
};

Dernæst om anvendeligheden af ​​dette. Ved at indsamle information fra kommentarerne og relaterede emner vil jeg sige, at denne mekanisme bruges til at udtrykke hensigten om klasser, der implementerer en given grænseflade og gør grænsefladen mere nyttig og bekvem. Dette emne forklarer det mere detaljeret:Hvordan kan jeg få en interface-instansmetode til kun at acceptere argumenter af samme klasse?

I det andet tilfælde er der ingen fordel for klasser, der implementerer grænsefladen:

public interface IComparable<T extends IComparable<T>> {
  public int compare(T item);
}

Det kan dog tolkes som at udtrykke det faktum, at en enhed, som er comparable er kun sammenlignelig med sammenlignelige varer af samme type. Det er ikke det bedste eksempel, som nævnt i andre svar og kommentarer, da du stadig kan skrive class X implements IComparable<Y> så længe Y også er sammenlignelig. Flere detaljer her:Hvordan kan jeg få en interface-instansmetode til kun at acceptere argumenter af samme klasse?

Flere detaljer om brugen af ​​dette formsprog:http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ106.


Java tag