Java基础阶段
Java基础
JVM、JRE、JDK 有什么关系
JDK(Java Development Kit)是用于开发 Java 应用程序的软件环境。里面包含运行时环境(JRE)和其他 Java 开发所需的工具,比如说解释器(java)、编译器(javac)、文档生成器(javadoc)等等。
JRE(Java Runtime Environment)是用于运行 Java 应用程序的软件环境。也就是说,如果只想运行 Java 程序而不需要开发 Java 程序的话,只需要安装 JRE 就可以了。
JVM (Java Virtual Machine) ,也就是 Java 虚拟机,由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组成,屏蔽了不同操作系统(macOS、Windows、Linux)的差异性,使得 Java 能够“一次编译,到处运行”。
比如说我用 macOS 生成了一个 jar 包(里面是打包好的字节码——可以在 Java 虚拟机上运行的目标代码),可以丢给 Windows 用户直接运行,也可以直接上传到 Linux 服务器运行。
Java语法基础
字符串(String)
与其他编程语言不同,Java中的字符串不是基本类型(比如int、char等)。相反,所有字符串都是预定义的名为String的类的对象。例如,
//创建一个字符串 |
字符串方法:(按使用频率排序)
equals():判断两个字符串是否相等,用法str.equals(s);
length():返回字符串长度。
valueOf():讲其他基本类型转化为字符串,是静态方法,直接通过String.valueOf()调用。
getBytes():将字符串转化为字符数组,
charAt(): 返回目标位置的字符
indexOf():返回字符串中指定字符的位置。
replace():将指定的旧字符替换为新字符。
toLowerCase():将字符串全部小写化
toUpperCase():将字符串大写
subString():获得指定位置子串
concat():拼接两个字符串
split(String reg):分割字符串,返回分割后的字符串数组。
数据类型转换
从小往大是兼容的
byte -> short -> int -> long -> float -> double
char -> int -> long -> float -> double
//但是小往大是会报错不行的;自己的例子:
int result; |
解决方案:(int)n1*f1或者float result;
Integer和int
Java中的数据类型两种:
基本数据类型: byte,short,int,long,float,double,boolean,char,
引用数据类型:类,数组,接口
Java为每个基本数据类型提供了封装类:Byte,Short,Integer,Long,Float,Double,Boolean,Character
为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱拆箱操作,使得二者可以相互转化。
integer是int的包装类,必须实例化后才可以使用:Integer a = new Integer(2);
当数字在-128-127时integer有一个缓存池,作用是让这些常用的数字占用更少的内存,所以当使用Integer.valueof(12)时这个时候时取得缓存池中的数据,没有新建一个integer对象,因此无论这样创建几个integet对象都是相等的,但是使用new integer创建的是真实的对象,是不一样的。
Lambda表达式(args…)->{operation}
JAVA中最重要的内容之一:
使得java中的函数编程思想前进了一大步,通过(args…)->(operation)实现不关注于谁实现了方法,而关注于方法本身,不需要像面向对象编程一样必须需要创建对象再使用对象的方法可以一些表达式中直接使用lambda实现操作。
泛型不适用基本数据类型,
Stream流
好像一个过滤器,有中间操作和最终操作;
01. 创建流:
//如果是数组的话可以使用Arrays.stream或者stream。of |
助记:所有的都可以直接.stream()实现;
Reason:容器集合有自带的Stream()方法,直接调用就可以。对于数组来说,可以看作Arrays,所以需要调用Arrays.stream()其实大同小异。
- 操作流:
HashMap
整个遍历方式:
for(Map.Entry<Integer,Integer> entry = map.entrySet()){ |
Java面向对象编程
继承
通过extends实现继承,子类可以继承父类的属性和方法,但是不能继承构造方法。
可以使用super关键字来调用父类的构造方法。
通过@Override注解来标识重写父类的方法。final关键字:
- 可以用来修饰类、方法、变量(成员变量和局部变量)
- final表示修饰的类或方法是继承树的末端,无法被继承/重写。
- final修饰的变量是常量,只能被赋值一次,赋值后不能再改变。
- final修饰的方法不能被重写。
覆盖的规则:
- 子类方法的参数列表和返回值类型必须与父类方法的参数列表和返回值类型相同。
- 子类方法的访问权限不能低于父类方法的访问权限。
区别于重载:
- 重载是在同一个类中,方法名相同,参数列表不同。
- 重写是在子类中,方法名相同,参数列表相同。
- 重载是编译时多态,重写是运行时多态。
多态
多态(Polymorphism),字面意思是“多种形态”。在Java中,它指的是 允许不同类的对象对同一消息做出不同的响应。简单来说,就是 同一个方法调用,作用于不同的对象,会产生不同的执行结果。多態的核心在于 运行时绑定:程序在运行时,才动态地决定到底要调用哪个对象的方法。
多态的三个必要条件:
- 继承:必须有类之间的继承关系。
- 重写:子类必须重写父类的方法。
- 父类引用指向子类对象:父类类型 变量名 = new 子类类型();
抽象abstract
如果不想让某个类被初始化,就以abstract关键词将其标记为抽象的。
抽象类是用abstract关键字修饰的类:public abstract class Animal{},抽象类中可以有抽象方法和非抽象方法,抽象方法是用abstract关键字修饰的方法,抽象方法没有方法体,只有方法声明,抽象类不能被实例化,只能被继承。
抽象方法的作用是为了让子类去实现,抽象类的作用是为了让子类去继承。
举个例子,什么时候我们需要用到抽象类:
从面向对象设计的思想出发,如果我们有时候需要定义一个动物类,以及这个类下面的众多子类,比如宠物、禽兽等,再往下还有具体的猫、狗、狮子、河马等,对于这些具有实体的动物也是我们需要具体实现的类,但对于一些抽象的类,比如上层的动物,宠物这些,我们并不希望他们可以创建实体出来,只希望他们能够在上层实现一些动物通用的方法供给子类去继承使用或实现。
接口interface
接口就好像是100%的纯抽象类,是用interface关键字修饰的类``,接口中只能有抽象方法和常量,接口不能有构造方法,接口不能被实例化,只能被实现。
一个类只能继承一个父类,但是可以实现多个接口。
接口的定义:
public interface pet{ |
接口的实现:public class cat implements pet{}
接口的作用&使用思想:
举个例子:
比如动物类别下面有很多不同环境的动物,比如狮子,猫,狼,鲸鱼等,如果我们按照生物学上的分类的话,那么猫和老虎可能是继承自一个父类,但是现实中的某些动物比如猫、狗、松鼠等又可以归为一个宠物类别,这些不同属于一个父类,但是又具有统一的宠物行为,比如和主人互动,投喂等,在一个类只能继承一个父类的基础上我们再想统一这些不同的动物的行为,所以我们就可以使用接口来实现,我们可以定义一个public interface pet{}然后里面定义了一些宠物的行为,之后让猫狗鼠implements pet,之后就可以在每个具体的类中实现接口中抽象的方法了。(最重要的结果)继而我们可以在后面如果需要用到这些宠物的行为的话就可以使用类似于pet cat1 = new cat()构建一个pet类型,但是指向的是cat对象,因此就可以直接调用例如cat.sleep方法。
因为有着实现接口必须按照接口定义来的特性,所以我们有一句话:接口 定义 规范,就是这么得来的,很多规范可以在接口中确定,让后续加进来的类/对象必须按照要求实现。
静态方法
静态方法无需创建对象就可以调用对象的静态方法,用static修饰。
静态方法无法调用非静态方法及变量。
可以把static修饰的方法或变量看作是属于类的属性或方法,而不是属于这个类的任何一个实例对象的属性或方法。
static修饰的变量,也称为类变量,是指在类中使用static关键字修饰的变量,静态变量在类加载时就被初始化,且只被初始化一次,静态变量可以通过类名直接访问,也可以通过对象名访问。常用于统计对象初始化次数等。在一个类中,
static {}这种语法结构被称为 静态初始化块 (Static Initialization Block),或者常被叫做 静态代码块 (Static Block)。
静态代码块是属于类本身的、用于初始化类级别资源的一段代码。它不属于任何一个对象实例。
它的主要特点是:在Java虚拟机(JVM)加载该类时执行,并且只执行一次。
因为静态代码块是在任何对象实例创建前运行,因此- 它只能访问类中的静态成员
- 在静态代码块中,不能使用
this关键字,因为this关键字是指向当前对象的引用,而静态代码块是在类加载时执行的,此时对象还没有被创建,所以不能使用this关键字。
Java构造器
创建对象有三个步骤:
- 声明引用变量:
Animals cat - 创建对象
new Cat() - 连接对象与引用
Animals cat = new Cat()
在new创建对象时会调用对象的构造函数,如果已经写了一个有参数的构造函数,那么必须还需要写一个没有参数的构造函数。
当类继承了一个父类时,该类的构造函数会自动调用父类的构造函数,如果想要手动调用父类的构造函数可以使用super()方法,且必须置于第一行。
使用this()从某个构造函数调用另一个构造函数,this()必须置于第一行,且只能调用一个构造函数。
★容器(集合框架)
这一块如果照着官方继承图硬背,后面很容易越学越乱。更适合回顾的方式是按平时真正使用的习惯来分:
- 单列容器:一个元素就是一个值
- 双列容器:一个元素是一组
key-value
可以先记一句总纲:
List:有序、可重复Set:不重复Queue / Deque:排队、双端操作Map:键值映射
单列容器
单列容器的公共父接口是 Collection,很多基础方法都能通用。
1. Collection 常用方法
add(E e):添加元素remove(Object o):删除指定元素contains(Object o):判断是否包含某元素size():元素个数isEmpty():是否为空clear():清空集合iterator():获取迭代器addAll(c):添加另一个集合的所有元素removeAll(c):删除与另一个集合相同的部分retainAll(c):只保留与另一个集合相同的部分toArray():转数组
2. List
List 的特点:
- 有顺序
- 有下标
- 元素可以重复
适合场景:
- 想按索引访问
- 想保留插入顺序
- 数据允许重复
最常用实现类:
ArrayListLinkedList
2.1 List 常用方法
add(E e):尾部添加add(int index, E e):指定位置插入get(int index):按下标取值set(int index, E e):修改指定位置元素remove(int index):按下标删除remove(Object o):按元素删除indexOf(Object o):第一次出现的位置lastIndexOf(Object o):最后一次出现的位置
助记:
get是取set是改add是加remove是删
2.2 ArrayList
ArrayList 本质是动态数组。
特点:
- 查询快
- 尾部添加通常快
- 中间插入删除慢,因为后面元素要整体移动
创建方式:
List<String> list = new ArrayList<>(); |
常用操作:
list.add("Java"); |
2.3 LinkedList
LinkedList 是双向链表。
特点:
- 头尾插入删除方便
- 随机访问慢
- 同时实现了
List和Deque
创建方式:
LinkedList<Integer> list = new LinkedList<>(); |
常用操作:
list.addFirst(10); |
2.4 ArrayList vs LinkedList
可以直接这样记:
- 经常按下标访问:
ArrayList - 经常头尾插入删除:
LinkedList
一句话助记:
数组查得快,链表改得灵活。
3. Set
Set 的特点:
- 元素不能重复
- 一般没有下标
- 核心用途是“去重”和“判重”
最常用实现类:
HashSetLinkedHashSetTreeSet
3.1 Set 常用方法
add(E e):添加元素contains(Object o):判断是否存在remove(Object o):删除元素containsAll(c):是否包含另一个集合全部元素addAll(c):并集retainAll(c):交集removeAll(c):差集
助记:
addAll:并进去retainAll:留下交集removeAll:删掉重合部分
3.2 HashSet
特点:
- 无序
- 去重快
- 判断元素存不存在很常用
适合场景:
- 数组去重
- 判重
- 做
visited标记
3.3 LinkedHashSet
特点:
- 去重
- 保留插入顺序
适合场景:
- 既想去重,又想遍历顺序稳定
3.4 TreeSet
特点:
- 自动排序
- 元素不重复
适合场景:
- 有序去重
- 需要最小值/最大值
4. Queue
Queue 的特点:
- 先进先出
FIFO - 适合 BFS、层序遍历、任务排队
最常用实现:
LinkedListArrayDequePriorityQueue
4.1 Queue 常用方法
这组方法最好成对记:
| 操作 | 抛异常版本 | 返回特殊值版本 |
|---|---|---|
| 入队 | add(e) |
offer(e) |
| 看队头 | element() |
peek() |
| 出队 | remove() |
poll() |
助记:
add / remove / element:失败可能抛异常offer / poll / peek:更安全,刷题更常用
常见写法:
Queue<Integer> queue = new LinkedList<>(); |
4.2 PriorityQueue
PriorityQueue 是优先队列,不是普通的 FIFO 队列。
特点:
- 每次弹出的都是优先级最高的元素
- 默认是小根堆
创建方式:
Queue<Integer> pq = new PriorityQueue<>(); |
如果想改成大根堆:
Queue<Integer> pq = new PriorityQueue<>((a, b) -> b - a); |
适合场景:
- TopK
- 动态维护最值
- 堆相关题目
5. Deque 双端队列
Deque = Double Ended Queue。
特点:
- 两端都能进
- 两端都能出
- 既能当队列,也能当栈
最推荐实现类:
ArrayDeque
5.1 Deque 常用方法
| 含义 | 头部操作 | 尾部操作 |
|---|---|---|
| 添加 | addFirst() / offerFirst() |
addLast() / offerLast() |
| 查看 | getFirst() / peekFirst() |
getLast() / peekLast() |
| 删除 | removeFirst() / pollFirst() |
removeLast() / pollLast() |
助记:
First看左边Last看右边offer / poll / peek更安全
5.2 ArrayDeque
ArrayDeque 是刷题里特别常用的容器。
适合场景:
- 普通队列
- 栈
- 单调队列
- 滑动窗口
创建方式:
Deque<Integer> deque = new ArrayDeque<>(); |
当栈用:
deque.push(1); |
当队列用:
deque.offerLast(1); |
6. Stack 栈
栈的特点:
- 后进先出
LIFO
经典操作:
push():入栈pop():出栈peek():看栈顶
虽然 Java 里有老的 Stack:
Stack<Integer> stack = new Stack<>(); |
但平时更推荐:
Deque<Integer> stack = new ArrayDeque<>(); |
因为 Deque 更统一,也更常用。
双列容器
双列容器核心就是 Map,每个元素都是一组:
key -> value |
1. Map
Map 的特点:
- 键不能重复
- 值可以重复
- 一个 key 对应一个 value
Map不属于Collection体系,但和集合框架一起使用非常多
最常用实现类:
HashMapLinkedHashMapTreeMap
1.1 Map 常用方法
put(K, V):放入键值对putIfAbsent(K, V):key 不存在才放get(K):根据 key 取 valuegetOrDefault(K, defaultValue):取不到给默认值containsKey(K):判断 key 是否存在containsValue(V):判断 value 是否存在remove(K):按 key 删除replace(K, V):替换值keySet():所有 keyvalues():所有 valueentrySet():所有键值对
常见写法:
Map<String, Integer> map = new HashMap<>(); |
1.2 HashMap
特点:
- 无序
- 查询、插入、删除效率高
- 最常用
适合场景:
- 统计次数
- 建立映射
- 前缀和 + 哈希表
- 判断某元素是否出现过
统计次数模板:
Map<Integer, Integer> map = new HashMap<>(); |
1.3 LinkedHashMap
特点:
- 保留插入顺序
适合场景:
- 想让遍历顺序稳定
- LRU 相关思路
1.4 TreeMap
特点:
- 按 key 自动排序
适合场景:
- 需要按 key 有序遍历
- 需要最小 key、最大 key、范围 key
2. Map 的遍历方式
最推荐记 entrySet() 遍历。
2.1 遍历键值对
for (Map.Entry<String, Integer> entry : map.entrySet()) { |
2.2 只遍历 key
for (String key : map.keySet()) { |
2.3 只遍历 value
for (Integer value : map.values()) { |
2.4 Lambda 遍历
map.forEach((k, v) -> System.out.println("key=" + k + ", value=" + v)); |
常用容器快速对比
1. 单列容器怎么选
| 容器 | 特点 | 最适合做什么 |
|---|---|---|
ArrayList |
有序、可重复、查询快 | 存一组可重复数据,且经常按下标访问 |
LinkedList |
有序、可重复、头尾操作灵活 | 频繁头尾插入删除 |
HashSet |
无重复、无序 | 判重、去重 |
LinkedHashSet |
无重复、保留插入顺序 | 去重同时保序 |
TreeSet |
无重复、自动排序 | 有序去重 |
Queue |
FIFO | BFS、普通排队 |
PriorityQueue |
按优先级出队 | TopK、堆 |
Deque |
两端可进可出 | 栈、队列、单调队列 |
2. 双列容器怎么选
| 容器 | 特点 | 最适合做什么 |
|---|---|---|
HashMap |
无序,查找快 | 统计次数、建立映射 |
LinkedHashMap |
保留插入顺序 | 顺序敏感的映射 |
TreeMap |
按 key 排序 | 有序映射、范围问题 |
方法对比记忆
1. add()、offer()
- 都是“放元素”
add()失败可能抛异常offer()更适合队列,失败返回特殊值
2. remove()、poll()
- 都是“删除并返回元素”
remove()空时抛异常poll()空时返回null
3. element()、peek()
- 都是“查看但不删除”
element()空时抛异常peek()空时返回null
助记:
队列里优先记 offer / poll / peek 这一组。
4. get()、contains()
get(index):按下标拿元素,主要是Listget(key):按 key 拿值,主要是Mapcontains(x):判断元素在不在,主要是CollectioncontainsKey(k):判断 key 在不在,主要是Map
5. remove(index)、remove(object)
这是 List 很容易混淆的地方:
remove(int index):删下标remove(Object o):删元素
6. set() 和 add()
set(index, e):覆盖原位置元素add(index, e):在指定位置插入新元素
助记:
set是替换add是插入
7. entrySet()、keySet()、values()
entrySet():同时拿 key 和 value,最适合遍历键值对keySet():只拿 keyvalues():只拿 value
工具类:Arrays 和 Collections
1. Arrays
这是数组工具类,最常用方法:
Arrays.toString(arr):打印一维数组Arrays.deepToString(arr):打印二维数组Arrays.sort(arr):排序Arrays.fill(arr, val):填充Arrays.equals(a, b):比较数组内容Arrays.binarySearch(arr, target):二分查找Arrays.copyOf(arr, newLen):复制/扩容Arrays.asList(...):转成 List 风格
注意:
List<String> list = Arrays.asList("A", "B"); |
这里返回的是固定长度的 List,不能随便 add() / remove()。
2. Collections
这是集合工具类,操作的是 List、Set 等集合。
常用方法:
Collections.sort(list):排序Collections.shuffle(list):打乱Collections.max(list):最大值Collections.min(list):最小值Collections.swap(list, i, j):交换Collections.reverse(list):反转
3. 数组和集合排序对比
- 数组排序:
Arrays.sort(arr) - 集合排序:
Collections.sort(list)
如果要自定义排序,通常配合 Comparator:
Arrays.sort(arr, (o1, o2) -> o2 - o1); |
4. Iterator 接口
Iterator 用来遍历集合,常用方法:
hasNext():后面还有没有元素next():取下一个元素remove():删除当前迭代到的元素
最常见写法:
Iterator<Integer> it = list.iterator(); |
最后总结
如果以后只是想快速判断“该用哪个容器”,先按下面这套思路记:
- 要下标、要重复、要顺序:
List - 只想去重 / 判重:
Set - 先进先出:
Queue - 两端操作、既能栈也能队列:
Deque - 按优先级取元素:
PriorityQueue - 键值对映射:
Map
如果再缩成一句话:
- 单列容器看元素本身
- 双列容器看
key-value - 数组用
Arrays,集合用Collections
I/O输入输出
在Java中,流是从源读取并写入目标的数据序列。
输入流用于从源读取数据。并且,输出流用于将数据写入目标。
根据流包含的数据,可以将其分类为:
- 字节流
- 字符流
字节流(Byte): 它处理单元为1个字节(byte),操作字节和字节数组,存储的是二进制文件,如果是音频文件、图片、歌曲,就用字节流好点(1byte = 8位);字节流的主要类是:InputStream和OutputStream
字符流: 它处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,如果是关系到中文(文本)的,用字符流好点(1Unicode = 2字节 = 16位);主要类是:Reader和Writer;
InputStream类
InputStream类提供了由其子类实现的不同方法。以下是一些常用的方法
- read() - 从输入流中读取一个字节的数据
- read(byte[] array) - 从流中读取所有字节并存储在指定的数组中**(和不带参数的相比他一次读取全部字节)**-常用
- available() - 返回输入流中可用的字节数
- mark() - 标记输入流中数据所在的位置
- reset() -将控制点返回到流中设置标记的点
- markSupported()- 检查流中是否支持mark()和reset()方法
- skips() - 跳过和丢弃输入流中的指定字节数
- close() - 关闭输入流
Java Scanner 类
java.util包的Scanner类用于从不同的源(例如输入流,用户,文件等)读取输入数据。让我们举个实例。
import java.util.Scanner; |
算法Algorithms
Java集合框架提供了各种算法,可用于处理存储在数据结构中的元素。
Java中的算法是静态方法,可用于对集合执行各种操作。
由于算法可用于各种集合,因此也称为通用算法。
**sort()**使用sort来进行排序,使用:Collection.sort(T);Array.sort();
**shuffle()**刚好和sort相反,他是用来打乱集合内所有元素的
copy() - 创建从指定源到目标的元素副本
swap() - 交换集合中两个元素的位置
还有Java集合框架的min()和max()方法分别用于查找最小和最大元素。
sort()
sort使用:Arrays.sort(array);Collections.sort(list);
如果想要局部排序:sort(array, start,end);
想要降序排序:
关于引用对象类型数组的排序sort()方法要用到接口Comparator
//匿名内部类的方式 |


