JavaSE 学习
2025 年 5 月 6 日
Java 基础
背景
无论任何平台 只要有 JVM 就可以跑 java 程序。
JDK 包括 JRE,JRE 包括 JVM
编译,把.java 编译成 class -> 类装载器 -> 字节码校验器 -> 解释器 -> 操作系统平台
public class Hello{
public static void main(String[] args){
System.out.println("Hello world");
}
}
#编译
javac Hello.java
#运行
java Hello.class
三高问题:高可用,高性能,高并发
Java se 是指 standard edition
Java ee 是指 enterprise edition 企业级版本
Java 数据类型:
基本类型:整数类型(byte1 short2 int4 long8) 浮点类型(float4 double8) 字符类型 char2 以及 boolean 类型 True 1or false1 (数字为字节数)
int num0 = 10;
byte num = 20;
short num = 30
long num = 100L; //long类型数字后加L
float num = 50.1F; //float类型数字后加F
double num = 3.141592653589793238462643;
char name = 'A'; // string 不是基本类型
boolean bool = true;
String string = "name";// string 不是基本类型
//整数扩展 二进制 十进制 八进制 十六进制
int i = 10;
int i = 010;//八进制
int i = 0x10;//十六进制
//浮点数拓展 银行怎么表示
// BigDecimal
float f = 0.1f;
double e =1.0/10;
sout(f==e);//false
//字符拓展 输出ascii编码
char a = 'a';
char b = '中';
sout((int)a); // 65
sout((int)b); // 20013
char c = '\u0061';//\u可以转义unicode编码 \t是制表符 \n换行 ...
sout(c);// a
String sa = new String("hello world");
String sb = new String("hello world");
sout(sa==sb);// false
String sc = "hello world";
String sd = "hello world";
sout(sc==sd);// true
强制转换和自动转换
- 不能对布尔值转换
- 不能把对象类型转换为不相干的类型
- 高容量转低容量-> 强制转换,可能会内存溢出
- 低容量转高容量->自动转换,可能会精度问题
//数据溢出
int money = 10_0000_0000;// JDK 新特性
int years = 20;
int total = money*years; //溢出
long total2 = money*years; //溢出
long total3 = ((long)money)*years; //正常
变量和常量
public class Demo{
//类变量
static double salary = 2500;
//常量
static final double PI =3.142;
//实例变量
String name = 'Tom';
int age;
public static void main(String[] args){
// 局部变量
int i =1;
Demo demo = new Demo();
System.out.println(i);
System.out.println(demo.name);
System.out.println(demo.age);
System.out.println(salary);
}
}
变量命名原则
自增运算符
public class Demo{
public static void main(String[] args){
int a = 3;
int b = a++; // 先赋值,再自增
int c = ++a; // 先自增,再赋值
double p = Math.pow(2,3); // 借助Math函数进行乘方运算
}
}
短路运算
//一个有趣的知识点
int c = 5;
boolean d = (c<4)&&(c++>3);
System.out.println(d); // false
System.out.println(c); // c = 5, c++ 不会执行
位运算
public class Main {
public static void main(String[] args) {
A = 0011 1100
B = 0000 1101
A&B = 0000 1101
A|B = 0011 1101
A^B = 0011 0001 // 异或,不同为1,相同为0
~B = 1111 0010 // 取反
// 2*8如何运算最快int result = 2 << 3;
int result = 2 << 3;
System.out.println(result);
}
}
包管理
类似文件夹管理模式,引用其他类时需要导入包。
JavaDoc
没看太懂。查一下用 Intelli J 生成 JavaDoc 文档
流程控制
Scanner
方法
public class Demo{
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
String string = scanner.nextLine(); //可以有空格
String string = scanner.next();
String string = scanner.hasNextLine();
...
scanner.close();
}
}
Switch
Switch 支持 byte short int char。
从 Java7 开始支持字符串。内部具体实现方法为:string.hashCode();
数组
public class Demo{
public static void main(String[] args){
int[] numbers = {1,2,3,4,5}; // 数组定义方法
for(int x:numbers){ // 数组取出方法
...
}
}
}
输出质数
public class Demo{
public static void main(String[] args){
outer:for(int i=100;i<=150;i++){ // 100 到 150的质数
for(int j=2;j<i/2;j++){ // 为什么j<1/2
if(i % j == 0){
continue outer;
}
}
System.out.print(i+" ");
}
}
}
方法
就是函数。
单一出口。
但是方法需不需要用 static?
什么是值传递(Java)?什么是引用传递?
方法的重载
- 方法名字相同
- 参数类型必须不同
- 返回类型可以相同或不同
- 仅仅返回类型不同不足以算做重载
命令行传参
直接在编译和执行的时候传递参数
public class Demo{
public static void main(String[] args){
for(int i;i<=args.length;i++){
System.out.print("args["+i+"]:" + args[i] );
}
}
}
可变参数
Java 1.5 开始 Java 支持传递同类型的可变参数给一个方法,
方法声明中,在指定参数类型后加一个省略号
一个方法中只能指定一个可变参数,他必须是方法的最后一个参数,普通参数须在前面声明
?
递归
public class Demo{
public static void main(String[] args){
System.out.println(f(5));
public static int f(int n){ //简单写个阶乘
if(n==1){
return 1;
}else {
return n*f(n-1);
}
}
}
}
数组
数组声明创建
public class Demo{
public static void main(String[] args){
int[] nums; // 定义
// int nums[]; // c写法 不推荐
nums = new int[10]; // 创建数组
int[] nums2 = new int[10]; // 创建方法2
int[] nums3 = {1,2,3,4,5}; // 创建方法3 静态初始化
int[] nums4 = new int[4]; // 创建方法2 动态初始化
for(int i=0;i<nums.length;i++){
nums[i] = i+1; // 赋值
}
}
}
一旦创建,没法扩大
数组的传参
public class Demo{
public static void main(String[] args){
int[] nums2 = new int[10];
int[] reverse = reverse(nums2);
// System.out.println(reverse); // 这个输出不了,必须写个方法printArray();
printArray(reverse);
}
public static int[] reverse(int[] arrays){
int[] results = new int[arrays.length];
for(int i = 0,j=arrays.length-1;i < arrays.length;i++,j--){
results[j] = arrays[i];
}
return results;
}
public static void printArray(int[] arrays){
for (int num : arrays) { // for循环数组的快捷键:arrays.for()回车
System.out.print(num + " ");
}
}
}
多维数组
public class Demo{
public static void main(String[] args){
int[][] arrays = {
{1,2},{2,2,2},{3,3,3,4,1}};
}
}
Arrays 类
可以用一些方法操控数组。比如
public class Demo{
public static void main(String[] args){
int[] arrays = {3,3,3,4,1};
System.out.print(Arrays.toString(arrays));
Arrays.sort(arrays);
...
}
}
一些排序算法
冒泡排序(Bubble Sort)
- 原理:重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
- 时间复杂度:平均和最坏情况为 (O(n^2)),最好情况(已经有序)为 (O(n))。
- 空间复杂度:(O(1))。
- 稳定性:稳定。
选择排序(Selection Sort)
- 原理:在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
- 时间复杂度:平均、最坏和最好情况均为 (O(n^2))。
- 空间复杂度:(O(1))。
- 稳定性:不稳定。
插入排序(Insertion Sort)
- 原理:将未排序数据插入到已排序序列的合适位置。初始时,已排序序列仅包含第一个元素,然后依次将后续元素插入到已排序序列中。
- 时间复杂度:平均和最坏情况为 (O(n^2)),最好情况(已经有序)为 (O(n))。
- 空间复杂度:(O(1))。
- 稳定性:稳定。
希尔排序(Shell Sort)
- 原理:是插入排序的一种改进版本,也称为缩小增量排序。它通过将原始数据分成多个子序列来改善插入排序的性能,每个子序列的元素间隔逐渐缩小,最终变为 1,此时进行一次普通的插入排序。
- 时间复杂度:平均情况约为 (O(n^{1.3})),最坏情况为 (O(n^2))。
- 空间复杂度:(O(1))。
- 稳定性:不稳定。
归并排序(Merge Sort)
- 原理:采用分治法(Divide and Conquer),将一个数组分成两个子数组,分别对这两个子数组进行排序,然后将排好序的子数组合并成一个最终的有序数组。
- 时间复杂度:平均、最坏和最好情况均为 (O(n log n))。
- 空间复杂度:(O(n))。
- 稳定性:稳定。
快速排序(Quick Sort)
- 原理:同样采用分治法,选择一个基准值(pivot),将数组分为两部分,使得左边部分的元素都小于等于基准值,右边部分的元素都大于等于基准值,然后分别对左右两部分进行递归排序。
- 时间复杂度:平均情况为 (O(n log n)),最坏情况(数组已经有序)为 (O(n^2))。
- 空间复杂度:平均情况为 (O(log n)),最坏情况为 (O(n))。
- 稳定性:不稳定。
堆排序(Heap Sort)
- 原理:利用堆这种数据结构进行排序。首先将数组构建成一个最大堆(升序排序)或最小堆(降序排序),然后依次将堆顶元素与数组末尾元素交换,并调整堆结构,直到整个数组有序。
- 时间复杂度:平均、最坏和最好情况均为 (O(n log n))。
- 空间复杂度:(O(1))。
- 稳定性:不稳定。
非比较排序算法
计数排序(Counting Sort)
- 原理:假设输入的数据都是有确定范围的整数,统计每个整数出现的次数,然后根据统计结果将整数按顺序输出。
- 时间复杂度:(O(n + k)),其中 n 是数组的长度,k 是数据的范围。
- 空间复杂度:(O(k))。
- 稳定性:稳定。
桶排序(Bucket Sort)
- 原理:将数组元素分配到有限数量的桶中,每个桶再分别进行排序(可以使用其他排序算法),最后将所有桶中的元素按顺序合并。
- 时间复杂度:平均情况为 (O(n + k)),最坏情况为 (O(n^2)),其中 n 是数组的长度,k 是桶的数量。
- 空间复杂度:(O(n + k))。
- 稳定性:取决于桶内排序算法。
基数排序(Radix Sort)
- 原理:按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。
- 时间复杂度:(O(d(n + k))),其中 n 是数组的长度,k 是基数(通常为 10),d 是最大数的位数。
- 空间复杂度:(O(n + k))。
- 稳定性:稳定。
一些查找算法
顺序查找
- 原理:从数据结构的一端开始,逐个检查元素,直到找到目标元素或遍历完整个数据结构。
- 时间复杂度:平均情况为 (O(n)),最坏情况为 (O(n)),其中 n 是数据集合的元素个数。
- 空间复杂度:(O(1))。
- 适用场景:适用于数据量较小、数据无序的情况。
二分查找
- 原理:针对有序数组,每次将查找区间缩小一半。取中间元素与目标元素比较,若相等则找到;若中间元素大于目标元素,则在左半区间继续查找;若中间元素小于目标元素,则在右半区间继续查找。
- 时间复杂度:平均和最坏情况均为 (O(log n))。
- 空间复杂度:(O(1))。
- 适用场景:仅适用于有序数组,对于大规模的有序数据查找效率高。
插值查找
- 原理:类似于二分查找,但它不是简单地取中间位置,而是根据要查找的关键字与查找区间两端点关键字的大小关系,计算一个更接近目标的位置进行比较,从而更快地缩小查找范围。
- 时间复杂度:在均匀分布的情况下,平均时间复杂度为 (O(log log n)),但在最坏情况下可能退化为 (O(n))。
- 空间复杂度:(O(1))。
- 适用场景:适用于数据分布均匀的有序数组,当数据规模较大且分布均匀时,性能通常优于二分查找。
斐波那契查找
- 原理:利用斐波那契数列来确定查找的分割点。与二分查找类似,也是通过不断缩小查找区间来找到目标元素,但分割点的选择基于斐波那契数列的特性。
- 时间复杂度:平均情况和最坏情况均为 (O(log n)),比二分查找的性能略好。
- 空间复杂度:(O(1))。
- 适用场景:适用于有序数组,在某些情况下,对于特定的数据分布,斐波那契查找可能具有更好的性能。
哈希查找
- 原理:通过哈希函数将关键字映射到一个有限的哈希表中,然后直接在哈希表中查找目标元素。如果发生哈希冲突(不同关键字映射到相同位置),则通过特定的冲突解决策略来处理。
- 时间复杂度:平均情况下,查找操作的时间复杂度接近 (O(1)),但在最坏情况下,可能退化为 (O(n)),例如当所有元素都映射到同一个位置时。
- 空间复杂度:取决于哈希表的大小和装填因子,通常为 (O(n)),其中 n 是要存储的元素个数。
- 适用场景:适用于需要快速查找的情况,特别是对于大规模数据集合,只要哈希函数设计合理,哈希查找能够提供高效的查找性能。常用于数据库索引、缓存系统等场景。
二叉排序树查找
- 原理:二叉排序树是一种特殊的二叉树,左子树的所有节点值小于根节点值,右子树的所有节点值大于根节点值。查找时,从根节点开始,根据目标值与当前节点值的大小关系,决定向左子树或右子树继续查找,直到找到目标节点或遍历到叶子节点也未找到。
- 时间复杂度:平均情况为 (O(log n)),最坏情况(树退化为链表)为 (O(n))。
- 空间复杂度:(O(n)),用于存储二叉排序树的节点。
- 适用场景:适用于动态数据集合的查找,不仅可以快速查找元素,还方便进行插入和删除操作。
平衡二叉树查找
- 原理:在二叉排序树的基础上,通过调整树的结构,使其保持平衡,避免出现树的高度过高导致查找性能下降的情况。常见的平衡二叉树有 AVL 树、红黑树等。
- 时间复杂度:平均和最坏情况均为 (O(log n))。
- 空间复杂度:(O(n))。
- 适用场景:适用于对动态数据集合进行高效的查找、插入和删除操作。在需要频繁更新数据且对查找性能要求较高的场景中广泛应用,如操作系统的进程调度、数据库的索引结构等。
稀疏数组
压缩与还原?
面向对象
方法的调用
当在一个类中调用另个一类的方法时,如果是静态方法,那么可以直接类名.方法调用。非静态方法需要用到实例化这个类。
静态方法如果想调用非静态方法,也必须实例化这个类。
继承中的 Super This (重载)
Super:
- Super 调用弗雷德构造方法必须在构造方法中的第一个
- Super 必须只能出现在子类的方法或者构造方法中
- Super 和 this 不能同时调用构造方法
This:
-
代表的对象不同: this 本身调用这个对象
super:代表父类对象的应用
-
前提
this 没有继承也可以使用
super 只有在继承条件下才可以使用
-
构造方法
this 本类的构造
super 父类的构造
重写
public class B {
public static void test() {
System.out.pringtln("B->test");
}
}
public class A extends B{
@Override
public static void test() {
System.out.pringtln("A->test");
}
}
public class APP {
public static void main(String[] args){
A a = new A();
a.test();
B b = new A();
B.test();
// 如果是静态方法,那么输出是A->test和 B->test;
// 如果是非静态方法,那么输出就都是A->test;
// 子类重写了父类的方法 只和非静态方法有关,而且必须是public
// 1. 方法名必须相同
// 2、参数列表必须相同
// 3. 修饰符 范围可以扩大 public > Protected > default > private
// 4. 抛出的异常 范围可以被缩小,但是不能扩大 ClassNotfoundException-->Exception
// 为什么要重写:父类的功能,子类不一定需要或满足
//
}
}
多态
Object 是所有类的父类
多态注意事项:
- 多态是方法的多态,属性是没有多态的
- 父类和子类,必须有联系,否则类型转换异常
- 存在条件:继承关系,方法需要重写,父类引用指向子类对象
public class Person {
public static void test() {
System.out.pringtln("B->test");
}
}
public class Student extends Person {
@Override
public static void test() {
System.out.pringtln("A->test");
}
}
public class APP {
public static void main(String[] args){
// 可以指向的引用类型可以不确定:父类的引用指向子类
Student s1 = new Student();
Person s2 = new Student();
Object s3 = new Student();
// 对象能执行哪些方法,主要看左边的类型,和右边的关系不大,也就是不能执行右边类里的独有的方法
s2.test(); //子类重写了父类的方法,执行子类的方法
((Studeng) s2).test(); //父类可以强制转换为子类
}
}
instanceof
用来查看是否能转换为子类: Objective instanceof Student; true
Person instanceof Student; true
Static
public class Person {
public static int age;
private double score;
// 静态变量,被所有对象共享,估计方法也是
// 第二个执行 赋初值
{
匿名代码块
}
// 第一个执行 只执行一次
static {
静态代码块
}
// 第三个执行
public Person(){
构造方法
}
public static void test() {
}
}
Final
Final 修士的类没有子类。(断子绝孙类)
抽象类
public abstract class Action {
// abstract 抽象类 extends:只能单继承 但是interface: 接口可以多继承
public void dosomething(); // 可以不写函数内容,但是创建子类时必须重写这个方法
// 不能new抽象类,只能继承他
// 抽象类可以写普通方法
// 抽象方法必须在抽象类中
}
接口
public interface Userservice{
void add();
void delete();
void get();
}
public interface Timer{
void timer();
}
public class User implements Userservice, Timer{
void add();
void delete();
void get();
void timer();
// 可以继承多个接口,但是必须全部都重写
// 接口也不能实例化
}
内部类
public class Outer{
private int id;
public void out(){
sout("outer");
}
public void method(){
class Inner2{
// 局部内部类
public void in(){
sout("inner");
}
}
}
public class Inner{
private int id;
public void in(){
sout("inner");
}
public void getID(){
sout(id) //内部类可以获得外部类的属性
}
}
}
public class app{
psvm(){
Outer outer = new Outer();
Outer.Inner inner = new Inner();
inner.in(); // inner
}
}
异常处理
try {
if(b==0) throws Exception{ //方法体上用throws
throw new Exception(); // 主动抛出异常,一般用于方法
}
}catch() { // throwable 最高的异常类型 其次是 Error Exception 两大类
}finally{
}
自定义异常类?
//自定义异常类
class myException extends Exception{
private int detail;
public myException(int a){
this.detail = a;
}
@Override
public String toString(){
return "MyException{"+detail+"}";
}
}
public class Test{
static void test(int a) throws MyException{
if(a>0){
throw new MyException(a); //抛出
}
System.out.print("ok");
}
public static void main(String[] args){
try{
test(11);
}catch (MyException e){
System.out.println("MyException=>"+e)
}
}
}
Java SE 总结
多线程
注解和反射
Annotation
检查语法
内置注解
@Override @Deprecated @SupperessWarnings
元注解
注解其他注解
@Target @Retention @Documented @Inherited
自定义注解
反射
Reflection
类加载内存分析
??
类的初始化
??
类加载器
??
双亲委派机制
获取类的运行时结构
获取属性
获取方法
获取构造器
通过反射动态创建对象
获取泛型
反射操作注解
ORM Object Relation Mapping
Spring
优点
控制反转 IOC 面向切面编程 AOP
不过已经成了配置地狱
IOC
在之前的业务中,用户的需求可能会影响原来的代码,需要根据用户的需求修改原来的代码,如果程序代码量十分大,修改一次的成本价非常昂贵
private UserDao userdao;
// 利用set实现动态注入
public void setUserDao(UserDao userdao) {
this.userDao = userDao;
}