1. L'introduction du concept de génériques (pourquoi les génériques sont-ils nécessaires) ?
Tout d'abord, jetons un coup d'œil au code court suivant :
public class GenericTest { public static void main(String[] args) { List list = new ArrayList(); list.add("qqyumidi"); list.add("corn"); list.add(100); for (int i = 0; i < list.size(); i++) { String name = (String) list.get(i); // 1 System.out.println("name:" + name); } } }
définit une collection de type List, et y ajoute d'abord deux valeurs de type chaîne, puis ajoutez une valeur de type Integer. Ceci est tout à fait autorisé, car le type de liste par défaut est Objet. Dans les boucles suivantes, des erreurs similaires à //1 peuvent facilement se produire en raison de l'oubli d'ajouter des valeurs de type Integer à la liste avant ou pour d'autres raisons de codage. Parce que la phase de compilation est normale, mais une exception "java.lang.ClassCastException" se produit pendant l'exécution. De telles erreurs sont donc difficiles à détecter lors du codage.
Avant Java SE 1.5, en l'absence de génériques, le paramètre "arbitrary" était implémenté via une référence au type Object. L'inconvénient de "arbitrary" était qu'une conversion de type explicite était nécessaire. la conversion nécessite que le développeur connaisse au préalable le type de paramètre réel. Pour les erreurs de conversion de type forcée, le compilateur peut ne pas générer d'erreur et une exception se produira pendant l'exécution. Il s'agit d'un risque de sécurité. L'avantage des génériques est que la sécurité des types est vérifiée lors de la compilation et que tous les transtypages sont automatiques et implicites, améliorant ainsi la réutilisation du code.
2. Que sont les génériques ?
Les génériques sont une nouvelle fonctionnalité de Java SE 1.5. L'essence des génériques est un type paramétré, ce qui signifie que le type de données sur lequel l'opération est effectuée est spécifié en tant que paramètre. Ce type de paramètre peut être utilisé dans la création de classes, d'interfaces et de méthodes, appelées respectivement classes génériques, interfaces génériques et méthodes génériques. Alors, comment comprenez-vous les types paramétrés ? Comme son nom l'indique, le type est paramétré à partir du type spécifique d'origine, similaire aux paramètres variables de la méthode. À ce stade, le type est également défini sous forme de paramètres (qui peuvent être appelés paramètres de type), puis le type. un type spécifique est transmis lors de l'utilisation/de l'appel du type (argument de type).
Cela semble un peu compliqué. Tout d’abord, jetons un coup d’œil à la manière générique d’écrire l’exemple ci-dessus.
public class GenericTest { public static void main(String[] args) { /* List list = new ArrayList(); list.add("qqyumidi"); list.add("corn"); list.add(100); */ List<String> list = new ArrayList<String>(); list.add("qqyumidi"); list.add("corn"); //list.add(100); // 1 提示编译错误 for (int i = 0; i < list.size(); i++) { String name = list.get(i); // 2 System.out.println("name:" + name); } } }
Après avoir utilisé l'écriture générique, une erreur de compilation se produira lors de la tentative d'ajout d'un objet de type Integer à //1 via List
Combiné avec la définition générique ci-dessus, nous savons que dans List
public interface List<E> extends Collection<E> { int size(); boolean isEmpty(); boolean contains(Object o); Iterator<E> iterator(); Object[] toArray(); <T> T[] toArray(T[] a); boolean add(E e); boolean remove(Object o); boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean addAll(int index, Collection<? extends E> c); boolean removeAll(Collection<?> c); boolean retainAll(Collection<?> c); void clear(); boolean equals(Object o); int hashCode(); E get(int index); E set(int index, E element); void add(int index, E element); E remove(int index); int indexOf(Object o); int lastIndexOf(Object o); ListIterator<E> listIterator(); ListIterator<E> listIterator(int index); List<E> subList(int fromIndex, int toIndex); }
Nous pouvons voir qu'après avoir utilisé la définition générique dans l'interface List, le E dans
Naturellement, ArrayList est la classe d'implémentation de l'interface List, et sa forme de définition est :
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } public E get(int index) { rangeCheck(index); checkForComodification(); return ArrayList.this.elementData(offset + index); } //...省略掉其他具体的定义过程 }
De là, nous comprenons du point de vue du code source pourquoi l'objet de type Integer est ajouté at //1 Erreur de compilation, et le type obtenu par get() at //2 est directement le type String.
3. Qu'est-ce que la bibliothèque de classes tuple et comment l'utiliser ?
Pourquoi utiliser un tuple ?
Les tuples, comme les listes, peuvent être utilisés pour le stockage de données et contenir plusieurs données ; mais ils sont différents des listes : les listes ne peuvent stocker que le même type de données, mais les tuples sont différents, ils peuvent stocker différents types de données, tels que int, string, list, etc., peuvent être stockés en même temps et peuvent être étendus à l'infini selon les besoins.
Par exemple, dans les applications Web, un problème souvent rencontré est la pagination des données. L'interrogation de la pagination doit inclure plusieurs informations : le numéro de page actuel et la taille de la page renvoie des données : l'enregistrement de données du ; page actuelle, mais si vous devez afficher la page actuelle, la taille de la page, le nombre total de pages et d'autres informations au premier plan, vous devez avoir une autre information : le nombre total d'enregistrements de données, puis calculer le nombre total de pages et autres informations basées sur les informations ci-dessus. À l'heure actuelle, lors de l'interrogation d'une certaine page d'informations, deux types de données doivent être renvoyés, l'un est une liste (enregistrement de données actuel) et l'autre est un int (nombre total d'enregistrements). Bien entendu, ces deux valeurs peuvent être obtenues selon deux méthodes et deux connexions à la base de données. En fait, lors de l'interrogation de la liste, le nombre total d'enregistrements a été obtenu via une requête SQL. Si vous ouvrez une autre méthode et effectuez une autre connexion à la base de données pour interroger le nombre total d'enregistrements, ce sera un peu inutile, une perte de temps. un gaspillage de code et un gaspillage de vie. Mots sérieux ~ Dans ce cas, nous pouvons utiliser des tuples pour obtenir le nombre total d'enregistrements et les enregistrements de la page actuelle dans une seule connexion à la base de données, et les stocker dedans, simple et clair !
4. Interfaces génériques personnalisées, classes génériques et méthodes génériques
从上面的内容中,大家已经明白了泛型的具体运作过程。也知道了接口、类和方法也都可以使用泛型去定义,以及相应的使用。是的,在具体使用时,可以分为泛型接口、泛型类和泛型方法。
自定义泛型接口、泛型类和泛型方法与上述Java源码中的List、ArrayList类似。如下,我们看一个最简单的泛型类和方法定义:
public class GenericTest { public static void main(String[] args) { Box<String> name = new Box<String>("corn"); System.out.println("name:" + name.getData()); } } class Box<T> { private T data; public Box() { } public Box(T data) { this.data = data; } public T getData() { return data; } }
在泛型接口、泛型类和泛型方法的定义过程中,我们常见的如T、E、K、V等形式的参数常用于表示泛型形参,由于接收来自外部使用时候传入的类型实参。那么对于不同传入的类型实参,生成的相应对象实例的类型是不是一样的呢?
public class GenericTest { public static void main(String[] args) { Box<String> name = new Box<String>("corn"); Box<Integer> age = new Box<Integer>(712); System.out.println("name class:" + name.getClass()); // com.qqyumidi.Box System.out.println("age class:" + age.getClass()); // com.qqyumidi.Box System.out.println(name.getClass() == age.getClass()); // true } }
由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),当然,在逻辑上我们可以理解成多个不同的泛型类型。
究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
五. 类型通配符
接着上面的结论,我们知道,Box
为了弄清这个问题,我们继续看下下面这个例子:
public class GenericTest { public static void main(String[] args) { Box<Number> name = new Box<Number>(99); Box<Integer> age = new Box<Integer>(712); getData(name); //The method getData(Box<Number>) in the type GenericTest is //not applicable for the arguments (Box<Integer>) getData(age); // 1 } public static void getData(Box<Number> data){ System.out.println("data :" + data.getData()); } }
我们发现,在代码//1处出现了错误提示信息:The method getData(Box
public class GenericTest { public static void main(String[] args) { Box<Integer> a = new Box<Integer>(712); Box<Number> b = a; // 1 Box<Float> f = new Box<Float>(3.14f); b.setData(f); // 2 } public static void getData(Box<Number> data) { System.out.println("data :" + data.getData()); } } class Box<T> { private T data; public Box() { } public Box(T data) { setData(data); } public T getData() { return data; } public void setData(T data) { this.data = data; } }
这个例子中,显然//1和//2处肯定会出现错误提示的。在此我们可以使用反证法来进行说明。
假设Box
好,那我们回过头来继续看“类型通配符”中的第一个例子,我们知道其具体的错误提示的深层次原因了。那么如何解决呢?总部能再定义一个新的函数吧。这和Java中的多态理念显然是违背的,因此,我们需要一个在逻辑上可以用来表示同时是Box
类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box>在逻辑上是Box
public class GenericTest { public static void main(String[] args) { Box<String> name = new Box<String>("corn"); Box<Integer> age = new Box<Integer>(712); Box<Number> number = new Box<Number>(314); getData(name); getData(age); getData(number); } public static void getData(Box<?> data) { System.out.println("data :" + data.getData()); } }
有时候,我们还可能听到类型通配符上限和类型通配符下限。具体有是怎么样的呢?
在上面的例子中,如果需要定义一个功能类似于getData()的方法,但对类型实参又有进一步的限制:只能是Number类及其子类。此时,需要用到类型通配符上限。
public class GenericTest { public static void main(String[] args) { Box<String> name = new Box<String>("corn"); Box<Integer> age = new Box<Integer>(712); Box<Number> number = new Box<Number>(314); getData(name); getData(age); getData(number); //getUpperNumberData(name); // 1 getUpperNumberData(age); // 2 getUpperNumberData(number); // 3 } public static void getData(Box<?> data) { System.out.println("data :" + data.getData()); } public static void getUpperNumberData(Box<? extends Number> data){ System.out.println("data :" + data.getData()); } }
此时,显然,在代码//1处调用将出现错误提示,而//2 //3处调用正常。
类型通配符上限通过形如Box extends Number>形式定义,相对应的,类型通配符下限为Box super Number>形式,其含义与类型通配符上限正好相反
六. 怎么构建复杂模型如list元组?
泛型的一个重要好处是能够简单而安全地创建复杂的模型。如List元组。
package Generics; import java.util.ArrayList; class ThreeTuple2<A,B,C>{ public final A first; public final B second; private final C three; public ThreeTuple2(A a,B b,C c){ first = a; second = b; three = c; } public String toString(){ return "(" + first + "," + second + "," + three + ")"; } } public class TupleList<A,B,C> extends ArrayList<ThreeTuple2<A,B,C>> { static ThreeTuple2<Integer,String,Character> h(){ return new ThreeTuple2<Integer,String,Character>(99,"掌上洪城",'a'); } public static void main(String[] args) { TupleList<Integer,String,Character> ts = new TupleList<Integer,String,Character>(); ts.add(h()); ts.add(h()); for(ThreeTuple2<Integer,String,Character> ttp:ts) System.out.println(ttp); } } package Generics; import java.util.ArrayList; class ThreeTuple2<A,B,C>{ public final A first; public final B second; private final C three; public ThreeTuple2(A a,B b,C c){ first = a; second = b; three = c; } public String toString(){ return "(" + first + "," + second + "," + three + ")"; } } public class TupleList<A,B,C> extends ArrayList<ThreeTuple2<A,B,C>> { static ThreeTuple2<Integer,String,Character> h(){ return new ThreeTuple2<Integer,String,Character>(99,"掌上洪城",'a'); } public static void main(String[] args) { TupleList<Integer,String,Character> ts = new TupleList<Integer,String,Character>(); ts.add(h()); ts.add(h()); for(ThreeTuple2<Integer,String,Character> ttp:ts) System.out.println(ttp); } } /* 输出结果为: (99,掌上洪城,a) (99,掌上洪城,a) */
七. 泛型的擦除
package generics; import java.util.*; public class ErasedTypeEquivalence { public static void main(String[] args) { Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println(c1 == c2); } } /* * Output: true */// :~
在泛型内部,无法获得任何有关泛型参数类型的信息。
ArrayList
擦除的补偿
要想在表达式中使用类型,需要显式地传递类型的class对象。
package generics; class Building { } class House extends Building { } public class ClassTypeCapture<T> { Class<T> kind; public ClassTypeCapture(Class<T> kind) { this.kind = kind; } public boolean f(Object arg) { return kind.isInstance(arg); } public static void main(String[] args) { ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<Building>(Building.class); System.out.println(ctt1.f(new Building())); System.out.println(ctt1.f(new House())); ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.class); System.out.println(ctt2.f(new Building())); System.out.println(ctt2.f(new House())); } } /* * Output: true true false true */// :~
八. 可以创建泛型数组吗?相应的应用场景怎么处理?
正如你在下面示例Erased.java中所见,不能创建泛型数组。一般的解决方案是任何想要创建泛型数组的地方都使用ArrayList:
package generics; public class Erased<T> { private final int SIZE = 100; public static void f(Object arg) { if (arg instanceof T) { } // Cannot make a static reference to the non-static type T T var = new T(); // Error T[] array = new T[SIZE]; // Error T[] array = (T) new Object[SIZE]; // Unchecked warning } } /// :~
使用ArrayList示例
package generics; import java.util.*; public class ListOfGenerics<T> { private List<T> array = new ArrayList<T>(); public void add(T item) { array.add(item); } public T get(int index) { return array.get(index); } } /// :~
九. 泛型限定(上限和下限)的表达式是怎样的?
上限:?extends E:可以接收E类型或者E的子类型对象。
下限:?super E:可以接收E类型或者E的父类型对象。
上限什么时候用:往集合中添加元素时,既可以添加E类型对象,又可以添加E的子类型对象。为什么?因为取的时候,E类型既可以接收E类对象,又可以接收E的子类型对象。
下限什么时候用:当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。
十. 什么时候用泛型?
当接口、类及方法中的操作的引用数据类型不确定的时候,以前用的Object来进行扩展的,现在可以用泛型来表示。这样可以避免强转的麻烦,而且将运行问题转移到的编译时期。
泛型的细节:
1)、泛型到底代表什么类型取决于调用者传入的类型,如果没传,默认是Object类型;
2)、使用带泛型的类创建对象时,等式两边指定的泛型必须一致;
原因:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了;
3)、等式两边可以在任意一边使用泛型,在另一边不使用(考虑向后兼容);
ArrayList
//要保证左右两边的泛型具体类型一致就可以了,这样不容易出错。
ArrayList al = new ArrayList
al.add("aa"); //错
//因为集合具体对象中既可存储String,也可以存储Object的其他子类,所以添加具体的类型对象不合适,类型检查会出现安全问题。 ?extendsObject 代表Object的子类型不确定,怎么能添加具体类型的对象呢?
public static voidmethod(ArrayList extends Object> al) {
al.add("abc"); //错
//只能对al集合中的元素调用Object类中的方法,具体子类型的方法都不能用,因为子类型不确定。
十一. Java类库中的泛型有那些?
所有的标准集合接口都是泛型化的—— Collection
除了集合类之外,Java 类库中还有几个其他的类也充当值的容器。这些类包括 WeakReference、SoftReference 和 ThreadLocal。