java进阶-第八讲 泛型、集合

java进阶-第八讲 泛型、集合

1 什么是泛型(Generic)

public class Node {
    int elment;
    Node next;

    public Node() {
    }

    public Node(int elment, Node next) {
        this.elment = elment;
        this.next = next;
    }
}


..............
public class LinkList {
    Node head;
    int size = 0;
    // 顺序添加,也就是在尾结点上添加元素
    void add(int elem) {
        if (head == null) {
            head = new Node(elem, null);
        }
        Node current = head;
        while (current.next != null) {
            current = current.next;
        }
        current.next = new Node(elem,null);
        size++;
    }
}
..................
public class TestList {

    public static void main(String[] args) {
        LinkList linkList = new LinkList();
        linkList1.add(1);
        linkList1.add(1);
        linkList1.add(1);
        linkList1.add(1);
        linkList1.add(1);
        linkList1.add(1);
        System.out.println(linkList1.size);
    }
}
  • 以上是简单的单链表,我们构造的链表中只能存放int型的数据,如果我们想要存放String类型的数据,那么我们就需要修改代码或者是重新构造一个链表。这样做不通用。
  • 要使得程序变得通用,我们就要使用泛型(jdk1.5新特性)
  • 具体的做法如下:
public class Node<T> {

    T elment;

    Node next;

    public Node() {
    }

    public Node(T elment, Node next) {
        this.elment = elment;
        this.next = next;
    }
}


..............
public class LinkList<T> {
    Node head;
    int size = 0;
    // 顺序添加,也就是在尾结点上添加元素
    void add(T elem) {
        if (head == null) {
            head = new Node(elem, null);
        }
        Node current = head;
        while (current.next != null) {
            current = current.next;
        }
        current.next = new Node(elem,null);
        size++;
    }

    public T get(T elem) {
        if (head == null) {
            throw new NullPointerException("链表不存在");
        }
        Node curr = head;
        while (curr.next != null) {
            if (curr.elment != elem) {
                curr = curr.next;
            }else if (curr.elment == elem){
                break;
            }
        }
        return (T)curr.elment;
    }
}
..................
public class TestList {

    public static void main(String[] args) {
        LinkList<String> linkList = new LinkList<>();

        linkList.add("a");
        linkList.add("b");
        linkList.add("a");
        linkList.add("a");
        linkList.add("a");
        linkList.add("a");

        System.out.println(linkList.size);

        LinkList<Integer> linkList1 = new LinkList<>();
        linkList1.add(1);
        linkList1.add(1);
        linkList1.add(1);
        linkList1.add(1);
        linkList1.add(1);
        linkList1.add(1);
        System.out.println(linkList1.size);
    }
}
什么是泛型:
    泛型就是类型参数化。也就是说,SUN公司给java程序员提供了一个机制,这种机制和方法参数的传递类似。他们把类型(基础类型、引用类型)做成了一个类似变量的东西(类型参数 T V K),使得我们可以给这个变量传类型。
    T ---> 基础数据类型 Object Stering int[]
思考问题:
	程序写好以后,要编译,问,给类型参数T传递一个值(类型名)比如:Object,这个过程在哪个阶段发生?运行时还是编译时?编译时干了什么,运行时又干了什么?
	编译的时候把所有的T都替换了,替换成什么了?Object
	因为java中就没有"T V K"之类的类型,所以编译的时候就做好替换。
	运行时其实应该是多态的机制在起作用。
	这叫做类型擦除。
	思考,泛型在哪里不能使用?static修饰的变量或者是方法,不能使用泛型。static修饰的是没有多态的。static修饰的只有一种形态,叫做静态。
	
	注意:<T>这种形式,其实是泛型的声明,一般出现在类名后,或者是方法返回值类型的位置。
	在方法返回值类型的位置出现<T>T
	以上的方式是自定义泛型,也可以直接<具体的类型名>
	比如<Object> <String>
	如果是具体的类型,不能是基础数据类型,类型名一般推荐使用大写字母,
	不推荐使用小写字母或者是单词,更不能使用关键字。

2 集合(重点,超级重点)

集合就是一种容器,可以装很多东西。
在java中,集合就是数据结构,其实就是工具。比如链表、顺序表、树等...
集合在java中,只能存放引用,不存放具体的值。
集合:Collection
Interface Collection<E> 是一个接口,它的父接口是Iterable(可迭代的,可遍历)
Collection的继承关系:

在这里插入图片描述

3 先学习ArrayList

补充一个关键字的介绍:transient 该属性不被序列化
跟着API帮助文档,和源码学习:
	看源码,不是所有的代码都要能看懂。要看一些关键的代码。
	要注意源码中的成员属性,尤其要注意一些有意义的常量,这些是以后面试常问
	笔试常考的内容。同时还要看注释。
	如果要了解一个类,就概要的了解一下这个类的源码。
	java可以对接口多实现,最重要的接口一般都在最前面。
	要了解一个类的实现方式,首先看成员属性,如果成员属性中没有具体的信息
	要追溯到父类中去看。
	
ArrayList类源码:

	private static final int DEFAULT_CAPACITY = 10;// 默认的初始化容量
	private static final Object[] EMPTY_ELEMENTDATA = {};// 空数组
	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	成员属性:
		transient Object[] elementData;
		这说明ArrayList底层(这就是说ArrayList要构造对象,对象中的内容是什么?)
		是Object[] elementData;
		private int size;// 这应该是数组中元素的个数
构造方法:
	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    这是一个无参构造,用无参构造构造出来的ArrayList对象,初始化容量是多大?
    有无参构造构造出来的ArrayList对象,初始化容量大小就是 "0"
   	解释如下图:

在这里插入图片描述

  • 补充泛型:
<? extends E>
	1.?和E都是引用类型,只要引用类型,就可能存在继承关系
	2.? extends E 说明,? 所代表的类型,继承自E这个类型
	3.? 表示不可知,不知道,在这里是个占位符,就是说?是表示不知道的任何类型
	4.因为有了extends之后,说明,这个?可以传递的类型参数是E的子类
	5.这个?是一个占位符,它占住了一个位置。只能是E的子类。
	6.也就是说,这个泛型的上界只能是E,也就是说这种语法规定了泛型参数的范围
	7.这里可以传入的参数只能是E或者是E的子类。

<? super E>:只能传入E或者是E的父类。这规定了类型参数的下界
public void test(Collection<? super T> c) {
        
    }	
  • 再来看ArrayList的有参构造
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

解析:
    如果传入的初始化容量大于0,那么就会在堆中new Object[initialCapacity]对象
    如果传入的参数为0,那么ArrayList中的Object[] elementData数组指向常量池
    中的EMPTY_ELEMENTDATA空元素的Object[]数组。
    如果小于0,直接抛出运行时异常:IllegalArgumentExceptio
    这是运行时异常,不强制要求处理。
  • 有参(>0的情况下)和无参构造的内存图

在这里插入图片描述

  • 第二个有参构造:通过集合的实现类来构造ArrayList
public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
这种用法不多,用得最多的是通过ArrayList来构造ArrayList
    具体的执行方式如下:
    (第一张图是指ArrayList作为参数的时候,已经有内容了。)
    (第二张图是指ArrayList作为参数的时候,调用了无参构造)
    A表示:EMPTY_ELEMENTDATA
    B表示:DEFAULTCAPACITY_EMPTY_ELEMENTDATA

在这里插入图片描述

在这里插入图片描述

  • 补充一个重要的发现:泛型的类型推断 infer arguments

泛型是可以进行类型推断的,钻石表达是就是推断类型用的
    ArrayList<String> al = new ArrayList<>();new ArrayList<>()中,"<>"砖石表达式中不需要有任何的类型
    编译器会自动推断出它的类型,它的类型推断的依据是ArrayList<String>中
    它的String类型参数
    
    
有上下界的泛型,它的推断机制如下:
 public ArrayList(Collection<? extends E> c) // 方法的原型   
ArrayList<Integer> arrList = new ArrayList<>();// 构造一个构造方法中传入的参数

ArrayList<String> arrayList2 = new ArrayList<>(arrList);
上面这个构造方法是通过ArrayList对象构造一个新的ArrayList对象
这条语句,编译报错,因为无法进行类型推断。"cannot infer arguments"

如果没有砖石表达式"<>",下面这条语句,编译能够通过,还能执行。
ArrayList<String> arrayList2 = new ArrayList(arrList);
但是,它不符合我们的要求,因为我们希望在new ArrayList(Conllection<? extends E> c)
    构造方法中,限定可以传入的类型参数,我们希望这个类型是String或者是String的子类
    但是,我们知道String没有子类
    所以这里只能传入String,不能传入Integer
    但是,如果没有砖石表达式,这里编译通过,说明,没有砖石表达式,根本不做类型推断
    
这种情况处理的方式有两种:
    第一种,确保传入的参数一定是E的子类或者是E,这样编译能过
    第二种,在方法参数之前的砖石表达式<>中传入具体的类型参数。
    ArrayList<String> arrayList2 = new ArrayList<String>(arrList);
这时,报错在参数列表中,有利于程序员的判断,知道是哪里出问题了。
    
    类型参数 引用类型名作为参数 String、Object等等作为参数
    参数类型 参数是String、Object等等类型
    
    new ArrayList<String>(arrList);这里是就近推断,也是就近原则。
    
    T[] oK
  • 记住,在使用泛型的时候,等号右边一定要带有砖石表达式。不带不行。

4 ArrayList中的add(E)方法:这是重点

  • 在ArrayList中的add方法运行的原理是: 初始化的时候如果调用无参构造,那么第一次调用add方法的时候,会扩容
  • 当add方法执行完毕以后,elementData数组的大小扩容为10
  • 当elementData中的元素个数为elementData.length 的时候,会再次扩容。
  • 也就是说,当数组满了的时候,会再次扩容。
  • 再次扩容为(当前长度+当前长度/2)
加入第一个元素
public boolean add(String e = "a") {
        ensureCapacityInternal((size = 0) + 1);  
    // 这里做完之后,使得elementData有10个空间。
        elementData[size++] = e; // elementData[0] = "a"; 
    //将元素加入elementData[0]的位置上
        return true;
    }

private void ensureCapacityInternal(int minCapacity = 1) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // true 判断是不是第一次加入元素
            minCapacity = Math.max(DEFAULT_CAPACITY = 10, minCapacity = 1); 
            // 10
        }

        ensureExplicitCapacity(minCapacity = 10);
    }

private void ensureExplicitCapacity(int minCapacity = 10) {
        modCount++; 
      // ArrayList中的属性,初值为0 自加1之后 为1

        // overflow-conscious code
        if (minCapacity = 10 - elementData.length = 0 > 0) // true
            grow(minCapacity = 10);
    }

private void grow(int minCapacity = 10) {
        // overflow-conscious code
        int oldCapacity = elementData.length = 0;
        int newCapacity = (oldCapacity = 0) + (((oldCapacity = 0) >> 1)=0); 
      //newCapacity = 0 
        if (newCapacity = 0 - minCapacity = 10 < 0) //true
            newCapacity = minCapacity = 10;
        if (newCapacity - MAX_ARRAY_SIZE > 0) //false
            newCapacity = hugeCapacity(minCapacity);
       
        elementData = Arrays.copyOf(elementData, newCapacity = 10); 
    	// elementData = copy[10]
    }

public static Object[] copyOf(Object[] original = elementData ,
                              int newLength = 10) {
        return (Object[]) copyOf(original = elementData, 
                                 newLength = 10, 
                                 original.getClass() = Object[].class);
    }


public static Object[] copyOf(
    Object[] original = elementData, 
    int newLength = 10, 
    Class<? extends Object[]> newType= Object[].class) {
        @SuppressWarnings("unchecked")
        Object[] copy = ((Object)newType == (Object)Object[].class)//true
            ? ( Object[]) new Object[newLength = 10]
            : ( Object[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original = elementData, 0, copy, 0,
                         Math.min(original.length = 0, newLength = 10));
                         //从原来的数组中拷贝了0个元素
        return copy;
    }

返回copy
    
private void grow(int minCapacity = 10) {       
   elementData = Arrays.copyOf(elementData, newCapacity = 10); 
    	// elementData = copy
    }    
elementData = copy 这时elementData中有了10个元素大小的空间,但是没有值
接着执行以下语句:
public boolean add(String e = "a") {
        ensureCapacityInternal((size = 0) + 1);  
    // 这里做完之后,使得elementData有10个空间。
        elementData[size++] = e; 
    // elementData[0] = "a"; 
    //将元素加入elementData[0]的位置上
        return true;
    }    
第一次调用add方法,完成
所得结果为:Object[] elementData 大小已经为10
且elementData[0] = "a";
内存图如下:

在这里插入图片描述

5 java中的位运算

">>" 带符号的右移 
2 >> 1 = 1
-4 >> 1 = -2

"<<" 带符号的左移
2 << 1 = 4
-4 << 1 = -8

"<<<" : 无符号左移
">>>":无符号右移
在程序设计中,位运算是最快的,%运算是效率是最差的
位运算效率最高 + > - > * > / > %
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值