Java笔记(11):比较器、集合、Lambda表达式
比较器
当我们需要实现对象的排序问题的时候,就要使用到Java的比较器。
Java实现对象排序的接口有两个:
- 自然排序:java.lang.Comparable
- 定制排序:java.util.Comparator
自然排序
String、包装类等都默认实现了Comparable接口,重写了comparaTo(obj)方法,因此都可以直接使用自然排序。
自定义类若想实现自然排序,我们需要去实现Comparable接口,然后重写comparaTo(obj)方法方法,重写comparaTo(obj)方法具有一定的规则:
如果当前对象this大于形参对象obj,则返回正整数;如果当前对象this小于形参对象obj,则返回负整数;如果相等,则返回0。
1 | public int compareTo(Object o) { |
定制排序
当元素的类没有实现Comparable接口而又不便修改代码,或是实现了Comparable接口的排序不适合当前操作,则我们可以使用Comparator的对象进行排序。
由于Comparator也是一个接口,同样需要重写方法,我们需要重写compare(Object o1, Object o2)方法,重写规则与自然排序相类似:
如果返回正整数,则代表o1大于o2;如果返回负整数,则代表o1小于o2;如果返回0,则表示o1与o2相等。
使用的方式是在调用sort()等排序方法的时候,在参数列表添加Comparator的对象。
1 | Arrays.sort(goods, new Comparator() { |
集合
Java集合是Java的一种容器,可以动态的把多个对象的引用放入容器中。集合与数组类似,都是Java容器,但是数组作为存储对象的容器方面具有一些弊端。
数组在内存存储方式的特点:
- 数组初始化以后,长度是确定的
- 数组声明的类型,决定了元素初始化的类型
数组在存储数据方面的弊端:
- 数组初始化以后,长度不可改变,不可拓展
- 数组提供的属性和方法太少,不便于增删改操作,且往往效率低,而且无法直接获取存储元素的个数(只能通过遍历等方法)
- 数组存储的数据是有序的,可以重复的,导致存储数组的特点单一
Java集合可以分为两种体系:
-
Collection接口:单列数组,定义了一组对象的方法的集合
- List接口:元素有序、可重复的集合
- Set接口:元素无序、不可重复的集合
- …
-
Map接口:双列数据,保存具有映射关系“Key-value对”的集合,类似于“函数”,一个key只能对应一个value,一个value可以有多个key
Collection接口
Collection接口常用的方法
List接口:有序、可重复
迭代方式:fori配合size() 与 for-each
1 | //size()方法用于计算List的大小 |
- LinkedList:链表,查询慢、增删快,无同步,线程不安全
- ArrayList:动态数组,查询快、删减慢,无同步,线程不安全
- Vector:动态数组,查询快、删减慢,同步,线程安全,但效率相较低
Set接口:无序、不可重复
无序性不等于随机性,无序性指的是存放的元素不是按照索引的顺序添加的,而是根据元素的哈希值决定的。
迭代方式:for-each,不可以用fori迭代。
-
HashSet:基于HashMap实现,Set接口的主要实现类,它不会记录插入的顺序。HashSet不是线程安全的。
添加元素的过程:
-
对添加的元素计算哈希值。当添加元素为对象时,一般而言,我们都会在对象的类中重写hashCode()方法,若不重写,则Object继承下来的hashCode()的作用仅为生成一个随机数
-
取16(定义的底层数组的默认容量)的模,放入对应的位置
-
若遇到取模相同的,哈希值不同时,则会使用链表的方式存储,jdk7时,会使新元素替代原元素,并指向原元素,jdk8则会让原元素指向新元素
-
若遇到哈希值相同时,则需要equals()比较,若equals()相同则不填入,若equals()不相同,则会使用链表
-
-
LinkedHashSet:是HashSet的子类,使用链表使得迭代结果是添加顺序的。
-
TreeSet:底层为红黑树结构存储,可以实现排序的实现类,要求放入的元素为相同类的对象。向TreeSet中添加元素,在遍历时默认会以自然排序的顺序遍历。
若我们添加的元素是对象之类的,由于没有比较的方式,则会报错,我们需要重写对象的类的compareTo()方法。自然排序实际上是实现了Comparable接口。在自然排序中,判断对象是否相等的标准为compareTo()返回是否为0,不再是equals。
而定制排序则是实现了Comparator接口,我们要使用定制排序,则需要在new TreeSet的时候,使用他的有参构造器,在参数内填入Comparator接口实现类的对象。在定制排序中,判断对象是否相等的标准为compare()返回是否为0,不再是equals。
Set接口里没有定义新的方法。
向Set接口的实现类添加的元素,一定需要重写hashCode()和equals()方法。且两个方法一定要保持规则的一致性,保证具有相等的散列码。
使用Iterator接口遍历
Iterator对象成为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。
迭代器定义:提供一种方法访问一个容器对象中各个元素,而不需要暴露该对象的内部细节。
Collection接口继承了java.lang.Iterable接口,因此它的实现类都提供了一个iterator()方法,它可以返回一个Iterable接口的对象,集合对象每次调用iterator()都会得到一个全新的迭代器对象,默认游标为第一个元素之前。
得到一个Iterable接口对象后,我们可以用Iterable的方法来获取集合内的元素:
1 | while (iterator.hasNext()){//hasNext()判断有无下一个元素 |
Iterable有一个方法remove(),可以移除集合中的当前元素。
我们也可以使用for-each循环遍历集合,这样可以不使用Iterable接口。
Map接口
Map接口的常用方法
Map接口的实现类
-
HashMap:作为Map的主要实现类。线程不安全,效率高。可以存储null的key和value。
-
LinkedHashMap:HashMap的子类。添加了一个链表的机构,可以使得在遍历时,按照添加顺序遍历。对于频繁的遍历操作,可以使用这个实现类。
-
TreeMap:可以按照添加的Key进行排序,按照自然排序或定制排序遍历,类似于TreeSet,底层实现为红黑树。
Map的特殊方法:
-
Hashtable:最早的实现类,在Map出现之前就有。线程安全,效率低。不可以存储null的key和value。
-
Properties:Hashtable的子类。常用于处理配置文件,key和value都是String类型。
总结
- ArrayXxx:底层数据结构是数组,查询快,增删慢
- LinkedXxx:底层数据结构是链表,查询慢,增删快
- HashXxx:底层数据结构是哈希表。依赖两个方法:hashCode()和equals()
- TreeXxx:底层数据结构是二叉树。两种方式排序:自然排序和比较器排序
Lambda表达式
Lambda是一个匿名函数,可以理解为一段可以传递的代码。使用Lambda表达式可以使得我们的代码更加简洁。
Java的Lambda表达式本质是作为接口的实例。
使用举例:
1 | //未使用Lambda表达式 |
使用方法
首先我们看到举例代码内的主要部分:
1 | (o1, o2) -> Integer.compare(o1, o2) |
格式:
-> :Lambda操作符 / 箭头操作符
左侧:Lambda形参列表 实际上即为接口中抽象方法的形参
右侧:Lambda体 实际上即为实现的抽象方法的方法体
具体格式大致可分为六种情况:
-
无参无返回值
不使用Lambda表达式:
1
2
3
4
5
6
7Runnable runnable = new Runnable(){
public void run() {
System.out.println("test");
}
};
runnable.run();使用Lambda表达式:
1
2Runnable runnable2 = () -> System.out.println("test");
runnable2.run(); -
有参无返回值
不使用Lambda表达式:
1
2
3
4
5
6
7Consumer<String> con1 = new Consumer<>(){
public void accept(String s) {
System.out.println(s);
}
};
con1.accept("114514");使用Lambda表达式:
1
2
3
4Consumer<String> con2 = (String s) -> {
System.out.println(s)
};
con2.accept("1919"); -
数据类型省略 "类型推断"
上面一种情况,编译器可以判断出数据类型,因此我们可以省略数据类型:
1
2
3
4Consumer<String> con3 = (s) -> {
System.out.println(s)
};
con3.accept("810"); -
只需要一个参数时,可省略参数小括号
上面的情况,因为只有一个参数,因此我们可以省略参数的小括号:
1
2
3
4Consumer<String> con2 = s -> {
System.out.println(s)
};
con2.accept("没活了"); -
多参数且多条执行语句,且拥有返回值
不使用Lambda表达式:
1
2
3
4
5
6
7
8Comparator<Integer> com1 = new Comparator<>(){
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};使用Lambda表达式:
1
2
3
4
5Comparator<Integer> com2 = (o1, o2) -> {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}; -
当Lambda体只有一条语句时,return与大括号可以省略
不使用Lambda表达式:
1
2
3
4
5
6Comparator<Integer> com1 = new Comparator<>(){
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};使用Lambda表达式:
1
Comparator<Integer> com2 = (o1, o2) -> o1.compareTo(o2);
泛型
泛型是 JDK 5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
注:例子来源 Java 基础 - 泛型机制详解 | Java 全栈知识体系 (pdai.tech)
泛型的作用
-
代码复用
在没有泛型的情况下,如果我们想实现不同类型的加法,我们需要每种类型都重载一个add方法(如下):
1
2
3
4
5
6
7
8
9
10
11
12
13
14private static int add(int a, int b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static float add(float a, float b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static double add(double a, double b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}通过泛型,我们可以复用为一个方法:
1
2
3
4private static <T extends Number> double add(T a, T b) {
System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
return a.doubleValue() + b.doubleValue();
}实际上这就是一种模板开发的方式,在实例化前不指定T的类型,在实例化时再去指定,可以达到代码复用的作用。
-
类型安全
泛型中的类型在使用时指定,不需要强制类型转换
1
2
3
4List list = new ArrayList();
list.add("String");
list.add(100d);
list.add(new Person());在使用list时,list里的元素是Object类型的,我们无法约束其中的类型,所以当我们取出元素的时候,很容易会出现类型转换错误的问题。
使用泛型可以达到类型约束的效果,提供了一个编译前的检查:
1
List<String> list = new ArrayList<String>();
泛型的使用
泛型的使用大概分为三种类型:泛型类、泛型接口、泛型方法
泛型类、泛型接口
泛型类和泛型接口是一种模板化开发的思想,也就是我们提及的第一个作用。
如果定义了泛型类,在实例化时没有指定类的泛型,则认为此泛型为Object,不建议这样使用。
一个简单的泛型类:
1 | class Point<T>{ // 此处可以随便写标识符号,T是type的简称 |
多元泛型:
1 | class Notepad<K,V>{ // 此处指定了两个泛型类型 |
泛型方法
泛型方法的使用并不是简单将类型替换为泛型,以下例子均不是泛型方法:
1 | public void setKey(K key){ |