Java中的泛型 --- Java 编程思想

简介: Java中的泛型 --- Java 编程思想 我一直都认为泛型是程序语言设计中一个非常基础,重要的概念,Java 中的泛型到底是怎么样的,为什么会有泛型,泛型怎么发展出来的。通透理解泛型是学好基础里面中非常重要的。

Java中的泛型 --- Java 编程思想

我一直都认为泛型是程序语言设计中一个非常基础,重要的概念,Java 中的泛型到底是怎么样的,为什么会有泛型,泛型怎么发展出来的。通透理解泛型是学好基础里面中非常重要的。于是,我对《Java编程思想》这本书中泛型章节进行了研读。可惜遗憾的是,自己没有太多的经验,有些东西看了几次也是有点懵。只能以后有机会,再进行学习了。但是自己也理解了挺多的。下面就是自己对于泛型的理解与感悟。如有不对,望指出。

2|0概念


由来: Java 一开始设计之初是没有泛型这个特性的,直到jdk 1.5中引入了这个特性。Java 的泛型是由擦除来实现的。要知道擦除是什么?往下看。

概念:一般的类和方法,只能使用具体的类型;要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。泛型实现了参数化类型的概念,使代码应用于多个类型。泛型在编程语言中出现时,其最初的目的是希望类和方法具有广泛的表达能力。

3|0简单泛型


​ 有很多原因促成泛型的出现,其中最重要的一个原因就是为了创造容器类。我们暂时不指定类型,而是稍后再决定具体使用什么类型。要达到这个目的,需要使用类型参数,用尖括号括住,放在类名的后面。然后使用这个类的时候,再用实际的类型替换此类型参数。在下面例子中,T就是类型参数。代码如下:

 
public class Holder<T> { private T a; public Holder(T a) { this.a = a; } public void set(T a) { this.a = a; } public T get(){ return a; } public static void main(String[] args) { Holder<String> h = new Holder<>(); h3.set("hello"); String a = h3.get(); } } class Automobile{}

​ 但是往往很多的源码中一些通用的类都是有多个泛型参数,譬如 java.util.function.BiFunction 中就有三个类型参数 T,U,R 。

4|0接口泛型


​ 泛型在接口上的运用是非常多的,例如迭代器(Iterable)中的 Iterator 。

 
public interface Itreator<T>{ // 判断是否还有元素 boolean hasNext(); // 洗一个元素 E next(); .... }

​ 这个是我们都很常用的吧,其实接口使用泛型和类使用泛型没什么区别。

5|0泛型方法


​ 泛型方法,在返回参数类型前面添加泛型参数列表,由<>括着
eg:

 
// 泛型方法 两个泛型参数,T,R 返回类型为 R public <T, R> R test(T t, R r){ return r; }

​ 泛型方法使得该方法独立于类而产生变化。在需要编写泛型代码的时候,基本的的指导原则是:无论何时,只要你能做到,就尽量使用泛型方法。意思是如果使用泛型方法可以代替整个类的泛型化,那就用泛型方法,因为它可以使事情更加清楚明白。另外对于static的方法而言,无法访问泛型类的类型参数,所以如果static方法需要使用泛化能力,就必须使其成为泛型方法。

6|0泛型的擦除


​ 在看 《Java编程思想》 中泛型章节中 ’擦除的神秘之处‘ 这一小节的时候,看的我特别的晕晕乎乎的,然后再往下面看时就越来越混了。 特别是看到’边界‘,’通配符‘ 这块了就有点懵了。首先看下什么是擦除。在泛型代码内部,无法获得有关泛型参数类型的信息。 Java 的泛型是由擦拭来实现的,这意味着当你使用泛型的时,任何具体的类型都被擦除了,你唯一知道的就是你在使用一个对象。由于 Java 一开始没有引入 泛型这个特性,在为了兼容 JDK 老版本的情况下。擦除是 Java 泛型实现的一种折中。 因此你在运行时 List<String> 和 List<Integer> 是一样的,注意是在运行时,但是在编译时,List<String> 表示这个 String 类型的 List 容器, List<Integer> 表示这个时 Integer 类型的List容器。 举个例子,例子来源于 Java编程思想

 
#include <iostream> using namespace std; temple<class T> class Manipulator{ T obj; public : Manipulator(T x){obj = x;} void manipylate() {obj.f();} }; class HasF{ public: void f(){cout<<"HasF::f()"<< endl;} }; int main(){ HasF hf; Manipulator<HasF> manipulator(hf); manipulator.manipylate(); }

输出 HasF:f()

​ Manipulator 类存储了一个类型T的对象,在 manipylate 方法里,他在 obj上调用了方法 f() ; 它是怎么知道 f() 是类型参数T 中有的方法呢? 当你实例化这个模板的时,c++编译器将进行检查,如果他在 Manipulator<HasF> 被实例化的这刻,它看到HasF如果拥有这个方法 f(), 就编译通过,否则不通过。 这个代码就算不会 c++ 的人应该也看的懂吧。我们看下 Java 的版本:

 
public class HasF{ public void f(){ System.out.println("HasF::f()"); } } class Manipulator<T>{ private T obj; public Manipulator(T obj){ this.obj = obj; } public void manipulator(){ //错误: 这个是不可以调用 f() 这个方法的。 // obj.f(); } public static void main(String[] args){ HasF hf = new HasF(); Manipulator<HasF> manipulator = new Manipulator<>(hf); manipulator.manipulator(); } }

看到没有,Java 中有了擦除, Java 编译器不知道 obj 中有没有 f() 这个方法的事情。

7|0泛型的边界


7|1T extends Class


​ Java 中的泛型,在编译时,T代表一种类型,如果没有指定任何的边界,那么他就相当于 Object 。 我们可以通过 extends 关键字,给泛型指定边界。 上面代码我们为了能够调用f(), 我们可以协助泛型类,给定泛型类的边界,告诉编译器必须接受遵循这个边界的类型。这里用 extends 关键字。 将上面代码改成

 
public class HasF{ public void f(){ System.out.println("HasF::f()"); } } class Manipulator<T extends HasF>{ private T obj; public Manipulator(T obj){ this.obj = obj; } public void manipulator(){ obj.f(); } public static void main(String[] args){ HasF hf = new HasF(); Manipulator<HasF> manipulator = new Manipulator<>(hf); manipulator.manipulator(); } }

输出:HasF::f()

这样子在编译的时候就相当告诉编译器 Manipulator 类中 obj 的参数类型 为 HasF 或着它的子类。

7|2? extends T


? 是泛型表达式中的通配符。 ? extends T 表示: T 数据类型或着 T 的子类数据类型。 举个例子

 
class Vegetables{} // 白菜 class Cabbage extends Vegetables{} // 小白菜 class Pakchoi extends Cabbage{} //大蒜 class Garlic extends Vegetables{} public class Main{ public static void main(String[] args) { // 这个是会报错的。 // ArrayList<Vegetables> vegetables = new ArrayList<Cabbage>(); } }

​ 上面的中 main 方法里面是报错的因为,ArrayList<Vegetables>表示这个ArrayList容器中只能存放蔬菜,new ArrayList<Cabbage>() 表示 这个使一个只能放白菜的容器。这两个容器是不可以相等的,类型不一样。但是我们可以通过 ? extends T 来解决这个问题,代码如下:

 
public class Main{ public static void main(String[] args) { ArrayList<? extends Vegetables> vegetables = new ArrayList<Cabbage>(); // 报错 不能添加白菜进去 // vegetables.add(new Cabbage()); } }

​ 我们可以用 vegetables 表示 new ArrayList<Cabbage>(), 这是向上转型。但是,为什么 vegetables.add(new Cabbage()) ; 会报错,因为 ArrayList<? extends Vegetables>表示这个 ArrayList 容器中能够存放任何蔬菜。 但是 ArrayList 具体是什么容器,完全不知道,add 的时候,你添加什么东西进去到这个容器中都是不安全的。 这个时候,我们可以用 ? super T 来进行操作,具体往下看。

7|3? super T


? super T 表示: T 数据类型 或着 T的超类数据类型, super 表示超类通配符。 上面代码可以用以下表示

 
public class Main{ public static void main(String[] args) { ArrayList<? super Cabbage> cabbages = new ArrayList<Vegetables>(); cabbages.add(new Cabbage()); cabbages.add(new Pakchoi()); // cabbages.add(new Vegetables()); System.out.println(cabbages); } }

上面ArrayList<? super Cabbage> 表示ArrayList这个容器中怎么都可以存放 Cabbage 以及Cabbage子类的数据类型。 cabbages 指向的是 蔬菜的容器类。 add 进去的是白菜以及白菜的子类型数据。这个当然是支持的。

7|4? extends T VS ? super T


? extends T ,? super T 一般用于方法参数列表。

 
public class Main{ public static void main(String[] args) { List<Cabbage> cabbages = new ArrayList<>(); cabbages.add(new Cabbage()); cabbages.add(new Cabbage()); cabbages.add(new Cabbage()); extendsTest(cabbages); List<? super Cabbage> list = superTest(new ArrayList<Vegetables>()); System.out.println(list); } public static void extendsTest(List<? extends Vegetables> list){ for (Vegetables t : list){ System.out.println(t); } } public static List<? super Cabbage> superTest(List<? super Cabbage> list){ list.add(new Cabbage()); list.add(new Pakchoi()); return list; } }

? extends T 表示消费者 list 然后把里面的数据消费掉。
? super T 表示生产者 传入一个list 然后往里面添加数据,进行其他操作。

8|0总结


​ 对于 ? extends Class ,? extends T,? super T,不是很理解的,可以自己把例子写一下,然后想一想。Java 泛型的特性在很多开源的框架上是用的非常多的。这快需要深入的理解一下,我想随着敲代码的年限上,应该到了后面会有不一样得理解吧。现在通过书上能够知道,理解得就只有这么多了。


__EOF__

作  者家里那只橘猫
出  处https://www.cnblogs.com/Krloypower/p/10454507.html

相关文章
|
2月前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
57 2
|
20天前
|
Java 编译器 容器
Java——包装类和泛型
包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
25 9
Java——包装类和泛型
|
23天前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
20天前
|
存储 安全 搜索推荐
Java中的泛型
【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `&lt;&gt;` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。
|
2月前
|
安全 Java Go
Java&Go泛型对比
总的来说,Java和Go在泛型的实现和使用上各有特点,Java的泛型更注重于类型安全和兼容性,而Go的泛型在保持类型安全的同时,提供了更灵活的类型参数和类型集的概念,同时避免了运行时的性能开销。开发者在使用时可以根据自己的需求和语言特性来选择使用哪种语言的泛型特性。
38 7
|
2月前
|
存储 算法 Java
14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
42 2
14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
|
2月前
|
存储 安全 Java
如何理解java的泛型这个概念
理解java的泛型这个概念
|
2月前
|
存储 缓存 Java
|
2月前
|
安全 Java
【Java 第六篇章】泛型
Java泛型是自J2 SE 1.5起的新特性,允许类型参数化,提高代码复用性与安全性。通过定义泛型类、接口或方法,可在编译时检查类型安全,避免运行时类型转换异常。泛型使用尖括号`&lt;&gt;`定义,如`class MyClass&lt;T&gt;`。泛型方法的格式为`public &lt;T&gt; void methodName()`。通配符如`?`用于不确定的具体类型。示例代码展示了泛型类、接口及方法的基本用法。
11 0
|
2月前
|
Java
【Java基础面试四十五】、 介绍一下泛型擦除
这篇文章解释了Java泛型的概念,它解决了集合类型安全问题,允许在创建集合时指定元素类型,避免了类型转换的复杂性和潜在的异常。
下一篇
无影云桌面