一、泛型
代码示例:
import java.util.*;
class GenericDemo
{
public static void main(String[] args)
{
ArrayList al = new ArrayList();
al.add("java01");
al.add("java0991");
al.add("java014");
al.add(4); //al.add(newInteger(4));
Iterator it = al.iterator();
while(it.hasNext())
{
String s = (String)it.next();
System.out.println(s+":"+s.length());
}
}
}
运行结果:
1、什么是泛型?
是指在对象建立时不指定类中属性的具体类型,而是由外部在声明及实例化对象时指定其具体类型。
2、泛型出现的时间:
JDK1.5版本之后出现的新特性。
3、作用:用于解决安全问题,是一个类型安全机制。
没有定义泛型之前,程序在编译的时候不一定会出现异常,而是可能会发生在程序运行的时候,存在了安全隐患,不利于判断和解决问题。
4、泛型的好处:
①、泛型就是将程序从运行时期出现的ClassCastException异常转移到了编译时期。
②、避免了强制转换的麻烦。
代码示例:
class GenericDemo
{
public static void main(String[] args)
{
ArrayList<String> al = new ArrayList<String>();
al.add("java01");
al.add("java0991");
al.add("java014");
Iterator<String> it = al.iterator();
while(it.hasNext())
{
String s = it.next();
System.out.println(s+":"+s.length());
}
}
}
运行结果:
5、泛型定义格式:通过一对尖括号< >来定义要操作的引用数据类型。
6、什么时候使用泛型?
通常在集合框架中很常见。在对象声明时就要限定集合中的元素的类型时,就要使用泛型来约束。
其实< >就是用来接收类型的。只要将集合中要存储的数据类型做为参数传递到尖括号<>中即可。
注意:
①、泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入。编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响。
对于参数化的泛型类型,getClass()方法的返回值和原始数据类型完全一致。
代码示例1:验证不能泛型类型的同种集合是否是同一个字节码
package cn.itcast.day2;
import java.util.ArrayList;
public class GenericTest {
public static void main(String[]args) throws Exception{
ArrayList<String> collection1 = new ArrayList< String>();
ArrayList<Integer> collection2 = new ArrayList<Integer>();
System.out.println(collection1.getClass()== collection2.getClass()); //true 表示两个集合,虽然泛型类型不一样,但是确实同一份字节码,因为编译器编译时会去掉泛型类型信息。
}
}
②、由于编译器生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据。(例如:用反射得到集合,再调用其add()方法即可)
代码示例2:跳过编译器往集合中加入一个与泛型类型不一致的类型数据
package cn.itcast.day2;
import java.util.ArrayList;
public class GenericTest {
public static void main(String[]args) throws Exception{
ArrayList<Integer> collection2 = new ArrayList<Integer>();
//collection3.add("abc");
Collection2.getClass().getMethod("add", Object.class).invoke(collection3,"abc");
System.out.println(collection3.get(0)); //结果:abc 虽然集合的泛型要求只能添加Integer类型的元素,但是通过反射的方式却可以跳过编译器往集合中添加其它类型的元素。
}
}
7、了解泛型
①、泛型中的几个术语:以ArrayList集合为例
- 整个ArrayList<E>称为泛型类型
- ArrayList<E>中的E称为类型变量或者类型参数
- 整个ArrayList<Integer>称为参数化的类型
- ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
- ArrayList<Integer>中的尖括号<>念:typeof
- ArrayList称为原始类型
②、参数化类型与原始类型的兼容性:
参数化类型可以引用一个原始类型的对象。
例如:Collection<String> c = new Vector();
原始类型可以引用一个参数化类型的对象。
例如:Collection c = new Vector<String>();
③、参数化类型不考虑类型参数的继承关系
例如:Vector<String> v = new Vector<Object>(); //错
Vector<Object> v = newVector<String>(); //错
//两种方式都会报错。
④、在创建数组实例时,数组的元素不能使用参数化的类型
例如:Vector<Integer>vList[] = new Vector<Integer>[10];
//这种写法是错误的,不能这样写。
思考题:下面的代码会报错吗?
Vector v1 = new Vector<String>();
Vector<Object> v2 = v1;
答:以上两行代码都不会报错。
因为第一行是代码是将一个原始类型的引用指向一个参数化类型的对象,不会报错。第二行是将一个参数化类型的引用指向一个原始类型的引用,也不会报错。
8、自定义泛型类
什么时候需要自定义泛型类?
当类中要操作的引用数据类型不确定的时候,需要定义自定义泛型类。
早期定义Object来完成扩展,现在定义泛型来完成扩展。
示例代码:
class Worker{ }
class Student{ }
class Utils<QQ>
{
private QQ q;
public void setObject(QQq)
{
this.q = q;
}
public QQ getObject()
{
return q;
}
}
class GenericDemo3
{
public static void main(String[] args)
{
Utils<Worker>u = new Utils<Worker>();
u.setObject(new Student());
Worker w = (Worker)u.getObject();
}
}
编译结果:将异常在编译时期暴露出来。
9、泛型方法
①、为什么要定义泛型方法?
泛型类定义的泛型,在整个类中有效。如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经被固定了。
为了让不同方法可以操作不同类型,而且类型还不确定。那么可以将泛型定义在方法上。
②、泛型方法定义格式:
权限修饰符 <数据类型>返回值类型方法名(数据类型参数名)
{
//执行内容。
}
代码示例:
class Demo
{
public<T> void show(T t) //泛型方法。
{
System.out.println("show:"+t);
}
public<Q> void print(Q q) //泛型方法。
{
System.out.println("print:"+q);
}
}
class GenericDemo4
{
public static void main(String[] args)
{
Demo d = new Demo();
d.show("haha"); //show:haha
d.show(newInteger(4)); //show:4
d.print("heihei"); //print:heihei
}
}
③、静态泛型方法:被static修饰的泛型方法。
代码示例:
class Demo<T>
{
public void show(T t)
{
System.out.println("show:"+t);
}
public <Q> void print(Q q)
{
System.out.println("print:"+q);
}
public static <W> void method(W w)
{
System.out.println("method:"+w);
}
}
class GenericDemo4
{
public static void main(String[] args)
{
Demo<String> d = new Demo<String>();
d.show("haha");
d.print(5);
d.print("hehe");
Demo.method("hahahaha");
}
}
静态泛型方法的特殊之处:
静态方法不可以访问类上定义的泛型。如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上。
注意:泛型方法中的尖括号< >必须定义在方法返回值类型的前面。否则属于语法错误,编译会报错。
泛型方法注意事项:
①、用于放置泛型的类型参数的尖括号应该出现在其它所有修饰符之后和方法返回值类型之前,也就是紧邻方法返回值类型之前。
通常类型参数用单个大写字母表示。
例:private static<T> void swap(T[ ] a,int i,int j){
T tmp = a[i];
a[i] = a[j];
a[j]= tmp;
}
②、只有引用类型才能作为泛型方法的实际参数。
例:swap(new String[]{"abc","xyz","123"},1,2); 编译通过。
swap(newint[]{1,3,5,7,9,0},2,3); 编译报错。
③、除了可以在应用泛型时可以使用限定符,在定义泛型时也可以使用泛型限定符。并且还可以使用&符号来指定泛型的多个边界。
例:Class类中getAnnotation()方法的定义:
public <A extends Annotation> A getAnnotation(Class<A>annotationClass):表示泛型类型是Annotation类或者Annotation的子类。
或者
public <V extends Serializable & cloneable> voidmethod(){}:使用&符号指定泛型类型的多个边界。
④、普通方法、构造方法和静态方法中都可以使用泛型。
⑤、可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于异常处理的catch()语句当中。
例:private static <Textends Exception> sayHello() throws T
{
try
{
}catch(Exception e)
{
throw (T)e;
}
}
⑥、在泛型中可以同时有多个类型参数,在定义它们的尖括号中用都好分隔。
例:public static<K,V> V getValue(K key)
{
return map.get(key);
}
泛型的类型推断:
①、什么是泛型的类型推断?
编译器判断泛型方法的实际类型参数的过程称为类型推断。类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
②、根据调用泛型方法时实际传递的参数类型或者返回值的类型来推断,具体规则如下:
当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型。
例:调用方法:swap(newString[3],1,2); //根据调用方法传递的参数类型可知该泛型参数的类型为String类型。
泛型方法定义:static <E> voidswap(E[] arr,int x,int y){}
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定。
例:调用方法:add(3,5);
泛型方法定义:static <T> Tadd(T x,T y){ }
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型。
例:调用方法:fill(new Integer[3],3.5f); //该处对应的类型就是Number类型,编译时没有问题,但运行时会报错。
泛型方法定义:static <T> voidfill(T[] array,T value){ }
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,这时候优先考虑返回值的类型。
例:调用方法:Number x = add(3,3.5f); //此处调用方法的返回值类型既不是int/Integer,也不是float/Float,所以x的类型就是Number,这样才不会报错。
泛型方法定义:static <T> Tadd(T a, T b){ }
参数类型的类型推断具有传递性。
例1:调用方法:copy(newInteger[5],new String[5]);
泛型方法定义:static <T> voidcopy(T[] a,T[] b){}
//以上示例1中可以推断实际参数类型为Object,编译没有问题。
例2:调用方法:copy(newVector<String>(),new Integer[5]);
泛型方法定义:static <T> voidcopy(Collection<T> a , T[] b){}
//以上示例2根据参数化的Vector类实例将类型直接确定为String类型,编译无法通过,会报错。
练习题:通过反射来获取泛型类型。
代码示例:
package com.hy.test;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.Vector;
public class GetGenericType {
public static void main(String[]args) throws Exception{
Method applyMethod = GetGenericType.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]);
}
//自定义一个泛型方法,并传入一个有泛型类型的参数。
public static void applyVector(Vector<Date> v){
}
}
10、泛型接口。
泛型接口的定义格式:
interface 接口名<数据类型参数名>
{
抽象方法;
}
代码示例:
interface Inter<T>
{
void show(T t);
}
class InterImpl implements Inter<String>
{
public void show(Stringt)
{
System.out.println("show:"+t);
}
}
class GenericDemo5
{
public static void main(String[] args)
{
InterImpl i = new InterImpl();
i.show("haha");
}
}
注意:当一个类实现一个泛型接口但不确定要操作的数据类型的时候,可以在接口实现类上也定义泛型。这时接口实现类中使用的数据类型就是从泛型接口中继承过来的。
代码示例:
interface Inter<T>
{
void show(T t);
}
class InterImpl<T> implements Inter<T>
{
public void show(T t)
{
System.out.println("show:"+t);
}
}
class GenericDemo5
{
public static void main(String[] args)
{
InterImpl<Integer>i = new InterImpl<Integer>();
i.show(4);
}
}
输出结果:
11、泛型限定
①、泛型中的通配符/占位符:?
当集合中的泛型的具体类型不确定的时候,可以使用通配符?来表示可以接收任意数据类型的泛型参数。
示例:
——加通配符之前的现象:
import java.util.*;
class GenericDemo6
{
public static void main(String[] args)
{
ArrayList<String>al = new ArrayList<String>();
al.add("abc1");
al.add("abc2");
ArrayList<Integer>al1 = new ArrayList<Integer>();
al1.add(4);
al1.add(7);
printColl(al);
printColl(al1); //编译会报错。因为printColl方法中已经明确只能接收String类型元素的集合,而集合对象al1是Integer类型,类型不一致,则编译会出现错误。
}
public static void printColl(ArrayList<String> al)
{
Iterator<String>it = al.iterator();
while(it.hasNext())
{
System.out.println(it.next().toString());
}
}
}
——加通配符之后的现象:只需要修改printColl方法中接收的集合泛型类型。
public static void printColl(ArrayList<?> al)
{
Iterator<?> it = al.iterator();
while(it.hasNext())
{
System.out.println(it.next().toString());
}
}
注意:
使用?通配符可以引用其它各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法(例如:Collection集合中的size()方法),不能调用与参数化有关的方法(例如:add()方法)。
②、泛型限定
作用:是用于泛型扩展用的。
向上限定:
? extends E:可以接收E类型或者E的子类型。
向下限定:
? super E:可以接收E类型或者E的父类型。
泛型限定代码示例:
import java.util.*;
class GenericDemo6
{
public static void main(String[] args)
{
ArrayList<Person>al = new ArrayList<Person>();
al.add(new Person("abc1"));
al.add(new Person("abc2"));
al.add(new Person("abc3"));
ArrayList<Student>al1 = new ArrayList<Student>();
al.add(new Student("abc1"));
al.add(new Student("abc2"));
al.add(new Student("abc3"));
printColl_1(al); //可以接收Person类型的参数。
printColl_1(al1); //可以接收Student类型的参数。
}
public static void printColl_1(ArrayList<? extends Person>al)
//向下限定:表示printColl_1方法可以接收Person类型或者其子类类型的参数。
{
Iterator<? extends Person> it = al.iterator();
while(it.hasNext())
{
System.out.println(it.next().getName());
}
}
public static void printColl_2(ArrayList<?super Student> al)
//向上限定:表示printColl_2方法可以接收Student类型或者其父类类型的参数。
{
Iterator<? super Student> it = al.iterator();
while(it.hasNext())
{
System.out.println(it.next().getName());
}
}
}
class Person
{
private String name;
Person(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
}
class Student extends Person
{
Student(String name)
{
super(name);
}
}
运行结果:
abc1
abc2
abc3
abc1
abc2
abc3
二、Map集合
1、Map集合的特点:该集合存储的是键值对。一对一对的往里存,而且要保证键的唯一性。
Map集合中的元素都是使用“key—value”的形式存储在集合中。
2、Map集合体系结构以及特点:
Map
|--Hashtable:底层是哈希表数据结构,不可以存入null键null值。该集合是线程同步的。JDK1.0版本出现,效率低。无序存放。
|--HashMap:底层是哈希表数据结构,允许使用null键null值,该集合是不同步的。JDK1.2版本出现,效率高。无序存放。
|--TreeMap:底层是二叉树数据结构。线程不同步。可以用于给Map集合中的键进行排序。有序存放。
注:Map集合和Set集合很像。其实Set集合底层就是使用了Map集合。
3、Map集合中的常用方法:
①、添加
V put(K key, V value)
void putAll(Map<? extends K,? extends V> m)
②、删除
void clear()
V remove(Object key)
③、判断
boolean containsKey(Object key)
boolean containsValue(Object value)
boolean isEmpty()
④、获取
V get(Object key)
int size()
Collection<V>values()
Set<Map.Entry<K,V>>entrySet()
Set<K> keySet()
代码示例:
import java.util.*;
class MapDemo
{
public static void main(String[] args)
{
Map<String,String> map = new HashMap<String,String>();
//1、添加元素。
map.put("01","zhangsan1");
map.put("02","zhangsan2");
map.put("03","zhangsan3");
//3、判断。
//集合中是否有“02”这个键。
System.out.println("containsKey:"+map.containsKey("02"));
//2、删除。
//删除集合中“02”这个键,以及其对应的值。
System.out.println("remove:"+map.remove("02"));
//4、获取。
System.out.println("get:"+map.get("02"));
map.put("04",null);
System.out.println("get:"+map.get("04"));
//可以通过get方法的返回值来判断一个键是否存在。通过返回null来判断。
//获取map集合中所有的值。
Collection<String> coll = map.values();
System.out.println(coll);
System.out.println(map); //打印集合。
}
}
注意:如果在添加元素时出现了相同的键,那么后添加的键对应的值会覆盖原有键对应的值,并且put方法会返回被覆盖的值。
4、Map集合的两种取出方式:
①、Set<K>keySet():
1) 将Map集合中所有所有的键存入到Set集合中。
2) 因为Set集合具备迭代器,所以可以使用迭代方式取出所有的键。
3) 再根据Map集合中的get(key)方法,获取每一个键对应的值。
取出原理:将Map集合转成Set集合,再通过迭代器取出。
示例代码:
import java.util.*;
class MapDemo2
{
public static void main(String[] args)
{
Map<String,String> map = new HashMap<String,String>();
map.put("02","zhangsan2");
map.put("03","zhangsan3");
map.put("01","zhangsan1");
map.put("04","zhangsan4");
//1、先获取map集合的所有键的Set集合:keySet();
Set<String> keySet = map.keySet();
//2、有了Set集合,就可以获取其迭代器。
Iterator<String> it = keySet.iterator();
while(it.hasNext())
{
String key = it.next();
//3、有了键,就可以通过Map集合的get方法获取其对应的值。
String value = map.get(key);
System.out.println("key:"+key+"...value:"+value);
}
}
}
keySet方法图形示例:
②、Set<Map.Entry<k,v>>entrySet():
1) 先将Map集合中的映射关系存入到了Set集合中,而这个关系的数据类型就是:Map.Entry。
2) 再通过Set集合的迭代器取出Map集合的映射关系。
3) 然后再通过Map.Entry这个映射关系提供的getKey()和getValue方法取出关系。中的键和值。
代码示例:
import java.util.*;
class MapDemo2
{
public static void main(String[] args)
{
Map<String,String> map = new HashMap<String,String>();
map.put("01","zhangsan1");
map.put("02","zhangsan2");
map.put("03","zhangsan3");
map.put("04","zhangsan4");
//使用entrySet方法将Map集合中的映射关系存入到Set集合中。这个关系的数据类型就是Map.Entry
Set<Map.Entry<String,String>> entrySet = map.entrySet();
//再使用Set集合的迭代器取出这些映射关系。
Iterator<Map.Entry<String,String>> it = entrySet.iterator();
while(it.hasNext())
{
Map.Entry<String,String> me = it.next();
//通过Map.Entry提供的getKey和getValue方法获取映射关系中的键和值。
String key = me.getKey();
String value = me.getValue();
System.out.println(key+"::"+value);
}
}
}
entrySet方法图形示例:
5、Map.Entry介绍
Map.Entry:表示键值的一种映射关系。其实是一个接口。
Entry也是一个接口,它是Map接口中的接口,也就是内部接口,和内部类类似。
Map.Entry的实际定义:
interface Map
{
public static interface Entry //由于可以直接被外部接口调用并对外暴露,所以内部接口是被static和public所修饰。
{
public abstract Object getKey(); //用于获取key的抽象方法。
public abstract Object getValue(); //用于获取value的抽象方法。
}
}
6、什么时候使用Map集合?
当数据之间存在着映射关系时,可以选择Map集合。因为Map集合中存放的就是映射关系。