Java后端研发

一、基础知识

1.1、Java特点

1.面向对象

面向对象(Object-Oriented Programming,OOP)就是Java语言的基础,也是Java语言的重要特性。面向对象:生活中的一切事物都可以被称之为对象,生活中随处可见的事物就是一个对象,我们可以将这些事物的状态特征(属性)以及行为特征(方法)提取并出来,并以固定的形式表示。

2.简单好用

Java语言是由C和C++演变而来的,它省略了C语言中所有的难以理解、容易混淆的特性(比如指针),变得更加严谨、简洁、易使用。

3.健壮性

Java的安全检查机制,将许多程序中的错误扼杀在摇蓝之中。 另外,在Java语言中还具备了许多保证程序稳定、健壮的特性(强类型机制、异常处理、垃圾的自动收集等),有效地减少了错误,使得Java应用程序更加健壮。

4.安全性

Java通常被用在网络环境中,为此,Java提供了一个安全机制以防恶意代码的攻击,从而可以提高系统的安全性。

5.平台无关性

Java平台无关性由Java 虚拟机(JVM)实现,Java软件可以不受计算机硬件和操作系统的约束而在任意计算机环境下正常运行。

6.支持多线程

在C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持。多线程机制使应用程序在同一时间并行执行多项任务,该机制使得程序能够具有更好的交互性、实时性。

7.分布式(支持网络编程)

Java语言具有强大的、易于使用的网络能力,非常适合开发分布式计算的程序。java中提供了网络应用编程接口(java.net),使得我们可以通过URL、Socket等远程访问对象。

8.编译与解释共存

Java 是编译与解释共存的语言,它的执行过程中同时包含了编译和解释两个阶段。

编译阶段(Compile Time):Java 源代码首先经过编译器编译成字节码(bytecode),字节码是一种与平台无关的中间代码。这个阶段将 Java 源代码翻译成字节码,但并没有直接翻译成底层机器代码,因此称为编译阶段。

解释阶段(Runtime):Java 字节码由 JVM 解释执行。在运行时,JVM 将字节码翻译成特定平台的机器码,并执行这些机器码来完成程序的运行。这个过程是解释阶段,因为字节码在运行时才被翻译成机器码。

1.2、数据类型

1.3、数组

//创建一维数组
int arr[];    //声明
arr = new int[5];  //分配内存
        
int arr[] = new int[5];  //创建2
//初始化一维数组
int arr1[] = new int[]{1, 2, 3, 4, 5}; //初始化方式1
int arr2[] = {1, 2, 3, 4, 5}; //初始化方式2
int[] arr={9,3,6,0};

// 1、遍历数组:for循环、foreach语句
for (int num : arr) {   // int是arr的元素类型,num是随意起的变量名,arr是要遍历的数组
      System.out.println(num);
}
        
/**2、填充替换数组元素:
Arrays.fill(int[] a,int value)---将指定int值分配给int型数组的每个元素
fill(int[] a,int fromIndex,int toIndex,int value)
将指定的int值分配给int型数组指定范围中的每个元素。填充的范围从索引fromIndex(包括)到索引    toIndex(不包括)
变更数=toIndex-fromIndex **/
Arrays.fill(arr,1,3,999); // 结果为9,999,999,0
        
//3、对数组进行排序:Arrays.sort(object)---升序排序
Arrays.sort(arr);

/**4、复制数组:Array.copyOf(arr,int newLength)--复制数组至指定长度
Array.copyOfRange(arr,int fromIndex,int toIndex)---将指定数组的指定长度复制到一个新数组中**/
int[] temp=Arrays.copyOf(arr,6); //arr不足6位会追加0给temp

/**5、数组查询:
binarySearch(Object[],Object key)---如果key包含在数组中,则返回搜索值的索引,否则返回-1或‘-’(插入点)
插入点:是要插入数组的那一点,即第一个大于此键的元素索引)
binarySearch(Object[]a,int fromIndex,int toIndex,Object key)---在指定的范围内检索某一元素,指定范围应小于数组长度**/
int[] a1={9,6,8}; // int[] a2={1,6,8} 时返回-1
System.out.println(Arrays.binarySearch(a1,5)); //5<9,所以返回-1

1.4、字符串

1.4.1、String

String类在lang包里。被final修饰,不可继承。

不可变性(Immutability):String 对象一旦创建,其值就不能被修改。这意味着一旦创建了一个 String 对象,它的内容将永远不会改变,直到这个对象被销毁。如果需要修改字符串的内容,将会创建一个新的 String 对象。

安全性(Security):不可变性使得字符串在多线程环境下更安全,因为它们的值无法被修改,多个线程可以安全地共享同一个 String 对象,不需要额外的同步操作。

不可变性有助于提高字符串的性能,因为它可以进行一些优化,例如在编译时可以合并相同的字符串字面值,减少内存占用。但也由于不可变性,对 String 进行连接、替换等操作时会频繁地创建新的字符串对象,这可能会导致内存开销和性能损失。

//1.直接定义一个字符串
String str="java";
 
//2.通过new String对象
String str2 = new String("java");

//3.通过字符数组进行构造
char[] chars = {'n','f','s'};
String str3 = new String(chars);

// 1、字符串长度str.length()
System.out.println(str.length()); //4

// 2、查找字符串,indexOf()、lastIndexOf()搜索指定条件的字符串或字符
// 通常用于查找对象在列表中的索引,而不是用于查找基本数据类型
System.out.println(str.indexOf("v")); //2

// 3、charAt()获取指定索引位置的字符
str.charAt(0); // j

/** 4、获取子字符串: substring(int beginIndex)从索引位置到结尾
substring(int beginIndex,int endIndex)不包括结束位置 **/
str.substring(1,3); //av

// 5、去除空格:str.trim()去除首部和尾部空格
String s=" a bc ";
System.out.println(s.trim()); //a bc

// 6、字符串替换:str.replace(char oldChar,char newChar)
str.replace('j','c'); //cava

// 7、判断字符串的开头结尾:startsWith(),endsWith()返回boolean类型
str.startsWith("j"); //true

// 8、判断字符串是否相等:equals()--区分大小写,equalsIgnoreCase()--忽略大小写
str.equals("JAVA"); //false

// 9、按字典顺序比较两个字符串:compareTo()--基于字符串中首字符的Unicode值,相等取0,大于取正,小于取负
"bas".compareTo("App"); //b>a>Z; a-Z=32

// 10、字母大小写转换:toLowerCase()-转为小写,toUpperCase()--转为大写
str.toUpperCase();

/** 11、字符串分割:split()--按指定分割字符或字符串进行分割,返回数组
split(String sign)--根据给定的分隔符对字符串拆分
split(String sign,int limit)--限定拆分的次数 **/
String[] s1=str.split("v"); //s1={"ja","a"}

/** 12、格式化字符串:
str.format(String format,Object...args)---format格式字符串,args--引用的参数
format(Local l,String format,Object...args)---l格式化过程中要应用的语言环境**/
String name = "Alice";
int age = 30;
String mes = String.format("Hello, my name is %s and I am %d years old.", name, age);

// 13、数字字符串变int
String str="1246";
int a=Integer.parseInt(str);

因为字符串的使用率很高,为了减少内存开销,避免字符串的重复创建,JVM为其维护了一块特殊的内存空间——字符串池String Pool。对于 String 类型的对象,== 运算符比较的是两个 String 对象的引用是否相同,而equals() 方法比较的是两个 String 对象的内容是否相同。

String str1 = "hello"; //先在池中寻找hello,没有就创建
String str2 = "hello"; //在池中寻找hello,找到了就指向hello的地址
String str3 = new String("hello");
/** new 关键字会强制在堆内存中创建一个新的对象,
而不管字符串常量池中是否已经存在相同内容的字符串。
如果字符串常量池中已经存在 "hello" 这个字符串,
那么新创建的字符串对象不会添加到字符串常量池中。**/

System.out.println(str1 == str2);  // 输出 true,因为 str1 和 str2 都指向常量池中的同一个字符串对象
System.out.println(str1 == str3);  // 输出 false,因为 str1 和 str3 指向不同的内存地址
System.out.println(str1.equals(str2));  // 输出 true,因为两个字符串的内容相同
System.out.println(str1.equals(str3));  // 输出 true,因为两个字符串的内容相同

1.4.2、StringBuffer

1.StringBuffer的直接父类是AbstractStringBuilder,有final修饰,不能被继承

2.StringBuffer实现了Serializable,即StringBuffer的对象可以串行化

3.在父类中 AbstractStringBuilder 有属性 char[] value,不是final,使用字符数组保存字符串

4.StringBuffer存放在 char[] value ,所有的变化不用每次创建新对象,所以更换地址效率高于String

可变性(Mutability):与 String 不同,StringBuffer 对象的内容是可以被修改的。可以通过调用 append()insert()delete()replace() 等方法来修改 StringBuffer 中的字符序列。

线程安全性:StringBuffer 是线程安全的(通过在内部对共享数据进行同步控制来实现),这意味着它的方法在多线程环境下可以安全地使用,不会出现竞态条件或其他并发问题。这是通过使用同步机制来实现的,但也因此导致了一些性能开销

String a=null;
System.out.println(a.length()); // 报错,因为a是空的
StringBuffer string = new StringBuffer();
System.out.println(string.length()); // 0
string.append(a);
// 底层调用的是一个AbstractStringBuilder的appendNull()方法 如果为空会赋值null字符
System.out.println(string.length()); // 4,此时内容为”null“,非空
string.append("abc");
System.out.println(string); // nullabc
System.out.println(string.reverse()); // 翻转,cballun

1.4.3、StringBuilder 

与 StringBuffer 类似,StringBuilder是 Java 中用于处理可变字符串的类,但是不保证线程安全。与 StringBuffer 不同, StringBuilder的方法没有被 synchronized 修饰,因此在多线程环境下使用时需要自行确保线程安全性。

StringBuilder和String的转换(StringBuffer类似):

// String 转为 StringBuilder
// 1.构造方法
String s="hello";
StringBuilder sb = new StringBuilder(s); 

// 2.拼接方法
StringBuilder sb2 = new StringBuilder();
sb2.append("123");

// StringBuilder 转为 String
//通过StringBuilder对象调用toString()方法
String str = sb.toString();

1.4.4、三者区别

(1)String内容不可以修改,而StringBuffer与StringBuilder,提供了一系列插入、追加、 改变字符串里的字符序列的方法,并且修改不产生新的对象,而是在原对象的基础上修改

(2)三者效率

         StringBuilder > StringBuffer > String

(3)安全性和操作数据量

         如果要操作的数量比较小,应优先使用String类;

         如果是在单线程下操作大量数据,应优先使用StringBuilder类;

         如果是在多线程下操作大量数据,应优先使用StringBuffer类。

(4)StringBuffer使用了缓存区,StringBuilder没有使用缓存区,所以没有修改数据的情况下,多次调用StringBuffer的toString方法获取的字符串是共享底层的字符数组的。而StringBuilder不是共享底层数组的,每次都生成了新的字符数组。

1.5、类 

类是同一类事物的统称,如果将现实世界中的一个事物抽象成对象,类就是 这类对象 的统称。

类是封装对象的属性和行为的载体,具有相同属性和行为的一类实体被称为类。

Java语言中,类中对象的行为是以方法的形式定义的,对象的属性是以成员变量的形式定义的,而类中包括对象的属性和方法。

类实质就是封装对象属性和行为的载体,而对象则是类抽象出来的实例。

1.5.1、向上转型

若A是B的父类,当把子类对象赋值给父类对象的引用时,称父类的引用是子类对象的上转型对象。上转型就是把子类对象转为父类对象。上转型对象是一种特殊类型(功能受限)的子类对象,它强调父类的特性而忽略子类的特性。

Animal tom = new Dog("Tom"); // 狗类上转为动物类

1.子类重写一个方法后,上转型对象调用的方法必须是重写后的

2. 当子类继承或隐藏了某个成员变量或方法,此时上转型对象可以访问(上转型对象只能访问从父类继承而来的属性、方法以及 实例化对象时的类中定义的覆盖方法)

3.上转型对象不能操作子类新增的成员变量和新的方法,除非……

Animal animal=new Dog();
animal.show();
((Dog)animal).shows(); // 除非将上转型对象转换为子类对象

1.5.2、面向对象的三大特征

封装Encapsulation:指的是将对象的属性和方法封装起来,其载体就是类,类通常将对象的实现细节隐藏起来(private),然后通过一些公用方法来暴露该对象的功能;(避免外部操作对内部数据的影响,提高程序的可维护性)。

继承Extends:是面向对象实现软件复用的重要手段,当子类继承父类后,子类作为一种特殊的父类,将直接获得父类的属性和方法。Java支持单继承、多层继承,但不支持多继承。

多态Polymorphic:指的是子类对象可以直接赋给父类变量,但运行时依然表现出子类的行为特征,这意味着同一个类型的对象在执行同一个方法时,可能表现出多种行为特征。(多态的前提:继承、重写、向上转型)

1.5.3、权限修饰符

在修饰成员变量/成员方法时,有四种访问权限(从大到小):

public:该成员可以被任意包下,任意类的成员进行访问。

protected:该成员可以被该类内部成员访问,也可以被同一包下其他的类访问,还可以被它的子类访问;

default(不写):该成员可以被该类内部成员访问,也可以被同一包下其他的类访问;

private:该成员只能被该类内部成员访问;

在修饰类时,只有两种访问权限:

default:该类可以被同一包下其他的类访问;

public:该类可以被任意包下,任意的类所访问。

1.5.4、类的构造

构造器名必须和类名一致。

构造器没有返回值类型。

任何类都含有构造器。

如果没有显式地定义类的构造器,则系统会为该类提供一个默认的无参的构造器。

一旦在类中显式地定义了构造器,系统就不会再为这个类提供默认的构造器了,需要自己定义。

1.5.5、成员变量和局部变量

成员变量(实例变量)和局部变量是Java中的两种不同类型的变量,它们具有不同的作用域和生命周期。

  1. 成员变量

    • 成员变量是定义在类中,方法之外的变量,作用域是整个类。
    • 它们属于对象的一部分,每个对象都有自己的一组成员变量的副本。
    • 成员变量可以被类中的任何方法访问和修改。
    • 成员变量的值会在对象创建时分配内存,并在对象被销毁时释放内存。
public class MyClass {
    // 成员变量
    private int memberVariable;

    // 构造方法
    public MyClass(int memberVariable) {
        this.memberVariable = memberVariable;
    }

    // 方法
    public void setMemberVariable(int newValue) {
        this.memberVariable = newValue;
    }
}
  1. 局部变量

    • 局部变量是定义在方法、代码块或构造方法中的变量。
    • 它们只在声明它们的方法、代码块或构造方法中可见,超出该范围就不能被访问。
    • 局部变量必须在声明后才能被使用,并且它们的作用域仅限于声明它们的代码块内。
    • 局部变量的生命周期从声明开始,到其所属的代码块执行结束或者变量被销毁时结束。
public class Example {
    public void method() {
        // 局部变量
        int localVar = 10;
        System.out.println(localVar);
    }
}

1.5.6、重载和重写

重载overloading:同一个类中用同一个方法名定义多个参数不同的方法(参数不同指参数个数和参数类型不同,与顺序无关)

重写overwriting(覆盖):子类中创建的某个方法与父类中的某个方法同名、同参数、同返回类型,只是方法体不同(以实现不同于父类的功能)。注意:重写时方法的权限修饰符一定不小于父类的方法;重写时不能抛出新的检查异常/不能声明比父类更宽泛的检查型异常

1.5.7、抽象类

抽象类(Abstract Class)是Java中一种特殊的类,它不能被实例化,只能被继承。抽象类通常用于定义具有共同特征和行为的一组类的模板,它可以包含成员变量、抽象方法和非抽象方法。
1. 不能被实例化
   - 抽象类不能直接创建对象实例,因为它们存在未实现的抽象方法,所以需要子类去实现这些抽象方法后才能创建子类的对象。

2. 包含抽象方法
   - 抽象类一定包含抽象方法,这些方法在抽象类中只是声明而不包含具体的实现(没有大括号),必须以";"结尾。
   - 子类继承抽象类时必须实现(重写)抽象类中的所有抽象方法,除非子类也是一个抽象类。

3. 可以包含非抽象方法
   - 抽象类中不一定要包含抽象方法,它可以包含普通的非抽象方法,这些方法在子类中可以直接继承或者重写。

4. 用于继承和多态
   - 抽象类作为一种模板,一般作为父类,用于被子类继承,子类通过实现抽象类中的抽象方法(也可以继承非抽象方法)来体现自己的特性和行为。
   - 通过抽象类,可以实现面向对象编程中的多态性,即同样的方法调用可以根据具体的子类对象表现出不同的行为。

注意:构造方法、静态方法、私有方法不能被声明为抽象的方法,但可以存在于抽象类。

// 抽象类
abstract class Shape {
    // 抽象方法,表示计算图形面积
    public abstract double calculateArea();

    // 非抽象方法
    public void display() {
        System.out.println("Displaying shape");
    }
}

// 子类1:圆形
class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

// 子类2:矩形
class Rectangle extends Shape {
    private double length;
    private double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public double calculateArea() {
        return length * width;
    }
}

public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle(5.0);
        Shape rectangle = new Rectangle(4.0, 6.0);

        System.out.println("Area of circle: " + circle.calculateArea());
        System.out.println("Area of rectangle: " + rectangle.calculateArea());

        circle.display();
        rectangle.display();
    }
}

在上面的示例中:
- `Shape` 是抽象类,它包含一个抽象方法 `calculateArea()` 和一个非抽象方法 `display()`。
- `Circle` 和 `Rectangle` 分别是 `Shape` 的子类,它们都必须实现 `calculateArea()` 方法。
- 在 `Main` 类中,我们创建了 `Circle` 和 `Rectangle` 的对象,并调用它们的 `calculateArea()` 方法和继承的 `display()` 方法。

抽象类的使用有助于提高代码的灵活性和可扩展性,因为它们提供了一个通用的接口和行为模板,同时也强制了子类实现特定的方法,保证了程序的正确性和一致性。

1.5.8、接口

接口(Interface)是Java中一种抽象类型,它定义了一组方法的规范,但没有提供这些方法的具体实现。接口可以看作是一种契约,规定了类应该具有哪些行为,而具体的实现规则由实现接口的类来完成。在Java中,一个类可以实现(implements)一个或多个接口,从而表明该类将提供接口中定义的所有方法的具体实现。(从某些方面来说,接口是特殊的抽象类)

  1. 只包含方法声明

    接口中只包含方法的声明,而不包含方法的实现。方法默认是公共的(public),不需要使用关键字 public 声明。
  2. 不能包含成员变量

    接口中不能包含成员变量,只能包含常量(使用 final static 修饰的变量)。
  3. 类实现接口

    类通过关键字implements来实现接口,实现接口的类必须提供接口中定义的所有方法的具体实现。一个类可以实现多个接口,使用逗号分隔。
  4. 多态性

    接口可以实现多态性,即一个接口的引用可以指向实现了该接口的任何类的对象。
  5. 用于实现类之间的松耦合

    接口定义了类之间的契约,提供了一种松耦合的方式来组织和设计代码,使得代码更易于维护和扩展。
// 定义接口
interface Animal {
    final int AGE=10;
    void eat();
    void sleep();
}

// 实现接口的某个类
class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }

    @Override
    public void sleep() {
        System.out.println("Dog is sleeping");
    }
}

在上面的示例中:

  • Animal是一个接口,它定义了eat() 和sleep()方法的规范。
  • Dog是实现了Animal接口的类,它必须提供eat() 和sleep()方法的具体实现。

接口的使用可以使代码更灵活,降低类之间的耦合度,同时也使得代码更易于扩展和维护。通过接口,可以实现多态性,提高代码的可重用性和可扩展性。

1.6、关键字

1.6.1、final

意思是最终的、不可修改的,最见不得变化 ,用来修饰类、方法和变量

修饰类:表示该类不能被继承,即为最终类。这样的类不能有子类。final 类中的所有成员方法都会被隐式的指定为 final 方法。

final class MyClass {
    // 类的定义
}

修饰方法:表示该方法不能被子类重写,即为最终方法。

class MyClass {
    final void myMethod() {
        // 方法的定义
    }
}

修饰变量:表示该变量只能被赋值一次,即为常量。一旦赋值后,就不能再修改其值。对于基本数据类型,这意味着其值不能改变;对于引用类型变量,意味着在对其初始化之后便不能让其指向另一个对象,但是其状态(如果是可变的对象)可以改变。

final int myConstant = 10; // 声明一个常量
final MyClass obj = new MyClass(); // 声明一个引用类型的常量

1.6.2、static

用于声明静态成员

静态变量(类变量):被所有类的实例共享,在类加载时被初始化,在整个程序的生命周期内只有一份拷贝。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。static成员变量的初始化顺序按照定义的顺序进行初始化。

class MyClass {
    static int count = 0; // 声明一个静态变量
}

静态方法:不依赖于类的实例,可以直接通过类名调用(对于静态方法来说,是没有this的),常用于工具类或者实现特定功能的方法。因此,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。

class MyClass {
    static void myStaticMethod() {
        // 方法的定义
    }
}

静态代码块:在类加载时执行,一般用于为类的工作做一些初始化工作,如初始化静态变量或执行静态方法。一个类中可以有许多静态初始化块,并且它们可以出现在类体的任何地方。运行时系统会保证静态初始化块会按照它们在源代码中出现的顺序被调用。static块可以用来优化程序性能:因为它只会在类加载的时候执行一次。

class MyClass {
    static {
        // 静态代码块的内容
    }
}

静态成员属于类而不是实例,可以通过类名直接访问,不需要创建类的对象。通常情况下,静态成员被用来表示类级别的特性或者共享的资源。

1.6.3、this

一种特殊的引用,指向当前对象

在构造方法中引用当前对象:当使用this关键字作为构造方法的第一句语句时,表示调用当前类的其他构造方法,this()必须要书写在第一行。

class MyClass {
    private int value;

    // 构造方法
    public MyClass() {
        this(0); // 调用另一个构造方法
    }

    public MyClass(int value) {
        this.value = value;
    }
}

在方法中引用当前对象的成员变量或方法:通过 this 关键字可以在方法中访问当前对象的成员变量或方法。

class MyClass {
    private int value;

    // 方法
    public void setValue(int value) {
        this.value = value; // 使用 this 关键字访问成员变量
    }

    public int getValue() {
        return this.value; // 使用 this 关键字返回成员变量值
    }
}

在构造方法中返回当前对象的引用:在构造方法中可以使用 this 返回当前对象的引用。

class MyClass {
    private int value;

    // 构造方法
    public MyClass setValue(int value) {
        this.value = value;
        return this; // 返回当前对象的引用
    }
}

 局部变量和成员变量命名冲突时:可以通过this.成员变量名的方式区分成员变量和局部变量。

1.6.4、super

用于引用父类的成员

调用父类的构造方法:可以使用 super 关键字调用父类的构造方法,以在子类的构造方法中初始化父类的部分。每一个子类的构造方法在没有显示调用super()系统都会提供一个默认的super(),super()必须是构造器的第一条语句。(如果同时有this和super,则super在前)

class ParentClass {
    ParentClass(int value) {
        // 父类构造方法的定义
    }
}

class ChildClass extends ParentClass {
    ChildClass(int value) {
        super(value); // 调用父类的构造方法
    }
}

访问父类的成员变量或方法:通过 super 关键字可以在子类中访问父类的成员变量或方法(当子类的成员变量和父类的重名时,子类继承的该变量会隐藏,因此需要super调用)。

class ParentClass {
    int parentValue;

    void parentMethod() {
        // 父类方法的定义
    }
}

class ChildClass extends ParentClass {
    void childMethod() {
        int value = super.parentValue; // 访问父类的成员变量
        super.parentMethod(); // 调用父类的方法
    }
}

在方法重写时调用父类的方法:在子类中重写父类的方法时,可以使用 super 关键字调用父类的方法。

class ParentClass {
    void parentMethod() {
        // 父类方法的定义
    }
}

class ChildClass extends ParentClass {
    @Override
    void parentMethod() {
        super.parentMethod(); // 调用父类的方法
        // 子类方法的定义
    }
}

1.6.5、break

用于终止循环或者 switch 语句的执行。在循环中,break 语句用于跳出当前循环体。在 switch 语句中,break 用于结束 case 分支的执行,避免进入下一个 case 分支或者 default 分支。若无break,会继续执行下一个case分支。

for (int i = 0; i < 10; i++) {
    System.out.println(i);
    if (i == 5) {
        break; // 当 i 等于 5 时终止循环
    }
}
int day = 3;
switch (day) {
    case 1:
        System.out.println("Monday");
        break;
    case 2:
        System.out.println("Tuesday");
        break;
    case 3:
        System.out.println("Wednesday");
        break; // 结束当前 case 分支的执行
    default:
        System.out.println("Other day");
}

1.6.6、continue

用于结束当前循环的迭代,跳过本次循环中剩余的代码,直接进入下一次循环迭代。

for (int i = 0; i < 5; i++) {
    if (i == 2) {
        continue; // 当 i 等于 2 时跳过本次循环的剩余代码,直接进入下一次循环
    }
    System.out.println(i); // 不打印 2,但会打印 3
}
outerloop:
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (i == 1 && j == 1) {
            continue outerloop; // 跳过外部循环的下一次迭代
        }
        System.out.println("i=" + i + ", j=" + j);
    }
}

二、进阶知识 

2.1、异常

2.2、集合

集合框架:用来代表和操纵集合的统一架构。包括:

1. 接口:代表集合的抽象数据类型。接口允许集合独立操纵其代表的细节。接口通常形成一个层次

2. 实现类:集合接口的具体实现。本质上是可重复使用的数据结构

3. 算法:实现集合接口的对象里的方法执行的一些有用的计算,如搜索和排序

所有的集合接口和集合类都在java.util包下

2.2.1、集合接口

Collection:最基本的集合接口,储存无序不唯一的数据。单列集合

List:实现了Collection接口,储存有序不唯一的数据

Set:实现了Collection接口,储存无序唯一的数据

Map:以键值对的形式存储数据,以键取值,键不能重复,值可以重复。双列集合

注意:Collection和Collections

Collection是一个接口,它代表了一组对象的集合。Collection接口继承自Iterable接口,定义了一些基本的操作集合元素的方法,如添加、删除、遍历等。

Collections是一个工具类,它提供了一系列静态方法用于操作集合和Map。Collections类包含了各种对集合进行排序、查找、填充等操作的方法,例如sort()、binarySearch()、reverse()等。

2.2.2、List集合

List接口常见实现类有ArrayList、LinkedList、Vector

ArrayList:继承于AbstractList;实现了RandomAccess,可对元素快速访问;实现了Serializable,说明ArrayList可被序列化;实现了Cloneable,可被复制;允许null存在;不是线程安全的,线程安全的替代类如CopyOnWriteArrayList。

LinkedList:以双向链表存储;最初这个链表中没有任何元素,first和last引用都是null

Vector: 底层也是一个数组;扩容后是原来2倍;所有的方法都是线程同步的,有synchronized修饰,是线程安全的

2.2.3、Set集合

Set集合中的元素不按特定方式排序,只是简单的加入集合;Set中不能包含重复的元素,并且最多允许包含一个null

去重原理:哈希值。哈希值就是JDK根据对象地址或者字符串或者数值,通过自己内部的计算出来的一个整数类型数据。同一个对象多次调用hashCode()方法得到的哈希值是一样的;通常情况下,不同的对象的哈希值不一样

HashSet:查找某个对象时首先使用hashCode()方法计算出这个对象的哈希值,然后根据哈希值到相应的存储区域用equals()方法查找,从而提高了效率

TreeSet:底层实际上是一个TreeMap。同时实现了Set和SortSet。SortSet和Set的子接口,实现了对集合的自然排序(升序)。TreeSet只能对实现了Comparable的类实例进行排序,因为Comparable接口中有一个compareTo(Object o)方法用于比较两个对象的大小

2.2.4、Map集合

Map是一种键值对(key/value)集合,Map集合的每一个元素都包含一个键对象和一个值对象。其中,键不允许重复,而值可以,并且值对象也可以是Map类型。键、值都是引用数据类型。Map和Collection没有继承关系

HashMap:key部分的元素其实放到了HashSet集合。需要重写hashCode()和equals()方法。根据key可以直接获取value,具有很快的访问速度。当存放的key重复时,会覆盖value并返回新value。HashMap集合的默认初始化容量是16,默认加载因子是0.75。HashMap初始化容量必须是2的倍数,为了达到散列均匀,以提高HashMap集合的存取效率

TreeSet:基于红黑树。该映射根据其键的自然顺序排序,或者根据创建映射时提供的Comparator排序(取决于你如何构造)。

2.3、泛型

2.4、反射

2.5、I/O流

2.6、多线程

2.6.1、进程和线程

进程Process:当一个可执行文件被加载到内存时,这个程序就成为进程。进程=程序段+数据段+PCB。进程是分配资源的最小单位,是可独立调度和分派的基本单位。

线程Thread:每个线程是CPU使用的一个基本单元,包括线程ID、程序计数器、寄存器组和堆栈,是CPU调度的最小单元。同一进程的线程共享代码段、数据段和其他操作系统资源。

为什么要有线程:因为进程拥有资源,进程切换会有较大开销,所以进程数目不宜过多、切换频率不宜过快,这就限制了并发程度,所以要分开进程的基本属性——资源和调度,对于资源不频繁切换,对于调度不拥有资源,这样系统效率就大大提升。

线程和进程一样分为五个阶段:创建、就绪、运行、等待、终止。

多进程是指操作系统能同时运行多个任务(进程)。多线程是指在同一程序中有多个顺序流在执行。

每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM就是在操作系统中启动了一个进程。

Java实现多线程编程的方式主要有2种——继承Thread类,实现Runnable接口(常用)。

2.6.2、继承Thread类

查看Thread类,可以发现它实现了Runnable接口,他们之间具有多态关系。其实使用Thread类或者是Runnable接口创建的线程在工作时的性质是一样的,没有本质的区别。

但是使用Thread类的最大局限就在于Java是单继承的,继承了Thread类后就不能再继承其他类。

// Thread类常用构造方法
Thread t1=new Thread();
Thread t2=new Thread(String name);
Thread t3=new Thread(Runnable target);
Thread t4=new Thread(Runnable target, String name);

 继承Thread类的基本语法:

public class MyThread extends Thread {
    int count = 5;
    // 线程实现的业务需求代码需要放到run()方法里
    // 然后调用start()方法执行线程,即调用run()方法
    public void run() {
        super.run(); //执行Thread类中定义的默认行为
        count--;
        System.out.println(this.currentThread().getName() + "->" + count);
    }
}

使用多线程时,代码的运行结果与代码执行顺序或调用顺序无关,如执行下面这段程序

MyThread a = new MyThread();
a.start();
System.out.println("Finish");

 结果为

不共享数据时,输出都是4,不过线程的输出次序也是乱序的

public class Main {
    public static void main(String[] args) {
        MyThread a = new MyThread();
        MyThread b = new MyThread();
        MyThread c = new MyThread();
        // 创建了3个MyThread对象,每个对象有自己的count
        a.start();
        b.start();
        c.start();
    }
}

共享数据时,结果可能都是4,也可能是432,也可能是243,取决于CPU,这就存在安全问题。

public class Main {
    public static void main(String[] args) {
        MyThread a = new MyThread();
        Thread b = new Thread(a, "b");
        Thread c = new Thread(a, "c");
        Thread d = new Thread(a, "d");
        // b、c、d共享MyThread类的属性count
        b.start();  
        c.start();
        d.start();
    }
}

2.6.3、实现Runnable接口

基本步骤:1.实现接口,覆盖run()方法;2.创建一个Runnable对象;3.使用参数含Runnable对象的构造方法创建Thread实例;4.调用start()方法启动线程

public class MyRunnable implements Runnable{

    @Override
    public void run() {
        // 在这里定义线程的执行逻辑
        for (int i = 0; i < 5; i++) {
            System.out.println("Hello from MyRunnable " + i);
            try {
                Thread.sleep(1000); // 线程休眠1秒钟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
MyRunnable myRunnable =new MyRunnable();
Thread thread=new Thread(myRunnable);
thread.start();

 实现Runnable接口相对于继承Thread类的优点

1.适合多个相同的程序代码的线程去处理同一个资源

2.没有单继承的限制

3.线程池只能放入实现Runnable或callable类线程,不能直接放入继承Thread的类

2.6.4、同步

同步是指控制多个线程之间对共享资源的访问,以确保线程安全和数据一致性。在多线程环境下,如果不进行适当的同步,可能会导致数据竞争、内存可见性问题等,并可能产生不确定的结果或错误。

内置锁机制: 在Java的对象头中有一个用来存储锁信息的字段,当线程访问同步方法或同步代码块时,会尝试获取该对象的锁。如果锁已经被其他线程占用,则当前线程会被阻塞,直到获取到锁为止。这种内部锁机制保证了对共享资源的安全访问。

一、synchronized

synchronized关键字是Java中最基本、最常用的实现线程同步的机制之一。它简单易用,通过控制对共享资源的访问,保证了多线程环境下的操作原子性、数据安全性和线程间的协调执行。它可以用来标记方法或代码块,确保同一时刻只有一个线程可以访问被标记的代码,从而保证对共享资源的访问是安全的。

同步方法:使用synchronized关键字标记整个方法,使得整个方法在同一时刻只能被一个线程执行。当用此关键字修饰方法时,内置锁会保护整个方法。如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法。

public synchronized void myMethod() {
    // 同步方法
    // 这里的代码在同一时刻只能被一个线程执行
}

同步代码块:使用synchronized关键字标记代码块,只同步代码块中的部分代码,使得在同一时刻只有一个线程可以执行该代码块。这种方式可以更灵活地控制同步范围,而且同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized同步关键代码块即可。this指的是调用这个方法的对象。

public void myMethod() {
    // 非同步代码

    synchronized (this) {
        // 同步代码块
        // 这里的代码在同一时刻只能被一个线程执行
    }

    // 非同步代码
}

 二、volatile

volatile关键字是一种比较轻量级的线程同步机制,为域变量的访问提供了一种免锁机制。它的主要作用是确保变量在多个线程之间的可见性,即当一个线程修改了volatile变量的值时,其他线程能立即看到修改后的值。使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,因此每次使用该域就要重新计算,而不是使用寄存器中的值。

volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。

可见性: 在多线程环境下,每个线程都有自己的工作内存,用于存储变量的副本。当一个线程修改了变量的值时,如果该变量不是volatile的,那么其他线程可能无法立即看到这个修改,因为修改可能只是保存在该线程的工作内存中,并没有及时写回主内存。但如果该变量被声明为volatile,那么修改后的值会立即被写回主内存,从而使得其他线程能够立即看到最新的值。

禁止指令重排序: volatile关键字还具有禁止指令重排序的功能。在Java内存模型中,编译器和处理器会对指令进行优化和重排序,但在多线程环境下,这种重排序可能会导致程序出现错误。使用volatile关键字修饰的变量,可以禁止编译器和处理器对其进行重排序,保证了指令的顺序性。

volatile只适用于一些简单的同步场景,因为即使一个变量被声明为volatile,多个线程对该变量的操作仍然可能发生交错,导致最终结果出现问题。比如复合操作(如递增操作)不能保证原子性,此时还是需要使用synchronized来确保线程安全。

2.7、网络

2.8、JVM

2.8.1、组成

1.类加载器(Class Loader):负责将字节码加载到内存中,并将其转换为JVM内部表示的类和接口。

2.执行引擎(Execution Engine):负责执行已加载的字节码指令。执行引擎通常包括解释器和即时编译器(JIT编译器)两种执行方式,解释器逐条解释执行字节码指令,而即时编译器将字节码直接编译成本地机器代码以提高执行效率。

3. ​​​运行时数据区(Runtime Data Area):包括方法区、堆、栈、程序计数器和本地方法栈等不同的内存区域,用于存储程序执行过程中所需的数据。

4.本地方法接口(Native Interface):允许Java程序调用本地方法,即用其他语言编写的特定于平台的代码,提供了Java与底层系统交互的接口。

5.本地方法库(Native Method Library):包含了一组本地方法的库,用于执行由本地方法接口调用的本地方法。

2.8.2、运行时数据区

1、方法区(Method Area):方法区是所有线程共享的内存区域,它用于存储已被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在HotSpot虚拟机中,方法区被称为"永久代"(Permanent Generation)或"元空间"(Metaspace)。

当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。

方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。

在该区内很少发生垃圾回收,但不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类型的卸载。

2、堆(Heap):用于存储对象实例和数组等动态分配的内存。堆被所有线程共享,是JVM中最主要最大的一块的内存区域,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例。堆是垃圾收集器管理的主要区域,因此也被称为“GC堆”。

存储对象实例:在Java程序中,通过关键字"new"创建的对象实例都存储在堆中。堆的大小可以动态调整,以满足程序运行过程中对象的动态分配需求。

存储数组:Java中的数组也是对象,因此数组对象同样存储在堆中。数组的大小可以动态调整,堆会根据需要进行扩展或收缩。

自动内存管理:堆通过垃圾回收器进行自动内存管理。当对象不再被引用时,垃圾回收器会将其标记为可回收对象,并在适当的时候对其进行回收,释放内存空间。

分代特性:Java堆通常被划分为不同的代,包括新生代(Young Generation)、老年代(Old Generation)和永久代(或元空间)。不同代的对象具有不同的生命周期和回收方式,以提高垃圾回收的效率。

堆的大小可以通过JVM参数进行调整,但是过小的堆容易导致OutOfMemoryError,而过大的堆可能会增加垃圾回收的时间。

<----------------------------------方法区和堆是共享的,下面3个是私有的---------------------------------->

3、栈(Stack):每个线程都有一个私有的栈,为java方法服务,每个方法在执行的时候都会创建一个栈帧Stack Frame,用于存储局部变量表、操作数栈、动态链接和方法出口等信息,它的生命周期与线程相同。

局部变量表里存储的是8个基本数据类型、returnAddress类型(指向一条字节码指令的地址)和对象引用,这个对象引用有可能是指向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定。

操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式。

动态链接就是将常量池中的符号引用在运行期转化为直接引用。每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。

出口正常的话就是return,不正常的话就是抛出异常。

没有GC:JVM 直接对栈的操作只有两个:调用方法入栈 、执行结束后出栈。因此对于栈来说不存在垃圾回收问题。

虚拟机栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。当线程请求的栈深度大于虚拟机所允许的深度时 , 会出现StackOverflowError。

4、程序计数器(Program Counter Register):一块较小的内存空间,记录了当前线程正在执行的字节码指令地址,是运行速度最快的存储区域。程序计数器内存区域是JVM中唯一没有规定OutOfMemoryError 情况的区域。线程私有,生命周期与线程生命周期保持一致。

JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。

5、本地方法栈(Native Method Stack):与Java方法栈类似,但用于执行Native方法的栈空间,用于管理本地方法的调用。本地方法栈也是线程私有的。

2.8.3、垃圾回收GC

GC(Garbage Collection)是JVM自动管理内存的过程,用于回收不再使用的对象,并释放它们占据的内存空间,以便让程序继续运行时能够使用这些空间。GC是Java中内存管理的核心机制之一。

垃圾是指在运行程序中没有任何引用指向的对象,这个对象就是需要被回收的垃圾。如果不及时对内存中的垃圾进行清理,那么,这些垃圾对象所占的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用。甚至可能导致内存溢出。

System.gc(); // 手动回收垃圾

分代收集: 大多数GC算法都采用了分代收集的策略,将堆内存划分为不同的代,包括新生代(Young Generation)、老年代(Old Generation)和永久代(或元空间),以便更有效地管理不同生命周期的对象。垃圾收集器可以对年轻代回收,也可以对老年代回收,甚至是全栈和方法区的回收,其中堆是垃圾收集器的工作重点。频繁收集 Young 区、较少收集 Old 区、基本不收集元空间(方法区)。

Minor GC:新生代GC。Minor GC非常频繁,一般回收速度也比较快。当申请创建一个对象,发现内存不够用时,触发一次MinorGC

Major GC:老年代GC。通常执行Major GC会连着Minor GC一起执行。Major GC的速度要比Minor GC慢的多。

Full GC:清理整个堆空间,包括年轻代和老年代

主要算法 ->

1.标记-清除算法(Mark and Sweep): 这是最基本的垃圾收集算法之一。它通过标记那些仍然在使用的对象,然后清除那些没有标记的对象,释放它们占用的内存空间。但标记-清除算法存在着内存碎片问题,可能导致频繁的内存碎片整理操作。

2.复制算法(Copying): 用于占空间比较小、刷新次数多的新生代。复制算法将堆内存分为两个区域,一半为活动对象,另一半为非活动对象。垃圾收集时,将活动对象复制到另一半区域,然后清除非活动对象。这种算法效率高、避免了内存碎片问题,但是需要额外的空间来存储复制后的对象。

3.标记-压缩算法(Mark and Compact): 标记-压缩算法首先标记那些仍然在使用的对象,然后将它们压缩到堆的一端,清理掉堆的另一端的非活动对象。这种算法解决了标记-清除算法的内存碎片问题,但是需要额外的压缩操作,可能会影响性能。移动过程中,需要全程暂停用户应用程序(即:STW)

三、研发工具

四、应用框架

五、运维知识

六、参考资料

链接1:https://blog.csdn.net/acx0000/article/details/127177623

链接2:https://blog.csdn.net/Lzy410992/article/details/118852573

链接3:https://blog.csdn.net/weixin_45185267/article/details/125573581

链接4:https://blog.csdn.net/leader_song/article/details/132094080

链接5:https://blog.csdn.net/weixin_56846554/article/details/129802936

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值