所谓泛型 其实就是一种类型参数(我们平常所见到的参数指的就是方法中的参数 他接收有外界传递来的值 然后在方法中进行使用) 并且还提高了代码的复用率
何以见得提高了代码的复用率 其实就是通过对比使用了泛型技术和没有使用泛型技术之间的区别:
以下是没有使用泛型技术的案例
public class Student1 {
// 第一个学生类他的分数接收的是一个浮点类型的数据
double score;
// 定义getter、setter方法
void setScore(double score){
this.score = score;
}
double getScore(){
return score;
}
}
public class Student2 {
// 这个学生类接收的是字符串类型的分数
String score;
// 定义getter、setter方法
void setScore(String score){
this.score = score;
}
String getScore(){
return score;
}
}
public class Student3 {
// 第三个学生类的分数就定义为整型
int score;
// 定义相关的getter、setter方法
void setScore(int score){
this.score = score;
}
int getScore(){
return score;
}
}
从以上代码中我们可以看出 定义了三种不同类型的分数 就要定义三份逻辑相似的代码 如果分数的类型规模一旦庞大起来 那么其实效率是十分低下的
现在引入了泛型技术以后 就不会出现以上这种局面 并且会提高同一份代码的复用率
public class Student<T>{
// 泛型是一种类型参数 有外界的实参所决定 并且可以在类中使用
T score;
// 定义一个getter、setter方法
void setScore(T score){
this.score = score;
}
T getScore(){
return score;
}
}
我们可以在主函数中测试一下这个代码的正确性
public class Main {
public static void main(String[] args) {
// 我们来试用一下泛型好不好用吧
Student<String> s1 = new Student<>();
Student<Integer> s2 = new Student<>();
Student<Double> s3 = new Student<>();
s1.setScore("优秀");
s2.setScore(100);
s3.setScore(99.5);
System.out.println(s1.getScore());
System.out.println(s2.getScore());
System.out.println(s3.getScore());
}
}
从结果可以看出 这个泛型类型是写的很成功的 并且从中我们还能够得知 从java7开始 右边<>内就可以不用写出具体的泛型了
而且我们的泛型名称是有命名建议的:
T Type
E Element
K Key
N Number
V Value
S、U、V 当你想要表示连续类型中的第二个、第三个、第四个泛型的时候的命名建议
类名后面不仅可以支持一个泛型 也可以支持多个泛型
public class Student<N, S>{
private N no;
private S score;
public Student(N no, S score){
this.no = no;
this.score = score;
}
}
public class Main {
public static void main(String[] args) {
Student<Integer, Double> s = new Student<>(1, 100.0);
}
}
何为泛型类型?其实就指的是哪些使用了泛型的类或者接口
比如一些诸如java.util.Comparator、java.util.Comparable之类的接口
我们从前面的多态都知道 如果类或者接口之间存在父子关系的话 那么让父类引用指向子类对象是可以编译成功的
以下的案例中 从结果可以明显发现strBox和intBox不存在父子关系 objBox和strBox之间不存在父子关系
有些人可能会以泛型的继承关系来作为泛型类型的继承关系 那好 我们可以超这个方向继续往下想 如果结论果真是如此的话 那么第一对关系肯定不符合 因为String和Integer压根不存在父子关系 只有第二对中的String和Object之间存在父子关系 好 接着往下想 如果是父子关系的话 多态是可以编译通过 也就是说 strBox和objBox指向的是同一个对象 然后我通过objBox设置了所指对象的element值 他的类型为Object类型 然后通过strBox获取所指对象的element值 并且通过一个String变量进行获取 但是我们可以清楚的发现由于之前element已经被设置为Object类型 所以导致Object赋值给了String 其实如果没有强制转换的话 这个语句是不能通过编译的 但是实际上这个语句是编译通过的 所以说我们刚才的strBox和objBox之间是不存在父子关系的
public class Main {
public static void main(String[] args) {
Box<String> strBox = new Box<>();
Box<Integer> intBox = new Box<>();
Box<Object> objBox = new Box<>();
strBox = intBox;// error
objBox = strBox;// error
// 如果上面代码是正确的话 那么思考一下下面的代码
objBox.setElement(new Object());
String str = strBox.getElement();
}
}
所以说下面这张图其实清楚的阐明了Number和Integer以及Box之间的关系
接着一个案例
我们都知道jdk中存在这这样如图所示的这样一种关系
由于上述各种接口或者类之间存在这父子关系 所以以下的代码其实都是可以编译通过的
public class Main {
public static void main(String[] args) {
Iterable<String> it = null;
Collection<String> col = null;
List<String> li = null;
ArrayList<String> al = null;
it = col;
col = li;
li = al;
}
}
但是如果是以下代码的话 就不能够编译通过了
假设我们的List和ArrayList都是自定义的(就是非jdk源码 是允许的) 并且他们类或者接口的结构是一样的 那么如果按照刚才那个反例来看的话 那么list和al是父子关系 那么list和al所指对象是一致的(假设list的泛型为Object al的泛型是String) 并且我通过list设置所指对象的no 这个no就是Object类型的 然后通过al获取所指对象的no 并且通过String变量保留 而Object是不能够在没有强制转换的前提下完成这个赋值的 但是实际上这个操作是允许的 所以说list和al其实不是父子关系
public class Main {
public static void main(String[] args) {
List<Object> list = null;
ArrayList<String> al = null;
list = al;// error
}
}
我可以总结一下就是说
当泛型不一致的话 那么泛型类型就一定不存在父子关系
当泛型一致的话 那么泛型类型也不一定存在父子关系 只有当泛型类型是有继承或者实现关系的话 那么泛型类型才有父子关系
前面说到泛型可以支持连续多个的 当我们两个类或者接口中的第一个泛型是一致的 并且子类可以存在第二个泛型的时候 那么这时候子类和父类之间是存在父子关系的
public interface MyList<E, T> extends List<E> {
void setNo(T no);
}
public class Main {
public static void main(String[] args) {
List<String> li = null;
MyList<String, Integer> ml1 = null;
MyList<String, Double> ml2 = null;
MyList<String, String> ml3 = null;
li = ml1;
li = ml2;
li = ml3;
}
}
从结果显示 编译是通过的 说明其实上述的这种操作是完全被允许的
何为原始类型 其实就是没有为泛型提供一个具体的类型
从上图中 我们想说的是:
当我们使用了原始类型的时候 那么编译器就会警告我们这是一个原始类型 发出rawtypes警告
当我们将非原始类型赋值给原始类型的时候 编译器是很正常的
但是当我们将原始类型赋值给非原始类型的时候 编译器会发出unchecked警告
我们对于以上这些由编译器发出的警告可以通过@SuppressWarnings这个注解进行消除
public class Box<T>{
private T no;
public T getNo(){
return no;
}
public void setNo(T no){
this.no = no;
}
}
public class Main {
public static void main(String[] args) {
Box box = new Box();
box.setNo(new Object());
Object o = box.getNo();
}
}
通过上面这段代码 我想说的是如果是原始类型的话 那么当我们进行getter和setter方法的时候 所涉及到的no都是Object类型的 那么我们可能会有一个疑惑 就是这不是和泛型为Object的泛型类型一样吗 那么这两个等价吗 其实他们两者是有着本质的区别的:
一个是原始类型 一个是非原始类型 只是原始类型的getter和setter方法中涉及到的no都是Object类型的