Phase2 DAY4 List&泛形

总结

Collection
  |-- List
    |-- ArrayList
    |-- Vector
      |-- Stack
    |-- LinkedList
  |-- Queue
     |-- Deque
       |-- LinkedList

LinkedList

LinkedList 实现了接口 List, Deque

  • 底层是链表, 增删快, 查找慢
  • 不同步, 线程不安全, 效率高
构造方法

LinkedList() 
          构造一个空列表。 
LinkedList(Collection<? extends E> c) 
          构造一个包含指定 collection 中的元素的列表,这些元素按其 collection 的迭代器返回的顺序排列。 
/
LinkedList 特有的API

public void addFirst(E e)addLast(E e)
public E getFirst()getLast() 
public E removeFirst()public E removeLast()
//
Deque接口实现的方法

     第一个元素(头部)                  最后一个元素(尾部)
     抛出异常       特殊值         抛出异常        特殊值
插入 addFirst(e)   offerFirst(e)  addLast(e)   offerLast(e)
移除 removeFirst() pollFirst()    removeLast() pollLast()
检查 getFirst()    peekFirst()    getLast()    peekLast()

栈的API
void push(E e)
E pop()
E peek()
///
ListIterator<E> listIterator(int index) 
          返回此列表中的元素的列表迭代器(按适当顺序),从列表中指定位置开始 
Iterator<E> descendingIterator() 
          返回以逆向顺序在此双端队列的元素上进行迭代的迭代器 
boolean removeFirstOccurrence(Object o) 
          从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。  
boolean removeLastOccurrence(Object o) 
          从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表时)。 
          

继承Vector的栈Stack

API:
    boolean empty()
    E peek()
    E pop()
    E push(E item)
    int search(Object o)

但是不推荐使用Vector的栈,而推荐使用Deque的栈,因为:

  • Stack继承Vector的, 效率比较低
  • Stack具有Vector所有的API, 可以在任意位置添加和删除, 使用起来不是很安全。

Vector的栈


问题:哪一端当作栈顶比较好?尾

MyStack持有了Vector对象,因此,它就可以具有Vector的所有功能,并且可以对这些功能进行"加强".

哪些技术可以对一个方法进行加强?
    a. 继承
    b. 组合
哪一种方式比较好?
    设计原则:组合优先于继承。
什么时候可以使用继承
    类与类之间是 is a 的关系的时候。
 
public class MyStack {
    private Vector vector; //组合

    public MyStack(Vector vector) {
        this.vector = vector;
    }

    public void push(Object obj){
        vector.add(obj);
    }

    public Object pop() {
        return vector.remove(vector.size() - 1);
    }

    public Object peek() {
        return vector.get(vector.size() - 1);
    }

    public boolean isEmpty() {
        return vector.isEmpty();
    }

}

去重练习

思路1:
    a. 新建一个List
    b. 遍历原集合,拿到每一个元素
        如果在新集合中已经存在, 不添加
        如果在新集合中不存在, 添加
        
public static List removeDuplicated(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;
 }


思路2:
    拿到第一个元素, 然后删除后面所有与第一个元素相等的元素
    拿到第二个元素, 然后删除后面所有与第二个元素相等的元素...

 public static void removeDuplicated(List list) {
        // int size = list.size();
        for(int i = 0; i < list.size(); i++) {
            Object obj = list.get(i);
            for (int j = i + 1; j < list.size(); j++) {
                if (obj.equals(list.get(j))) {
                    list.remove(j);
                    j--; // caution! 需要回退一步
                }
            }
        }
    }

泛形

概念
 有许多原因促成了泛型的出现,而最引人注目的一个原因,就是为了创造容器类。有些情况下,我们确实希望容器能够同时持有多种类型的对象。但是,通常而言我们只会使用容器来存储一种类型的对象。
 泛型的主要目的之一就是用来指定容器要持有什么类型的,因此与其使用Object,我们更喜欢暂时不指定类型,而是稍后再決定具体使用什么类型。要达到这个目的,需要使用类型参数,用尖括号括住,放在类名后面。然后在使用这个类的时候,再用实际的类型替换此类型参数。
 如果不申明泛型的类,JAVA则会自动使用Object来描述创建的对象,使用数据时必须进行强制类型转换,存在安全隐患。


泛型类
格式:public class 类名<泛型类型1,>
泛型的命名
    语法规则:只要满足标识符的规则即可
    业界规则:用大写的字母表示
        T   type
        E   element
        K   key
        V   value
        U

public class Tool<T> {// T就是泛型(类型形参), 泛型定义在类上, 作用域就是整个类
    T obj;

    public T getObj() {
        return obj;
    }

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



泛型接口
格式:public  interface 接口名<泛型类型1>
泛型接口的实现类
    a. 泛型类
    b. 普通类
    
public interface Auto<T> { //泛型定义在接口上面, 作用域就是整个接口
    T run(T t);
}


泛形方法

public static<T> void fun(Messages temp){
        System.out.println(temp.getMsg());
    }
    


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

设计原则:尽早失败原则
    a.节省计算资源
    b.容易定位问题

public class GenericDemo1 {
    public static void main(String[] args) {
       
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("world");
        list.add("java");
        dosomething(list);
    }

    private static void dosomething(List list) {
        for(Iterator it = list.iterator(); it.hasNext(); ) {
            String s = (String) it.next(); // ClassCastException
            System.out.println(s.toUpperCase());
        }
    }//不使用泛形需强制转换

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



问题:
    String是Object的子类吗? 是
    String[]是Object[]的子类吗? 不是
	为了代码书写方便, JVM对数组类型进行了特殊处理。 但是同时也引入了一些问题。
	数组是一个可协变类型。
	集合不是一个可协变类型。
public static void main(String[] args) {
        String s = "hello";
        Object obj = s;
        String[] strs = new String[10];
        Object[] objs = strs;
        Date[] dates = strs;//error
        objs[1] = new Object(); // ArrayStoreException*/

        List<String> strs = new ArrayList<>();
        List<Object> objs = strs;//error
    }




泛型通配符
 利用泛型技术虽然解决了向下转型所带来的安全隐患,但同时又会产生一个新的问题:即便是同一个类,由于泛形设置类型不同,其对象表示的含义也不一样,因此不能直接进行引用操作.

例如
public static void fun(Messages<String> temp){
	此方法只能用来接收String类型的对象
        System.out.println(temp.getMsg());
}

public static void fun(Messages<?> temp){
	此方法可以接受任意类型的的对象
        System.out.println(temp.getMsg());
}


class Messages<T>{
    private T msg;

    public T getMsg() {
        return msg;
    }

    public void setMsg(T msg) {
        this.msg = msg;
    }
}

一个问题:能否使用Object描述一切泛型,或者不设置类型?

  • 问题的关键是:在明确设置一个类为泛型类型时若没有继承的概念范畴,也就是说虽然Object类与String类属于父子关系,但在泛型中就是两个完全独立的概念
  • 如果在定义时不设置类型,也可以实现任意泛型类对象的就收,但存在一个问题:不指派类型,JAVA则默认Object,也就是说方法中可以随意修改接收到的对象的属性内容,例如:
	public static void main(String[] args) {
        Messages<Integer> m1 = new Messages<>();
        m1.setMsg(100);
        fun(m1);
    }
    public static void fun(Messages temp){
        temp.setMsg("我把你Integer改成String了牛不牛皮");
        System.out.println(temp.getMsg());
    }
    此时输出的就是String类,Integer被改了属性
  • 上述方法就是非常不严谨的,所以必须要使用通配符<?>来制约这种随意修改数据的问题,故被通配符修饰的类型只可表示取出,不能设置内容,否则编译器报错.

设置泛型上下限

  • ? extends E
    设置上限,E及其子类

  • ? super E
    设置下限,E及其父类


泛型通配符:提供类似数组的功能,但是同时要避免数组可能存在的问题。

        Collection<Object> c1 = new ArrayList<Object>();//error
        Collection<Object> c2 = new ArrayList<Animal>();//error
        Collection<Object> c3 = new ArrayList<Dog>();//error
        Collection<Object> c4 = new ArrayList<Cat>();//error

        Collection<?> c1 = new ArrayList<Object>();
        Collection<?> c2 = new ArrayList<Animal>();
        Collection<?> c3 = new ArrayList<Dog>();
        Collection<?> c4 = new ArrayList<Cat>();
        c2.add("string");//error

        Collection<? extends Animal> c1 = new ArrayList<Object>();//error
        Collection<? extends Animal> c2 = new ArrayList<Animal>();
        Collection<? extends Animal> c3 = new ArrayList<Dog>();
        Collection<? extends Animal> c4 = new ArrayList<Cat>();
        c3.add(new Animal());//error
        c3.add(new Dog());//error
        c3.add(new Cat());//error

        Collection<? super Animal> c1 = new ArrayList<Object>();
        Collection<? super Animal> c2 = new ArrayList<Animal>();
        Collection<? super Animal> c3 = new ArrayList<Dog>();//error
        Collection<? super Animal> c4 = new ArrayList<Cat>();//error
        c2.add(new Animal());
        c2.add(new Object());//error

练习:往List类型的对象中,插入一个整数。
   如何解决这个问题?我们可以利用反射绕过泛型检查

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

        Class<? extends List> cl = list.getClass();
        Method addMethod = cl.getDeclaredMethod("add", Object.class);
        addMethod.invoke(list, 1);
        System.out.println(list);

       /* String s = list.get(3);
        System.out.println(s);*/无法获取

        如何获取这个值呢?继续通过反射
        Method getMethod = cl.getDeclaredMethod("get", int.class);
        int value = (int) getMethod.invoke(list, 3);
        System.out.println(value);

数组和集合的转换

        List<String> list = new ArrayList<>();
        list.add("刘亦菲");
        list.add("茜茜");
        list.add("赵灵儿");

        String[] arr = new String[3];
        String[] strs = list.toArray(arr);
        System.out.println(Arrays.toString(strs));//[刘亦菲, 茜茜, 赵灵儿]
        System.out.println(strs == arr); // true*/

        String[] arr = new String[1];
        String[] strs = list.toArray(arr);
        System.out.println(Arrays.toString(strs));//[刘亦菲, 茜茜, 赵灵儿]
        System.out.println(Arrays.toString(arr));//[null]
        System.out.println(strs == arr);//false

        String[] arr = new String[list.size()];
        list.toArray(arr);
        System.out.println(Arrays.toString(arr));[刘亦菲, 茜茜, 赵灵儿]

增强形循环:foreach


foreach循环:
作用:简化数组和Collection集合的遍历
格式:
for(元素数据类型 变量 : 数组或者Collection集合) {
	使用变量即可,该变量就是元素
}

好处:简化了数组和集合的遍历操作
坏处:
    a. 不能获取索引信息
    b. 不能修改集合和数组
    
建议:
    尽量使用foreach循环, 简洁易懂。
    
原理:
    编译器对数组单独进行处理
    集合的foreach循环底层就是迭代器

问题:什么情况下可以使用foreach循环呢?
    数组, 集合
    实现了Iterable接口的对象, 都可以使用foreach循环

Iterable: (JDK1.5)
    实现这个接口允许对象成为 "foreach" 语句的目标。
    API:
        Iterator<T> iterator()
        
// 遍历数组

一般方法:
        int[] arr = {1, 2, 3};
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
        
foreach方法:
        for(int a : arr) {
            System.out.print(a + " ");
        }
        System.out.println();

 // 遍历集合

        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("world");
        list.add("java");
        
一般方法:
        for(Iterator<String> it = list.iterator(); it.hasNext(); ) {
            String s = it.next();
            System.out.print(s + " ");
        }
        System.out.println();
        
foreach方法:
        for(String s : list) {
            System.out.print(s + " ");
            // if ("hello".equals(s)) list.remove(s);//reeor
        }
        System.out.println();
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值