Java基础-5.collection、list、set、map

一、 集合

1.集合的由来

数组长度是固定,当添加的元素超过了数组的长度时需要对数组重新定义,java内部提供了集合类,能存储任意对象,长度是可以改变的,随着元素的增加而增加,随着元素的减少而减少。

2.数组和集合的区别

区别1

数组既可以存储基本数据类型,又可以存储引用数据类型,基本数据类型存储的是值,引用数据类型存储的是地址值。

集合只能存储引用数据类型(对象),集合中也可以存储基本数据类型,但是在存储的时候会自动装箱变成对象。

区别2

数组长度是固定的,不能自动增长。

集合的长度的是可变的,可以根据元素的增加而增长。

3.数组和集合什么时候用

如果元素个数是固定的推荐用数组。如果元素个数不是固定的推荐用集合。集合在增长的时候,是将原来的集合丢弃掉,再重新创建一个集合,将原来的元素拷贝到新的集合中,所以从内存角度考虑,比较浪费空间。

二、Collection

collection包含list和set,体系结构如下:

List:ArrayList(数组)、linkedlist(链表)、vector(数组)(按顺序,可重复,存取一致)

Set:hashset(哈希算法)、treeset(二叉树算法)、linkedhashset(不按顺序,不可重复,存取不一致)

1.Collection的方法

boolean add(E e)

boolean remove(Object o)

void clear()

boolean contains(Object o)

boolean isEmpty()

int size()

2.Collection All方法

bolean addAll(Collection c) 

bolean removeAll(Collection c) ,删除的是和C的交集

bolean containsAll(Collection c),判断是否包含C的全部

bolean retainAll(Collection c) 取交集,返回的如果还是集合本身,是false,否则是true

三、集合遍历

1.转成数组遍历

可以实现集合的遍历:toArray()

Collection coll = new ArrayList();

coll.add(new Student("张三",23));

coll.add(new Student("李四",24));

coll.add(new Student("王五",25));

coll.add(new Student("赵六",26));

object[] arr = coll.toArray();   //将集合转换成数组

for (int i = 0; i < arr.length; i++) {

    Student s = (Student)arr[i];  //强转成Student

    System.out.println(s.getName() + "," + s.getAge());

}

2.迭代器遍历

集合是用来存储元素,存储的元素需要查看,那么就需要迭代(遍历)

Collection c = new ArrayList();

c.add("a");

c.add("b");

c.add("c");

c.add("d");

Iterator it = c.iterator();        //获取迭代器的引用

while(it.hasNext()) {          //集合中的迭代方法(遍历)

    System.out.println(it.next());

}

四、Collection存储对象并遍历

1.自定义对象遍历

Collection存储自定义对象并用迭代器遍历

Collection c = new ArrayList();

c.add(new Student("张三",23));

c.add(new Student("李四",24));

c.add(new Student("王五",25));

for(Iterator it = c.iterator();it.hasNext();) {

    Student s = (Student)it.next();

    System.out.println(s.getName() + "," + s.getAge());

}

Iterator it = c.iterator();       //获取迭代器

while(it.hasNext()) {         //判断集合中是否有元素

   Student s = (Student)it.next();

    System.out.println(s.getName() + "," + s.getAge());

}

2.迭代器原理

迭代器是对集合进行遍历,而每一个集合内部的存储结构都是不同的,每一个集合存和取都是不一样,那么就需要在每一个类中定义hasNext()和next()方法,这样做是可以的,但是会让整个集合体系过于臃肿,迭代器是将这样的方法向上抽取出接口,然后在每个类的内部,定义自己迭代方式,这样做的好处有二,第一,规定了整个集合体系的遍历方式都是hasNext()和next()方法,第二,代码有底层内部实现,使用者不用管怎么实现的,会用即可。

3.并发修改异常

一个集合判断里面有没有"world"这个元素,如果有,就添加一个"javaee"元素。

List list = new ArrayList();

list.add("world");

list.add("d");

list.add("e");

Iterator it = list.iterator();

while(it.hasNext()) {

    String str = (String)it.next();

    if(str.equals("world")) {

        list.add("javaee");  //这里会抛出CncurrentMdificatinExceptin并发修改异常,因为生成迭代器的时候,只告诉迭代器总共给了几个元素,所以迭代器本身不知道。

    }

}

解决方法:ListIterator

ListIterator lit = list.listIterator(); //在遍历的过程中添加元素,可以用ListIterator中的add方法

while(lit.hasNext()) {

    String str = (String)lit.next();

    if(str.equals("world")) {

        lit.add("javaee");  

        //list.add("javaee");

    }

}

对于ListIterator

bolean hasNext()是否有下一个

bolean hasPrevious()是否有前一个

object next()返回下一个元素

object previous();返回上一个元素

五、ArrayList

void add(int index,E element)

E remove(int index)

E get(int index)

E set(int index,E element)

通过size()和get()方法结合使用遍历。

List list = new ArrayList();

list.add(new Student("张三",18));

list.add(new Student("李四",18));

for(int i = 0; i < list.size(); i++) {

    Student s = (Student)list.get(i);

    System.out.println(s.getName() + "," + s.getAge());

}

六、Vector

public void addElement(E bj)

public E elementAt(int index)

public Enumeratin elements()

Vector的迭代

Vector v = new Vector();                //创建集合对象,List的子类

v.addElement("a");

v.addElement("b");

v.addElement("c");

Enumeration en = v.elements();          //获取枚举

while(en.hasMoreElements()) {           //判断集合中是否有元素

    System.out.println(en.nextElement());//获取集合中的元素

}

ArrayList:底层数据结构是数组,查询快,增删慢。线程不安全,效率高。

Vector:底层数据结构是数组,查询快,增删慢。线程安全,效率低。

              Vector相对ArrayList查询慢(线程安全的)

              Vector相对LinkedList增删慢(数组结构)

七、LinkedList

LinkedList:底层数据结构是链表,查询慢,增删快。线程不安全,效率高。

ArrayList和LinkedList的区别

    ArrayList底层是数组结果,查询和修改快,增删慢

    LinkedList底层是链表结构的,增和删比较快,查询和修改慢

共同点:都是线程不安全的

查询多用ArrayList,增删多用LinkedList,如果都多ArrayList。

1.LinkedList的特有功能

public void addFirst(E e)及addLast(E e),打印出来的顺序是逆序的,因为不断在头加入新元素

public E getFirst()及getLast()

public E removeFirst()及public E removeLast()

public E get(int index);

pubic void add(E e) 顺序添加

2.栈和队列数据结构

栈:先进后出,队列:先进先出

用LinkedList模拟栈数据结构的集合

public class Stack {

    private LinkedList list = new LinkedList();     //创建LinkedList对象

    public void in(Object obj) {

        list.addLast(obj);               //封装addLast()方法

    }

    public Object out() {

        return list.removeLast();         //封装removeLast()方法

    }

    public boolean isEmpty() {

        return list.isEmpty();             //封装isEmpty()方法

    }

}

八、泛型

1.泛型概述

通过Object转型问题引入,早期的Object类型可以接收任意的对象类型,但是在实际的使用中,会有类型转换的问题。也就存在隐患,所以Java提供了泛型来解决这个安全问题。泛型提高安全性(将运行期的错误转换到编译期),省去强转的麻烦。例如,如果ArrayList中存储了true、11、person对象,当想调用person对象时,必须通过转型,而true无法转型成person,所以运行期间就会出错,所以使用泛型将ArrayList限定为只能输入person对象。

ArrayList<Person> list = new ArrayList<>();

<>中放的必须是引用数据类型,前后的泛型必须一致,或者后面的泛型可以省略不写(1.7的新特性菱形泛型)。

2.泛型类

把泛型定义在类上,public class 类名<泛型类型1,…>

泛型类的使用,下面的例子可以将tool类比ArrayList进行理解。当调用Tool类,传入的Q是car对象,则下面所有的Q都会是car对象。

public class Tool<Q> {

         private Q q;

         public Q getObj() {

            return q;

        }

         public void setObj(Q q) {

            this.q = q;

        }

      #public <泛型类型> 返回类型 方法名(泛型类型 变量名)

       public<T> void show(T t) { //方法泛型最好与类的泛型一致

            System.out.println(t); //如果不一致,需要在方法上声明该泛型

       }

       //这里加T,相当于方法有了一个自己的泛型

        public static<W> void print(W w) { //静态方法必须声明自己的泛型

             System.out.println(w);

       }这里是因为static时候对象并没有创立,所以并没有类声明的泛型

}

3.泛型接口

把泛型定义在接口上:public interface 接口名<泛型类型>

interface Inter<T> {

      public void show(T t);

}

/*class Demo implements Inter<String> { //推荐用这种

        @Override

         public void show(String t) {

                System.out.println(t);

        }

}*/

 

class Demo<T> implements Inter<T> { //没有必要在实现接口的时候给自己类加泛型,建议用上面的方式

         @Override

         public void show(T t) {

                 System.out.println(t);

         }

}

4.泛型高级之通配符

<?>,任意类型,如果没有明确,那么就是Object以及任意的Java类了

? extends E,向下限定,E及其子类

ArrayList<Person> list1 = new ArrayList<>();

list1.add(new Person("张三", 23));

list1.add(new Person("李四", 24));

list1.add(new Person("王五", 25));

ArrayList<Student> list2 = new ArrayList<>();

list2.add(new Student("赵六", 26));

list2.add(new Student("周七", 27));

list1.addAll(list2);因为student继承了person,所以可以放进去。

? super E,向上限定,E及其父类

5.可变参数

定义方法的时候不知道该定义多少个参数。

修饰符 返回值类型 方法名(数据类型… 变量名){}

这里的变量其实是一个数组,如果一个方法有可变参数,并且有多个参数,那么,可变参数肯定是最后一个,否则都会把后面的参数放在可变数组里面。

6.增强for循环

for(元素数据类型 变量 : 数组或者Collection集合) {

            使用变量即可,该变量就是元素

}

7.三种迭代的能否删除

普通for循环,可以删除,但是索引要--

迭代器,可以删除,但是必须使用迭代器自身的remove方法,否则会出现并发修改异常

增强for循环不能删除,因为底层采用迭代器

九、Arrays工具类

1.asList()

Arrays工具类的asList(T..a)方法的使用,将数组转换成集合,可以通过集合的方法操作数组,用的就是可变参数。数组转换成集合,不能对数组进行增加删除,但是可以使用集合的其他方法。

int[] arr = {11,22,33,44,55};

List<int[]> list = Arrays.asList(arr); 基本数据类型的数组转换成集合,会将整个数组当作一个对象转换,容易理解,因为集合本来存储的就都是对象。

System.out.println(list); //打印出来的是地址值

如果非要转换成集合,则需要采用引用数据类型

Integer[] arr = {11,22,33,44,55}; //将数组转换成集合,数组必须是引用数据类型

List<Integer> list = Arrays.asList(arr);

System.out.println(list);//打印出的是各个元素

2. toArray

Collection中toArray(T[ ] a)泛型版的集合转数组

ArrayList<String> list = new ArrayList<>();

list.add("a");

list.add("b");

list.add("c");

String[] arr = list.toArray(new String[10]);

当集合转换数组时,数组长度如果是小于等于集合的size时,转换后的数组长度等于集合的size,如果数组的长度大于了size,分配的数组长度就和你指定的长度一样。

十、HashSet

1.存储对象

HashSet<String> hs = new HashSet<>();

boolean b1 = hs.add("a");

boolean b2 = hs.add("a");   //当存储不成功的时候,返回false,不能存重复的

System.out.println(b1);

System.out.println(b2);

for(String s : hs) {

      System.out.println(s);

}

2.存储自定义对象

HashSet<Person> hs = new HashSet<>();

hs.add(new Person("张三", 23));

hs.add(new Person("张三", 23));

hs.add(new Person("李四", 23));

要重写hashCode()和equals()方法,否则会全部存进去

public int hashCode() {

  int h = hash;

  if (h == 0 && value.length > 0) {

     char val[] = value;

     for (int i = 0; i < value.length; i++) {

           h = 31 * h + val[i];

     }

     hash = h;

  }

  return h;

}

3.HashSet保证元素唯一性的原理

使用Set集合都是需要去掉重复元素的, 如果在存储的时候逐个equals()比较,效率较低,哈希算法提高了去重复的效率, 降低了使用equals()方法的次数,当HashSet调用add()方法存储对象的时候,先调用对象的hashCode()方法得到一个哈希值,然后在集合中查找是否有哈希值相同的对象,如果没有哈希值相同的对象就直接存入集合,如果有哈希值相同的对象, 就和哈希值相同的对象逐个进行equals()比较,比较结果为false就存入,true则不存。

类中必须重写hashCode()和equals()方法。

hashCode():属性相同的对象返回值必须相同, 属性不同的返回值尽量不同(提高效率)

equals():属性相同返回true, 属性不同返回false,返回false的时候存储

4.LinkedHashSet 

可以保证怎么存就怎么取

5.将集合中的重复元素去掉

public static void getSingle(List<String> list) {

    LinkedHashSet<String> lhs = new LinkedHashSet<>();

    lhs.addAll(list);            //将list集合中的所有元素添加到lhs

    list.clear();                   //清空原集合

    list.addAll(lhs);            //将去除重复的元素添回到list中

}

十一、TreeSet

TreeSet是用来排序的,可以指定一个顺序,对象存入之后会按照指定的顺序排列。

1.自然顺序(Comparable)

public class Person implements Comparable<Person>自定义对象需要实现接口,实现Comparable必须要重写compareTo()方法。

TreeSet类的add()方法中会把存入的对象提升为Comparable类型,调用对象的compareTo()方法和集合中的对象比较,根据compareTo()方法返回的结果进行存储

2.比较器顺序(Comparator)

创建TreeSet的时候可以制定一个Comparator

TreeSet<String> ts = new TreeSet<>(new CompareByLength());需要传入比较器

class CompareByLength implements Comparator<String>{

       @Override

       public int compare(String o1, String o2) {

               int i = o1.length()-o2.length();

               return i==0? o1.compareTo(o2):i;

       }

}

如果传入了Comparator的子类对象,那么TreeSet就会按照比较器中的顺序排序,add()方法内部会自动调用Comparator接口中compare()方法排序。调用的第一个参数是传入的

3.两种方式的区别

TreeSet构造函数什么都不传,默认按照类中Comparable的顺序(没有就报错ClassCastException),TreeSet如果传入Comparator,就优先按照Comparator。

十二、集合框架练习

1.练习

在一个集合中存储了无序并且重复的字符串,定义一个方法,让其有序(字典顺序),而且还不能去除重复

    public static void main(String[] args) {

        ArrayList<String> list = new ArrayList<>();

        list.add("ccc");

        list.add("ccc");

        list.add("aaa");

        list.add("aaa");

        list.add("bbb");

        list.add("ddd");

        list.add("ddd");

        sort(list); //自定义方法

        System.out.println(list);

    } 

       // Collections.sort(list); 一句话搞定

    public static void sort(List<String> list) {

        TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {       //定义比较器(new Comparator(){}是Comparator的子类对象)

            @Override

            public int compare(String s1, String s2) {      //重写compare方法

                int num = s1.compareTo(s2);              //比较内容

                return num == 0 ? 1 : num;   //如果内容一样返回一个不为0的数字即可

            }

        });

        ts.addAll(list);                   //将list集合中的所有元素添加到ts中

        list.clear();                                          //清空list

        list.addAll(ts);           //将ts中排序并保留重复的结果在添加到list中

    }

2.练习

从键盘接收一个字符串,程序对其中所有字符进行排序,例如键盘输入:helloitcast,程序打印:acehillostt

Scanner sc = new Scanner(System.in);            //创建键盘录入对象

System.out.println("请输入一行字符串:");

String line = sc.nextLine();                    //将键盘录入的字符串存储在line中

char[] arr = line.toCharArray();                //将字符串转换成字符数组

TreeSet<Character> ts = new TreeSet<>(new Comparator<Character>() {

    @Override

    public int compare(Character c1, Character c2) {

        //int num = c1.compareTo(c2);

        int num = c1 - c2;                  //自动拆箱

        return num == 0 ? 1 : num;

    }

});

for(char c : arr) {

    ts.add(c);

}

for(Character ch : ts) {

    System.out.print(ch);

}

3.练习

程序启动后,可以从键盘输入接收多个整数, 直到输入quit时结束输入,把所有输入的整数倒序排列打印。

Scanner sc = new Scanner(System.in); //创建键盘录入对象 

System.out.println("请输入:"); 

TreeSet ts = new TreeSet<>(new Comparator() {//将比较器传给TreeSet的构造方法

        @Override

        public int compare(Integer i1, Integer i2) {

            //int num = i2 - i1;                    //自动拆箱

            int num = i2.compareTo(i1);

            return num == 0 ? 1 : num;

        }

    });

    while(true) {

        String line = sc.nextLine();  //将键盘录入的字符串存储在line中

        if("quit".equals(line))   //如果字符串常量和变量比较,常量放前面,这样不会出现空指针异常,变量里面可能存储null

            break;

        try {

            int num = Integer.parseInt(line);       //将数字字符串转换成数字

            ts.add(num);

        } catch (Exception e) {

            System.out.println("您录入的数据有误,请输入一个整数");

        }

    }

    for (Integer i : ts) {                      //遍历TreeSet集合

        System.out.println(i);

    }

十三、Map

将键映射到值的对象,一个映射不能包含重复的键,每个键最多只能映射到一个值。存储自定义对象时候要重写hashcode和equal方法。

Map是双列的,Collection是单列的

Map的键唯一,Collection的子体系Set是唯一的

Map集合的数据结构值针对键有效,跟值无关;Collection集合的数据结构是针对元素有效

Set底层依赖的是map,只不过是value不显示

Hashmap

LinkedHashMap:底层是链表实现的可以保证怎么存就怎么取

TreeMap:类需要实现comparable接口,并实现compareTo方法,或者可以传入比较器comparator,并重写compare方法。

1.Map方法

添加功能

V put(K key,V value):添加元素。如果键是第一次存储,就直接存储元素,返回null,如果键不是第一次存在,就用值把以前的值替换掉,返回以前的值

删除功能

void clear():移除所有的键值对元素

V remove(Object key):根据键删除键值对元素,并把值返回

判断功能

boolean containsKey(Object key):判断集合是否包含指定的键

boolean containsValue(Object value):判断集合是否包含指定的值

boolean isEmpty():集合是否为空

获取功能

Set<Map.Entry<K,V>> entrySet():所有键值对对象

V get(Object key):根据键获取值

Set keySet():获取集合中所有键的集合

Collection values():获取集合中所有值的集合

长度功能

int size():返回集合中的键值对的个数

2.遍历

HashMap<String, Integer> hm = new HashMap<>();

hm.put("张三", 23);

hm.put("李四", 24);

hm.put("王五", 25);

hm.put("赵六", 26);

/*Set<String> keySet = hm.keySet();         //获取集合中所有的键

Iterator<String> it = keySet.iterator();    //获取迭代器

while(it.hasNext()) {                       //判断单列集合中是否有元素

    String key = it.next();                 //获取集合中的每一个元素,其实就是双列集合中的键

    Integer value = hm.get(key);            //根据键获取值

    System.out.println(key + "=" + value);  //打印键值对

}*/

for(String key : hm.keySet()) {             //增强for循环迭代双列集合第一种方式

    System.out.println(key + "=" + hm.get(key));

}

或者

for(Entry<String,Integer> en : hm.entrySet()) {

    System.out.println(en.getKey() + "=" + en.getValue());

}

3.HashMap和Hashtable的区别

Hashtable是JDK1.0版本出现的,是线程安全的,效率低,HashMap是JDK1.2版本出现的,是线程不安全的,效率高。Hashtable不可以存储null键和null值,HashMap可以存储null键和null值。

4.Collections

针对集合操作的工具类

public static <T> void sort(List<T> list)

public static <T> int binarySearch(List<?> list,T key)

public static <T> T max(Collection<?> coll)

public static void reverse(List<?> list)

public static void shuffle(List<?> list)

十四、泛型边界

1.? super E

固定下边界

class CompareByAge implements Comparator<Student> {

     @Override

     public int compare(Student s1, Student s2) {

         int num = s1.getAge() - s2.getAge();

         return num == 0 ? s1.getName().compareTo(s2.getName()) : num;

     }

}

2.? extends E

固定上边界

public static void demo1() {

ArrayList<Student> list1 = new ArrayList<>();

list1.add(new Student("张三", 23));

list1.add(new Student("李四", 24));

ArrayList<BaseStudent> list2 = new ArrayList<>();

list2.add(new BaseStudent("王五", 25));

list2.add(new BaseStudent("赵六", 26));

list1.addAll(list2);

Basestudent继承了student,但是basestudent也可以放进去比较

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值