集合②
List接口
ArrayList
LinkedList
Vector泛型
foreach
List接口常用方法
1、List集合存储元素特点:有序可重复
有序:List集合中的元素有下标。从0开始,以1递增。
重复:存储一个1,还可以再存储1。
2、List既然是Collection接口的子接口,那么肯定List接口有自己“特色”的方法:
以下只列出List接口特有的常用的方法:
void add(int index, Object element)
Object set(int index, Object element)
Object get(int index)
int indexOf(Object o)
int lastIndexOf(Object o)
Object remove(int index)
以上几个方法不需要死记硬背,可以自己编写代码测试一下,理解一下,以后开发的时候,还是要翻阅帮助文档。
API链接: JDK11版本.
API链接: JDK8版本.
关于List接口常用方法的代码演示:
public class ListTest01 {
public static void main(String[] args) {
创建List类型的集合。
List myList = new LinkedList();
List myList = new Vector();
List myList = new ArrayList();
添加元素
默认都是向集合末尾添加元素。
myList.add("A");
myList.add("B");
myList.add("C");
myList.add("C");
myList.add("D");
在列表的指定位置插入指定元素(第一个参数是下标)
这个方法对于ArrayList集合来说效率比较低。
因为每次插入都会导致后续的所有元素后移一位。
对于LinkedList效率可以。(链表特性)
myList.add(1, "KING");
迭代
Iterator it = myList.iterator();
while(it.hasNext()){
Object elt = it.next();
System.out.println(elt);
}
根据下标获取元素
Object firstObj = myList.get(0);
System.out.println(firstObj);
因为有下标,所以List集合有自己比较特殊的遍历方式
通过下标遍历。【List集合特有的方式,Set没有。】
for(int i = 0; i < myList.size(); i++){
Object obj = myList.get(i);
System.out.println(obj);
}
获取指定对象第一次出现处的索引。
System.out.println(myList.indexOf("C")); // 3
获取指定对象最后一次出现处的索引。
System.out.println(myList.lastIndexOf("C")); // 4
删除指定下标位置的元素
删除下标为0的元素
myList.remove(0);
System.out.println(myList.size()); // 5
System.out.println("====================================");
修改指定位置的元素
myList.set(2, "Soft");
遍历集合
for(int i = 0; i < myList.size(); i++){
Object obj = myList.get(i);
System.out.println(obj);
}
}
}
ArrayList
ArrayList集合总结:
ArrayList集合:
1、默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10。)
2、集合底层是一个Object[]数组。
3、构造方法:
new ArrayList();
new ArrayList(20);
4、ArrayList集合的扩容:
增长到原容量的1.5倍。
ArrayList集合底层是数组,怎么优化?
尽可能少的扩容。
因为数组扩容效率比较低,建议在使用ArrayList集合的时候预估计元素的个数,给定一个初始化容量。
5、数组优点:
检索效率比较高。
(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)
6、数组缺点:
随机增删元素效率比较低。
另外数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间。)
7、向数组末尾添加元素,效率很高,不受影响。
8、面试官经常问的一个问题?
这么多的集合中,你用哪个集合最多?
答:ArrayList集合。
因为往数组末尾添加元素,效率不受影响。
另外,我们检索/查找某个元素的操作比较多。
9、ArrayList集合是非线程安全的。(不是线程安全的集合。)
ArrayList集合在满了的时候是自动扩容的,且每次扩容的大小是前一次的1.5倍
例如第一次的大小为4,那么第二次就是4*1.5=6。这是由于他在底层代码中使用了位运算。
关于ArrayList的构造方法:
public class ArrayListTest02 {
public static void main(String[] args) {
默认初始化容量10
List myList1 = new ArrayList();
指定初始化容量100
List myList2 = new ArrayList(100);
创建一个HashSet集合
Collection c = new HashSet();
添加元素到Set集合
c.add(100);
c.add(200);
c.add(900);
c.add(50);
c.add(350);
c.add(150);
通过这个构造方法就可以将HashSet集合转换成List集合。
List myList3 = new ArrayList(c);
for(int i = 0; i < myList3.size(); i++){
System.out.println(myList3.get(i));
}
}
}
位运算符
public class BinaryTest {
public static void main(String[] args) {
>> 1 二进制右移1位。
>> 2 二进制右移2位。
10的二进制位是:00001010 【10】
10的二进制右移1位是:00000101 【5】
System.out.println(10 >> 1);
右移1位就是除以2
二进制位左移1位
10的二进制位是:00001010 【10】
10的二进制左移1位:00010100 【20】
System.out.println(10 << 1);
}
}
LinkedList
LinkedList总结:
LinkList创建的集合是一个双向链表!
LinkedList集合没有初始化容量。
链表的优点:
由于链表上的元素在空间存储上内存地址不连续。
所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkedList。
链表的缺点:
不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止。
所以LinkedList集合检索/查找的效率较低。
ArrayList:把检索发挥到极致。(末尾添加元素效率还是很高的。)
LinkedList:把随机增删发挥到极致。
加元素都是往末尾添加,所以ArrayList用的比LinkedList多。
具体的各种方法前往API查阅即可。
Vector
Vector总结:
1、底层也是一个数组。
2、初始化容量:10
3、扩容之后是原容量的2倍。
10--> 20 --> 40 --> 80
4、ArrayList集合扩容特点:
ArrayList集合扩容是原容量1.5倍。
5、Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的。
但是效率比较低,使用较少了。
泛型
1、JDK5.0之后推出的新特性:泛型
2、泛型这种语法机制,只在程序编译阶段起作用,只是给编译器参考的。(运行阶段泛型没用!)
3、使用了泛型好处是什么?
第一:集合中存储的元素类型统一了。
第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”!
4、泛型的缺点是什么?
导致集合中存储的元素缺乏多样性!
大多数业务中,集合中元素的类型还是统一的。所以这种泛型特性被大家所认可。
为了探究泛型的作用,我们首先来创建以下的几个类:
class Animal {
父类自带方法
public void move(){
System.out.println("动物在移动!");
}
}
class Cat extends Animal {
特有方法
public void catchMouse(){
System.out.println("猫抓老鼠!");
}
}
class Bird extends Animal {
特有方法
public void fly(){
System.out.println("鸟儿在飞翔!");
}
}
泛型到底能干啥?首先来看一段没有使用泛型的代码,并分析其缺点:
public static void main(String[] args) {
不使用泛型机制,分析程序存在缺点
List myList = new ArrayList();
准备对象
Cat c = new Cat();
Bird b = new Bird();
将对象添加到集合当中
myList.add(c);
myList.add(b);
遍历集合,取出每个Animal,让它move
Iterator it = myList.iterator();
while(it.hasNext()) {
没有这个语法,通过迭代器取出的就是Object
Animal a = it.next();
只能这样写:(因为迭代器取出来的就是Object)
Object obj = it.next();
obj中没有move方法,无法调用,需要向下转型!
if(obj instanceof Animal){
Animal a = (Animal)obj;
a.move();
}
}
}
可以看到,在没有使用泛型时,程序必须通过向下转型的方式才能去调用Animal中的方法。
然而我们集合中如果只存了Animal类的话,我们希望是能够直接调用!而不是去麻烦的转型!
往往在实际的开发中我们也都希望一个集合中只有同一个类型的数据,这样在迭代器获取时就不会将他默认归为Object类,从而无法调用特定方法了。
那么这个时候就需要我们去使用 泛型 来统一集合中的数据类型,从而简化代码。
下面的代码使用了泛型后:
public static void main(String[] args) {
使用JDK5之后的泛型机制
使用泛型List<Animal>之后,表示List集合中只允许存储Animal类型的数据。
用泛型来指定集合中存储的数据类型。
List<Animal> myList = new ArrayList<Animal>();
指定List集合中只能存储Animal,那么存储String就编译报错了。
这样用了泛型之后,集合中元素的数据类型更加统一了。
错误:myList.add("abc");
Cat c = new Cat();
Bird b = new Bird();
myList.add(c);
myList.add(b);
获取迭代器
这个表示迭代器迭代的是Animal类型。
Iterator<Animal> it = myList.iterator();
while(it.hasNext()){
使用泛型之后,每一次迭代返回的数据都是Animal类型。
Animal a = it.next();
这里不需要进行强制类型转换了。直接调用。
a.move();
当然调用子类型特有的方法还是需要向下转换的!
Animal a = it.next();
if(a instanceof Cat) {
Cat x = (Cat)a;
x.catchMouse();
}
if(a instanceof Bird) {
Bird y = (Bird)a;
y.fly();
}
}
}
自动类型推断机制(钻石表达式)
ArrayList<这里的类型会自动推断>(),前提是JDK8之后才允许。
自动类型推断,钻石表达式!
List<Animal> myList = new ArrayList<>();
与前面未使用自动类型推断机制比较:
List<Animal> myList = new ArrayList<Animal>();
简化了代码。
自定义泛型
自定义泛型的时候,<> 尖括号中的是一个标识符,随便写。
java源代码中经常出现的是:
<E>和<T>
E是Element单词首字母。
T是Type单词首字母。
public class GenericTest03<E> {
public void doSome(E o){
System.out.println(o);
}
public static void main(String[] args) {
new对象的时候指定了泛型是:String类型
GenericTest03<String> gt = new GenericTest03<>();
类型不匹配
错误:gt.doSome(100);
gt.doSome("abc");
}
}
这样就可以在自己写的各种方法,类中去使用泛型了。
foreach(增强for循环)
JDK5.0之后推出了一个新特性:叫做增强for循环,或者叫做foreach
public class ForEachTest01 {
public static void main(String[] args) {
int类型数组
int[] arr = {432,4,65,46,54,76,54};
遍历数组(普通for循环)
for(int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
增强for(foreach)
以下是语法
for(元素类型 变量名 : 数组或集合){
System.out.println(变量名);
}
System.out.println("======================================");
foreach有一个缺点:没有下标。在需要使用下标的循环中,不建议使用增强for循环。
for(int data : arr) {
data就是数组中的元素(数组中的每一个元素。)
System.out.println(data);
}
}
}
同理集合也可以用,不再演示。