Java与Spring学习

Java

Views:  times Posted by Jiawei Bai on May 25, 2025

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

三高问题:高可用,高性能,高并发

截屏2025-05-06 14.40.08

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

强制转换自动转换

  1. 不能对布尔值转换
  2. 不能把对象类型转换为不相干的类型
  3. 高容量转低容量-> 强制转换,可能会内存溢出
  4. 低容量转高容量->自动转换,可能会精度问题
//数据溢出
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);
  }

}

变量命名原则

截屏2025-05-06 18.09.44

自增运算符

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)?什么是引用传递?

方法的重载

  1. 方法名字相同
  2. 参数类型必须不同
  3. 返回类型可以相同或不同
  4. 仅仅返回类型不同不足以算做重载

命令行传参

直接在编译和执行的时候传递参数

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:

  1. Super 调用弗雷德构造方法必须在构造方法中的第一个
  2. Super 必须只能出现在子类的方法或者构造方法中
  3. Super 和 this 不能同时调用构造方法

This:

  1. 代表的对象不同: this 本身调用这个对象

    super:代表父类对象的应用

  2. 前提

    this 没有继承也可以使用

    super 只有在继承条件下才可以使用

  3. 构造方法

    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 是所有类的父类

多态注意事项:

  1. 多态是方法的多态,属性是没有多态的
  2. 父类和子类,必须有联系,否则类型转换异常
  3. 存在条件:继承关系,方法需要重写,父类引用指向子类对象
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抽象类,只能继承他
  // 抽象类可以写普通方法
  // 抽象方法必须在抽象类中

}

接口

截屏2025-05-11 22.17.43

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
  }
}

异常处理

截屏2025-05-11 23.02.19

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

截屏2025-05-14 21.43.47

自定义注解

截屏2025-05-14 21.53.03

反射

Reflection

截屏2025-05-14 22.03.58

截屏2025-05-14 22.10.56

截屏2025-05-14 22.13.17

类加载内存分析

??

类的初始化

??

类加载器

??截屏2025-05-14 23.36.27

截屏2025-05-14 23.35.44

双亲委派机制

获取类的运行时结构

获取属性

截屏2025-05-14 23.53.01

获取方法

截屏2025-05-14 23.52.20

获取构造器

截屏2025-05-14 23.51.52

通过反射动态创建对象

截屏2025-05-14 23.58.59

获取泛型

截屏2025-05-15 0.07.56

截屏2025-05-15 0.08.52

反射操作注解

ORM Object Relation Mapping

截屏2025-05-15 0.11.13

截屏2025-05-15 0.15.55

截屏2025-05-15 0.16.42

截屏2025-05-15 0.17.21

Spring

优点

控制反转 IOC 面向切面编程 AOP

不过已经成了配置地狱

IOC

在之前的业务中,用户的需求可能会影响原来的代码,需要根据用户的需求修改原来的代码,如果程序代码量十分大,修改一次的成本价非常昂贵

private UserDao userdao;
// 利用set实现动态注入
public void setUserDao(UserDao userdao) {
  this.userDao = userDao;
}