week06_day04_List子类&&泛型

List的子类
下面我们所讲的每个方法都是某个类所特有的方法,如继承自父类的add、remove这种方法,都不讲。

ArrayList
特性:
底层数据结构是数组,增删慢,查找快。
不同步, 线程不安全, 效率高。
存储null元素
容量会自动扩容

构造方法:
ArrayList(): 默认初始大小为10
ArrayList(int initialCapacity): 可以指定数组的初始大小
ArrayList(Collection c):将集合c中的元素传入ArrayList中

API:
void ensureCapacity(int minCapacity):
作用:避免频繁扩容。
如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。
应用场景是刚开始我们不知道存储多少元素,用ArrayList():的构造方法去初始化了一个ArrayList,但到了某个时候,知道应该存多少元素了,就用这种方法。
void trimToSize():
将此 ArrayList 实例的容量调整为底层数组的实际存储元素的大小。
慎用,确保元素不会在添加的情况下用。

········································································································································································································

Vector在Collection提出之前就有了
Vector
特性:
底层是数组,增删慢,查找快
同步, 线程安全, 效率比较低
存储null元素

API:
Vector的很多API名字起得都很繁琐,它的API其实就类似于右侧—>后的collection中的API

void addElement(E obj)                  -->     void add(E e)
void copyInto(Object[] anArray)         -->     Object[] toArray()
E elementAt(int index)                  -->     E get(int index)
void insertElementAt(E obj, int index)  -->     void add(int index, E e)
void removeAllElements()                -->     void clear()
boolean removeElement(Object obj)       -->     boolean remove(Object obj)
void removeElementAt(int index)         -->     E remove(int index)
void setElementAt(E obj, int index)     -->     E set(int index)
Enumeration<E> elements()               -->     Iterator iterator()

int capacity()     //返回此向量的当前容量。默认也是10
void setSize(int newSize)        //设置此向量的大小。
capacity和size不一样,capacity是Vector占用的总空间的大小,而size是Vector中真正存储元素的总空间的大小

E firstElement()
E lastElement()
//返回第一个和最后一个元素,如果Vector为空,报错:NoSuchElement

int indexOf(Object o, int index)
//从index开始向后查找元素o,返回查到的第一个o的索引下标
int lastIndexOf(Object o, int index)
//从index开始向前查找元素o,返回查到的第一个o的索引下标

Enumeration<E> elements()

接下来重点看一下 Enumeration elements()这个方法,Enumeration是一个接口,这是一个古老的接口。其实可以类比Iterator这个接口。但是实际开发中还是用Iterator遍历Vector吧。
在这里插入图片描述
Enumeration: --> Iterator
boolean hasMoreElements() --> boolean hasNext()
E nextElement() --> E next()

public class VectorDemo2 {

    public static void main(String[] args) {
        
        Vector vector = new Vector();
        vector.add("hello");
        vector.add("world");
        vector.add("java");

        for(Enumeration e = vector.elements(); e.hasMoreElements(); ) {
            String s = (String) e.nextElement();
            System.out.println(s);
        }

        for(Iterator it = vector.iterator(); it.hasNext(); ) {
            String s = (String) it.next();
            System.out.println(s);
        }
    }
}

········································································································································································································

栈和队列:
在这里插入图片描述
········································································································································································································

LinkedList
LinkedList实现了Deque接口。
关于Deque(双端队列)
在这里插入图片描述
addFirst表示插入时队列为空会抛出异常。
removeFirst和getFirst表示取队列元素或移除队列元素时队空会抛出异常。
offerFirst表示插入队头时,队满会返回一个特殊值null。
pollFirst和peekFirst表示取队列元素或移除队列元素时队空会返回一个特殊值null。
后面两列和前面一样,只不过是在队尾进行操作。
在这里插入图片描述
在这里插入图片描述
我们发现,一个双端队列可以模拟栈和队列,可是竟然发现Deque没有isEmpty方法。
发现Deque继承自Queue接口,Queue接口继承自Collection接口,而Collection接口中有isEmpty方法。

Deque接口
概述:双端队列,可以在两端插入和删除
它包含了栈和队列的API,当你在一端插入一端删除时,就是队列
当你在一端进行插入删除时,就是队列。

LinkedList implements List, Deque
特性:
底层数据结构是链表,增删快,查找慢
不同步, 线程不安全, 效率高
允许null元素
实现了Deque这个接口,可以当作栈,队列和双端队列来使用

构造方法:
LinkedList() //默认初始大小为10
LinkedList(Collection c) //将集合c中的元素复制到LinkedList中

API:
Iterator descendingIterator() //逆向遍历LinkedList中的元素
boolean removeFirstOccurrence(Object o)
//从此双端队列移除第一次出现的指定元素。
boolean removeLastOccurrence(Object o)
//从此双端队列移除最后一次出现的指定元素。

在两端的操作
boolean offerFirst
boolean pollFirst
boolean peekFirst

栈的API:
void push(E e)
E pop()
E peek()
注意事项:Java中提供了Stack类,但是我们应该优先使用Deque, 而不应该使用Stack

为什么?
a. Stack同步的, 效率相对来说比较低。
b. Stack继承了Vector, 所以Stack拥有Vector中所有的方法, 使用起来不安全。
Stack extends Vector

Stack extends Vector,所以Stack不仅有自己的API,还有继承自Stack的API,那Stack就可以在任意指定的位置删除和插入元素了。
而Deque只能在两端进行操作,会比Stack安全很多。
Stack和Vector在java1.0时就有了,在很早以前设计的。
应当少用继承,多用组合。

······································································································································································································

练习1:去重

package com.cskaoyan.exercise;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

/*
1.去重
输入:[a, b, c, a, a, b, c]
输出:[a, b, c]

思路1:
1. 新键一个List result = new ArrayList();
2. 遍历原来的list, 判断元素是否在result中存在
   存在:不添加
   不存在:添加
3. 遍历结束后, 返回result

思路2:
1. 逆序遍历list
2. 获取元素, 截取从0到nextIndex的子列表,判断元素是否在子列表中存在
   存在:删除元素
   不存在:
3. 遍历结束后,完成去重
 */
public class Ex2 {
    
    public static List disctinct1(List list) {
        List result = new ArrayList();
        for (Iterator it = list.iterator(); it.hasNext(); ) {
            Object obj = it.next();
            if (!result.contains(obj)) result.add(obj);
        }
        return result;
    }

    public static void disctinct2(List list) {
        for (ListIterator it = list.listIterator(list.size()); it.hasPrevious(); ) {
            Object previous = it.previous();
            //subList(fromIndex,toIndex)  范围[fromIndex,toIndex)
            if (list.subList(0, it.nextIndex()).contains(previous)) {
                it.remove();
            }
        }
    }

    public static void main(String[] args) {
        // [a, b, c, a, a, b, c]
        List list = new ArrayList();
        list.add('a');
        list.add('b');
        list.add('c');
        list.add('a');
        list.add('a');
        list.add('b');
        list.add('c');
        // List result = disctinct1(list);
        disctinct2(list);
        System.out.println(list);
    }
}

练习2:请用ArrayList实现栈数据结构,并测试。

以下代码这样的设计方式叫组合

a. 如果一个类持有某个类的成员,那么就能够"拥有"这个类的所有公共方法。(像继承)
但是我们可以对这些方法进行限制。
b. 组合还可以"加强"另一个的方法,如下面的push方法。
c. 可以组合多个对象

"加强"一个方法,既可以用 继承,也可以用 组合
继承只能单一继承,组合可以组合多个对象,如我还可以设计一个String类对象,在里面调用String的成员方法。

设计原则:
优先使用组合,而不是继承

什么情况下,可以使用继承呢?
如果两个类之间有"is a"的关系,可以使用继承。

GO语言不支持继承。

为啥不用继承,就向上一个代码中 Stack继承了Vector, 所以Stack拥有Vector中所有的方法, 使用起来不安全。
栈只要有在栈头插入和删除元素就行了,你却让有一个父类中的add(int index, E element) 方法,让Stack可以在任意位置插入元素。

下面两个练习:
3. 集合的嵌套遍历
4. 获取10个1-20之间的随机整数,要求集合中的数不能重复
留给晚上作业。

······································································································································································································

JDK5新特性

一、静态导入
导包
必须导入到这一级别
作用:导入的类就好像定义在当前这个包下面。
静态导入:
必须导入到方法这一级别,并且必须是静态方法。
作用:导入的静态方法就好像定义在当前这个类中一样。
推荐:不要使用静态导入,可读性差。
别人以为你的sqrt方法是自己定义的

面试题:
总结:static的用法有哪些?
a. 静态代码块:对类进行初始化,类加载的时候执行,并且只执行一次
b. 静态变量:该变量是类所有,被该类的所有成员共享
c. 静态方法
d. 静态内部类
e. 静态导入:导入静态方法

package com.cskaoyan.jdk5;

import java.util.Date;
//静态导入
//使用Math的所用方法用import static java.lang.Math.*;
//使用Math的某一个方法用import static java.lang.Math.sqrt;
import static java.lang.Math.*;
 
public class StaticImportDemo1 {

    public static void main(String[] args) {
        
        //导包的两种方法
        java.util.Date date = new java.util.Date();
        Date date2 = new Date();

        //静态导入
        System.out.println(sqrt(1.0));
        System.out.println(abs(-100));
        System.out.println(max(2, 3));
    }
}

······································································································································································································

泛型
不用泛型的话,会发现list里面就算添加了个1,编译时也不会报错,而运行时则会报:ClassCastException 类型转换异常。

public class GenericDemo1 {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("hello");
        list.add("world");
        list.add("java");
        //编译时不会报错
        list.add(1);

        doSomething(list);
    }

    private static void doSomething(List list) {
        for(Iterator it = list.iterator(); it.hasNext(); ) {
            String s = (String) it.next(); // ClassCastException
            s.toUpperCase();
        }
    }
}

用了泛型后,就不允许你add(1)。更不容易出错。

public class GenericDemo1 {

    public static void main(String[] args) {

        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("world");
        list.add("java");
        // list.add(1);
        doSomething(list);
    }

    private static void doSomething(List list) {

        for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
            String s = it.next();
            System.out.println(s.toUpperCase());
        }
    }
}

泛型的好处:
a. 提高了程序的安全性
b. 将运行期遇到的问题转移到了编译期
c. 省去了类型强转的麻烦

设计原则:
及早失败原则(如果你的程序有问题,就让他尽早把问题显示出来)
这样方便你尽早定位出错的原因。就向扁鹊见蔡恒公,及早发现,及早治疗

对大家的要求:
a. 可以利用泛型操作集合
b. 能够看懂别人些的泛型代码

······································································································································································································

泛型类
把泛型定义在类上
格式:public class 类名<泛型类型1,…>
注意:参数化类型必须是引用类型

package com.cskaoyan.jdk5;
/*
泛型类:
    泛型定义在类上面
    作用域:整个类
    格式: public class 类名<泛型类型1,…>

泛型的命名规则:
    必须满足标识符的规则
业界规范:
    一般用大写字母表示
    T:type
    E:element
    K:key
    V:value
 */
public class Tool2<T> { // 定义泛型, T就是泛型, 类似于形式参数
    T obj; // 使用泛型

    public T getObj() {
        return obj;
    }

    public void setObj(T obj) {
        this.obj = obj;
    }
}

package com.cskaoyan.jdk5;
/*
创建对象的时候指定类型:
    默认是Object类型

JDK7特性:<>棱形操作符,可以利用类型推断机制。
 */
public class GenericDemo2 {

    public static void main(String[] args) {

        // String是参数化类型, 类似实参
        //就用下面这行代码初始化一个泛型类对象就行。
        Tool2<String> tool2 = new Tool2<>();
        tool2.setObj("hello");
        // tool2.setObj(1);
        String s = tool2.getObj();
        System.out.println(s);

        Tool2<Object> tool1 = new Tool2();
        tool1.setObj("hello");
        Object obj = tool1.getObj();
    }
}

······································································································································································································

泛型方法
把泛型定义在方法上
格式:public <泛型类型> 返回类型 方法名(泛型类型 形参…)

为什么泛型要定义在返回值类型的前面呢?
因为返回值类型也可以是泛型,泛型必须先定义才能使用

问题:
有泛型方法的类一定是泛型类吗? 不是

类似这种问题,定义多个相似的方法,怎么改进?

public class Tool3 {

    public String echo(String s) {
        return s;
    }

    public Date echo(Date s) {
        return s;
    }
}

没有泛型之前可以这样做,缺点是需要强制类型转换

public class Tool3 {

    public Object echo(Object obj) {
        return obj;
    }
}

有了泛型之后:

public class Tool3 {

    // T t;
    //泛型方法中的<T>是根据实参的类型来定义的
    //传给他的是String,T就代表String
    public <T> T echo(T t) {
        return t;
    }
}

······································································································································································································

泛型接口
把泛型定义在接口上
作用域:接口内
格式:public interface 接口名<泛型类型1…>

package com.cskaoyan.jdk5;

public interface Auto<T> {
    T run(T t);
}

泛型的实现类:
1.普通类

package com.cskaoyan.jdk5;

public class Car implements Auto<String> {

    @Override
    public String run(String s) {
        return null;
    }
}

2.泛型类

package com.cskaoyan.jdk5;

//第一种情况,Auto中传递的是具体的参数
/*public class Bus<T> implements Auto<String> {
    @Override
    public String run(String s) {
        return null;
    }
}*/

//第二种情况,Auto中传递的是具体的参数
// Bus<T>是定义泛型T, Auto<T>是使用泛型T
//这两个T是同一个T
//创建对象时,就会给第一个T赋值,赋的值就会传递给第二个T
//然后第二个T会传递给泛型接口Auto中的T
public class Bus<T> implements Auto<T> {
    @Override
    public T run(T t) {
        return null;
    }
}

······································································································································································································

泛型通配符:

① 泛型通配符<?>
任意类型,如果没有明确,那么就是Object以及任意的Java类了
② ? extends E
向下限定,E及其子类
③ ? super E
向上限定,E及其父类

引入:
数组:
问题1:String是Object的子类吗? 是
问题2:String[]是Object[]的子类吗? 不是
父类的引用变量可以指向子类的对象。

JVM对数组进行特殊"照顾",但是这样也会引入一些问题。

public class GenericDemo4 {

    public static void main(String[] args) {
        String[] strs = {"hello", "world", "java"};
        Object[] objs = strs; // JVM对数组进行特殊"照顾"
        objs[1] = new Date(); // ArrayStoreException
        //编译时不报错,运行时会报错,因为不能在String数组里添加Date元素

		
        List<String> strs2 = new ArrayList<>();
        strs2.add("hello");
        strs2.add("world");
        strs2.add("java");
        
        //对于泛型,这样做是不允许的
        //List<Object> objs2 = strs;
    }
}

泛型通配符:
目的:提供类似数组的功能,但是不要引入数组中可能出现的问题。

public class GenericDemo5 {

    public static void main(String[] args) {
        Collection<Object> c1 = new ArrayList<Object>();

        //根据引例代码,下面三行代码编译时不会通过的
        /*Collection<Object> c2 = new ArrayList<Animal>();
        Collection<Object> c3 = new ArrayList<Cat>();
        Collection<Object> c4 = new ArrayList<Dog>();*/

        //而将Object改成?就能解决问题
        Collection<?> c11 = new ArrayList<Object>();
        Collection<?> c2 = new ArrayList<Animal>();
        Collection<?> c3 = new ArrayList<Cat>();
        Collection<?> c4 = new ArrayList<Dog>();
        //但是为了防止发生错误,不能添加元素。因为我只知道集合中存的元素是Object子类,
        // 万一我指向的是cat类(如c3),但你添加的是一个dog类,就会出错。
        // c2.add(new Dog());
        // c2.add(new Animal());

       // Collection<? extends Animal> c13 = new ArrayList<Object>();
        Collection<? extends Animal> c23 = new ArrayList<Animal>();
        Collection<? extends Animal> c33 = new ArrayList<Cat>();
        Collection<? extends Animal> c43 = new ArrayList<Dog>();
        //依然不可以添加元素,因为我只知道集合中存的元素是Animal子类,
        //万一我指向的是cat类(如c33),但你添加的是一个dog类,就会出错。
        /*c3.add(new Animal());
        c3.add(new Dog());
        c3.add(new Cat());*/

        Collection<? super Animal> c14 = new ArrayList<Object>();
        Collection<? super Animal> c24 = new ArrayList<Animal>();
        // Collection<? super Animal > c3 = new ArrayList<Cat>();
        // Collection<? super Animal> c4 = new ArrayList<Dog>();
        //可以添加Animal和Animal子类对象,因为我知道里面的元素是Animal或Animal父类
        //就可以指向Animal或Animal子类对象
        c24.add(new Animal());
        c24.add(new Dog());
        c24.add(new Cat());
        // c2.add(new Object());
    }
}

class Animal {
}

class Dog extends Animal {
}

class Cat extends Animal {
}

······································································································································································································

作业:

  1. 生成20个 1-20 的随机整数,把其中不重复的整数存入 List 集合中(相同的整数只存一个)。
public class Homework02 {

    public static void main(String[] args) {

        ArrayList<Integer> list = new ArrayList<>(20);
        for (int i = 0; i < 20; i++) {
            int k = (int) (Math.random() * 20 + 1);
            if (!list.contains(k)) {
                list.add(k);
            }
        }

        System.out.println(list);
    }
}

或者

public class Homework02_teacher {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        Random r = new Random();
        for (int i = 0; i < 20; i++) {
            int num = r.nextInt(20) + 1;
            if (!list.contains(num)) list.add(num);
        }
        System.out.println(list);
    }
}

关于Math.random()和new random()
https://www.cnblogs.com/ningvsban/p/3590722.html

  1. 用双向链表实现LRU,要求可以指定缓存大小,并且可以存储任意类型的数据。
    (要求用泛型,只需要实现添加方法即可)。

public class LRU<E> {

    private static final int DEFAULT_CAPACITY = 100;
    private int capacity;
    private int size;
    private Node head = new Node(null); //Dummy node
    private Node end = new Node(null); //Dummy node

    public LRU() {
        head.next = end;
        end.prev = head;
        capacity = DEFAULT_CAPACITY;
    }

    public LRU(int capacity) {
        if (capacity <= 0) {
            throw new IllegalArgumentException("capacity=" + capacity);
        }
        head.next = end;
        end.prev = head;
        this.capacity = capacity;
    }

    private class Node {
        E value;
        Node prev;
        Node next;

        public Node(E value) {
            this.value = value;
        }

        public Node(E value, Node prev, Node next) {
            this.value = value;
            this.prev = prev;
            this.next = next;
        }
    }

    public void add(E element) {
        Node node = head.next;
        while (node != end) {
            if (node.value == element) break;
            node = node.next;
        }
        // 存在
        if (node != end) {
            // 删除node
            node.prev.next = node.next;
            node.next.prev = node.prev;
            // 在头部添加
            node.prev = head;
            node.next = head.next;
            head.next.prev = node;
            head.next = node;
        } else {
            Node x = new Node(element, head, head.next);
            // 不存在,且容量未满
            if (size < capacity) {
                head.next.prev = x;
                head.next = x;
                size++;
            } else {
                // 不存在,且容量满了
                // 删除最后一个元素
                end.prev = end.prev.prev;
                end.prev.next = end;
                // 在头部添加
                head.next.prev = x;
                head.next = x;
            }
        }
    }
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-玫瑰少年-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值