文章目录
集合
集合体系
单列集合
双列集合
Collection 接口
Collection接口,作为单列集合中顶级接口,里面定义了单列集合共有的方法,方法以增,删,改,查,判断,转换为主
Collection, ArrayList后面是jdk5之后的语法—泛型
可以通过泛型语法,为集合设置一个类型,这样就只能存储设置的数据类型
集合中建议存储同一类型数据
集合是容器,可以存储不同的数据,严格上讲集合中是可以存储任何类型的(只能存储引用类型).
//collection接口中常用的方法(单列集合中所共有的方法)
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add("a");
c.add("b");
c.add("c");
c.add("d");
Collection<String> c1 = new ArrayList<>();
c1.add("a");
c1.add("b");
c1.add("c");
c1.add("d");//向集合末尾添加元素
//c1.addAll(c);//把一个集合添加到另一个集合
//c.clear();//清空集合中的元素
System.out.println(c.equals(c1));//比较两个集合中的内容是否相等
System.out.println(c.isEmpty());//判断集合中元素的个数是否为空
System.out.println(c.remove("h"));//删除指定元素,成功返回true,没有返回false
//System.out.println(c.retainAll(c1));//在c中保留c1中交集的元素,发生变化返回true,没有变化返回false
System.out.println(c.size());//数组长度length,字符串长度length(),集合长度size()
System.out.println(c);
System.out.println(c1);
Object[] objs = c.toArray();//将集合转为Object类型数组
String[] sobject = c.toArray(new String[c.size()]);//将集合转为指定类型数组
}
}
List
ArrayList
ArrayList底层是数组实现 ,可重复存储元素
add();向集合中添加元素时,底层会默认创建一个长度为10的Object类型数组,当数组装满时,再次添加元素,会创建一个原来数组长度1.5倍的新数组,将原数组内容复制过来 最后将新数组地址赋给底层的数组
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("d");
list.add(0, "X");// 向指定下标位置上添加元素
System.out.println(list.get(11));// elementData[index];
System.out.println(list.remove(0));// 删除并返回指定位置的元素
list.set(2, "K");// 替换指定位置的元素
System.out.println(list.size());// 返回集合中实际存储的元素个数
System.out.println(list);
}
扩容机制:
(1)创建对象时创建一个指定容量的数组;
(2)添加前判断元素添加后,数组是否能容纳下,如果可以则直接添加,否则会创建一个新数组(扩容);
(3)扩容为原来的1.5倍
LinkedList
存储重复元素,按照添加顺序排放
public static void main(String[] args) {
LinkedList<String> llist = new LinkedList<>();
llist.add("a");//向链表中添加元素 [0,1,2,3,4]
llist.add("b");
llist.add("c");
llist.add("d");
llist.add("e");
System.out.println(llist.get(3));//要找的元素位置小于size/2,从头结点开始查找,否则从尾结点开始查找
llist.addFirst("X");
llist.addLast("Y");
System.out.println(llist.removeFirst());
System.out.println(llist.removeLast());
System.out.println(llist);
}
Vector
底层也是数组实现,但是是线程安全的
public static void main(String[] args) {
Vector<String> v = new Vector<>();
v.add("a");
v.add("b");
v.add("c");
v.add("d");
v.add("a");
v.get(1);
System.out.println(v);
}
List集合接口迭代
(1)for循环
for(int i=0;i<alist.size();i++){
System.out.println(alist.get(i));
}
(2)增强for循环
for(String item:alist){
System.out.println(item);
}
(3)迭代器
Iterator<String> it = alist.iterator();
while(it.hasNext()){
String item = it.next;
if(item.equals("a")){
it.remove();
}
}
Set
无需且不重复的集合,元素没有索引
set集合只能用增强for循环和迭代器遍历
public HashSet()
{
map = new HashMap<>(); 底层使用的是HashMap实现的.
}
HashSet
HashSet如何判断元素是否重复?
答:在add的底层方法中,每添加一个元素时,就会判断集合中是否已经包含了此元素.在底层使用hashCode()和equals()方法来判断内容是否重复。
hashCode()是Object类中的方法
public native int hashCode(); native修饰的方法称为本地方法,java没有实现,是调用的操作系统中的方法Object类中的 hashCode()获取的对象在内存中的地址
其他类中重写的hashCode(),都不是对象的地址,而是根据对象内容计算出来的哈希值.
aaaaa–>int 1234
abcd–>int 1234
equals(); 除了Object类中的equals是比较对象地址,其他类中都是比较内容是否相等,因为equls效率很低
hashCode(); 判断时,先调用hashCode()计算哈希值,用整数哈希值来比较是否相等,这样提高了比较效率,但是此种做法不安全,因为内容不同,可能计算的哈希值相同.
equals(); 当有哈希值相同时,再去调用equal()方法比较内容是否相等.
public class HashSetDemo2 {
public static void main(String[] args) {
//hash值相同,调用equals()进行比较
HashSet<String> set = new HashSet<>();
/*add添加时,先用内容调用hashCode()方法计算出一个hash值(int 类型),
用hash值比较是否相同,效率高,但只用hash值比较不安全,
这时调用equals()方法对每个字符进行比较
*/
set.add("a");
set.add("b");
set.add("通话");
set.add("c");
set.add("重地");
System.out.println(set);
}
}
TreeSet
有序(可以根据元素自然顺序排序) 且不能存储重复元素,添加时要对元素进行排序和去重。
//TreeSet不重复可以对元素进行排序
TreeSet<String> set = new TreeSet<>();
set.add("c");
set.add("b");
set.add("z");
set.add("a");
set.add("x");
set.add("x");
System.out.println(set);
存储对象时必须实现Comparable接口。
TreeSet底层数据结构是二叉树(红黑树是一种自平衡的二叉树)
public class Car implements Comparable<Car>{
private int no;
private String name;
public Car(int no, String name) {
super();
this.no = no;
this.name = name;
}
@Override
public int compareTo(Car o) {
return this.no-o.no;
}
@Override
public String toString() {
return "Car [no=" + no + ", name=" + name + "]";
}
}
public class TreeSetDemo {
public static void main(String[] args) {
Car car1 = new Car(1,"宝马1");
Car car2 = new Car(2,"宝马2");
Car car3 = new Car(3,"宝马3");
TreeSet<Car> carset = new TreeSet<>();
carset.add(car1);
carset.add(car3);
carset.add(car2);
System.out.println(carset);
}
}
Map
HashMap
HashMap 键不能重复,值可以重复键是无序,可以存储一个为null的键
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
map.put("a", "aa");
map.put("s", "ss");
map.put("b", "bb");
map.put("x", "xx");
map.put("x", "aa");
map.put(null, "cc");
//System.out.println(map.remove("a"));//删除key,返回其值
System.out.println(map.containsKey("a"));//true 是否包含指定的键
System.out.println(map.containsValue("aa"));//true 是否包含指定的值
System.out.println(map.isEmpty());//false 是否为空
System.out.println(map.size());//5
Collection<String> c = map.values();//获取map中值的那一列,存放到一个c中
Set<String> set = map.keySet();//获取map中键的那一列,存放到一个set中
System.out.println(map.get("b"));
map.replace("b", "XXXX");
System.out.println(map);
System.out.println(c);
System.out.println(set);
}
HashMap的底层实现原理:
哈希函数根据内容的哈希值计算出在哈希表中的位置;
第一次添加元素时会创建哈希表,将元素插入到对应的位置,后面如果有位置相同的元素就放在链表中;
当链表长度大于等于7时,将链表转为红黑树;
当哈希表容量达到整数数组的0.75时,扩容为原来的2倍。
HashTable
键不能重复,值可以重复 键是无序
不可以存储一个为null的键和值,是线程安全的.
线程安全,锁住了整个put(),访问量较小时可以使用,访问量较高时,效率太低了。
TreeMap
键不能重复,值可以重复键可以排序,作为键类型的类必需实现排序接口
Collections类
Collections是集合类的工具类,与数组的工具类Arrays类似.
public class Collections {
public static void main(String[] args) {
/*ArrayList<String> list = new ArrayList<>();
list.add("a");
Collections.addAll(list, "a","b","c","d");
System.out.println(list);*/
ArrayList<Integer> list = new ArrayList<>();
list.add(5);
list.add(2);
list.add(3);
list.add(4);
list.add(1);
Collections.swap(list, 0, 2);//交换指定位置的元素
//Collections.sort(list); //排序
/* Collections.sort(list,new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
}); 自定义倒序排序*/
//System.out.println(Collections.binarySearch(list,4));//二分查找
ArrayList<Integer> dlist = new ArrayList<>();
dlist.add(0);
dlist.add(0);
dlist.add(0);
dlist.add(0);
dlist.add(0);
dlist.add(0);
Collections.copy(dlist, list);
//Collections.fill(list,1);
System.out.println(Collections.max(list));
Collections.shuffle(list);
System.out.println(list);
System.out.println(dlist);
// List<Integer> list1 = Collections.emptyList();//返回的是一个内部类EmptyList,此集合不能操作使用,用于逻辑判断
//test(10, 5,6,3,3,2);
}
/*
int...a 可变长度的参数 本质是数组
一个参数列表中,只能允许有一个可变长度参数
如果有,必须放在参数列表的末尾
*/
public static void test(int b,int...a){
System.out.println(b);
System.out.println(Arrays.toString(a));
}
}
泛型
泛型类
泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开
放相同的接口。
一个最普通的泛型类:
public class Demo<T>{ / /T可以为任意标识符,常见的如T、E、K、V等形式的参数常用于表示泛型
private T key; / /key这个成员变量的类型为T,T的类型由外部指定
public Demo(T key) { / /泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}
public T getKey(){ / /泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
}
传入的实参类型需与泛型的类型参数类型相同,即为
Integer.Demo<Integer> demo = new Demo<Integer>(123456);
1.泛型的类型参数只能是类类型
2.泛型的类型参数可以有多个。
3.如果没有定义具体类型,默认为Object
从泛型类派生子类
子类也是泛型类,子类和父类的泛型类型要一致
class A<T> extends Demo<T>
子类不是泛型类,父类要明确泛型的数据类型
class A extends Demo<String>
泛型通配符
类型通配符一般是使用"?"代替具体的类型实参。
? 表示实际传入的参数的泛型类型 ?表示可以是任意的 也称无界通配符
public void show(Demo<?> obj){
?表示实际传入的参数类型可以是任意的
}
● 类型通配符上限
类/接口<?extends 实参类型>
要求该泛型的类型,只能是实参类型,或实参类型的子类类型。
● 类型通配符下限
类/接口<?super 实参类型>
要求该泛型的类型,只能是实参类型,或实参类型的父类类型。
类型擦除
泛型是Java 1.5版本才引进的概念,在这之前是没有泛型的,但是,泛型代码能够很
好地和之前版本的代码兼容。那是因为,泛到信息只存在于代码编译阶段,在进入JVM之
前,与泛型相关的信息会被擦除掉,我们称之为一类型擦除。
泛型类被类型擦除后,相应的类型就被替换成 Object 类型或者上限类型.