Java基础系列文章
Java基础(一):语言概述
Java基础(二):原码、反码、补码及进制之间的运算
Java基础(三):数据类型与进制
Java基础(四):逻辑运算符和位运算符
Java基础(五):流程控制语句
Java基础(六):数组
Java基础(七):面向对象编程
Java基础(八):封装、继承、多态性
Java基础(九):Object 类的使用
Java基础(十):关键字static、代码块、关键字final
Java基础(十一):抽象类、接口、内部类
Java基础(十二):枚举类
Java基础(十三):注解(Annotation)
Java基础(十四):包装类
Java基础(十五):异常处理
Java基础(十六):String的常用API
Java基础(十七):日期时间API
Java基础(十八):java比较器、系统相关类、数学相关类
Java基础(十九):集合框架
Java基础(二十):泛型
目录
- 一、泛型概述
- 1、集合中使用泛型
- 二、自定义泛型结构
- 1、泛型的基础说明
- 2、自定义泛型类或泛型接口
- 2.1、说明
- 2.2、注意
- 3、自定义泛型方法
- 3.1、说明
- 三、泛型在继承上的体现
- 四、通配符的使用
- 1、通配符的读与写
- 2、使用注意点
- 3、有限制的通配符
一、泛型概述
- 在JDK5.0之前只能把元素类型设计为Object
- JDK5.0时Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时指定集合元素的类型
- 所谓泛型,就是允许在定义类、接口时通过一个
标识
表示类中某个属性的类型
或者是某个方法的返回值或参数的类型
1、集合中使用泛型
- 集合中没有使用泛型时
- 集合中使用泛型时
举例:
//泛型在List中的使用
@Test
public void test1(){
//举例:将学生成绩保存在ArrayList中
//标准写法:
//ArrayList<Integer> list = new ArrayList<Integer>();
//jdk7的新特性:类型推断
ArrayList<Integer> list = new ArrayList<>();
list.add(56); //自动装箱
list.add(76);
list.add(88);
list.add(89);
//当添加非Integer类型数据时,编译不通过
//list.add("Tom");//编译报错
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
//不需要强转,直接可以获取添加时的元素的数据类型
Integer score = iterator.next();
System.out.println(score);
}
}
二、自定义泛型结构
1、泛型的基础说明
- <类型>这种语法形式就叫泛型
- <类型>的形式我们称为
类型参数
,这里的"类型"习惯上使用T表示,是Type的缩写。即:<T> - <T>:代表
未知
的数据类型,我们可以指定为<String>,<Integer>,<Circle>等 - 类比方法的参数的概念,我们把<T>,称为类型形参,将<Circle>称为类型实参,有助于我们理解泛型
- 这里的T,可以替换成K,V等任意字母
- 声明类或接口时,在类名或接口名后面声明泛型类型,我们把这样的类或接口称为
泛型类
或泛型接口
【修饰符】 class 类名<类型变量列表> 【extends 父类】 【implements 接口们】{
}
【修饰符】 interface 接口名<类型变量列表> 【implements 接口们】{
}
//例如:
public class ArrayList<E>
public interface Map<K,V>{
....
}
- 声明方法时,在【修饰符】与【返回值类型】
之间
声明类型变量,我们把声明了类型变量的方法,称为泛型方法
[修饰符] <类型变量列表> 返回值类型 方法名([形参列表])[throws 异常列表]{
//...
}
//例如:java.util.Arrays类中的
public static <T> List<T> asList(T... a){
....
}
2、自定义泛型类或泛型接口
- 当我们在类或接口中定义某个成员时,该成员的相关类型是不确定的
- 而这个类型需要在使用这个类或接口时才可以确定,那么我们可以使用泛型类、泛型接口
2.1、说明
- 我们在声明完自定义泛型类以后,可以在类的内部(比如:属性、方法、构造器中)使用类的泛型
- 我们在创建自定义泛型类的对象时,可以指明泛型参数类型
- 一旦指明,内部凡是使用类的泛型参数的位置,都具体化为指定的类的泛型类型
- 如果在创建自定义泛型类的对象时,没有指明泛型参数类型
- 那么泛型将被擦除,泛型对应的类型均按照Object处理,但不等价于Object
- 经验:泛型要使用一路都用。要不用,一路都不要用
- 泛型的指定中
必须
使用引用数据类型。不能
使用基本数据类型,此时只能使用包装类替换 - 除创建泛型类对象外,子类继承泛型类时、实现类实现泛型接口时,也可以确定泛型结构中的泛型参数
- 如果我们在给泛型类提供子类时,子类也不确定泛型的类型,则可以继续使用泛型参数
- 我们还可以在现有的父类的泛型参数的基础上,新增泛型参数
2.2、注意
- 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
- JDK7.0 开始,泛型的简化操作:ArrayList<Fruit> flist = new ArrayList<>()
- 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity]
- 参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组
- 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,但不可以在静态方法中使用类的泛型
- 静态方法需要在类加载初始化默认值,类泛型在创建对象时候才能确定类型,所以自洽
- 异常类不能是带泛型的
举例1:
class Person<T> {
// 使用T类型定义变量
private T info;
// 使用T类型定义一般方法
public T getInfo() {
return info;
}
public void setInfo(T info) {
this.info = info;
}
// 使用T类型定义构造器
public Person() {
}
public Person(T info) {
this.info = info;
}
// static的方法中不能声明泛型
//public static void show(T t) {
//
//}
// 不能在try-catch中使用泛型定义
//public void test() {
//try {
//
//} catch (MyException<T> ex) {
//
//}
//}
}
举例2:
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{
}
// 2)具体类型
class Son2 extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {
}
3、自定义泛型方法
- 如果我们定义类、接口时没有使用<泛型参数>
- 但是某个方法形参类型不确定时,这个方法可以单独定义<泛型参数>
3.1、说明
- 泛型方法的格式:
[访问权限] <泛型> 返回值类型 方法名([泛型标识 参数名称]) [抛出的异常]{
}
- 方法,也可以被泛型化,与其所在的类是否是泛型类没有关系
- 泛型方法中的泛型参数在方法被调用时确定
- 泛型方法可以根据需要,声明为
static
的
举例1:
public class DAO {
public <E> E get(int id, E e) {
E result = null;
return result;
}
}
举例2:
public static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o);
}
}
public static void main(String[] args) {
Object[] ao = new Object[100];
Collection<Object> co = new ArrayList<Object>();
fromArrayToCollection(ao, co);
String[] sa = new String[20];
Collection<String> cs = new ArrayList<>();
fromArrayToCollection(sa, cs);
}
三、泛型在继承上的体现
- 如果B是A的一个子类型(子类或者子接口)
- 而G是具有泛型声明的类或接口
- G<B>并不是G<A>的子类型!
- 比如:String是Object的子类,但是List<String>并不是List<Object>的子类
public void testGenericAndSubClass() {
Person[] persons = null;
Man[] mans = null;
//Person[] 是 Man[] 的父类
persons = mans;
Person p = mans[0];
// 在泛型的集合上
List<Person> personList = null;
List<Man> manList = null;
//personList = manList;(报错)
}
四、通配符的使用
- ? 为泛型非限定通配符,表示类型未知,不用声明,可以匹配任意的类
- 比如:
List<?>
,Map<?,?>
List<?>
是List<String>
、List<Object>
等各种泛型List的父类
1、通配符的读与写
写操作
- 将任意元素加入到其中不是类型安全的
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 编译时错误
- 因为我们不知道c的元素类型,我们不能向其中添加对象
- 唯一可以插入的元素是null,因为它是所有引用类型的默认值
读操作
- 读取List<?>的对象list中的元素时,永远是安全的
- 因为不管 list 的真实类型是什么,它包含的都是Object
举例1:
public class TestWildcard {
public static void m4(Collection<?> coll){
for (Object o : coll) {
System.out.println(o);
}
}
}
举例2:
public static void main(String[] args) {
List<?> list = null;
list = new ArrayList<String>();
list = new ArrayList<Double>();
// list.add(3);//编译不通过
list.add(null);
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
l1.add("尚硅谷");
l2.add(15);
read(l1);
read(l2);
}
public static void read(List<?> list) {
for (Object o : list) {
System.out.println(o);
}
}
2、使用注意点
- 注意点1:编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?
public static <?> void test(ArrayList<?> list){
}
- 注意点2:编译错误:不能用在泛型类的声明上
class GenericTypeClass<?>{
}
- 注意点3:编译错误:不能用在创建对象上,右边属于创建集合对象
ArrayList<?> list2 = new ArrayList<?>();
3、有限制的通配符
<?>
:允许所有泛型的引用调用- 通配符指定上限:
<? extends 类/接口 >
:使用时指定的类型必须是继承某个类,或者实现某个接口,即(子类)<= - 通配符指定下限:
<? super 类/接口 >
:使用时指定的类型必须是操作的类或接口,或者是操作的类的父类或接口的父接口,即(父类)>= - 说明:
<? extends Number> //(无穷小 , Number]
//只允许泛型为Number及Number子类的引用调用
<? super Number> //[Number , 无穷大)
//只允许泛型为Number及Number父类的引用调用
<? extends Comparable>
//只允许泛型为实现Comparable接口的实现类的引用调用
举例1
class Creature{}
class Person extends Creature{}
class Man extends Person{}
class PersonTest {
public static <T extends Person> void test(T t){
System.out.println(t);
}
public static void main(String[] args) {
test(new Person());
test(new Man());
test(new Creature());// 编译报错
}
}
举例2:
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement1(list1);
getElement1(list2);//报错
getElement1(list3);
getElement1(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
举例3:
@Test
public void test1(){
//List<Object> list1 = null;
List<Person> list2 = new ArrayList<Person>();
//List<Student> list3 = null;
List<? extends Person> list4 = null;
list2.add(new Person());
list4 = list2;
//读取:可以读
Person p1 = list4.get(0);
//写入:除了null之外,不能写入
list4.add(null);
// list4.add(new Person());
// list4.add(new Student());
}
@Test
public void test2(){
//List<Object> list1 = null;
List<Person> list2 = new ArrayList<Person>();
//List<Student> list3 = null;
List<? super Person> list5 = null;
list2.add(new Person());
list5 = list2;
//读取:可以实现
Object obj = list5.get(0);
//写入:可以写入Person及Person子类的对象
list5.add(new Person());
list5.add(new Student());
}