目录
一、异常
实际开发工作中,遇到的情况不可能是完全理想的,其遇到的问题就叫异常。Exception。
1.1、简单异常
1.1.1、检查型异常
最具代表性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如用户要打开一个不存在的文件时引发的异常,这些异常在编译时不能被简单地忽略。
1.1.2、运行时异常
是可能被程序员避免的异常,与检查性异常相反,运行时异常可以在编译时忽略
1.1.3、错误Error
错误不是异常,而是脱离程序员控制的问题。错误在代码经常被忽略。例如当栈溢出,一个异常就发生了,它们在编译也检查不到。
Error
- java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类。
- 在Java API中已经定义了许多异常类,这些异常分为两大类,错误Error和异常Exception
Exception
- 在Exception分支中有一个重要的子类RuntimeException(运行时异常)
- ArrayIndexOutOfBoundsException数组下标越界
- NullPointerException空指针异常
- ArithmeticException算术异常
- MissingResourceException丢失资源
- ClassNotFoundException找不到类,等异常,这些异常是不检查异常,可以捕获处理也可以不处理
- 这些异常一般都是由程序逻辑错误引起的,程序应该从逻辑角度去尽可能地避免这类异常地发生
- Error和Exception的区别:Error通常是灾难性的致命错误,是程序无法控制和处理的,当这些异常出现时,JVM一般会选择终止线程;Exception通常情况下是可以被程序处理,并且程序中应该尽可能的去处理这些异常。
1.2、异常处理机制
- 抛出异常
- 捕获异常
- 异常处理关键字:try、catch、finally、throw、throws
public static void main(String[] args) {
int a = 1;
int b = 0;
try { //try监控区域
System.out.println(a/b);
}catch (ArithmeticException e){ //catch 捕获异常
System.out.println("程序出现异常,变量b不能为0");
}catch (Exception e){
e.printStackTrace();
}finally { //一定会执行,处理善后工作,如关闭资源
System.out.println("finally");
}
if(b==0){ //抛出异常一般在方法中使用
throw new ArithmeticException(); //主动抛出异常
}
}
1.3、自定义异常
- java内置的异常类可以描述大部分常见异常,除此之外还能自定义异常。用户自定义异常类,只需继承Exception类即可。
- 自定义异常类步骤如下
- 创建自定义异常类
- 在方法中通过throw关键字抛出异常
- 如果在当先抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声名处通过throws关键字指明要抛出给方法调用者的异常
- 在出现异常方法的调用者中捕获并处理异常
public class MyException extends Exception {
private int detail;
public MyException(int a) {
this.detail = a;
}
//toString:异常的打印信息
@Override
public String toString() {
return "MyException{" +
"detail=" + detail +
'}';
}
}
public class ExceptionTest {
public static void test(int a) throws MyException {
System.out.println("传参为:" + a);
if (a>10) {
throw new MyException (a);
}
System.out.println("OK");
}
public static void main(String[] args) {
try {
test(11);
} catch (MyException e) {
System.out.println(e);
}
}
}
1.4、实际应用中的经验总结
- 处理运行时异常时,采用逻辑去何理规避同时辅助try-catch处理
- 在多重catch块后面,可以加一个catch(Exception)来处理有可能会被遗漏的异常
- 对于不确定的代码,也可以加上try-catch,处理潜在的异常
- 尽量去处理异常,切记只是简单的调用printStackTrace()去打印输出
- 具体如何处理异常,要根据不同的业务需求和异常类型去决定
- 尽量添加finally语句块去释放占用的资源。
二、容器
学到现在一说存放对象变量什么的,就会下意识的使用数组,数组在数据存取方面也确实很方柏霓,其存储效率高访问速度快,但是也有很多限制,比如数组长度以及类型,当我需要一组String类型同时还需要Integer类型时,就需要定义两次,同时,数组长度也受到限制,即使是动态定义数组长度,但长度依然固定在一个范围内,不方便也不灵活。
如果说我想消除这个限制和不便怎么办呢?Java提供的方法就是Java容器,java容器时JavaAPI所提供的一系列类的实例,用于在程序中存放对象,位于Java.util包,其长度不受限制,类型不受限制。
2.1、Collection
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素。一些Collection允许元素重复,但有一些不行,一些有序而有一些无序。JavaSDK不提供直接继承自Collection的类,JavaSDK提供的类都是继承自Collection的子接口如List和Set。
public static void main(String[] args) {
Collection<String> lstcoll = new ArrayList<>();
lstcoll.add("China");
lstcoll.add(new String("ZD"));
System.out.println("size="+lstcoll.size());
System.out.println(lstcoll);
}
2.1.1、List接口
List是有序的Collection,使用此接口能精准的控制每个元素插入的位置,用户能通过索引来访问List中的元素,也就是说它是有序的。和Set不同,List允许元素重复。
public static void main(String[] args) {
List<String> l1 = new LinkedList<>();
for (int i=0; i<=5; i++) {
l1.add("a" + i);
}
System.out.println(l1);
l1.add(3, "a100");
System.out.println(l1);
l1.add(6, "a200");
System.out.println(l1);
System.out.println((String)l1.get(2)+"");
l1.remove(1);
System.out.println(l1);
}
2.1.1.1、ArrayList
简单来说ArrayList就相当于有序存储,包装了一个Object[]数组,实例化ArrayList是数组也被实例化,当向ArrayList中添加对象时,数组的大小也相应的改变。这样就带来了以下特点:
- 快速访问,无需考虑性能问题,通过get(索引)就能快速访问对应元素。
- 新增速度慢,操作元素对象速度慢,操作有序数据结构会涉及到内存,导致速度慢。
2.1.1.2、LinkedList
和严格有序的ArrayList不同,LinkedList属于链式结构,通过节点彼此链接。每一个节点都包括了前一个节点的引用、后一个节点的引用和节点存储的值三部分。当一个新节点插入时,只需要修改其中保持先后关系的节点的引用即可,删除也是。所以:
- 新增、操作元素对象速度快,只需要改变链接
- 访问速度慢,需要一个个节点去索引
2.1.2、Set接口
与List接口不同,Set是一种不允许元素重复的Collection,比如任意元素e1和e2都有e1.equals(e2)=false,就连null元素也只能有一个
2.1.2.1、HashSet
HaskSet实现Set接口,有HashMap实例支持。它不保证set的迭代顺序;特别是它不保证顺序恒久不变,并且允许使用null元素。不能重复且无序。
public static void main(String[] args) {
Set<String> s = new HashSet<String>();
s.add("Hello");
s.add("Hello");
System.out.println(s);
}
2.2、Map接口
值得注意的是Map没有继承Collection接口,Map接口是提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。即是一一映射,Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。
2.2.1、HashMap
添加数据put(key, value),取出数据get(key),允许null,将HashMap视为Collection时,其迭代操作时间开销和HashMap的容量成正比。因此如果迭代操作的性能相当重要的话,不要将HashMap的的初始化容量设得太高,或者load factor过低。
public static void main(String[] args) {
Map<String, String> M = new HashMap<String, String>();
M.put("one", new String("1"));
M.put("two", new String("2"));
System.out.println(M);
}
2.3、总结
Java容器事实上只有三种:Map、List和Set;但又有各种不同的实现版本,区别在于由什么支持。
比如:ArrayList和LinkedList都实现了List接口,因此无论选择哪一个,基本操作都一样。但ArrayList是由数组提供底层支持,而LinkedList是由双向链表实现的。所以,如果要经常向List里插入或删除数据,LinkedList会比较好,否则应该用速度更快的ArrayList。
HashSet总是比TreeSet 性能要好,而后者存在的理由就是它可以维持元素的排序状态,所以如果需要一个排好序的Set时,才应该用TreeSet。
三、泛型
3.1、简介
泛型:使得数据变得参数化
定义泛型时,对应的数据类型是不确定的,泛型方法被调用时,会指定具体的类型。
泛型的目的:解决容器类型在编译时安全检查问题
泛型分为:泛型类、泛型接口以及泛型方法
3.2、泛型类
class 类名 <泛型标识,即类型>{
修饰符 泛型标识(成员变量类型);
修饰符 构造函数(泛型表示 参数);
......
}
需要注意的是,泛型的参数不支持基本类型,在编译的时候就会用具体的数据类型替换掉泛型定义。
定义泛型类
import lombok.Data;
@Data
public class GenericClassExample<T> {
private T member;
public GenericClassExample(T member)
{
this.member = member;
}
public T handleSomething(T target)
{
return target;
}
}
使用泛型类
public class GenericDemo {
public static void main(String[] args) {
GenericClassExample<String> stringExample = new GenericClassExample<String>("abc");
GenericClassExample<Integer> integerExample = new GenericClassExample<Integer>(123);
System.out.println(stringExample.getMember().getClass());
System.out.println(integerExample.getMember().getClass());
}
}
在泛型中使用具有继承关系的类,泛型是不接受的。
比如即使Number是Integer的父类,当指定泛型Integer类,而调用时指定的类却是Number时,编译是无法通过的。
那有没有办法呢?当然是有的。
- 使用通配符【?】,但是这样的话就会使泛型的类型检查失去原有的意义。
- 加上上边界:extends E,指定传入的泛型类必须是继承E的。
- 加上下边界:super E,指定传入的泛型数据类型必须是E的父类。
3.3、泛型接口
与泛型类的用法基本一致,用于数据类型的生产工厂接口中
定义泛型接口
//泛型接口
public interface GenericIFactory<T,N> {
T nextObject();
N nextNumber();
}
定义实现类
public class RobotFactory implements GenericIFactory<String,Integer> {
private String stringRobot[] = new String[]{"Hello","Hi"};
private Integer integerRobot[] = new Integer[]{111,000};
@Override
public String nextObject() {
Random random = new Random();
return stringRobot[random.nextInt(2)];
}
@Override
public Integer nextNumber() {
Random random = new Random();
return integerRobot[random.nextInt(2)];
}
}
使用
public static void main(String[] args) {
RobotFactory factory = new RobotFactory();
System.out.println(factory.nextObject()+" " +factory.nextNumber());
}
3.4、泛型方法
能用在泛型类、泛型接口里,也能用在普通类或者接口里
定义一个泛型方法在我们之前定义的泛型类中
//泛型类
@Data
public class GenericClassExample<T> {
private T member;
public GenericClassExample(T member)
{
this.member = member;
}
public T handleSomething(T target)
{
return target;
}
//泛型函数
public static<E> void printArray(E[] inputArray)
{
for(E element:inputArray)
{
System.out.printf("%s ",element);
}
System.out.println();
}
}
public class GenericDemo {
public static void main(String[] args) {
//使用泛型函数
GenericClassExample<String> example = new GenericClassExample<String>("abc");
Integer[] integers = {1,2,3,4,5,6};
Double[] doubles={1.1,2.2,3.3,4.4,5.5};
Character[] characters={'A','B','C'};
example.printArray(integers);
example.printArray(doubles);
example.printArray(characters);
}
}
3.5、泛型字母的含义
- E — Element:在集合中使用,因为集合存放的是元素
- T — Type:Java类
- K — Key:键
- V — Value:值
- N — Number:数值类型