Java集合与泛型
集合介绍
数组:一旦创建对象,分配内存空间,不可改变空间大小(无法动态扩容)
Collection:可以动态扩容,从而避免数组下标越界。缺点:扩容会消耗你的内存,所以使用集合比使用数组内存开销大。
集合体系结构
ArrayList集合
特征:本质是一个Object [] 数组
可以向集合放入元素,也可以从集合中获取元素,还可以删除集合中的元素
既然是一个数组,放入元素和获取元素(根据下标获取)速度快,删除首部元素和中间元素效率低
为什么要使用泛型?
你可以将任意类型的数据放入ArrayList,取出数据必须强制转换
泛型:能够在编译期对放入和取出的对象(元素)做类型检查
定义泛型:类型不确定,一旦你在某个类上定义了泛型,可以将泛型作为属性、参数、返回值
使用泛型:必须明确数据类型,数据类型必须在<>
中定义
什么时候使用ArrayList?
如果你需要频繁创建元素,频繁获取元素,可以使用
优点:创建和查询(获取)元素快
什么时候不能使用ArrayList?
当你频繁创建和频繁删除元素,不要使用ArrayList
缺点:删除首部元素和中间元素效率低
如何创建一个ArrayList类型的对象
使用new的方式创建
package com.whsxt.day5.arraylist;
import java.util.ArrayList;
import java.util.List;
public class TestArrayList1 {
public static void main(String[] args) {
System.out.println("start");
//ArrayList list = new ArrayList();
//ArrayList是一个List
//new ArrayList 是实实在在的对象,在堆内存中创建
// list 在栈内存中:用来操作堆内存中的ArrayList
// list遥控器 new ArrayList()电视机
List list = new ArrayList();
//调用ArrayList的add(e)方法,将对象放入ArrayList
//第一次调用list对象的add(e)方法添加元素,会在ArrayList内部的数组中分配10个空间的元素
list.add("tom1");
list.add("tom2");
list.add("tom3");
list.add("tom4");
list.add("tom5");
list.add("tom6");
list.add("tom7");
list.add("tom8");
list.add("tom9");
list.add("tom10");
//还有新元素添加到ArrayList中,不会出现“数组下标越界异常”
list.add("tom11");
list.add("tom12");
System.out.println("end");
}
}
String str1 = "a" + 1 + 2; // a12
String str2 = 'a' + 1 + "2"; // 982 (a的ASCll码为97)97+1+"2"=982
String str3 = 1 + 2 + "a"; // 3a
小结:
第一次调用list对象的add(e)方法添加元素,会在ArrayList内部的数组中分配10个空间的元素,当数组空间全部使用完毕,还有新元素添加到ArrayList中,不会出现“数组下标越界异常”,此时数组会扩容
ArrayList是一个可以动态扩容的数组
ArrayList扩容(重点)
当我创建ArrayList对象,里面的数组大小0,第一次调用add(E)方法,数组大小为10,当元素全部占满数组大小为10,如果继续添加元素,数组大小15
0---->10---->15--->22
int size =15;
int newCapacity= (size>>1)+size; 1111>>1 111+1111=22
扩容规律:原始长度>>1 + 原始长度
小结:
在调用add(e)方法添加元素之前,先判断数组有没有空间存储新元素,如果有不会扩容,如果没有才扩容
每次扩容ArrayList里面的Object elementData[] 指向都会发生改变
扩容机制:在原始数组的基础上重新拷贝一份新数组,此时新数组会存储原始数组里面的元素
import java.util.ArrayList;
import java.util.List;
public class TestArrayList1 {
public static void main(String[] args) {
System.out.println("start");
//ArrayList list = new ArrayList();
//ArrayList是一个List
//new ArrayList 是实实在在的对象,在堆内存中创建
// list 在栈内存中:用来操作堆内存中的ArrayList
// list遥控器 new ArrayList()电视机
List list = new ArrayList();
//调用ArrayList的add(e)方法,将对象放入ArrayList
//第一次调用list对象的add(e)方法添加元素,会在ArrayList内部的数组中分配10个空间的元素
list.add("tom1");
list.add("tom2");
list.add("tom3");
list.add("tom4");
list.add("tom5");
list.add("tom6");
list.add("tom7");
list.add("tom8");
list.add("tom9");
list.add("tom10");
list.add("tom11");
list.add("tom12");
list.add("tom13");
list.add("tom14");
list.add("tom15");
list.add("tom16");
System.out.println("end");
}
}
ArrayList其它方法
get(int index): 根据下标获取集合中的元素
size():获取集合实际的大小
remove(int index):根据下标从集合中删除元素
remove(E): 根据元素内容从集合中删除元素
contains(): 判断某个元素在集合中是否存在,true存在,false不存在
import java.util.ArrayList;
import java.util.List;
public class TestArrayList2 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("Tom");
list.add(123);
list.add(3.14);
list.add('M');
list.add(true);
//获取集合的第一个元素
Object element =list.get(0);
int size =list.size();
System.out.println(element); //5
System.out.println("删除之前 size="+size);
//删除集合中第一个元素
list.remove(0);
int size2=list.size(); //4
System.out.println("删除之后 size="+size2);
//判断Abc是否在集合中存在 结果false Abc在集合中不存在
boolean result =list.contains("Abc");
System.out.println(result);
}
}
以下代码存在的问题:
1、 放入集合的类型在编译器不能确定,可以放入任意类型
2、 由问题已引入问题2:元素类型不专一
3、 获取元素必须使用强制类型转换
4、 强制转换的代码不安全 int obj= (int) list.get(0); 可能会出现运行时类型转换异常ClassCastException
要求:解决该问题
目的:放入集合的元素类型必须要专一,要么全部放String、要么全部放Boolean
能够提供编译器的类型检查,例如我规定了集合中只能放置String,如果你放置了Boolean,会提示编译失败
package com.whsxt.day5.arraylist;
import java.util.ArrayList;
import java.util.List;
public class TestArrayList2 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("Tom");
list.add(123);
list.add(3.14);
list.add('M');
list.add(true);
int obj= (int) list.get(0);
System.out.println(obj);
}
}
泛型
SinceJDK1.5,提供编译期检查,解决放入集合元素类型不一致的问题,例如:我定义一个ArrayList集合并且规定了只能放置String,如果你放置其他类型就会编译报错
泛型:
1、定义泛型(数据类型不确定)
2、使用泛型 (必须明确数据类型)
定义泛型语法:
< >
用于定义泛型, 钻石运算符,泛型运算符
public class 类名称<类型>{
}
使用泛型:
import java.util.ArrayList;
import java.util.List;
public class TestArrayList2 {
public static void main(String[] args) {
//创建一个ArrayList对象,支持泛型,规定了ArrayList只能存放String类型的元素(对象)
List<String> list = new ArrayList<>();
list.add("Tom");
//编译错误:不能讲int类型的数据放入ArrayList,因为ArrayList在定义的时候使用了泛型,规定了只能存放String类型
//list.add(123);
//使用泛型好处:获取元素不用强制类型转换
String name = list.get(0);
System.out.println(name);
//编译错误:获取元素也能够提供编译检查,由于泛型规定了只能放置String,那么使用非String类型接收,就会编译错误
//int num = list.get(0);
//小结:定义ArrayList使用泛型,好处能够在add()方法和get()方法提供编译期类型检查
}
}
Java泛型不支持基本类型
import java.util.ArrayList;
import java.util.List;
public class TestArrayList3 {
public static void main(String[] args) {
//泛型:不支持基本类型
//List<double> list = new ArrayList<>();
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(1);
//Java提供了自动装箱和自动拆箱,放入ArrayList的元素是包装类型,取出元素可以用基本类型接受
int num = list.get(0);
System.out.println(num);
System.out.println(list);
}
}
自定义泛型
/**
* @author caojie
* 自己定义一个泛型类
* T: Type 类型
* E: Element 元素
* K: Key 键
* V: Value 值
* Foo<T>:我定义的类型Foo支持泛型
* 可以将泛型用于属性,也可以用于参数,该可以用于返回值
*/
public class Foo<T> {
/**
* 定义一个属性,名称叫做type,它在编译期是一个不确定的类型
*/
private T type;
// 编译错误:泛型属性名称必须跟定义泛型的类型一致<T> private T type;
// private E element;
/**
* T:表示返回类型是一个泛型
* @return
*/
public T getType() {
return type;
}
/**
* T type 表示参数是一个泛型
* @param type
*/
public void setType(T type) {
this.type = type;
}
}
/**
* @author caojie
* 可以定义多个泛型,但是必须使用逗号分隔
* @param <K>
* @param <V>
*/
public class FooTwo<K,V> {
private K key;
private V value;
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
public class FooThree<T> {
private T [] types;
public T [] getTypes() {
return types;
}
public void setTypes(T [] types) {
this.types = types;
}
}
/**
* @author caojie
* 使用定义的泛型
*/
public class TestFoo {
public static void main(String[] args) {
// 不使用泛型,程序编译期不能提供类型检查,运行期就会出现异常
/* Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at com.whsxt.day5.generic.TestFoo.main(TestFoo.java:14) */
// Foo foo = new Foo();
// foo.setType("Tom");
// int num=(int)foo.getType();
// System.out.println(num);
Foo<String> foo = new Foo<>();
foo.setType("Tomson");
String name = foo.getType();
// 使用泛型能够提供编译期类型检查
// int num = foo.getType();
System.out.println(name);
}
}
import java.util.Arrays;
public class TestFooThree {
public static void main(String[] args) {
FooThree<String> foo =new FooThree<>();
String names[] = {"TOm","Jerry","Jason"};
foo.setTypes(names);
String result[] =foo.getTypes();
System.out.println(Arrays.toString(result));
}
}
public class Test2FooThree {
public static void main(String[] args) {
FooThree<Integer> foo = new FooThree<>();
//int arrs[]= {10,20,30};
//Java虽然能支持自动装箱和自动拆箱,但是无法运用到泛型
//foo.setTypes(arrs);
Integer arrs[]= {110,20,30};
foo.setTypes(arrs);
}
}
使用泛型自定义一个ArrayList
import java.util.Arrays;
/**
* @author caojie
* 自定义的ArrayList,支持泛型
*/
public class ArrayList<E> {
/**
* ArrayList核心是一个数组
*/
private Object elementData [];
/**
* ArrayList大小
*/
private int size;
public ArrayList() {
//调用带参数的构造方法
this(10);
}
/**
* 带有一个参数的构造方法
* @param capacity 初始容量
*/
public ArrayList(int capacity) {
elementData = new Object[capacity];
}
/**
* 返回集合的大小(实际容量,不是数组长度)
* @return 集合大小
*/
public int size() {
return size;
}
/**
* 根据下标获取集合的元素
* @param index 外界传入的下标
* @return 返回集合的元素
*/
@SuppressWarnings("unchecked")
public E get(int index) {
//条件成立:下标是非法的,无法获取数组元素,抛出数组下标越界异常
if(index<0 || index>=size) {
throw new ArrayIndexOutOfBoundsException("size:"+size+" index:"+index);
}
return (E) elementData[index];
}
// 定义扩容次数
private int index = 0;
public void add(E element) {
// 新元素加入到ArrayList中,先检查容量,如果容量不够了,需要扩容
int length = elementData.length;
// 条件成立:表示elementData数组容量已经满了,需要扩容
if(size>=length) {
//新容量= (旧数组长度>>1)+旧数组长度
int newCapacity = (length>>1)+length;
elementData =Arrays.copyOf(elementData,newCapacity);
index++;
System.out.println("扩容"+index+"次");
}
//新加入的元素填充到数组中
elementData[size++]= element;
}
}
测试泛型
package com.whsxt.day5.generic;
public class TestArrayList {
public static void main(String[] args) {
/*
* 使用无参构造方法创建ArrayList对象扩容太频繁(23次)
* 不要频繁扩容,也不要永远不扩容(性能消耗大)
* 理想方案:扩容次数15次以内
* 注意:每当你定义ArrayList的时候,思考一个问题,我的ArrayList大概需要容纳多少元素,调用对应的有参数构造方法
* */
ArrayList<Integer> list = new ArrayList<>(2000);
for(int i=0;i<100000;i++) {
list.add(i+10);
}
System.out.println(list.size());
}
}