还是那句话,学习某个知识一定要想想为什么要学它,这方面的知识用来解决什么问题的,怎么用,并且要总结的体系化,不能散的到处都是,方便以后查看博客。

今天参考廖雪峰老师官网学习并总结下泛型廖老师官网

1.什么是泛型

1.1引出泛型

泛型是一种“代码模板”,可以用一套代码套用各种类型。

在讲解什么是泛型之前,我们先观察Java标准库提供的ArrayList,它可以看作“可变长度”的数组,因为用起来比数组更方便。

实际上ArrayList内部就是一个Object[]数组,配合存储一个当前分配的长度,就可以充当“可变数组”:

public class ArrayList {
private Object[] array;
private int size;
public void add(Object e) {...}
public void remove(int index) {...}
public Object get(int index) {...}
}

如果用上述ArrayList存储String类型,会有这么几个缺点:

  • 取出时需要强制转型(如(String),这样写);

  • 不方便,易出错。

例如,代码必须这么写:

ArrayList list = new ArrayList();
list.add("Hello");
// 获取到Object,必须强制转型为String:
String first = (String) list.get(0);

很容易出现ClassCastException,因为容易“误转型”:

list.add(new Integer(123));
// ERROR: ClassCastException:
String second = (String) list.get(1);

要解决上述问题,我们可以为String单独编写一种ArrayList:

public class StringArrayList {
private String[] array;
private int size;
public void add(String e) {...}
public void remove(int index) {...}
public String get(int index) {...}
}

这样一来,存入的必须是String,取出的也一定是String,不需要强制转型,因为编译器会强制检查放入的类型:

StringArrayList list = new StringArrayList();
list.add("Hello");
String first = list.get(0);
// 编译错误: 不允许放入非String类型:
list.add(new Integer(123));

问题暂时解决。

然而,新的问题是,如果要存储Integer,还需要为Integer单独编写一种ArrayList:

public class IntegerArrayList {
private Integer[] array;
private int size;
public void add(Integer e) {...}
public void remove(int index) {...}
public Integer get(int index) {...}
}

实际上,还需要为其他所有class单独编写一种ArrayList:

  • LongArrayList
  • DoubleArrayList
  • PersonArrayList

    ...

    这是不可能的,JDK的class就有上千个,而且它还不知道其他人编写的class。

为了解决新的问题,我们必须把ArrayList变成一种模板:ArrayList,代码如下:

public class ArrayList<T> {
private T[] array;
private int size;
public void add(T e) {...}
public void remove(int index) {...}
public T get(int index) {...}
}

T可以是任何class。这样一来,我们就实现了:编写一次模版,可以创建任意类型的ArrayList:

// 创建可以存储String的ArrayList:
ArrayList<String> strList = new ArrayList<String>();
// 创建可以存储Float的ArrayList:
ArrayList<Float> floatList = new ArrayList<Float>();
// 创建可以存储Person的ArrayList:
ArrayList<Person> personList = new ArrayList<Person>();

因此,泛型就是定义一种模板,例如ArrayList,然后在代码中为用到的类创建对应的ArrayList<类型>:

ArrayList<String> strList = new ArrayList<String>();

由编译器针对类型作检查:

strList.add("hello"); // OK
String s = strList.get(0); // OK
strList.add(new Integer(123)); // compile error!
Integer n = strList.get(0); // compile error!

这样一来,既实现了编写一次,万能匹配,又通过编译器保证了类型安全:这就是泛型。

1.2泛型的向上转型

在Java标准库中的ArrayList实现了List接口,它可以向上转型为List:

public class ArrayList<T> implements List<T> {
...
} List<String> list = new ArrayList<String>();
//List引用指向了ArrayList这个子类对象,故称为向上转型

即类型ArrayList可以向上转型为List。

要特别注意:不能把ArrayList向上转型为ArrayList或List。

这是为什么呢?假设ArrayList可以向上转型为ArrayList,观察一下代码:

// 创建ArrayList<Integer>类型:
ArrayList<Integer> integerList = new ArrayList<Integer>();
// 添加一个Integer:
integerList.add(new Integer(123));
// “向上转型”为ArrayList<Number>:
ArrayList<Number> numberList = integerList;
// 添加一个Float,因为Float也是Number:
numberList.add(new Float(12.34));
// 从ArrayList<Integer>获取索引为1的元素(即添加的Float):
Integer n = integerList.get(1); // ClassCastException!

我们把一个ArrayList转型为ArrayList类型后,这个ArrayList就可以接受Float类型,因为Float是Number的子类。但是,ArrayList实际上和ArrayList是同一个对象,也就是ArrayList类型,它不可能接受Float类型, 所以在获取Integer的时候将产生ClassCastException。

实际上,编译器为了避免这种错误,根本就不允许把ArrayList转型为ArrayList。

ArrayList和ArrayList两者完全没有继承关系。即有没有继承关系看的不是泛型,而是类型本身

同时思考下,廖老师并没有讲的向下转型,我想只要大家懂了向上转型。向下转型自然就理解了,也是不可以的。

1.3小结

泛型就是编写模板代码来适应任意类型;

泛型的好处是使用时不必对类型进行强制转换,它通过编译器对类型进行检查,将运行期的异常提前到了编译期,并避免了强制转换的出现,同时提高了代码的复用性;

注意泛型的继承关系:可以把ArrayList向上转型为List(T不能变!),但不能把ArrayList向上转型为ArrayList(T不能变成父类)。

再来一张图嘿嘿,黑马的,总结的不错也,定义更好,形参和实参方面去理解下再:

2.使用泛型:

使用ArrayList时,如果不定义泛型类型时,泛型类型实际上就是Object,即你虽然支持写泛型,但是我就是不用现在:

// 编译器警告:
List list = new ArrayList();
list.add("Hello");
list.add("World");
String first = (String) list.get(0);
String second = (String) list.get(1);

此时,只能把当作Object使用,没有发挥泛型的优势,代码还需要强转,并且可以添加各种类型,可能导致异常,编译器也没有检查。

当我们定义泛型类型后,List的泛型接口变为强类型List:

// 无编译器警告:
List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("World");
// 无强制转型:
String first = list.get(0);
String second = list.get(1);

当我们定义泛型类型后,List的泛型接口变为强类型List:

List<Number> list = new ArrayList<Number>();
list.add(new Integer(123));
list.add(new Double(12.34));
Number first = list.get(0);
Number second = list.get(1);

编译器如果能自动推断出泛型类型,就可以省略后面的泛型类型,jdk1.7以后就支持了。例如,对于下面的代码:

List<Number> list = new ArrayList<Number>();

编译器看到泛型类型List就可以自动推断出后面的ArrayList的泛型类型必须是ArrayList,因此,可以把代码简写为:

// 可以省略后面的Number,编译器可以自动推断泛型类型:
List<Number> list = new ArrayList<>();

2.1使用泛型接口

除了ArrayList使用了泛型,还可以在接口中使用泛型。例如,Arrays.sort(Object[])可以对任意数组进行排序,但待排序的元素必须实现Comparable这个泛型接口:

public interface Comparable<T> {
/**
* 返回负数: 当前实例比参数o小
* 返回0: 当前实例与参数o相等
* 返回正数: 当前实例比参数o大
*/
int compareTo(T o);
}

可以直接对String数组进行排序:

// sort
import java.util.Arrays; public class Main {
public static void main(String[] args) {
String[] ss = new String[] { "Orange", "Apple", "Pear" };
Arrays.sort(ss);
System.out.println(Arrays.toString(ss));
}
}

运行结果:[Apple, Orange, Pear]

这是因为String本身已经实现了Comparable接口。如果换成我们自定义的Person类型试试:

// sort
import java.util.Arrays; public class Main {
public static void main(String[] args) { Person[] ps = new Person[] {
new Person("Bob", 61),
new Person("Alice", 88),
new Person("Lily", 75),
};
Arrays.sort(ps);
System.out.println(Arrays.toString(ps));
}
}
class Person {
String name;
int score;
Person(String name, int score) {
this.name = name;
this.score = score;
}
public String toString() {
return this.name + "," + this.score;
}
}

运行程序,我们会得到ClassCastException,即无法将Person转型为Comparable。我们修改代码,让Person实现Comparable接口:

异常信息:Exception in thread "main" java.lang.ClassCastException: class Person cannot be cast to class java.lang.Comparable (Person is in unnamed module of loader com.sun.tools.javac.launcher.Main$MemoryClassLoader @52e677af;

// sort
import java.util.Arrays; public class Main {
public static void main(String[] args) {
Person[] ps = new Person[] {
new Person("Bob", 61),
new Person("Alice", 88),
new Person("Lily", 75),
};
Arrays.sort(ps);
System.out.println(Arrays.toString(ps));
}
}
class Person implements Comparable<Person> {
String name;
int score;
Person(String name, int score) {
this.name = name;
this.score = score;
}
public int compareTo(Person other) {
return this.name.compareTo(other.name);
}
public String toString() {
return this.name + "," + this.score;
}
}

运行结果:[Alice,88, Bob,61, Lily,75]

运行上述代码,可以正确实现按name进行排序。

也可以修改比较逻辑,例如,按score从高到低排序。请自行修改测试。

2.2小结

使用泛型时,把泛型参数替换为需要的class类型,例如:ArrayList,ArrayList等;

可以省略编译器能自动推断出的类型,例如:List list = new ArrayList<>();;

不指定泛型参数类型时,编译器会给出警告,且只能将视为Object类型;

可以在接口中定义泛型类型,实现此接口的类必须实现正确的泛型类型。

补充:如果对Java 中 Comparable 和 Comparator 还不太了解,可以看下此博客,Comparable是类实现接口后该类支持排序,像jdk中的类,很多已经实现了此接口,有默认的排序规则,而实现Comparator则是临时定义的类排序规则。点击查看

3.泛型编写

3.1编写泛型类

编写泛型类比普通类要复杂。通常来说,泛型类一般用在集合类中,例如ArrayList,我们很少需要编写泛型类。

如果我们确实需要编写一个泛型类,那么,应该如何编写它?

可以按照以下步骤来编写一个泛型类。

首先,按照某种类型,例如:String,来编写类:

public class Pair {
private String first;
private String last;
public Pair(String first, String last) {
this.first = first;
this.last = last;
}
public String getFirst() {
return first;
}
public String getLast() {
return last;
}
}

然后,标记所有的特定类型,这里是String

public class Pair {
private String first;
private String last;
public Pair(String first, String last) {
this.first = first;
this.last = last;
}
public String getFirst() {
return first;
}
public String getLast() {
return last;
}
}

最后,把特定类型String替换为T,并申明:

public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}

熟练后即可直接从T开始编写。

3.2静态方法

编写泛型类时,要特别注意,泛型类型不能用于静态方法。例如:

public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() { ... }
public T getLast() { ... } // 对静态方法使用<T>:
public static Pair<T> create(T first, T last) {
return new Pair<T>(first, last);
}
}

上述代码会导致编译错误,我们无法在静态方法create()的方法参数和返回类型上使用泛型类型T。

有些同学在网上搜索发现,可以在static修饰符后面加一个,编译就能通过:

public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() { ... }
public T getLast() { ... } // 可以编译通过:
public static <T> Pair<T> create(T first, T last) {
return new Pair<T>(first, last);
}
}

但实际上,这个和Pair类型的已经没有任何关系了。

对于静态方法,我们可以单独改写为“泛型”方法,只需要使用另一个类型即可。对于上面的create()静态方法,我们应该把它改为另一种泛型类型,例如,:

public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() { ... }
public T getLast() { ... } // 静态泛型方法应该使用其他类型区分:
public static <K> Pair<K> create(K first, K last) {
return new Pair<K>(first, last);
}
}

这样才能清楚地将静态方法的泛型类型和实例类型的泛型类型区分开。

3.3多个泛型类型

泛型还可以定义多种类型。例如,我们希望Pair不总是存储两个类型一样的对象,就可以使用类型<T, K>:

public class Pair<T, K> {
private T first;
private K last;
public Pair(T first, K last) {
this.first = first;
this.last = last;
}
public T getFirst() { ... }
public K getLast() { ... }
}

使用的时候,需要指出两种类型:

Pair<String, Integer> p = new Pair<>("test", 123);

Java标准库的Map<K, V>就是使用两种泛型类型的例子。它对Key使用一种类型,对Value使用另一种类型。

3.4小结

编写泛型时,需要定义泛型类型;

静态方法不能引用泛型类型,必须定义其他类型(例如)来实现静态泛型方法,进行区分;

泛型可以同时定义多种类型,例如Map<K, V>。


Java泛型学习--第一篇的更多相关文章

  1. Java泛型学习---第二篇

    泛型学习第一篇 1.泛型之擦拭法 泛型是一种类似"模板代码"的技术,不同语言的泛型实现方式不一定相同. Java语言的泛型实现方式是擦拭法(Type Erasure). 所谓擦拭法 ...

  2. Java队列学习第一篇之列介绍

    Java并发之显式锁和隐式锁的区别 在面试的过程中有可能会问到:在Java并发编程中,锁有两种实现:使用隐式锁和使用显示锁分别是什么?两者的区别是什么?所谓的显式锁和隐式锁的区别也就是说说Synchr ...

  3. 从.Net到Java学习第一篇——开篇

    以前我常说,公司用什么技术我就学什么.可是对于java,我曾经一度以为“学java是不可能的,这辈子不可能学java的.”结果,一遇到公司转java,我就不得不跑路了,于是乎,回头一看N家公司交过社保 ...

  4. Java并发包下锁学习第一篇:介绍及学习安排

    Java并发包下锁学习第一篇:介绍及学习安排 在Java并发编程中,实现锁的方式有两种,分别是:可以使用同步锁(synchronized关键字的锁),还有lock接口下的锁.从今天起,凯哥将带领大家一 ...

  5. JVM学习第一篇思考:一个Java代码是怎么运行起来的-上篇

    JVM学习第一篇思考:一个Java代码是怎么运行起来的-上篇 作为一个使用Java语言开发的程序员,我们都知道,要想运行Java程序至少需要安装JRE(安装JDK也没问题).我们也知道我们Java程序 ...

  6. LINQ to XML LINQ学习第一篇

    LINQ to XML LINQ学习第一篇 1.LINQ to XML类 以下的代码演示了如何使用LINQ to XML来快速创建一个xml: public static void CreateDoc ...

  7. Java图像处理最快技术:ImageJ 学习第一篇

    ImageJ是世界上最快的纯Java的图像处理程序. 它能够过滤一个2048x2048的图像在0.1秒内(*). 这是每秒40万像素!ImageJ的扩展通过使用内置的文本编辑器和Java编译器的Ima ...

  8. Java泛型学习笔记 - (七)浅析泛型中通配符的使用

    一.基本概念:在学习Java泛型的过程中, 通配符是较难理解的一部分. 主要有以下三类:1. 无边界的通配符(Unbounded Wildcards), 就是<?>, 比如List< ...

  9. Android基础学习第一篇—Project目录结构

    写在前面的话: 1. 最近在自学Android,也是边看书边写一些Demo,由于知识点越来越多,脑子越来越记不清楚,所以打算写成读书笔记,供以后查看,也算是把自己学到所理解的东西写出来,献丑,如有不对 ...

随机推荐

  1. Linux运维入门到高级全套系列PDF

    Linux运维入门到高级全套系列PDF(转) [日期:2016-08-01] 来源:Linux社区  作者:Linux [字体:大 中 小]     Linux 学习技巧 初学者可以自己安装虚拟机,然 ...

  2. 8. 格式化器大一统 -- Spring的Formatter抽象

    目录 ✍前言 本文提纲 版本约定 ✍正文 Printer&Parser Formatter 时间日期格式化 Date类型 代码示例 JSR 310类型 整合DateTimeFormatter ...

  3. 【Qt】实现程序重启的两种方法

    Qt5/PyQt5 实现程序重启的两种方法 前言 最近在写一个开源项目,需要实现一个程序自动重启的功能.尝试了好几种方式,效果均不太理想. 一开始的实现思路是,记为思路一吧.大概就是写一些 shell ...

  4. Desired_Capabilities配置

    appium服务器初始化参数 最全: https://github.com/appium/appium/blob/master/docs/cn/writing-running-appium/caps. ...

  5. 【Linux】Linux介绍和安装 - 测试题

    第一部分测试题 Linux介绍和安装 测试题 做点练习题,巩固一下咯~ ~ _ 10 个选择题. 1.让我们选择开机时进哪个操作系统的软件叫什么? A. booter B. bootloader C. ...

  6. load data local带来的安全问题

    load data默认读的是服务器上的文件,但是加上local参数后,就可以将本地具有访问权限的文件加载到数据库中,这在带来方便的同时,也带来了以下安全问题, 可以任意加载本地文件到数据库, 在web ...

  7. 【ORA】ORA-16629解决办法

    数据库向保护模式报告不同的保护级别"警告消息. 首先查看主备库的保护模式和保护级别 select protection_mode,protection_level from v$databa ...

  8. 【Oracle】转:通过案例学调优之--Oracle Time Model(时间模型)

    转自:http://blog.51cto.com/tiany/1596012 通过案例学调优之--Oracle Time Model(时间模型) 数据库时间 优化不仅仅是缩短等待时间.优化旨在缩短最终 ...

  9. 如何实现微信小程序动画?添加到我的小程序动画实现详细讲解,轻松学会动画开发!附壁纸小程序源码下载链接

    为了让用户能尽可能多地使用小程序,也算是沉淀用户,现在很多小程序中,都有引导用户"添加到我的小程序"的操作提示,而且大多都是有动画效果.在高清壁纸推荐小程序首页,用户每次进入,都会 ...

  10. su3和SU01中参数说明

    对于SU3和SU01中的的"参数"tab栏中的参数可以自己添加和删除. 所有的参数都存在表TPARA中,并且有对应的参数的说明. 那么这些参数如何使用呢? 通常的使用是,通过类似  ...