Java 基础 整个基础班 day11 -- day15 完整学习知识(代码+练习+打印结果)跟着手把手敲一遍就会

一直觉得自己写的不是技术,而是情怀,一个个的教程是自己这一路走来的痕迹。靠专业技能的成功是最具可复制性的,希望我的这条路能让你们少走弯路,希望我能帮你们抹去知识的蒙尘,希望我能帮你们理清知识的脉络,希望未来技术之巅上有你们也有我。

Java 基础 整个基础班 day06 – day10 完整学习知识(代码+练习+打印结果)跟着手把手敲一遍就会

文章目录

day11

navite关键字

在java框架中,被navite修饰的方式,只有方法的声明没有实现,它的内部是C/C++实现。
例如:

Math.pow(2, 3);

final 常量 无法改变

final:最终的,不可更改的,它的用法有:

1、修饰类

表示这个类不能被继承,没有子类

final class Eunuch{//太监类
	
}
class Son extends Eunuch{//错误
	
}

2、修饰方法

表示这个方法不能被子类重写

class Father{
	public final void method(){
		System.out.println("father");
	}
}
class Son extends Father{
	public void method(){//错误
		System.out.println("son");
	}
}

3、声明常量

final修饰某个变量(成员变量或局部变量),表示它的值就不能被修改,即常量,常量名建议使用大写字母。

如果某个成员变量用final修饰后,没有set方法,并且必须初始化(可以显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)

public class Test{
    public static void main(String[] args){
    	final int MIN_SCORE = 0;
    	final int MAX_SCORE = 100;
    }
}
class Chinese{
	public static final String COUNTRY = "中华人民共和国";	
	private String name;
	public Chinese( String name) {
		super();
		this.name = name;
	}
	public Chinese() {
		super();
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	//final修饰的没有set方法
	public static String getCountry() {
		return COUNTRY;
	}
}

Object类

创建一个类,如何自动生成setter getter方法

public class Teacher {

    private String name;
    private String age;
    private String sex;

    
}

在这里插入图片描述

public class Teacher {

    private String name;
    private String age;
    private String sex;

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
}

toString()

它的作用是自动打印当前类里面的属性信息
在这里插入图片描述

class Person{
    private String name = "fenghanxu";
    private int age = 18;

    Person() {}

    Person(String name, int age){
        this.name = name;
        this.age = age;
    }

    public void speak(){
        System.out.println(name + "说:我今年" + age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

操作

public class day01 {
    public static void main(String[] args){

        Person p1 = new Person();
        System.out.println("p1" + p1);

    }
}

getClass()

获取运行时内存,跟iOS的[person class];一样

Person p1 = new Person();
System.out.println("p1类型:" + p1.getClass());

finailze()

它的主要作用是在对象被垃圾回收器回收之前,执行一些清理操作

public class Teacher {

    private String name;
    private String age;
    private String sex;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("轻轻的我走了.......");
    }
}

操作

public class day01 {
    public static void main(String[] args){

        Teacher t1 = new Teacher();
        System.out.println("t1类型:" + t1.getClass());

        t1 = null;//先至空
        System.gc();//在通知垃圾回收期回收

    }
}

hashCode()

返回当前对象的hash码

public class day01 {
    public static void main(String[] args){

        Teacher t1 = new Teacher();
        System.out.println("t1 hashCode:" + t1.hashCode());


    }
}

打印
在这里插入图片描述

类中的hashCode使用

在这里插入图片描述

equals()

跟iOS的isEqualsTo是一样的

开发应用:用于插入更新列表数据
java开发,有这样的一个类,有一个模型数组去存储每一个模型,然后我想插入一个新的模型到模型数组,前提条件是,这个模型要与模型数组里面的模型不同,如何插入前进行判断这个模型

类中生成equals方法。使用com+N生成

在这里插入图片描述

完整模型

import java.util.Objects;

public class Teacher {
    private String name;
    private String age;
    private String sex;

    // 构造函数
    public Teacher(String name, String age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    // Getter 和 Setter 方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    // 重写 equals 方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Teacher teacher = (Teacher) o;
        return Objects.equals(name, teacher.name) &&
                Objects.equals(age, teacher.age) &&
                Objects.equals(sex, teacher.sex);
    }

    // 重写 hashCode 方法
    @Override
    public int hashCode() {
        return Objects.hash(name, age, sex);
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }

}

操作

public class day01 {
    public static void main(String[] args){

        Teacher t1 = new Teacher("Alice", "30", "Female");
        Teacher t2 = new Teacher("Bob", "35", "Male");
        Teacher t3 = new Teacher("Alice", "30", "Female");

        System.out.println("t1等于t2 ?:" + t1.equals(t2));

        System.out.println("t1等于t2 ?:" + t1.equals(t3));

    }
}

打印

在这里插入图片描述

抽象初识

这个知识点了解就好,为后面的接口用法作铺垫,相当于iOS的协议使用,只有声明,没有实现

注意:
1.继承抽象类必须实现抽象类的方法

创建抽象类

//抽象类
public abstract class Bird {
    //抽象方法
    public abstract void eat();
}

抽象类的使用

public class Bigbird extends Bird {

    @Override
    public void eat() {
        System.out.println("jiao");
    }
}

接口

相当于iOS的协议,但是跟iOS的规则是有区别的

接口:

  • 1.支持方法的声明
  • 2.支持方法的实现
  • 3.支持常量(不支持属性)
  • 4.支持遵守多接口
  • 5.如果接口方法没有实现就是抽象方法,子类必须实现,如果接口方法有实现就是default方法,可以选择实现(重写),如果接口是静态方法就不能重写,只能直接调用,如果是私有方法,就只能接口类自己内部调用

接口类

public interface LiveAble {
    // 定义抽象方法
    public abstract void eat();
    public abstract void breathe();
    //定义默认方法
    public default void sleep(){
        System.out.println("静止不动");
    }
    //定义静态方法
    public static void drink(){
        System.out.println("喝水");
    }
}

子类

public class Plant implements LiveAble {
    //重写/实现接口的抽象方法
    @Override
    public void eat() {
        System.out.println("吸收营养");
    }
    //重写/实现接口的抽象方法
    @Override
    public void breathe(){
        System.out.println("吸入二氧化碳呼出氧气");
    }

    //重写接口的默认方法
    @Override
    public void sleep() {
        System.out.println("闭上眼睛睡觉");
    }
}

操作

public class day01 {
    public static void main(String[] args){

        //创建实现类(子类)对象
        Plant p = new Plant();
        p.eat();
        p.breathe();
        p.sleep();
    }
}

打印
在这里插入图片描述

多接口实现

定义多个接口:

interface A {
    public abstract void showA();
    public abstract void show();
}

interface B {
    public abstract void showB();
    public abstract void show();
}

定义实现类:

public class C implements A,B{
    @Override
    public void showA() {
        System.out.println("showA");
    }

    @Override
    public void showB() {
        System.out.println("showB");
    }

    @Override
    public void show() {
        System.out.println("show");
    }
}

多接口出现相同方法的实现问题

多接口出现相同方法,如果重写了就执行实现自己的方法,如果想执行给的接口方法通过super执行
接口一

public interface Study {
    public default void sleep() {
        System.out.println("study sleep");
    }
}

接口二

public interface Work {

    public default void sleep() {
        System.out.println("Work sleep");
    }
}

操作,实现接口方法

class Person implements Study, Work{

    @Override
    public void sleep() {
        System.out.println("接口 睡觉");

        Study.super.sleep();
        Study.super.sleep();
    }

    public static void main(String[] args){
        Person p = new Person();
        p.sleep();
    }

}

打印

在这里插入图片描述

学完接口之后,下面就开始介绍系统常见的接口方法

Comparable 类内部比较器

开发中不会用到,有封装好的函数调用更加方便,就像iOS的高阶函数的使用

内部是指,比较器要在类里面遵守接口,在类里面实现方法

Object类有Comparable这样的一个方法,遵守接口,实现方法进行类里面的属性比较

实现方法,比较规则是自己写的

class Student implements Comparable{
	private int id;
	private String name;
	private int score;
	
    public Student(int id, String name, int score) {
        this.id = id;
        this.name = name;
        this.score = score;
    }
	
	//省略了构造器、get/set、toString等方法

	@Override
	public int compareTo(Object o) {
		//这些需要强制,将o对象向下转型为Student类型的变量,才能调用Student类中的属性
		Student stu = (Student) o;
		if(this.score != stu.score){
			return this.score - stu.score;
		}else{//成绩相同,按照学号比较大小
			return this.id - stu.id;
		}
	}
	
}

实现操作

public class TestComparable {
	public static void main(String[] args) {
		Student s1 = new Student(1,"张三",89);
		Student s2 = new Student(2,"李四",89);
		if(s1.compareTo(s2)>0){
			System.out.println("s1>s2");
		}else if(s1.compareTo(s2)<0){
			System.out.println("s1<s2");
		}else{
			System.out.println("s1 = s2");
		}
	}
}

打印
在这里插入图片描述

day12

Comparator 外部比较器

开发中不会用到,有封装好的函数调用更加方便,就像iOS的高阶函数的使用

外部比较器是指,需要比较的类没有实现比较器接口,但是比较,可以创建一个比较的类去实现接口,需要比较的时候把类传递进去,实现比较

学生累没有实现比较器接口方法

class Student{
	private String name;
	private int score;
	public Student(String name, int score) {
		super();
		this.name = name;
		this.score = score;
	}
	public Student() {
		super();
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getScore() {
		return score;
	}
	public void setScore(int score) {
		this.score = score;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + ", score=" + score + "]";
	}
	
}

定制一个比较器类

class StudentScoreCompare implements Comparator{

	@Override
	public int compare(Object o1, Object o2) {
		Student s1 = (Student) o1;
		Student s2 = (Student) o2;
		return s1.getScore() - s2.getScore();
	}
	
}

操作

import java.util.Comparator;

public class TestComparator {
	public static void main(String[] args) {
		Student stu1 = new Student("张三",89);
		Student stu2 = new Student("李四",78);
		
		StudentScoreCompare ssc = new StudentScoreCompare();
		if(ssc.compare(stu1, stu2)>0){
			System.out.println(stu1 + ">" + stu2);
		}else if(ssc.compare(stu1, stu2)<0){
			System.out.println(stu1 + "<" + stu2);
		}else{
			System.out.println(stu1 + "=" + stu2);
		}
	}
}

枚举

枚举比较适用于固定范围的选择,例如:世界上就只有男和女

public class day01 {

    public enum Day {
        SUNDAY,
        MONDAY,
        TUESDAY,
        WEDNESDAY,
        THURSDAY,
        FRIDAY,
        SATURDAY
    }

    public static void main(String[] args) {

        Day today = Day.MONDAY;

        switch (today) {
            case MONDAY:
                System.out.println("Today is MONDAY");
                break;
            case TUESDAY:
                System.out.println("Today is TUESDAY");
                break;
            case WEDNESDAY:
                System.out.println("Today is WEDNESDAY");
                break;
            case THURSDAY:
                System.out.println("Today is THURSDAY");
                break;
            case FRIDAY:
                System.out.println("Today is FRIDAY");
                break;
            case SATURDAY:
                System.out.println("Today is SATURDAY");
                break;
            case SUNDAY:
                System.out.println("Today is SUNDAY");
                break;
        }

    }

}

枚举实现接口的用法

定义接口

public interface Activity {
    void perform();
}

枚举实现接口

public enum Day implements Activity {
    MONDAY {
        @Override
        public void perform() {
            System.out.println("Working hard on Monday!");
        }
    },
    TUESDAY {
        @Override
        public void perform() {
            System.out.println("Continuing work on Tuesday.");
        }
    },
    WEDNESDAY {
        @Override
        public void perform() {
            System.out.println("Midweek, keep going!");
        }
    };

    // 枚举常量必须实现接口的方法
    @Override
    public abstract void perform();
}

操作

public class EnumInterfaceExample {
    public static void main(String[] args) {
        Day today = Day.MONDAY;
        today.perform(); // 输出: Working hard on Monday!

        Day tomorrow = Day.TUESDAY;
        tomorrow.perform(); // 输出: Continuing work on Tuesday.
    }
}

包装类

包装类就是把基本数据类型封装成对象,然后通过对象点出方法进行方便使用,例如:

int a = Integer.parseInt("123");

下面是基本类型对应的包装类型

序号基本数据类型包装类
1byteByte
2shortShort
3intInteger
4longLong
5floatFloat
6doubleDouble
7charCharacter
8booleanBoolean
9voidVoid

装箱与拆箱

装箱(Boxing):将基本数据类型转换为对应的包装类对象。

拆箱(Unboxing):将包装类对象转换为对应的基本数据类型。

Integer i = 4; // 自动装箱,相当于 Integer i = Integer.valueOf(4);
i = i + 5; // 自动拆箱,相当于 i.intValue() + 5,然后再自动装箱

包装类的一些API

基本数据类型和字符串之间的转换:

基本数据类型转字符串:

int a = 10;
String str = a + ""; // 方式一
String str = String.valueOf(a); // 方式二

字符串转基本数据类型:

int a = Integer.parseInt("123");
double d = Double.parseDouble("123.45");
boolean b = Boolean.parseBoolean("true");

数据类型的最大最小值:

Integer.MAX_VALUE; // int的最大值
Integer.MIN_VALUE; // int的最小值

字符转大小写:

Character.toUpperCase('a'); // 转为大写
Character.toLowerCase('A'); // 转为小写

整数转进制:

Integer.toBinaryString(10); // 转为二进制字符串
Integer.toHexString(10); // 转为十六进制字符串
Integer.toOctalString(10); // 转为八进制字符串

静态内部类

Java很少用到
意思就是类里面还有一个类,它可以通过静态调用

语法格式:

【修饰符】 class 外部类{
    【其他修饰符】 static class 内部类{
    }
}

举例

//外部类
class Outer{
	private static int a = 1;
	private int b = 2;

   //内部类:静态
	protected static class Inner{
		static int d = 4;//可以
		void inMethod(){
			System.out.println("out.a = " + a);
//			System.out.println("out.b = " + b);//错误的
		}
		static void inTest(){
			System.out.println("out.a = " + a);
		}
        static void inFun(int a){
			System.out.println("out.a = " + Outer.a);
            System.out.println("local.a = " + a);
		}
	}
}

使用

public class TestInner{
    public static void main(String[] args){
    
        //创建静态内部类
    	Outer.Inner in= new Outer.Inner();
    	in.inMethod();
    	
    	Outer.Inner.inTest();
        
        Outer.Inner.inFun(3);
    }
}

非静态内部类

Java很少用到
意思就是类里面还有一个类,它可以通过实例对象创建调用

语法格式

【修饰符】 class 外部类{
    【修饰符】 class 内部类{
    }
}

举例

//外部类
class Outer{
	private static int a = 1;
	private int b = 2;
	
   //内部类
	protected class Inner extends Father{
//		static int d = 4;//错误
		int b = 5;
		void inMethod(){
			System.out.println("out.a = " + a);
			System.out.println("out.b = " + Outer.this.b);
			System.out.println("in.b = " + b);
			System.out.println("father.c = " + c);
		}
	}
	
	public static void outMethod(){
//		Inner in = new Inner();//错误的
	}
	public Inner getInner(){
		return new Inner();
	}
}

使用

public class TestInner{
    public static void main(String[] args){
    	Outer out = new Outer();
    	Outer.Inner in= out.new Inner();
    	in.inMethod();
    	
    	Outer.Inner inner = out.getInner();
    	inner.inMethod();
    }
}

day13

局部内部类

Java很少用到

当我们在开发过程中,需要用到一个抽象类的子类的对象或一个接口的实现类的对象,而且只创建一个对象,而且逻辑代码也不复杂。那么我们原先怎么做的呢?

(1)编写类,继承这个父类或实现这个接口

(2)重写父类或父接口的方法

(3)创建这个子类或实现类的对象

例如:
协议类

public interface Runnable{
    public abstract void run();
}

类实现协议

//声明接口实现类
public class MyRunnable implements Runnable{
    public void run(){
        while(true){
            System.out.println("大家注意安全");
            try
            	Thread.sleep(1000);
            }catch(Exception e){                
            }
        }
    }
}

使用(以前的写法)

public class Test{
    public static void main(String[] args){
        //如果MyRunnable类只是在这里使用一次,并且只创建它的一个对象
        //分开两个.java源文件,反而不好维护
        Runnable target = new MyRunnable();
        Thread t = new Thread("安全提示线程",target);
        t.start();
    }
}

使用匿名类写法

public class Test{
    public static void main(String[] args){
        //MyRunnable类只是在这里使用一次,并且只创建它的一个对象,那么这些写代码更紧凑,更好维护
        Runnable target = new Runnable(){
            public void run(){
                while(true){
                    System.out.println("大家注意安全");
                    try
                        Thread.sleep(1000);
                    }catch(Exception e){                
                    }
                }
            }
        };
        Thread t = new Thread("安全提示线程",target);
        t.start();
    }
}

注解

注解是给编译器看的

@OVerride 重写方法,该方法来自父类继承

@Deprecated 过时不是不能使用,只是不推荐使用

用于表示被标记的数据已经过时,不建议使用,但是他的可以使用了,只是现在有更加好的方法去替代它
在这里插入图片描述

如果自己创建一个类用@Deprecated修饰,显示会出现过时的写画线,看下面的图片
在这里插入图片描述

@SuppressWarnings 抑制警告

@SuppressWarnings({“unused”,“rawtypes”, “unchecked”})

如果创建出来一个对象,然后不去使用,会出现警告的字样,如果你觉得难看想消除该警告,可以使用@SuppressWarnings修饰消除它。
在这里插入图片描述

day14 异常

运行异常

在这里插入图片描述

编译异常

文件不存在

在这里插入图片描述

异常处理 try { } catch { }

public class day01 {
    public static void main(String[] args){

        int [] arr = {10, 20, 30};

        try {
            System.out.println(arr[10]);
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        
        System.out.println("Game Over");
    }
}

在这里插入图片描述

举例1: 发生异常可以声明多个异常类型

import java.util.InputMismatchException;
import java.util.Scanner;

public class day01 {
    public static void main(String[] args){

        double sum = 0;

        double score[] = new double[2];

        try {
            Scanner in = new Scanner(System.in);
             for (int i = 0; i < score.length; i++) {
                 System.out.println("请输入第" + (i + 1) + "个学生的成绩");

                 double s = in.nextDouble();

                 score[i] = s;

                 sum += s;
             }
        } catch (ArrayIndexOutOfBoundsException | InputMismatchException e) {
            System.out.println("发生了异常: " + e.getMessage());
        }

        System.out.println("第一个学生的成绩是: " + score[0] + ",总分是: " + sum + ",平均分是:" +  sum/score.length);

    }
}

打印

在这里插入图片描述

可以多重catch

 try {
     
 } catch (ArrayIndexOutOfBoundsException i) {
     System.out.println("发生了异常: " + i.getMessage());
 } catch (InputMismatchException e) {
     System.out.println("发生了异常: " + e.getMessage());
 }

finally

注意:

  • 1.在程序没有发生异常时 有return 语句 要先执行finally 再执行返回操作

  • 2.在catch语句块内 进行return 那么也要先执行 finally 再执行返回操作

  • 3.如果在finally中存在 return,那么无论前面在那个位置 有return 都会返回finally中的return值。

finally 一般可用于输出关键信息

 try{
     
 }catch(...){
     
 }finally{
     无论try中是否发生异常,也无论catch是否捕获异常,也不管try和catch中是否有return语句,都一定会执行
 }

实例代码

import java.util.InputMismatchException;
import java.util.Scanner;

public class day01 {
    public static void main(String[] args){

        double sum = 0;

        double score[] = new double[2];

        try {
            Scanner in = new Scanner(System.in);
             for (int i = 0; i < score.length; i++) {
                 System.out.println("请输入第" + (i + 1) + "个学生的成绩");

                 double s = in.nextDouble();

                 score[i] = s;

                 sum += s;
             }
        } catch (ArrayIndexOutOfBoundsException | InputMismatchException e) {
            System.out.println("发生了异常: " + e.getMessage());
        } finally {
            System.out.println("finally 必须执行");
        }

        System.out.println("第一个学生的成绩是: " + score[0] + ",总分是: " + sum + ",平均分是:" +  sum/score.length);
        
    }
}

打印

在这里插入图片描述

throw 和 throws 抛出异常

throw 和 throws 抛出异常区别

关键字作用用法示例
throws方法签名 中声明异常,不处理,仅告知调用者方法名() throws 异常类型public void test() throws IOException {}
throw方法体内部 抛出具体异常实例throw new 异常对象;throw new IOException(“错误信息”);

就是写法上的区别
throw
在这里插入图片描述
throws
在这里插入图片描述

throw 方法体内抛出异常

throw 的用法就是谁给异常我,我就把异常还给谁,谁调用我出现异常的,我就给回谁,然后我就在调用我出现异常的地方使用try…catch…就可以了,看下面两个形象的例子

看下面的图片45行代码出现异常,它就把代码跑给调用它的38行
在这里插入图片描述
例如:
假设我们有一个方法,用于检查年龄是否合法。如果年龄小于0或大于120,我们抛出一个自定义异常 IllegalAgeException。

// 自定义异常类
class IllegalAgeException extends Exception {
    public IllegalAgeException(String message) {
        super(message);
    }
}

public class day01 {

    public static void main(String[] args) {
        try {
            validateAge(-5); // 这里会抛出异常
        } catch (IllegalAgeException e) {
            System.out.println("捕获异常: " + e.getMessage());
        }

        try {
            validateAge(130); // 这里也会抛出异常
        } catch (IllegalAgeException e) {
            System.out.println("捕获异常: " + e.getMessage());
        }

        try {
            validateAge(25); // 这里不会抛出异常,但仍需处理
        } catch (IllegalAgeException e) {
            System.out.println("捕获异常: " + e.getMessage());
        }

    }

    // 检查年龄的方法
    public static void validateAge(int age) throws IllegalAgeException {
        if (age < 0) {
            throw new IllegalAgeException("年龄不能为负数: " + age);
        } else if (age > 120) {
            throw new IllegalAgeException("年龄不能超过120岁: " + age);
        } else {
            System.out.println("年龄合法: " + age);
        }
    }
}

打印

在这里插入图片描述

方法外抛出异常

public class day01 {
    // 使用 throws 声明异常
    public static void validateAge(int age) throws IllegalArgumentException {
        if (age < 18) {
            // 使用 throw 抛出异常
            throw new IllegalArgumentException("年龄必须大于18岁");
        }
        System.out.println("年龄验证通过");
    }

    public static void main(String[] args) {
        try {
            validateAge(16);
        } catch (IllegalArgumentException e) {
            System.out.println("捕获异常: " + e.getMessage());
        }
    }
}

打印

在这里插入图片描述

异常方法的重写

意思就是父类有个抛出异常的方法,子类继承父类重写父类抛出异常的方法,需要注意是子类抛出的异常不能比父类抛出的异常大,看下面的异常关系和举例的例子,意思就是抛出异常范围可以平级或者缩小异常范围

异常关系图
在这里插入图片描述

class Parent {
    public void check() throws Exception {
        System.out.println("父类方法");
    }
}

class Child extends Parent {
    @Override
    public void check() throws IllegalAgeException { // 只能抛出比 Exception 更具体的异常
        System.out.println("子类方法");
    }
}

自定义异常

自定义异常就是系统打印的信息并不能满足自己的需求,想通过自己的加工和特俗的逻辑处理显示出来。

// 自定义异常类:余额不足异常
class InsufficientFundsException extends Exception {
    //InsufficientFundsException是继承Exception(并不是自定义)
    public InsufficientFundsException(String message) {
        super(message);
        //对异常的内容进行加工,写自己想写,但是本例子没有写自己的逻辑进行特殊的处理,而是自己使用父类的语句,super(message);
    }
}

// 银行账户类
class BankAccount {
    private String accountHolder;
    private double balance;

    public BankAccount(String accountHolder, double balance) {
        this.accountHolder = accountHolder;
        this.balance = balance;
    }

    // 取款方法
    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException("余额不足!当前余额:" + balance + ",尝试取款:" + amount);
        }
        balance -= amount;
        System.out.println("取款成功!剩余余额:" + balance);
    }
}

public class day01 {
    public static void main(String[] args) {
        BankAccount account = new BankAccount("张三", 500.0);

        try {
            account.withdraw(600); // 余额不足,抛出异常
        } catch (InsufficientFundsException e) {
            System.out.println("异常捕获:" + e.getMessage());
        }

        try {
            account.withdraw(200); // 取款成功
        } catch (InsufficientFundsException e) {
            System.out.println("异常捕获:" + e.getMessage());
        }
    }
}

打印

在这里插入图片描述

day 15 多线程

多线程

什么是线程?

什么是进程?

什么是串行?

什么是并发?

获取当前线程

System.out.println(Thread.currentThread().getName());

创建多线程运行 - 方法一 - 继承Thread类

自定义线程类:

public class MyThread extends Thread {
	//定义指定线程名称的构造方法
	public MyThread(String name) {
		//调用父类的String参数的构造方法,指定线程的名称
		super(name);
	}
	/**
	 * 重写run方法,完成该线程执行的逻辑
	 */
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(getName()+":正在执行!"+i);
		}
	}
}

测试类:

public class Demo01 {
	public static void main(String[] args) {
		//创建自定义线程对象
		MyThread mt = new MyThread("新的线程!");
		//开启新线程
		mt.start();
		//在主方法中执行for循环
		for (int i = 0; i < 10; i++) {
			System.out.println("main线程!"+i);
		}
	}
}

打印

在这里插入图片描述

创建多线程运行 - 方法二 - 实现Runnable接口

自定义线程类:

public class MyThread extends Thread {
	//定义指定线程名称的构造方法
	public MyThread(String name) {
		//调用父类的String参数的构造方法,指定线程的名称
		super(name);
	}
	/**
	 * 重写run方法,完成该线程执行的逻辑
	 */
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(getName()+":正在执行!"+i);
		}
	}
}

测试类:

public class day01 {
	public static void main(String[] args) {
		//创建自定义线程对象
		MyThread mt = new MyThread("新的线程!");
		//开启新线程
		mt.start();
		//在主方法中执行for循环
		for (int i = 0; i < 10; i++) {
			System.out.println("main线程!"+i);
		}
	}
}

打印
在这里插入图片描述

匿名类创建多线程 - 方法三

继承 Thread 类

public class ThreadDemo {
    public static void main(String[] args) {
        // 使用匿名类继承 Thread 并重写 run 方法
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("线程 1 正在运行:" + Thread.currentThread().getName());
            }
        };
        thread.start();
    }
}

实现 Runnable 接口(推荐)

public class RunnableDemo {
    public static void main(String[] args) {
        // 使用匿名类创建 Runnable 对象
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程 2 正在运行:" + Thread.currentThread().getName());
            }
        });
        thread.start();
    }
}

isAlive() 检测线程是否活跃

class MyThread extends Thread {
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 正在运行...");
        try {
            Thread.sleep(2000); // 休眠2秒,模拟线程运行过程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 运行结束");
    }
}

public class day01 {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        System.out.println("线程启动前,isAlive():" + thread.isAlive()); // false
        
        thread.start();
        System.out.println("线程启动后,isAlive():" + thread.isAlive()); // true
        
        try {
            Thread.sleep(3000); // 主线程等待足够时间,让子线程执行完
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("线程运行结束后,isAlive():" + thread.isAlive()); // false
    }
}

打印

在这里插入图片描述

线程的优先级

获取线程的优先级+设置线程的优先级

public class day01 {
    public static void main(String[] args) {
        // 创建线程
        Thread thread = new Thread(() -> {
            System.out.println("子线程运行中...");
            System.out.println("子线程优先级:" + Thread.currentThread().getPriority());
        });

        // 设置线程优先级
        thread.setPriority(Thread.MAX_PRIORITY); // 设置为最高优先级 (10)

        // 启动线程
        thread.start();

        // 主线程获取自身优先级
        System.out.println("主线程优先级:" + Thread.currentThread().getPriority());
    }
}

打印

在这里插入图片描述

join() 线程等待

它的作用是让调用 join() 方法的线程等待,直到该 Thread 对象执行完毕(即线程终止)采取执行下一个任务

有点想iOS GCD的调度组,执行A(接口获取数据) B(接口获取数据) 任务后才执行C任务(回来主线程刷新UI)

下面举例它的使用:

class MyThread extends Thread {
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 开始执行");
        try {
            Thread.sleep(2000);  // 模拟耗时操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 执行完毕");
    }
}

public class JoinExample {
    public static void main(String[] args) {
        Thread thread1 = new MyThread();
        Thread thread2 = new MyThread();

        thread1.start();
        thread2.start();

        try {
            thread1.join();  // 主线程等待 thread1 执行完毕
            thread2.join();  // 主线程等待 thread2 执行完毕
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("主线程继续执行");
    }
}

打印

在这里插入图片描述

volatile保证线程间的数据的可见性

Java中,volatile 是一种关键字,用于保证线程之间的 可见性,它可以确保当一个线程修改了某个变量的值,其他线程可以立即看到这个值的变化。

可见性解释:
默认情况下,Java中的变量在不同线程之间是不可见的。也就是说,线程A对变量的修改,线程B可能无法立即看到。这是由于JVM可能对内存做优化,比如缓存或重排序等。volatile 关键字通过禁止对变量的值进行缓存,从而确保变量的最新值对于所有线程是可见的。

使用场景:
当多个线程共享某个变量时,可以通过volatile来保证这个变量的最新值被其他线程及时更新和读取。常用于标志位、状态变量等简单数据。

如何使用:
在声明变量时,使用volatile关键字。例如:

private volatile boolean flag = false;

示例:

class VolatileExample {
    private static volatile boolean flag = false;

    public static void main(String[] args) throws InterruptedException {
        Thread writer = new Thread(() -> {
            try {
                // 模拟一些操作
                Thread.sleep(1000);
                flag = true;  // 修改共享变量
                System.out.println("Flag has been set to true.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread reader = new Thread(() -> {
            while (!flag) {
                // 只要flag是false,就一直循环
            }
            System.out.println("Flag has been detected as true.");
        });

        writer.start();
        reader.start();

        writer.join();
        reader.join();
    }
}

解释:

  • 在上面的代码中,flag 是一个共享的变量,writer线程修改了它的值,而reader线程则不断检查它的值。
  • 由于flag声明为volatile,所以当writer线程修改flag的值时,reader线程能够立即看到这个变化,从而跳出循环并打印消息。
  • 如果没有volatilereader线程可能不会立刻看到writer线程对flag的修改。

注意:
volatile只保证可见性,并不能保证原子性。如果多个线程同时修改一个变量,可能会导致问题。在这种情况下,需要使用synchronized 或其他同步机制来保证原子性。

yield() 线程的礼让

在Java中,yield()Thread 类的一个静态方法,表示线程的“礼让”行为。调用 yield() 方法的线程会暂停其执行并将 CPU 的使用权交给同优先级的其他线程,从而使得调度器有机会选择其他线程执行。它并不意味着当前线程立即被挂起或暂停,只是建议调度器放弃当前线程的 CPU 使用权。

如何使用 yield() 方法

在Java中,你可以直接在一个线程的执行过程中调用 Thread.yield() 来表示线程希望“让步”。代码示例如下:

public class day01 {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("Thread 1 - " + i);
                    Thread.yield();  // 线程在这里让步
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("Thread 2 - " + i);
                    Thread.yield();  // 线程在这里让步
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

打印

在这里插入图片描述

注意点

  • 1.并不是强制:yield() 只是建议当前线程让步,操作系统的线程调度器可能会忽略这个建议,当前线程有可能仍然继续执行。
  • 2.不等同于休眠:与 sleep() 不同,yield() 只是暂停当前线程的执行并释放 CPU 使用权,而 sleep() 会让线程进入休眠状态,并在指定时间后自动恢复执行。

总结来说,Thread.yield() 用于让当前线程“礼让”给其他线程,但这并不是一种强制的行为,具体的执行效果取决于系统的调度机制。

stop()(非常不建议使用)

stop()会强制终止线程的执行,并可能导致资源没有被正确释放,从而引发各种问题

public class day01 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (true) {
                    System.out.println("Thread running: " + count++);
                }
            }
        });
        thread.start();
        
        try {
            Thread.sleep(1000);  // 等待1秒钟
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 强制停止线程 (不推荐使用)
        thread.stop();  // 弃用,不推荐
    }
}

setDaemon(true)(守护线程.了解)

Thread.setDaemon(true)方法用于设置线程为守护线程。守护线程是在后台运行的线程,当所有非守护线程(用户线程)结束时,JVM会自动终止所有守护线程。守护线程通常用于执行一些后台任务,比如垃圾回收、定时任务等。

public class day01 {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("Daemon thread is running...");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        
        daemonThread.setDaemon(true);  // 设置为守护线程
        daemonThread.start();
        
        try {
            Thread.sleep(2000);  // 主线程休眠2秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        // 主线程结束后,守护线程也会自动停止
        System.out.println("Main thread finished");
    }
}

注意: 在调用setDaemon(true)时,必须在start()方法之前设置。如果在start()方法之后设置,会抛出IllegalThreadStateException异常。

打印

在这里插入图片描述

线程安全

举例子:售票处,3个窗口(开启异步线程)同时卖60张票,结果出现出乱,卖多了

同步代码块解决

public class TicketSale {
    private int tickets = 60; // 总共60张票

    // 定义售票窗口
    public void sellTicket() {
        // 使用同步代码块确保线程安全
        synchronized (this) { 
            if (tickets > 0) {
                tickets--;  // 卖出一张票
                System.out.println(Thread.currentThread().getName() + " sold 1 ticket. Remaining tickets: " + tickets);
            } else {
                System.out.println("No tickets left.");
            }
        }
    }

    public static void main(String[] args) {
        TicketSale ticketSale = new TicketSale();

        // 定义3个售票窗口(线程)
        Runnable sellTask = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    ticketSale.sellTicket();
                    try {
                        Thread.sleep(10); // 模拟销售过程中短暂的时间延迟
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        // 创建并启动3个线程
        Thread window1 = new Thread(sellTask, "Window 1");
        Thread window2 = new Thread(sellTask, "Window 2");
        Thread window3 = new Thread(sellTask, "Window 3");

        window1.start();
        window2.start();
        window3.start();
    }
}

打印

在这里插入图片描述

解释:
  • synchronized代码块:synchronized (this) 关键字用来锁定当前对象(this),确保在任何时刻只有一个线程能够进入该代码块,其他线程必须等待直到当前线程退出该块。
  • 票数操作:当一个线程进入同步代码块时,它会减少票数,并且在执行完成之前不会有其他线程可以操作票数,从而避免了超卖的情况。
  • 模拟多个窗口:通过创建多个线程模拟3个售票窗口。
结果:

当3个线程同时尝试售票时,由于使用了synchronized,每次只有一个线程能够修改tickets变量,避免了卖多票的情况。

这种方式能有效解决线性安全问题,确保线程安全地操作共享资源。

同步方法解决

public class day01 {
    private int count = 0;

    // 通过 synchronized 关键字保证方法是线程安全的
    public synchronized void increment() {
        count++;
    }

    // 通过 synchronized 保证获取 count 的时候数据是最新的
    public synchronized int getCount() {
        return count;
    }

    public static void main(String[] args) {
        day01 counter = new day01();

        // 创建多个线程同时对 count 进行操作
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        };

        // 创建两个线程执行任务
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();

        // 等待线程执行完
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 由于方法是同步的,最终 count 值应该是 2000
        System.out.println("Final Count: " + counter.getCount());
    }
}

在这里插入图片描述

横向比较:

上面两种方法的比较:
同步代码块 是在代码执行时仅对某个代码片段进行加锁,而 同步方法 是对整个方法加锁。同步代码块可以提高性能,因为它允许更精细地控制锁的范围,只锁住关键部分,而不是整个方法。它们的主要区别在于锁的粒度不同。

两种方法都属于:synchronized方法

解决线性安全还有很多方法:

方法适用场景线程安全
synchronized代码块/方法同步
ReentrantLock更灵活的锁控制
volatile可见性(非原子性操作)🚫
AtomicInteger计数器等简单操作
ThreadLocal线程独立变量
ConcurrentHashMap线程安全的Map
CopyOnWriteArrayList读多写少的场景
ExecutorService线程池管理
ForkJoinPool并行计算
Semaphore并发访问控制

单例模式

单例的写法有一下几种方式

方式线程安全懒加载推荐程度
饿汉式⭐⭐⭐
懒汉式(同步方法)⭐⭐
懒汉式(双重检查锁定)⭐⭐⭐
静态内部类⭐⭐⭐⭐
枚举⭐⭐⭐⭐⭐

示例:静态内部类(推荐,线程安全)

优点:类加载时不会初始化实例,只有调用 getInstance() 时才创建,避免了资源浪费,同时线程安全。

public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

使用

public class day01 {
    public static void main(String[] args) {
        // 获取单例对象
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();

        // 打印对象地址,验证是否是同一个实例
        System.out.println(singleton1);
        System.out.println(singleton2);

        // 判断两个对象是否相同
        System.out.println(singleton1 == singleton2);  // true
    }
}

打印

在这里插入图片描述

示例:懒汉式(线程安全) - 用到的时候才实例化对象

public class Singleton {
    private static Singleton instance;

    // 私有构造方法,防止外部实例化
    private Singleton() {}

    // 提供全局访问点(双重检查锁定,保证线程安全)
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    public void showMessage() {
        System.out.println("Hello, Singleton!");
    }
}

使用

public class day01 {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        singleton.showMessage();
    }
}

打印

在这里插入图片描述
本章结束…

下一篇:
Java 基础 整个基础班 day16 – day20 完整学习知识(代码+练习+打印结果)跟着手把手敲一遍就会

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冯汉栩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值