# learnJava
**Repository Path**: hb-haoren/learn-java
## Basic Information
- **Project Name**: learnJava
- **Description**: Java基础入门
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-01-05
- **Last Updated**: 2026-01-16
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 接口和抽象类的区别
| | 抽象类 | 接口 |
|------|-------------|----------------------------|
| 属性 | 与普通类一致 | 只能声明常量 |
| 构造器 | 与普通类一致 | 不能声明构造器 |
| 方法 | 与普通类一致,抽象方法 | 抽象方法,私有方法,default实例方法,静态方法 |
| 实例化 | 不能实例化 | 不能实例化 |
## 思考以下问题
- 为什么有了抽象类还有接口?
- 抽象类能不能继承普通类
- 普通类能不能作为抽象类的父类
- 抽象类是否可以继承接口
- 接口能不能继承接口
- 接口是否有单继承的限制
- 接口能不能继承抽象类
# 内存基础
- 栈内存(虚拟机栈 / 方法栈):Java 程序运行时,每调用一个方法,JVM 就会为该方法在虚拟机栈中创建一个对应的栈帧(而非简单的 “方法栈”)。栈帧中主要存放方法的局部变量(包括基本数据类型的值、引用数据类型的引用地址)、方法的操作数栈、动态链接等信息。当方法执行完毕(正常返回或异常终止)时,对应的栈帧会被立即出栈销毁,栈帧内的局部变量也随之释放,内存空间被回收。栈内存的分配和回收由 JVM 自动完成,效率极高。
- 堆内存:通过new关键字(或反射、克隆等方式)创建的对象(包括实例对象、数组),其本体数据都存储在堆内存中。堆内存是 JVM 中最大的内存区域,所有线程共享。堆中对象的生命周期不由方法执行周期决定,而是由垃圾回收器(GC) 统一管理:当一个对象不再被任何可达的引用变量指向(即 “不可达”),且经过 GC 的可达性分析、标记 - 清除等流程确认后,会被判定为垃圾对象;在 GC 触发时,这些垃圾对象占用的内存会被回收。程序员无法通过代码主动销毁堆中的对象或触发 GC(System.gc()仅为建议,不保证立即执行)。
# 作业
1. 完成上述的思考问题
2. 将课程中的抽象类和接口的案例进行手动复现
# 核心类
## Object
示例代码
```java
Point p1=new Point(1,1);
Point p2=new Point(1,1);
```
内存图

- Object类是所有类的父类,Object类中定义了Object类所有对象所共有的方法。
- **toString()**:以字符串的形式将对象的内容返回
```java
@Override
public String toString() {
return "Point[x="+this.x+",y="+this.y+"]";
}
```
- **equals**:判断两个对象是否"相等"
```java
@Override
public boolean equals(Object obj) {
//1 非空判断
if(obj == null) return false;
//2 判断地址值是否一致
if(obj== this) return true;
//3 判断对象的内容
// 判断obj的实际运行时类型是否属于Point或者Point的子类
if(obj instanceof Point){//判断是否属于统一类型
Point p=(Point)obj;
//定义内容的判断逻辑
return this.x == p.x && this.y == p.y;
}
return false;
}
```
- **hashCode()**:返回对象的哈希值.java中重写了equals方法必须重写hashCode方法。确保equals方法为true的两个对象的hashCode方法返回值也相等(参与equals比较的属性参与到hashCode的计算中)。无需确保两个不相等的对象的hashCode方法返回值不相等
```java
@Override
public int hashCode() {
return this.x>>this.y;
}
```
练习
根据下面的类完成类的定义

# String 字符串
Java中使用双引号表示字符串的常量,String类是final类,不能被继承。**String一旦初始化之后值不可变**。
思考:String为什么要使用final修饰?
```java
String s1=new String("abc");
```
上述创建了两个对象:
1. new关键字在堆内存中创建的String对象
2. 在常量池中创建的abc (常量池是运行时常量池,JVM启动时创建,存放字符串常量(字面量),实现复用)
```java
String s1="abc";
String s2="abc";
System.out.println(s1==s2);//true
```
思考:上述代码创建了几个对象?
## String中常用的方法
- **char charAt(int index)**:返回指定索引处的char值
- **int length()**:返回字符串的长度
- **String substring(int beginIndex,int endIndex)**:返回从beginIndex到endIndex的字符串
- **String toLowerCase()**:将字符串中的大写字母转换成小写字母
- **String toUpperCase()**:将字符串中的小写字母转换成大写字母
- **boolean equals(Object obj)**:判断两个字符串是否相等
- **boolean equalsIgnoreCase(String str)**:忽略大小写判断两个字符串是否相等
- **String replace(char oldChar,char newChar)**:将字符串中的oldChar替换成newChar
- **String trim()**:删除字符串两端的空格
- **String[] split(String regex)**:按照给定的正则表达式,将字符串进行切割
- **int indexOf(String str)**:返回字符串中str第一次出现的索引
- **int indexOf(String str,int fromIndex)**:返回字符串中str第一次出现的索引,从fromIndex开始查找
- **int lastIndexOf(String str)**:返回字符串中str最后一次出现的索引
- **int lastIndexOf(String str,int fromIndex)**:返回字符串中str最后一次出现的索引,从fromIndex开始查找
- **boolean startsWith(String prefix)**:判断字符串是否以prefix开头
- **boolean endsWith(String suffix)**:判断字符串是否以suffix结尾
- **boolean contains(String str)**:判断字符串中是否包含str
- **boolean isEmpty()**:判断字符串是否为空
## 字符编码
字符编码涉及到中文乱码的问题。
1. **ASCII**:ASCII码表,英文字母、数字、符号的编码,占一个字节(键盘)。
2. **GBK (GB2312)**:中文编码,中文字符的编码,占两个字节(汉字)。
3. **UTF-8**:万国码,任意字符的编码,占一个或多个字节。一个中文三个字节
4. **UTF-16**:万国码,任意字符的编码,占两个字节。一个中文两个字节。不利于在网络中传输,因为网络传输存在丢包的问题。
5. **iso-8859-1**: iso标准,英文字符的编码,占一个字节,不支持中文。
6. 编码特性对比
| 编码标准 | 汉字字节数 | 兼容性 | 网络适用性 |
|---------|-----------|--------|-----------|
| GBK | 2字节 | 中文优化 | 一般 |
| UTF-8 | 3字节 | 全球通用 | 优秀 |
| UTF-16 | 2/4字节 | 一般 | 较差 |
案例
```java
String str="abc计算机";
//指定字符编码进行转码
byte[] gbkBytes=str.getBytes("gbk");
byte[] utf8Bytes=str.getBytes("utf-8");
byte[] utf16Bytes=str.getBytes("utf-16");
//[97, 98, 99, -68, -58, -53, -29, -69, -6]
System.out.println(Arrays.toString(gbkBytes));
//[97, 98, 99, -24, -82, -95, -25, -82, -105, -26, -100, -70]
System.out.println(Arrays.toString(utf8Bytes));
//[-2, -1, 0, 97, 0, 98, 0, 99, -117, -95, 123, -105, 103, 58]
System.out.println(Arrays.toString(utf16Bytes));
//利用String(byte[] bytes, String charsetName)构造器实现字符串解码
String gbk = new String(gbkBytes, "gbk");
System.out.println(gbk);
//如果编码和解码的字符集不一致,出现乱码
String utf8 = new String(utf8Bytes, "gbk");
System.out.println(utf8);
```
### 编码选择建议
1. **Web开发**:统一使用UTF-8编码
2. **数据库**:设置与应用程序一致的字符集
3. **文件处理**:明确指定读写文件的字符编码
4. **网络传输**:在协议头中声明使用的字符集
### 问题排查技巧
1. 检查系统默认编码设置
2. 验证数据传输各环节的字符集一致性
3. 使用编码检测工具分析乱码原因
4. 在代码中显式指定字符编码
### 扩展学习建议
- UTF-16在网络传输中的具体问题分析
- 不同编程语言中的字符编码处理机制
- 字符编码的历史演变和发展趋势
## 正则表达式
正则表达式是一种描述字符串规则的方式。
正则表达式的语法:
- 描述一个字符:[]
* [abc] 表示该字符为a、b、c中的一个
* [^abc] 表示该字符不为a、b、c中的一个
* [a-z] 描述字符a到z之间的任意字符
* [a-zA-Z] 描述字符a到z、A到Z之间的任意字符
* [0-9] 描述字符0到9之间的任意字符
* [a-zA-Z0-9] 描述字符a到z、A到Z、0到9之间的任意字符
* . 描述任意字符
* 邮编:规则6个数字[0-9][0-9][0-9][0-9][0-9][0-9]
- 预定义字符
* \d 描述数字[0-9]
* \D 描述非数字[^0-9]
* \w 描述单词字符[a-zA-Z_0-9]
* \s 描述空白字符[ \t\n\x0B\f\r]
- 描述次数:{ }
* {n}: 表示该字符重复n次
* {n,m}: 描述该字符重复n到m次
* {n,}: 描述该字符重复n次以上
* {,m}: 描述该字符重复0到m次
* 邮编:规则6个数字[0-9]{6}
* \* 表示该字符重复0次或者多次
* \+ 描述该字符重复1次或者多次
* ? 描述该字符重复0次或者1次
* 案例:匹配零个或者多个空格 \s*
- 边界符
* ^ 描述字符串的开始
* $ 描述字符串的结束
练习:
1.匹配一个手机号码国内手机号码的规则:
- 1开头,共11位数字组成
- 第2位为3、4、5、7、8、9中的一个
- 第2位以后为0-9之间的任意数字
正则表达式:1[3-9]\d{9}
2. 匹配身份证号码
- 共18位
- 前17位为0-9之间的任意数字
- 最后一位为x、X、0-9之间的任意字符
正则大表达式:\d{17}[xX0-9]
课程代码
```java
//正则表达式:定义字符串格式的规则
//手机号码,身份证号码
//String mailCodeRegex="[0-9][0-9][0-9][0-9][0-9][0-9]";
//String mailCodeRegex="[0-9]{6}";
String mailCodeRegex="^\\d{6}$";
System.out.println("123456".matches(mailCodeRegex));//true
System.out.println("123a56".matches(mailCodeRegex));//false
String scopeRegex="^\\s*$";
System.out.println(" ".matches(scopeRegex));//true
System.out.println("123456".matches(scopeRegex));//false
System.out.println("".matches(scopeRegex));//true
```
## 作业
- 提供一个字符串,获得该字符串所有的子串
基本的思路

```java
public String[] childString(String data){
String [] arr=new String[0];
//格式校验
if(data.matches("^\\s*$")){
throw new RuntimeException("字符串不能为空");
}
//len表示子串的长度
for(int len=data.length();len>=1;len--){
for(int start=0;start<=data.length()-len;start++){
String sub=data.substring(start,start+len);
arr=Arrays.copyOf(arr,arr.length+1);
arr[arr.length-1]=sub;
}
}
return arr;
}
```
- 获得两个字符串中最长的公共子串
# StringBuilder
StringBuilder类是一个可变字符串类,用于创建和操作字符串。StringBuilder类提供了一些方法,用于在字符串的末尾添加新的字符或字符串,并返回新的字符串。StringBuilder类与String类一样,也是不可变字符串类,也就是说,一旦创建了一个StringBuilder对象,就不能修改它的内容。但是,StringBuilder类提供了一些方法,可以修改字符串的内容,并返回新的字符串。
```java
StringBuilder sb=new StringBuilder("01234abcd");
sb.append("ABC");
System.out.println(sb);//01234abcdABC
sb.reverse();
System.out.println(sb);//CBAdcba43210
```
开发中如果存在大量字符串的拼接推荐使用StringBuilder#append方法,效率更高。
# 日期类型
可以使用long类型描述时间,是底层常用的时间API
```java
//获得当前距离1970.1.1 00:00:00的毫秒数
long l = System.currentTimeMillis();
//1767771342732
System.out.println(l);
```
如果需要描述生日的日期,商品的生产日期,这些不推荐使用long类型进行描述。
## JDK8.0之前的日期类型
java.util.Date:描述日期和时间.在JDK8.0之前推荐描述日期概念的对象。比如:注册时间,生产日期等等
Date的底层实现存在缺陷,很多API已经过时不推荐使用。Date不能进行格式转换,不能进行日期的计算。
```java
Date d=new Date();
System.out.println(d);//Wed Jan 07 15:41:18 CST 2026
//126 获得的year是当前年份减去1900 (千年虫问题)
System.out.println(d.getYear());
// 0-11 表示1到12月
System.out.println(d.getMonth());//0
```
## LocalDateTime
- java.time.LocalDate:描述日期,JDK8.0之后推荐使用LocalDate进行描述。
- java.time.LocalDateTime:描述日期和时间,JDK8.0之后推荐使用LocalDateTime进行描述。LocalDateTime类提供了很多方法,用于获取日期和时间的属性,比如:年、月、日、时、分、秒、纳秒等等。
### 初始化
```java
//获得当前的日期和时间
LocalDateTime now = LocalDateTime.now();
System.out.println(now);//2026-01-07T15:46:05.612392900
//指定一个时间
LocalDateTime t1=LocalDateTime.of(2025,12,12,15,0,0);
System.out.println(t1);//2025-12-12T15:00
```
### 常用的API
```java
//当前时间 2026-01-07 154826
LocalDateTime now = LocalDateTime.now();
System.out.println(now.getYear());//2026
System.out.println(now.getDayOfMonth());//7
System.out.println(now.getDayOfYear());//7
System.out.println(now.getDayOfWeek());//WEDNESDAY
//对日期进行计算
//3天前
LocalDateTime t1 = now.minusDays(3);
System.out.println(t1);//2026-01-04T15:49:57.439230500
//1个月后
LocalDateTime t2 = now.plusMonths(1);
System.out.println(t2);//2026-02-07T15:50:38.296868
//1小时后
LocalDateTime t3 = now.plus(1, ChronoUnit.HOURS);
//2026-01-07T16:53:08.048997500
System.out.println(t3);
//with方法
LocalDateTime t4 = now.withDayOfMonth(20);
//2026-01-20T16:20:50.287718400
System.out.println(t4);
//LocalDateTime t5 = now.with(ChronoField.DAY_OF_WEEK, 5);
LocalDateTime t5 = now.with(ChronoField.DAY_OF_WEEK, DayOfWeek.FRIDAY.getValue());
System.out.println(t5);
```
### 时间的格式化
在进行数据交互的过程中,存在字符串转时间和时间转字符串的场景。
- java.time.format.DateTimeFormatter:用于格式化LocalDateTime对象
- java中时间的预定义格式
* yyyy:表示4位年
* MM:表示2位月,如01
* M: 表示1-12月,如1
* dd:表示2位日,如01
* HH:表示2位时,24小时制,如01
* hh:表示2位时,12小时制,如01
* mm:表示2位分,如01
* ss:表示2位秒,如01
```java
LocalDateTime now = LocalDateTime.now();
//实例化一个DateTimeFormatter对象,指定日期的格式
DateTimeFormatter formatter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
//2026-01-07 16:10:17
String str = now.format(formatter);
System.out.println(str);
formatter=formatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
//2026年01月07日 16时12分13秒
String str1 = now.format(formatter);
System.out.println(str1);
//字符串转日期
DateTimeFormatter datematter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime t1 = LocalDateTime.parse("2026-01-07 16:10:17",datematter);
System.out.println(t1);
```
### 作业
1. 定义一个方法parseIdCard,解析身份证上的出生日期获得当前用户的年龄。
```java
public int parseIdCard(String idCard){
//1. 对idCard进行格式校验
String idCardRegex="^\\d{17}[0-9Xx]$";
if(!idCard.matches(idCardRegex)){
throw new RuntimeException("身份证号码格式错误!");
}
//2. 格式校验通过之后获得出生日期的信息xxxxxx19xx1201xxxx
String data = idCard.substring(6, 14);
//将字符串转成日期类型LocalDate,日期格式yyyyMMdd
DateTimeFormatter timeFormatter=DateTimeFormatter.ofPattern("yyyyMMdd");
//出生的时间信息
LocalDate newDate = LocalDate.parse(data, timeFormatter);
//当前的时间
LocalDate nowDate = LocalDate.now();
int age= nowDate.getYear()-newDate.getYear();
//获得今年的生日的日期
LocalDate birthday = newDate.withYear(nowDate.getYear());
if(birthday.isAfter(nowDate)){
age--;
}
//3. 计算年龄
return age;
}
```
# 包装类
Java中使用对象的概念对八种基本数据类型提供的封装。
| 基本数据类型 | 包装类 |
|---------|-----------|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| boolean | Boolean |
| char | Character |
## Integer
Integer是int类型的包装类,为整型int提供了面向对象的封装
- 属性
* MAX_VALUE
* MIN_VALUE
- 方法
* parseInt(String s):将字符串转换成int类型
* toBinaryString(int i): 将整型转成二进制
* toHexString(int i): 将整型转成十六进制
* toOctalString(int i): 将整型转成八进制
## 装箱和拆箱
- 装箱:将基本数据类型转换成对应的包装类对象
```java
Integer i = Integer.valueOf(100);//new Integer(100);
//将基本数据类型200直接赋值给Integer类型
Integer i1= 200;//编译时,自动调用valueOf方法
```
关于Integer.valueOf的实现
```java
@IntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
```
缓存机制:IntegerCache.low和IntegerCache.high为-128到127之间,IntegerCache.cache为-128到127的缓存对象。
- 拆箱:将包装类对象转换成对应的基本数据类型
```java
//Integer.valueOf的结构为Integer类型,但是可以直接赋值对应的基本数据类型
int i=Integer.valueOf(100);
```
# 泛型
泛型是JDK5引入的语法现象,在编译期间对数据类型进行约束,保证数据安全。
```java
public class MyList {
//存储数据长度
private int size;
//储存数据的数组
private T[] cache;
public MyList(){
this.size=0;
this.cache=(T[])new Object[5];
}
//添加数据
public void add(T obj){
//判断数组是否已满
if(size==cache.length) {
//扩容
T[] newCache=(T[])new Object[cache.length*2];
//将cache中的数据复制到newCache中
System.arraycopy(cache,0,newCache,0,cache.length);
//将newCache赋给cache
cache=newCache;
}
//将数据添加到数组中
cache[size]=obj;
//数据长度加1
size++;
}
@Override
public String toString() {
return Arrays.toString(cache) ;
}
}
```
实例化的方法
```java
MyList list=new MyList();
list.add(100);
list.add(Integer.valueOf(200));
//list.add("123");
//list.add(1.2);
//list.add(false);
System.out.println(list);
```
## 类型擦除
泛型在JDK5之前,编译期间不会进行类型检查,在JDK5之后,泛型在编译期间进行对类型进行检测,编译完成之后,将泛型的类型擦除,只保留基本的数据类型。
```java
MyList list=new MyList();
//编译之后:只有MyList的类型,不存在MyList的类型
System.out.println(list instanceof MyList);
System.out.println(list.getClass());
```
## 菱形语法
在实例化对象是,编译时类型中的泛型类型必须和运行时泛型的类型一致。可以将运行时的泛型的类型省略,使用菱形语法。
```java
MyList list1=new MyList<>();
```
# 集合框架
java提供集合框架对数据进行处理。基于底层的数据结构对数据集合进行管理。数组在实际的数据处理上存在问题。最核心的长度不可变。
数组:存储相同数据类型的顺序结构,一旦初始化之后长度不可变。
## Collection
定义了集合框架的基本API,java种提供了java.util.Colletion定义集合框架的基本功能。
> The root interface in the collection hierarchy. A collection represents a group of objects, known as its elements. Some collections allow duplicate elements and others do not. Some are ordered, and others are unordered. Collections that have a defined encounter order are generally subtypes of the SequencedCollection interface. The JDK does not provide any direct implementations of this interface: it provides implementations of more specific subinterfaces like Set and List. This interface is typically used to pass collections around and manipulate them where maximum generality is desired.
Collection中常见的子接口:
* List: 基于线性表的实现
* Set: 基于哈希表的实现
### Collection中的抽象方法

* add(E a):向集合中添加一个数据
* empty() :判断集合是否为空
* size() :集合中的数据量
* contains(Object o) :判断集合中是否存在对象o,依赖该对象的equals方法
## List
List是Collection的子接口。
> An ordered collection, where the user has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list.Unlike sets, lists typically allow duplicate elements.
List集合的特点
* 记录数据的顺序
* 支持下标的操作
* 允许有重复元素
### List的扩展的API
提供了支持下标的操作。
* add(int index, E element)
* get(int index)
* remove(int index)
### ArrayList
> Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is used internally to store the list. (This class is roughly equivalent to Vector, except that it is unsynchronized.)
ArrayList的特点
* 基于可变长数组的List的实现(基于顺序表的实现)
* 支持null的元素
* ArrayList线程不安全的,效率高
案例1:
```java
//1. 示例化一个ArrayList
List l1=new ArrayList<>();
l1.add(1);
l1.add(100);
System.out.println(l1.size());
```
案例2:
```java
Apple a1=new Apple("red",160,"陕西");
Apple a2=new Apple("green",170,"陕西");
Apple a3=new Apple("red",160,"陕西");
//将苹果添加到集合中
List lists=new ArrayList<>();
lists.add(a1);
lists.add(a2);
//判断集合中是否存在a1相同的对象
//Apple类中必须重写equals方法,否则contains总是返回false
System.out.println(lists.contains(a3));
lists.add(a3);
System.out.println(lists.size());
```
### LinkedList
LinkedList是基于双向链表实现。
Doubly-linked list implementation of the List and Deque interfaces. Implements all optional list operations, and permits all elements (including null).
LinkedList源码中关于节点Node的定义
```java
private static class Node {
E item;
Node next;
Node prev;
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
```
注意:LinkedList同时是Deque和Queue接口的实现
- Deque 是双向队列的定义
- Queue 是单向队列的定义
## Set
官方API的说明
> A collection that contains no duplicate elements. More formally, sets contain no pair of elements e1 and e2 such that e1.equals(e2), and at most one null element. As implied by its name, this interface models the mathematical set abstraction.
Set基本的特点
* 不包含重复的数据(能够自动去重复)
* 去重复数据的功能依赖数据的equals和hashCode方法
* 不支持下标操作
如果向Set集合中添加的数据没有重写equals和hashCode方法,那么不能实现去重的效果。
set通过add添加数据的流程图

### HashSet
> This class implements the Set interface, backed by a hash table (actually a HashMap instance). It makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time. This class permits the null element.
HasSet的特点
* 基于哈希表的Set的实现
* 不记录数据的顺序
案例
```java
Set set=new HashSet<>();
set.add("abc");
set.add("123");
set.add("012");
set.add("abc");
System.out.println(set.size());//
System.out.println(set);//[012, 123, abc]
//set的遍历 :for-each
for(String str:set){
System.out.println(str);
}
```
### LinkedHashSet
> Hash table and linked list implementation of the Set interface, with well-defined encounter order. This implementation differs from HashSet in that it maintains a doubly-linked list running through all of its entries. This linked list defines the encounter order (iteration order), which is the order in which elements were inserted into the set (insertion-order).
LinkedHashSet的特点
* 基于Hash表和链表的实现
* 能够记录数据的顺序
* 去重复
## 作业
1. 统计苹果集合中苹果的总重量,平均重量,最大重量,最小重量
```java
// 1. 定义一个类封装统计数据
public class Stats{
//总重量
private int sum;
//平均重量
private double avg;
//最大重量
private int max;
//最小重量
private int min;
}
//2 定义一个方法对苹果的集合进行统计
public Stats statsAppleDatas(List datas){
return null;
}
```
2. 统计苹果集合中有几种产地
```java
public Set statsAppleLocation(List datas){
return null;
}
```
## 扩展学习
1. ArrayList和LinkedList如何选择(性能问题)
2. ArrayList线程不安全为什么还要推荐使用?
# Collections
java.util.Collections是JDK提供的针对Collection的工具类。
## 常用的API
- addAll(Collection super T> c, T... elements):T...表示可变长的参数;Collection super T>:?是泛型中的通配符(任何类型),super T限制了该类型是T或者T的子类
- binarySearch(List extends Comparable super T>> list, T key):二分查找
- shuffle(List> list):洗牌
- synchronizedList(List list):将一个List转成线程安全的对象
## Comparable
Comparable定义类的排序规则。
- sort(List list) :对List进行排序的处理,T的泛型约束>
定义一个类实现Comparable\接口重写compareTo方法
```java
public class Apple implements Comparable {
@Override
public int compareTo(Apple o) {
return this.getWeight() - o.getWeight();
}
```
## Comparator
java中通过Comparator定义临时的排序规则.
- sort(List list, Comparator super T> c)
```java
Collections.sort(apples,new SortByColor());
for(Apple a:apples){
System.out.println(a.getColor()+":"+a.getWeight());
}
class SortByColor implements Comparator{
@Override
public int compare(Apple o1, Apple o2) {
return o1.getColor().compareTo(o2.getColor());
}
}
```
## 思考
- Set和List是否可以进行相互转换?
# Map
Map是使用键值对的形式存储对象
> An object that maps keys to values. A map cannot contain duplicate keys; each key can map to at most one value.
Map特点
- 使用键值对的形式存在
- 不能包含重复的键key
- 一个键只能对应一个值 value
## 常见API
java.util.Map接口定义了键值对的基础的API
- get(Object key) :根据key获得对应的value值
- put(Objet key,Object Value): 将一个键值对保存到Map中。如果有重复的key直接覆盖
- size(): Map中键值对的数量
- isEmpty():判断Map是否为空
- clear():清空Map
## HashMap
> Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits null values and the null key. (The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.
HashMap是基于哈希表的Map的实现。特点如下
- 允许有null的key或者null的值
- 不保留顺序
```java
Map maps=new HashMap<>();
maps.put("1001",2000);
maps.put("1002",2001);
maps.put("1001",1000);//出现重复的key,直接覆盖
System.out.println(maps.size());//2
System.out.println(maps);
System.out.println(maps.get("1001"));//1000
```
### Map的遍历
方法1:基于Map的keySet方法
Map提供了keySet方法可以获得Map中由key组成的Set集合。
```java
Map maps=new HashMap<>();
maps.put(1,"张三丰");
maps.put(2,"张无忌");
maps.put(4,"李逵");
//对map进行遍历
Set keySet = maps.keySet();
for(Integer key:keySet){
String value=maps.get(key);
System.out.println(key+":"+value);
}
```
方法2:基于Map的entrySet方法
Java中通Map接口中声明了一个Entry的接口,通过Entry描述一个由key,value组成的对象。
```java
Map maps=new HashMap<>();
maps.put(1,"张三丰");
maps.put(2,"张无忌");
maps.put(4,"李逵");
//对map进行遍历
Set> entrySet = maps.entrySet();
for(Map.Entry entry:entrySet){
System.out.println(entry.getKey()+":"+entry.getValue());
}
```
## 练习
输入一个字符串,获得字符串中每个字符出现的次数
```java
public Map parseCharacter(String str){
Map maps=new HashMap<>();
for(int index=0;index parseCharacter(String str){
Map maps=new HashMap<>();
for(int index=0;index datas = AppleDataGenerator.generateAppleList();
//匿名内部类
Collections.sort(datas,new Comparator(){
@Override
public int compare(Apple o1, Apple o2) {
return o1.getColor().compareTo(o2.getColor());
}
});
}
```
# Lambda表达式
JDK8.0以后,支持Lambda表达式,可以通过Lambda表达式实现函数(代码块)作为参数传递的效果。
```java
List datas = AppleDataGenerator.generateAppleList();
Collections.sort(datas, (o1,o2)->o1.getColor().compareTo(o2.getColor()));
```
## 上下文推断
Lambda能够实现代码片段的传入,是依赖于上下文的推断,不存在二义性。
1. 调用Collections.sort方法中,能够接收两个参数的sort只有唯一的一个。

2. 推断出,该sort方法的第二个参数必须是Comparator的实例。
3. Comparator接口只有一个抽象方法compare方法
4. 推导出代码片段提供给compare方法实现逻辑的
5. sort方法中第一个参数data,数据类型为List从而决定了Comparator中的o1,o2的类型
注意:能够使用Lambda表达式的接口必须是函数式接口(接口中只有一个抽象方法的)
## Lambda基本语法
基本的结构如下
```
(变量列表)->{执行体;}
```
案例
```java
public void test01(){
Comparator c01=new Comparator() {
@Override
public int compare(String o1, String o2) {
return o1.length()-o2.length();
}
};
//能够使用Lambda表达式的前提是Comparator是函数式接口
//上下文推断代码片段只能是compare的实现部分。可以省略无关的接口,方法名
Comparator c02=(String o1, String o2)->{
return o1.length()-o2.length();
};
//声明时泛型为String,推断出方法参数只能是String类型
Comparator c03=(o1, o2)->{
return o1.length()-o2.length();
};
//Lambda执行体如果只有一行代码可以省略return关键字和花括号
Comparator c04= (o1, o2)-> o1.length()-o2.length() ;
}
}
```
# java.util.function
java.util.function是JDK8.0提供了函数式接口的包。
## Predicate 谓词
通过Predicate接口,可以获得一个boolean类型的结果。
```java
@FunctionalInterface
public interface Predicate {
boolean test(T t);
}
```
案例
```java
@Test
public void test(){
List datas = AppleDataGenerator.generateAppleList();
List result = filterApple(datas, a -> "红色".equals(a.getColor()));
}
public List filterApple(List datas, Predicate predicate){
//实例化一个集合接收结果
List result=new ArrayList<>();
//对datas中的数据进行遍历(迭代)
for(Apple apple:datas){
if(predicate.test(apple)){
//存储到结果中
result.add(apple);
}
}
return result;
}
```
## Function
通过Function实现类型的转换,输入一个T类型的数据获得一个R类型的结果
```java
@FunctionalInterface
public interface Function {
R apply(T t);
}
```
案例:获得苹果数据集中所有苹果的重量
```java
public void testWeights(){
List datas = AppleDataGenerator.generateAppleList();
List result = getWeights(datas, a->a.getWeight() );
}
public List getWeights(List datas, Function function){
List list=new ArrayList<>();
for(Apple apple:datas){
Integer r = function.apply(apple);
list.add(r);
}
return list;
}
```
## Supplier\
通过Supplier获得一个T类型的结果(生产者)
```java
@FunctionalInterface
public interface Supplier {
T get();
}
```
## Consumer\
通过Consumer接收T类型的参数但是没有结果的返回
```java
@FunctionalInterface
public interface Consumer {
void apply(T t);
}
```
## 类型特化
为了规避大量的装箱和拆箱的操作,java.util.function包中提供类型特化的结构。如ToIntFunction
```java
//定义一个谓词判断整数是否为偶数(可能有装箱和拆箱的操作)
Predicate pre01=i->i%2==0;
//类型的特化
IntPredicate pre02= a -> a%2==0;
//将字符串转成整型(可能有装箱和拆箱的操作)
Function fun1= str->str.length();
//返回结果就是int类型(避免装箱的操作)
ToIntFunction fun2= str->str.length();
```
## 作业
1. 分析苹果的数据集,获得种颜色苹果的最大值,最小值,平均值
2. 课堂中Lambda表达式案例
3. 预习:stream 地址:https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/package-summary.html
# Stream
java.util.stream 用于支持对元素流进行函数式作的类,例如 作为集合上的映射约简变换。例如:
```java
//获得集合中红苹果的总重量
int sum = widgets.stream()
.filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();
```
## Stream和集合的区别
- 无存储性:流并非用于存储元素的数据结构,而是从数据源(如数据结构、数组、生成器函数或输入输出通道)中传输元素,并通过一系列计算操作构成的管道来处理这些元素。
- 函数式特性:对流执行的操作会生成一个结果,但不会修改其数据源。例如,对一个从集合中获取的流执行过滤操作,会生成一个不包含被过滤元素的新流,而非直接从源集合中删除元素。
- 惰性求值倾向:许多流操作(如过滤、映射或去重)可通过惰性方式实现,从而为优化提供可能。例如,执行 “查找第一个包含三个连续元音的字符串” 这一任务时,无需遍历所有输入字符串。流操作分为中间操作(会生成流的操作)和终端操作(会生成值或产生副作用的操作)两类,其中中间操作始终具备惰性。
- 潜在无限性:集合的大小是有限的,而流则可以是无限的。limit(n) 或 findFirst() 这类短路操作,能够让基于无限流的计算在有限时间内完成。
- 可消费性:在一个流的生命周期内,其元素仅能被访问一次。与迭代器类似,若要重新访问数据源中的相同元素,必须重新生成一个新的流。
## 获得Stream
- 从**集合**中获取:通过集合的 stream() 方法(获取串行流)或 parallelStream() 方法(获取并行流);
- 从**数组**中获取:借助 Arrays.stream(Object[]) 方法;
- 从流类的静态工厂方法中获取:例如 Stream.of(Object[])、IntStream.range(int, int) 或 Stream.iterate(Object, UnaryOperator);
- 从**文件**行中获取:通过 BufferedReader.lines() 方法获取文件中的文本行流;
- 从文件路径中获取:调用 Files 类中的相关方法,可得到文件路径流;
- 从随机数中获取:通过 Random.ints() 方法获取随机数流;
- 从 JDK 提供的其他大量生成流的方法中获取:包括 BitSet.stream()、Pattern.splitAsStream(java.lang.CharSequence) 以及 JarFile.stream() 等。
## java.util.stream.Stream
> A sequence of elements supporting sequential and parallel aggregate operations.
通过Stream对象提供的API对流进行操作,Stream提供方法根据返回值的不同,分为中间操作和终端操作
- 中间操作:该方法执行后返回Stream类型的结果。中间操作是惰性的,没有执行终端操作难么中间操作不执行。如fitler

- 终端操作:该方法执行后返回非Stream类型的结果。如toList

基础API训练
```java
List datas =AppleDataGenerator.generateAppleList();
//获得红苹果
List result = datas.stream()
.filter(a -> "红色".equals(a.getColor()))
.toList();
System.out.println(result.size());
//获得苹果产地组成的集合
List list = datas.stream() //Stream
.map(a -> a.getLocation()) //Stream
.toList();
System.out.println(list);
Set sets=new HashSet<>(list);
System.out.println(sets);
//获得苹果重量组成的集合
List list1 = datas.stream()
.map(a -> a.getWeight())
.toList();
//获得苹果的总重量
int sum = datas.stream()
.mapToInt(a -> a.getWeight())
.sum();
//遍历数据:将苹果的颜色打印输出
datas.stream()
.forEach(a->System.out.println(a.getColor()));
//获得红苹果的总重量
int redApplesWeight = datas.stream()
.filter(a -> "红色".equals(a.getColor()))
.mapToInt(a -> a.getWeight())
.sum();
System.out.println(redApplesWeight);
String[] arr={"hello","word"}; //需要获得["h","e","l","l","o","w",....]
/* List list = Arrays.stream(arr)
.map(str -> str.split(""))
.flatMap(data->Arrays.stream(data))
.distinct()
.toList();
System.out.println(list);*/
List list = Arrays.stream(arr)
.map(str -> str.split(""))
.flatMap(Arrays::stream)
.distinct()
.toList();
System.out.println(list);
List datas =AppleDataGenerator.generateAppleList();
//根据产地对苹果进行分类
Map> locations = datas.stream()
.collect(Collectors.groupingBy(a -> a.getLocation()));
```
# File
java中使用java.io.File描述系统中的文件或者是目录。通过File可以管理(创建/删除)文件/目录。
基本API
```java
//将当前目录实例化为一个File对象
// . 表示当前目录
// .. 表示上一级目录
File f1= new File(".");
//获得该目录的绝对路径
//E:\java_workspaces\learnJava\.
System.out.println(f1.getAbsolutePath());
//是否为目录 :true
System.out.println(f1.isDirectory());
//当前目录在磁盘中是否存在
System.out.println(f1.exists());
```
注意:File默认的当前目录为当前的项目中。
创建新的目录和文件
```java
//在当前路径下创建一个dir01的资源
File dir=new File("dir01");
//判断该资源是否在磁盘中存在,如果不存在则创建
if(!dir.exists()){
//创建目录
dir.mkdir();//创建文件夹
}
//在dir01目录下创建a01.txt文件
File a01=new File(dir,"a01.txt");
if(!a01.exists()){
a01.createNewFile();//创建文件
}
```
练习:在当前目录下创建dir02目录,dir02下面创建一个datas.txt的文件
通过File可以创建或删除文件,操作文件的相关属性(读写操作),修改文件的名称。但是无法通过File读取文件的内容。
通过delete可以删除目录或者文件。但是目录必须是空目录
# Path
java.nio.Path一个可用于在文件系统中**定位文件**的对象。它通常表示一个与系统相关的文件路径。
可以通过File提供的toPath获得Path的实例
# Files
java.nio.Files是提供对文件操作的工具类
常见的工具方法
- copy(Path source, Path target, CopyOption... options)
- createDirectories(Path dir, FileAttribute>... attrs)
- deleteIfExists(Path path)
- isDirectory(Path path, LinkOption... options)
- move(Path source, Path target, CopyOption... options)
```java
//在dir01目录下创建一个a02.txt文件
Path a02 = Path.of("./dir01/a02.txt");
if(!Files.exists(a02)){
Files.createFile(a02);
}
```
# IO流
通过IO流对文件的内容进行读写操作
# 字节流
计算机底层能够操作的最小单元是byte。所有的文件在底层都是byte而组成的集合。
关于输入流和输出流的介绍

- java.io.InputStream :定义了字节流的输入流
* read() 读取一个字节,并返回。如果返回为-1表示在文件的末尾。
* read(byte[] buff) 从文件中读取最多buff.length个byte到buff中。返回值为实际读入的数量。如果返回值为-1,表示读取结束
- java.io.OutputStream:定义了字节流的输出流
* write(int b) :将一个byte写入到文件中
* write(byte[] buff):将一个byte数组写入到文件中
- java.io.FileInputStream
- java.io.FileOutputStream
* FileOutputStream(String name, boolean append):name指定文件的名称,append设置写入的内容是否追加。append为true,文件不存在则篡改就该文件,内容以追加的形式写入。append为false表示总是创建新的文件。
案例:
```java
public class ByteStreamTest {
@Test
public void writeContext() throws Exception{
//1. 实例化一个FileOutputStream
FileOutputStream fos=new FileOutputStream("dir01/a03.txt",true);
//2. 定义byte数据
byte[] bytes = "abc计算机".getBytes();//使用文件默认的字符编码转成byte序列
//3. 写入到文件中
fos.write(bytes);
//4. 释放资源
fos.flush();
fos.close();
}
@Test
public void readContent() throws Exception{
FileInputStream fis = new FileInputStream("dir01/a03.txt");
byte[] buff=new byte[0];
int data =-1;
while( (data=fis.read())!=-1){
buff = Arrays.copyOf(buff, buff.length + 1);
buff[buff.length-1]=(byte)data;
}
String str = new String(buff);
System.out.println(str);
//释放资源
fis.close();
}
@Test
public void readContent2() throws Exception{
FileInputStream fis = new FileInputStream("dir01/a03.txt");
byte[] buff=new byte[1024];
int length = fis.read(buff);
System.out.println("实际读入byte的数量:"+length);
byte[] result = Arrays.copyOf(buff, length);
String s = new String(result);
System.out.println(s);
fis.close();
}
}
```
练习:实现文件的复制
```java
public void copyFilev01(String src, String target)throws Exception{
//读取src文件
FileInputStream fis =null;
FileOutputStream fos=null;
try {
fis= new FileInputStream(src);
fos=new FileOutputStream(target,true);
int data=-1;
while((data=fis.read())!=-1){
//写入到target文件中
fos.write(data);
}
fos.flush();
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
//物理资源必须手动释放,在finally块中执行
fis.close();
fos.close();
}
}
```
# AutoCloseable
如果AutoColoseable的类在try-with-resources的代码块中,会自动执行close方法。无需显示的在finally中调用close方法释放资源
try-with-resources的代码块
```java
try(实例化AutoColoseable对象){
//执行相关的api
}catch(异常类型 xx){
//异常处理
}
```
注意:try后面的小括号里面只能写实例化AutoColoseable对象的代码,不能写其他调用api的代码。
案例:改写复制的功能
```java
public void copyFilev01(String src, String target){
//读取src文件
try (FileInputStream fis = new FileInputStream(src);
FileOutputStream fos=new FileOutputStream(target,true);){
int data=-1;
while((data=fis.read())!=-1){
//写入到target文件中
fos.write(data);
}
fos.flush();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
```
高效的文件复制
```java
public void copyFile(String src, String target){
//声明一个缓冲数组,长度为4k
byte[] buff=new byte[4*1024];
//读取src文件
try (FileInputStream fis = new FileInputStream(src);
FileOutputStream fos=new FileOutputStream(target,true);){
int length=-1;
while((length=fis.read(buff))!=-1){
//写入到target文件中
fos.write(buff,0,length);
}
fos.flush();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
```
## 字节流缓冲流
- BufferedInputStream:字节流输入流的缓冲流
- BufferedOutputStream:字节流输出流的缓冲流(默认提供一个缓冲的byte数组)
```java
try(
FileOutputStream fos=new FileOutputStream(fileName);
BufferedOutputStream bos=new BufferedOutputStream(fos);
){
}
```
# 字符流
通过字符流可以更好的操作文本文件。
## 抽象类
Reader定义字符流输入流的基本API
- read() 返回一个字符
- read(char[] cbuf) 以字符数组的形式读入
Writer定义字符流的输出流的API
- write(String str) :以字符串的形式写入
- write(char[] cbuf) : 以字符数组的形式写入
## 实现类
InputStreamReader :字符流输入流的实现类
- InputStreamReader(InputStream in, String charsetName)
```java
@Test
public void testReader01(){
try( FileInputStream fis=new FileInputStream("dir01/b01.txt");
InputStreamReader isr=new InputStreamReader(fis, "utf-8")
){
int data=-1;
while((data=isr.read())!=-1){
System.out.print((char)data);
}
}catch (Exception e){
throw new RuntimeException(e);
}
}
```
OutputStreamWriter :字符流输出流的实现类
- OutputStreamWriter(OutputStream out, String charsetName):可以指定字符编码
```java
public void writerContext(String context){
try( FileOutputStream fos=new FileOutputStream("dir01/b01.txt",true);
OutputStreamWriter os=new OutputStreamWriter(fos,"utf-8");
){
os.write(context);
//写入换行符
os.write("\n");
os.flush();
}catch (Exception e){
throw new RuntimeException(e);
}
}
```
## 字符流的缓冲流
BufferedReader: 字符流输入流的缓冲流,提供了readLine()读取一行文本的API.
- BufferedReader(Reader in)
```java
public void read(){
try( FileInputStream fis=new FileInputStream("dir01/b01.txt");
InputStreamReader isr=new InputStreamReader(fis, "utf-8");
BufferedReader br=new BufferedReader(isr);
){
String data=null;
while((data=br.readLine())!=null){
System.out.print(data);
}
}catch (Exception e){
throw new RuntimeException(e);
}
}
```
BufferedWriter:字符流的缓冲流,提供newLine()的换行符,兼容不同的平台
- BufferedWriter(Writer out)
```java
public void writer(String context){
try(FileOutputStream fos=new FileOutputStream("dir01/b02.txt",true);
OutputStreamWriter osw=new OutputStreamWriter(fos,"utf-8");
BufferedWriter bw=new BufferedWriter(osw);
){
bw.write(context);
bw.newLine();
bw.flush();
}catch(Exception e){
throw new RuntimeException(e);
}
}
```
## 字符流简化流
FileReader :字符流输入流的简化流
- FileReader(String fileName, Charset charset)
FileWriter : 字符流输出流的简化流
- FileWriter(File file, Charset charset, boolean append) :JDK11提供
- FileWriter(String fileName, Charset charset, boolean append) JDK11提供
## PrintWriter
字符流输出流,提供println(String x)写入字符串信息的同时写入换行符
- PrintWriter(OutputStream out, boolean autoFlush)
- PrintWriter(String fileName, Charset charset)
```java
public void printWriter(String context){
try(FileOutputStream fos=new FileOutputStream("dir01/b03.txt",true);
PrintWriter pw=new PrintWriter(fos,true)){
pw.println(context);
}catch (Exception e){
throw new RuntimeException(e);
}
}
```
通过Stream读取文件
```java
public void readStream(){
try( FileInputStream fis=new FileInputStream("dir01/b03.txt");
InputStreamReader isr=new InputStreamReader(fis, "utf-8");
BufferedReader br=new BufferedReader(isr);
){
br.lines().forEach(s->System.out.println(s));
}catch (Exception e){
throw new RuntimeException(e);
}
}
```
Files.lines方法
```java
Path path=Path.of("dir01/b03.txt");
Stream stream = Files.lines(path, Charset.defaultCharset());
stream.forEach(s->System.out.println(s));
```
## 作业
1. 统计每门课程的数据:优秀率,及格率
2. 对每门课程的成绩进行排序
3. 获得每个学生的绩点数据
