泛型概述
泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)
· 简单理解即在需要类型定义时,使用一个变量代替类型定义,再需要时再传入
· 有写类似于C++的模版机制,但需要注意的是,泛型编程和模版元编程是两套不同的编程范式
· 两种编程范式形式上相似但底层原理不同,C++同时支持两种编程范式,而java仅支持泛型,此处提到仅为帮助了解过C++模版机制的朋友更好的认识泛型编程。
*关于编程范式后续笔者会有单独一篇讲解
泛型的基本底层原理
· 在编译之后程序会采取去泛型化的措施。
· 即Java中的泛型,只在编译阶段有效。
· 在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。
· 也就是说,泛型信息不会进入到运行时阶段
(仅了解即可)
泛型的基本使用
一.泛型在类中的使用
示范:
public class ClassName<T>{//用T代替不确定的类型,用<>扩住
private T data;//data的类型未知,根据使用时传入的类型决定
public T getData() {//同上
return data;
}
public void setData(T data) {
this.data = data;
}
}
· T即代替具体参数类型的变量,
· 理论上可以使用任何字母作为变量,但约定俗称上,某些字母拥有特殊含义
· 在需要使用泛型时,以尖括号(<>)扩住所使用的代替具体参数类型的变量,在不确定类型时使用该变量代替,而在使用该类时如同传入参数一样传入一个“类型”以代替原来的“类型变量”
具体使用示范:
package 基础语法.泛型编程;
public class Person <A>{
private String name;
private int age;
private A data;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public A getData() {
return data;
}
public void setData(A data) {
this.data = data;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", data=" + data +
'}';
}
}
定义一个Person类,其中使用泛型,< A > 使用以代替具体类型,即data成员的类型未知,相应的其set方法的参数和get方法的返回值也是< A >(未确定类型的)
package 基础语法.泛型编程;
public class Demo {
Person<String> p = new Person<>();
}
· 创建Person类的对象时,传入String类型,即说明它的数据成员data此时为String类型
· jdk1.7开始,右边无需再次传入参数,如果是更早的版本需要写成
Person< String> p = new Person< String>();
二: 泛型在接口中的使用
public interface IntercaceName<T>{
T getData();
}
与类种使用泛型相似,但在接口中使用泛型时,在实现接口时可选择是否制定类型
指定类型
public class Interface1 implements IntercaceName<String> {
private String text;
@Override
public String getData() {
return text;
}
}
不指定类型
public class Interface1<T> implements IntercaceName<T> {
private T data;
@Override
public T getData() {
return data;
}
}
区别仅在于使用类时,前者与一般类相同,后者需像泛型类一样,传入对应类型参数
三:泛型在方法中的使用
需要注意,类和接口的泛型声明在类名/接口名后面,而方法中泛型的声明在权限修饰符后面,即返回类型声明的前面
示范:
private static <T> T 方法名(T a, T b) {}
该示范稍有特殊,它返回值恰好使用了未定类型T,但说明了泛型方法除却泛型声明外与一般方法无异,而泛型声明的类型变量使用时也与一般类型无异
具体使用示范:
public class Demo {
public static void main(String[] args){
print(123);
}
public static <A> void print(A a){
System.out.println(a);
}
}
· 方法print进行泛型声明,在使用时传入参数,当调用该泛型方法时,编译器会根据传入的参数类型自动推断出泛型参数的具体类型。
· 此处不是类型转换,而是类型推断。
此外,若需要多个类型变量,在泛型声明的尖括号中,用逗号隔开即可
示范:
public class ClassName<T,A, X>{//用T代替不确定的类型,用<>扩住
private T data;//data的类型未知,根据使用时传入的类型决定
private A name;
private x leader;
public T getData() {//同上
return data;
}
public void setData(T data) {
this.data = data;
}
}
泛型限定和通配符
泛型默认允许传入所有类型,但是有时我们会有特殊需求,要求仅能使用某些特定类型,此时我们可以指定泛型的限定区域 ,称为泛型限定
泛型限定
<T extends 类或接口1 & 接口2>
示范:
package 基础语法.泛型编程;
//泛型限定的使用示范
public class Demo01 {
Plate<Apple> p = new Plate<>();
Plate<Banana> b = new Plate<Banana>();
// Plate<String> s = new Plate<>();
}
interface Fruit{}
class Apple implements Fruit{
}
class Banana implements Fruit{
}
class Plate<T extends Fruit>{
T data;
}
· 此时,Plate类传入的参数类型只能是Fruit的子类,如果传入String则会报错。
· 无论是类还是接口,都用extends限定子类/实现类
通配符
类型通配符是使用?代替方法具体的类型实参。
1 <? extends Parent> 指定了泛型类型的上届
2 <? super Child> 指定了泛型类型的下届
3 <?> 指定了没有限制的泛型类型
示范:
public class Demo02 {
public static void main(String[] args){
Plate<? extends Fruit> p = new Plate<Apple>();
}
}
interface Fruit1{}
class Apple1 implements Fruit{
}
class Banana1 implements Fruit{
}
class Plate1<T>{
T data;
}
多态仅适用于引用类型和对象存在继承关系的场景,而此处引用类型和对象都是Plate所以不能写成:
(因为盘子本身不存在子父的概念,仅仅存在于盘子里的内容)
Plate<Fruit> p = new Plate<Apple>();
所以此时需要使用通配符:
Plate<? extends Fruit> p = new Plate<Apple>();
?代表什么都可以,而extends Fruit对其仅限上届限定,限定为Fruit的子类
另外两种限定
下届限定,限制为Apple的父类
Plate<? super Apple> p = new Plate<Fruit>();
无限制,可以用任何类型代替?
Plate<?> p = new Plate<String>();
指定?可以当做Objiect看待,但使用时原理有很大区别
· Objiect代表可以调用时传入任何类型的参数
· ?为可以传递任何类型代替,但传递类型后调用仅能传入相应的那一种类型的参数
泛型的作用
1、 提高代码复用率
2、 泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)
写在最后
java也要考试,对几个不太熟悉的知识点先进行整理,假期会系统的对每块知识进行整理
顺带吐槽一下,手写代码的方式考核真的很让人排斥,也很让人头疼!!!!
本博文为记录且仅为记录学习,不作商业用途。其中部分内容基于海贼宝藏https://www.haizeix.com/课程,笔者仅为加深印象和系统性以笔记形式呈现,若有侵权请联系作者删除