4.1 泛型
4.1.1 概念
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable{}
public interface Deque<E> extends Queue<E> {}
public interface Queue<E> extends Collection<E> {}
public interface Collection<E> extends Iterable<E> {}
我们上面的代码中出现的<?>是什么东西呢 它叫泛型,常用来和集合对象一同使用,所以我们在开始学习集合之前,必须先了解下什么是泛型。而且泛型概念非常重要,它是程序的增强器,它是目前主流的开发方式。
泛型是(Generics)是JDK1.5 的一个新特性,其实就是一个『语法糖』,本质上就是编译器为了提供更好的可读性而提供的一种小手段,小技巧,虚拟机层面是不存在所谓『泛型』的概念的。
4.1.2 作用
l 通过泛型的语法定义,约束集合元素的类型,进行安全检查,把错误显示在编译期
l 代码通用性更强,后面有案例
l 泛型可以提升程序代码的可读性,但它只是一个语法糖(编译后这样的东西就被删除,不出现在最终的源代码中),对于JVM运行时的性能是没有任何影响的。
4.1.3 泛型示例
我们创建一个ArrayList,上面看到eclipse提示有个黄线,什么意思呢?
ArrayList is a raw type. References to generic type ArrayList<E> should be parameterized.
ArrayList使用了泛型,在声明时需指定具体的类型<E>。
那我们把这个<>里的方式就称为泛型。上面的泛型有什么作用呢?就是在编译阶段就检查我们传入的参数类型是否正确。
有了泛型,我们可以看到人家要求存放String,而我故意存放的整数100,所以eclipse提示我们错误:
The method add(int, String) in the type List<String> is not applicable for the arguments (int)。
类型List<String>的add方法要求增加的类型为String类型,不正确不能存入。
4.1.4 泛型声明
泛型可以在接口、方法、返回值上使用:
java.util.List泛型接口/类:
public interface Collection<E> {}
泛型方法的声明:
public <E> void print(E e) {}
在方法返回值前声明了一个<E>表示后面出现的E是泛型,而不是普通的java变量。
4.1.5 常用名称
l E - Element (在集合中使用,因为集合中存放的是元素)
l T - Type(Java 类)
l K - Key(键)
l V - Value(值)
l N - Number(数值类型)
l ? - 表示不确定的java类型
4.1.6 用途:编译时类型检查
package seday12new;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test1 {
public static void main(String[] args) {
int[] a = new int[3];
a[0]=1;
a[1]=2;
//int类型的数组,规定了数组里的数据类型,类型不对就报错。
// a[2]="hello";
//1,泛型的标志<>
//2,泛型的好处:规定了数据的类型,不能想放什么数据就放什么类型,要遵守泛型规定的类型
//3,泛型的数据类型只能是引用类型,不能是基本类型
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
//4,如果类型不对,把运行时期才会 报的错ClassCastException直接在编译时期就报出来
// list.add("a");
// list.add('b');
Iterator it = list.iterator();
while(it.hasNext()) {
Integer s = (Integer) it.next();
System.out.println(s);
}
}
}
4.1.7 用途:代码通用性更强
传统方式通过重载多态实现,方法同名,参数类型不同。
package javase.base.gennarics;
public class TestOldStyle {
public static void print(Integer[] dArray) {
for( Integer d : dArray) {
System.out.println(d);
}
}
public static void print( String[] sArray) {
for( String s : sArray) {
System.out.println(s);
}
}
public static void main(String[] args) {
Integer[] scores = new Integer[]{100,98,80};
String[] names = new String[]{"语文","数学","英语"};
TestOldStyle.print(scores);
TestOldStyle.print(names);
}
}
泛型方式
package javase.base.gennarics;
public class TestGenarics {
public static <E> void print(E[] arr) {
for(E e : arr) {
System.out.println(e);
}
}
public static void main(String[] args) {
Integer[] scores = new Integer[]{ 100,98,80 };
String[] names = new String[]{ "语文","数学","英语" };
Double[] moneys = new Double[] { 10.1,20.2,30.3 };
TestGenarics.print(scores);
TestGenarics.print(names);
TestGenarics.print(moneys);
}
}
4.1.8 类型擦除
泛型只是在编译期间生存,编译后就被干掉了,真正运行时,大多情况下取而代之的是Object。
下面的代码利用了jdk提供的强大的反射功能,后续会专门详细讲解,今天先初体验下其强大的功能。
package javase.generics;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
//泛型类型擦除
public class TestGenerics {
public static void main(String[] args) throws Exception {
List<Integer> list = new ArrayList<Integer>();
//1. 编译器按泛型检查,类型报错。这是在编译阶段
//list.add("chenzs");
//2. 但在实际运行时,泛型的地方就被替代为通用类型Object
Class<?> clazz = list.getClass();
Method m = clazz.getDeclaredMethod("add", Object.class);
//3. 利用发射得到的对象是运行时对象,其就可以设置非整形的数据
m.invoke(list, "chenzs");
System.out.println(list.get(0));
}
}
4.2 Collection接口
4.2.1 概述
英文名称Collection,是用来存放对象的数据结构。其中长度可变,而且集合中可以存放不同类型的对象。并提供了一组操作成批对象的方法。
数组的缺点:长度是固定不可变的,访问方式单一,插入、删除等操作繁琐。
4.2.2 集合的继承结构
Collection接口
-- List接口 : 数据有序,可以重复。
-- ArrayList子类
-- LinkedList子类
-- Set接口 : 数据无序,不可以存重复值
-- HashSet子类
-- Map接口 : 键值对存数据
-- HashMap
Collections工具类
4.2.3 常用方法
boolean add(E e):添加元素。
boolean addAll(Collection c):把小集合添加到大集合中 。
boolean contains(Object o) : 如果此 collection 包含指定的元素,则返回 true。
boolean isEmpty() :如果此 collection 没有元素,则返回 true。
Iterator<E> iterator():返回在此 collection 的元素上进行迭代的迭代器。
boolean remove(Object o) :从此 collection 中移除指定元素的单个实例。
int size() :返回此 collection 中的元素数。
Objec[] toArray():返回对象数组
4.2.4 练习1:测试常用方法
package seday11;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Test_301_Collection {
public static void main(String[] args) {
Collection c = new ArrayList();//接口无法直接创建对象
c.add("hello");//添加元素
c.add("java");//添加元素
c.add("~");//添加元素
c.add(10);//jdk5后有自动装箱功能,相当于把10包装Integer.valueOf(10)
System.out.println(c.remove("~"));//移除元素
System.out.println(c.contains("a"));//判断包含关系
System.out.println(c.size());//集合的长度
System.out.println(c);
//for遍历集合
for(int i =0 ;i<c.size();i++) {
System.out.println(c.toArray()[i]);
}
//iterator迭代器遍历
Iterator it = c.iterator();//对 collection 进行迭代的迭代器
while (it.hasNext()) {//如果仍有元素,则返回 true
System.out.println(it.next());//返回迭代获取到的下一个元素
} } }
4.3 List接口
4.3.1 概述
有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
4.3.2 特点
1、 数据有序
2、 允许存放重复元素
3、 元素都有索引
4.3.3 常用方法
ListIterator<E> listIterator()
返回此列表元素的列表迭代器(按适当顺序)。
ListIterator<E> listIterator(int index)
返回列表中元素的列表迭代器(按适当顺序),从列表的指定位置开始。
void add(int index, E element)
在列表的指定位置插入指定元素(可选操作)。
boolean addAll(int index, Collection<? extends E> c)
将指定 collection 中的所有元素都插入到列表中的指定位置(可选操作)。
List<E> subList(int fromIndex, int toIndex)
返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。
E get(int index)
返回列表中指定位置的元素。
int indexOf(Object o)
返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。
4.3.4 练习1:测试常用方法
创建day12工程
创建cn.tedu.list包
创建Test1_List.java
package cn.tedu.list;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
//这类用来测试List接口的常用方法
public class Test1_List {
public static void main(String[] args) {
//1、创建List对象
//特点1:List集合元素都有索引,可以根据索引直接定位元素
List list = new ArrayList();
//2、常用方法
list.add(111);
list.add(222);
list.add(333);
list.add(444);
list.add('a');
list.add("abc");
list.add(3,666);//在3的索引处添加指定元素
//特点2:元素有序, 怎么存就怎么放
System.out.println(list);//[111, 222, 333, 666, 444, a, abc]
Object obj = list.get(4);//get(m)-m是索引值,获取指定索引位置的元素
System.out.println(obj);
//3、迭代/遍历集合中的元素
//使用Collection接口提供的iterator()
Iterator it = list.iterator();
while(it.hasNext()) {//判断集合里有没有下个元素
Object o = it.next();
// System.out.println(o);
}
//使用List接口提供的listIterator()
//interfaceListIterator extends Iterator
//区别:可以使用父接口的功能,同时拥有自己的特有功能,不仅顺序向后迭代还可以逆向迭代
ListIterator it2 = list.listIterator();
while(it2.hasNext()) {
Object o = it2.next();
// System.out.println(o);
}
System.out.println();
List list2 = list.subList(1, 3);//subList(m,n)-m是开始索引,n是结束索引,其中含头不含尾类似于String.subString(m,n)
System.out.println(list2);
}
}
4.4 ArrayList
4.4.1 概述
1) 存在于java.util包中。
2) 内部用数组存放数据,封装了数组的操作,每个对象都有下标。
3) 内部数组默认初始容量是10。如果不够会以1.5倍容量增长。
4) 查询快,增删数据效率会降低。
4.4.2 创建对象
new ArrayList():初始容量是10
4.4.3 练习1:测试常用方法
常用API,包括下标遍历,迭代器遍历
package seday11;
import java.util.ArrayList;
public class Test3_AL {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("aaa");//存入数据
list.add("123");
list.add("ccc");
list.add("ddd");
System.out.println(list);//list中内容
System.out.println(list.size());//集合长度
System.out.println(list.get(1));//根据下标获取元素
System.out.println();
System.out.println(list.remove(2));//移除下标对应的元素
System.out.println(list);
//下标遍历
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i));
}
//Iterator迭代遍历,封装了下标
Iterator<String> it = list.iterator();
while (it.hasNext()) {//如果有数据
String s = it.next();//一个一个向后遍历
System.out.println(s);
}
}
}
4.5 LinkedList
4.5.1 概述
双向链表,两端效率高。底层就是数组和链表实现的。
4.5.2 常用方法
add()
get()
size()
remove(i)
remove(数据)
iterator()
addFirst() addLast()
getFirst() getLast()
removeFirst() removeLast()
4.5.3 练习1:测试迭代器遍历
双向链表:下标遍历效率低,迭代器遍历效率高
package dd;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
public class tt {
public static void main(String[] args) throws Exception {
LinkedList ll = new LinkedList ();
for (int i = 0; i < 100000; i++) {
ll.add(100);
}
f1(ll);
f2(ll);
}
private static void f2(LinkedList<Integer> ll) {
long t = System.currentTimeMillis();
Iterator it = ll.iterator();
while(it.hasNext()) {
it.next();
}
t = System.currentTimeMillis()-t;
System.out.println("=====iterator========="+t);//16
}
private static void f1(LinkedList<Integer> ll) {
long t = System.currentTimeMillis();
for (int i = 0; i < ll.size(); i++) {
ll.get(i);
}
long t1 = System.currentTimeMillis();
System.out.println("~~~~for~~~~~~~"+(t1-t));//9078
}
}
4.6 扩展
4.6.1 ArrayList扩容
ArrayList相当于在没指定initialCapacity时就是会使用延迟分配对象数组空间,当第一次插入元素时才分配10(默认)个对象空间。假如有20个数据需要添加,那么会分别在第一次的时候,将ArrayList的容量变为10 (如下图一);之后扩容会按照1.5倍增长。也就是当添加第11个数据的时候,Arraylist继续扩容变为10*1.5=15(如下图二);当添加第16个数据时,继续扩容变为15 * 1.5 =22个
ArrayList没有对外暴露其容量个数,查看源码我们可以知道,实际其值存放在elementData对象数组中,那我们只需拿到这个数组的长度,观察其值变化了几次就知道其扩容了多少次。怎么获取呢?只能用反射技术了。
4.6.2 HashMap扩容
成长因子:
static final float DEFAULT_LOAD_FACTOR = 0.75f;
前面的讲述已经发现,当你空间只有仅仅为10的时候是很容易造成2个对象的hashcode 所对应的地址是一个位置的情况。这样就造成 2个 对象会形成散列桶(链表)。这时就有一个加载因子的参数,值默认为0.75 ,如果你hashmap的 空间有 100那么当你插入了75个元素的时候 hashmap就需要扩容了,不然的话会形成很长的散列桶结构,对于查询和插入都会增加时间,因为它要一个一个的equals比较。但又不能让加载因子很小,如0.01,这样显然是不合适的,频繁扩容会大大消耗你的内存。这时就存在着一个平衡,jdk中默认是0.75,当然负载因子可以根据自己的实际情况进行调整。
package cn.tedu.io;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Scanner;
import javax.swing.text.DefaultEditorKit.CopyAction;
//测试文件复制
/*1.读取源文件的数据
* 2.把数据写出到目标文件里去
*
* */
public class Test1_Copy {
public static void main(String[] args) {
//1.接收键盘输入的源文件路径
String formpath=new Scanner(System.in).nextLine();
File from=new File(formpath);
//2.接收键盘输入的目标文件路径
String topath=new Scanner(System.in).nextLine();
File to=new File(topath);
//3.调用复制方法
copy(from,to);
}
//复制方法
/*针对释放资源时的优化方式2:jdk1.7对于IO自动资源管理的优化--
* try with resource
* */
private static void copy(File from, File to) {
try(//1.创建读取流和写出流--字节流/字符流/普通流/高级流
InputStream in=new BufferedInputStream(
new FileInputStream(from));
OutputStream out=new BufferedOutputStream(
new FileOutputStream(to));
) {
// 2.读取源文件的数据
int b=0;//定义变量记录到的数据
while((b=in.read()) != -1) {
//3.把数据写出到目标文件里去
out.write(b);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// finally {
// //4.释放资源--保证一定会被执行,放入finally块中
// IOUtils.close(in);
// IOUtils.close(out);
try {
in.close();
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package cn.tedu.io;
import java.io.Closeable;
import java.io.IOException;
public class IOUtils {
//参数最好能够通用,也就是说,
//最好能够把所有的字节流字符流高级普通流都释放掉--
//多态Closeable.close()
public static void close(Closeable c) {
try {
c.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package cn.tedu.collection;
import java.util.ArrayList;
import java.util.List;
//测试泛型
public class Test2_Gnerics {
public static void main(String[] args) {
/*泛型来想要模拟数组--统一数据类型--编译器就报错
* 泛型的作用:
* 1.检查集合中的元素类型
* 2.如果不对编译期报错
* */
List list=new ArrayList();
//集合中可以添加任意类型的数据--太自由
list.add(1.1);
list.add(10);
list.add(true);
list.add("jack");
System.out.println(list);
//想要约束集合中的元素类型?--泛型
List<String> list2=new ArrayList<>();
list2.add("1.1");
//添加失败,因为元素的类型,没有通过泛型的类型String检查
// list2.add(1);
//泛型里约束的元素类型,必须是引用类型,不能是基本类型
List<Integer> list3=new ArrayList<>();
list3.add(1);
list3.add(2);
list3.add(3);
list3.add(4);
}
}
package cn.tedu.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
//测试集合
public class Test3_Collection {
public static void main(String[] args) {
// 1.创建对象
Collection<Integer> c=new ArrayList<>();
//2.常用方法
c.add(1);
c.add(2);
c.add(3);
c.add(5);
c.add(4);
System.out.println(c);
//c.clear();//清空集合
System.out.println(c.contains(1));//判断是否包含指定元素
System.out.println(c.equals(1));//判断集合是否和指定元素相等
System.out.println(c.hashCode());//获取集合在内存中的哈希码值
System.out.println(c.isEmpty());//判断集合是否为空
System.out.println(c.remove(2));//移除指定元素
System.out.println(c.size());//获取集合长度
Object[] os=c.toArray();//把元素存入数组
System.out.println(c.toArray(os));//
//集合间的操作
Collection<Integer> c2=new ArrayList<>();//
c.add(1);
c.add(2);
c.add(3);
System.out.println(c.addAll(c2));//把c2添加到c里面
System.out.println(c);
System.out.println(c.containsAll(c2));//判断c中是否包含c2
//System.out.println(c.removeAll(c2));//删除交集元素
System.out.println(c);
System.out.println(c.retainAll(c2));//取差集
//迭代集合/循环/遍历
//Iterator<E> iterator();--返回可以迭代集合的迭代器
Iterator<Integer> it=c.iterator();
while(it.hasNext()) {//判断集合中是否有下一个元素,有返回true
Integer in=it.next();//获取下一个元素
System.out.println(in);
}
}
}
package cn.tedu.collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
//测试List接口
public class Test4_List {
public static void main(String[] args) {
// 1.创建对象
List<String> list=new ArrayList<>();
//2.常用方法
//继承自Collection接口的方法
list.add("xiongda");
list.add("xionger");
list.add("guangtq");
list.add(null);
list.add("xiongda");
list.add("xionger");
//List特点:元素有序+元素可以重复+可以存null+元素都有下标
System.out.println(list);
//3.List接口特有方法--都是根据下标操作数据方式
list.add(2, "美队");//在指定下标处,插入指定元素
System.out.println(list.get(2));//根据下标获取元素
System.out.println(list.indexOf("xiongda"));
//0获取指定元素第一次出现的索引值
System.out.println(list.lastIndexOf("xiongda"));
//5获取指定元素最后第一次出现的索引值
System.out.println(list.remove(2));
//按照索引删除元素并返回被删除的元素
System.out.println(list.set(1, "皮皮虾"));
//把指定索引对应的值替换掉
List<String> list2=list.subList(2,4);//[2,4)含头不含尾的截取子list
System.out.println(list2);//[guangtq, null]
//迭代List接口的方式:
/*1.Iterator<E> iterator()--返回父接口
* 2.ListIterator<E> listIterator()--返回子接口
* 向后遍历 逆向遍历
* 3.for循环
* 4.增强for循环foreach
* */
//1.Iterator<E> iterator()
Iterator<String> it=list.iterator();
while(it.hasNext()) {//有下一个元素吗?
String s=it.next();//获取下一个元素
System.out.println(s);
}
//2.ListIterator<E> listIterator()--子接口list
ListIterator<String> it2 = list.listIterator();
while(it2.hasNext()) {//有下一个元素吗?
String s=it2.next();//获取下一个元素
System.out.println(s);
}
//3.for循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//4.增强for循环foreach
/*for(数据的类型 变量名 : 要遍历的数据){}
* */
for (String s : list2) {
System.out.println(s);
}
}
}
package cn.tedu.collection;
import java.util.ArrayList;
//测试ArrayList
public class Test5_ArrayList {
public static void main(String[] args) {
// 1.创建对象
//ArrayList底层维护了一个数组Object[],用来存储多个元素
//数组的默认大小是10,大于10的时候,底层会自动扩容,
//扩容方式是:以前容量的1.5倍
//int newCapacity = oldCapacity + (oldCapacity >> 1);
ArrayList<Integer> list = new ArrayList<>();
//2.常用方法
/*继承自Collection接口的方法
* 继承自List接口的方法
* */
list.add(1);
}
}