异常,泛型和集合框架
1.1 异常
什么是异常?异常有什么作用
异常就是在程序当中出现的一些问题(即不正常的情况和问题);异常可以增强程序的健壮性。
1.2 异常体系结构
异常在java当中都是以类的形式存在,每一个异常类都可以创建一个异常对象;
Java.Lang.Theowable是所有异常的父类,他的子类有Error和Exception两类;Error代表系统错误;Exception才代表异常,它代表我们在程序当中可能会出现的问题,所以我们通常用Exception和它的孩子来封装程序出现的问题;
异常通常分为两种,第一种叫做运行时异常(RunTime Exception及其子类),指在编译阶段不会出现问题,但是程序在运行过程当中出现问题,例如,数组的索引引起的越界异常
第二种就是编译时异常,指在编译阶段就会出现错误提醒的,例如,日期解析异常
1.3异常的解决办法
1.throws方法上报给调用者(推卸责任,调用者知道)
在方法声明的位置上使用 throws
关键字抛出,谁调用我这个方法,我就抛给谁。抛给 调用者
来处理。这种处理异常的态度:上报。
1.try......catch方法处理,捕捉(调用者自己不知道知道)
这个异常不会上报,自己把这个事儿处理了。异常抛到此处为止,不再上抛了。
注意:
- 只要异常没有捕捉,采用上报的方式,此方法的
后续代码不会执行
。 - try语句块中的某一行出现异常,该行
后面的代码不会执行
。 - try…catch捕捉异常之后,后续代码可以执行。
总结
1.异常是代码在编译或者执行当中可能出现的问题。
2.异常的代表是Exception,分为编译时异常和运行时异常;编译时异常没有继承RunTimeException的异常,在编译时就会出现错误;运行时异常继承RunTime Exception或者其子类的异常,编译阶段不会报错,运行时出现错误。
3.异常的作用:用来查找bug;可以作为方法内部的特殊返回值,通知上层调用者底层的执行情况。
2.1 泛型
定义类,接口,方法时,同时声明了一个或者多个变量(如,<E>),称为泛型类,泛型接口,泛型方法,统称为泛型;
作用:泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力。避免强制类型转换,以及可能出现的问题;
泛型的本质:把具体的数据类型作为参数传给数据类型。
2.2泛型类和泛型接口
修饰符 class/interface 类名<类型变量(一个或者多个,用,隔开)>{ }
注意:类型变量建议用大写的英文字母,常用的有:E,T,K,V,等;
2.3泛型方法,通配符和上下限
泛型方法:
修饰符 <类型变量(一个或者多个,用,隔开)> 返回值类型 方法名称(形参列表){ }
通配符:
就是"?",可以在"使用泛型"的时候代表一些类型;E T K V 是在定义泛型的时候使用。
泛型的上下限:
泛型上限:? extends 类型名称:?能够接收的必须是类型名称或者其子类;
泛型下限:? super 类型名称:?能够接收的必须是类型名称或者其父类;
2.4泛型支持的类型
泛型不支持基本数据类型,只能支持对象类型(即引用数据类型)。
包装类:
就是把基本类型的数据包装成对象的类型。byte->Byte;short->Short;int->Integer;long->Long;char->Character;float->Float;double->Double;boolean->Boolean;
自动装箱和自动拆箱:
自动装箱:基本数据类型可以自动转换为包装类型。
自动拆箱:包装类型可以自动转换为基本数据类型。
包装类具备的其他功能:
可以把基本数据类型转换为字符换类型;可以把字符串类型的数值转换成数值本身所对应的实际值;
3.1Collection集合
集合体系结构
单列集合Collection,每一个元素(数据)只能包含一个值;
Collection集合特点:
List系列集合:添加的元素是有序,可重复,有索引;(ArrayList,LinkedList);
ArrayList:
ArrayList底层是基于数组存储数据的;数组的特点是:根据索引查询数据的速度快;增加删除数据的效率低,需要增加数据之后扩容或者,将删除之后的数据进行前移;
LinkedList:(双链表),提高查询速度,但是占比内存更大,对于首位操作很快
可以用来设计队列,先进先出,后进后出;可以用来设计栈,先进后出,后进先出;
LinkedList底层是基于链表存储数据的;链表的特点是:链表中的数据是由一个一个独立的结点组成的,在堆内存当中不一定连续,每一个结点包含数据值和下一个结点的地址;查询数据很慢,需要从头开始找;增加删除数据相对与数组较快;
Set系列集合:添加的元素是无序,不重复,无索引;(HashSet,LinkedSet,TreeSet)
LinkedHashSet添加元素有序,不重复,无索引;TreeSet添加元素升序排序,不重复,无索引;
HashSet底层原理为数组+链表+红黑树;(JDK8开始,当链表长度大于等于64,且数组长度超过8时,自动将链表转化为红黑树;红黑树就是可以自平衡的二叉树)
LinkedHashSet底层原理依然为数组+链表+红黑树;但是他添加元素有序;原因是他的每一个元素都会多一个额外的双链表机制去记录他的前一个元素和后一个元素的位置;
3.2并发修改问题
1.如果集合支持索引,可以使用for循环遍历,每一次删除一个数据之后i--;或者直接倒着遍历;
2.可以使用迭代器遍历,并使用迭代器提供的方法删除数据;
注意:增强for循环/Lambda遍历均不能解决并发修改异常问题,因此他们只适合做数据的遍历,不适合同时做修改操作;
4.1Map集合
双列集合Map,每一个元素包含两个值(值键对);<K,V>
Map集合体系的特点:
Map系列集合的特点都是由键决定的,值只是一个附属品,不做要求;
HashMap:无序,不重复,无索引;
LinkedMap:有序,不重复,无索引;
TreeMap:排序,不重复,无索引;(默认为大小升序排序)
5.1Stream流
Stream流是从JDK8开始新增的一套API,可以用于操作集合或者数组的数据;
优势:Stream流大量的结合了Lambda的语法风格来编程,功能强大,性能高效,代码简洁,可读性高;
Stream流操作处理数据的步骤:先得到集合或者数组的Stream流;然后调用Stream流的方法对数据进行处理;获取处理的结果。
6.1方法中可变参数的使用
可变参数就是一种特殊形参,定义在方法,构造器的形参列表当中,格式是:数据类型...参数名称
特点:可以不传数据;可以只传一个或者多个数据,可以传数组;
好处:常常用来灵活的处理数据;
注意:可变参数在形参列表当中只能出现一次,也只能出现在形参列表的最后;
综合案例:斗地主
创建一副完整的牌(包含大小王),将牌打乱,创建三名玩家,每一个人获得17牌,随机抽取一个人成为地主,地主得到剩下的三张牌,总共二十张牌,再将每一位玩家的牌打印出来
Test类:
package Game;
public class Test {
public static void main(String[] args) {
//目标;完成斗地主游戏的开发
//1.创建牌类,封装牌对象
Room r = new Room();
r.start();
}
}
Card类:
package Game;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Card {
private String num;
private String color;
private int sum;
@Override
public String toString() {
return color + num;
}
}
Room类:
package Game;
import java.util.*;
public class Room{
//2.准备一个容器存放牌
private List<Card> cards = new ArrayList<Card>();
//3.初始化牌的点数和花色
String[] colors = {"♥","♠","♣","♦"};
String[] nums = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
int sum = 0;
//4.使用增强for遍历牌的花色和点数,生成牌,存入容器
public Room() {
for (String num : nums) {
sum = sum+1;
for (String color : colors) {
Card card = new Card(num, color,sum);
cards.add(card);
}
}
//添加大小王
cards.add(new Card("","小王",++sum));
cards.add(new Card("","大王",++sum));
System.out.println(cards);
}
public void start() {
//5.洗牌,使用Collections中的shuffle方法
Collections.shuffle(cards);
System.out.println(" ");
System.out.println(cards);
//6.创建3个玩家,每一个玩家创建一个可以拿到牌的容器,用map来写
Map<String,List<Card>> players = new HashMap<>();
List<Card> ldh = new ArrayList<Card>();
players.put("刘德华",ldh);
List<Card> zxy = new ArrayList<Card>();
players.put("张学友",zxy);
List<Card> pyy = new ArrayList<Card>();
players.put("彭于晏",pyy);
//7.发牌,从第0张牌开始,发到第51张牌,发到3个人,每张牌发给一个人
for (int i = 0; i < cards.size()-3; i++) {
Card card = cards.get(i);
if(i%3==0)
ldh.add(card);
else if(i%3==1)
zxy.add(card);
else if(i%3==2)
pyy.add(card);
}
//8.显示最后剩下的三张底牌,底牌放在一个容器中
List<Card> dipai = new ArrayList<Card>();
for (int i = cards.size()-3; i < cards.size(); i++) {
Card card = cards.get(i);
dipai.add(card);
}
System.out.println("底牌是:"+dipai);
//9.抢地主,随机获取一个数,根据数获取对应的玩家,把底牌发给对应的玩家
Random r = new Random();
int index = r.nextInt(3) + 1;
System.out.println(index);
if(index==1){
ldh.addAll(dipai);
}
if(index==2){
zxy.addAll(dipai);
}
if(index==3){
pyy.addAll(dipai);
}
//10.排序
sortCard(ldh);
sortCard(zxy);
sortCard(pyy);
//11.遍历map,显示每个玩家的牌
for (Map.Entry<String, List<Card>> entry : players.entrySet()) {
String name = entry.getKey();
List<Card> cards = entry.getValue();
System.out.println(name+"的牌是:"+cards);
}
}
private void sortCard(List<Card> cards) {
Collections.sort(cards,(o1,o2) -> o1.getSum() - o2.getSum());
}
}
测试结果: