语法格式
定义
泛型,官方一点的解释就是——参数化类型
为什么这样说呢?其实不难理解,但是首先要明白一点,什么是参数化呢?
提起参数我们想到的无非就是形参实参,这些参数的特点就是:
我们使用一个类型来限制它,我们并不关心具体传递进来的数据,只关心它的类型是否符合限制条件,即数据是可变的,类型是具体的
比如 void say(Strinbg word); 我们在调用 say 方法的时候,并不关心传递来的具体数据,只关心它的类型是不是 String
再举个生活中的栗子:你老婆让你出门买点水果带回来,不管是橘子还是苹果都不是核心,重点只要是水果就行
那么参数化类型到底是什么呢?
顾名思义,参数化类型即将类型参数化,也就是说我们现在将类型视为可变元素,类型不再具体
类型也定义成参数形式(可以称之为类型形参),然后在使用时传入具体的类型(类型实参),
注意哦!这里传入的具体类型必须是引用数据类型(即类类型,包括自定义类),不能是基础数据类型(比如, byte short int …)
注意
泛型是在编译之后程序会采取的措施。
也就是说Java中的泛型,只在编译阶段有效。
在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段
使用
使用泛型定义的数据类型被定义为一个参数,将这种参数类型用在
类、接口和方法中,分别被称为泛型类、泛型接口和泛型方法
泛型类
语法格式:
class 类名 代表一种类型形参
class 类名<T1,T2…Tn> 代表n种类型形参
其中T,T1,T2…Tn都叫做通配符。Java中常用的通配符有T,E,K,V分别表示类型、元素、键、值
但是这并不是硬性规定,而是一种规范,这里的通配符也可以定义成其他值 ,比如 class 类名
示例:
public class ClassName{
private T value;
public ClassName() {
} public ClassName(T value) {
this.value = value;
} public T getValue() {
return value;
} public void setValue(T value) {
this.value = value;
}
}
泛型接口(包含测试)
语法格式和类基本相同:
public interface IntercaceName{
T getData();
}
关于子类实现泛型以及接口中的方法的不同场景,分为一下几种:
注意观察子类中实现的抽象方法 类型以及参数类型和接口中的区别
1、子类没有使用泛型,可在实现泛型接口的时候没有加上泛型(没有FATHER)。此时子类实现时默认使用 Object来代替父类接口中的所有泛指类型,不管是方法还是参数,都默认使用Object来代替。文笔有限,讲的有些拗口,请看实例,下同
如下:
//此处为了更好的区分子类父类 用SON 和FATHER 来标识泛型
public class InterfaceImpl implements IntercaceName{
@Override
public Object method01() {
return null;
}
@Override
public Object method02(int data) {
return null;
}
@Override
public Object method03(Object data) {
return null;
}
@Override
public Object method04(Fruit data) {
return null;
}
}
interface IntercaceName<FATHER>{
/**
* 接口中四种不同类型的抽象方法
* */
FATHER method01();
FATHER method02(int data);
FATHER method03(FATHER data);
FATHER method04(Fruit data);
}
/*
* 水果类以及其子类苹果类
*/
class Fruit{}
class Apple extends Fruit{}
2、子类使用了泛型(SON),但是接口实现处没有加上泛型(没有FATHER),此时子类依旧默认使用Object来代替接口中使用泛型的方法返回值类型,但是子类实现的时候,也可以使用子类定义的泛型来代替这些Object(仅限替换方法返回值的泛型类型,形式参数列表中的泛型类型变量不可以替换,否则会报错)
如下:(默认实现)
//此处为了更好的区分子类父类 用SON 和FATHER 来标识泛型
public class InterfaceImpl<SON> implements IntercaceName{
@Override
public Object method01() {
return null;
}
@Override
public Object method02(int data) {
return null;
}
@Override
public Object method03(Object data) {
return null;
}
@Override
public Object method04(Fruit data) {
return null;
}
}
interface IntercaceName<FATHER>{
/**
* 接口中四种不同类型的抽象方法
* */
FATHER method01();
FATHER method02(int data);
FATHER method03(FATHER data);
FATHER method04(Fruit data);
}
class Fruit{}
class Apple extends Fruit{}
使用子类的泛型SON来代替方法返回值类型处的默认实现 (Object)可以看出依然是正确的没有报错
如下:(替代实现)
//此处为了更好的区分子类父类 用SON 和FATHER 来标识泛型
public class InterfaceImpl<SON> implements IntercaceName{
@Override
public SON method01() {
return null;
}
@Override
public SON method02(int data) {
return null;
}
@Override
public SON method03(Object data) {
return null;
}
@Override
public SON method04(Fruit data) {
return null;
}
}
interface IntercaceName<FATHER>{
/**
* 接口中四种不同类型的抽象方法
* */
FATHER method01();
FATHER method02(int data);
FATHER method03(FATHER data);
FATHER method04(Fruit data);
}
class Fruit{}
class Apple extends Fruit{}
截屏证实
那么进一步替换,将原来的抽象方法中的泛指类形式参数也进行替换会是怎样的呢?
啊哦,报错了,提示我们此时子类并未完全实现接口中的方法,因为此时的method03已经算不上是接口的实现方法了
3、子类没有使用泛型,但是在继承泛型接口的时候在接口处声明了泛型类型,注意,此处必须的声明必须是准确的类型,否则会报错
错误演示:
比如将其声明为String类型
//此处为了更好的区分子类父类 用SON 和FATHER 来标识泛型
public class InterfaceImpl implements IntercaceName<String>{
@Override
public String method01() {
return null;
}
@Override
public String method02(int data) {
return null;
}
@Override
public String method03(String data) {
return null;
}
@Override
public String method04(Fruit data) {
return null;
}
}
也可以是自定义类型当该自定义类型有子类的时候,也可以类中使用
//此处为了更好的区分子类父类 用SON 和FATHER 来标识泛型
public class InterfaceImpl implements IntercaceName<Fruit>{
@Override
//此处用Fruit的子类
public Apple method01() {
return null;
}
@Override
public Fruit method02(int data) {
return null;
}
@Override
public Fruit method03(Fruit data) {
return null;
}
@Override
public Fruit method04(Fruit data) {
return null;
}
}
interface IntercaceName<FATHER>{
/**
* 接口中四种不同类型的抽象方法
* */
FATHER method01();
FATHER method02(int data);
FATHER method03(FATHER data);
FATHER method04(Fruit data);
}
class Fruit{}
class Apple extends Fruit{}
可以看出,使用泛型的时候,可以使得被限制的内容元素更加统一!
4、子类使用泛型且在继承泛型接口的时候在接口处明确了泛型类型
当子类和父类使用同一种泛型的时候:此时父类接口不用指明泛指类型
public class InterfaceImpl<FATHER> implements IntercaceName<FATHER>{
@Override
public FATHER method01() {
return null;
}
@Override
public FATHER method02(int data) {
return null;
}
@Override
public FATHER method03(FATHER data) {
return null;
}
@Override
public FATHER method04(Fruit data) {
return null;
}
}
interface IntercaceName<FATHER>{
/**
* 接口中四种不同类型的抽象方法
* */
FATHER method01();
FATHER method02(int data);
FATHER method03(FATHER data);
FATHER method04(Fruit data);
}
class Fruit{}
class Apple extends Fruit{}
泛型方法
语法格式:
权限修饰符 <泛型> 返回值 类型 方法名(形式参数列表(可以使用泛型))
//方法体
}注意:
当泛型方法出现在了泛型类中的时候,假设泛型类中使用的泛型通配符为A,那么里面的非静态方法都可以使用该泛型
而且按照泛型方法的语法可以创建类定义中不存在的新的通配符B,除此之外,如果泛型类中的泛型方法创建的通配符和泛型 类的通配符相同。比如都是A,那么这两个A可以不相同,即各自为政,代表不同的泛指类型
关于类中的静态方法,如果要使用泛型,那么必须自己定义,即使是在泛型类中,也无法 直接使用类上 定义的泛型
泛型中的上下边界限定
使用泛型的时候还可以加上边界的限制,以帮助我们更好的统一元素类型,注意,泛型边界的添加是与声明同时发生的
1、<? extends Parent> 指定了泛型类型的上界
2 、<? super Child> 指定了泛型类型的下界
3 、<?> 指定了没有限制的泛型类型,注意,此处的?类似于Object但不是,因为Object代表你可以传递任何数据类型(比如这个地方用String,下个地方用Integer),而这里的 ?代表你可以在实际使用的时候使用任何类型来代替的,即代替后的类型仍然是统一的(比如你使用String来替换?,那么就只能使用String)
综合实例
package com.blog;
import java.util.ArrayList;
import java.util.Iterator;
/**
* @author 尽欢
*/
//此处使用泛型数组来帮助更好的理解泛型
public class Test01{
public static void main(String[] args) {
/**
* 上界
* */
//看下面代码,我们希望将一个装着苹果的盘子看做是一个装着水果的盘子,但是却报错了
//这显然不合乎常理,明明苹果是水果(即是其子类),那么怎么修改呢?
//为泛型添加上界,则传入的类型就可以是该指定上界的本类或者子类
//Plate<Fruit> plate = new Plate<Apple>();
Plate<? extends Fruit> plate01 = new Plate<Apple>();
/**
* 同理,下界
* */
Plate<? super Apple> plate02 = new Plate<Fruit>();
//接口可以吗?试一下,我们知道Integer实现了Comparable接口,测试如下,显然可以
Plate<? super Integer> plate03 = new Plate<Comparable>();
/**
* 通配符
* */
//定义一个泛型数组,注意在java中是不能明确一个泛型类数组的,即
//ArrayList<Integer>[] fruits = new ArrayList<Integer>[10];
//但是使用通配符是可以的
ArrayList<?>[] arrayLists = new ArrayList<?>[10];
/**
* 创建三个数组列表
* */
//JDK8之后,引入了自动类型推断机制(钻石表达式),即使用泛型实例化的时候不用说明具体类型,编译器会去“猜"
ArrayList<String> al01 = new ArrayList<String>();
al01.add("123");
al01.add("456");
al01.add("789");
//使用钻石表达式
ArrayList<Fruit> al02 = new ArrayList<>();
al02.add(new Fruit());
al02.add(new Apple());
//注意,是不可以直接实例化对象的时候使用通配符的
// 这是错的 ArrayList<?> al03 = new ArrayList<?>();
ArrayList<?> al03 = new ArrayList<>();
//使用通配符后不可以添加元素了,除了null,即使将引用指向其他统一元素类型的数组对象
al03.add(null);
//al03.add(new Object()); 不可以添加
//读取数据 默认为 Object类型
Object o = al03.get(0);//null
printArrayList(al01);
System.out.println("================");
printArrayList(al02);
System.out.println("================");
printArrayList(al03);
}
/**
* 定义一个打印数组中元素的方法
* 这里使用了泛型的通配符
* */
public static void printArrayList(ArrayList<?> list) {
Iterator<?> iterator = list.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
}
}
class Fruit{}
class Apple extends Fruit{}
/**
* 碟子类
* T 代表碟子中放的物品
* */
class Plate<T>{}
好了,以上就是Java中泛型的日常使用说明了,以上均为本人个人观点,借此分享。如有不慎之处,劳请各位批评指正!鄙人将不胜感激并在第一时间进行修改!