MENU

JAVA学习笔记

April 14, 2025 • Read: 59 • Java,学习

AI 摘要

正在加载摘要...
这是以前刚学 JAVA 时候记录的,现在补充到博客上

JAVA学习笔记

变量

局部变量和全局变量

全局变量有默认值,而局部变量即方法中的变量无默认值,因此方法中的变量必须进行复制才能引用

局部变量前面不能加修饰符(public,private,等)

变量类型注意

  • JAVA中默认的小数常量为double类型,若需要float类型,则在数字末尾添加f或者F
  • 当类型为double或是float时,0.*中的0可省略,例如 0.1 可写成 .1

自动转换类型

低精度到高精度可自动转换

转换规则:

    char->int->long->float->double
    byte->short->int->long->float->double

eg:

int num = "a"; //a是由char类型转换为int类型,从低精度转换为高精度  char -> int
double d = 80  // int -> double

自由转换数据类型

转为字符串

只需要在数值后面加上 "" 即可

eg:

int a = 10;
String b = a + "";

String类型转其它

需要引入Integer类

eg:

//转为Int
String a = "aa";
int b = Integer.parseInt(a);
//转为double
double c = Double.parseDouble(a);
//以此类推即可

运算符

+-*/不再赘述

%取余

a % b => a - a / b * b

前++和后++

作为独立语句时

i++和++i作为独立语句时据等价于i = i + 1

作为非独立语句时

i++表示先赋值后运算

++i表示先运算后赋值

eg:

int a = 1;
int b = a++;  //等价于 b = a,a = a + 1
int c = 1;
int d = ++c;  //等价于 d = c + 1,c = d或c = c + 1

三元运算符

基本语法

条件表达式?表达式1:表达式2

运算规则

1.若条件表达式返回为True,则执行表达式2
2.若条件表达式返回为False,则执行表达式2

int a = 1;
int b = 2;
int result = a > b?a++:b--;
// 返回result=1
// 这里原来的a,b变量仍然不会改变

Switch

如下代码,表达式中返回一个结果,case中匹配常量和结果,若相同,则执行同级case下的语句,否则匹配下个case,若没有相同的常量,则默认执行default下的语句

表达式的返回值必须是 byte short int srting char enum

case后的语句必须是常量或是 常量表达式,不能是变量

当执行完一个case语句没有break语句时,会顺序执行到switch结尾,直到遇到break,注意,期间不管case是否匹配,都会执行下面的语句

switch(表达式){
    case 表达式结果等于这里的常量时:
    // 语句1
    break;
    case 常量2:
    // 语句2
    break;
    default:
    // 语句三
    break;
}

do...while

与while不同的是,while是先判断在执行循环体,do...while是先执行循环体在判断

while不一定执行循环体,do...while至少执行一次循环体

int count = 0;
do{
    count++;
    System.out.println(count);
}while(count<=10);

break

细节

通过lable可控制具体跳出的循环体

若不指定跳出的循环体,则默认跳出离break最近的循环体

lable1:{}

break lable1;
lable1:
for(){
    lable2:
    while(){
        lable3 do{
          
        }while();
        break lable1;
        break lable2;
        break lable3;
    }
}

递归函数

强烈推荐使用内存分析法

下面是案例分析

阶乘案例

class A{
    public int factorial(int num){
        if n == 1{
            return 1;
        }else{
            return factorial(num - 1) * num;
        }
    }
}

A test = new A();
int ret = test.facaorial(5);
System.out.println(ret);  // 返回结果是120

分析

首先传入参数5,进入到第6行的factorial函数中,然后再在栈中新开辟一个内存空间假设为t1,t1的sum为4,然后再在执行factorial函数,在开辟t2,t2sum为3,....t4sum为1,所以t4返回值为1,t4空间销毁回收,返回给t3,即1sum,t3的sum为2,即1*2,返回给t2,返回值为2,然后2*3返回为6,t1是6*4返回给第一次计算的空间即24*5,所以最后结果为120

image-20220907222211121

可变参数

当一个方法接收的参数类型一样且为多个时,可以用可变参数接收

public void static main(String[] args){
    class sum{
        public int sum(int...nums){
            # nums的数据类型相当于数组
            System.out.println(nums.length);
            # 求和
            int res = 0; 
            for(int i = ;i < nums.length ; i++){
                res += nums[i];
            }
        }
    }
}

注意

  • 可变参数可以接受数组
  • int...nums 中int为数据类型,... 不可动,nums为形参
  • 普通形参和可变参数放在一起时,可变参数要放在普通形参后面
  • 一个方法中只能有一个可变参数

构造器

注意事项

  • 格式: [修饰符] 类名(形参){}
  • 不能有return语句
  • 构造器可以重载
  • 构造器中如果有This语句,则This语句必须放在第一条
class Person(){
    String name;
    int age;
    public Person(String pname,int page){
        name = pname;
        age = page
    }
}
Person  p = new Person("名字",10);
System.out.println(p.name + p.age);
# 名字10

This,Super关键字

Super只能在构造器中使用,且This和Super不能同时使用

This相当于Python整的self,在类中则代表当前对象

Java示例如下

class Dog{
    String name;
    int age;
    public Dog(String name,int age){
        this.name = name;
        this.age = age;
    }
    public void t1(){
        System.out.println("t1()方法被调用");
    }
    public void t2(){
        // 调用t1的方法
        this.t1();
    }
}

Python示例如下

class Dog:
    def __init__(self,name, age):
        self.name = name
        self.age = age
    def t1():
        print("t1()方法被调用")
    def t2():
        # 调用t1方法
        self.t1()

访问构造器

注意

  • 访问构造器语法 this(); 必须放在构造器第一行
class Dog{
    String name;
    int age;
    public Dog(){
        this("小明", 20);
    }
    public Dog(String name, int age){
        this.name = name;
        this.age = age;
        System.out.println(this.name + this.age);
    }
}

Dog d = new Dog();
// 输出 小明20
  • this(name)继承中代表访问一个参数只有name的构造器
class Person {
    public static void prt(String s) {
        System.out.println(s);
    }

    Person() {
        prt("父类·无参数构造方法: "+"A Person.");
    }//构造方法(1) 

    Person(String name) {
        prt("父类·含一个参数的构造方法: "+"A person's name is " + name);
    }//构造方法(2) 
}

public class test extends Person {
    test() {
        super(); // 调用父类构造方法(1)
        prt("子类·调用父类无参数构造方法: "+"A chinese coder.");
    }

    test(String name) {
        super(name);// 调用父类具有相同形参的构造方法(2)
        prt("子类·调用父类含一个参数的构造方法: "+"his name is " + name);
    }

    test(String name, int age) {
        this(name);// 调用具有相同形参的构造方法(3)
        prt("子类:调用子类具有相同形参的构造方法:his age is " + age);
    }

    public static void main(String[] args) {
        test cn = new test();
//        cn = new test("codersai");
        cn = new test("codersai", 18);
    }
}

// 输出如下
//父类·无参数构造方法: A Person.
//子类·调用父类无参数构造方法: A chinese coder.
//父类·含一个参数的构造方法: A person's name is codersai
//子类·调用父类含一个参数的构造方法: his name is codersai
//子类:调用子类具有相同形参的构造方法:his age is 18

Super

super.属性可以访问跟父类的属性,但不能访问父类的私有属性

spuer.方法名(参数列表)可以访问父类的方法,但是不能访问父类私有的方法

super()必须放在子类构造器的第一行,且不能与this同时存在

访问修饰符

image-20221015114126118

类中的变量,函数都可用访问修饰符

类也可以用访问修饰符,不过只能同 public 和 默认

继承

关键词 extends

注意事项

  • 子类继承了所有的属性和方法,但是私有属性和方法不能在子类直接访问,要通过公用的方法去访问
  • 当创建子类对象时,不管子类使用哪个构造器,默认情况下会去调用父类的无参构造器,如果父类中没有无参构造器,则必须在子类的构造器中用 super()去指定父类哪个构造器来完成对父类的初始化工作,否则编译不通过。

多态

多态因为可以理解为多种形态

方法的多态

方法的重载,重写实际上就是多态的一种

对象的多态

1.一个对象的编译类型和运行类型可以不一致
2.编译类型在定义对象时,就确定了,不能改变了
3.运行类型是可以改变的
4.编译类型看定义时 = 号 的左边,运行类型看 = 号 的右边

class Animal{  //父类
    private String Name;
    public void setName(String Name){
        this.Name = Name;
    }
    public String getName(){
        return Name;
    }
}
class Dog extends Animal{  //子类
    private String Name = "Dog";
    public String getName(){
        return Name;
    }
}
class Cat extends Animal{  //子类
    private String Name = " Cat";
    public String getName(){
        return Name;
    }
}


public static void main(String[] args){
    Animal dog = new Dog();  //dog的编译类型是Animal,运行类型是Dog
    dog = new Cat(); //此时dog的编译类型还是Animal,运行类型变成了Cat
}

对象的多态,就是在调用类的时候传入的参数可以是一个对象,通过对象去调用其子类或本类的方法
eg:

public static void main(String[] args){
    Animal dog = new Dog();  
    Animal cat = new Cat(); 
    call(dog); //输出Dog is runing
    call(cat); //输出Cat is runing
}
public void call(Animal name){
    System.out.println(name.getName + " is runing");
}

向上转型

向上转型就是 Animal dog = new Dog() dog去调用Dog类和Animal类的方法,弊端是假如说Dog中有A方法可以调用,Animal中的A方法为私有方法或者没有此方法,那么就无法编译通过。虽然是从子类到父类去寻找方法,但是由于Java特性,无法编译通过,于是有了向下转型

向下转型

书接上回,dog如何调用Dog类中独有的方法呢?就用到了向下转型,也可以理解为强转,转的 不是编译类型,因为编译类型确定后就无法改变,转的是运行类型。

Animal dog = new Dog();
Dog dog1 = (Dog) dog;

这样dog1对象就能调用Dog类的方法了。

注意

  • 语法 子类类型 引用名 = (子类类型)父类引用;
  • 要求父类的引用必须指向的是当前目标类型的对象(也就是说上方代码第二行最后一个的dog的运行类型必须是dog1的编译类型
  • 变量不能重写,以向上转型为例,Animal animal = new Dog(); System.out.print(amimal.name) 假设父类Animal和子类Dog都有公共的变量name,则以编译类型的变量为主,打印出来的就是Animal的name。
  • 属性看遍历类型,方法看运行类型。

instanceof

语法: 对象A instanceof 类B 返回布尔值

判断对象A的运行类型的类是否为类B或类B的子类,若是则为True,反之为False

动态绑定机制

  1. 当调用对象方法时候,该方法会和该对象的内存地址/运行类型绑定
  2. 当调用对象属性时,没有动态的绑定机制,哪里声明哪里使用。
class A {  //父类
    public int i =10;
    public int sum(){
        return getI + 10;
    }
    public int sum1(){
        return i + 10;
    }
    public int getI(){
        return i;
    }
}
class B extends A {   //子类
    public int i = 20;
    public int sum(){
        return i + 20;
    }
    public int getI(){
        return i;
    }
    public int sum1(){
        return i + 10;
    }
}
// main方法
A a = new B(); //向上转型
System.out.println(a.sum()); //40
System.out.println(a.sum1()); //30

!!!

如果删除掉子类B的sum()方法,则调用 a.sum()时候会继承A的sum()方法,返回的时 getI() + 10,而由于动态绑定机制,getI()指向的是a的运行类型也就是B类中的getI()方法,所以此时返回的是30

多态数组

定义:数组的定义为父类类型,厘米那的实际元素类型为子类类型。

代码块

代码块和方法不同没有方法名,没有返回值,没有参数,只有方法体,而且不用通过对象或类显式调用,而是在加载类时,,或创建类时隐式调用

基本语法

[修饰符]{
    Code
};

注意

  • 修饰符为可选项,要写的话,也只能写static
  • 代码块分为两类,静态代码块即用static修饰的代码块和普通代码块即没有修饰符的
  • 末尾;可写可不写

代码块什么时候被加载

  1. 创建对象实例时
  2. 创建子对象实例时候,父类的代码块也会被加载(父类的代码块先加载,接着加载子类的代码块)
  3. 使用类的静态成员时(静态方法,静态变量)ps:使用类的静态成员时普通代码块即非静态代码块不会被执行

静态代码块只会被执行1次,非静态的没创建一次就会被执行一次

优先级

  • 静态代码块和静态属性的优先级一样
  • 倘若有多个静态代码块和多个静态属性,则按照书写顺序加载
class A{
    private static int num = getNum();
    static {
        System.out.println("静态代码块初始化");
    }
    public int getNum(){
        System.out.println("getNum方法被调用");
        return 100;
    }
}

new A();

!!!

当实例化A类时候,优先初始化第一个静态属性即 num,此时 num调用了 getNum方法,所以优先输出 getNum方法被调用,然后再初始化静态代码块

  • 静态代码块和静态属性的优先级高于普通代码块和普通属性
  • 普通代码块和普通属性的优先级一样
  • 构造器的优先级是最低的

    class AAA{
        public AAA(){
            //super();
            //调用本类的普通代码块
            System.out.println("AAA类的构造器被调用");
        }
    }
    class BBB extends AAA{
        {
            System.out.println("BBB的普通代码块被调用");
        }
        public BBB(){
            //super();
            //调用本类的普通代码块
            System.out.println("BBB类的构造器被调用");
        }
    }
    new BBB();

    每个类的构造器下都有隐藏的代码

    1. 调用 super()
    2. 调用本类的普通代码块

      所以上述代码的运行结果就是 1 AAA类构造器 2 BBB普通代码块 3 BBB构造器

    总结

    执行顺序如下

  1. 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
  2. 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
  3. 父类的普通代码块和普通属性(优先级一样,按定义顺序执行)
  4. 父类的构造方法
  5. 子类的普通代码块和普通属性(优先级一样,按定义顺序执行)
  6. 子类的构造方法

final关键字

结论

不能被继承,重写,修改里面的值

三种赋值方式

  1. 定义时
  2. 构造器中
  3. 代码块中

    如果final修饰的属性是静态的,那么只能在代码块和定义时赋值。

final修饰的类不能被继承,一般final修饰类后,方法就不用加final修饰了

final和static

final和static同时使用不会创建类、

class Test{
    public final static int num = 555;
    static{
        System.out.println("静态代码块被调用");
    }
}

假如只有static没有final,那么调用 Test.num时候静态代码块也会被调用,若加上final,则不会调用静态代码块。

抽象类 abstract

抽象类就是没有方法体的方法,方便子类继承重写方法,抽象类和抽象方法的定义需要用到 abstract关键字

当一个类中存在抽象方法时,该类必须声明为抽象类

abstract class Animal{
    abstract public void eat(); 
}

细节

  1. 抽象类不能被实例化
  2. 抽象类不一定要有抽象方法
  3. abstract关键字只能修饰类和方法,不能修饰属性
  4. 如果一个类继承了抽象类,则必须实现抽象类的所有抽象方法,否则子类也要声明为抽象类
  5. 抽象方法不能用 private,static,final关键字修饰,因为这些关键字是与重写相违背的

接口 interFace

基本语法

定义接口

//接口类中可省略abstract和public

public interface Method{
    //属性
    //方法(1.抽象方法 2.默认default方法 3.静态static方法)
    void job(); //接口类中可省略abstract和public
}
//实现(重写接口中的方法)
public class Mian implenets Method{
    public void job(){
        System.out.println("Hello World");
    }
}
public class T1(){
    public void run(interface Method){
        Method.job();
    }
}
//调用
public class void T2{
    public static void main(String[] args){
        T1 tt = new T1();
        Main mm = new Main();
        tt.run(mm);
    }
}

注意

  1. 如果一个类实现了一个接口方法,即 一个类 implenets 接口 则这个类必须实现这个接口的所有的抽象方法
  2. 在JDK8之后,可以有静态方法
  3. 如果用到非抽象方法,需要加上default的关键字
  4. 接口不能被实例化
  5. 一个类可以同时实现多个接口
  6. 接口中的属性,只能是final的,且为 public static final,假设我写了一个int x = 1,那么它就是public static final int x = 1;(必须初始化)
  7. 接口不能继承类,但是接口可以继承extends多个接口

内部类

局部内部类(有类名)

  1. 局部内部类可以直接访问外部类的所有成员对象,包括私有的
  2. 内部类不能用除final的修饰符修饰
  3. 如果外部类和局部内部类的成员名重名时,遵循就近原则,如果要调用外部类则需要 外部类名.this.成员名

匿名内部类(没有类名)

基于接口

首先定义一个接口innerTest,使其拥有一个test方法

image-20230404213617994

以下是内部匿名类的用法,通过实例化一个接口并重写其方法来实现匿名内部类

image-20230404213601608

下图是上图的底层源码

image-20230404213632535

匿名内部类实际上是有名字的,我们看不到,系统自动去分配,而他的底层源码就是上图所示的形式

上图的编译类型是接口的innerTest类型,而他的运行类型则是个底层代码的te的类型

基于类

image-20230404220357888

image-20230404220416172

跟基于接口的类似

很牛逼的使用匿名内部类

匿名内部类可以直接当作一个参数使用

interface IA{
    void test();
}
class main{
    public static void method(IA i){
        i.test();
    }
    public static void main(String[] args){
        method(new IA(){
           public void test(){
               System.out.println("世界名画!");
           } 
        });
    }
}

// 输出 世界名画

细节

  1. 匿名类有两种调用模式,第一种就是上面使用的的,利用可动态绑定机制,第二种可以直接去调用

    class outer{
        new test(){
            public void test(){
                System.out.println("我重写了test函数");
            }
        }.test();
    }
    class test{
        public void test(){
            System.out.println("test");
        }
    }
  2. 外部其他类不能访问内部类

成员内部类

  1. 成员内部类是定义在外部类的成员位置
  2. 成员内部类可以访问外部类的所有成员,包括私有的
  3. 可以添加任意的访问修饰符,因为它本身就是一个成员
  4. 如果外部类和成员内部类的方法或者属性变量重名,依然遵守就近原则,访问外部类:外部类.this.方法名/属性名

外部其他类调用成员内部类

方法一
// 假设outer为外部类,inner为成员内部类
outer.inner test = new outer.inner();
方法二

在外部类中写一个方法返回内部类的对象

静态内部类

说明:静态内部类仍然定义在外部类的成员位置,只是多了个static修饰

  1. 可以直接访问外部类的静态成员,包括私有的,但是不能直接访问非静态的
  2. 因为他也是成员内部类,所以可以使用修饰符
  3. (不太一样,无this)如果外部类和成员内部类的方法或者属性变量重名,依然遵守就近原则,访问外部类:外部类.方法名/属性名

调用方法同上

枚举

自定义枚举

  1. 构造器私有化
  2. 创建一个静态成员new一个所需的对象,用static final修饰
class test{
    public int i;
    public static final test CREATEST1 = new test(1);
    public static final test CREATEST2 = new test(2);
    public static final test CREATEST3 = new test(3);
    private test(int i){
        this.i = i;
    }
}
// static 和 final 一起使用时候会使类不会被加载
// 调用就直接 test.CREATEST1.XXX
// 枚举对象名一般大写

关键字enum

优化后的代码

enum test{
    CREATEST1(1),CREATEST2(2),CREATEST3(3),WHAT;
    public int i;
    private test(int i){
        this.i = i;
    }
    private test(){
    }
}

注意

  1. 使用关键字enum代替class
  2. public static final test CREATEST1 = new test(1);直接使用CREATEST1(1)代替,若有多个则用逗号隔开
  3. CREATEST1(1) 括号里面是实参,对应构造器的形参
  4. CREATEST1(1)必须写在最前面
  5. 如果使用无参构造器,则可以省略 ( ) 直接写常量对象名
  6. 使用enum定义枚举时候,就不能在继承其他类了,因为它已经隐式的继承了enum类了,然仍然能实现接口

注解/元数据

@Override

该注解表示方法重写了父类的某个方法

该注解只能用在某个方法上

如果写了@Override注解,java编译器在编译时候会寻找父类中是否含有该方法,如果真的被重写,则编译通过。否则就抛出异常

@Deprecated

表示程序元素(类,方法)已经过时了

可以修饰 类 方法 参数 包 字段等等

过时并不能代表不能用

@SuppressWarnings

抑制编译器警告

用法

@SuppressWarnings{""}

{" "}里面写抑制的类型,all 代表一直所有警告

@Target

该注解是修饰注解的注解,称为元注解

集合

image-20230503221503465

image-20230423183548296

List

基本方法(实现了Collection的接口方法)

  • add:添加单个元素
  • remove:删除元素(可指定索引或指定删除的对象)
  • contains:查找元素是否存在
  • size:获取元素个数
  • isEmpty:判断是否为空
  • clear:清空
  • addAll:添加多个元素(可以放实现了Collection接口的对象,比如说集合)
  • containsAll:查找多个元素是否都存在(可以放实现了Collection接口的对象,比如说集合)
  • removeAll:删除多个元素(可以放实现了Collection接口的对象,比如说集合)

迭代器遍历

Iterator iterator = coll.iterator() //得到一个集合的迭代器

.hasNext()是否还有下一个元素 编译类型是Object

.next() 显示当前元素内容,并对指针进行下移

image-20230423191329732

第二次遍历需要重置迭代器,否则就会抛出异常

iterator = coll.iterator() //重置迭代器

IDEA快速生成whlie循环遍历的快捷键:itit

IDEA快速生成增强for循环遍历的快捷键:I

增强for循环遍历

for(元素类型(Object) name:col){
        System.out.println(clo);
}

Set

Set接口介绍

  1. 无序的(添加和取出的顺序不一样),没有索引
  2. 不允许有重复的元素,所以最多包含一个null

注意:取出的顺序虽然是无序的,但是是固定的,底层算法决定的

方法说明

因为set没有索引,故不能用get方法取出,遍历的话同上实现了List的接口的类一样,增强for循环和迭代器

  • add方法说明,add方法在这里有一个布尔类型的返回值,用来判断是否添加成功
  • remove 删除指定对象

Map

key:value

方法说明

  • put(key,value) 添加:key的值是唯一的不能重复,value的值可以重复,假如说key的值重复了,那么则会替换先前存在key的value的值(Hashtable的key和value都不能为空)
  • remove(key)根据键删除对应的映射关系
  • get(key)获取对应key的value 返回值类型为object
  • size()返回k-v的个数
  • isEmpty()判断是否为空
  • clear()清空键值对
  • containsKey(key)查找键是否存在
  • keySet() 返回一个set集合,集合里面是所有的key

遍历的六大方式

(比List和Set稍微复杂点,但是基本原理相同)

    1. 利用 keySet()方法获取所有的key,然后用增强for循环get到每一个value
Map map = new HashMap();
        map.put("no1","Lin");
        map.put("no2","Kenen");
        map.put("no3","f");
        map.put("no4","z");
        Set set = map.keySet();
        for(Object i:set){
            System.out.println(map.get(i));
        }
    1. 迭代器
Iterator iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            Object key =  iterator.next();
            System.out.println(map.get(key));
        }
    1. 增强for 同上
    2. 迭代器 同上
    3. entry
    Set set = map.entrySet();
            for (Object i:set){
                Map.Entry m = (Map.Entry) i;
                System.out.println(m.getKey()+"-"+m.getValue());
            }

Collections工具类

Collections类能对Set,List,Map等集合进行操作

常用方法

  • reverse(List) :反转列表
  • shuffle(List) :对列表进行随机排序
  • sort(List) :排序
  • sort(List,Comparatoe) :根据Comparator产生的顺序对列表排序
  • swap(List,int,int) :将指定List的集合的两个元素进行交换位置
  • copy(desp,list) :将list的内容拷贝到desp中。注意:desp的长度必须>=list的长度,都则会抛出异常
  • boolean replaceALL(List,old,new) :将List集合中的old值全部替换为new的值
  • frequency(Collection,Object) :返回指定集合中指定元素出现的次数

泛型

泛型的通配和继承

  1. 泛型不具备继承机制 List<Object> list = new list<String>(); //错误
  2. <?> 支持任意泛型
  3. <? extends A>支持A类及A类的子类,规定了泛型的上限
  4. <? super A> 支持A类及A类的父类,规定了泛型的下线

举例

public void test(List<?> list){
    for(Object o:list){
        System.out.println(o);
    }
}
public void test(List<? extends AA> list){
    for(Object o:list){
        System.out.println(o);
    }
}
public void test(List<? super AA> list){
    for(Object o:list){
        System.out.println(o);
    }
}

多线程

创建线程的两种方式

  1. 继承Thread类,重写run方法
  2. 实现Runnable接口,重写run方法

*为什么是开启新线程是start方法而不是run方法?

进入start方法可以看到,最主要开启的新的线程的方法是

start0()方法,这个是JVM的内部方法,底层是c/c++编写,实际就是由底层创建了一个新的线程去调用了run方法

代码示例

//继承实现
public class Cat extends Thread{
    @Override
    public void run(){
        int count = 0;
        while (true){
            System.out.println("我是小猫,喵喵喵"+ (++count));
            if (count > 8){
                break;
            }
            try {
                Thread.sleep(1000);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}
//实现Runnable接口实现
public class Dog implements Runnable{
    @Override
    public void run(){
        int count = 0;
        while (true){
            System.out.println("我是小狗,汪汪汪"+ (++count));
            if (count > 8){
                break;
            }
            try {
                Thread.sleep(1000);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}
//主方法
public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        Dog dog = new Dog();
        cat.start();
        Thread thread = new Thread(dog);
        thread.start();
    }
}

总结

继承了Thread的类可以直接调用start方法,但由于Java是单继承机制,所以可以实现Runnable接口,此时开启新线程就需要在创建一个新的Thread的类,并传入实例化的对象,然后调用其start的方法

区别

  1. 从Java的设计来看,继承Thread和实现Runnable没有本质的区别
  2. 实现Runnable的接口方式更适合多个线程共享一个资源的情况,并避免了单继承机制的限制
class Te implements Runnable{
    public void run(){}
}
class main{
    public static void main(String[] args){
        Te t0 = new Te();
        Thread t1 = new Thread(t0);
        Thread t2 = new Thread(t0);
        t1.start;
        t2.start;
    }
}

线程常用方法

  • setName:设置线程名称
  • getName:获取线程名称
  • start:开始执行线程
  • run:调用线程run方法
  • setPriority:更改线程优先级
  • setPriority:获取线程优先级
  • sleep:休眠
  • interrput:中断线程,不是结束线程 一般用于zheng'zai

线程插队

  1. yield:线程的礼让,让出CPU,让其他线程执行,但是礼让时间不确定,所以礼让不一定成功
  2. join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程的所以任务(会阻塞线程(主线程也会被阻塞))

守护线程

引入

当子线程是一个无限循环的线程时,主线程退出,子线程并不会退出,为了此时让子线程也退出,就用到了守护线程

解决办法

线程名.setDaemon(true);

Synchronized

线程同步机制

  • 在多线程编程,一些敏感的数据不允许被多个线程同时访问,此时就是使用同步访问技术,保证数据在任何同一时刻,最多只有一个线程访问,以保证数据的完整性。
  • 也可以理解成:线程同步:即当有一个线程对内存进行操作时,其他线程都不可以对这个内存进行操作,知道该线程完成操作,其他线程才能对该内存进行操作

实现同步的具体方法-Synchronized

  1. 同步代码块
synchronized (对象){
    // 这里放需要被同步的代码
}

public void m1(){
    // Code
    synchronized (this){
        // Code 
    }
}
  1. Synchronized放在方法声明中,表示整个方法为同步方法
public synchronized void m1(){
    // 这里放需要被同步的代码
}
注意
选择同步代码块的对象

this 是指本对象,如果多个new对象对其进行start,那么是锁不住的,所以推荐采用实现Runnable接口的方法实现多线程

线程的死锁

介绍
多个线程占用了对面的资源,但不肯相让,导致了死锁。

释放锁

会释放锁的操作
  1. 当前线程的同步方法,同步代码块执行完毕
  2. 当前线程在同步方法,同步代码块中遇到了return或break
  3. 当前线程在同步方法,同步代码块中遇到了Error或Exception,导致异常结束
  4. 当前线程在同步方法,同步代码块中执行了wait()方法,当前线程暂停,并释放锁
不会释放锁的操作
  1. 当前线程在同步方法,同步代码块中执行了 Thread.sleep() 或者 Thread.yield()方法
  2. 当前线程在同步方法,同步代码块时,其他线程调用了该线程的suspend()方法,该线程将会被挂起,不会被释放

四大核心函数式接口

函数式接口称谓参数类型用途
Consumer<T>消费性接口T对类型为T的对象应用操作,包含方法 void accept(T t)
Supplier<T>供给型接口返回类型为T的对象,包含方法 T get()
Function<T,R>函数型接口T对类型为T的对象应用操作,并返回结果,结果是R类型的对象,包含方法 R apply(T t)
Predicate<T>判断型接口T确定类型为T的对象是否满足约束,并返回 boolean值,包含方法 boolean test(T t)
public class MyTest {

    // Consumer Interface Demo
    // 和 JavaScript 的 CallBack 有异曲同工之妙
    public void consumer(Integer num, Consumer<Integer> consumer) {
        consumer.accept(num);
    }

    @Test
    public void testConsumer() {
        // 可以这么理解 consumer(x,callback)
        consumer(500,num -> System.out.println("The number is " + num));
    }

    // Supplier Interface Demo
    public <T> T supplier(Supplier<T> supplier) {
        return supplier.get();
    }

    public String hello(){
        return "hello";
    }

    @Test
    public void testSupplier() {
        System.out.println(supplier(() -> hello()));
    }

    // Function Interface Demo
    public <T,R> R function(T t,Function<T,R> function){
        return function.apply(t);
    }

    @Test
    public void testFunction() {
        String function = function(100, num -> String.valueOf(num));
        System.out.println(function);
    }

    // Predicate Interface Demo
    public <T> void predicate(T t,Predicate<T> predicate) {
        System.out.println(predicate.test(t));
    }

    @Test
    public  void testPredicate() {
        predicate(100,num -> num == 1000);
    }
Last Modified: June 1, 2025