一、何为泛型,为什么要使用泛型。
第一个问题何为泛型?
首先泛型是一个类型(类似于int double),这就解释了泛型中的型字。我们再看泛型中的泛字,泛指广泛。
两者结合一起,泛型就代表广泛的类型,即它可以代不同的类型具有多样性。
接着我们来看第二个问题,为什么要使用泛型。
举个例子,现在我要录入学生的成绩,我们大家都知道表示学生成绩有多种方式。
可以是的分数制(77,89,95),也可是是等级制(A、B、C、D)。
那么问题来了,就当我抽疯了,一会想用分数制,一会想用等级制。
那我们该怎么办???
我们想一想,好像可以建两个Studetn类,一个类里面的成绩用String类型,一个类里面的成绩用float。
好像可以,确实也可以,但是一次你建两个类,不嫌麻烦吗?万一需求多了成百上千个类,还不得累死。
我们继续想别的方法,可不可以把成绩设为Object类型用于接受所有类型,后续我们再通过强制转换转成对应输入的类型。
想一想这个好像可以好像还不错,说干就干,实践出真知,我们先来试一下。
1 public class Test{ 2 public static void main(String[] args) { 3 Student s1 = new Student(90); 4 Student s2 = new Student("A"); 5 System.out.println((int)s1.score);//此处强制转型过程 :Object——>Integer——>int 6 System.out.println((String)s2.score); 7 } 8 } 9 10 class Student{ 11 Object score; 12 public Student(Object score){ 13 this.score = score; 14 } 15 }
运行结果: 90 A
发现这样做不错哎,可以实现需求,Obeject好像也可以作为“泛型”。泛型好像没有存在的必要了。
我们接着来看泛型的例子,对比下两者的区别。
举例之前先要了解泛型的基本使用
class Studetn{ // 就是声明一个泛型T,可以用任意字母表示可以是A、B、C..只不过约定俗成用T表示类型,K,V表示键值相关,E表示元素 T score; //这时T就是我们定义好的一个泛型(泛型也是类型,只不过是特殊的类型),用T声明score,此时的T是类型。 public Studetn(T score){ //代表接受时的类型,具体是什么类型需要在使用时决定。
this.score = score;
} }
注意有一点,泛型要在使用时指定类型。
1 public class Test{ 2 public static void main(String[] args) { 3 Students1 = new Student (90); //使用时指定类型,这里指定为Integer就可以接受Integer类型的数据 4 Student s2 = new Student ("A"); //这里同样指定了String, 5 System.out.println(s1.score); //注意这里不能直接指定八种基本类型(int,float..),只能指定它们的包装类(Integer, Float)。
6 System.out.println(s2.score);
7 }
8 }
9
10 class Student{
11 T score;
12 public Student(T score){
13 this.score = score;
14 }
15 }
运行结果: 90 A
看了上面使用泛型的例子,好像和Object接受所有类型差不多,看不出使用泛型有什么优点。
下面举一个例子就看出泛型的优点。
例如我现在设置一个学生的成绩是等级制(A,B,C)使用Obejct接受,在打印的时候不小心将其强制转换成了int。
我们看程序编写完没有报任何错误警告。
但是我们运行下看下结果。
运行后出现了类转换异常,字符串是不能强制转换为int的。
大家可能会说,我没那么智障将字符串强制转换为int,是的一般人不会这样做,但我要说的不是强制转换的问题了,而是类型转换安全的问题。
这里字符串确实不能强制转换为int,但是我们用Objec接受,之后将Object强制转换为别的类,编译器认为是合法的,不会提前报出任何错误和警告。
如果是泛型遇到这种情况会出现什么效果呢?我们来看下。
泛型是一开始就指定好类型,然后会对传递的参数类型做检查,如果类型不匹配就会直接在一开始报错。
这样我们可以做到防范于未然。
Object接受:可以接受任意类,也可以将其强制转换成对应类,这在语法上是符合逻辑的,不合法的转换(例如String->int)编译器一开始不会报错,只有在运行时才会报错。
使用泛型:一开始就指定了类型,会对类型做检查,不正确直接会报错警告,在源头解决了问题。
编译器对泛型:检查下进来的类型和指定类型匹不匹配,匹配就过去不匹配就警告,警告解除才能过去。
编译器对Object:什么类型都可以过来我都能接受,最后你想转什么类型都可以,你问我这个类型转换合不合法?我不知道我不管,语法上是合法的我就让你通过。我只体提醒语法错误,类型合不合法你去运行就知道了,我不负责类型检查这事。
大家对比下这两个,明显泛型更安全,在一开始就进行了检查。
二.泛型的继承
1.父类指定具体属性,子类不是泛型。
1 public class Test{ 2 public static void main(String[] args) { 3 Children1 c1 = new Children1(); 4 c1.name = "hcf"; 5 c1.output(c1.name); 6 } 7 } 8 9 class Father{ 10 T name; 11 void fun(T t){ 12 } 13 } 14 15 class Children1 extends Father {//继承泛型时指定类型。 16 void output(String t) { 17 System.out.println(t); 18 } 19 }
运行结果;
hcf
我看Father是一个泛型类,在Children1继承泛型类Father时,Father指定了具体类型,这时Children1就相当于。
class Children1 { String name; void output(String t) { System.out.println(t); } }
虽然Children1继承了泛型类,但它本身不是泛型类,它只是继承了泛型类Father指定属性之后的类。
2.父类泛型,子类泛型。
1 public class Test{ 2 public static void main(String[] args) { 3 Children2c1 = new Children2 (); 4 c2.name = "hcf"; 5 c2.output(c2.name); 6 } 7 } 8 9 class Father { 10 T name; 11 void fun(T t){ 12 } 13 } 14 15 class Children2 extends Father {//这里的T也是继承过去的。如果Father 那么同样Children2中的T要变成T2。 16 void output(T t) { //重写方法时类型的参数要和父类一致,所以这里类型是泛型T。 17 System.out.println(t); 18 } 19 }
运行结果:
hcf
Children2可以看做继承了泛型类Father的属性及方法,以及泛型T。这时Children2类是一个泛型类,泛型T具体的属性由使用时确定。
这里的T是继承自Father的。Children2就相当于:
class Children2{ T name; void output(T t) { System.out.println(t); } }
3.父类单个泛型,子类多个泛型。
1 public class Test{ 2 public static void main(String[] args) { 3 Children3c3 = new Children3 (); 4 c3.name = "hcf"; 5 c3.number = 20; 6 c3.output(c3.name); 7 System.out.println(c3.number); 8 } 9 } 10 11 class Father { 12 T name; 13 void fun(T t){ 14 } 15 } 16 17 class Children3 extends Father {//children3除了继承Father外,自身还有一个T1. 18 T1 number; 19 void output(T t) { 20 System.out.println(t); 21 } 22 }
这里的Children3不仅继承了Father中的属性和方法还包含自身的泛型T1,Chidren3中T,T1的具体属性由使用时确定。此时的Children3就相当于:
class Children3{ T name; T1 number; void output(T t) { System.out.println(t); } }
4.子类父类同时擦除泛型
1 public class Test{ 2 public static void main(String[] args) { 3 Children3 c3 = new Children3(); 4 c3.name = "hcf"; 5 c3.output(c3.name); 6 } 7 } 8 9 class Father{ 10 T name; 11 void fun(T t){ 12 } 13 } 14 15 class Children3 extends Father{//这里泛型被擦除了,则属性默认为Object 16 17 void output(Object t) { 18 System.out.println(t); 19 } 20 21 }
运行结果:
hcf
此时父类和子类同时擦除,这时子类继承父类的类型默认为Object。
继承自Father的name由于擦除的原因,所以默认为Object类。
三、泛型的多态问题。
我们回顾下实现多态的三个条件。
1.继承
2.方法重写。
3.父类引用执向子类对象。
举个小例子:
1 public class Test{ 2 public static void main(String[] args) { 3 Children1 c1 = new Children1("11"); 4 Children2 c2 = new Children2("22"); 5 output(c1); 6 output(c2); 7 } 8 static void output(Object f){ 9 System.out.println(f.toString()); 10 } 11 } 12 13 class Children1 extends Object{ 14 String name; 15 public Children1(String name){ 16 this.name = name; 17 } 18 19 public String toString(){ 20 return name; 21 } 22 } 23 24 class Children2 extends Object{ 25 String name; 26 public Children2(String name){ 27 this.name = name; 28 } 29 30 public String toString(){ 31 return name; 32 } 33 }
运行结果;
11
22
这里面用Object作为父类接受子类来实现多态。
倘若我们把这里换做是泛型,也用同样的思路来实现下:
我们看用泛型的
要实现泛型的多态,我们就需要用>来接受,?代表可以接受任意指定泛型对象。
1 public class Test{ 2 public static void main(String[] args) { 3 Children1c1 = new Children1 ("11"); 4 Children2 c2 = new Children2 ("22"); 5 output(c1); 6 output(c2); 7 } 8 static void output(Father> f){ //此处用?就可以实现接受任意类型的泛型。 9 f.print(); 10 } 11 } 12 class Father { 13 void print(){ 14 System.out.println(); 15 } 16 } 17 18 class Children1 extends Father { 19 T name; 20 public Children1(T name){ 21 this.name = name; 22 } 23 void print(){ //重写父类中的方法。 24 System.out.println(name); 25 } 26 } 27 28 class Children2 extends Father { 29 T name; 30 public Children2(T name){ 31 this.name = name; 32 } 33 void print(){ 34 System.out.println(name); 35 } 36 }
运行结果:
11
22
用>接受任意类型就可以实现泛型的多态。
四、泛型方法
之前使用的:
1 class Father{ 2 T name; 3 void fun(T t){ 4 }
其中的fun并不能称为泛型方法,它是依赖于Fatehr类的,如果Father不是泛型类,则不能写void fun(T t);
泛型方法定义:
class Father{ public staticvoid fun(T t){ //加上 标识后就代表是泛型方法, //具体类型由使用时决定 } //加上fun就是一个单独的泛型方法 } //不需要依赖于泛型类,和泛型类的T是分开的。 class Father{ public staticT fun(T t){ //返回值为T,fun函数的参数也是T,都是在使用时决定。 return t; } }
例子:
1 public class Test{ 2 public static void main(String[] args) { 3 Fatherf1 = new Father (); 4 f1.setNmae("hcf"); 5 f1.out1(1); 6 System.out.println(f1.name); 7 System.out.println(f1.out2("String")); 8 } 9 } 10 11 class Father { 12 T name; 13 public void setNmae(T name){ 14 this.name = name; 15 } 16 @SuppressWarnings("hiding") 17 public void out1(T t){ 18 System.out.println(t); 19 } 20 21 @SuppressWarnings("hiding") 22 public T out2(T t){ 23 return t; 24 } 25 }
运行结果: 1 hcf String
上图中Father泛型类的对象在创建时指定为String类型,但是out1和out2方法参数的一个是String类型,一个是Integer类型。此处想说明的是输入Father,out1,out2
的泛型标识都是T,但是他们不是同一T,他们是不同的类型。泛型方法的类型由使用该方法时决定,泛型类的类型也是使用该类时决定,但它们的类型是分开的。
out1和out2的类型由使用时传递进去的类型所决定。
五、泛型的嵌套
1 public class Test{ 2 public static void main(String[] args) { 3 Output> o = new Output >(); 4 Info i = new Info (); 5 i.setName("hcf"); //如上 >就是一个泛型嵌套。 6 i.setNum(1001); 7 o.set(i); 8 System.out.println(o.print().getName() + o.print().getNum()); 9 10 } 11 } 12 class Output{ 13 T i; 14 public void set(T i) { 15 this.i = i; 16 } 17 T print(){ 18 return i; 19 } 20 } 21 22 class Info { 23 T1 num; 24 T name; 25 public T1 getNum() { 26 return num; 27 } 28 public void setNum(T1 num) { 29 this.num = num; 30 } 31 public T getName() { 32 return name; 33 } 34 public void setName(T name) { 35 this.name = name; 36 } 37 }
运行结果:
hcf1001
上述代码中
那么Output中的T应该是Info类型的,Info中的T1,T应该是Interge和String类型。
使用时一层一层解开就可以了。
六、受限泛型
泛型类型的设置不仅可以是具体的属性,还可以是一个范围。
这里的范围主要体现在设置泛型的上限或下限。
设置上限用extends,设置下限用super。
例如。Info extends Number> 就设置了泛型的上限是Number,这个设置了上限的泛型可接收Number和它的子类。
1 public class Test{ 2 public static void main(String[] args) { 3 InfoInte = new Info (); 4 Inte.number = 11; 5 Info Flo = new Info (); 6 Flo.number = 10.1f; 7 Info Dou = new Info (); 8 Dou.number = 10.01; 9 outputNum(Inte); 10 outputNum(Flo); 11 outputNum(Dou); 12 } 13 static void outputNum(Info extends Number> i){//此处设置了上限Number 14 System.out.println(i.number); //可以接受Number的子类及自身。 15 16 } 17 } 18 19 class Info { 20 T number; 21 }
运行结果:
11
10.1
10.01
可以看出我们将outputNum函数中的参数类型设置为 extends Number>,由于Double,Float,Integer都是它的子类,所以可接受所有数字。
可以理解为在继承关系上<=Number类。
Info super String> 就是设置了下限,即类型要>=String类型,所以只能接受String或Object类型(String是继承Object的,所以大于String的只有Objcet)。
1 public class Test{ 2 public static void main(String[] args) { 3 Infostr = new Info (); 4 str.info = "hello"; 5 Info
运行结果:
hello
object