Java常用API(一)
Easul Lv6

相关链接

基本常识

  • java.lang包:核心类,不需要导包(自动导入)
  • 正则转义方式String s = "\\.",第一个反斜线将第二个反斜线转义为反斜线,然后反斜线转义.
  • ascii(American Standard Code for Information Interchange)
  • GB2312(6千多汉字),GBK(2万多汉字),GB18030(7万多汉字)
  • 基本数值比较用大于小于的运算符,对象比较都用方法(对象自身才知道如何比较)
  • 当需要使用循环中的某个结果,可以用一个变量引用来接收。
    1
    2
    3
    while((index = s.indexOf(subString, index)) != -1){
    index += i;
    }
  • curd(create update read delete)
  • JDK的升级
    1. 简化书写(通常有局限性)
    2. 提高效率(可能有弊端)
      3.增加安全性(可能书写会麻烦)
  • 数据多了,用容器来装。
  • 魔法值:未经定义的常量
  • 常用单词
    1
    2
    3
    4
    5
    6
    7
    wrapper: 包装
    parse: 转换
    refactor: 重构
    separator: 分隔
    utility: 公用程序
    concurrent: 并发
    Encapsulation: 封装
  • 声明异常就是希望被做处理
  • 代码中常用的数据可以做成常量
  • 将自己的代码封装为方法,项目中更好识别。是一种代码转换动作。
  • 装箱是变成对象,拆箱是变成基本数据类型
  • bean类:Java的类的一种表现形式,用于描述事物的属性,将事物复杂的属性封装为对象
  • 块注释可以添加到代码之间
    1
    2
    3
    public /*hello*/ static void main/*world*/() {

    }
  • 视图:可以看作可视化的映射关系。会返回某种结构。
  • 类加载器:也叫类装载器,JVM启动,读取并解析类文件,将类文件加载到内存。
  • 对于字符的范围判断可以直接使用if (myChar >= 'a' && myChar <= 'z')来进行判断,就不必用数字来进行判断了。这种情况下,可以看作数字和字符是一种效果。
  • 常用容器
    • 数组(用于存大量相同数据,但只能存固定长度)
    • StringBuilder(用于大量数据转为一整个字符串的操作)
    • 对象(可用于存不同类型的数据,但只能存固定类型和固定含义的数据)
    • 集合(可用于存很多的对象,而且可以存可变长度)
    • 容器只能放数组,对象,集合,不能放基本数据类型
  • 使用HashSet添加某对象后去重,需要给该对象重写hashCodeequals,使用TreeSet添加对象实现自动比较需要对象实现Comparable接口或TreeSet传入该对象的Comparator比较器

java中哈希相关知识

  • 哈希表:通过哈希算法算出来的哈希值的集合。
  • 哈希表内部仍旧是数组,但根据元素特征算出了存放位置,访问更快。(查找元素的位置的时候就不需要遍历数组,根据元素计算之后得到的索引就可以得到该元素)
  • 每个对象都从Object继承来了哈希值,方便找某个元素的位置,而不必去遍历找某个元素在哪里。默认的hashCode()方法由系统底层实现。自己重写该方法,可以建立自己的哈希值。
  • 元素都根据自身特点算出位置,所以相同元素,位置相同,因而不会有重复元素。
  • 哈希算法最后会进行取模运算,以达到将大的计算结果存到较小长度的数组里。
  • 哈希表判断两个元素是否相同
    • 判断两个元素哈希值是否相同。相同则判断两对象内容是否相同
    • 判断哈希值相同,判断的就是对象的hashCode方法。判断对象内容是否相同,用equals方法
    • 哈希值不同,则不需要判断equals
  • 哈希冲突:哈希值一样,但是内容不一样,这个时候没有办法存入进去。但哈希算法比较复杂,一般没有该情况。
    • 出现冲突后:
      1. 可以将该元素顺延存放。(位置不够就加长数组)。
      2. 串联:拉链法,根据该哈希算法算出来的位置在算一个位置。在原位置在挂一个。

学习方法

  • 体系的学习是看顶层结构,用底层功能
  • 学习框架需要知道框架是做什么的,需要知道配置文件怎么配置,需要知道常见对象怎么用即可。
    • 也可以了解下框架的底层原理

小技巧

自然排序

自己的对象想要实现自然排序需要实现Comparable接口,重写compareTo方法
compareTo_2实现了compareTo方法的优化,简洁了很多。将比较通过减法实现。
多个个对象compareTo比较时,原本从小到大输出,想要变成从大到小输出,可以改变compareTo返回值(太麻烦)或者修改两个对象比较的位置
a.compareTo(b)变成b.compareTo(a)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class Person implements Comparable<Person>{
private int age;
private String name;

public Person(int age, String name) {
this.age = age;
this.name = name;
}

@Override
public String toString() {
return "age:" + this.age + ",name:" + this.name;
}

@Override
// compareTo的重写要根据实际情况确定用哪个数据比较
public int compareTo(Person o) {
if (this.age > o.age) {
return 1;
}

if (this.age < o.age) {
return -1;
}

// 年龄相同时,使用名称比较
// String本身实现了Comparable接口,所以可以用compareTo方法
return this.name.compareTo(o.name);
}

// 上边代码的优化
public int compareTo_2(Object o) {
Person newPerson = (Person)o;
int age = this.age - newPerson.age;
// 年龄相同比较名字,不同根据情况返回正负值
return age == 0 ? this.name.compareTo(newPerson.name) : age;
}
}

public class Test {
public static void main(String[] args) {
Set<Person> set = new TreeSet<>();
set.add(new Person(4, "Easul"));
set.add(new Person(1, "Hello"));
set.add(new Person(1, "Iello"));
set.add(new Person(3, "world"));
Iterator<Person> iter = set.iterator();

// 可以判断比较结果是按照实现的方式进行的
while (iter.hasNext()) {
System.out.println(iter.next());
}
}
}

集合自身排序

相当于为对象创建了一个比较器,更方便
需要创建一个类实现Comparator接口,重写compare方法
然后创建TreeSet时传入该类的实例
重写之后会自动使用compare方法来进行比较,而不使用元素自身比较方式比较。
Comparator有两个抽象方法,不需要重写equals是因为继承了Object已经进行了重写。有这个equals方法主要是为了判断比较器是否相同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class Person{
private int age;
private String name;

public int getAge() {
return this.age;
}

public String getName() {
return this.name;
}

public Person(int age, String name) {
this.age = age;
this.name = name;
}

@Override
public String toString() {
return "age:" + this.age + ",name:" + this.name;
}
}

public class PersonCompare implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
int age = o1.getAge() - o2.getAge();
// 年龄相同比较名字,不同根据情况返回正负值
return age == 0 ? o1.getName().compareTo(o2.getName()) : age;
}
}

public class Test {
public static void main(String[] args) {
Set<Person> set = new TreeSet<>(new PersonCompare());
set.add(new Person(4, "Easul"));
set.add(new Person(1, "Hello"));
set.add(new Person(1, "Iello"));
set.add(new Person(3, "world"));
Iterator<Person> iter = set.iterator();

// 可以判断比较结果是按照实现的方式进行的
while (iter.hasNext()) {
System.out.println(iter.next());
}
}
}

toString的经典实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static String toString(Object[] a) {
if (a == null)
return "null";

int iMax = a.length - 1;
if (iMax == -1)
return "[]";

StringBuilder b = new StringBuilder();
b.append('[');
// 中间省略了条件判断,提高了效率
for (int i = 0; ; i++) {
b.append(String.valueOf(a[i]));
if (i == iMax)
return b.append(']').toString();
b.append(", ");
}
}

非同步集合加锁原理

日常使用,直接用Collections.synchronizedList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
将一个非同步的Collection变成同步的,需要给所有的方法都进行加锁操作。
下边只是演示代码,未重写所有方法,使用会报错。
使用一个工具类,里边使用一个静态方法synchronizedList返回给传入的List加锁后的List
创建工具类中的内部类,创建私有变量接收List,创建一个锁,之后重写所有抽象方法,加上同步锁即可。
*/
class MyCollections {
public static List synchronizedList(List list) {
// 接收list对象,返回加锁后的list对象
return new MyList(list);
}

// 只实现了list接口,可以对list的对象进行加锁操作
private class MyList implements List {
private List list;
private final Object lock = new Object();

MyList (List list) {
this.list = list;
}

public boolean add(Object obj) {
synchronized(lock) {
// 重写list方法,将传入的list对象的该方法进行同步封装
return list.add(obj);
}
}

public boolean remove(Object obj) {
synchronized(lock) {
// 所有的抽象方法用原功能实现就可以,仅仅加个锁就行
return list.remove(obj);
}
}
}
}

Scanner

1
2
3
4
5
6
7
8
9
10
11
12
// 需要传入调用的系统输入
Scanner sc = new Scanner(System.in);
// 获取输入的整数
int firstNumber = sc.nextInt();
// 获取输入的字符串,可以读取空格,Enter结束读取
String firstStr = sc.nextLine();
// 获取输入的字符串,忽略前边的空格,tab,Enter结束读取
String secondStr = sc.next();
// 获取输入的double值
double firstDouble = sc.nextDouble();
// 判断是否还有下一个输入
boolean sc.hasNext();

String

特点

  • 创建字符串对象先看字符串常量池有没有该对象,有则将该对象地址指向该引用,没有就创建对象到字符串常量池。该对象可以共享
  • 字符串不能改变,所以所用的方法都会返回新的新的字符串或其他结果

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 1. 在字符串常量池创建的字符串对象。只在常量池创建一个对象
String str = "asdf";
String str1 = "asdf";
// hashCode相等
System.out.println(str.hashCode() == str_1.hashCode());
// 变量 + 变量不创建到字符串常量池
String strAdd = str + str1;

// 2. 在堆内存创建的String对象,传进了字符串对象,这个是新对象,不再是常量池的对象
String str2 = new String("asdf");
String str = "asdf";
// hashCode不相等
System.out.println(str.hashCode() == str2.hashCode());
// 直接引号,创建到池中,new创建到堆内存中

// 3. 创建空字符串对象
String s = new String()

// 4. 将字节数组查ascii表变成字符串,可以有offset和length的重载(转变一部分)
byte[] arr = {65, 66, 67};
String s = new String(arr);
String s = new String(arr, 1, 2);

// 5. 将字符数组变成字符串,可以有offset和count的重载(转变一部分)
char[] arr = {'w','b', 'a'};
String s = new String(arr);
String s = new String(arr, 1, 2);

// 6. 将int数组查unicode表变成字符串,只有offset和count一种方法
int[] arr = {200, 201};
String s = new String(arr, 0, 2);

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
获取: 
length(): 获取字符串长度(数组的长度是属性,字符串的长度是方法)
charAt(): 根据某位置获取某字符
indexOf(int): 根据某字符或数字对应的表的字符获取该字符首次出现的位置
indexOf(int,int): 从指定位置进行ch索引。
indexOf(String): 查看字符串中某子串第一次出现位置
indexOf(String,int): 从指定位置查看字符串中某子串第一次出现位置
lastIndexOf(int): 查找某字符或数字对应表字符最后一次出现位置
lastIndexOf(int,int): 从字符串某位置开始查找某字符或数字对应表字最后第一次出现位置
lastIndexOf(String): 从字符串末尾查找某子串最后一次出现位置
lastIndexOf(String,int): 从字符串某位置查找某子串最后一次出现位置
substring(int): 从某位置获取子串到末尾
substring(int,int): 从某位置到某位置前一个字串获取
转换:
split(String reg): 按某规则分割字符串
toCharArray(): 将字符串打散为字符数组
getByte(): 将字符串转为字节数组,中文编码两个字节,最高位是1,所以输出是负数。默认使用当前文件编码将字符串转字节数组
toUpperCase(): 将字符串中的英文字符转为大写
toLowerCase(): 将字符串中的英文字符转为小写
replace(char,char): 将字符串中某字符替换为另一个字符,没有找到某字符则不替换,返回原串,不创建新串
replace(CharSequence,CharSequence): 将字符串中某字符串替换为其他字符串。String,StringBuffer实现了CharSequence接口
trim(): 去掉字符串两端空格
concat(String): 将字符串进行连接,更专业。指挥对象做事情
valueOf(): 将基本数据类型转为字符串,类似""+ 1 + true;
判断:
equals(Object): 比较字符串的内容是否相同,重写了Object的equals(),所以参数列表也一样
equalsIgnoreCase(String): 比较字符串是否相同,忽略大小写
contains(CharSequence): 查看某字符串是否包含另一字符串
startsWith(): 字符串是否以指定字符串开头
endsWith(): 字符串是否以指定字符串结尾
比较:
compareTo(String): 按ascii表比较两个字符串。实现了Comparable接口,重写了compareTo方法。
- 相等,返回0
- 此字符串小于参数字符串,返回负数(两个字符串中第一个不相同的字符差距)
- 大于,返回正数(两个字符串中第一个不相同的字符差距)。
正则:
matches(String regex): 检查字符串是否匹配该正则,返回boolean
split(String regex): 将字符串按正则切割为字符串数组,返回String[]
replaceAll(String regex, String replacement): 使用replacement替换符合正则的部分,返回字符串
- 第二个参数想要用第一个参数的正则内容,可以用$表示
- replaceAll("(.)\\1+", "$1") $n就是匹配第n组
- 这里就可以在第二个参数获取第一个参数匹配的一个字符了.
其他:
intern(): 调用该方法如果池中有该字符串,则返回池中字符串,没有则将字符串添加到池中,返回池中的引用
1
2
3
4
String first = "asdf";
String second = new String("asdf");
// 结果相等
System.out.println(second.intern() == first);

StringBuffer

含义

字符串缓冲区,存储数据的容器
线程安全(线程同步)

特点

  • 长度可变(比数组好),满了之后新开辟一个扩充大小后的空间,将数组复制到新的内存区域,
  • 可以存不同数据类型(数组只能存一种)
  • 最后转成字符串进行使用
  • 可以对字符串修改(普通字符串创建之后无法修改)

创建

1
2
3
4
5
6
// 初始化后,默认能存放16个字符。
new StringBuffer();
// 在字符串缓冲区同时初始化字符串
new StringBuffer("asdf");
// 创建指定容量的字符串缓冲区,用于知道数据量的情况下,可以提高效率,不必新开辟空间
new StringBuffer(int);

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
添加:
append(): 可以添加任意基本类型数据和String(除了byte和short)。char可以添加数组。默认添加到对象缓冲区中
- sb.append(1).append("true");
insert(index, data): 将数据插入到字符串某字符下标处
删除:
delete(start,end): 删除范围内字符串,可以清空字符串
deleteCharAt(index): 删除字符串缓冲区某下标字符
查找:
indexOf(String): 获取子串第一次出现下标
lastIndexOf(String): 获取子串最后一次出现下标
charAt(int): 获取某下标字符
修改:
replace(start,end,string): 替换字符串缓冲区start到end-1的字符串
setCharAt(int,char): 修改某下标字符
其他:
length(): 获取字符串缓冲区长度
setLength(num): 设置缓冲区长度(多的裁掉,少的补上空格),也可以起到清空缓冲区的功能
reverse(): 反转字符串

StringBulider

特点

  • 兼容StringBuffer,功能和用法一样,也是容器。
  • StringBuffer保证线程安全(线程同步),StringBulider不保证线程安全
    • StringBuffer有append()delete(),不同线程同时操作可能不同步。
    • 但是在单线程中不需要判断锁,所以使用StringBulider,提高缓冲区效率
  • 不执行同步,速度更快(不需要判断锁)

用途

需要存很多字符串,就用这两个方式(如果用String,会在字符串常量池产生很多字符串常量)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
public static String arrayToString(int[] arr){
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < arr.length; i++) {
if(i != arr.length - 1)
// 如果用String,每连接一次逗号,字符串常量池就多一个字符串常量,有很多冗余数据
sb.append(arr[i] + ",");
else
sb.append(arr[i] + "]");
}
// 将StringBuilder类型转为String
return sb.toString();
}

基本数据类型对象包装类

特点

  • 为了更方便操作基本数据类型值,所以对其进行了对象封装。在对象中定义了属性和行为,丰富了其操作
  • 用于描述该对象的类就叫做基本数据类型对象包装类
  • 对应于每个基本数据类型: Byte, Short, Integer, Long, Float, Double, Character, Boolean

作用

  • 主要用于基本数据类型字符串的转换
    1
    2
    3
    4
    5
    6
    7
    基本类型转字符串: 
    - "" + 基本类型
    - String.valueOf(值)
    - 包装类的valueOf,Integer.valueOf(1).toString()
    字符串转基本类型:
    - 包装类的parseXxx(),Integer.parseInt("1"),Character没有parseXxx(),可以直接读取字符串某个字符
    - 创建包装类对象时传入字符串,使用xxxValue()将包装类转成基本数据类型
    1
    2
    Integer i = new Integer("123");
    System.out.println(i.intValue() + 1);

Integer

常用属性

1
2
Integer.MAX_VALUE: 获取int最大值
Integer.MIN_VALUE: 获取int最大值

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 可以放一个int值
new Integer(num);
// 可以放一个String的int值
new Integer(String);
// 简写(自动装箱),相当于new Integer(4)。因为是引用类型,赋值null,自动拆箱会抛异常。需要相应处理
Integer i = 4;
// 这里i是包装类,相当于进行了i.intValue()(自动拆箱)
// 装箱是变成对象,拆箱是变成基本数据类型
i = i + 6;

// 调用时相当于Object a = new Integer(55);自动装箱
public static void show(Object a){
System.out.println(a);
}
show(55);

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
十进制转其他进制:
Integer.toBinaryString(int): 将十进制转成二进制
Integer.toHexString(int): 将十进制转为十六进制
Integer.toOctalString(int): 将十进制转为八进制
Integer.toString(num, 进制): 将num转为某进制,不是Object的toString()的重写
其他进制转十进制:
Integer.parseInt(String, 进制): 将某进制的字符串转为十进制整数
字符串转整形:
Integer.parseInt(String): 将整数字符串数字转为int型,有异常抛出,可以判断是否输入了数字。
其他:
equals(): 比较包装类中的值是否相同
compareTo(): 比较包装类中的值,返回 0 相等 1 前大 2 后大

1
2
3
4
5
6
Integer a = new Integer("3");
Integer b = new Integer(3);
// 比较的时候会先将字符串3转为数字3
System.out.println(a.equals(b));
// 结果为0
System.out.println(a.compareTo(b));

集合

位置

java.util包,工具包

特点

  • 是一个容器,用于存很多的对象
  • 长度可变
  • 不可以存基本数据类型
  • 可以存不同类型的数据,更多存的是自定义对象
  • 容器自身都有特定的结构(类比有茶格和没茶格的杯子),叫做数据结构(数据存放方式)。依据不同需求用不同容器
  • 集合容器因为内部的数据结构不同,有多种容器

集合技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
元素需要唯一吗?
├── 唯一:Set
│ └── 需要指定顺序吗
│ ├── 需要和存储一致的顺序(有序):LinkedHashSet
│ ├── 需要:TreeSet
│ └── 不需要:HashSet
└── 不唯一:List
└── 需要频繁增删吗
├── 需要:LinkedList
├── 同步:Vector(只有他是同步的,很慢,基本不用了)
└── 不需要:ArrayList

如何记住每一个容器的接口和所属体系?
├── 看后缀名字,后缀名就是List或者Set体系
└── 前缀名是该集合的数据结构
├── array是数组(有角标,查询快)
├── linked是链表(增删快,要想到add,get,remove,first,last等方法)
├── hash是哈希表(唯一性,元素需要覆盖hashCode和equals方法)
└── tree是二叉树(要想到排序,Comparable和Comparator接口)

常用集合容器都是不同步的

集合类关系图

虚线都是接口,实线的都是实现类
List中常用的有ArrayListLinkedList,Vector
image

Collection

Collection是集合根接口,以下方法所有集合体系都有
Collection<? extends E>表示泛型为E的集合或者是E子类的集合。?表示通配符,1个或多个
System.out.println(collection)可以直接输出集合中的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
插入: 
add(E): 给集合添加对象,返回添加是否成功
addAll(Collection coll): 给集合添加很多对象,返回是否添加成功。会追加所有的元素,可能有相同的。这里没有限制添加的都是什么类型的元素,有类型安全隐患
addAll(Collection<? extends E> coll): 这里限制了Collection对象添加的元素都是其泛型E或其子类,所以才能统一操作
删除:
remove(obj): 给集合删除对象,返回是否删除成功。可以用于改变集合的长度
removeAll(Collection<?> coll): 给集合删除很多对象,返回是否删除成功。删除调用者的交集。底层使用equals,因为所有对象都有该方法,所以可以传任意对象,因此使用?通配符
clear(): 清空集合中的所有对象
判断:
contains(Obj): 查看集合是否包含某对象
containsAll(Collection<?> coll): 查看集合是否包含某组所有对象.用通配符是因为底层实现用equals,每个对象都具备,所以所有对象都支持比较。也就可以任意传对象了。
isEmpty(): 判断集合是否有元素(用size()是否为0判断的)
equals(): 判断两个集合是否相等
获取:
size(): 获取集合中对象个数
iterator(): 返回迭代器对象,使用迭代器获取元素。
- Iterator是接口 Iterator it = coll.iterator();
- 该对象依赖于具体的容器(不同容器数据结构不同),因此该对象需要在容器内部实现。使用者无需了解内部实现.
- 通过容器获取实现的迭代器对象即可。用Iterator接口统一取出。
其他:
retainAll(Collection coll): 取两个集合的交集覆盖到调用集合
toArray(): 将集合转成数组,返回了Object类型的数组。
<T> T toArray(T[] a): 将集合转成数组,可以对集合中元素操作的方法进行限定,使其不允许增删。可以用,不允许动里边的数据。Set也可以转。
- 需要传入一个指定类型的数组,集合的元素会放到数组里。最好长度指定为集合的size
- 如果长度小于集合的size,该方法创建一个同类型并和集合相同size的数组
- 如果长度大于集合的size,该方法将会使用指定的数组长度存储集合元素,其他位置默认为null
1
2
3
4
5
6
7
8
9
Collection<String> c = new ArrayList<String>();
c.add("123");
String[] c2 = new String[3];
String[] test = c.toArray(c2);
// 结果为true,还是原来的数组
System.out.println(c2 == test);
for (String str : test) {
System.out.println(str);
}

List

特点

  • 有序(存入和取出的顺序一致)
  • 元素都有索引
  • 元素可以重复
  • 可以对元素进行增删改查。

特有方法

除了Collection提供的之外的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
插入: 
add(index,elem): 添加元素到某下标
addAll(index, collection): 添加集合到某下标
删除:
remove(index): 删除某下标元素并返回该元素
修改:
set(index, Element): 将某下标修改为替换元素,返回旧元素
获取:
get(index): 获取某下标元素
indexOf(Object): 索引某对象位置
lastIndexOf(Object): 索引某对象最后一次出现的位置
subList(from,to): 获取子列表
iterator(): 获取列表迭代器
iterator(index): 从list某下标开始获取列表迭代器
1
2
3
4
// list的特有遍历方式,set不行,可以用for循环获得,也可以用while获得
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}

常见异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ConcurrentModificationException,表示集合和迭代器同时在对同一个元素进行操作,迭代出现了问题。
// 1.可以操作集合的时候不迭代,操作迭代的时候不集合
// 2.使用List的listIterator()拿到列表迭代器进行集合操作
list.add("abc1");
list.add("abc2");
list.add("abc3");
list.add("abc4");
Iterator it = list.iterator();
while(it.hasNext()){
Object o = it.next();
if(o.equals("abc2")) {
// 迭代过程中,操作集合时候可能出现ConcurrentModificationException异常
// 可以使用Iterator子接口ListIterator来完成迭代中对集合更多的操作。
list.add("abc9");
} else {
System.out.println("next:" + o);
}
}
System.out.println(list);

Vector

特点

  • 内部是数组数据结构
  • 是线程安全的(同步)
  • 数组大小可变(增加一倍,浪费空间,效率更低)
  • 增删查询都慢。

常用方法

1
2
Enumeration elements(): 返回向量中的枚举,Vector特有的遍历方法,功能与Iterator相同,但因名字过长被Iterator代替
- 也可以使用下标或者Iterator来遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vector<String> a = new Vector<>();
a.add("asdf");
// 第一种
for (int i = 0; i < a.size(); i++) {
System.out.println(a.get(i));
}
// 第二种
Enumeration<String> test = a.elements();
while (test.hasMoreElements()) {
System.out.println(test.nextElement());
}
// 第三种
Iterator<String> test2 = a.iterator();
while (test2.hasNext()) {
System.out.println(test2.next());
}

ArrayList

特点

  • 内部是数组数据结构
  • 是线程不安全的(不同步),替代了Vector,单线程效率更高(多线程仍使用ArrayList,要么加锁,要么用其他方式)
  • 数组大小可变(增加一半)
  • 增删元素速度慢(需要进行数据的大量移动),查询速度快(下标直接读取)
  • 默认创建的列表长度为10
  • 如果添加数字,可以直接使用a1.add(5);这里相当于对数字5进行了自动装箱,省去了new Integer(5);的步骤。本质还是需要添加对象。
  • 基本数据类型赋值给引用数据类型,才会进行装箱
    1
    2
    3
    4
    5
    6
    public void show1(Integer num){}
    public void show2(int num){}
    // 装箱
    show1(1);
    // 不装箱
    show2(1);
  • 引用数据类型和基本数据类型进行运算的时候就会自动拆箱
    1
    2
    3
    public void show1(Integer num){
    num +8;
    }
  • ArrayList列表长度为10,只填充的是对象的引用(地址),不是将对象装入ArrayList。所以可以放不同数据类型的对象

LinkedList

特点

  • 内部是链式数据结构(用指针域记录数据的地址)
  • 是线程不安全的(不同步)
  • 增删元素速度快(使用指针指向),查询速度慢(需要用指针一个一个遍历)
  • 有索引,因为是List的子类,但找的时候是一个一个往下找的

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
添加:
addFirst(E): 将元素添加到链表开头,返回void
addLast(E): 将元素添加到链接结尾,返回void
offerFirst(E): 将元素添加到链表开头,只会返回true
offerLast(E): 将元素添加到链接结尾,只会返回true
push(E): 插入到第一个元素
获取:
getFirst(): 获取链表第一个元素,指针不移动。如果没有元素会抛异常NoSuchElementException
getLast(): 获取链表最后一个元素,指针不移动。如果没有元素会抛异常NoSuchElementException
peekFirst(): 获取链表第一个元素,指针不移动。如果没有元素会返回null
peekLast(): 获取链表最后一个元素,指针不移动。如果没有元素会返回null
peek(): 获取链表第一个元素,但不将第一个元素删除。
删除:
removeFirst(): 移除并返回链表第一个元素(可以用于LinkedList的正向遍历,但是所有元素会被删掉)。如果没有元素会抛异常NoSuchElementException
removeLast(): 移除并返回链表最后一个元素(可以用于LinkedList的反向遍历,但是所有元素会被删掉)。如果没有元素会抛异常NoSuchElementException
pop(): 第一个元素弹出,并将当前的第一个元素删除。调用的removeFirst(),相当于短名称
poll(): 获取第一个元素并删除该元素。没有元素返回null。相当于短名称
pollFirst(): 移除并返回链表第一个元素(可以用于LinkedList的正向遍历,但是所有元素会被删掉)。如果没有元素会返回null
pollLast(): 移除并返回链表最后一个元素(可以用于LinkedList的反向遍历,但是所有元素会被删掉)。如果没有元素会返回null

Set

特点

  • 无序(存和取的顺序可能不同)
  • 元素不重复
  • 其中的方法与Collection一致
  • Set集合的取出方式只有迭代器,因为无序,所以不能用索引取。

HashSet

特点

  • 内部数据结构是哈希表(不保证迭代顺序,不保证顺序恒久不变)
  • 存储方式由算法完成。算法改变,存储顺序改变。
  • 可以存null
  • 此实现是非同步的

常用操作

  • 使用HashSet存储自定义对象,可以重写hashCode()方法来计算存储的位置,同时重写equals()来判断两个对象是否相同。
    • 在存储Person对象的时候,hashCode返回int,所以可以返回name的hashCode值+age(name是String类型,拥有int类型的hashCode值)
    • HashSet在使用add()的时候会自动调用这两个方法来进行判断。先调用hashCode,如果插入位置没有元素,则不调用equals方法。否则调用equals方法。
    • 为了尽量保证hash值唯一,可以给数字乘一些数,然后拉大不同运算参数之间的距离。
    • 具体对于hashCode或者equals需要重写哪一个,可以查看文档进行查询。

比较

  • ArrayList和HashSet的区别:
    • ArrayList判断元素只需要使用equals方法即可(删除的时候只判断equals)
    • HashSet判断元素需要使用hashCode和equals方法(删除的时候判断hashCode与equals,所以重写hashCode也是为了可以删除)
    • 判断方式的不同是因为使用了不同的数据结构

LinkedHashSet

特点

  • 集合中的数据唯一,且有序
  • HashSet子类
  • 哈希表和链表实现(通过指针域来存储下一个添加元素的地址)

用法

1
2
// 用多态的方式来改变存储过程。这样就可以以链表形式存储。
HashSet hs = new LinkedHashSet();

比较

  • Set与List异同
    • Set元素唯一,List元素不唯一
    • 都可以使用有序存储,Set使用LinkedHashSet就可以有序存储。使用HashSet是无序存储。

TreeSet

特点

  • 存入元素按照元素的自然顺序排列(字符串是按照字典顺序进行排序的)
  • 需要根据元素的比较结果进行排序。TreeSet自身不具备比较功能。
  • 实现不同步
  • TreeSet和HashSet的底层代码是由TreeMap和HashMap实现。为了保证单列集合中元素的唯一性,所以通过Map创建了Set,更方便应用。(相当于只用了HashMap中的键来保证Set的唯一性)
  • TreeSet对元素排序的方法
    1. 自然排序,使用某类默认的比较方式(compareTo),使元素自身拥有比较功能
      • 存入对象实现Comparable接口,重写compareTo方法。(普通对象默认没有排序方法)。
      • 不重写会报转换异常错误。(需要使用Comparable接口来使用该对象重写的compareTo方法进行比较)
    2. 使集合自身拥有比较功能(存入的对象没有比较功能,或比较功能不是自己所需要的方式)。需要在添加元素之前,创建对象时进行功能添加
      • 使用传入实现了Comparator接口的对象的构造函数。
  • 与hashCode()和equals()方法无关,使用compareTo方法来进行元素是否相同的判断。
  • 底层使用二叉树,排序效率为很高,相同数据将不再插入
  • 每次都是插入元素和原本元素比

Map

特点

  • 一次添加一对元素(Collection一次添加一个元素),存储键值对
  • Map集合叫做双列集合。Collection集合叫做单列集合。
  • 每个键映射一个值,有键的唯一性
  • 用于有映射关系,查表关系时
  • 创建的时候使用泛型。
    1
    2
    // 通过泛型来指定传入的键值是什么类型。
    Map<Integer, String> map = new HashMap<Integer, String>();
  • Map没有迭代器,与Collection不同。如果想要遍历所有的值,只能够通过keySet()取出所有的键,然后遍历每一个键取出值
  • Map也可以这样存<”1号教室”, Set> ,相当于一个键对应一个集合,这样可以放更多的值。相当于1对多。这里只需要放对象即可

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
添加:
put(key, value): 给某个键下添加值。如果原来该键下没有值,就返回null,有值则返回该值
删除:
clear(): 清空Map集合
remove(key): 根据指定的key删除这个键值对,返回该键值对中的值
判断:
containsKey(key): 判断Map是否包含某个键
containsValue(value): 判断Map是否包含某个值
isEmpty(): 判断Map是否为空
获取:
get(key): 通过键获取值。没有该键,返回null。也可以通过返回null来判断是否包含指定键。可以直接用get结果替代containsKey
size(): 获取键值对的个数
toString(): 可以直接打印Map对象,用来查看Map中所有元素,形式为{8, "旺财" 9, "小强"}
Set<keyType> keySet(): 返回Map中所有的键的set视图。可以再继续通过Set的迭代器得到每一个键,之后获取Map中所有的值。
Set<Map.Entry<k,v>> entrySet(): 返回Map中的所有键值映射关系。
- 映射关系的类型叫做Map.Entry。取出的是每一个键值对(键和值被封装到了一个对象里边)。
- 可以通过getKey(),getValue()方法获得键和值。
Collection<valueType> values(): 以Collection返回Map中所有键值对中的值。
1
2
// 需要在泛型中指定该Set元素的类型,键会自动在后续过程中装箱拆箱。
Set<Integer> allkeys = map.keySet();

HashTable

特点

  • Map的子类
  • 内部结构是哈希表
  • 1.0版本就有(单列集合是Vector,双列集合是HashTable)
  • 同步的
  • 不允许null键和null值

Properties

特点

  • HashTable子类
  • 用于属性集,用于存储配置文件的信息(键值对型配置文件信息)
  • 可以和IO结合

HashMap

特点

  • 内部结构是哈希表
  • 不是同步的
  • 允许null键和null值
  • HashMap是无序的,对于HashMap<Student, String>,插入内容相同但是不同对象的键,就需要重写hashCode和equals.当判断两个对象的hashCode相同的时候,也就表示这两个对象的标识一样,接着判断equals是否相同。equals比较的Student内部数据是否相同。相同将不再插入该键,只是进行相应值的修改。否则插入该键值
  • HashMap遍历出来的顺序按存储的键进行排序。有序是如果按照怎样存怎样取,可以使用LinkedHashMap(存入和取出顺序相同),用HashMap接收即可。
  • 哈希表的数值对象的哈希值和数值排序顺序一样。

TreeMap

特点

  • 内部结构是二叉树
  • 不是同步的
  • 可以对Map集合中的键进行排序
  • TreeMap按照二叉树排序,这样的话要么需要插入的元素实现了Comparable接口,要么需要集合传入Comparator的比较器。如果比较器用的是父类的比较器,子类进行排序,也是可以进行排序的。因为子类继承了父类的成员,所以可以使用与父类相同的比较方式。如果子类需要的比较方式和父类不同,那么就需要重新创建一个子类的比较器。

Iterator接口

常用方法

1
2
3
hasNext(): 判断是否还有下一个元素
next(): 每次调用获取下一个元素,因为是Object类型,所以可以使用Object obj = it.next()接收。
remove(): 在Iterator指向的Collection中删除获取的当前元素,返回为空
1
2
3
4
5
6
7
8
// while使用
while(it.hasNext()){
System.out.println(it.next());
}
// for使用,迭代器使用完了时候会自动从内存中删除该引用。用于迭代后迭代器已经无用的情况
for(Iterator it = coll.iterator(); it.hasNext(); ){
System.out.println(it.next());
}

设计方式

  • 因为每一种容器的数据结构不同,所以每一种容器的取出方式也不一样,所以将取出的方式放到容器中,变成内部类,该内部类会更了解该容器的取出具体的实现(取出的过程可能需要容器的某些方法或属性进行操作)。而为了让所有的数据取出都有统一的规则,所以使用向上抽取为Iterator接口来统一管理,在重写的方法中具体实现取出的过程,返回内部的数据。
  • 容器和取出方式之间的耦合性降低了。
  • 迭代器就是实现了Iterator接口的每一个容器的内部类对象。

ListIterator接口

Iterator子接口

特点

  • List专有,用于迭代过程中对集合进行更多的操作。
  • 可以在迭代过程中对List进行增删改查。默认的Iterator在迭代中增删改查会有异常
  • 可以正序或反序遍历。

常用方法

先获取迭代器ListIterator lit = list.listIterator()

1
2
3
4
5
6
7
add(E): 获取了某元素的指针,将新元素添加到该元素后边(操作的是ListIterator对应的List)
set(E): 将List当前元素修改为该元素
hasPrevious(): 判断是否有上一个元素(可以指针通过hasNext遍历到之后使用)
previous(): 获取上一个元素
nextIndex(): 返回随后调用元素在列表的索引
previousIndex(): 返回前一个调用元素在列表的索引
remove(): 删除列表中next()或previous()返回的元素。

Enumeration接口

Vector特有的可以遍历成员的方法

1
2
hasMoreElement(): 查看枚举是否有更多元素
nextElement(): 获取下一个元素

Map.Entry接口

特点

  • 返回Map中所有的键值对。可以看作键值映射关系对象。
  • 是Map接口的内部接口,默认会加上public static

常用方法

1
2
3
getKey(): 获取一个键值对的键
getValue(): 获取一个键值对的值
setValue(): 替换本键值对中的值,返回原来存的值。

集合工具类

Collections

特点

  • Collection是接口,Collections是集合框架的工具类。
  • 都是静态方法

常用方法

<T extends Comparable<? super T>>表示实现了Comparable接口的类,而Comparable接口可以由T类实现,也可以由T的父类实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<T extends Comparable<? super T>>  sort(List<T> list): 因为希望Collections的sort方法可以比较所有对象,所以用泛型来进行定义。但并不是所有的对象都实现了比较的方法,因此限定只有实现了Comparable接口的类或子类才能进行比较,但这里为了提高扩展性,又可以添加T的实现了Comparable接口的类或其父类。限定了可以传入的类的类型,就可以在后边的List<T>中进行使用了。明白了原理就可以自己写了。
<T> void sort(List<T>, Comparator<? super T> c): 如果想要自定义排序方式,可以传入比较器来进行排序。不受限于对象本身的compareTo()方法。TreeSet就利用了该特性,完成了二叉树的特性比较。
swap(list, i, j): 用于交换列表中的元素
<T> int binarySearch(List<? extends Comparable<? super T>> list, T key): 折半查找,如果需要进行查找的话,源数据需要先进行排序,之后根据key进行查找。返回负数表示不存在该元素(-1),可以在-(1+index)的位置插入。比下标多一个就是为了保证找不到元素的时候,每次都可以返回负数。如果插入0下标处就返回-1
<T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c): 如果需要按照自定义顺序排列再查找,可以传入比较器后查找
<T extends Object & Comparable<? super T>>T max(Collection<? extends T> coll): 对集合(list或set)按照自然顺序取出最大的元素。这里的泛型限定可以看作<T extends Object>和<T extends Comparable<? super T>>
<T> T max(Collection<? extends T> coll, Comparator<? super T> comp): 可以传入比较器进行最大值的取出。
<T> Comparable<T> Collections.reverseOrder(): 返回一个比较器,将自然顺序排序的元素逆转。对象排序,想要逆转都用这个。传入这个方法,比较的对象会自动传入,然后反转比较过程即可。(o1.compareTo(o2)变成了o2.compareTo(o1))
<T> Comparable<T> Collections.reverseOrder(Comparator<T> comp): 返回一个比较器,将已有比较器排序的元素逆转。
reverse(List<?> list): 反转List的排序
<T> boolean replaceAll(List<T> list, T oldVal, T newVal): 将List中的所有oldVal替换为newVal,可以看作set(indexOf(oldVal), newVal),将代码进行了合并操作。
<T> fill(List<? super T> list, T obj): 将所有元素都换成同一个元素。可以用于集合中所有元素初始化。
shuffle(List<?> list): 将传入的集合随机的进行排序。相当于扑克牌洗牌
<T> ArrayList<T> list(<Enumeration<T> e>): 将枚举转为List
<T> Enumeration<T> enumeration(<Collection<T> c>): 将集合转为枚举

重点方法
集合用于多线程,不安全需要加锁。使用Collections的方法进行加锁操作

1
2
3
4
<T> List<T> synchronizedList(List<T> list): 将非同步的list转为同步的list并返回。
<T> Collection<T> synchronizedCollection(Collection<T> c): 将非同步的Collection转为同步的Collection并返回。
<T> Map<K,V> synchronizedMap(Map<K,V> m): 将非同步的Map转为同步的Map并返回。
<T> Set<T> synchronizedSet(Set<T> s): 将非同步的Set转为同步的Set并返回。

Arrays

特点

  • 方法都是静态的
  • 除了基本类型,对象,泛型都可以操作。

常用方法

1
2
3
4
5
6
7
8
9
10
11
equals(object[] a,object[] b): 比较两个数组是否相同
fill(object[] a, object val): 将数组所有元素替换为指定元素
fill(object[] a, int fromIndex, int toIndex, object val): 将数组部分元素替换为指定元素
sort(object[] a): 数组的排序
sort(object[] a, int fromIndex, int toIndex): 数组的排序
binarySearch(arr, 45): 二分法查找45的index值.
- 返回正数表示下标值
- 返回负数表示不存在该元素(-1),可以在-(1+index)的位置插入.
deepEquals(Object[] a1, Object[] a2): 比较数组里的元素对象,同时还会比较对象里边的内容
toString(Object[] a): 返回数组内容的字符串表达形式。普通数组打印出来,toString是类名+hashCode,用Arrays的toString()打印更有意义。
<T> List<T> asList(T[] t): 将数组转为List集合。因为数组功能有限,所以转为List可以进行更多操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Arrays.toString()的使用
Person[] test = {new Person(1, "E"), new Person(12, "w")};
System.out.println(Arrays.toString(test));

// asList的使用
// 例如使用List的包含操作。可以使用集合的方法操作数组。
// 1. 但由于数组长度是固定的,所以不可以使用集合的增删方法,不可以改变数组长度。否则会有UnsupportOperationException
// 不使用List的contains方法,就需要写自己关于String的方法
String[] testStr = {"asdf", "haha"};
List<String> list = Arrays.asList(testStr);
System.out.println(list.contains("asdf"));
// list.add("fdab");不能操作,因为数组长度固定,这里不继续添加。但是修改,判断可以使用。

// 2. 如果数组中的元素是对象。转成集合时,直接将数组中的元素作为集合中的元素进行集合存储
// 如果数组中的元素是基本类型数值,会将该数组作为集合中的元素进行存储。基本数据类型无法存入集合。
// 如果这里用了Integer[],存进去的是4个数据,相当于存了对象
int[] arr = {1, 2, 3};
// 将数组对象存了进去,泛型类型和arr修饰符一样即可
List<int[]> list = Arrays.asList(arr);
System.out.println(list);

泛型

特点

  • 英文翻译为genetic
  • JDK1.5后出现。
  • 如果使用泛型的话,创建容器的时候就明确要放什么类型的数据。
  • 避免在容器存入了不合适的数据类型,导致编译时不会报错,运行时出了问题,减少后期隐患。
    • 例如ArrayList,存了String和Integer两种数据,后边只将数据转换为String,报错,所以只能存的时候自己保证存的都是同一个类型(程序员主观判断)。但这样极易出错,用泛型来指定数据类型,会使代码编写更加严谨。
  • 中括号用于数组,小括号用于方法,大括号用于定义范围,只能用尖括号表示泛型。
  • ArrayList 这里E大写,因为要传入引用数据类型(类,接口,数组),类接口的首字母都是大写的。所以这里也大写。
  • 用于编译时期的安全技术,提高了编译时期的安全性。因为编译没有问题,所以在运行时候才没有问题。
  • 泛型技术是给编译器用的技术。确保了类型的安全。
  • 运行的时候,会将泛型去掉,生成的class文件是不带泛型的。这个称为泛型的擦除。因为用的类加载器是以前的,为了使加入泛型后的代码,以前的类加载器仍旧可以兼容使用,所以去掉了泛型,仍旧使用旧加载器加载。
  • 泛型的补偿
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ArrayList<String> a1 = new ArrayList<>();
    // 运行时去掉了泛型,这里实际添加的是Object类型
    a1.add("asdf");
    a1.add("asdf1");

    Iterator<String> it = a1.iterator();
    while (it.hasNext()) {
    // 遍历的时候it.next()本来是Object类型,但是自动类型补偿为String
    String str = it.next();
    System.out.println(str);
    }
    • 因为写代码的时候加了泛型,运行的时候去掉了泛型。所以add添加的时候仍旧是Object类型,但是在代码遍历中却不用再进行强转(String str = it.next();),因为进行了类型补偿
    • 泛型补偿告诉了类装载器,编译时期检查代码没有类型转换问题,使用补偿程序,根据元素获取其类型然后对迭代时期的元素进行类型转换(自动完成)。元素类型可以使用getClass()方法获取字节码文件对象,然后获取元素类型。该补偿机制自动执行。
  • 泛型保证了元素类型的统一,所以插入的时候不可能插入不同类型的数据。也就保证了泛型补偿的时候不会出现类型转换异常。(只有固定的类型)
  • 多用于集合框架,在该框架下,很多容器无法明确传入对象类型,所以使用泛型来明确。

好处

  • 将运行时期的问题ClassCastException转到了编译时期
  • 避免了迭代时强制转换的麻烦。

泛型什么时候用?

  • 用于操作的引用数据类型不确定的时候。传入要操作的引用数据类型即可。相当于定义参数类型的范围
  • 用到了带<>的类或接口,就传入具体引用数据类型。

注意

  • 泛型不能用基本数据类型,int不行,但是可以传入类,接口,数组(int[]),即只能传入引用数据类型。需要传入基本数据类型,就用包装类
  • 当集合(ArrayList)已经明确了存入的是String类型ArrayList,那么使用迭代器取出数据的时候,迭代器类型就可以直接使用泛型来接收
    (Iterator),而不用在迭代器迭代的时候再进行类型转换。

泛型简单操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Tool {
private Object person;

public Object getPerson() {
return person;
}

public void setPerson(Object person) {
this.person = person;
}

public Tool(Object person) {
this.person = person;
}

public Tool() {

}
}
public static void main(String[] args) {
Tool tool = new Tool();
// 存的时候可以随便存
tool.setPerson(new Student());
// 取的时候如果不知道类型,可能会有问题
// 不用泛型,这里需要进行强转,存的时候已经向上造型为Object
Student stu = (Student)tool.getPerson();
Tool tool1 = new Tool();
tool.setPerson(new Worker());
// 强转出错,因为可以放入Object类型数据,传入Worker无误,但强转有问题。
Student stu1 = (Student) tool.getPerson();

}

泛型示例

静态方法的泛型定义在修饰符之前,返回值之后。因为静态方法不创建对象,不能获得类的泛型,所以需要在方法定义
public static <T extends Person> void show(T p)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 工具类,加入了泛型
public class Tool<MyTest> {
private MyTest q;

public Tool(MyTest q) {
this.q = q;
}

public Tool() {
}

public MyTest getQ() {
return q;
}

public void setQ(MyTest q) {
this.q = q;
}

// 泛型方法演示
public void print(MyTest q){
// 这个相当于是泛型的方法使用,类上泛型中传入了什么样的类型,这里就操作什么样的类型。提高了扩展性。
System.out.println(q);
}

// 通过Object操作所有对象,达到扩展的功能
public void show1(Object q){
System.out.println("show1:" + q);
}

// 这里相当于将泛型定义在了方法上,传入任何对象都可以进行操作,和Object实现的效果类似
public <Animal> void show1(Animal q){
System.out.println("show1:" + q);
}

/*
方法为静态时,不能访问类上定义的泛型。
因为静态不需要对象,泛型需要用对象进行明确new Tool<String>()
如果想要在静态方法上使用泛型就只能将泛型定义在方法上
*/
public static void method(MyTest q){
// 这里会出错
System.out.println("print:" + q);
}

/*
在静态方法上定义泛型,将可以实现泛型的使用。
泛型放到修饰符之后,返回值之前。
泛型传入的对象只能够使用Object对象的方法,因为这些方法是所有对象共有的。
*/
public static <Y> void methodNew(Y q){
System.out.println("print:" + q);
}
}


public static void main(String[] args) {
Tool<Student> tool = new Tool<Student>();
tool.setQ(new Worker());/*因为上边指定Student为使用的类,所以这里传入Worker(),将报错。类型检查没过去。*/
/*
1 用了泛型,不需要再进行强转
2 如果传入的对象类型和泛型指定的类型不一致会报错,降低运行时错误
*/
Student stu = tool.getQ();

}

泛型接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 使用的泛型需要定义到接口上,所以是泛型接口
// 这个泛型T相当于用一个参数来接收传进来的数据类型
// 虽然不知道是什么类型,但是可以用T代表这个类型
interface Inter<T>{
// 在使用方法的时候,不知道会传入什么类型的数据,所以使用泛型。
public void show(T t);
}
// 实现了接口,这里就知道需要什么类型的数据了,所以将泛型换成具体数据类型
class InterImpl implements Inter<String>{
public void show(String s) {
System.out.println(s);
}
}
// 实现了泛型接口之后,仍旧不知道会传入什么类型的数据
// 那么该类仍旧使用泛型接收数据,再将泛型传给接口。创建对象的时候可以传入实际数据类型。
class InterImpl2<Q> implements Inter<Q>{
public void show(Q s) {
System.out.println(s);
}
}

泛型通配符

  1. 同时对ArrayListLinkedListHashSet等类进行相同操作,可以使用他们的共同父类Collection进行操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public static void main(String[] args) {
    ArrayList<String> al = new ArrayList<String>();
    al.add("abc");
    al.add("hehe");

    HashSet<String> a2 = new HashSet<String>();
    al.add("abc1");
    al.add("hehe1");

    printCollection(al);
    printCollection(a2);
    }

    public static void printCollection(Collection<String> al) {
    Iterator<String> it = al.iterator();
    while(it.hasNext()){
    String item = it.next();
    System.out.println(item);
    }
    }
  2. 泛型通配符的使用(用于传入未知类型,可能会传入StringIntegerPerson或更多类型)
    ?相当于是<? extends Object>,用于参数中,public static void printCollection(Collection<?> al)
    也可以写成public static <T extends Object> void printCollection(Collection<T> al)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    public static void main(String[] args) {
    ArrayList<String> al = new ArrayList<String>();
    al.add("abc");
    al.add("hehe");

    HashSet<Integer> a2 = new HashSet<Integer>();
    a2.add(123);
    a2.add(312);

    printCollection(al);
    printCollection(a2);
    }

    /*
    加上?是因为不明确传入的类型,也不需要对传入数据进行太多操作。
    加上?相当于是<? extends Object>
    也可以不加<?>,,直接Collection用来接收所有类型的数据,兼容老版本Java,但建议写上
    */
    public static void printCollection(Collection<?> al) {
    Iterator<?> it = al.iterator();
    while(it.hasNext()){
    // 这里只能直接操作,因为不知道传入的是什么类型.但是无论传入什么类型的数据,都可以调用toString()进行打印
    System.out.println(it.next());
    }
    }

    // 可以方法上加泛型,这样就可以进行更多的操作了,
    // 这里定义传入的Collection泛型类型为T,返回值为T
    public static <T> T printCollection1(Collection<T> al) {
    Iterator<T> it = al.iterator();
    while(it.hasNext()){
    // 可以用变量接收it.next()
    T t = it.next();
    System.out.println(t);
    }
    // 相当于在不明确传入的是什么类型的数据的情况下,以原数据类型返回需要的数据。
    return t;
    }
  3. 泛型的extends

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 写泛型,要左右两边传入的泛型一致。
    // 否则传入的时候如果前边泛型限定了Person,那么后边容器应该能够同时放Student和Worker
    // 但是容器里只放了Student,说明不符合
    // 后边尖括号的泛型可以省略,默认使用原类型
    Collection<Person> p1 = new ArrayList<>();
    // 直接写泛型没有继承关系,只能传入什么,接收什么
    // 但是可以通过通配符进行泛型的限定
    // ? extends Person相当于传入很多种类型的数据,但是这些数据都是继承自Person的。
    Collection<? extends Person> p1 = new ArrayList<Student>();
    // 方法的参数使用? extends Person
    public static void printCollection(Collection<? extends Person> c){
    Iterator<? extends Person> it = c.iterator();
    while(it.hasNext()){
    Person p = it.next();
    System.out.println(p.getName() + ":" + p.getAge());
    }
    }
  4. 泛型的super

    • 使用<? super E>可以接收E和其父类型,下限!。(用的少)
    • TreeSet有该构造器TreeSet(Comparator<? super E> comparator),传父类比较器可以统一比较父类及其子类。
    • 通常对集合中元素进行取出操作的时候,可以使用下限。
      1
      2
      3
      4
      5
      6
      7
      public static void printCollection(Collection<? super Student> c){
      // 迭代器的泛型和Collection的泛型一致
      Iterator<? super Student> it = c.iterator();
      while(it.hasNext()){
      System.out.println(it.next());
      }
      }

集合框架1.5新特性

Collection

Collection是单列集合的顶层接口,但是JDK1.5后Collection又继承自Iterable接口。

Iterable

Iterable接口的方法只有一个,iterator()。有这个方法是因为以后集合框架可能仍需扩展,但希望每个集合框架都能有iterator()方法来拥有迭代功能。

增强for循环

JDK1.5后新添增强for循环(一般称为foreach),可以遍历Collection和数组

  • for (类型 变量 : Collection||Array) {},简化了Iterator的遍历过程
  • 增强for循环将迭代工作简化成了一个语句,一般只用于迭代,而Iterator在迭代的过程中还可以继续操作原Collection。增强for循环简化之后少了些功能。
  • 传统for循环和增强for循环的比较
    1. 传统for循环可以完成对语句执行很多次,因为可以定义控制循环的增量和条件
    2. 增强for循环是一种简化形式,必须有被遍历的目标(数组或Collection单列集合,不可以直接获取Map的key和value,只能用,Map.Entry或者获取了key再获取value)
    3. 对于数组,如果仅遍历元素可以使用增强for循环,需要使用角标的操作只能用传统for循环。(增强for循环无法获得角标,自定义增量就不如使用传统for循环了)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      List<String> list = new ArrayList<String>();
      list.add("abc1");
      list.add("abc2");
      list.add("abc3");
      for (String item : list) {
      // 简化了书写,就像最开始Iterotor代替了Enumeration
      System.out.println(item);
      }

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

函数可变参数

  • Arrays.asList用过这个特性。
  • 对于加法运算,如果传入10个数需要进行运算,可以传一个数组过去(数据多了就可以用数组来进行存储)。但这样很麻烦
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public static void main(String[] args) {
    int[] arr = {1, 2, 3, 4};
    int sum = add(arr);
    System.out.println("sum=" + sum);
    }

    public static int add(int[] operateNumbers) {
    int result = 0;
    for (int item : operateNumbers) {
    result += item;
    }
    return result;
    }
  • 使用可变参数列表,里边可以传数组,也可以传不定量个数据(int... arr)一个都不传相当于是给了他一个空数组
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public static int add(int[] operateNumbers) {
    int firstAdd = newAdd(operateNumbers);
    // 本质是一个数组,但接收的是数组中的元素。传入后将元素自动封装为数组。简化了书写。
    return newAdd(firstAdd, 1, 2, 4, 5);
    }

    // ...代表省略,有很多参数,int...相当于int[]的新写法,将创建数组和传数组过程简化,使其内部实现
    public static int newAdd(int... arr) {
    int result = 0;
    for (int item : arr) {
    result += item;
    }
    return result;
    }
  • 可变参数类型必须放在参数列表的结尾,因为读取顺序而产生问题。(数组因为传入的不是未知数量的参数,所以没关系)
    1
    2
    3
    4
    5
    6
    7
    8
    // 正确,这里相当于把第一个数算成a的,从第二个开始放到arr里
    public static int test1(int a, int... arr) {}
    // 错误,这里会把后边的数都当成int放到arr里
    public static int test2(int... arr, int a) {}
    // 这里只相当于两个参数
    public static int test3(int[] arr, int a) {}
    // 错误,不可以放多种可变参数列表,只能放一种
    public static int newAdd(String... test, int... a) {}

静态导入

  • 如果使用方法的时候觉得Collections.sort的Collections写起来也很费劲,可以将该方法直接导入进来
  • 静态导入导进来的的是静态成员(静态方法,静态变量都可以)
  • 如果方法名和其他类的方法名冲突,类名就不可省略了。
  • 一般不使用,使用了之后,反而隐藏了方法使用的层级关系,减少了阅读性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 导入静态的方法,或者import static java.util.Collections.* 
    import static java.util.Collections.sort;
    // println的使用
    // 使用其中的方法的时候可以省略System前缀,println()由一个对象操纵,out持有该对象,所以只能简化到out
    import static java.lang.System.*;
    public static void main(String[] args) {
    List<String> list = new ArrayList<String>();

    list.add("abc1");
    list.add("abc4");
    list.add("abc2");
    list.add("abc3");

    System.out.println(list);
    // 使用的时候不需要再加上Collections就可以直接进行使用了
    sort(list);
    // 用out调用println方法
    out.println(list);
    }
 评论