一、泛型方法
1.什么是泛型方法
语法格式:
【修饰符】<泛型的类型> 返回值类型 方法名(形参列表)[throws异常列表]{}
例如:public void method (T t){
}
3,为什么有泛型方法
(1)对于静态方法来说那我们之前声明的<泛型>是不能用于静态成员,所以如果某个静态方法需要用到泛型,那么可以考虑使用泛型方法
(2)类或者接口已经声明,不是泛型类后新增的方法可以使用泛型
不管是(1)还是(2)都有一个原因,因为在设计这个方法的时候,可能它的形参类型或返回值类型未知。这个时候考虑使用泛型方法
4.注意
(1)泛型方法的<泛型类型形参列表>同样可以声明上限
<T extends 上限1 & 上限2 …>
上限中类只能有一个,必须在左边,接口可以有多个
多个上限之间是&的关系
(2)泛型方法的<泛型类型形参列表>必须写在返回值类型前面,修饰符的后面
(3)泛型方法的泛型类型不用手动指定,会根据实参的类型自动推断形参的类型。
二、泛型通配符
1.长什么样子
(1)<?>: ?代表任意类型
(2)<? extends xx>
(3)<? super xx>
2、作用
(1)声明一个变量时,该变量的类型如果是一个泛型类或泛型接口,呢么可以加通配符,例如:Student 类
声明变量student<?> stu;
(2)声明一个形参时,该形参的类型如果是一个泛型类或泛型接口,那么可以加通配符,
例如:public [static ] void method(Student <?> stu){}
(3)注意;不能用在声明泛型的位置
public class MyClass <?>{}
public static <?> void method(){}
3、为什么要有通配符?
(1)我们在使用泛型类或泛型接口时,除非泛型被擦除了,否则你指定了什么类型,就必须是什么类型。
如果希望是任意类型,又不想泛型擦除,只能用<?>。
public class TestWildCard {
//需求一:声明一个方法,该方法,可以接受任意一种学生对象,至于方法的功能我们暂时先不管
/*public void method1(Student stu){//写法一:可以,但是没有指定泛型,相当于泛型擦除
//...
}*/
public void method2(Student<Object> stu){//写法二:有问题,只能接收new Student<Object>();
//...
}
public void method3(Student<?> stu){//写法三:可以,这个?代表的就是任意类型
//...
}
@Test
public void test01(){
method2(new Student<Object>());
// method2(new Student<String>());//错误的
// method2(new Student<Integer>());//错误的
}
@Test
public void test02(){
method3(new Student<Object>());
method3(new Student<String>());
method3(new Student<Integer>());
}
//需求2:用泛型类Student,声明一个数组,该数组中可以存储任意学生对象
@Test
public void test03(){
// Student<Object>[] arr = new Student[3];
Student<?>[] arr = new Student[3];
arr[0] = new Student<Object>();
arr[1] = new Student<String>();
arr[2] = new Student<Integer>();
}
}
class Student<T>{
private String name;
private T score;
public Student(String name, T score) {
this.name = name;
this.score = score;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public T getScore() {
return score;
}
public void setScore(T score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
泛型通配符<?>等形式有局限性:
如果某个泛型类或泛型接口使用时,用到<?>等形式,该类型对应的变量修改值就受到了限制。
三、集合
1.容器
java中容器:数组(可以装各种基本数据类型的值和对象)、
集合:可以装各种对象,不能装基本数据类型的值
分好多中:列表list,集:set,队列Queue:,映射:Map…
2.有数组还要有集合?
(1)数组:
优点:速度快,可与根据下标定位元素
缺点:长度是固定的,
数组要求开辟一整块连续的空间GC回收导致性能降低
(2)集合
优点:类型很多,表示我们的可选性多,可以根据需求选择合适的集合;集合的底层可以自动扩容
缺点:类型很多,需要了解每一种类型的特点,好选择
3、集合的分类
(1)collection系列:包括列表List,集Set,队列Queue等
存储一组对象,比喻:party
(2)Map系列
存储键值对(映射关系),(key,value),比喻
3.1collection
1.java.util.collection接口
(1)层次结构中的跟接口
(2)表示一组对象
(3)一些collection允许有重复的元素,另一些不允许
一些有序,一些无序。
(4)没有类能直接实现collection接口,可以实现子接口(list、Set)
(5)此接口通常用来传递collection ,并在需要最大普遍性的地方操作这些collection.
即如果我需要List,Set等集合之间转换的时候,可以使用Collection作为桥梁进行传递,说白了就是多态引用
2.collection的常用方法
他下面的子接口的实现类都有该接口的方法
代表了集合的通用操作:增、查、删除等操作
Collection泛型接口
之前用T,代表Type,通用类型
现在用E代表元素的类型,
(1)添加
一次添加一个元素
boolean add(E e) 一次添加多个元素
boolean addAll(Collection<? extends E> c) :一次添加多个元素
<? extends E>:当前集合的元素是E,c集合的元素类型要求是E或E的子类对象。 ###### (2)删除元素 void clear() : 清空 boolean remove(Object o):删除一个元素 用Object的原因,早期没有泛型,后期有了之后没改,添加的时候要检查类型是否符合,删除时不用。 boolean removeAll(Collection <?> c): this当前集合 = this当前集合 - this当前集合 ∩ c集合 <?>表示任意类型(3)修改:没有提供修改元素的方法
(4)查询
int size() 获取元素的个数
boolean isEmpty() : 判断是否是空集合
boolean contains(Object o):是否包含某个对象、
boolean containsAll(Collection<?> c):是否包含c集合的所有元素
返回true,c是this集合的子集
5.其他
boolean retainAll(Collection<?> c)保留this集合和c集合的交集,即this集合 = this集合 ∩ c集合、
Object[] toArray() :把集合中的元素放到一个Object数组中返回
T[] toArray(T[] a):把集合的元素放到a数组中,但是a数组的类型没有检查是否符合当前集合的元素E类型。用的时候谨慎。
public void test01(){
// Collection coll = new Collection() ;//错误,接口不能直接new对象
Collection<String> coll = new ArrayList<>() ;//ArrayList是List子接口的实现类,它也是Collection系列。
//多态引用,coll编译期间只能看Collection里面的方法,关注点更集中。
//<String>代表集合的元素类型是String
coll.add("hello");
coll.add("world");
coll.add("java");
System.out.println(coll);//说明ArrayList实现类重写类toString方法
}
二、集合的便利
1、Collection 系列的集合有一个方法:
Iteratoriterator()
1、Collection系列的集合支持foreach便利
foreach:称为增强for循环,比普通的for循环简洁,功能更强大,可以直接遍历数组、Collection系列的集合等等,不需要下标等信息。
语法格式:
for(元素的类型 元素名 : 数组/集合的名称){
}
注意:元素名是自己命名的,只是一个临时名称,在当前for循环中使用的临时变量。
问题1:foreach是如何工作的?
foreach其实本质上就是Iterator迭代器。
问题2:数组为什么支持呢?
数组这个类型是编译器动态生成的类型,它在生成的过程中,也会提供迭代器的实现。
问题3:是否所有的集合,或者容器都支持foreach迭代器呢?
不是。
只有实现了java.lang.Iterable接口。
实现这个Iterable接口允许对象成为 “foreach” 语句的目标。
因为Iterable接口的抽象方法是 Iterator iterator() 。如果要实现这个方法,就要手动实现迭代器,为foreach的运行提供基础。
问题4:在foreach遍历集合的过程中,能不能调用集合的remove方法呢?
不能
结论:根据条件删除元素,只有一种方式,用Iterator迭代,并且用 Iterator迭代器的remove方法。
如果精确删除,就直接调用集合remove方法即可。
Collection系列的集合有一个方法:
itertor()
java.util.Iterator迭代接口,有三个方法:
(1)boolean hasNext():判断是否还有下一个元素
(2)E next(): 取出下一个元素
(3)void remove():删除刚刚迭代的元素
Collection的remove适合用于精确删除
迭代器中的无法精确的说明元素时用迭代器的remove方法
public class TestIterator {
@Test
public void test03() {
Collection<String> coll = new ArrayList<>();
coll.add("hello");
coll.add("world");
coll.add("java");
coll.add("java");
Iterator<String> iterator = coll.iterator();
while(iterator.hasNext()) {
String element = iterator.next();
if (element != null && element.startsWith("j")) {
coll.remove(element);
//报ConcurrentModificationException:并发修改异常。 两条线路在同时访问与修改集合,一个是iterator,一个是coll
//你的coll的remove方法删除了元素之后,没有把最新的信息告知iterator
}
}
System.out.println(coll);
}
@Test
public void test02(){
Collection<String> coll = new ArrayList<>();
coll.add("hello");
coll.add("world");
coll.add("java");
//需求:删除以w开头的字符串
/*
思考:用Collection接口的remove方法能否完成?
*/
// coll.remove();//无法完成,即Collection的remove适用于精确删除
//使用Iterator迭代器删除,标准写法
Iterator<String> iterator = coll.iterator();
while(iterator.hasNext()){
String element = iterator.next();
if(element!=null && element.startsWith("w")){
iterator.remove();
}
}
}
@Test
public void test01(){
Collection<String> coll = new ArrayList<>();
coll.add("hello");
coll.add("world");
coll.add("java");
Iterator<String> iterator = coll.iterator();
/* System.out.println(iterator.next());
System.out.println(iterator.next());
System.out.println(iterator.next());
System.out.println(iterator.next());//NoSuchElementException:没有这个元素了*/
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
四、Set接口
1.java.util.Set
一个不包含重复元素的collection,对数学中的集的抽象
2.Set时Collection子接口
,但是Set接口没有增加自己的方法和Collection的方法一样
3.Set接口有一些实现类:
(1)HashSet:
不能重复,无序(和添加顺序不一致)
根据hashCode和equals方法。
hashCode值,影响他的储存位置
(2)TreeSet:
元素也不能重复。
元素有大小顺序的,说明元素要支持比较大小,要求元素必须实现java.lang.Comparable接口。
如果元素没有实现Comparable接口,或者实现这个接口的比较规则不适合当前的需求,就要借助java.util.Comparator。
大小顺序和不能重复依据的是什么?
大小顺序和Comparable的compareTo或Comparator的compare方法有关;
不可重复也是和Comparable的compareTo或Comparator的compare方法有关。
Comparable的compareTo或Comparator的compare方法返回0,就认为是“重复”元素。
TreeSet底层用红黑树(是一种自平衡的二叉树)实现。
3、LinkedHashSet
元素也不能重复。
元素有顺序,遍历结果是按照添加的顺序遍历的。
底层实现:哈希表 + 链表。 链表的作用是记录添加的顺序。
什么时候用LinkedHashSet?
当我们需要元素不可重复,但是又要记录添加顺序时用它。
LinkedHashSet比HashSet效率低,因为它在添加和删除等过程中,要考虑顺序的问题。
五、List集合
1、java.util.List接口:结合列表,也称为序列。
有序的collection(也称为序列)
此接口的用户可以队列表中每个元素的插入位置进行精确地控制。用户根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。 == >List的很多操作和【index】有关与set不用,列表通常允许重复的元素。
List:有序可重复。
2、List的方法,在Collection的基础上增加了很多方法都和index有关
(1)添加
void add(int index, E element):在[index]位置添加一个元素
boolean addAll(int index,Collection<? extends E> c)在[index]位置添加多个元素
(2)删除
E remove(int index)
(3)修改
E set(int index , E element);替换[index]位置的元素
(4)查询
E get(int index); 获取[index]位置的元素
int indexOf(Object o ):查询o对象在当前List中的下标,如果没有返回-1,如果多个返回第一个
int lastIndexOf(Object o):查询o对象在当前List中的小标,如果没有返回-1,如果有多个,返回最后一个
List subList(int fromIndex, int toIndex):截取一段列表
(5)遍历
ListIterator listIerator()
ListItertor listIterator(int index)
ListIterator是Itertor的子接口比Itertor多了几个功能:
倒序遍历、获取下表信息、遍历过程中添加和替换元素
public class TestList {
@Test
public void test13() {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
list.add("xxx");
ListIterator<String> iter = list.listIterator();
while(iter.hasNext()){
String element = iter.next();
if(element.startsWith("w")){
iter.set("atguigu");
}
}
System.out.println(list);//[hello, java, atguigu, xxx]
}
@Test
public void test12() {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
list.add("xxx");
ListIterator<String> iter = list.listIterator();
while(iter.hasNext()){
String element = iter.next();
if("world".equals(element)){
iter.add("atguigu");//遍历过程中插入元素
}
}
System.out.println(list);//[hello, java, world, atguigu, xxx]
}
@Test
public void test11() {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
list.add("xxx");
//倒序遍历
ListIterator<String> iter = list.listIterator(list.size());//游标默认在列表的结尾
while(iter.hasPrevious()){
System.out.println(iter.previousIndex() + ":" + iter.previous());
}
}
@Test
public void test10() {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
list.add("xxx");
ListIterator<String> iter = list.listIterator();//游标默认在列表的开头
while(iter.hasNext()){
//遍历过程中获取元素的下标
System.out.println(iter.nextIndex() + ":" + iter.next());
}
}
@Test
public void test09() {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
list.add("xxx");
ListIterator<String> iter = list.listIterator();
while(iter.hasNext()){
System.out.println(iter.next());
}
}
@Test
public void test08() {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
list.add("xxx");
List<String> subList = list.subList(1, 3);
System.out.println(subList);//[java, world]
}
@Test
public void test07() {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.set(0,"atguigu");
System.out.println(list);//[atguigu, java]
}
@Test
public void test06() {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
//删除元素值是1
// list.remove((Object)1);
// Collection<Integer> coll = list;
// coll.remove(1);
list.remove(Integer.valueOf(1));
System.out.println(list);//[2, 3, 4, 5]
}
@Test
public void test05() {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.remove(1);//删除[1]位置的元素
System.out.println(list);//[1, 3, 4, 5]
}
@Test
public void test04() {
List<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);
list.add(40);
list.add(50);
list.remove(1);
System.out.println(list);//[10, 30, 40, 50]
}
@Test
public void test03() {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.remove(0);
System.out.println(list);
}
@Test
public void test02() {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
List<String> list2 = new ArrayList<>();
list2.add("chai");
list2.add("xxx");
list.addAll(1, list2);
System.out.println(list);//[hello, chai, xxx, java]
}
@Test
public void test01(){
List<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add(0,"atguigu");//[atguigu, hello, java]
list.add(5,"chai");//IndexOutOfBoundsException: Index: 5, Size: 3
//我们之前见过的ArrayIndexOutOfBoundsException是IndexOutOfBoundsException的子类
System.out.println(list);
}
}
3、List接口的实现类们
(1)ArrayList:动态数组
底层实现:数组
(2)Vector:动态数组
底层实现:数组
(3)LinkdList:双向链表
(4)Stack: 栈
底层实现:数组,因为他是Vector的子类
问题1:ArrayList与Vector的区别?
Vector是最古老的动态数组,比Collection和List接口还要早。线程安全。
Vector在扩容时,默认扩容为2倍。(扩容的频率第,浪费空间的可能性增加)
Vector的默认初始化容量是10。
ArrayList是比Vector新一点。线程不安全。
ArrayList在扩容时,默认扩容为1.5倍。(扩容的频率高,空间利用率高)
ArrayList的默认初始化容量,在JDK1.7之前也是10,在JDK1.7之后,一开始是0,第一次添加元素时初始化为10.
无论是Vector还是ArrayList使用时,如果对元素的个数有预估,最好使用:
ArrayList(int initialCapacity):一开始就指定初始化容量,就避免扩容。
Vector(int initialCapacity) :一开始就指定初始化容量,就避免扩容。
Vector(int initialCapacity, int capacityIncrement):除了指定初始化容量,还可以指定每次增加几个元素。
问题2:ArrayList从JDK1.7之后为什么默认初始化容量是0?
我们很多方法的返回值类型是List或ArrayList,特别是DAO(数据库层)方法。
这些方法在实现时,如果没有结果返回,一般不返回null。因为返回null,调用者就必须加if(xx != null)避免空指针,太麻烦。
不返回null,但是又没有数据,就返回了一个空的ArrayList对象。
那么如果初始化容量是10的话,其实这10个空间是浪费的。所以,就初始化容量为0。
问题3:动态数组与双向链表有什么区别?
区别一:
动态数组的元素:数据本身
双向链表的元素:结点
结点包含:
①前一个结点的首地址:prev
②数据本身
③后一个结点的首地址:next
区别二:
动态数组的元素是连续存储,提前开辟一整块连续的内存空间。
双向链表的元素不要求连续存储,来一个申请一个连接一个。
连续的好处:查询快,可以根据数组的首地址,快速的定位到某个元素。
不连续的缺点:只能从头或尾挨个查找,定位到对应的位置。
连续的坏处:插入,删除时,需要移动元素,插入时可能考虑扩容。
不连续的好处:不用提前申请空间,插入和删除只需要修改前后元素的prev和next
问题4:Stack
它是Vector的子类。
在Vector的基础上增加了几个方法:
①E peek():查看栈顶元素
②E pop():返回栈顶元素,栈顶换元素
③ E push(E item):把元素压入栈称为新的栈顶元素
…
栈的结构特点:后进先出(LIFO)或先进后出(FILO)
LIFO:Last In First Out
FILO:First In Last Out
像桶,箱子
| | 栈顶
| |
| |
|______ | 栈底
现在不太建议我们用Stack,如果要用栈结构,建议用LinkedList。因为LinkedList也提供了这些方法,使我们实现栈的效果。
问题5:LinkedList说起来是双向链表,但是它又是Queue,又是Deque,还可以用作栈等。
名称 deque 是“double ended queue(双端队列)”的缩写,通常读为“deck”。
队列结构特点:先进先出(FIFO)