JCF(Java Collection Framework)
容器框架
容器:
能够存放数据的空间结构。例如: 数组/多维数组 ,列表/散列集/树…
容器框架:
为表示和操作容器而规定的一种标准体系结构。
容器框架包含三部分:
1.对外的接口:容器中所能存放的抽象数据。
2.接口的实现:可复用的数据结构。
3.算法:对数据的查找和排序。
容器框架的优点
提高数据存取效率,避免程序员重复劳动。
优秀的容器框架: C++的STL Java的JCF
JCF集合框架介绍
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DQphk47y-1599374508043)(/Users/beifeng/Documents/JCF.png)]
JCF的集合接口是Collection
常用方法 | 详解 |
---|---|
boolean add(Object o) | 添加对象到集合 |
boolean remove(Object o) | 删除制定的对象 |
Int size() | 返回集合中元素的数量 |
boolean contains(Object o) | 查找集合中是否有指定的对象 |
Iterator iterator() | 返回一个迭代器 |
boolean containsAll(Collection c) | 查找集合中是否有集合c中的元素 |
boolean addAll(Collection c) | 将集合c中所有的元素添加给该集合 |
void clear() | 删除集合中所有元素 |
void removeAll(Collection c) | 从集合中删除c集合中也有的元素 |
void retainAll(Collection c) | 从集合删除集合c中不包含的元素 |
JCF的迭代器接口Iterator
常用方法 | 详解 |
---|---|
boolean hasNext() | 如果迭代器中还有元素,那么返回true |
next() | 返回迭代器下一个元素 |
void remove() | 从迭代器指向的 collection 中移除迭代器返回的最后一个元素。每次调用 next 后只能调用一次此方法。如果进行迭代时用调用此方法之外的其他方式修改了该迭代器所指向的 collection,则迭代器的行为是不确定的。 |
JCF主要的数据结构实现类
列表(List,ArrayList ,LinkedList)
集合(Set,HashSet,TreeSet,LinkedHashSet)
映射(Map,HashMap,TreeMap,LinkedHashMap)
JCF主要的算法类
Arrays: 对数组进行查找和排序等操作
Collection:对Collection及其子类进行排序
列表List
List:列表
有序的Collection
允许重复元素
可以存储一组不唯一、有序的对象。
可以添加任何类型的数据,并且添加的数据都将转换成Object类型
List 主要实现
ArrayList(非同步的)
LinkedList(非同步的)
Vector (同步,多线程)
ArrayList
以数组实现的列表,不支持同步。
同步可以调用Collections类中的synchronizedList()方法,以collection对象为参数
利用索引位置可以快速定位访问
不适合指定位置的插入、删除操作(相较于LinkedList)
适合变动不大,主要用于查询的数据
和Java数组相比,其容量是可动态调整的,ArrayList在元素填满容器时会自动扩充容器大小的50%
public static void main(String[] args){
ArrayList<Integer> al= new ArrayList<Integer>();
al.add(2);
al.add(3);
al.add(4);
al.add(new Integer(5));
System.out.print("The third element is");
System.out.println(al.get(3));
al.remove(3);
al.add(3,9);
for (int i=0;i<al.size();i++){
System.out.println(al.get(i));
}
System.out.println("===============遍历方法============");
ArrayList<Integer> as= new ArrayList<Integer>(100000);
traverseByIterator(as);
traverseByIndex(as);
traverseByFor(as);
}
public static void traverseByIterator(ArrayList<Integer> al){
long startTime=System.nanoTime();
System.out.println("=======迭代器遍历======");
Iterator<Integer> iter=al.iterator();
while(iter.hasNext()){
iter.next();
}
long endTime=System.nanoTime();
long runTime=endTime-startTime;
System.out.println( runTime+"纳秒");
}
public static void traverseByIndex(ArrayList<Integer> al){
long startTime=System.nanoTime();
System.out.println("========随机索引遍历======");
for(int i=0;i<al.size();i++){
al.get(i);
}
long endTime=System.nanoTime();
long runTime=endTime- startTime;
System.out.println(runTime+"纳秒");
}
public static void traverseByFor(ArrayList<Integer> al){
long startTime=System.nanoTime();
System.out.println("========for循环遍历======");
for (Integer item:al) {
}
long endTime=System.nanoTime();
long runTime=endTime-startTime;
System.out.println(runTime+"纳秒");
}
LinkedList
以双向链表实现的列表,不支持同步
同步可以调用Collections类中的synchronizedList()方法,以collection对象为参数
可被当作堆栈、队列和双端队列进行操作
顺序访问高效,随机访问较差,中间插入和删除高效
适用于经常变化的数据
public static void main(String[] args){
LinkedList<Integer> ll=new LinkedList<Integer>();
ll.add(3);
ll.add(2);
ll.add(5);
ll.add(6);
ll.add(6);
System.out.println(ll.size());
ll.addFirst(9); //在头部增加9
ll.add(3, 10); //将10插入到第四个元素,四以及后续的元素往后挪动
ll.remove(3); //将第四个元素删除
LinkedList<Integer> list = new LinkedList<Integer>();
for (int i=0; i<100000; i++)
{
list.add(i);
}
traverseByIterator(list);
traverseByIndex(list);
traverseByFor(list);
}
public static void traverseByIterator(LinkedList<Integer> list)
{
long startTime = System.nanoTime();
System.out.println("============迭代器遍历==============");
Iterator<Integer> iter1 = list.iterator();
while(iter1.hasNext()){
iter1.next();
}
long endTime = System.nanoTime();
long duration = endTime - startTime;
System.out.println(duration + "纳秒");
}
public static void traverseByIndex(LinkedList<Integer> list){
long startTime = System.nanoTime();
System.out.println("============随机索引值遍历==============");
for(int i=0;i<list.size();i++)
{
list.get(i);
}
long endTime = System.nanoTime();
long duration = endTime - startTime;
System.out.println(duration + "纳秒");
}
public static void traverseByFor(LinkedList<Integer> list){
long startTime = System.nanoTime();
System.out.println("============for循环遍历==============");
for(Integer item : list){
}
long endTime = System.nanoTime();
long duration = endTime - startTime;
System.out.println(duration + "纳秒");
}
Vector
和ArrayList类似,可变数组实现的列表
Vector同步,适合在多线程下使用
官方文档建议在非同步情况下,优先采用ArrayList
ArrayList和LindkeList的比较
public static void main(String[] args) {
int times = 10 * 1000;
// times = 100 * 1000;
// times = 1000 * 1000;
ArrayList<Integer> arrayList = new ArrayList<Integer>();
LinkedList<Integer> linkedList = new LinkedList<Integer>();
System.out.println("Test times = " + times);
System.out.println("-------------------------");
// ArrayList add
long startTime = System.nanoTime();
for (int i = 0; i < times; i++) {
arrayList.add(0,i);
}
long endTime = System.nanoTime();
long duration = endTime - startTime;
System.out.println(duration + " <--ArrayList add");
// LinkedList add
startTime = System.nanoTime();
for (int i = 0; i < times; i++) {
linkedList.add(0,i);
}
endTime = System.nanoTime();
duration = endTime - startTime;
System.out.println(duration + " <--LinkedList add");
System.out.println("-------------------------");
// ArrayList get
startTime = System.nanoTime();
for (int i = 0; i < times; i++) {
arrayList.get(i);
}
endTime = System.nanoTime();
duration = endTime - startTime;
System.out.println(duration + " <--ArrayList get");
// LinkedList get
startTime = System.nanoTime();
for (int i = 0; i < times; i++) {
linkedList.get(i);
}
endTime = System.nanoTime();
duration = endTime - startTime;
System.out.println(duration + " <--LinkedList get");
System.out.println("-------------------------");
// ArrayList remove
startTime = System.nanoTime();
for (int i = 0; i < times; i++) {
arrayList.remove(0);
}
endTime = System.nanoTime();
duration = endTime - startTime;
System.out.println(duration + " <--ArrayList remove");
// LinkedList remove
startTime = System.nanoTime();
for (int i = 0; i < times; i++) {
linkedList.remove(0);
}
endTime = System.nanoTime();
duration = endTime - startTime;
System.out.println(duration + " <--LinkedList remove");
}
运行结果:
Test times = 10000
-------------------------
17336101 <--ArrayList add
7821708 <--LinkedList add
-------------------------
2321748 <--ArrayList get
92962656 <--LinkedList get
-------------------------
14439890 <--ArrayList remove
1829960 <--LinkedList remove
Process finished with exit code 0
从运行结果可以看出LinkedList的随机添加和删除元素的效率比ArrayList效率高。而ArrayList随机访问元素的效率较高
小结:
ArrayList实现类长度可变的数组,在内存中分配连续的空间,遍历元素和随机访问元素的效率比较高。
LinkedList采用链表存储方式,插入、删除元素时效率较高。
Vector 和ArrayList类似,可变数组实现的列表,支持同步,多线程安全。官方文档建议在非同步情况下,优先采用ArrayList
集合Set
集合Set
确定性:对任意对象都能判定其是否属于某一个集合
互异性:集合每个元素都是无差异的,注意是内容差异
无序性:集合内的顺序无关
存储一组唯一、无序的对象。
Java中的集合接口Set
HashSet(基于散列函数的集合,无序,不支持同步)
TreeSet(基于树结构的集合,可排序,不支持同步)
LinkedHashSet(基于散列函数和双向链表的集合,可排序不支持同步)
HashSet
基于HashMap实现的,可以容纳null元素,不支持同步。
同步可以调用Collections类中的synchronizedList()方法,以collection对象为参数
- 它是无序的,即添加的顺序和遍历出来的顺序是不同的
- 它里面不允许有重复元素,是因为它是基于HashMap实现的
- 实现了Set接口,由哈希表(实际上是一个HashMap实例)支持
- 底层数据结构是哈希表
常用方法 | 含义 |
---|---|
add | 添加一个元素 |
clear | 清楚整个HashSet |
contains | 判定是否包含一个元素 |
remove | 删除一个元素 |
size | 大小 |
retainAll | 计算两个集合的交集 |
public static void main(String[]args){
HashSet<Integer> hs=new HashSet<Integer>();
hs.add(null); //允许添加null元素
hs.add(2);
hs.add(2); //2重复
hs.add(3);
System.out.println(hs.size()); //size 3 重复元素不添加
if(!hs.contains(6)){ //是否包含6
hs.add(6);
}
System.out.println(hs.size()); //4
// hs.clear();
System.out.println("==========for循环==========");
for (Integer item:hs){
System.out.println(item);
}
System.out.println("==========测试多种遍历方法速度=========");
HashSet<Integer> hs2=new HashSet<Integer>();
for (int i=0;i<100000;i++){
hs2.add(i);
}
traverseByIterator(hs2);//迭代器循环
traverseByFor(hs2);//for循环
}
public static void traverseByIterator(HashSet<Integer> hs){
long startTime=System.nanoTime();
System.out.println("=========迭代器循环=======");
Iterator<Integer> iter=hs.iterator();
while(iter.hasNext()){
iter.next();
}
long endTime=System.nanoTime();
long time=endTime-startTime;
System.out.println("iterator:\t"+time);
}
public static void traverseByFor(HashSet<Integer> hs){
long startTime=System.nanoTime();
System.out.println("=========for循环========");
for (Integer iter:hs ) {
}
long endTime=System.nanoTime();
long time=endTime-startTime;
System.out.println("for:\t"+time);
}
LinkedHashSet
继承HashSet,也是基于HashMap实现的
不支持同步
方法和Collection基本一致: add, clear ,contains ,remove,size
通过一个双向链表维护插入顺序
根据插入顺序排序,可以容纳null元素
TreeSet
基于TreeMap实现的,不可以容纳null元素,不支持同步
同步可以调用Collections类中的synchronizedList()方法,以collection对象为参数
方法和Collection常用方法基本一致: add ,clear ,caontains, remove ,size
根据compareTo方法或指定Comparator排序
自然数排序(升序排列)。
HashSet/LinkedHashSet去重原理总结:
A:HashSet底层数据结构是哈希表(是一个元素为链表的数组)
B:哈希表底层依赖两个方法:hashCode()和equals()
执行顺序:
首先比较哈希值是否相同
相同:继续执行equals()方法
返回true:元素重复了,不添加
返回false:直接把元素添加到集合
不同:就直接把元素添加到HashSet集合里
C:元素唯一性由hashCode()和equals()保证的,二者缺一不可
示例:
System.out.println("============未进行任何修改=======");
HashSet<Cat> hs=new HashSet<Cat>();
hs.add(new Cat(1));
hs.add(new Cat(2));
hs.add(new Cat(1));
System.out.println(hs.size());//返回size 3
System.out.println("============重写Object中的 HashCOde equals toString方法 =======");
HashSet<Dog> hs1=new HashSet<Dog>();
hs1.add(new Dog(1));
hs1.add(new Dog(2));
hs1.add(new Dog(1));
System.out.println(hs1.size());//返回size 2
System.out.println("=========LinkedHashSet=========");
LinkedHashSet<Dog> lhs2= new LinkedHashSet<Dog>();
lhs2.add(new Dog(2));
lhs2.add(new Dog(1));
lhs2.add(new Dog(3));
lhs2.add(new Dog(5));
lhs2.add(new Dog(4));
lhs2.add(new Dog(4));
System.out.println(lhs2.size()); //5
Cat中代码
public class Cat {
private int size;
public Cat(int size){
this.size=size;
}
}
Dog中代码
public class Dog {
private int size;
public int getSize() {
return size;
}
public Dog(int size){
this.size=size;
}
/**
* 重写Object类中的hashCode 、equals、toString方法
*/
public boolean equals(Object obj2){
System.out.println("Dog equals");
if(0==size-((Dog) obj2).getSize()){
return true;
}else{
return false;
}
}
public int hashCode(){
System.out.println("Dog hashCode()");
return size;
}
public String toString(){
System.out.println("Dog toString");
return size+"";
}
}
TreeSet去重原理总结:
可以实现排序及去重:如果compareTo返回0,说明是重复的,返回的是自己的某个属性和另一个对象的某个属性的差值,如果是负数,则往前面排,如果是正数,往后面排;
示例:
TreeSet<Brid> ts3 = new TreeSet<Brid>();
ts3.add(new Brid(2));
ts3.add(new Brid(1));
ts3.add(new Brid(3));
ts3.add(new Brid(5));
ts3.add(new Brid(4));
ts3.add(new Brid(4));
System.out.println(ts3.size()); //5
Brid中代码
public class Brid implements Comparable{
private int size;
public int getSize() {
return size;
}
public Brid(int size){
this.size=size;
}
@Override
public int compareTo(Object o) {
return size-((Brid)o).getSize();
}
}
小结
HashSet,LinkedHashSet,TreeSet的元素都只能是对象
映射Map
Map映射:
数学定义:两个集合之间的元素对应关系。
一个输入对应一个输出 {Key , Value}, 键值对,K-V对
Java中Map
Hashtable(同步,慢,数据小)
HashMap(不支持同步,块,数据量大)
Properites(同步,文件形式,数据量小)
Hashtable
K-V对,K和V都不允许为null。
同步,多线程安全
无序的,适合小数据量
主要方法:clear,contains/contains Value,containsKey,get,put,remove,size
Hashtable<Integer,String> ht=new Hashtable<Integer,String>();
// ht.put(1,null);编译不报错,运行报错
// ht.put(null,1);编译报错
ht.put(1,"aaa");
ht.put(2,"bbb");
ht.put(3,"ccc");
System.out.println(ht.contains("aaa"));
System.out.println(ht.containsValue("aaa"));//contains和containsValue方法都是比较value值
System.out.println(ht.containsKey(1));
ht.put(3,"ddd");//覆盖ccc
System.out.println(ht.get(3));
ht.remove(1);
System.out.println(ht.size());//2
ht.clear();
System.out.println(ht.size());//0
HashMap
K-V对,K和V都允许为null。
不同步,多线程不安全
无序的
主要方法:clear, containsValue, containsKey, get, put, remove, size
keySet(键集合) values(值集合)
HashMap<Integer,String> hm=new HashMap<Integer,String>();
hm.put(1,null);
hm.put(null,"aaa");//HashMap 允许key和value为null
Iterator<Integer> iterator =hm.keySet().iterator();
Integer key;
String value;
System.out.println("=========keySet 迭代器==========");
while(iterator.hasNext()){
key=iterator.next();
//获取value
value=hm.get(key);
System.out.println("key:"+key+"\tvalue:"+value);
}
System.out.println("========Entry 迭代器==========");
Iterator<Map.Entry<Integer,String>> iter=hm.entrySet().iterator();
while(iter.hasNext()){
Map.Entry<Integer,String> entry=iter.next();
//获取key value
key=entry.getKey();
value=entry.getValue();
System.out.println("key:"+key+"\tvalue:"+value);
}
LinkedHashMap
基于双向链表的维持插入顺序的HashMap
TreeMap
基于红黑树的Map,可以根据key的自然排序或者compareTo方法进行排序输出
Properties
继承与Hashtable
可以将K-V对保存在文件中
适用于数据量少的配置文件
继承自Hasht的方法: clear, contains/containsValue, containsKey, get, put remove size
从文件加载的load方法,写入到文件中的store方法
获取属性getProperty,设置属性setProperty
//写入properties信息
public static void WritProperites(String path,String key,String value)throws IOException {
File file =new File(path);
if(!file.exists()){
file.createNewFile();
}
Properties pps=new Properties();
InputStream in =new FileInputStream(file);
//从输入流中读取属性列表(键值对)
pps.load(in);
//调用Hashtable的方法put 。使用getProperty方法提供并行
//强制要求为属性的键和值使用字符串。返回值是Hashtable调用put的结果
OutputStream out=new FileOutputStream(file);
pps.setProperty(key,value);
//以适合使用load方法加载到Properties表中的格式
//将此Properties表中的属性列表(键和值)写入输出流
pps.store(out,"Update useName passWord ");
out.close();
}
//读取properties的全部信息
public static void GetAllProperties(String path)throws IOException{
Properties pps=new Properties();
InputStream in=new BufferedInputStream(new FileInputStream(path));
pps.load(in);//所有的K-V对都加载
Enumeration en =pps.propertyNames();//得到配置文件
while(en.hasMoreElements()){
String strKey=(String) en.nextElement();
String strValue=pps.getProperty(strKey);
System.out.println("userName:"+strKey+"\tpassWord:"+strValue);
}
}
//根据key值获取value
public static String GetValueByKey(String path,String key){
Properties pps=new Properties();
try {
InputStream in = new BufferedInputStream(new FileInputStream(path));
pps.load(in);
String value=pps.getProperty(key);
return value;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
main方法
public static void main(String[] args) throws IOException {
System.out.println("写入Test.properties=======");
WritProperites("Test.properties","熊大","123456");
System.out.println("加载Test.properties========");
GetAllProperties("Test.properties");
System.out.println("从Test.properties加载================");
String value = GetValueByKey("Test.properties", "熊大");
System.out.println("熊大 is " + value);
}
• 总结
–HashMap是最常用的映射结构
–如需要排序,考虑LinkedHashMap和TreeMap
–如需要将K-V存储为文件,可采用Properties类
JCF的工具类
特点:不存储数据,而是在数据容器上,实现高效操作
排序,搜索
包含:Arrays类 Collections类
Arrays
Arrays:处理对象是数组
排序:对数组排序,sort/parallelSort
查找:从数组中查找一个元素,BinarySearch
批量拷贝:从源数组批量复制元素到目标数组,copyOf
批量赋值:对数组进行批量赋值,fill
等价性比较:判定两个数组内容是否相同,equals
Collections
Collections:处理对象是Collection及其子类
排序:对List进行排序,sort
搜索:从List中搜索元素,binarySearch
批量赋值:对List批量赋值,fill
最大、最小:查找集合中最大/最小值,max,min
反序:将List反序排列,reverse
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(12);
list.add(2);
list.add(19);
// 排序
Collections.sort(list);
// 检索
System.out.println("元素所在的索引值是:" + Collections.binarySearch(list, 12));
//最大最小
System.out.println("最大值:" + Collections.max(list));
System.out.println("最小值:" + Collections.min(list));
Collections.reverse(list); //翻转不需要用到排序
Collections.fill(list, 100); //全部赋值为100
对象比较
• 对象实现Comparable接口(需要修改对象类)
–compareTo方法
• > 返回1, ==返回0,<返回-1
–Arrays和Collections在进行对象sort时,自动调用该方法
public class Person implements Comparable<Person>{
String name;
int age;
public String getName() { return name;}
public int getAge() { return age;}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int compareTo(Person an){
int i=0;
i=name.compareTo(an.name);
if (i==0){
//如果名字一样,比较年龄,返回年龄的结果
return age-an.age;
}else{
return i;//名字一样返回i
}
}
public static void main(String[] args) {
Person[] ps=new Person[3];
ps[0]=new Person("tom",20);
ps[1]=new Person("mike",18);
ps[2]=new Person("mike",20);
Arrays.sort(ps);
for (Person p:ps) {
System.out.println(p.getName()+","+p.getAge());
}
}
}
• 新建Comparator(适用于对象类不可更改的情况)
–compare方法
• > 返回1, ==返回0,<返回-1
–Comparator比较器将作为参数提交给工具类的sort方法
public class Person2Comparator implements Comparator<Person2>{
@Override
public int compare(Person2 o1, Person2 o2) {
int i=0;
i=o1.getName().compareTo(o2.getName());
if (i==0){
//如果名字一样,比较年龄,返回比较年龄结果
return o1.getAge()-o2.getAge();
}else{
return i;//名字不一样,返回比较名字的结果
}
}
public static void main(String[] args) {
Person2[] ps=new Person2[3];
ps[0]=new Person2("tom",20);
ps[1]=new Person2("mike",18);
ps[2]=new Person2("mike",20);
Arrays.sort(ps,new Person2Comparator());//将person2comparator比较器作为参数提交给工具类的sort方法
for(Person2 p:ps){
System.out.println(p.getName()+","+p.getAge());
}
}
}
对象类
public class Person2 {
private String name;
private int age;
public String getName() { return name;}
public int getAge() { return age;}
public Person2(String name, int age) {
this.name = name;
this.age = age;
}
}
泛型
转载自Java3y泛型就这么简单:https://segmentfault.com/a/1190000014120746
1.什么是泛型?
反省设计原则:只要编译时期没有出现警告,那么运行时期就不会出现classCastException异常
泛型: 把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型
参数化类型:
把类型当作是参数一样传递。
<数据类型>只能是引用类型。
相关术语:
ArrayList<E>
中的E称为类型参数变量ArrayList<Integer>
中的Integer称为实际类型参数- 整个称为
ArrayList<E>
泛型类型 - 整个
ArrayList<Integer>
称为参数化的类型ParameterizedType
2.为什么需要泛型?
早期Java是使用Object来代表任意类型,但是向下转型有强转的问题,这样程序就不太安全
泛型的优点:
代码更加简洁【不用强制转换】
程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现classCastException异常】
可读性和稳定性【在编写集合时,就限定了类型】
3.泛型基础
3.1泛型类
泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来…这样的话,用户明确了什么类型,该类就代表着什么类型…用户在使用的时候就不用担心强转的问题,运行时转换异常的问题了。
- 在类上定义的泛型,在类的方法中也可以使用!
/*
1:把泛型定义在类上
2:类型变量定义在类上,方法中也可以使用
*/
public class ObjectTool<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
- 测试代码:
用户想要使用哪种类型,就在创建的时候指定类型。使用的时候,该类就会自动转换成用户想要使用的类型了。
public static void main(String[] args) {
//创建对象并指定元素类型
ObjectTool<String> tool = new ObjectTool<>();
tool.setObj(new String("钟福成"));
String s = tool.getObj();
System.out.println(s);
//创建对象并指定元素类型
ObjectTool<Integer> objectTool = new ObjectTool<>();
/**
* 如果我在这个对象里传入的是String类型的,它在编译时期就通过不了了.
*/
objectTool.setObj(10);
int i = objectTool.getObj();
System.out.println(i);
}
3.2泛型方法
前面已经介绍了泛型类了,在类上定义的泛型,在方法中也可以使用…
现在呢,我们可能就仅仅在某一个方法上需要使用泛型…外界仅仅是关心该方法,不关心类其他的属性…这样的话,我们在整个类上定义泛型,未免就有些大题小作了。
- 定义泛型方法…泛型是先定义后使用的
//定义泛型方法..
public <T> void show(T t) {
System.out.println(t);
}
- 测试代码:
用户传递进来的是什么类型,返回值就是什么类型了
public static void main(String[] args) {
//创建对象
ObjectTool tool = new ObjectTool();
//调用方法,传入的参数是什么类型,返回值就是什么类型
tool.show("hello");
tool.show(12);
tool.show(12.5);
}
3.3泛型类派生出的子类
前面我们已经定义了泛型类,泛型类是拥有泛型这个特性的类,它本质上还是一个Java类,那么它就可以被继承
那它是怎么被继承的呢??这里分两种情况
- 子类明确泛型类的类型参数变量
- 子类不明确泛型类的类型参数变量
3.3.1子类明确泛型类的类型参数变量
- 泛型接口
/*
把泛型定义在接口上
*/
public interface Inter<T> {
public abstract void show(T t);
}
- 实现泛型接口的类…
/**
* 子类明确泛型类的类型参数变量:
*/
public class InterImpl implements Inter<String> {
@Override
public void show(String s) {
System.out.println(s);
}
}
3.3.2子类不明确泛型类的类型参数变量
- 当子类不明确泛型类的类型参数变量时,外界使用子类的时候,也需要传递类型参数变量进来,在实现类上需要定义出类型参数变量
/**
* 子类不明确泛型类的类型参数变量:
* 实现类也要定义出<T>类型的
*
*/
public class InterImpl<T> implements Inter<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
测试代码:
public static void main(String[] args) {
//测试第一种情况
//Inter<String> i = new InterImpl();
//i.show("hello");
//第二种情况测试
Inter<String> ii = new InterImpl<>();
ii.show("100");
}
值得注意的是:
- 实现类的要是重写父类的方法,返回值的类型是要和父类一样的!
- 类上声明的泛形只对非静态成员有效
3.4类型通配符
为什么需要类型通配符????我们来看一个需求…
现在有个需求:方法接收一个集合参数,遍历集合并把集合元素打印出来,怎么办?
- 按照我们没有学习泛型之前,我们可能会这样做:
public void test(List list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
上面的代码是正确的,只不过在编译的时候会出现警告,说没有确定集合元素的类型…这样是不优雅的…
- 那我们学习了泛型了,现在要怎么做呢??有的人可能会这样做:
public void test(List<Object> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
这样做语法是没毛病的,但是这里十分值得注意的是:该test()方法只能遍历装载着Object的集合!!!
强调:泛型中的<Object>
并不是像以前那样有继承关系的,也就是说List<Object>
和List<String>
是毫无关系的!!!!
那现在咋办???我们是不清楚List集合装载的元素是什么类型的,List<Objcet>
这样是行不通的…于是Java泛型提供了类型通配符 ?
所以代码应该改成这样:
public void test(List<?> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
?号通配符表示可以匹配任意类型,任意的Java类都可以匹配…
现在非常值得注意的是,当我们使用?号通配符的时候:就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。
记住,只能调用与对象无关的方法,不能调用对象与类型有关的方法。因为直到外界使用才知道具体的类型是什么。也就是说,在上面的List集合,我是不能使用add()方法的。因为add()方法是把对象丢进集合中,而现在我是不知道对象的类型是什么。
3.4.1设定通配符上限
首先,我们来看一下设定通配符上限用在哪里…
现在,我想接收一个List集合,它只能操作数字类型的元素【Float、Integer、Double、Byte等数字类型都行】,怎么做???
我们学习了通配符,但是如果直接使用通配符的话,该集合就不是只能操作数字了。因此我们需要用到设定通配符上限
List<? extends Number>
上面的代码表示的是:List集合装载的元素只能是Number的子类或自身
public static void main(String[] args) {
//List集合装载的是Integer,可以调用该方法
List<Integer> integer = new ArrayList<>();
test(integer);
//List集合装载的是String,在编译时期就报错了
List<String> strings = new ArrayList<>();
test(strings);
}
public static void test(List<? extends Number> list) {
}
3.4.2设定通配符下限
既然上面我们已经说了如何设定通配符的上限,那么设定通配符的下限也不是陌生的事了。直接来看语法吧
//传递进来的只能是Type或Type的父类
<? super Type>
设定通配符的下限这并不少见,在TreeSet集合中就有…我们来看一下
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
那它有什么用呢??我们来想一下,当我们想要创建一个TreeSet<String>
类型的变量的时候,并传入一个可以比较String大小的Comparator。
那么这个Comparator的选择就有很多了,它可以是Comparator<String>
,还可以是类型参数是String的父类,比如说Comparator<Objcet>
…
这样做,就非常灵活了。也就是说,只要它能够比较字符串大小,就行了
经评论去补充:在泛型的上限和下限中有一个原则:PECS(Producer Extends Consumer Super)
书上是这样写的:
带有子类限定的可以从泛型读取【也就是—>(? extend T)】-------->Producer Extends
带有超类限定的可以从泛型写入【也就是—>(? super T)】-------->Consumer Super
也有相关博文写得很好:
http://blog.51cto.com/flyingc…
https://blog.youkuaiyun.com/xx32666…
3.5通配符和泛型方法
大多时候,我们都可以使用泛型方法来代替通配符的…
//使用通配符
public static void test(List<?> list) {
}
//使用泛型方法
public <T> void test2(List<T> t) {
}
上面这两个方法都是可以的…那么现在问题来了,我们使用通配符还是使用泛型方法呢??
原则:
- 如果参数之间的类型有依赖关系,或者返回值是与参数之间有依赖关系的。那么就使用泛型方法
- 如果没有依赖关系的,就使用通配符,通配符会灵活一些.
3.6泛型擦除
泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。
3.6.1兼容性
JDK5提出了泛型这个概念,但是JDK5以前是没有泛型的。也就是泛型是需要兼容JDK5以下的集合的。
当把带有泛型特性的集合赋值给老版本的集合时候,会把泛型给擦除了。
值得注意的是:它保留的就类型参数的上限。
List<String> list = new ArrayList<>();
//类型被擦除了,保留的是类型的上限,String的上限就是Object
List list1 = list;
如果我把没有类型参数的集合赋值给带有类型参数的集合赋值,这又会怎么样??
List list = new ArrayList();
List<String> list2 = list;
它也不会报错,仅仅是提示“未经检查的转换”
4、泛型的应用
当我们写网页的时候,常常会有多个DAO,我们要写每次都要写好几个DAO,这样会有点麻烦。
那么我们想要的效果是什么呢??只写一个抽象DAO,别的DAO只要继承该抽象DAO,就有对应的方法了。
要实现这样的效果,肯定是要用到泛型的。因为在抽象DAO中,是不可能知道哪一个DAO会继承它自己,所以是不知道其具体的类型的。而泛型就是在创建的时候才指定其具体的类型。
- 抽象DAO
public abstract class BaseDao<T> {
//模拟hibernate....
private Session session;
private Class clazz;
//哪个子类调的这个方法,得到的class就是子类处理的类型(非常重要)
public BaseDao(){
Class clazz = this.getClass(); //拿到的是子类
ParameterizedType pt = (ParameterizedType) clazz.getGenericSuperclass(); //BaseDao<Category>
clazz = (Class) pt.getActualTypeArguments()[0];
System.out.println(clazz);
}
public void add(T t){
session.save(t);
}
public T find(String id){
return (T) session.get(clazz, id);
}
public void update(T t){
session.update(t);
}
public void delete(String id){
T t = (T) session.get(clazz, id);
session.delete(t);
}
}
- 继承抽象DAO,该实现类就有对应的增删改查的方法了。
CategoryDao
public class CategoryDao extends BaseDao<Category> {
}
BookDao
public class BookDao extends BaseDao<Book> {
}