Java泛型中都有哪些关键概念和用法?

2026-05-22 15:431阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计3651个文字,预计阅读时间需要15分钟。

Java泛型中都有哪些关键概念和用法?

1. 泛型概述 1.1 使用泛型的原因 没有泛型,在编写代码时只能使用具体类或Object类型,无法直接使用用户期望的类型。例如:创建一个方法,需要指定形参类型,才能使用用户期望的类型。

1.泛型概述 1.1.为什么使用泛型

没有泛型,在编写代码时只能使用具体类型或Object类型,无法做到使用者想要使用什么类型就是类型。比如:创建一个方法,形参需要指定需要使用的数据类型,在创建方法之初就已经决定了该方法可以处理的数据类型,这大大限制了编程的灵活性。正因如此,才出现了在使用时才决定具体类型是什么的泛型编程。

1.2.泛型是什么

泛:广泛、普遍,非具体的东西,泛型就是定义之初用符号表示不具体的类型,在使用的时候才动态地指定具体的类型。更应该明白这种泛型编程设计思想,使用泛型带来的好处是代码更加简洁、更加灵活、使程序更加健壮(编译期没警告,运行期不会出现类强转异常--ClassCastException)。

2.泛型接口、类、方法

泛型允许在定义接口、类、方法时使用,将在声明变量、创建对象、调用方法时动态地指定。

2.1.泛型接口

定义泛型接口:比如集合中的List接口

// 定义接口时指定泛型:E,E类型在接口中就可以作为类型使用 public interface List<E> extends Collection<E>{ …… boolean add(E e); Iterator<E> iterator(); …… } // 定义接口时指定泛型:K 和 V,K和V类型在接口中就可以作为类型使用 public interface Map<K,V>{ …… Set<K> keySet(); Collection<V> values(); Set<Map.Entry<K, V>> entrySet(); …… }

使用泛型接口:List接口的泛型类型E,在使用时指定为具体类型String

public static void main(String[] args) { List<String> list = new ArrayList<>();// 指定泛型类型E=String list.add("我只认识字符串");//boolean add(E e); 等价于boolean add(String e); Iterator<String> iterator = list.iterator();//Iterator<E> iterator(); while (iterator.hasNext()){ String next = iterator.next();//不需要强转为String System.out.println(next); } }

关于泛型接口Map<K,V> 集合怎么用,就自行编写感受下。

2.2.泛型类

普通泛型类

定义泛型类

Java泛型中都有哪些关键概念和用法?

public class DemoFx<D> { private D dd; public D getDd(){ return this.dd; } public void setDd(D dd){ this.dd = dd; } }

使用泛型类

public static void main(String[] args) { DemoFx<String> stringDemoFx = new DemoFx<>(); stringDemoFx.setDd("我是字符串类型"); System.out.println(stringDemoFx.getDd()); }

泛型类的继承与实现

定义泛型类:以ArrayList 类为例,继承泛型抽象类和实现泛型接口:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ …… public E get(int index) { rangeCheck(index); return elementData(index); } …… }

使用泛型类

public static void main(String[] args) { List<String> list = new ArrayList<String>();// 指定泛型类型E=String list.add("我只认识字符串"); String s = list.get(0);// 返回值为String } 2.3.泛型方法

定义泛型方法:还是ArrayList案例

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ …… public <T> T[] toArray(T[] a) { if (a.length < size) // Make a new array of as runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } …… }

使用泛型方法:public <T> T[] toArray(T[] a)

public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("s1"); list.add("s2"); list.add("sn"); // public <T> T[] toArray(T[] a) String[] strings = list.toArray(new String[list.size()]); System.out.println(Arrays.asList(strings)); } 3.类型通配符 3.1.使用类型通配符

通配符表示符号是问号<?>,它是未知类型,可以匹配任何类型,也称为无界通配符

对比”通配符“和”泛型“创建的方法

// 通配符定义 public void foreach(List<?> list){ for (int i =0 ;i<list.size();i++) { Object o = list.get(i); System.out.println(o.toString()); } } // 泛型定义 public <T> void foreach2(List<T> list){ for(T t : list){ System.out.println(t.toString()); } } // 使用 public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("s1"); list.add("s2"); list.add("sn"); Demo demo = new Demo(); demo.foreach(list); // 通配符 demo.foreach2(list); // 泛型 }

通配符和泛型都可以实现相同的效果,并且泛型方法还可以使用本身定义的泛型类型,而通配符的”?“不可以当作数据类型来使用,所以通配符方法案例中只能用Object来接收list的元素,这也是通配符的缺点:无法确定未知类型是什么类型。

所以通配符的出现到底有什么用呢?

通配符为泛型的一种特例,无需定义既可在形参中使用的未知类型。

泛型和通配符的区别

  • Java编译器把泛型推断成T类型,在代码块中允许出现 T类型变量;而把通配符推断成未知类型,不存在 ?类型变量;
  • Class<T>需要依赖于T,需要在方法声明时指定<T>,而Class<?>则不需要;

这样可能更好理解泛型和通配符:泛型 强调的是类型,通配符 强调的是符号

Class<?> 表示任意类型,但又不等同于Class<Object>,前者在类型不匹配的情况下只能够插入null,但是后者可以插入Object或任何Object对象的子类。

例如:不能往List<?> list里添加任意类型的对象,除了null

3.2.类型上限

通配符上限:<? extends Demo> ,通配符的上限是Demo类型,既是<? extends Demo> 的范围是Demo或其子类类型。

泛型上限:<T extends Demo> ,和通配符理解一样。类型上限如图

案例

创建三个类DemoFather、Demo、DemoChildren,关系如上图

public class DemoTest { public static void main(String[] args) { List<DemoChildren> demoChildrens = new ArrayList<>(); demoChildrens.add(new DemoChildren()); demoChildrens.add(new DemoChildren()); DemoTest test = new DemoTest(); test.testDemo(demoChildrens); // 通配符 test.testDemo2(demoChildrens);// 泛型 } // 通配符上限:控制list 集合允许的类型范围为Demo或其子类 public void testDemo(List<? extends Demo> list){ // 若无上限,这里只能用Object类型代替Demo类型 for (Demo demo : list){ System.out.println(demo.toString()); } } // 泛型上限:控制list 集合允许的类型范围为Demo或其子类 public <T extends Demo> void testDemo2(List<T> list){ for (T t : list){ System.out.println(t.toString()); } // or for(Demo demo:list){ System.out.println(demo.toString()); } } }

泛型的上限是在定义时确定上限<T extends Demo>;通配符直接在形参上确定上限<? extends Demo>。其实都很好理解,类型上限就是在一般写法的基础上加入范围“上限”,既是 extends xxx。

源码的一些例子

// 接口泛型上限 public interface ObservableArray<T extends ObservableArray<T>> extends Observable {……} // 抽象类泛型上限 public abstract class ArrayListenerHelper<T extends ObservableArray<T>> extends ExpressionHelperBase {……} public abstract class CellBehaviorBase<T extends Cell> extends BehaviorBase<T> {……} // 方法泛型上限 public static <T extends Number> ReadOnlyLongProperty readOnlyLongProperty(final ReadOnlyProperty<T> property) {……} // 通配符上限 void putAll(Map<? extends K, ? extends V> m); 3.3.类型下限

通配符下限:<? super Demo> ,通配符的下限是Demo类型,既是<? super Demo> 的范围是Demo的父类类型。

泛型下限:。主要是因为类型下限会令人困惑并且不是特别有用。为什么类型参数没有下限的一些解释:www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ107

public static void main(String[] args) { Demo demo = new Demo(); List<Demo> demos = new ArrayList<>(); demos.add(demo); DemoTest test = new DemoTest(); test.testSuper(demos); DemoFather demoFather = new DemoFather(); List<DemoFather> demoFathers = new ArrayList<>(); demoFathers.add(demoFather); DemoTest test2 = new DemoTest(); test2.testSuper(demoFathers); } public void testSuper(List<? super Demo> list){ // 虽然有下限,但无法直接使用Demo类型接收参数 for (Object obj : list){ System.out.println(obj.toString()); } }

虽然有下限,但无法直接使用Demo类型接收参数。这就像“向上转型”和“向下转型”,向上转型是自动的,向下转型需要强转;类型上限可以使用最大类型(父类)接收比它小的类型(子类),类型下限不可以使用最小类型(子类)接受可能比它大的类型(父类)。

源码的一些例子

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { …… public void sort(Comparator<? super E> c) { final int expectedModCount = modCount; Arrays.sort((E[]) elementData, 0, size, c); if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; } } public class Arrays { public static <T> void sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c) { if (c == null) { sort(a, fromIndex, toIndex); } else { rangeCheck(a.length, fromIndex, toIndex); if (LegacyMergeSort.userRequested) legacyMergeSort(a, fromIndex, toIndex, c); else TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0); } } }

关于泛型字母E、K、V、T是什么?

  • E表示Element,
  • K表示Key,
  • V表示Value,
  • T表示Type,
  • N表示Number,
  • ? 表示 未知类型

除了,其他泛型符号你写成字符串都可以,但要注意可读性,一般都是使用单个大写字母表示,不然代码反而不简洁、不易阅读。

4.泛型实现原理--类型擦除

把一个具有泛型信息的对象 赋给 另一个没有泛型信息的变量引用,类型信息都将被擦除

案例一:不指定泛型上限,类型擦除后为Object

public static void main(String[] args) { // 定义集合泛型E = String List<String> stringArrayList = new ArrayList<>(); stringArrayList.add("test1"); // 获取到的类型为:String String s = stringArrayList.get(0); // 把带有泛型信息的stringArrayList 对象赋给不确定泛型的List List listObject = stringArrayList; // listObject 只知道get的类型为Object,而不是String Object obj = listObject.get(0); }

案例二:指定泛型上限,类型擦除后为上限的类型

public class DemoFather {} public class Demo extends DemoFather{} public class DemoChildren<T extends DemoFather> { private T t; public T getT(){ return this.t; } public void setT(T t){ this.t= t; } } // 测试public class DemoTest { public static void main(String[] args) { //class DemoChildren<T extends DemoFather>,指定泛型T=Demo类型 DemoChildren<Demo> demoChildren = new DemoChildren<Demo>(); // 拿到的方法类型确实是T=Demo类型 Demo demo = demoChildren.getT(); // 把带有泛型信息的 demoChildren 对象赋给不确定泛型的demoChildren2 DemoChildren demoChildren2 =demoChildren; // 再来获取方法的类型时,变为了上限的DemoFather类型 DemoFather demoFather = demoChildren2.getT(); } }

结论:

指定泛型上限时,类型擦除后为上限的类型;反之是Object类型,因为Java中所有类都默认继承了Object类。

所以案例二的泛型类在编译阶段是长这样的

public class DemoChildren { private DemoFather t; public DemoFather getT(){ return this.t; } public void setT(DemoFather t){ this.t= t; } } // 原来的泛型类,对比一下 public class DemoChildren<T extends DemoFather> { private T t; public T getT(){ return this.t; } public void setT(T t){ this.t= t; } }

Java往期文章
Java全栈学习路线、学习资源和面试题一条龙
我心里优秀架构师是怎样的?
免费下载经典编程书籍

本文共计3651个文字,预计阅读时间需要15分钟。

Java泛型中都有哪些关键概念和用法?

1. 泛型概述 1.1 使用泛型的原因 没有泛型,在编写代码时只能使用具体类或Object类型,无法直接使用用户期望的类型。例如:创建一个方法,需要指定形参类型,才能使用用户期望的类型。

1.泛型概述 1.1.为什么使用泛型

没有泛型,在编写代码时只能使用具体类型或Object类型,无法做到使用者想要使用什么类型就是类型。比如:创建一个方法,形参需要指定需要使用的数据类型,在创建方法之初就已经决定了该方法可以处理的数据类型,这大大限制了编程的灵活性。正因如此,才出现了在使用时才决定具体类型是什么的泛型编程。

1.2.泛型是什么

泛:广泛、普遍,非具体的东西,泛型就是定义之初用符号表示不具体的类型,在使用的时候才动态地指定具体的类型。更应该明白这种泛型编程设计思想,使用泛型带来的好处是代码更加简洁、更加灵活、使程序更加健壮(编译期没警告,运行期不会出现类强转异常--ClassCastException)。

2.泛型接口、类、方法

泛型允许在定义接口、类、方法时使用,将在声明变量、创建对象、调用方法时动态地指定。

2.1.泛型接口

定义泛型接口:比如集合中的List接口

// 定义接口时指定泛型:E,E类型在接口中就可以作为类型使用 public interface List<E> extends Collection<E>{ …… boolean add(E e); Iterator<E> iterator(); …… } // 定义接口时指定泛型:K 和 V,K和V类型在接口中就可以作为类型使用 public interface Map<K,V>{ …… Set<K> keySet(); Collection<V> values(); Set<Map.Entry<K, V>> entrySet(); …… }

使用泛型接口:List接口的泛型类型E,在使用时指定为具体类型String

public static void main(String[] args) { List<String> list = new ArrayList<>();// 指定泛型类型E=String list.add("我只认识字符串");//boolean add(E e); 等价于boolean add(String e); Iterator<String> iterator = list.iterator();//Iterator<E> iterator(); while (iterator.hasNext()){ String next = iterator.next();//不需要强转为String System.out.println(next); } }

关于泛型接口Map<K,V> 集合怎么用,就自行编写感受下。

2.2.泛型类

普通泛型类

定义泛型类

Java泛型中都有哪些关键概念和用法?

public class DemoFx<D> { private D dd; public D getDd(){ return this.dd; } public void setDd(D dd){ this.dd = dd; } }

使用泛型类

public static void main(String[] args) { DemoFx<String> stringDemoFx = new DemoFx<>(); stringDemoFx.setDd("我是字符串类型"); System.out.println(stringDemoFx.getDd()); }

泛型类的继承与实现

定义泛型类:以ArrayList 类为例,继承泛型抽象类和实现泛型接口:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ …… public E get(int index) { rangeCheck(index); return elementData(index); } …… }

使用泛型类

public static void main(String[] args) { List<String> list = new ArrayList<String>();// 指定泛型类型E=String list.add("我只认识字符串"); String s = list.get(0);// 返回值为String } 2.3.泛型方法

定义泛型方法:还是ArrayList案例

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ …… public <T> T[] toArray(T[] a) { if (a.length < size) // Make a new array of as runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } …… }

使用泛型方法:public <T> T[] toArray(T[] a)

public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("s1"); list.add("s2"); list.add("sn"); // public <T> T[] toArray(T[] a) String[] strings = list.toArray(new String[list.size()]); System.out.println(Arrays.asList(strings)); } 3.类型通配符 3.1.使用类型通配符

通配符表示符号是问号<?>,它是未知类型,可以匹配任何类型,也称为无界通配符

对比”通配符“和”泛型“创建的方法

// 通配符定义 public void foreach(List<?> list){ for (int i =0 ;i<list.size();i++) { Object o = list.get(i); System.out.println(o.toString()); } } // 泛型定义 public <T> void foreach2(List<T> list){ for(T t : list){ System.out.println(t.toString()); } } // 使用 public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("s1"); list.add("s2"); list.add("sn"); Demo demo = new Demo(); demo.foreach(list); // 通配符 demo.foreach2(list); // 泛型 }

通配符和泛型都可以实现相同的效果,并且泛型方法还可以使用本身定义的泛型类型,而通配符的”?“不可以当作数据类型来使用,所以通配符方法案例中只能用Object来接收list的元素,这也是通配符的缺点:无法确定未知类型是什么类型。

所以通配符的出现到底有什么用呢?

通配符为泛型的一种特例,无需定义既可在形参中使用的未知类型。

泛型和通配符的区别

  • Java编译器把泛型推断成T类型,在代码块中允许出现 T类型变量;而把通配符推断成未知类型,不存在 ?类型变量;
  • Class<T>需要依赖于T,需要在方法声明时指定<T>,而Class<?>则不需要;

这样可能更好理解泛型和通配符:泛型 强调的是类型,通配符 强调的是符号

Class<?> 表示任意类型,但又不等同于Class<Object>,前者在类型不匹配的情况下只能够插入null,但是后者可以插入Object或任何Object对象的子类。

例如:不能往List<?> list里添加任意类型的对象,除了null

3.2.类型上限

通配符上限:<? extends Demo> ,通配符的上限是Demo类型,既是<? extends Demo> 的范围是Demo或其子类类型。

泛型上限:<T extends Demo> ,和通配符理解一样。类型上限如图

案例

创建三个类DemoFather、Demo、DemoChildren,关系如上图

public class DemoTest { public static void main(String[] args) { List<DemoChildren> demoChildrens = new ArrayList<>(); demoChildrens.add(new DemoChildren()); demoChildrens.add(new DemoChildren()); DemoTest test = new DemoTest(); test.testDemo(demoChildrens); // 通配符 test.testDemo2(demoChildrens);// 泛型 } // 通配符上限:控制list 集合允许的类型范围为Demo或其子类 public void testDemo(List<? extends Demo> list){ // 若无上限,这里只能用Object类型代替Demo类型 for (Demo demo : list){ System.out.println(demo.toString()); } } // 泛型上限:控制list 集合允许的类型范围为Demo或其子类 public <T extends Demo> void testDemo2(List<T> list){ for (T t : list){ System.out.println(t.toString()); } // or for(Demo demo:list){ System.out.println(demo.toString()); } } }

泛型的上限是在定义时确定上限<T extends Demo>;通配符直接在形参上确定上限<? extends Demo>。其实都很好理解,类型上限就是在一般写法的基础上加入范围“上限”,既是 extends xxx。

源码的一些例子

// 接口泛型上限 public interface ObservableArray<T extends ObservableArray<T>> extends Observable {……} // 抽象类泛型上限 public abstract class ArrayListenerHelper<T extends ObservableArray<T>> extends ExpressionHelperBase {……} public abstract class CellBehaviorBase<T extends Cell> extends BehaviorBase<T> {……} // 方法泛型上限 public static <T extends Number> ReadOnlyLongProperty readOnlyLongProperty(final ReadOnlyProperty<T> property) {……} // 通配符上限 void putAll(Map<? extends K, ? extends V> m); 3.3.类型下限

通配符下限:<? super Demo> ,通配符的下限是Demo类型,既是<? super Demo> 的范围是Demo的父类类型。

泛型下限:。主要是因为类型下限会令人困惑并且不是特别有用。为什么类型参数没有下限的一些解释:www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ107

public static void main(String[] args) { Demo demo = new Demo(); List<Demo> demos = new ArrayList<>(); demos.add(demo); DemoTest test = new DemoTest(); test.testSuper(demos); DemoFather demoFather = new DemoFather(); List<DemoFather> demoFathers = new ArrayList<>(); demoFathers.add(demoFather); DemoTest test2 = new DemoTest(); test2.testSuper(demoFathers); } public void testSuper(List<? super Demo> list){ // 虽然有下限,但无法直接使用Demo类型接收参数 for (Object obj : list){ System.out.println(obj.toString()); } }

虽然有下限,但无法直接使用Demo类型接收参数。这就像“向上转型”和“向下转型”,向上转型是自动的,向下转型需要强转;类型上限可以使用最大类型(父类)接收比它小的类型(子类),类型下限不可以使用最小类型(子类)接受可能比它大的类型(父类)。

源码的一些例子

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { …… public void sort(Comparator<? super E> c) { final int expectedModCount = modCount; Arrays.sort((E[]) elementData, 0, size, c); if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; } } public class Arrays { public static <T> void sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c) { if (c == null) { sort(a, fromIndex, toIndex); } else { rangeCheck(a.length, fromIndex, toIndex); if (LegacyMergeSort.userRequested) legacyMergeSort(a, fromIndex, toIndex, c); else TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0); } } }

关于泛型字母E、K、V、T是什么?

  • E表示Element,
  • K表示Key,
  • V表示Value,
  • T表示Type,
  • N表示Number,
  • ? 表示 未知类型

除了,其他泛型符号你写成字符串都可以,但要注意可读性,一般都是使用单个大写字母表示,不然代码反而不简洁、不易阅读。

4.泛型实现原理--类型擦除

把一个具有泛型信息的对象 赋给 另一个没有泛型信息的变量引用,类型信息都将被擦除

案例一:不指定泛型上限,类型擦除后为Object

public static void main(String[] args) { // 定义集合泛型E = String List<String> stringArrayList = new ArrayList<>(); stringArrayList.add("test1"); // 获取到的类型为:String String s = stringArrayList.get(0); // 把带有泛型信息的stringArrayList 对象赋给不确定泛型的List List listObject = stringArrayList; // listObject 只知道get的类型为Object,而不是String Object obj = listObject.get(0); }

案例二:指定泛型上限,类型擦除后为上限的类型

public class DemoFather {} public class Demo extends DemoFather{} public class DemoChildren<T extends DemoFather> { private T t; public T getT(){ return this.t; } public void setT(T t){ this.t= t; } } // 测试public class DemoTest { public static void main(String[] args) { //class DemoChildren<T extends DemoFather>,指定泛型T=Demo类型 DemoChildren<Demo> demoChildren = new DemoChildren<Demo>(); // 拿到的方法类型确实是T=Demo类型 Demo demo = demoChildren.getT(); // 把带有泛型信息的 demoChildren 对象赋给不确定泛型的demoChildren2 DemoChildren demoChildren2 =demoChildren; // 再来获取方法的类型时,变为了上限的DemoFather类型 DemoFather demoFather = demoChildren2.getT(); } }

结论:

指定泛型上限时,类型擦除后为上限的类型;反之是Object类型,因为Java中所有类都默认继承了Object类。

所以案例二的泛型类在编译阶段是长这样的

public class DemoChildren { private DemoFather t; public DemoFather getT(){ return this.t; } public void setT(DemoFather t){ this.t= t; } } // 原来的泛型类,对比一下 public class DemoChildren<T extends DemoFather> { private T t; public T getT(){ return this.t; } public void setT(T t){ this.t= t; } }

Java往期文章
Java全栈学习路线、学习资源和面试题一条龙
我心里优秀架构师是怎样的?
免费下载经典编程书籍