目录
一.异常处理
二.JAVA容器
一个键值(key)对应一个对象(value)的容器接口,并且没有排序
1.异常
一、 异常机制的概述
异常机制是指当程序出现错误后,程序如何处理。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。
程序错误分为三种:1.编译错误;2.运行时错误;3.逻辑错误。
(1)编译错误是因为程序没有遵循语法规则,编译程序能够自己发现并且提示我们错误的原因和位置,这个也是大家在刚接触编程语言最常遇到的问题。
(2)运行时错误是因为程序在执行时,运行环境发现了不能执行的操作,这种错误在编译时发现不了,只有在运行时才会报错。
(3)逻辑错误是因为程序没有按照预期的逻辑顺序执行。异常也就是指程序运行时发生错误,而异常处理就是对这些错误进行处理和控制。
1.在Java中当程序出现异常时,可以使用try···catch、try···catch···finally或try···finally进行处理。
1. 使用try···catch处理异常
try后是正常执行的语句,而catch后是对异常处理的语句,catch的括号中是程序需要处理的异常类型。语法格式如下:
try {
正常执行的语句
} catch(Exception e) {
对异常进行处理的语句
}
这里举一个算数异常的例子,如下。
public class ExceptionTest {
public static void main(String[] args) {
int result = 1 / 0;
try {
System.out.println(result);
} catch (Exception e) {
System.out.println("抛出异常:" + e.getMessage());
}}}
2. 使用try···catch···finally处理异常
这里try、catch后的语句与前面的相同,而finally后的语句无论是否发生异常都要执行,因此finally语句块通常用于执行垃圾回收。语法格式如下:
try {
正常执行的语句
} catch(Exception e) {
对异常进行处理的语句
} finally {
一定会被处理的语句
}
3. 使用try···finally处理异常
在程序发生异常时,就可以在finally语句块中对其进行相应的处理,另外当程序没有发生异常时,执行完try和finally之间的语句后,也将执行finally语句块中的代码。
举一个用try catch的例子,如下所示:
【NumberFormatException异常】编写一个程序,提示用户输入两个整数,然后显示它们的和。用户输入错误时提示用户重新输入。
如下所示:
package example;
import java.util.Scanner;
public class Test1 {
public static void main(String[] args) {
String string = new String();
Scanner input = new Scanner(System.in);
System.out.println("输入数字");
string = input.nextLine();
int num = Integer.parseInt(string);
System.out.println(num);
}
}
进行异常抛出:
try{
System.out.println("请输入两个数:");
num1 = input.nextLine();
num2 = input.nextLine();
int sum = Integer.parseInt(num1.trim())+ Integer.parseInt(num2.trim());
System.out.println(num1 + " + "+ num2 +" = " + sum);
judge = false;
}
catch(NumberFormatException ex){
System.out.println("请输入两个有意义的数");
}
注:
trim() 方法用于删除字符串的头尾空白符,空白符包括:空格、制表符 tab、换行符等其他空白符等。
更多String方法可参考我的java基础类库博客
JAVA基础类库(第四周)(三只松鼠,奥利给)_阿狗哲哲的博客-CSDN博客
https://blog.csdn.net/qq_52438590/article/details/116951958?spm=1001.2014.3001.5501
pareInt()方法如下所示,即将字符串转化为int类型
var num1 = parseInt("1234blue"); // 1234
var num2 = parseInt(""); // NaN
var num3 = parseInt("0xA"); // 10(十六进制数)
var num4 = parseInt(22.5); // 22
var num5 = parseInt("070"); // 56(八进制数)
var num6 = parseInt("70"); // 70(十进制数)
var num7 = parseInt("0xf"); // 15(十六进制数)
var num8 = parseInt("AF", 16); //175
除了上面的try···catch语句处理之外,还可以使用throws声明或throws语句抛出异常。
1. 使用throws声明抛出异常
throws用于方法声明,在声明方法时使用throws声明抛出异常,然后在调用该方法中对异常进行处理。
如需声明多个异常,各异常间要用逗号分隔,语法格式如下:
数据类型 方法名(形参列表) throws 异常类1, 异常类2, ……, 异常类n {
方法体;
}
2. 使用throw语句抛出异常
如果希望程序自行抛出异常,可以使用throw语句来实现。语法格式如下: throw new Exception("对异常的说明");
使用throw语句抛出的是异常类的实例,通常与if语句一起使用。
2.数组与容器
1.可与前面的博客作比较:链表c语言_阿狗哲哲的博客-CSDN博客
https://blog.csdn.net/qq_52438590/article/details/116943029?spm=1001.2014.3001.5501
c语言中的数组与链表的区别与JAVA中的数组与容器的区别有相似相通之处:
优势:数组是一种线性序列,本身是一种数组容器可快速访问数组元素,效率高。
劣势:数组容器需要事先定义好容量,不能随时扩容,因此需要一种功能更强大的数组 来存放数据,因此有了容器的概念java容器可以动态调节自己的大小,因此编程中可以将任意数量的对象放置到容器中,类似于c++中的vector容器,功能很强大。
容器嘛,所以里边的操作必然是添加、删除、判断、加获取。
上图所示为所有容器的示意图:
其中Collection与Map是并列的概念,
Collection接口是List,Set等的父接口,该接口定义的方法既可以操作List也可以操作Set,
Map接口用于存放键值对形式的元素,描述了由不重复的键到值的映射。
1. List
存储的元素是有序的、可重复的。
Arraylist: Object[]数组
Vector:Object[]数组
LinkedList: 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)
2. Set
存储的元素是无序的、不可重复的。
HashSet:无序,唯一,基于 HashMap 实现的,底层采用 HashMap 来保存元素
LinkedHashSet:LinkedHashSet 是 HashSet 的子类,并且其内部是通过 LinkedHashMap 来实现的。有点类似于 LinkedHashMap 其内部是基于 HashMap 实现一样
TreeSet:有序,唯一,红黑树
3. Map
HashMap:JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突⽽存在的。JDK1.8以后在解决哈希冲突时有了变化, 当链表⻓度⼤于阈值(默认为8)时,将链表转化为红⿊树,以减少搜索时间
LinkedHashMap:LinkedHashMap继承⾃HashMap,所以它的底层仍然是基于数组和链表/红⿊树组成。另外,LinkedHashMap 在上面结构的基础上,增加了⼀条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
Hashtable: 数组+链表组成的,数组是 Hashtable 的主体,链表则是主要为了解决哈希冲突而存在的
TreeMap: 红黑树
其中,红黑树的理解感悟可参考csdn大佬的博客
漫画算法:5 分钟搞明白红黑树到底是什么?_CSDN资讯-CSDN博客
原文链接:https://data.newrank.cn/m/s.html?s=PigpOzA/LTE%3D
1.迭代器
在说具体容器之前,有必要先了解一下迭代器。作为一种设计模式,迭代器给我们提供了遍历容器中元素的方法, Iterator是作为一个接口存在的,它定义了迭代器所具有的功能,接口如下:
package java.util;
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
迭代器只能通过容器本身得到,每个容器都通过自己的内部类完成迭代器,因此可以通过迭代器遍历容器,类似于c++中vector的遍历,也是需要通过迭代器遍历vector
其中,c++的遍历如下所示:
vector<Phonecard_contact>::iterator temp; //phone_contact类型的指针
for(temp=phonecard_contact.begin();temp!=phonecard_contact.end();temp++)//遍历
{
cout<<(*temp);
cout<<endl;
}
java的遍历如下所示,区别非常大:
ArrayList<String> list = new ArrayList<String>();
//省略初始化list……
……
//从list得到其迭代器
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
//
String element = iterator.next();
System.out.println(element);
}
如果只是向前遍历List,并不打算修改List对象本身,使用foreach语法会更加简洁。
for(String e : list){
System.out.println(e);
}
2.ArrayList 和 LinkedList
ArrayList详解
ArrayList是顺序容器,底层是数组,允许放入null值。每个ArrayList都有一个容量capacity,表示底层实现数组的大小,当添加元素的时候,如果capacity不够,会自动增加数组的大小。
List 元素可重复,且有序。ArrayList和LinkedList是List的子类。
1.add()方法
(1)ArrayList末尾添加元素的方法是add(E e),指定位置插入元素的方法是add(int index, E e),在添加的过程中,可能会存在capacity容量不足的问题,在每次添加前都要进行容量检查,如果容量不足,需要使用grow()扩容方法进行自动扩容
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//扩展空间完成后复制
elementData = Arrays.copyOf(elementData, newCapacity);
}
add(int index, E e)需要先对插入位置之后的元素进行移动,然后完成插入操作,数组size+1,方法有线性时间复杂度。
(2)Linkedlist
add()方法包含两个,add(E e)方法在链表末尾插入元素,借助last指针,末尾插入只需常数时间;add(int index, E element)方法在指定下标处插入元素,需要先查找位置再执行插入。
add(E e)方法与add(int index, E element)大同小异,只介绍难度更复杂的add(int index, E element),其实add(int index, E element)与c++中的动态链表也是大同小异,具体详情可翻阅之前的博客链表
public void add(int index, E element) {
//下标越界检查(index >= 0 && index <= size)
checkPositionIndex(index);
if (index == size)
//插入位置是末尾,或列表为空
add(element);
else{
//先根据index找到要插入的位置
Node<E> succ = node(index);
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
}
3.Map:
一个键值(key)对应一个对象(value)的容器接口,并且没有排序
//遍历方式:由于Map没有提供iterator()函数,而是用keySet(),values()方法取代
//方法一
Collection<String> c=m.values();
for(Iterator<String> it=c.iterator();it.hasNext();) {
System.out.print(it.next()+" ");
}
System.out.println();
//方法二
Set<Integer> s=m.keySet();
for(Iterator<Integer> it=s.iterator();it.hasNext();) {
System.out.print(it.next()+" ");
}
System.out.println();
HashMap:
一个我们平常使用最最频繁的Map容器,它的查询速度是最快的,HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。
TreeMap:
TreeMap:和TreeSet类似,红黑树结构,同样进行排序(次序由存入TreeMap的类的compare()或compareTo()方法决定,也就是说装入TreeMap的类必须实现HashCode(),equals(),compare()三个方法),同时TreeMap还有一个subMap()用来返回子树。