泛型
泛型的基本概念
泛型是jdk1.5之后增加的, 可以帮助我们建立类型安全的集合
泛型的本质就是"数据类型的参数化", 处理的数据类型不是固定的, 而是可以作为参数传入. 可以把泛型理解为数据类型的一个占位符(类似形参), 告诉编译器在调用泛型时必须传入实际类型. 这种参数类型可以用在类、接口和方法中, 分别被称为泛型类、泛型接口、泛型方法.
参数化类型简单说就是:
- 把类型当作是参数一样传递
- <数据类型> 只能是引用类型
泛型的好处
- 代码的可读性更好, 不需要像Object类那样强制转换
- 程序更加安全, 只要编译时期没有警告, 运行时期就不会出现ClassCastException异常
类型擦除
编码时采用泛型写的类型参数, 编译器会在编译时去掉, 称之为"类型擦除".
泛型主要用于编译阶段, 编译后产生的字节码class文件不包含泛型中的类型信息, 涉及类型强制转换仍然是普通的强制类型转换. 泛型参数在编译后会被替换成Object, 运行时虚拟机并没有泛型这个概念
泛型主要是方便程序员的代码编写, 以及更好的安全性检测
泛型的使用
定义泛型
泛型字符可以是任意标识符, 一般采用几个标记: E, T, K, V, N, ?
泛型标记 | 对应单词 | 说明 |
---|---|---|
E | Element | 在容器中使用, 表示容器中的元素 |
T | Type | 表示普通的java类 |
K | Key | 表示键, 例如: Map中的键Key |
V | Value | 表示值 |
N | Number | 表示数值类型 |
? | 表示不确定的java类型 |
泛型类
泛型类就是把泛型定义在类上, 用户使用该类的时候, 才把类型明确下来. 泛型类的具体使用方法是在类的名称后面添加一个或多个类型参数声明, 如: 、<T, K, V>
语法结构
public class 类名<泛型表示符号> {
}
泛型接口
泛型接口和泛型类的声明方式一致, 泛型接口的具体类型需要在实现类中进行声明
语法结构
public interface 接口名<泛型表示符号> {
}
泛型方法
泛型类中所定义的泛型, 在方法中也可以使用. 但是如果需要仅仅在某一个方法上使用泛型, 这时候可以使用泛型方法.
泛型方法是指将方法的参数类型定义成泛型, 以便在调用时接受不同类型的参数. 类型参数可以有多个, 用逗号隔开, 如: <K, V>. 定义时, 参数类型一般放到返回值前面.
调用泛型方法时, 不需要像泛型类那样告诉编译器是什么类型, 编译器可以自动判断.
非静态方法
语法结构
public <泛型表示符号> void getName(泛型表示符号 name){
}
-----------------------------------------------------------------------
public <泛型表示符号> 泛型表示符号 getName(泛型表示符号 name){
}
静态方法
静态方法无法访问类上定义的泛型; 如果静态方法操作的引用数据类型不确定的时候, 必须将泛型定义在方法上. 语法结构与非静态方法一样, 只是多了一个static
泛型方法与可变参数
在泛型方法中, 泛型也可以定义为可变参数类型
语法结构
public <泛型表示符号> void showMsg(泛型表示符号... args) {
}
通配符和上下限定
无界通配符
"?“表示类型通配符, 用于代替具体的类型. 它只能在”<>"中使用. 可以解决具体类型不确定的问题
语法结构
public void showFlag(Generic<?> generic) {
}
通配符的上限限定
上限限定表示通配符的类型是T类以及T类的子类或者T接口以及T接口的子接口. 该方式同样适用于泛型类的上限限定
语法结构
public void showFlag(Generic<? extends Number> generic){
}
通配符的下限限定
下限限定表示通配符的类型是T类以及T类的父类或者T接口以及T接口的父接口. 但是该方法不适用泛型类
语法结构
public void showFlag(Generic<? super Integer> generic) {
}
泛型总结
泛型主要用于编译阶段, 编译后生成的字节码class文件不包含泛型中的类型信息. 类型参数在编译后会被替换成Object, 运行时虚拟机并不知道泛型, 因此使用泛型时下面两种情况是错误的:
- 基本类型不能用于泛型
Test<int> t; //错误
Test<Integer> t; //正确
- 不能通过类型参数创建对象
T elm = new T(); 运行时类型参数T会被替换成Object, 无法创建T类型的对象, 容易引起误解, 所以在java中不支持这种写法
容器
基于数组不能满足对于管理和组织数据的需求, 需要一种更强大、更灵活、容量随时可扩的容器来装载对象, 容器(Collection)也称之为集合
容器的结构
结构图
单例集合
单例集合是指将数据一个一个的进行存储, 继承Collection接口的类都是单例集合
双例集合
双例集合是基于key-value结构来存储数据
单例集合的使用
Collection接口
jdk8以后Collection接口新增了几个方法(jdk新特性与函数式编程)
新增方法 | 说明 |
---|---|
removeIf | 删除容器中所有满足filter指定条件的元素 |
stream parallelStream | 分别返回该容器的Stream视图表示, 不同之处在于parallelStream返回并行的Stream, Stream是java函数式编程的核心类 |
spliterator | 可分割的迭代器, 不同以往的iterator需要顺序迭代, spliterator可以分割为若干个小的迭代器进行并行操作, 可以实现多线程提高操作效率 |
List接口
List接口有以下两个特点:
有序: 元素存入集合的顺序与取出的顺序一致, 每一个元素都有索引, 可根据索引精准访问元素
可重复: List允许加入重复的元素
除了Collection里面的方法, List多了一些跟索引有关的方法
常用方法
List<String> list = new ArrayList<>(); //实例化ArrayList容器
list.add("a"); //添加元素, 返回值为boolean类型
list.add("b");
list.add(1, "c"); //添加元素到指定位置, 后面的元素往后移, 返回值为void
System.out.println(list.remove(1)); //删除指定位置元素, 返回被删掉的元素
list.add("a");
System.out.println(list.remove("a")); //删除第一次出现的指定元素, 删除成功返回true, 否则返回false
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
list.set(0, "a");
System.out.println(list.set(1, "b")); //替换指定位置的元素, 返回原来的元素, 所指定的位置index < size
list.clear(); //清空容器
System.out.println(list.isEmpty()); //判断容器是否为空
System.out.println(list.contains("a")); //判断容器是否包含指定元素
System.out.println(list.indexOf("a")); //查找元素第一次出现时的位置
list.add("a");
System.out.println(list.lastIndexOf("a")); //查找元素最后一次出现时的位置
单例集合转换成泛型类型数组
/**
* 单例容器转换为泛型类型数组
*/
//方法一: 循环强转, 不常用
Object[] ob = list.toArray();
System.out.println(Arrays.toString(ob)); //输出[a, b, c]
String[] str = new String[list.size()];
for (int i = 0; i < list.size(); i++) { //方法一: 循环强转, 不常用
str[i] = (String)ob[i];
}
System.out.println(Arrays.toString(str)); //输出[a, b, c]
//方法二, 常用方法
String[] string = list.toArray(new String[0]); //toArray的参数需要是与泛型类型相同的数组
//new String[0]就是起一个模板的作用,指定了返回数组的类型,0是为了节省空间,因为它只是为了说明返回的类型
System.out.println(Arrays.toString(string)); //输出[a, b, c]
List的并交差操作
//容器的并集操作
List<String> list2 = new ArrayList<>();
list2.add("c");
list2.add("d");
list2.add("e");
System.out.println(list.addAll(list2)); //操作成功返回true, 注意: list2不能为null
System.out.println(list); //输出[a, b, c, c, d, e], List接口允许出现重复元素, 所以是传入容器整个添加到list后面
System.out.println();
//容器的交集操作
System.out.println(list); //[a, b, c, c, d, e]
System.out.println(list2); //[c, d, e]
list.retainAll(list2); //保留与传入容器都有的元素
System.out.println(list); //[c, c, d, e]
//容器的差集操作
list.removeAll(list2); //移除list2中有的所有元素
System.out.println(list); //输出[]
Vector容器类
Vector底层是用数组实现的, 相关的方法都加了同步检查, 因此"线程安全, 效率低". 比如, indexOf方法就增加了synchronized同步标记.
Vector的使用
Vector的使用与ArrayList是相同的, 都实现了List接口
Vector在初始化的时候就会分配空间, 数组容量是10, 而ArrayList是在add操作后才会给数组扩容, 并且Vector扩容一次容量变为之前的两倍, ArrayList是1.5倍
import java.util.List;
import java.util.Vector;
public class VectorTest {
public static void main(String[] args) {
//实例化Vector
List<Character> v = new Vector<>();
v.add('a');
v.add('b');
v.add('c');
for (int i = 0; i < v.size(); i++) {
System.out.println(v.get(i));
}
System.out.println("-----------------");
for (Character c: v) {
System.out.println(c);
}
}
}
Stack容器
Stack栈容器是Vector的一个子类, 它实现了一个标准的后进先出(LIFO)的栈, 通过5个操作方法对Vector进行扩展, 允许将向量视为堆栈
操作栈的方法
Stack的使用
import java.util.Stack;
public class StackTest {
public static void main(String[] args) {
Stack<Character> stack = new Stack<>();
System.out.println(stack.push('a'));
System.out.println(stack.push('b'));
System.out.println(stack.push('c'));
System.out.println(stack.push('d'));
System.out.println(stack.peek());
System.out.println(stack.pop());
System.out.println(stack.search('a')); //输出3, a是从栈顶往下数第3个元素
for (char c: stack) {
System.out.println(c); //从栈底开始打印
}
//用栈来判断括号是否匹配
String str = new String("{...{...(...[...]...)...}...}[");
Stack<Character> bracket = new Stack<>();
char c;
boolean flag = true;
for (int i = 0; i < str.length(); i++) {
c = str.charAt(i);
if (c == '{') {
bracket.push('}');
}
if (c == '[') {
bracket.push(']');
}
if (c == '(') {
bracket.push(')');
}
if (c == '}' || c == ']' || c == ')') {
if (bracket.empty()) {
flag = false;
break;
}
if (c == bracket.peek()) {
bracket.pop();
}
else {
flag = false;
break;
}
}
}
if (!bracket.empty()) {
flag = false;
}
System.out.println("括号是否匹配: " + flag);
}
}
LinkedList容器类
LinkedList底层用双向链表实现, 特点: 查询效率低, 增删效率高, 线程不安全
特有方法:
还有个peek()方法, 与Stack类相同
Set接口
Set接口继承自Collection, Set接口中没有新增方法, 方法和Collection保持完全一致, List中的方法在Set中仍然适用
Set的特点是无序、不可重复。无序指Set中的元素没有索引, 只能遍历查找;不可重复指不允许加入重复的元素, 更确切地讲,新元素如果和Set中某个元素通过equals()方法对比为true,则只能保留一个。
Set常用的实现类有:HashSet、TreeSet等,一般使用HashSet。
HashSet容器类
HashSet 是一个没有重复元素的集合,不保证元素的顺序。线程不安全, 而且 HashSet 允许有 null 元素。HashSet 是采用哈希算法实现,底层实际是用 HashMap 实现的(HashSet 本质就是一个简化版的 HashMap),因此,查询效率和增删效率都比较高。
import java.util.HashSet;
import java.util.Set;
public class HashSetTest {
public static void main(String[] args) {
Set<String> set = new HashSet<>(); //实例化
set.add("bva"); //添加元素
set.add("cvdss");
set.add("adsdb");
set.add("dvxcvs");
set.add("ccdfg");
set.add("bva");
//打印集合元素, 无序且无重复元素
for (String s: set) {
System.out.println(s); //无序输出
}
System.out.println(set.remove("a")); //删除元素
System.out.println(set.contains("a")); //查看指定元素是否存在
System.out.println(set.size());
}
}
TreeSet容器类
TreeSet 是一个可以对元素进行排序的容器。底层实际是用 TreeMap 实现的,内部维持了一个简化版的TreeMap, 通过key来存储集合的元素. TreeSet内部需要对存储的单元进行排序, 因此需要给定排序规则.
排序规则实现方式:
- 通过元素自身实现比较规则
- 通过比较器指定比较规则
通过元素自身实现比较规则
在元素自身实现比较规则时,需要实现 Comparable 接口中的 compareTo 方法,该方法中用来定义比较规则。TreeSet 通过调用该方法来完成对元素的排序处理。
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetTest {
public static void main(String[] args) {
Set<Figure> set = new TreeSet<>();
set.add(new Figure("miku", 999));
set.add(new Figure("daodao", 699));
set.add(new Figure("mermaid", 999));
for (Figure f: set) {
System.out.println(f);
}
}
}
class Figure implements Comparable<Figure> {
int price;
String name;
Figure() {}
Figure(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Figure)) return false;
Figure figure = (Figure) o;
return price == figure.price && name.equals(figure.name);
}
@Override
public int hashCode() {
return Objects.hash(price, name);
}
//通过元素自身实现比较规则
@Override
public int compareTo(Figure o) {
if (price > o.price) return 1; //升序排列
if (price == o.price) return name.compareTo(o.name);
else return -1;
}
@Override
public String toString() {
return "" + name + " $" + price;
}
}
通过比较器实现比较规则
通过比较器定义比较规则时,我们需要单独创建一个比较器,比较器需要实现Comparator 接口中的 compare 方法来定义比较规则。在实例化 TreeSet 时将比较器对象交给TreeSet 来完成元素的排处理。此时元素自身就不需要实现比较规则了。
import java.util.Comparator;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetTest {
public static void main(String[] args) {
Set<Figure> set = new TreeSet<>(new FigureComparator());
set.add(new Figure("miku", 999));
set.add(new Figure("daodao", 699));
set.add(new Figure("mermaid", 999));
for (Figure f: set) {
System.out.println(f);
}
}
}
//通过比较器实现比较规则
class FigureComparator implements Comparator<Figure> {
@Override
public int compare(Figure figure1, Figure figure2) {
if (figure1.getPrice() > figure2.getPrice())
return 1;
else if (figure1.getPrice() == figure2.getPrice())
return figure1.getName().compareTo(figure2.getName());
else
return -1;
}
}
双例集合
Map接口介绍
Map接口特点
Map 接口定义了双例集合的存储特征,它并不是 Collection 接口的子接口。双例集合的存储特征是以 key 与 value 结构为单位进行存储。体现的是数学中的函数 y=f(x)的概念。
Map 与 Collecton 的区别:
♦ Collection 中的容器,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。
♦ Map 中的容器,元素是成对存在的(理解为现代社会的夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。
♦ Collection 中的容器称为单列集合,Map 中的容器称为双列集合。
♦ Map 中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。
♦ Map 中常用的容器为 HashMap,TreeMap 等。
Map的常用方法
HashMap容器类
HashMap 是 Map 接口的接口实现类,它采用哈希算法实现,是 Map 接口最常用的实现类。 由于底层采用了哈希表存储数据,所以要求键不能重复,如果发生重复,新的值会替换旧的值。 HashMap 在查找、删除、修改方面都有非常高的效率。
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class HashMapTest {
public static void main(String[] args) {
//实例化
Map<Integer, String> map = new HashMap<>();
//添加元素
System.out.println(map.put(1, "abc")); //put方法, key之前不存在则返回null
System.out.println(map.put(1, "a")); //key已存在则返回原来的value值
Map<Integer, Integer> map_ = new HashMap<>();
System.out.println(map_.put(1, 1)); //value是Integer类型返回的也是null
map.put(2, "b");
map.put(3, "c");
map.put(4, null);
//获取元素
System.out.println(map.get(1));
Set<Integer> set = map.keySet();
for (Integer i: set) {
System.out.println(map.get(i));
}
//这种方法效率最高
Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
for (Map.Entry<Integer, String> entry: entrySet) {
System.out.println(entry.getKey() + "---" + entry.getValue());
}
//并集操作, 需要泛型类型相同
Map<Integer, String> map2 = new HashMap<>();
map2.put(1, "q");
map2.put(4, "d");
map2.put(5, "e");
map.putAll(map2); //并集操作, 重复key直接覆盖其value值
entrySet = map.entrySet();
for (Map.Entry<Integer, String> entry: entrySet) {
System.out.println(entry.getKey() + "---" + entry.getValue());
}
//删除操作
System.out.println(map.remove(1)); //返回所删除的key对应的value
//判断元素是否存在
System.out.println(map.containsKey(1));
System.out.println(map.containsValue("b"));
}
}
TreeMap容器类
TreeMap 和 HashMap 同样实现了 Map 接口,所以,对于 API 的用法来说是没有区别的。HashMap 效率高于 TreeMap;TreeMap 是可以对键进行排序的一种容器,在需要对键排序时可选用 TreeMap。TreeMap底层是由红黑树实现的。
在使用 TreeMap 时需要给定排序规则(跟TreeSet是一样的):
♦ 元素自身实现比较规则
♦ 通过比较器实现比较规则
Iterator迭代器
Iterator迭代器接口介绍
Collection接口继承了Iterable接口,在该接口中包含一个名为iterator的抽象方法,所有实现了Collection接口的容器类对该方法做了具体实现。iterator方法会返回一个Iterator接口类型的迭代器对象,在该对象中包含了三个方法用于实现对单例容器的迭代处理。
Iterator对象的工作原理:
Iterator接口定义了如下方法:
- boolean hasNext(); //判断游标当前位置是否有元素,如果有返回true,否则返回false;
- Object next(); //获取当前游标所在位置的元素,并将游标移动到下一个位置;
- void remove(); //删除上次调用next()时返回的元素
//while循环迭代输出元素
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
//for循环迭代输出元素
for (Iterator<String> it = set.iterator(); it.hasNext(); ) {
String s = it.next();
it.remove(); //删除迭代器元素, 也就是删除容器里的元素
System.out.println(s);
}
System.out.println("------------");
for (String s: set) {
System.out.println(s); //什么都没输出, 元素在迭代器里被删完了
}
Collections工具类
Collections 是一个工具类,它提供了对 Set、List、Map 进行排序、填充、查找元素的辅助方法。该类中所有的方法都为静态方法。
常用方法:
- void sort(List) //对 List 容器内的元素排序,排序的规则是按照升序进行排序。
- void shuffle(List) //对 List 容器内的元素进行随机排列。
- void reverse(List) //对 List 容器内的元素进行逆续排列 。
- void fill(List, Object) //用一个特定的对象重写整个 List 容器。
- int binarySearch(List, Object)//对于顺序的 List 容器,采用折半查找的方法查找特定对象。