Java学习之泛型
一、泛型概述
--->JDK1.5新特性
1、泛型的出现:
1、泛型是在JDK1.5以后出现的新特性。泛型是用于解决安全问题的,是一个安全机制。
2、JDK1.5的集合类希望在定义集合时,明确表明你要向集合中装入那种类型的数据,无法加入指定类型以外的数据。
3、泛型是提供给javac编译器使用的可以限定集合中的输入类型说明的集合时,会去掉“类型”信息,使程序运行效率不受影响,对参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。
4、由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,如用反射得到集合,再调用add方法即可。
2、好处:
1、使用泛型集合,可将一个集合中的元素限定为一个特定类型,集合中只能存储同一个类型的对象;这样就将运行时期出现的问题ClassCastException转移到了编译时期,方便与程序员解决问题,让运行时期问题减少,提高安全性。
2、当从集合中获取一个对象时,编译器也可知道这个对象的类型,不需要对对象进行强制转化,避免了强制转换的麻烦,这样更方便。
3、泛型格式:
通过<>来定义要操作的引用数据类型
如:
TreeSet<String> //来定义要存入集合中的元素指定为String类型
4、泛型定义中的术语:
如:ArrayList<E>类和ArrayList<Integer>
1、ArrayList<E>整个称为泛型类型
2、ArrayList<E>中的E称为类型变量或类型参数
3、整个ArrayList<Integer>称为参数化类型
4、ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
5、ArrayList<Integer>中的<>称为typeof
6、ArrayList称为原始类型
参数化:parametered,已经将参数变为实际类型的状态。
5、在使用java提供的对象时,何时写泛型?
通常在集合框架中很常见,只要见到<>就要定义泛型,其实<>就是用来接收类型的,当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。
6、关于参数化类型的几点说明:
1、参数化类型与原始类型的兼容性
第一、参数化类型可引用一个原始类型的对象,编译只是报警告,能不能通过编译,是编译器说了算。
如:
Collection<String> coll = new Date();
第二、原始类型可引用一个参数化类型的对象,编译报告警告
如:
Collection coll = new Vector<String>();
原来的方法接受一个集合参数,新类型也要能传进去。
2、参数的类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Objec>();//错误的
不写Object没错,写了就是明知故犯
Vector<Objec> v = new Vector<String>();//错误的
3、在创建数组实例时,数组的元素不能使用参数化的类型
如:
Vector<Integer> v[] = newVector<Integer>[10];//错误的
示例:
import java.lang.reflect.Constructor;
import java.util.*;
public class Generic {
public static void main(String[] args) throws Exception {
ArrayList<String> al = new ArrayList<String>();
al.add("25");
al.add("b");
System.out.println(al.get(1));
ArrayList<Integer> at = new ArrayList<Integer>();
at.add(23);
at.add(3);
System.out.println(at.get(1));
//编译器生成的字节码会去掉泛型的类型信息
System.out.println((al.getClass() == at.getClass()) +
"-->" + at.getClass().getName());
//at.add("ab")-->报错,存储的应为Integer类型
//反射方式,由于编译器生成的字节码会去掉泛型的类型信息,
//所以用反射可跳过编译器,存入任何类型
at.getClass().getMethod("add",Object.class).invoke(at,"abcd");
at.getClass().getMethod("add",Object.class).invoke(at,5);
System.out.println("反射方式:" + at.get(3));
System.out.println("反射方式:" + at.get(4));
//反射方式获得new String(new StringBuffer("abc"));
Constructor<String> cons = String.class.getConstructor(StringBuffer.class);
String st = cons.newInstance(new StringBuffer("abc"));
System.out.println(st);
二、泛型的通配符
1、泛型中的通配符?
当传入的类型不确定时,可以使用通配符?
1、使用?通配符可引用其他各种类型化的类型,通配符的变量主要用作引用,也可调用与参数化无关的方法,但不能调用与参数化有关的方法。
2、可对通配符变量赋任意值:
Collection<?> coll ---> coll = newHashSet<Date>();
如:
public static void printObj(Collection<?> coll){
//coll.add(1);是错误的,如果传入的是String类型,就不符合了
for(Object obj : coll){
System.out.println(obj);
}
}
示例:
import java.util.*;
class GenerticDemo
{
public static void main(String[] args)
{
ArrayList<String> p = new ArrayList<String>();
p.add("per20");
p.add("per11");
p.add("per52");
print(p);
ArrayList<Integer> s = new ArrayList<Integer>();
s.add(new Integer(4));
s.add(new Integer(7));
s.add(new Integer(1));
print(s);
}
public static void print(ArrayList<?> al) {
Iterator<?> it = al.listIterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
2、通配符的扩展-->泛型的限定:
对于一个范围内的一类事物,可以通过泛型限定的方式定义,
有两种方式:
1、? extends E:可接收E类型或E类型的子类型;称之为上限。
如:
Vector<? extends Number> x = newvector<Integer>();
2、? super E:可接收E类型或E类型的父类型;称之为下限。
如:
Vector<? super Integer>x = newvector<Number>();
示例如下:
/*
泛型的限定:
*/
import java.util.*;
class GenerticXian2
{
public static void main(String[] args)
{
TreeSet<Student> s = new TreeSet<Student>(new Comp());
s.add(new Student("stu0"));
s.add(new Student("stu3"));
s.add(new Student("stu1"));
print(s);
System.out.println("Hello World!");
TreeSet<Worker> w = new TreeSet<Worker>(new Comp());
w.add(new Worker("Worker0"));
w.add(new Worker("Worker3"));
w.add(new Worker("Worker1"));
print(w);
}
public static void print(TreeSet<? extends Person> ts) {
Iterator<? extends Person> it = ts.iterator();
while (it.hasNext()){
Person p = it.next();
System.out.println(p.getName());
}
}
}
class Person implements Comparable<Person> {
private String name;
Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int compareTo(Person p){
return this.getName().compareTo(p.getName());
}
}
class Comp implements Comparator<Person> {
public int compare(Person p1,Person p2){
return p1.getName().compareTo(p2.getName());
}
}
class Student extends Person {
Student(String name){
super(name);
}
}
class Worker extends Person {
Worker(String name){
super(name);
}
}
三、泛型方法
1、java中泛型方法的定义:
private static <T> T add(T a, T b){
......
return null;
}
········
add(3,5);//自动装箱和拆箱
Number x1 = add(3.5,5);//取两个数的交集类型Number
Object x2 = add(3,"abc");//去最大交集为Object
········
1、何时定义泛型方法:为了让不同方法可以操作不同的类型,而且类型不确定,那么就可以定义泛型方法
2、特殊之处:静态方法不可以访问类上定义的泛型,如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上。
2、泛型方法的特点:
1、位置:用于放置泛型的类型参数的<>应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前,按照惯例,类型参数通常用单个大写字母表示。
2、只有引用类型才能作为泛型方法的实际参数
3、除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符。
4、普通方法、构造函数和静态方法中都可以使用泛型。
5、可以用类型变量表示异常,称之为参数化的异常,可用于方法的throws列表中,但是不能用于catch子句中。
6、在泛型中可同时有多个类型参数,在定义它们的<>中用逗号分开。
public static <K,V> V getValue(K key){
Map<K, V> map = new HashMap<K, V>();
return map.get(key);
}
private static <T extends Exception> void sayHello() throws T{
try{}
catch(Exception e){
throw (T)e;
}
}
3、这个T和?有什么区别呢?
1、T限定了类型,传入什么类型即为什么类型,可以定义变量,接收赋值的内容。
2、?为通配符,也可以接收任意类型但是不可以定义变量。
但是这样定义,虽然提高了扩展性,可还是有一个局限性,就是不能使用其他类对象的特有方法。
3、总结:
通配符方案要比泛型方法更有效,当一个类型变量用来表达两个参数之间或参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用,而不是仅在签名的时候使用,才需要使用泛型方法。
四、泛型类
1、概述:
1、若类实例对象中多出要使用到同一泛型参数,即这些地方引用类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型。
2、何时定义泛型类:当类中要操作的引用数据类型不确定时,在早期定义Object来完成扩展,而现在定义泛型。
3、泛型类定义的泛型,在整个类中都有效,如果被方法调用,那么泛型类的对象要明确需要操作的具体类型后,所有要操作的类就已经固定了。
4、类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的。
2、语法格式:
1、定义
public class GenerDao1<T>{
private T field;
public void save(T obj){}
public T getByteId(int Id){}
}
2、举例:
扩展:Dao:Data Access Object,数据访问对象。
对其操作:crud即增上删改查
c:creat,创建、增加;
r:read,读取、查询;
u:update,更新、修改
d:delete,删除。
对javaEE的理解:13种技术。简单说就是对数据库的增删改查。
写Dao类有五个基本方法:增删改查,其中查包含查单个和对同类型集合的查询,如同性别或同地区的集合获取。
package cn.itcast.text2;
import java.util.*;
public class GenerticDao<T> {
public static <E> void staMethod(E e){}
public void add(T obj){}
public boolean delete(T obj){
return true;
}
public boolean delete(int id){
return true;
}
public T update(T obj){
return null;
}
public T findByUserName(String name){
return null;
}
public Set<T> findByPlace(String place){
Set<T> set = new TreeSet<T>();
//....
return set;
}
}
3、注意:
1、在对泛型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
2、当一个变量被声明为参数时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用,因为静态成员是被所有参数化的类共享的,所以静态成员不应该有类级别的类型参数。
总结:
对泛型的定义:
第一、定义泛型:当又不确定的类型需要传入到集合中,需要定义泛型
第二、定义泛型类:如果类型确定后,所操作的方法都是属于此类型,则定义泛型类
第三、定义泛型方法:如果定义的方法确定了,里面所操作的类型不确定,则定义泛型方法
示例:
//测试
class GenerticTest {
public static void main(String[] args) {
//创建泛型类对象
GenClass<Worker> g = new GenClass<Worker> ();
g.setTT(new Worker());
Worker w = g.getTT();
g.showC(w);
System.out.println("----------------------");
//泛型方法测试
GenMethod<String> g1 = new GenMethod<String>();
GenMethod.showS("SSS");
g1.show("sesf");
g1.print("heheh");
g1.printY(new Integer(5));
System.out.println("------------------------");
//泛型接口测试
GenInter g2 = new GenInter();
g2.show("haha");
System.out.println("Hello World!");
GenImpl<Integer> g3 = new GenImpl<Integer>();
g3.show(new Integer(95));
}
}
//泛型类
class GenClass<TT> {
//定义私有属性
private TT t;
//定义公共设置方法,设置属性
public void setTT(TT t) {
this.t = t;
}
//定义公共访问方法,访问属性
public TT getTT() {
return t;
}
//定义方法
public void showC(TT t) {
System.out.println("GenClass show:" + t);
}
}
//创建Worker类,作为类型传入泛型类中
class Worker {}
//泛型方法
class GenMethod<T> {
//静态的泛型方法
public static <S> void showS(S s) {
System.out.println("static show:" + s);
}
//非静态泛型方法
public void show(T t) {
System.out.println("未指定T show:" + t);
}
public void print(T t) {
System.out.println("指定T print:" + t);
}
//指定接受其他类型的泛型方法
public <Y> void printY(Y y) {
System.out.println("和类指定的不同,为Y print:" + y);
}
}
//泛型接口
interface Inter<T> {
void show(T t);
}
//一般类实现泛型接口
class GenInter implements Inter<String> {
public void show(String s) {
System.out.println("接口 show:" + s);
}
}
//泛型类实现泛型接口
class GenImpl<T> implements Inter<T> {
public void show(T t) {
System.out.println("类接收类型不确定的实现接口 show:" + t);
}
五、参数的类型推断
1、概述:
1、定义:编译器判断泛型方法的实际参数的过程,称之为类型推断。
2、类型推断是相对于直觉推断的,其实现方法是一种非常复杂的过程。
2、类型推断的具体规则:
根据调用泛型方法时,实际传递的参数类型或返回值的类型来推断。
1、当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时,该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时,传递的参数类型或返回值来决定泛型参数的类型,如:
swap(newString[3],1,2)
--->
static <E> void swap(E[] a, inti, int j);
2、当某个类型变量在某个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时,这多处的实际应用类型都对应同一种类型来表示,这很容易凭感觉推断出来:
add(3,5)
--->
static<T> T add(T a,T b);
3、若对应了不同类型,且没有使用返回值,这是取多个参数中的最大交集类型,如下面的对应类型Number,编译没问题,但是运行会出错:
fill(new Integer[3],3.5f)
--->
static<T> void fill(T[] a,T v);
4、若对应了不同类型,且使用了返回值,这时候优先考虑返回值类型,如下面语句实际对应的类型就是Integer了,编译将报错,将变量x类型改为float,对此eclipse报错提示,接着再将变量x类型改为Number,则就没了错误:
int x = add(3,3.5f)
--->
static<T> T add(T a,T b);
5、参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没问题,而第二种情况则会根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(newInteger[5],new String[5]);
--->
static<T> T copy(T[] a,T[] b);
六、扩展
--> 反射获得泛型的实际类型参数
举例说明:
package cn.itcast.text2;
import java.lang.reflect.*;
import java.sql.Date;
import java.util.*;
import cn.itcast.text1.ReflectPoint;
public class GenerticTest {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Object obj = "abc";
String str = autoContor(obj);
GenerticDao<ReflectPoint> gd = new GenerticDao<ReflectPoint>();
gd.add(new ReflectPoint(3,5));
//通过获得方法本身的方法
Method applyMethod = GenerticTest.class.getMethod("applyVector", Vector.class);
//通过方法的获取泛型参数的方法得到原始参数类型的集合
Type[] types = applyMethod.getGenericParameterTypes();
//将参数类型转换为参数化类型
ParameterizedType pType = (ParameterizedType)types[0];
//得到原始类型
System.out.println(pType.getRawType());
//得到实际参数类型
System.out.println(pType.getActualTypeArguments()[0]);
}