文章目录
一、集合概要
集合是用来存储对象的容器,和数组不同的地方在于集合只能存储引用类型数据(包括对象)。集合按照其存储结构,可以分为单列集合(java.util.Collection)和双列集合(java.util.Map)两大类,接下来学说一下Collection集合。
单列集合体系图如图所示:
二、集合API
2.1、Collection集合常用API
增加
----- add(E e) 添加成功返回true,添加 失败返回false.
----- addAll(Collection c) 把一个集合 的元素添加到另外一个集合中去。
删除
----- clear() 清空集合
----- remove(Object o); 删除集合中元素
----- removeAll(Collection c); //删除集合c1和集合c2的交集部分
----- retainAll(Collection c); 保留c1集合与c2集合的交集部分,删除两个集合中不相同的元素
例如:
public class test {
public static void main(String[] args) {
Collection c1 = new ArrayList();
c1.add("周杰伦");
c1.add("黄晓明");
c1.add("谢霆锋");
Collection c2 = new ArrayList();
c2.add("周杰伦");
c1.removeAll(c2); //删除集合c1和集合c2的交集部分
System.out.println(c1);
}
}
结果:[黄晓明, 谢霆锋]
public class test {
public static void main(String[] args) {
Collection c1 = new ArrayList();
c1.add("周杰伦");
c1.add("黄晓明");
c1.add("谢霆锋");
Collection c2 = new ArrayList();
c2.add("周杰伦");
c1.retainAll(c2); //保留c1集合与c2集合的交集部分
System.out.println(c1);
}
}
结果:[周杰伦]
查看
----- size(); 查看结合容量大小
判断
----- isEmpty(); 判断集合是否为空
----- contains(Object o);判断集合中是否包含元素
----- containsAll(Collection<?> c);判断集合中是否包含另一个集合
迭代
----- toArray(); 转化成数组迭代
----- iterator(); 使用迭代器迭代
----- 使用 增强for循环 进行迭代(底层使用的也是迭代器)
Iterator迭代器,是一个接口:迭代器(对集合进行遍历)
有两个常用方法:
boolean hasNext() 判断集合中有没有元素可以迭代,如果有则返回true。
E next() 返回迭代器下一个元素
迭代器使用步骤:
1、使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多台知识)
2、使用Iterator接口中的方法hasNext()判断还有没有下一个元素
3、使用Iterator接口中的方法next()取出下一个元素
方法一:
public class Demo08_iterator遍历集合元素 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("a");
c.add(true);
c.add("啦啦啦");
c.add(250);
Iterator it = c.iterator(); //获取一个迭代器
while(it.hasNext()){ //集合c1的迭代器it,通过hasNext()判断集合中是否有下一个元素,其中游标从0开始
Object o = it.next(); //因为没有学习泛型,返回值只能是Object
System.out.println(o);
}
}
}
结果:
a
true
啦啦啦
250
方法二:
//toArray 把集合中的所有元素存储到一个Object类型的数组返回
public class Demo09_toArray {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("你好啊");
c.add(250);
c.add(true);
c.add('男');
Object[] b = c.toArray(); //集合中的元素转化成数组
for (Object object : b) {
System.out.println(object);
}
}
}
结果:
你好啊
250
true
男
方法三:
/*JDK1.5特性
增强for循环底层也是使用迭代器,使用for循环的格式,简化了迭代器的书写
格式:
for (集合或数组的数据类型 变量名称: 集合名/数组名){
}
*/
public class test {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("周杰伦");
c.add("罗志祥");
c.add("成龙");
//1、如果不使用迭代器,那么需要先把集合转化成数组来遍历
Object[] o = c.toArray();
//2、使用增强for循环遍历
for (Object object : o) {
System.out.println(object);
}
}
}
结果:
周杰伦
罗志祥
成龙
2.2、List集合API
List是集合中的一个子接口。实现List接口的子类集合可以重复存取元素,且存、取元素按照一定顺序来,且存入集合中的元素有索引(下标)。因为List集合有索引,所以遍历也可以有索引。
List接口在继承了Collection接口的基础上,增加了自己特有的API,常用的如下:
------ void add(int index,E element); //指定位置添加元素,index是原色位置
------addAll(index,Collection); //在原集合指定位置追加集合
------ E remove(int index); //通过索引去删除
------ E get(int index); //得到集合中的一个元素
------ E set(int index,E element); //把集合中指定位置的元素修改
------indexOf(Object o); //获得某元素首次出现的索引位置
------lastIndexOf(Object o) //获取某元素最后一次出现的索引位置。
遍历:
listIterator() //list集合特有的迭代器
-------previous() //再向上移动,再获取移动后指针的值
-------next() //先获取当前指针的值,指针再向下移动。
-------add(); //在当前指针位置添加元素。
例如:E set(int index,E element);
public static void main(String[] args) {
List list = new ArrayList();
list.add("a");
list.add(true);
list.add(110);
list.add(3.14);
list.set(0,"666"); //证明集合的索引的序号也是从0开始
System.out.println(list);
}
结果:
[666, true, 110, 3.14]
例如:E get(int index);
public static void main(String[] args) {
List list1 = new ArrayList();
list1.add("a");
list1.add("b");
list1.add("c");
list1.add("d");
System.out.println("输入集合指定索引值:"+list1.get(2));
}
结果:
输入集合指定索引值:c
List四种遍历集合方法(有一种是List集合特有的)
public static void main(String[] args) {
List list = new ArrayList();
list.add("a");
list.add(true);
list.add(110);
list.add(3.14);
list.set(0,"666");
//方法一:
//for循环遍历(只有实现List接口的子类可以用get()方法)
for(int i=0; i<list.size(); i++){
Object o = list.get(i);
System.out.println(o);
}
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~");
//方法二:
//新型for遍历
for (Object object : list) {
System.out.println(object);
}
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~");
//方法三:一般Iterator遍历
Iterator it = list.iterator();
while(it.hasNext()){
Object o = it.next();
System.out.println(o);
}
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~");
//方法四:特有ListIterator遍历
ListIterator liIt = list.listIterator(); //获取List特有的迭代器
while(liIt.hasNext()){
Object o = liIt.next();
System.out.println(o);
}
}
List集合注意事项:
在迭代器迭代元素的时候,不允许使用集合对象改变集合中的元素个数,如果需要添加或者删除只能使用迭代器的方法进行操作。(也就是只要使用了it.next();之后不能使用 集合.add()或者 集合.remove()方法)
否则会报错 java.util.ConcurrentModificationException(并发修改异常)
java.util.ConcurrentModificationException报错案例:
public static void main(String[] args) {
List list = new ArrayList();
list.add("张三");
list.add("李四");
list.add("王五");
ListIterator liIt = list.listIterator();
while(liIt.hasNext()){
System.out.println(liIt.next());
list.add("成龙"); //正确的方法是迭代器的 liIt.add("成功");
list.remove(2);
}
}
结果:
Exception in thread "main" 张三
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at test.main(test.java:27)
2.3、ArrayList
ArrayList的底层是一个Object数组实现的,而数组的特点是内存地址是连续的,所有我们ArrayList可以使用get()方法通过数组指针快速查询到数据,但是增、删元素特别慢,比如增加元素,如果原来的数组长度不够,那么此时会先创建一个长度为:原数组长度+原数组0.5倍长度的数组,然后再把原数组的元素拷贝到现有的数组里面,如果涉及到很大数据的增、删,用ArrayList是非常慢的。删除元素也是一样的。
List(已经学习了List集合特有的方法) 如果实现了List接口集合类,具备有序,可重复特点
-
----|ArrayList (正要学ArrayList特有方法) 特点:查询速度快,插、删速度慢
-
----|LinkedList 底层是链表实现的,特点:查询速度慢,插入删除速度快
-
---- |Vector(了解即可) 底层也是维护了一个Object的数组实现的,实现与ArrayList是一样的,但是Vector是现成安全的,操作效率低
ArrayList特有方法:
-
ensureCapactity(int minCapacity) //该方法可以对ArrayList底层的数组进行扩容
ensureCapactity(int minCapacity) //该方法可以对ArrayList底层的数组进行扩容
在集合中,若参数值大于底层数组长度的1.5倍,则数组的长度就扩容为这个参数值;若小于底层数组长度的1.5倍,则数组长度就扩容为底层数组长度的1.5倍。若ArrayList中要添加大量元素,则使用ensureCapacity(int n)方法一次性增加,可优化运行效率。
例如:
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<Object>();
final int num =10000000;
//程序开始时间
long startTime = System.currentTimeMillis();
for(int i = 0;i<num;i++) {
list.add(i);
}
long endTime = System.currentTimeMillis();
//程序结束时间
System.out.print("扩容前程序执行时间:");
System.out.println(endTime-startTime+"ms");
list = new ArrayList<Object>();
long startTime1 = System.currentTimeMillis();
list.ensureCapacity(num); //ArrayList集合扩容
for(int i = 0;i<num;i++) {
list.add(i);
}
long endTime1 = System.currentTimeMillis();
System.out.print("扩容后程序执行时间:");
System.out.println(endTime1-startTime1+"ms");
}
结果:
扩容前程序执行时间:2882ms
扩容后程序执行时间:404ms
结论:
可看出使用ensureCapacity()方法大大增加运行效率,如果已经预知容器可能会装多少元素,最好显示的调用ensureCapacity这个方法一次性扩容到位。
*笔试题目:
使用ArrayList无参构造函数创建一个对象的时候,默认容量是多少?如果长度不够使用又自增长多少?
答:ArrayList底层是维护一个Object数组实现的,使用无参构造函数的时,默认容量是10,如果长度不够用时,自动增长0.5倍
2.4、LinkedList
由于LinkedList:在内存中的地址不连续,需要让上一个元素记住下一个元素,所以每个元素中保存的有下一个元素的位置.虽然也有角标,但是查找的时候,需要从头往下找,显然是没有数组查找快的。但是链表在插入新元素的时候,只需要让前一个元素记住新元素,让新元素记住下一个元素就可以了,所以插入很快。
由于链表实现,增加时只要让前一个元素记住自己就可以,,删除时让前一个元素记住后一个元素,,后一个元素记住前一个元素,这样的增删效率较高。但查询时需要一个一个的遍历, 所以效率较低。
LinkedList集合特有API:
addFirst(E e) //添加一个元素到集合头部
addLast(E e) //添加一个元素到集合尾部
getFirst() //获取头部第一个元素
getLast() //获取尾部第一个元素
removeFirst() //删除集合头部第一个元素
removeLast() //如果集合中没有元素,获取或者删除元素抛:NoSuchElementException
》如何使用方法我想大家都懂,就不在详细演示。
因为LinkedList同时实现了List和Deque接口,所以LinkedList还可以使用数据结构常用API:
数据结构
1:栈 (先进后出)
push(); //从集合头部追添加元素
pop(); //从集合头部删除元素
2:队列(双端队列1.5)
先进先出(按顺序)
offer(); //从集合尾部增加元素
poll(); //从集合头部删除元素
例如:
public static void main(String[] args) {
LinkedList list = new LinkedList();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.addLast("刘亦菲");
list.addFirst("成龙");
list.push("1-冷暴力"); //push到集合头部
list.push("2-合影全"); //push进集合的头部
System.out.println("push后的集合元素:"+list);
Object list2 = list.pop(); //从集合头部删除元素
System.out.println("pop的集合元素为:"+list2);
System.out.println("pop后的集合元素为:"+list);
}
结果:
push后的集合元素:[2-合影全, 1-冷暴力, 成龙, a, b, c, d, 刘亦菲]
pop的集合元素为:2-合影全
pop后的集合元素为:[1-冷暴力, 成龙, a, b, c, d, 刘亦菲]
2.5、Vector
Vector和ArrayList集合有些相似,底层都是通过数组实现。Vector是jdk1.0的时候出现的,ArrayList是JDK1.2出现,和ArrayList 集合区别在于:
ArrayList是线程不同步的,操作效率高。
Vector是线程同步的,操作效率低。
Vector特有API:
Vector也有属于自己的迭代器:Enumeration
例如:
public static void main(String[] args) {
Vector v = new Vector();
//添加元素
v.addElement("张三");
v.addElement("李四");
v.addElement("王五");
//迭代该集合
Enumeration e = v.elements(); //获取迭代器
while(e.hasMoreElements()){
System.out.println(e.nextElement());
}
}
结果:
张三
李四
王五
三、Set集合
set集合也继承与collection接口,其特点是:
- 存、取元素无序(内部无序,封装了HashMap。就是用HashMap的key位来存储值。)
- 存入的元素无索引
- 存入的元素不可重复
*--------|Set集合:
-
------| HashSet 底层是使用Hash表来支持的,特点:存取速度快
-
------| TreeSet 底层是使用二叉树实现,主要用于解决元素排序问题
3.1、HashSet
HashSet实现原理:
list集合存储对象都是有序放入的,而set集合存储元素都是通过Hash表计算存储的,当调用add方法的时候,set集合会先调用对象的hashCode方法(hashcode是对象的内存地址),通过移动等计算出hashCode值,然后存入Hash表格。如果当前表格元素没值,那么可以直接存表,如果有值,也就是如果两个对象hashcode一样,那么会调用最后一个对象的equals方法,两个值进行比较后, 如果返true,那么再把最后一个对象加到第一个对象上面(因为Hash表是桶式结构),如果返回false,那么最后一个元素会重新找个格子放进去。
根据原理,我们得出HashSet判断元素是否重复主要取决于HashCode();和equals();方法。下面我们通过例子说明:
学生类student
class student{
int id;
String name;
//构造器
public student(int id,String name){
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "{id=" + id + ", name=" + name + "}";
}
@Override
public int hashCode() {
System.out.println("===========hashCode===========");
return this.id;
}
@Override
public boolean equals(Object obj) {
System.out.println("===========equals===========");
student aa = (student)obj;
return this.id == aa.id;
}
}
创建一个HashSet集合,不断存入学生对象:
public static void main(String[] args) {
//创建HashSet集合,并存入学生
HashSet set = new HashSet();
set.add(new student(110,"张三")); //会调用四次hashCode方法
set.add(new student(111,"李四")); //会调用一次equals方法
set.add(new student(112,"王五"));
System.out.println("添加元素成功么?"+set.add(new student(110,"张三")));
System.out.println(set);
}
结果:
===========hashCode===========
===========hashCode===========
===========hashCode===========
===========hashCode===========
===========equals===========
添加元素成功么?false
[{id=112, name=王五}, {id=110, name=张三}, {id=111, name=李四}]
以上例子说明:当存入第一个对象的时候,jvm调用学生对象hashCode();方法,判断id是否相同,如果不同,则存入集合,如果相同则剔除。当存入id一致的学生对象时候,此时hashCode()一致,jvm会去调用equals()方法进行比较,因为我们重写的equals()方法比较的也是id,所以最后输出结果是调用四次hashcode()方法和一次equals()方法。
面试题
public static void main(String[] args) {
String str1 = "hello";
String str2 = new String("hello");
System.out.println("两个是同一个对象吗?"+(str1==str2));
System.out.println("str1的hashCode:"+ str1.hashCode());
System.out.println("str2的hashCode:"+ str2.hashCode());
/*
* HashCode默认情况下表示的是内存地址,String 类已经重写了Object的hashCode方法了。
注意: 在String中如果两个字符串的内容一致,那么返回的hashCode 码肯定也会一致的。
*
*/
}
结果:
两个是同一个对象吗?false
str1的hashCode:99162322
str2的hashCode:99162322
3.2、TreeSet
TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和指定排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
treeSet要注意的事项:
- 往TreeSet添加元素的时候,如果元素本身具备了自然顺序的特性,那么就按照元素自然顺序的特性进行排序存储。
- 往TreeSet添加元素的时候,如果元素本身不具备自然顺序的特性,那么该元素所属的类必须要实现Comparable接口,把元素的比较规则定义在compareTo(T o)方法上。
3.如果比较元素的时候,compareTo方法返回的是0,那么该元素就被视为重复元素,不允许添加(注意:TreeSet与HashCode、equals方法是没有任何关系的)
例如:具有自然特性的元素按自然顺序进行存储
public static void main(String[] args) {
TreeSet tree = new TreeSet();
/* tree.add(1);
tree.add(10);
tree.add(7);
tree.add(19);
tree.add(9);*/
tree.add('b');
tree.add('f');
tree.add('a');
tree.add('c');
System.out.println(tree);
}
结果:
[a, b, c, f]
如果元素本身不具备自然顺序的特性,那么有两种方法可以顺序存储元素
方法一:
对象所属的类必须要实现Comparable接口,把元素的比较规则定义在compareTo(T o)方法上。
例如:
class Emp implements Comparable{
int id;
String name;
int salary;
public Emp(int id, String name, int salary) {
super();
this.id = id;
this.name = name;
this.salary = salary;
}
@Override
public String toString() {
return "{ 编号:"+ this.id+" 姓名:"+ this.name+" 薪水:"+ this.salary+"}";
}
//@Override //元素与元素之间的比较规则。
// 负整数、零或正整数,根据此对象是小于、等于还是大于指定对象。
public int compareTo(Object o) {
Emp emp = (Emp)o;
System.out.println(this.name+"比较"+emp.name);
return this.salary- emp.salary;
}
}
添加Emp对象到TreeSet集合
public static void main(String[] args) {
TreeSet tree = new TreeSet();
tree.add(new Emp(110, "老陆", 100));
tree.add(new Emp(113, "老钟", 200)); //当插入第二个元素时候开始调用compareTo方法
tree.add(new Emp(220, "老汤", 300));
tree.add(new Emp(120, "老蔡", 500));
tree.add(new Emp(220, "老李", 500)); //元素相同,不插入(二叉树原理)
System.out.println("集合的元素:"+tree);
}
结果:
老陆比较老陆
老钟比较老陆
老汤比较老陆
老汤比较老钟
老蔡比较老钟
老蔡比较老汤
老李比较老钟
老李比较老汤
老李比较老蔡
集合的元素:[{ 编号:110 姓名:老陆 薪水:100}, { 编号:113 姓名:老钟 薪水:200}, { 编号:220 姓名:老汤 薪水:300}, { 编号:120 姓名:老蔡 薪水:500}]
方法二:
自定义一个类实现Comparator接口即可,把元素与元素之间的比较规则定义在compare方法内即可。
自定义比较器的格式:
class 类名 implements Comparator
例如:
emp类
class Eemp{
int id;
String name;
int salary;
public Eemp(int id, String name, int salary) {
super();
this.id = id;
this.name = name;
this.salary = salary;
}
@Override
public String toString() {
return "{ 编号:"+ this.id+" 姓名:"+ this.name+" 薪水:"+ this.salary+"}";
}
/*//@Override //元素与元素之间的比较规则。
// 负整数、零或正整数,根据此对象是小于、等于还是大于指定对象。
public int compareTo(Object o) {
Emp emp = (Emp)o;
System.out.println(this.name+"比较"+emp.name);
return this.salary- emp.salary;
}*/
}
自定义比较器:
//自定义一个比较器
class MyComparator implements Comparator{
@Override
//根据第一个参数小于、等于或大于第二个参数分别返回负整数、零、整数。
public int compare(Object o1, Object o2) {
Eemp e1 = (Eemp)o1;
Eemp e2 = (Eemp)o2;
return e1.id - e2.id;
}
}
往TreeSet里面存储元素:
public static void main(String[] args) {
//创建一个比较器对象
MyComparator comparator = new MyComparator();
//创建TreeSet的时候传入比较器
TreeSet tree = new TreeSet(comparator);
tree.add(new Eemp(110, "老陆", 100));
tree.add(new Eemp(113, "老钟", 200)); //当插入第二个元素时候开始调用compareTo方法
tree.add(new Eemp(220, "老汤", 300));
tree.add(new Eemp(120, "老蔡", 500));
tree.add(new Eemp(220, "老李", 500)); //元素相同,不插入(二叉树原理)
System.out.println("集合的元素:"+tree);
}
结果:
集合的元素:[{ 编号:110 姓名:老陆 薪水:100}, { 编号:113 姓名:老钟 薪水:200}, { 编号:120 姓名:老蔡 薪水:500}, { 编号:220 姓名:老汤 薪水:300}]
总结:
实现TreeSet自定义排序有两种方式:
1、类实现comparable接口;
2、实现comparator接口;
推荐使用comparator接口:因为comparable的比较规则只适用于一个类,但是comparator的比较规则适用于集合中所有元素。
四、泛型
泛型概述:
泛型代表可以接收任意引用类型数据类型的值,如果非要用一个类来代表泛型,那么这个类就是Object,所有的引用类型在Object中都能找到。
泛型好处:
1、将运行期的错误转换到编译期:意思也就是说没有泛型之前,可以add任意类型的值,编译器不会报错,程序编译可以通过,但是运行会报错。泛型就是明确add的类型,如果add其他类型的时候就提示报错然后遍历的时候不用强转也能输出。
==2、避免了无谓的类型强转的问题:==有了泛型之后,就明确了add进入集合的对象,在遍历的时候不需要进行类型强转也可以直接通过对象得到自己类中的属性值。
泛型基本使用:
<> 中放的必须是引用数据类型 (指定对象类型)
泛型使用注意事项:
前后的泛型必须一致,或者后面的泛型可以省略不写(1.7的新特性菱形泛型)
ArrayList<String> list = new ArrayList<String>(); true
ArrayList<Object> list = new ArrayList<String>(); false
ArrayList<String> list = new ArrayList<Object>(); false
ArrayList<String> list = new ArrayList(); false
泛型最好不要定义成Object,没有意义
泛型例子:
public static void main(String[] args) {
// ArrayList<Emp> list = new ArrayList<>(); 菱形泛型,JDK1.7新特性
ArrayList<Emp> list = new ArrayList<Emp>();
list.add(new Emp(110,"张三"));
list.add(new Emp(111,"李四"));
list.add(new Emp(112,"王五"));
list.add(new Emp(113,"赵六"));
Iterator<Emp> it = list.iterator();
while(it.hasNext()){
Emp o = it.next(); //泛型之后不用进行强转
System.out.print("{工号:"+o.getId()+",姓名:"+o.getName()+"}");
}
}
4.1、方法泛型
需求:
1、定义一个方法可以接收任意类型的参数
2、而且该方法返回值类型必须要与实参的类型一致
3、不能使用Object类型(因为Object类型需要进行类型强转)
如果想要满足以上需求,需要使用泛型方法
方法上自定义泛型格式:
修饰符 <声明自定义泛型>返回值类型 函数名(使用自定义泛型 e){
}
泛型方法注意事项:
1、在方法上自定义泛型,这个自定义泛型的具体数据类型是在调用该方法的时候传入实参时确定具体的数据类型的。
2、自定义泛型只要符合标识符的命名规则即可,自定义泛型我们一般使用一个大写字母表示,一般使用T或者E。
public static void main(String[] args) {
String str = getData("abc"); //泛型之后不用强转
Integer i = getData(120); //泛型返回值类型都是对象,所以要用Integer来接收
}
//方法泛型:泛型方法常常用来接口或者抽象类中
public static <T>T getData(T o){ //其中<T>代表声明,T是返回值类型变量,入参与返回值类型一致
return o;
}
4.2、类泛型
泛型类的使用很广,集合中的类都用到了泛型类,所以才会出现创建集合的时候要指定类型。如果一个类中的方法大部分是泛型,且在类中定法方法时候又不想给返回值增加泛型,那么可以使用泛型类
泛型类格式:
class 类型<声明自定义的泛型>{
}
泛型类自定义事项:
1、在类上自定义泛型具体数据类型是在使用该类的时候创建对象时候确定的。
2、如果一个类在类型上已经声明了自定义泛型,如果使用该类创建对象的时候没有指定泛型的具体数据类型,那么默认为是Object类型。
3、泛型类中泛型不能用于类中的静态方法,如果静态方法需要使用自定义泛型,那么需要在方法上自己声明使用。因为在类上的泛型是在创建对象时候确定的,属非静态,而静态方法随类的加载而存在,所以不能试用于静态方法。
4、泛型方法有个弊端,就是当想要往这个类里面添加另外一种类型,需要重新创建对象,并指定类型
案例
泛型工具类MyArray_Tool
class MyArray_Tool<T>{
//元素翻转
public void reverse(T[] arr){
for(int startIndex=0, endIndex=arr.length-1; startIndex < endIndex; startIndex++,endIndex--){
T temp = arr[startIndex];
arr[startIndex] = arr[endIndex];
arr[endIndex] = temp;
}
}
public <T>String toString(T[] arr){ //类泛型之后,非静态方法不必自定义泛型
StringBuilder sb = new StringBuilder(); //定义一个Bulider
for(int i=0; i < arr.length; i++){
if(i == 0){
sb.append("["+arr[i]+",");
}else if(i == arr.length-1){
sb.append(arr[i]+"]");
}else{
sb.append(arr[i]+",");
}
}
return sb.toString();
}
//静态方法自己声明泛型,静态方法确定类型是在传入参数时候确定的
public static <T>void print(T[] t){
}
//泛型类中可以有非泛型的方法
public int getInt(int i){
return i+1;
}
}
mian方法:
public static void main(String[] args) {
//定义一个数组,注意:泛型里面不能有基本类型,否则不能将arr当参数传入方法reverse();
Integer[] arr = {10,12,14,19};
//创建数组工具对象
MyArray_Tool<Integer> tool = new MyArray_Tool<Integer>();
tool.reverse(arr);
System.out.println(tool.toString(arr));
//创建数组工具对象
String[] arr2 = {"aaa","ccc","ddd"};
MyArray_Tool<String> tool2 = new MyArray_Tool<String>();
tool2.reverse(arr2);
System.out.println(tool2.toString(arr2));
System.out.println(tool2.getInt(10)); //泛型类里面可以有非泛型的方法
//创建数组工具对象
String[] arr3 = {"aaa","ccc","ddd"};
MyArray_Tool tool3 = new MyArray_Tool(); //如果不指定泛型的数据类型,默认为Object类型
}
结果:
[19,14,12,10]
[ddd,ccc,aaa]
11
4.3、接口泛型
需求:目前我实现一个接口的时候,我还不明确目前操作的类型,我要等待创建接口实现类对象的时候我才能指定泛型的具体数据类型。
泛型接口的格式:
interface 接口名<声明自定义泛型>
泛型接口注意事项:
1、接口上自定义泛型的具体数据类型是在实现一个接口的时候指定的。
2、在接口上自定义的泛型如果在实现接口的时候没有指定具体的数据类型,默认是Object类型
例如:
interface Dao<T>{ //泛型接口
public void add(T t);
public T show(T t);
}
public class Demo implements Dao<String>{
@Override
public void add(String t) { //这里一定是String类型,因为接口指定是String类型。
//如果接口不指定为String,那么该默认是Object类型
}
@Override
public String show(String t) {
return null;
}
}
泛型接口用的地方很多,比如comparable和comparator接口就是泛型接口。
4.4、泛型上、下限
需求:
1、定义一个函数可以接收任意类型的集合对象
2、要求接收的集合对象只能存储Number或者是Number子类类型的数据。(Integer的父类有Number,然后是Object)
泛型中的通配符:?
可以匹配集合中的任意类型
泛型上、下限
? super Integer : 只能存储Integer或者是Integer父类类型元素。 泛型的下限
? extends Number: 只能存储Number或者是Number类型的子类数据。 泛型的上限
public static void main(String[] args) {
//定义三个集合,一个的Integer,一个是Interger的父类型,一个是String类型
ArrayList<Integer> list1 = new ArrayList<Integer>();
ArrayList<Number> list2 = new ArrayList<Number>();
ArrayList<String> list3 = new ArrayList<String>();
print(list1); //true
print(list2); //true
// print(list3); //false
getData(list1); //true
// getData(list2); //false
// getData(list3); //false
}
//泛型的下限
//要求入参类型要么是Integer,要么是Integer的父类型
public static void print(Collection<? super Integer> c){
}
//泛型的上限
//要求入参类型要么是Integer,要么是Integer的子类型
public static void getData(Collection<? extends Integer> c){
}
(1)为什么要使用泛型方法呢?
因为泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新new一次,可能不够灵活;而泛>型方法可以在调用的时候指明类型,更加灵活。
(2)为什么使用泛型类呢?
使用泛型类可以解决重复业务的代码的复用问题,也就是业务颗粒的复用,同时使用泛型类型在编译阶段就可以确定,并发现错误,类型的转换都是自动或隐式的,提高了代码的准确率和复用率。
泛型方法和普通方法的区别
1、泛型方法可以不指定返回类型和入参数类型,调用方法的时候传入什么参数类型,返回什么参数类型,避免了出现返回值类型强转的情况。
2、方法标准化
五、集合工具类Collections
集合和集合工具类是不同的。其中Collections是工具类,Collection是集合
Collection:常见方法:
1、对Lish集合进行二分查找:(前提是集合中的数据是有序的)
int binarySerach(list,key);
int binarySerach(list,key,Comparator);
2、读集合进行排序。
sort(list);
sort(list,comparator); //按照指定比较器进行排序
3、对集合取最大值或者最小值
max(Collection)
max(Collection,comparator);
min(Collection)
min(Collection,comparator);
4、对List集合进行反转
reverse(List);
5、可以将不同步的集合变成同步的集合
Set synchronizedSet(Set<T> s)
Map synchronizedMap(Map<K,v> m)
List synchronizedMap(List<T> list)
方法使用如下:
coo类
class COO{
String name;
int age;
public COO(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "COO [name=" + name + ", age=" + age + "]";
}
}
自定义比较器
//自定义比较器
class AgeComparator implements Comparator<COO>{
@Override
public int compare(COO o1, COO o2) {
return o1.age - o2.age;
}
}
mian方法:
public static void main(String[] args) {
//具备自然顺序的元素排序
ArrayList<Character> list = new ArrayList<Character>();
list.add('b');
list.add('c');
list.add('d');
list.add('a');
Collections.sort(list);
System.out.println(list);
//不具备自然顺序的元素排序
ArrayList<COO> list2 = new ArrayList<COO>();
list2.add(new COO("张三",24));
list2.add(new COO("李四",28));
list2.add(new COO("王五",41));
list2.add(new COO("赵六",12));
Collections.sort(list2,new AgeComparator());
System.out.println(list2);
//最大最小值
System.out.println("最大值:"+Collections.max(list));
System.out.println("最大值:"+Collections.max(list2,new AgeComparator()));
System.out.println("最小值:"+Collections.min(list));
System.out.println("最大值:"+Collections.min(list2,new AgeComparator()));
//翻转:因为翻转不需要比较过程,所以不用传比较器
Collections.reverse(list);
Collections.reverse(list2);
// 5、可以将不同步的集合变成同步的集合(如果多个线程操作集合对象,我们也不必要使用vetor,只要使用下面方法就可以)
// Set synchronizedSet(Set<T> s)
// Map synchronizedMap(Map<K,v> m)
// List synchronizedMap(List<T> list)
//不单是list集合可以,其他集合也可以变成线程安全的。此类方法一般用户增、删频率很高的集合
list = (ArrayList<Character>) Collections.synchronizedList(list);
}
总结完这篇文章,腰疼,疼,很疼。我先休息下,链表、哈希表,二叉树等原理会稍后做补充。其他不足的地方铁汁们评论区留言,我会及时补充。