4.泛型

 一、何为泛型,为什么要使用泛型。

第一个问题何为泛型?

首先泛型是一个类型(类似于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> {  //<T>就是声明一个泛型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         Student<Integer> s1 = new Student<Integer>(90); //使用时指定类型,这里指定为Integer就可以接受Integer类型的数据
 4         Student <String> s2 = new Student<String>("A"); //这里同样指定了String,
 5         System.out.println(s1.score);                   //注意这里不能直接指定八种基本类型(int,float..),只能指定它们的包装类(Integer, Float)。  
6
System.out.println(s2.score);
7
}
8
}
9

10 class Student<T>{
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<T>{
10     T name;
11     void fun(T t){    
12     }
13 }
14 
15 class Children1 extends Father<String>{//继承泛型时指定类型。
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         Children2<String>c1 = new Children2<String>();
 4         c2.name = "hcf";
 5         c2.output(c2.name);
 6     }
 7 }
 8 
 9 class Father<T>{
10     T name;
11     void fun(T t){    
12     }
13 }
14 
15 class Children2<T> extends Father<T>{//这里的T也是继承过去的。如果Father<T1>那么同样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>{
    T name;
    void output(T t) {    
        System.out.println(t);
    }
}

3.父类单个泛型,子类多个泛型。

 1 public class Test{
 2     public static void main(String[] args) {
 3         Children3<String,Integer>c3 = new Children3<String,Integer>();
 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<T>{
12     T name;
13     void fun(T t){    
14     }
15 }
16 
17 class Children3<T,T1> extends Father<T>{//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,T1> {
    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<T>{
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作为父类接受子类来实现多态。

倘若我们把这里换做是泛型,也用同样的思路来实现下:

我们看用泛型的<Object>类型接受泛型<String>时会出现问题,泛型的Object不完全等同于Object类。

要实现泛型的多态,我们就需要用<?>来接受,?代表可以接受任意指定泛型对象。

 1 public class Test{
 2     public static void main(String[] args) {
 3         Children1<String> c1 = new Children1 <String>("11");
 4         Children2<String> c2 = new Children2 <String>("22");
 5         output(c1);
 6         output(c2);
 7     }
 8     static void output(Father<?> f){   //此处用?就可以实现接受任意类型的泛型。
 9         f.print();
10     }
11 }
12 class Father<T>{
13     void print(){
14         System.out.println();
15     }
16 }
17 
18 class Children1<T> extends Father<T>{
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<T> extends Father<T>{
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<T>{
2      T name;
3      void fun(T t){    
4      }

其中的fun并不能称为泛型方法,它是依赖于Fatehr类的,如果Father不是泛型类,则不能写void fun(T t);

泛型方法定义:

class Father{
    public static <T> void fun(T t){ //加上<T>标识后就代表是泛型方法,
                                     //具体类型由使用时决定
    }                                //加上fun就是一个单独的泛型方法
}                                    //不需要依赖于泛型类,和泛型类的T是分开的。

class Father{
    public static <T> T fun(T t){  //返回值为T,fun函数的参数也是T,都是在使用时决定。
        return t;
    }
}

 例子:

 1 public class Test{
 2     public static void main(String[] args) {
 3         Father <String>f1 = new Father<String>();
 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<T>{
12     T name;
13     public void setNmae(T name){
14         this.name = name;
15     }
16     @SuppressWarnings("hiding")
17     public <T> void out1(T t){
18         System.out.println(t);
19     }
20     
21     @SuppressWarnings("hiding")
22     public <T>  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 <Info<String,Integer>> o = new Output<Info<String,Integer>>();
 4         Info <String,Integer> i = new Info<String,Integer>();
 5         i.setName("hcf");        //如上<Info<String,Interge>>就是一个泛型嵌套。
 6         i.setNum(1001);
 7         o.set(i);
 8         System.out.println(o.print().getName() + o.print().getNum());
 9         
10     }
11 }
12 class Output<T>{
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<T,T1>{
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

上述代码中<Info<String,Integer>>可以看成首先是一个info类型,然后info类型中的泛型设置为<String,Interge>.

那么Output中的T应该是Info类型的,Info中的T1,T应该是Interge和String类型。

使用时一层一层解开就可以了。

猜你喜欢

转载自www.cnblogs.com/huang-changfan/p/9538159.html