泛型程序设计

泛型程序设计

从Java程序的设计语言1.0版本发布以来,变化最大的部分就是泛型。致使Java SE5.0中增加泛型机制的主要原因为了满足在1999年制定的最早的Java规范需求之一(JSR14).专家组花费了5年左右的时间来定义规范和测试实现。泛型正是我们需要的程序设计手段。使用泛型机制编写的代码要比那些杂乱的使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有用,例如,ArrayList就是一个无处不在的集合类。至少在表面上来看,泛型很像C++的模板。与Java一样,在C++中,模板也就是最先被添加到语言中支持强类型集合的。但是,多年以后人们发现模板还有其它的用武之地。

为什么要使用泛型程序设计

泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用。例如,我们并不希望为聚集在任何类型的对象。这是一个泛型程序设计的实例。这样做,因为一个ArrayList类可以聚集任何类型的对象。这是一个泛型程序设计的例子。

实际上,在Java增加泛型类之前已经有一个ArrayList类。下面来研究泛型程序设计的机制是如何演变的,另外还会讲解这对于用户和实现者来说意味着什么。

泛型问题的引出

在Java语言中,为了方便接收参数类型的统一,提供了一个核心类Object,利用此类型对象可以接收所有类型的数据。但是由于其所描述的数据范围过大,所以在实际使用中就会出现传入数据类型错误,从而引发ClassCastExeption异常。例如:现在要设计一个可以描述坐标点的类point,对于坐标点允许保存三种数据。

  • 整型数据
  • 浮点型数据
  • 字符串型数据
  • 整型数据
  • 浮点类型数据
  • 字符串型数据

例子:

class Point
{
  private Object x;
  private Object y;
  public void setX(Object x){
  this.x=x;
}
  public void setY(Object y){
  this.y=y;
}
public void getX(){
  return x;
}
  public void getY(){
  return y;
}
  
}


point类中的x与y属性都采用了Object作为存储类型,这样就可以接受任何任意的数据类型,于是此时就可能产生两种情况。

情况一:

 public class Demo1{
  public static void main(String args[]){
    point.setX(100);
    point.setY(200);
    int x=(Integer)point.getX();
    int y=(Integer)point.getY();
    System.out.println("x的坐标:"+x+"、y的坐标:"+y); 




}
}


程序运行的结果:x的坐标:100、y坐标:20

情况二:

 public class Demo2{
  public static void main(String args[]){
    Point point=new Point();
    point.setX(100);
    point.setY("东偏南20度");
    int x=(Integer)point.getX();
    int y=(Integer)point.getY();
    System.out.println("x的坐标:"+x+"、y的坐标:"+y); 




}
}


这个程序在设置Point类坐标数据时采用了不同的数据类型,所以在获取原始数据信息时就会出现程序运行的异常,即这类错误并不会在编译的时候告诉开发者,而是在执行过程中才会产生安全隐患,而造成此问题的核心原因就是Object类型能够接受的数据范围过大。

泛型基本定义:

泛型可以在编译时检测出程序的安全隐患,使用泛型技术可以使程序更加健壮。

如果要想解决项目中的可能出现的ClassCastExeption安全隐患,最为核心的方案就是避免强制性的进行对象向下转型操作。所以泛型设计的核心思想在于:类中的属性或方法的参数与返回值的类型采用动态标记:在对象实例化的时候动态配置要使用的数据类型。
###例子:在类定义上使用泛型

public class Point<T>{
   private T x;
   private T y;
   public void setX(T x){
     this.x=x;
                        }
   public void setY(T y){
     this.y=y;
                        }
   public void getX(){
      return x;
                        }
    public void getY(){
      return y;
                        }
public class Demo3{
   public static void main(Stribg args[]){
     Point<Integer> point=new Point<Integer>();
     point.setX(100);
     point.setY(200);
     int x=point.getX();
     int y=point.getY();
    




     }
  }


}

程序运行结果:x的坐标:100、y的坐标:200

类型参数的好处:

在Java中增加泛型类之前,泛型程序设计是用继承实现的。ArrayList类只维护一个Object引用的的数组

public class ArryList
{
  private Object[] elementData;
  ....
  public Object get(int i){....}
  public void add(Object o){......}
}


  • 这种方法有两个问题。当获取一个值时候必须进行强制类型转换。
ArrayList files=new ArrayList();
.....
String filename=(String) file.get(0);
  • 此外,这里没有错误类型检查。可以向数组列表中添加任何类的对象。
files.add(new File("...");
  • 对于这个调用,编译和运行都不会出错。然而,在其他地方,如果将get的结果强制类型转换为String类型,就会产生一个错误。
    泛型提供了一个更好的解决方案:类型参数(type parameters)。ArrayList类有一个类型参数用来指示元素的类型。
ArrayList<String> files=new ArrayList<String>();


这使得代码具有更好的可读性。人们一看就知道这个数组列表中包含的是String对象。

注释:

前面已经提到,在Java SE7及以后的版本中,构造函数中可以省略泛型类型:

ArrayList<String> fiels=new ArrayList<>();
  • 省略的类型可以从变量的类型推断得出

  • 编译器也可以更好的利用这个信息。当调用get的时候,不需要进行强制类型转换,编译器就知道返回值的类型为String,而不是Object:

String filename=files.get(0);
  • 编译器还知道ArrayList中add方法有一个类型为String的参数。这将比使用Object类型的参数安全一些。现在,编译器可以进行检查,避免插入错误类型的对象。例如:
files.add(new File("..."));

是无法通过编译的。出现编译错误比类在运行时出现类的强制类型转换异常要好很多。
类型参数的美丽在于:使得程序具有更好的可读性和安全性。

泛型接口:

泛型除了定义在类上也可以定义在接口上,这样的结构成为泛型接口。

例子:定义泛型接口

interface IMessage<T>{
  public String echo(T msg);



}

对于此事的IMessage泛型接口在进行子类定义时就有两种实现方式:在子类中继续声明泛型和子类中为父类设置为泛型。

例子:定义泛型接口子类

interface IMessage<T>{
  public String echo(T msg);
}
class MessageImpl<S> implemnets IMessage<S>{
  public String echo(S t){
     return "[echo]"+t;
  }
}
public class Demo4{
 public static void main(String args[]){
  IMessage<String> msg=new MessageImpl();
  System.out.println(msg.echo("Java编程中心");
 }
}

运行结果:[echo]Java编程中心

泛型方法

在一定的环境下,类与接口往往不需要进行泛型定义,然而对于该结构体中的方法又可能出现泛型要求。

例子:定义泛型方法

public class Demo5{
 public static void main(String args[]){
   Integer num[]=fun(1,2,3);
   for(int temp:num){
     System.out.println(msg.echo(temp+"、");
 }
public static<T> T[]fun(T ...args){

 return args;
  }

  }
  
}

##泛型总结:

  • 1.泛型具有参数化的能力。可以定义使用泛型类型的类或方法,编译器会用具体类型来替换泛型类型。
  • 2.泛型的主要优势是能够在编译时而不是运行时检查错误。
  • 3.泛型类或方法允许指定这个类或方法可以带有的对象类型。如果试图使用带有不兼容对象的类或方法,编译器会检测出这个错误。
  • 4.定义在类、接口或者静态方法中的泛型称为形式泛型类型,随后可以使用一个实际具体类型来替换它。替换泛型类型的过程称为实例化。
  • 5.不使用类型参数的泛型类称为原始类型,例如ArrayList。使用原始类型是为了向后兼容Java较早的版本。
  • 6.通配泛型类型有三种形式:?、?extends T和?super T,这里的T代表一个泛型类型。第一种形式?称为非受限通配,它和?extends Object是一样的。第二种形式?extends T称为受限通配,代表T或者T的一个子类型。第三种类型?super T称为下限通配,表示或者T的一个父类型。
  • 7.使用称为类型消除的方法来实现泛型。编译器使用泛型类型信息来编译代码,但是随后消除它。因此,泛型信息在运行是不可用的。这个放啊发能够使泛型代码向后兼容使用原始;类型的遗留代码。
  • 8.不能使用泛型类型参数来创建实例
  • 9.不能使用泛型类型参数创建数组
  • 10.不能再静态实例中使用类的泛型参数
  • 11.在异常类中不能使用泛型类型参数。

文章参考有:《Java从入门到项目实战》、《Java核心技术卷》

欢迎关注我微信公众号:Java编程中心

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值