linkin大话数据结构--泛型

泛型(Generic)





  • 什么是泛型?

java5开始出现的一种对Java语言类型的一种拓展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数类型时指定的类型占位符,就好比方法的形式参数是实际参数的占位符一样。泛型能保证大型应用程序的类型安全和良好的维护性。

上面的官方解释有点太官方,换成自己的话我觉得就是2点:

1,解决元素存储的安全性问题

2,解决获取数据元素时,不需要类型强转。具体代码如下:

package tz.web.main;

import java.util.List;
import com.google.common.collect.Lists; /**
*
* @version 1L
* @author LinkinPark
* @since 2014-12-7
* @motto 梦似烟花心似水,同学少年不言情
* @desc ^ 没有使用泛型
*/
public class Linkin
{
public static void main(String[] args)
{
//@SuppressWarnings("rawtypes") 这个压制警告,就是说原始的数据结构类型,也就是说没有加入泛型检查
//首先这里数据不安全,我本来是要想放字符串进集合的,结果不小心放了数字了,丫的编译不会有问题的
List list = Lists.newArrayList();
list.add("1");
list.add(2);
//在下面的代码
for (Object object : list)
{
//不加入泛型,下面的代码的类型默认都是object的,所以在使用的时候,一般都会强转
String str = (String) object;
//java.lang.Integer incompatible with java.lang.String
//在编译的时候,也是没有问题的,但是实际运行的时候,发生问题了,类型转换错误。除非你每次都来判断具体的类型:object instanceof String
System.out.println(str);
}
}
}
package tz.web.main;

import java.util.List;
import com.google.common.collect.Lists; /**
*
* @version 1L
* @author LinkinPark
* @since 2014-12-7
* @motto 梦似烟花心似水,同学少年不言情
* @desc ^ 使用了泛型
*/
public class Linkin
{
public static void main(String[] args)
{
List<String> list = Lists.newArrayList();
list.add("1");
//你要是这里放入list中的对象的类型不对的话,编译就不通过
//list.add(2);
//在迭代循环list的时候,也不需要自己来每次强转类型了
for (String string : list)
{
System.out.println(string);
}
}
}

总结:使用泛型的优势:

1,类型安全,使编译器对泛型定义的类型做判断限制.如保证TreeSet里的元素类型必须一致

2,消除强制类型的转换,如,使用Comparable比较时每次都需要类型强转。



  • 泛型类

在类声明时通过一个标识符表示类中某个字段的类型或者某个方法的返回值或参数的类型,这样在类声明或实例化的时候只要指定自己需要的类型就ok。

声明带泛型的类:

class 类名<泛型类型1,泛型类型2……>{

泛型类型  变量名;

泛型类型  方法名(){}

返回值类型 方法名(泛型类型 变量名){}

}

使用带泛型的类:

类名<具体类> 对象名 = new 类名<具体类>();

类型参数规范:推荐使用规范-常见的泛型,泛型只保存在源文件中,class文件中不存在;也就是说在编译阶段就会丢失,基本数据类型不能作为泛型类型;

K 键,比如映射的键  key的类型

V 值,比如Map的值 value类型

E 元素,比如Set<E>  Element表示元素,元素的类型

T 泛型,Type的意思

T只能是类,不能用基本数据类型填充。

关于泛型类,主要注意一下几点:

1.对象实例化时不指定泛型,默认为:Object。

2.泛型不同的引用不能相互赋值。

3.加入集合中的对象类型必须与指定的泛型类型一致。

4.静态方法中不能使用类的泛型。

5.如果泛型类是一个接口或抽象类,则不可创建泛型类的对象。

6.不能在catch中使用泛型

7.从泛型类派生子类,泛型类型需具体化

package tz.web.main;

/**
*
* @version 1L
* @author LinkinPark
* @since 2014-12-7
* @motto 梦似烟花心似水,同学少年不言情
* @desc ^ 自定义泛型类
*/
public class Linkin<T>
{
private T name;
private T andress; //以下是2个构造器,注意了:在创建带泛型声明的自定义类时,构造器还是原来的构造器,没有变化的话。变化的只是在使用的过程中,为泛型形参传入实际的类型参数。
public Linkin()
{ }
public Linkin(T name, T andress)
{
this.name = name;
this.andress = andress;
} public T getName()
{
return name;
} public void setName(T name)
{
this.name = name;
} public T getAndress()
{
return andress;
} public void setAndress(T andress)
{
this.andress = andress;
}<p><span style="color:blue;"><span style="white-space:pre"> </span>//</span><span style="color:blue;">static</span><span style="color:blue;">的方法中不能声明泛型</span></p><p><span style="color:#C00000;"><span style="white-space:pre"> </span>//public </span><span style="color:#C00000;">static void show(T t){</span></p><p><span style="color:#C00000;"><span style="white-space:pre"> </span>//}</span></p> public static void main(String[] args)
{
Linkin<String> linkin = new Linkin<String>();
System.out.println(linkin.getAndress());
}
}

若一个类中多个字段需要不同的泛型声明,则在声明类的时候指定多个泛型类型即可。

public interface Map<K,V> {
Set<Map.Entry<K, V>> entrySet();
}
  • 通配符

在进行引用传递的时候泛型类型必须匹配才可以传递,否则编译不通过;使用 ? ,表示未知类型的泛型对象:

List<?> 表示未知元素的List集合;这种带通配符的List仅表示各种泛型List的父类,并不能把元素添加入集合中。

package tz.web.main;

import java.util.ArrayList;
import java.util.List; /**
*
* @version 1L
* @author LinkinPark
* @since 2014-12-7
* @motto 梦似烟花心似水,同学少年不言情
* @desc ^编译器无法基于信息作类型推断
*/
public class Linkin
{
//表示可接受任意类型的List集合
public void show(List<?> list)
{ } public static void main(String[] args)
{
//这种带通配符的List仅仅表示他是各种泛型List的父类,并不能把元素添加到其中。
List<?> list = new ArrayList<Linkin>();
//编译错误 因为上面使用了通配符,这个list就不知道往他里面添加的是什么类型的对象,所以就不能丢进去
//list.add("111");
//这里编译通过,null是唯一的例外。
list.add(null);
}
}
  • 泛型的上限与下限

设置泛型对象的上限使用extends,表示参数类型只能是该类型或该类型的子类(或者是该接口的实现类),

声明对象:类名<? extends 类> 对象名

定义类:类名<泛型标签 extends 类>{}

注意了:与类同时继承父类,实现接口相似:为类型形参指定多个类型的时候,所有的接口上线必须位于类上限之后。





设置泛型对象的下限使用super,表示参数类型只能是该类型或该类型的父类:

声明对象:类名<? super 类> 对象名称

定义类:类名<泛型标签 extends类>{}

注意了:不能同时设置上限和下限

package tz.web.main;

public class Linkin<T extends Number>
{ public static void main(String[] args)
{
//传入的实际类型比如是Number的父类的子类,或者是接口的实现类。
Linkin<Integer> linkin1 = new Linkin<Integer>();
Linkin<Long> linkin2 = new Linkin<Long>();
//Bound mismatch: The type String is not a valid substitute for the bounded parameter <T extends Number> of the type Linkin<T>
//Linkin<String> linkin3 = new Linkin<String>();
}
}
  • 泛型接口

java5后,可以声明泛型接口,声明方式和声明泛型类是一样的。public interface IDAO<T>{}

泛型接口子类有两种方式:1,直接在子类后申明泛型;2,在子类实现的接口中给出具体的泛型类型

public class DaoImpl<T> implements IDAO<T>{



}





推荐使用这种方式,一般都是定义一个泛型接口,然后里面每个具体的实现类的类型都基本确定下来了。上面的那种情况里面的实现类其实也是泛型的,一般不会写这种情况的代码。

public class DaoImpl implements IDAO<String> {



}



  • 泛型方法

方法中可定义泛型参数,形参的参数类型就是实参的类型。声明泛型的标识符,只能在方法返回值类型前。

格式:

<泛型标签> 返回值类型 方法名([泛型标签 参数]...)

public static <T> T show(T param){

return param;

}

package tz.web.main;

public class Linkin
{ public static <T> T show(T param){
return param;
} public static void main(String[] args)
{
System.out.println(Linkin.show("LinkinPark..."));
}
}
  • 泛型方法和类型通配符的区别:

大多数情况下可以使用泛型方法来代替类型通配符。比如:

public void test(List<?> list);就可以写成 public <T> void test(List<T> list)

1,如果方法中的类型形参只使用了一次,类型形参的唯一效果就是可以在不同的调入点传入不同的实际类型,那么这个时候应该使用通配符,通配符就是被设计用来支持灵活子类的。如果方法中的类型形参表示一个或者多个参数之间的依赖关系,或者是说表示方法的返回值和方法参数之间的关系,就应该使用泛型方法。简单的说:使用一次,用通配符,使用了好多次呢,用泛型方法。

2,类型通配符即可以在方法签名中定义形参的类型,也可以用于定义变量的类型。但是泛型方法中类型形参必须在对应方法中显式申明。





  • 泛型的嵌套

可以从一个类的泛型中指向另一个类的泛型。

package tz.web.main;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set; public class Linkin
{ public static void main(String[] args)
{
Map<String,String> map = new HashMap<String,String>();
map.put("1", "A");
map.put("2", "B");
map.put("3", "C");
map.put("4", "D"); //循环map的2种方式
System.out.println("===============第一种===============");
Set<String> keySet = map.keySet();
for(Iterator<String> it = keySet.iterator();it.hasNext();)
{
String key = it.next();
System.out.println(key + "-->" + map.get(key));
}
System.out.println("===============第二种===============");
Set<Map.Entry<String, String>> set = map.entrySet();
Iterator<Map.Entry<String, String>> it = set.iterator();
while(it.hasNext())
{
Map.Entry<String, String> entry = it.next();
System.out.println(entry.getKey() + "-->" + entry.getValue());
}
}
}



  • 泛型的擦除

在严格的泛型代码里,带泛型声明的类总应该带着类型参数。但是为了和老的Java代码保持一致,也允许在使用带泛型声明的类时不指定类型参数,若没有为这个泛型类指定类型参数则该类型参数被称做一个原始类型,默认是该声明参数时指定的最上限类型;当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,则所有在尖括号之间的类型信息都被扔掉。

比如List<String> 类型转换成List,则该List对集合元素的类型检查变成了变量的上限即Object。

package tz.web.main;

import java.util.ArrayList;
import java.util.List; public class Linkin<T extends Number>
{
private T t;
public Linkin(T t)
{
this.t = t;
}
public T getT()
{
return t;
}
public void setT(T t)
{
this.t = t;
}
public static void main(String[] args)
{
Linkin<Integer> n = new Linkin<Integer>(5);
Integer i1 = n.getT();
Linkin n2 = n;//会丢掉泛型信息
Number num = n2.getT();
//下面这行代码比如要强转了
Integer i2 = (Integer) n2.getT(); List<Integer> intList = new ArrayList<Integer>();
intList.add(1);
//List<String> strList = null;intList = strList;//不能转换
List list = intList;
List<String> strList = list;//不会报错,只有未经检查警告,此时list实际引用的是List<Integer>
//java.lang.Integer incompatible with java.lang.String
System.out.println(strList.get(0));//企图当做String类型对象取出
//下面这行代码和上面演示的效果一模一样
//System.out.println((String) intList.get(0));
}
}
  • 泛型和数组

只能申明List<String>[]形式的数组,但是不能创建ArrayList<String>[10]这样子的数组。因为他违反了java泛型的设计原则:如果一段代码在编译时系统没有产生[unchecked]未检查警告,程序在运行中就不会发生“ClassCastException”。了解下就好了,一般用不到的。

  • 泛型开发实例

泛型接口 GenericDAO 包含一个常见的数据操作:增删改查

泛型接口实现类 GenericDAOImpl 实现泛型接口里的所有抽象方法





public interface IGenericDAO<T> {

T get(Serializable id);

T save(T newInstance);

void remove(Serializable id);

void update(T object);

}





public class GenericDAOImpl<T> implements IGenericDAO<T>{

public T get(Serializable id) {

return null;

}

public T save(T newInstance) {

return null;

}

public void remove(Serializable id) {

}

public void update(T object) {

}

public List<T> query() {

return null;

}

}



  • 特别需要注意的地方:

1,并不存在泛型类。系统并不会为ArrayList<String>生成新的class类,也不会把它当做新的类来处理。也就是说泛型只是存在与编译阶段,实际上在运行生成的class文件时一样的,那么我们就可以通过跑反射来避开泛型。

 2,在敲代码的过程中,只要是没有出现未检查的警告,那么程序在运行的时候肯定是不会发生类型转换异常的。

 3,在写泛型的方法时,要是只是泛型的类型参数不同,那么这2个方法就算是同一个方法。因为编译后的泛型就去掉了,所以当然是一个方法

public static void  show(List<? extends Number> l){

}

public static void  show(List<? super String> l){

}

4,静态方法中不能使用类的泛型。不能用基本类型实例化泛型类型参数,例如List<int> linkin = null ; 编译报错。



5,泛型和继承的关系:如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G<B>并不是G<A>的子类型!比如:String是Object的子类,但是List<String >并不是List<Object>的子类。关于上面这点,要和数组区分开来:[B] b是[A] a的子类,但是在泛型接口不行的。

6,对象实例化时不指定泛型,默认为:Object。泛型不同的引用不能相互赋值。

7,从泛型类派生子类,泛型类型需具体化。其实泛型最大的用处就是用来定义一个泛型DAO,里面实现基本的CRUD方法,所以在继承的时候就直接传入实际的类型好了。关于泛型dao,在整理完反射后会统一整理。

上一篇:JDK源码分析(8)之 Reference 完全解读


下一篇:Java中WebService实例