面向对象加强

EaborH

异常、泛型、集合框架

异常

关于异常

异常代表程序出现问题

Exception: 叫异常,它代表的才是我们程序可能出现的问题,所以,
我们程序员通常会用Exception以及它的孩子来封装程序出现的问题。

  • 运行时异常:RuntimeException及其子类,编译阶段不会出现错误提
    醒,运行时出现的异常(如:数组索引越界异常)
  • 编译时异常:编译阶段就会出现错误提醒的。(如:日期解析异常)

异常的基本处理

  1. 抛出异常(throws):
1
2
3
方法 throws 异常1, 异常2, 异常3{

}
  1. 捕获异常(try…catch):
1
2
3
4
5
6
7
try{
// 监视可能出现异常的代码
}catch(异常类型1 变量){
// 处理异常
}catch(异常类型2 变量){
// 处理异常
}...

异常的作用?

  1. 异常用来定位程序bug的关键信息
  2. 可以作为方法内部的一种特殊返回值以便通知上层调用者,方法的执行问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ExceptionDemo2 {  
public static void main(String[] args) {
// 搞清楚异常的作用
try {
System.out.println(div(10, 2));
System.out.println("程序正常结束");
} catch (Exception e) {
System.out.println("程序出现异常");
e.printStackTrace();
}

}
// 求两个数除的结果,并返回这个结果
public static int div(int a, int b) throws Exception {
if (b == 0) {
System.out.println("除数不能为0");
throw new Exception("除数不能为0,参数问题");
}
return a / b;
}
}

自定义异常

  • Java无法为这个世界上全部的问题都提供异常类来代表如果企业自己的某种问题,想通过异常来表示,以便用异常来管理该问题,那就需要自己来定义异常类了。
  1. 自定义运行时异常

    • 定义一个异常类继承RuntimeException.
    • 重写构造器。
    • 通过thrownew异常类(xxx)来创建异常对象井抛出。
      特点:编译阶段不报错,运行时才可能出现!提醒不属于激进型。
  2. 自定义编译时异常

    • 定义一个异常类继承Exception.
    • 重写构造器。
    • 通过thrownew异常类(xxx)创建异常对象并抛出。
      特点:编译阶段就报错,提醒比较激进
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package xyz.eabor.demo1exception;  

public class ExceptionDemo3 {
public static void main(String[] args) {
// 认识自定义异常
try {
saveAge(100);
} catch (AgeException e) {
e.printStackTrace();
System.out.println("失败了");
}
}

// 需求:年龄小于1岁或者大于200岁就是一个年龄非法异常
public static void saveAge(int age) throws AgeException {
if (age < 1 || age > 200) {
throw new AgeException("年龄不合法");
} else {
System.out.println("年龄合法");
System.out.println("年龄是:" + age);
}
}
}

package xyz.eabor.demo1exception;

public class AgeException extends Exception{
public AgeException() {
}

public AgeException(String message) {
super(message);
}

}

异常的处理方案

  1. 底层异常层层往上抛出,最外层捕获异常,记录下异常信息,并响应适合用户观看的信息进行提示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package xyz.eabor.demo1exception;  

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ExceptionDemo5 {
public static void main(String[] args) {
// 底层异常层层往上抛出,最外层捕获异常,记录下异常信息,并响应适合用户观看的信息进行提示
try {
show();
} catch (Exception e) {
e.printStackTrace();
}

}

public static void show() throws Exception {

String str = "2024-07-09 11:12:13";

SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date date = sdf.parse(str);
System.out.println(date);

InputStream is = new FileInputStream("D:/meinv.jpg");
}
}
  1. 最外层捕获异常后,尝试重新修复
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package xyz.eabor.demo1exception;  

import java.util.Scanner;

public class ExceptionDemo6 {
public static void main(String[] args) {
// 最外层捕获异常后,尝试重新修复
double num = 0;
while (true) {
try {
num = userInput();
System.out.println("输入数字" + num);
break;
} catch (Exception e) {
System.out.println("出现异常,请重新输入");
}
}


}
public static double userInput() {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个数字:");
double num = sc.nextDouble();
return num;

}
}

泛型

关于泛型

定义类、接口、方法时,同时声明了一个或者多个类型变量(如<E>) 称为泛型类、泛型接口,泛型方法、它们统称为泛型

1
2
3
public class ArrayList<E>{
...
}

作用: 泛型提供了编译阶段约束所能操作的数据类型,并自动进行检查的能力这样可以避免强制类型转换,及其可能出现的异常

泛型类

1
2
3
4
5
6
7
修饰符 class 类名<类型变量, 类型变量, ...>{

}

public class ArrayList<E>{
...
}

注意:类型变量建议用大写的英文字母,常用的有:E、T、K、V等

泛型接口

1
2
3
4
5
6
7
修饰符 interface 类名<类型变量, 类型变量, ...>{

}

public interface A<E>{
...
}

注意:类型变量建议用大写的英文字母,常用的有:E、T、K、V等

泛型方法、通配符、上下限

1
2
3
4
5
6
7
修饰符 <类型变量, 类型变量, ...> 返回值类型 方法名 (形参列表){

}

public static <T> void text(T t){

}

通配符
就是”?”,可以在“使用泛型”的时候代表一切类型;E T K V是在定义泛型的时候使用

泛型的上下限
泛型上限:?extends Car:?能接收的必须是Car或者其子类。
泛型下限:?super Car:?能接收的必须是Car或者其父类。

泛型支持的类型
泛型不支持基本数据类型,只能支持对象类型(引用数据类型)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package xyz.eabor.demo2genericity;  

public class GenericDemo2 {
public static void main(String[] args) {
// 把基本数据类型变成包装类对象
Integer it1 = Integer.valueOf(100);

// 自动装箱:int -> Integer
Integer it2 = 100;

// 自动拆箱:Integer -> int
int i = it2;

// 包装类新增的功能
// 1.把基本类型的数据转换成字符串
int j = 23;
String rs1 = Integer.toString(j);
System.out.println(rs1 + 1);

Integer i2 = j;
String rs2 = i2.toString();
System.out.println(rs2 + 1);

// 2.把字符串转换成基本类型的数据
String str = "123";
int k = Integer.parseInt(str);
int k1 = Integer.valueOf(str);
System.out.println(k1 + 1);
}
}

集合框架

认识集合
集合是一种容器,用来装数据的,类似于数组,但集合的大小可变,开发中也非常常用。

Collection集合

Collection集合特点

  1. List系列集合:添加的元素是有序、可重复、有索引。
    • ArrayList、LinekdList:有序、可重复、有索引。
  2. Set系列集合:添加的元素是无序、不重复、无索引。
    • HashSet:无序、不重复、无索引;
    • LinkedHashSet:有序、不重复、无索引。
    • TreeSet:按照大小默认升序排序、不重复、无索引。
方法名 说明
public boolean add(E e) 把给定的对象添加到当前集合中
public void clear() 清空集合中所有的元素
public boolean remove(E e) 把给定的对象在当前集合中删除
public boolean contains(object obj) 判断当前集合中是否包含给定的对象
public boolean isEmpty() 判断当前集合是否为空
public int size() 返回集合中元素的个数。
public Object[] toArray() 把集合中的元素,存储到数组中

Collection的遍历方式一:迭代器遍历
迭代器是用来遍历集合专用方式(数组没有迭代器),在Java中迭代器的代表是Iterator

方法名称 说明
Iterator<E>iterator() 返回集合中的迭代器对象,该选代器对象默认指向当前集合的第一个元素

Iterator迭代器中的常用方法

方法名称 说明
boolean hasNext() 询问当前位置是否有元素存在,存在返回true,不存在返回false
E next() 获取当前位置的元素,并同时将迭代器对象指向下一个元素处

Collection的遍历方式二:增强for循环
for (type name : names)

Collection集合的遍历方式三:Lambda表达式
得益于JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的方式来遍历集合。
需要使用Collection的如下方法来完成

方法名称 说明
default void forEach Consumer<? super T> action 结合lambda遍历集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class GenericDemo2 {  
public static void main(String[] args) {
// lambda表达式遍历集合
Collection<String> names = new ArrayList<>();
names.add("张三");
names.add("李四");
names.add("王五");

names.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});

names.forEach(s -> System.out.println(s));
names.forEach(System.out::println);

}
}

认识并发修改异常问题
遍历集合的同时又存在增删集合元素的行为时可能出现业务异常这种现象被称之为并发修改异常问题

List集合

List集合的特有方法
List集合因为支持索引,所以多了很多与索引相关的方法,当然,Collection的功能List也都继承了

方法名称 说明
void add(int index,E element) 在此集合中的指定位置插入指定的元素
E remove(int index) 删除指定索引处的元素,返回被删除的元素
E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
E get(int index) 返回指定索引处的元素

ArrayList底层是基于数组存储数据的

  1. 查询速度快(注意:是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同
  2. 增删数据效率低:可能需要把后面很多的数据进行前移

LinkedList底层是基于链表存储数据的

  1. 链表中的数据是一个一个独立的结点组成的,结点在内存中是不连续的,每个结点包含数据值和下一个结点的地址
  2. 查询慢,无论查询哪个数据都要从头开始找
  3. 链表增删相对快
  4. 对首尾元素进行增删改查的速度是极快的
    LinkedList新增了很多首尾操作的特有方法
方法名称 说明
public void addFirst(E e) 在该列表开头插入指定的元素
public void addLast(E e) 将指定的元素追加到此列表的末尾
public E getFirst() 返回此列表中的第一个元素
public E getLast() 返回此列表中的最后一个元素
public E removeFirst() 从此列表中删除并返回第一个元素
public E removeLast() 从此列表中删除并返回最后一个元素

Set集合

Set系列集合:添加的元素是无序、不重复、无索引。

  • HashSet:无序、不重复、无索引;
  • LinkedHashSet:有序、不重复、无索引。
  • TreeSet:排序(按照大小默认升序排序)、不重复、无索引。

HashSet集合的底层原理
哈希值: 就是一个int类型的随机值Java中每个对象都有一个哈希值
Java中的所有对象,都可以调用Obejct类提供的hashCode方法,返回该对象自己的哈希值。
public inthashCode():返回对象的哈希码值
对象哈希值的特点
同一个对象多次调用hashCode()方法返回的哈希值是相同的。不同的对象,它们的哈希值大概率不相等,但也有可能会相等(哈希碰撞)。
基于哈希表存储数据的。
哈希表

  • JDK8之前,哈希表=数组+链表

    1. 创建一个默认长度16的数组,默认加载因子为0.75,数组名table
    2. 使用元素的哈希值数组的长度做运算计算出应存入的位置
    3. 判断当前位置是否为null,如果是null直接存入如果不为null,表示有元素,则调用用equals方法比较相等,则不存不相等,则存入数组
      • JDK8之前,新元素存入数组,占老元素位置,老元素挂下面
      • JDK8开始之后,新元素直接挂在老元素下面
  • JDK8开始,哈希表=数组+链表+红黑树(JDK8开始,当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树)

  • 哈希表是一种增删改查数据,性能都较好的数据结构

LinkedHashSet的底层原理

  • 依然是基于哈希表(数组、链表、红黑树)实现的。
  • 但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置。

TreeSet集合

  • 特点:不重复、无索引、可排序(默认升序排序,按照元素的大小,由小到大排序
  • 底层是基于红黑树实现的排序。
    注意:
  • 对于数值类型:Integer,Double,默认按照数值本身的大小进行升序排序。
  • 对于字符串类型:默认按照首字符的编号升序排序。
  • 对于自定义类型如Student对象,TreeSet默认是无法直接排序的。

Map集合

认识Map集合

  • Map集合也被叫做“键值对集合”,格式:{key1=value1, key2=value2 ,key3=value3,...)
  • Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值

Map集合体系的特点
注意: Map系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的

  • HashMap(由键决定特点):无序、不重复、无索引;(用的最多)
  • LinkedHashMap(由键决定特点): 有序、不重复、无索引。
  • TreeMap(由键决定特点):按照大小默认升序排序、不重复、无索引。
方法名称 说明
public V put(K key,V value) 添加元素
public int size() 获取集合的大小
public void clear() 清空集合
public boolean isEmpty() 判断集合是否为空,为空返回true,反之
public V get(Object key) 根据键获取对应值
public V remove(Object key) 根据键删除整个元素
public boolean containsKey(Object key) 判断是否包含某个键
public boolean containsValue(Object value) 判断是否包含某个值
public Set<K> keySet() 获取全部键的集合
public Collection<V> values() 获取Map集合的全部值

Map集合的遍历方式

  1. 键找值: 先获取Map集合全部的键,再通过遍历键来找值
方法名称 说明
public Set<K> keySet() 获取所有键的集合
public V get(Object key) 根据键获取其对应的值
  1. 键值对: 把“键值对“看成一个整体进行遍历(难度较大)
方法名称 说明
Set<Map.Entry<K, V>> entrySet() 获取所有“键值对”的集合
1
2
3
4
5
6
Set<Map.Entry<String, Integer>> entries = map.entrySet();

for (Map.Entry<String, Integer> entry : entries){
String key = entry.getKey;
Integer value = entry.getValue;
}
  1. Lambda: JDK1.8开始之后的新技JDK1.8开始之后的新技术
方法名称 说明
default void forEach(BiConsumer<? super K, ? super V> action) 结合lambda遍历Map集合
1
map forEach((key, value) -> sout(key + value))

Stream流

是Jdk8开始新增的一套API(java.util.stream.*),可可以用于操作集合或者数组的数据
Stream流大量的结合了Lambda的语法风格来编程功能强大,性能高效,代码简洁,可读性好。

获取stream流
获取集合的Stream流

Collection提供的如下方法 说明
default Stream<E> stream() 获取当前集合对象的stream流

获取数组的Stream流

Arrays类提供的如下方法 说明
public static <T> Stream<T> stream(T[] array) 获取当前数组的stream流
Stream类提供的如下方法 说明
public static<T> Stream<T> of(T... values) 获取当前接收数据的stream流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class StreamDemo1 {  
public static void main(String[] args) {

// 1.获取集合的Stream流:调用集合的Stream方法
Collection<String> list = new ArrayList<>();
Stream<String> s1 = list.stream();

// 2.Map集合,怎么拿Stream流
Map<String, Integer> map = new HashMap<>();
// 获取键流
Stream<String> s2 = map.keySet().stream();
// 获取值流
Stream<Integer> s3 = map.values().stream();
// 获取键值对流
Stream<Map.Entry<String, Integer>> s4 = map.entrySet().stream();

// 3.数组,怎么拿Stream流
String[] arr = new String[]{"张三", "李四", "王五"};
Stream<String> s5 = Arrays.stream(arr);
System.out.println(s5.count());

Stream<String> s6 = Stream.of("张三", "李四", "王五");

}
}

Stream流的常见操作
中间方法指的是调用完成后会返回新的Stream流,可以继续使用(支持链式编程)

Stream提供的常用中间方法 说明
Stream<T> filter(Predicate<? super T> predicate) 用于对流中的数据进行过滤。
Stream<T> sorted() 对元素进行升序排序
Stream<I> sorted(Comparator<? super T> comparator) 按照指定规则排序
Stream<T> 1imit(long maxSize) 获取前几个元素
Stream<T> skip(long n) 跳过前几个元素
Stream<T> distinct() 去除流中重复的元素。
<R> Stream<R> map(Function<? super I,? extends R> mapper) 对元素进行加工,并返回对应的新流
static <T> Stream<T> concat(Stream a, Stream b) 合并a和b两个流为一个流

Stream流的终结方法
终结方法指的是调用完成后,不会返回新stream了,没法继续使用流了。

Stream提供的常用终结方法 说明
void forEach(Consumer action) 对此流运算后的元素执行遍历
long count() 统计此流运算后的元素个数
Optional<I> max(Comparator<? super I> comparator) 获取此流运算后的最大值元素
Optional<I> min(Comparator<? super T> comparator) 获取此流运算后的最小值元素

收集Stream流
收集stream流:就是把Stream流操作后的结果转回到集合或者数组中去返回。
Stream流:方便操作集合/数组的手段;集合/数组:才是开发中的目的。

Stream提供的常用终结方法 说明
R collect(Collector collector) 把流处理后的结果收集到一个指定的集合中去
Object[] toArray() 把流处理后的结果收集到一个数组中去
Collectors工具类提供了具体的收集方式 说明
public static <T> Collector toList() 把元素收集到List集合中
public static <T> Collector toSet() 把元素收集到set集合中
public static Collector toMap(Function keyMapper , Function valueMapper) 把元素收集到Map集合中

方法中可变参数

就是一种特殊形参,定义在方法、构造器的形参列表里,格式是:数据类型..参数名称;

可变参数的特点和好处
特点: 可以不传数据给它;可以传一个或者同时传多个数据给它;也可以传一个数组给它。
好处: 常常用来灵活的接收数据。

可变参数的注意事项

  • 可变参数在方法内部就是一个数组
  • 一个形参列表中可变参数只能有一个
  • 可变参数必须放在形参列表的最后面

Collections工具类

Collections 是一个用来操作集合的工具类
Collections提供的常用静态方法

方法名称 说明
public static <T> boolean addAll(Collection<? super T> c, .. elements) 给集合批量添加元素
public static void shuffle(List<?> list) 打乱List集合中的元素顺序
public static <T> void sort(List<T> list) 对List集合中的元素进行升序排序
public static <T> void sort(List<T> list, Comparator<? super T> c) 对List集合中元素,按照比较器对象指定的规则进行排序
  • Title: 面向对象加强
  • Author: EaborH
  • Created at : 2025-03-23 00:00:00
  • Updated at : 2025-03-28 12:56:28
  • Link: https://eabor.xyz/2025/03/23/加强/
  • License: This work is licensed under CC BY-NC-SA 4.0.