学习笔记:黑马程序员Java-中级篇(第二部分)

Java语言入门到精通章节

  1. 学习笔记:Java-基础篇(第一部分)_ljtxy.love的博客-CSDN博客
  2. 学习笔记:Java-中级篇(第二部分)_ljtxy.love的博客-CSDN博客
  3. 学习笔记:Java-高级篇(第三部分)_ljtxy.love的博客-CSDN博客
  4. 学习笔记:Java-进阶篇(一)(第四部分)_ljtxy.love的博客-CSDN博客
  5. 学习笔记:Java-进阶篇(二)(第五部分)_ljtxy.love的博客-CSDN博客
  6. 学习笔记:Java8新特性_ljtxy.love的博客-CSDN博客

文章目录

10.面向对象

笔记小结:见各个模块

10.1类

笔记小结:

  1. 含义:是一种对象的描述
  2. 定义:类的组成是由成员变量成员方法两部分组成

类的组成是由属性行为两部分组成

  • 属性:在类中通过成员变量来体现(类中方法外的变量)
  • 行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)

10.2方法

笔记小结:

  1. 含义:方法(method)是程序中最小的执行单元

  2. 方法的定义和调用

    • 无参数方法

      public static void 方法名 (   ) {
               
               
      	// 方法体;
      }
      // 注意,方法必须先定义,后调用,否则程序将报错
      
    • 带参数方法

      public static void 方法名 (参数1) {
               
               
      	方法体;
      }
      // 注意,方法调用时,参数的数量与类型必须与方法定义中的设置相匹配,否则程序将报错 
      
    • 带返回值方法

      public static 数据类型 方法名 ( 参数 ) {
               
                
      	return 数据 ;
      }
      // 注意,方法定义时return后面的返回值与方法定义上的数据类型要匹配
      
  3. 方法的注意事项

    • 方法不能嵌套定义
    • 定义方法时,需要明确方法返回值类型、明确方法参数个数
  4. 方法重载

    • 含义:一个类中多个方法,它们具有相方法名参数列表不同个数不同、类型不同或顺序不同)

    • 格式:

      public class MethodDemo {
               
               
      	public static void fn(int a) {
               
               
          	//方法体
          }
          public static int fn(double a) {
               
               
          	//方法体
          }
      }
      
  5. 构造方法

    • 含义:它是一种特殊的方法,用于创建并初始化对象

    • 格式:

      class Student {
               
               
          private String name;
          private int age;
      
          //构造方法
          public Student() {
               
               
              System.out.println("无参构造方法");
          }
      }
      
  6. 标准类

    ① 类名需要见名知意

    ② 成员变量使用private修饰

    ③ 提供至少两个构造方法

    ④ get和set方法

    ⑤ 如果还有其他行为,也需要写上

10.2.1概述

方法(method)是程序中最小的执行单元

注意:

  • 方法必须先创建才可以使用,该过程成为方法定义
  • 方法创建后并不是直接可以运行的,需要手动使用后,才执行,该过程成为方法调用

10.2.3定义和调用

10.2.3.1无参数方法定义和调用

基本用例

/*格式:
	public static void 方法名 (   ) {
	// 方法体;
} */

public static void method (    ) {
    
    
    // 方法体;
}

// 调用
/*格式:
	方法名();	*/
method();

注意:

​ 方法必须先定义,后调用,否则程序将报错

10.2.3.2带参数方法定义和调用

基本用例

/* 格式:
public static void 方法名 (参数1) {
	方法体;
}

public static void 方法名 (参数1, 参数2, 参数3...) {
	方法体;
}	*/

public static void isEvenNumber(int number){
    
    
    ...
}
public static void getMax(int num1, int num2){
    
    
    ...
}

// 调用

/* 格式:
方法名(参数);

方法名(参数1,参数2);	*/
isEvenNumber(10);

getMax(10,20);

说明:

​ 参数是由数据类型和变量名组成 - 数据类型 变量名 例如:int a

注意:

  • 方法定义时,参数中的数据类型与变量名都不能缺少,缺少任意一个程序将报错

  • 方法定义时,多个参数之间使用逗号( ,)分隔

  • 方法调用时,参数的数量与类型必须与方法定义中的设置相匹配,否则程序将报错

形参和实参

  1. 形参:方法定义中的参数

​ 等同于变量定义格式,例如:int number

  1. 实参:方法调用中的参数

​ 等同于使用变量或常量,例如: 10 number

10.2.3.3带返回值方法的定义和调用

基本用例

/*格式:public static 数据类型 方法名 ( 参数 ) { 
	return 数据 ;
} */
public static boolean isEvenNumber( int number ) {
    
               
	return true ;
}
public static int getMax( int a, int b ) {
    
    
	return  100 ;
}

// 调用

/* 方法名 ( 参数 ) ;
   数据类型 变量名 = 方法名 ( 参数 ) ; */
isEvenNumber ( 5 ) ;
boolean  flag =  isEvenNumber ( 5 ); 

注意:

  • 方法定义时return后面的返回值与方法定义上的数据类型要匹配,否则程序将报错
  • 方法的返回值通常会使用变量接收,否则该返回值将无意义

10.2.4方法的注意事项

10.2.4.1方法不能嵌套定义

  • 示例代码:

    public class MethodDemo {
          
          
        public static void main(String[] args) {
          
          
    
        }
    
        public static void methodOne() {
          
          
    		public static void methodTwo() {
          
          
           		// 这里会引发编译错误!!!
        	}
        }
    }
    

10.2.4.2void表示无返回值

可以省略return,也可以单独的书写return,后面不加数据

  • 示例代码:

    public class MethodDemo {
          
          
        public static void main(String[] args) {
          
          
    
        }
        public static void methodTwo() {
          
          
            //return 100; 编译错误,因为没有具体返回值类型
            return;	
            //System.out.println(100); return语句后面不能跟数据或代码
        }
    }
    

10.2.4.3方法的通用格式

格式:

public static 返回值类型 方法名(参数) {
    
    
   方法体; 
   return 数据 ;
}

说明:

  • public static 修饰符,目前先记住这个格式

  • 返回值类型

    • 方法操作完毕之后返回的数据的数据类型
    • 如果方法操作完毕,没有数据返回,这里写void,而且方法体中一般不写return
  • 方法名 调用方法时候使用的标识

  • 参数 由数据类型和变量名组成,多个参数之间用逗号隔开

  • 方法体 完成功能的代码块

  • return 如果方法操作完毕,有数据返回,用于把数据返回给调用者

  • 定义方法时,要做到两个明确

    • 明确返回值类型:主要是明确方法操作完毕之后是否有数据返回,如果没有,写void;如果有,写对应的数据类型
    • 明确参数:主要是明确参数的类型和数量
  • 调用方法时的注意:

    • void类型的方法,直接调用即可
    • 非void类型的方法,推荐用变量接收调用

10.2.5方法重载

10.2.5.1概述

​ 方法重载指同一个类中定义的多个方法之间的关系

满足下列条件的多个方法相互构成重载

  • 多个方法在同一个类中
  • 多个方法具有相同的方法名
  • 多个方法的参数不相同,类型不同或者数量不同

注意:

  • 重载仅对应方法的定义,与方法的调用无关,调用方式参照标准格式
  • 重载仅针对同一个类中方法的名称与参数进行识别,与返回值无关,换句话说不能通过返回值来判定两个方法是否相互构成重载

10.2.5.2基本用例

  • 正确范例:

    public class MethodDemo {
          
          
    	public static void fn(int a) {
          
          
        	//方法体
        }
        public static int fn(double a) {
          
          
        	//方法体
        }
    }
    
    public class MethodDemo {
          
          
    	public static float fn(int a) {
          
          
        	//方法体
        }
        public static int fn(int a , int b) {
          
          
        	//方法体
        }
    }
    
  • 错误范例:

    public class MethodDemo {
          
          
    	public static void fn(int a) {
          
          
        	//方法体
        }
        public static int fn(int a) {
          
           	/*错误原因:重载与返回值无关*/
        	//方法体
        }
    }
    
    public class MethodDemo01 {
          
          
        public static void fn(int a) {
          
          
            //方法体
        }
    } 
    public class MethodDemo02 {
          
          
        public static int fn(double a) {
          
           /*错误原因:这是两个类的两个fn方法*/
            //方法体
        }
    }
    

10.2.6构造方法

10.2.6.1概述

构造方法是一种特殊的方法

  • 作用:创建对象 Student stu = new Student();
  • 功能:主要是完成对象数据的初始化

10.2.6.2基本用例

/* 格式:
	public class 类名{
       修饰符 类名( 参数 ) {
        }
} */

class Student {
    
    
    private String name;
    private int age;

    //构造方法
    public Student() {
    
    
        System.out.println("无参构造方法");
    }

    public void show() {
    
    
        System.out.println(name + "," + age);
    }
}
/*
    测试类
 */
public class StudentDemo {
    
    
    public static void main(String[] args) {
    
    
        //创建对象
        Student s = new Student();
        s.show();
    }
}

10.2.6.3注意事项

  • 构造方法的创建

如果没有定义构造方法,系统将给出一个默认的无参数构造方法
如果定义了构造方法,系统将不再提供默认的构造方法

  • 构造方法的重载

如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法

  • 推荐的使用方式

无论是否使用,都手工书写无参数构造方法

  • 重要功能

可以使用带参构造,为成员变量进行初始化

  • 示例代码
/*
    学生类
 */
class Student {
    
    
    private String name;
    private int age;

    public Student() {
    
    }

    public Student(String name) {
    
    
        this.name = name;
    }

    public Student(int age) {
    
    
        this.age = age;
    }

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

    public void show() {
    
    
        System.out.println(name + "," + age);
    }
}
/*
    测试类
 */
public class StudentDemo {
    
    
    public static void main(String[] args) {
    
    
        //创建对象
        Student s1 = new Student();
        s1.show();

        //public Student(String name)
        Student s2 = new Student("林青霞");
        s2.show();

        //public Student(int age)
        Student s3 = new Student(30);
        s3.show();

        //public Student(String name,int age)
        Student s4 = new Student("林青霞",30);
        s4.show();
    }
}

10.2.6.4标准类制作

① 类名需要见名知意

② 成员变量使用private修饰

③ 提供至少两个构造方法

  • 无参构造方法
  • 带全部参数的构造方法

④ get和set方法

​ 提供每一个成员变量对应的setXxx()/getXxx()

⑤ 如果还有其他行为,也需要写上

10.3对象

笔记小结:

  1. 含义:对象是指一个具体的实例化的实体,就是一种客观存在的事物

  2. 使用:

    格式:类名 对象名 = new 类名();
    // 例如
    Phone p = new Phone();
    
  3. 内存图:

    • 单个对象内存图
      1. new出来的对象会存在堆内存
      2. 执行的方法会存在栈内存
    • 多个对象内存图
      1. 多个对象在堆内存中,都有不同的内存划分。其中,成员变量存储在各自的内存区域中
      2. 成员方法中,多个对象共用的一份栈内存

10.3.1概述

对象就是一种客观存在的事物,客观存在的事物皆为对象 ,所以我们也常常说万物皆对象。

    • 类的理解
      • 类是对现实生活中一类具有共同属性和行为的事物的抽象
      • 类是对象的数据类型,类是具有相同属性行为的一组对象的集合
      • 简单理解:类就是对现实事物的一种描述
    • 类的组成
      • 属性:指事物的特征,例如:手机事物(品牌,价格,尺寸)
      • 行为:指事物能执行的操作,例如:手机事物(打电话,发短信)
  • 类和对象的关系
    • 类:类是对现实生活中一类具有共同属性和行为的事物的抽象
    • 对象:是能够看得到摸的着的真实存在的实体
    • 简单理解:类是对事物的一种描述,对象则为具体存在的事物

10.3.2基本用例

/*格式:
	创建对象
		类名 对象名 = new 类名();*/

/*格式:
   使用成员变量
      格式:对象名.变量名
   使用成员方法
      格式:对象名.方法名()
             */
public class PhoneDemo {
    
    
    public static void main(String[] args) {
    
    
        //创建对象
        Phone p = new Phone();

        //使用成员变量
        System.out.println(p.brand);
        System.out.println(p.price);

        p.brand = "小米";
        p.price = 2999;

        System.out.println(p.brand);
        System.out.println(p.price);

        //使用成员方法
        p.call();
        p.sendMessage();
    }
}

10.3.3对象内存图(重点)

10.3.3.1单个对象内存图

  • 成员变量使用过程

    image-20230813145155388

  • 成员方法调用过程

    image-20230813145205253

10.3.3.2多个对象内存图

  • 成员变量使用过程

    image-20230813145212598

  • 成员方法调用过程

    image-20230813145218840

  • 总结:

    多个对象在堆内存中,都有不同的内存划分,成员变量存储在各自的内存区域中,成员方法多个对象共用的一份堆内存空间

10.4封装

笔记小结:

  1. 含义:指隐藏对象的内部实现细节,并将其暴露出来可以使用的公共接口
  2. 作用:
    • 隐藏实现细节
    • 简化编程
    • 提高代码复用性
    • 接口隔离

10.4.1概述

​ 在 Java 中,封装是面向对象编程中的一种重要的概念,它指的是将类的属性和方法保护起来,以避免外部程序直接访问和修改它们,从而提高了类的安全性和可维护性。封装的实现可以通过访问控制修饰符(public、private、protected)来实现。

10.4.2基本用例

public class Person {
    
    
    private String name;
    private int age;
    private double height;

    public String getName() {
    
    
        return name;
    }

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

    public int getAge() {
    
    
        return age;
    }

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

    public double getHeight() {
    
    
        return height;
    }

    public void setHeight(double height) {
    
    
        this.height = height;
    }
}

说明:

​ 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问

10.5继承

笔记小结:

  1. 概述:

    • 含义:就是子类继承父类的属性行为,使得子类对象可以直接具有与父类相同的属性、相同的行为

    • 优点:

      1. 代码重用
      2. 可以实现多态性
      3. 代码可维护性高
      4. 代码可扩展性强
      5. 代码复用性增强
    • 格式;

      class 父类 {
               
               
      	...
      }
      
      class 子类 extends 父类 {
               
               
      	...
      }
      // 子类 extends 父类
      
  2. 注意事项:子类不能继承的内容

    • 构造方法:非私有(不能),私有(不能)
    • 成员变量非私有(能),私有(能)
    • 成员方法虚方法表(能),否则(不能)
    • 注意:子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量
  3. 继承后的特点:

    • 成员变量:

      1. 成员变量不重名不影响

      2. 成员变量重名:子类会优先访问自己对象中的成员变量

      3. 要想在子类访问父类成员变量可通过super关键字

        // 例如
        super.school
        
    • 成员方法:

      1. 成员方法不重名不影响
      2. 成员方法重名:子类会优先访问自己对象中的成员方法
      3. 方法重写:
        • 含义:声明不变,重新实现
        • @Override:注解,重写注解校验
        • 注意:
          1. 必须是子类与父类的关系
          2. 权限大于等于父类权限
          3. 返回值类型、函数参数列表需要一样
    • 构造方法:

      • 特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法

      • 子类构造方法的第一行都隐含了一个**super()**去调用父类无参数构造方法,**super()**可以省略不写

        public Student() {
                   
                   
            //super(); // 调用父类无参,默认就存在,可以不写,必须再第一行
            System.out.println("子类无参");
        }
        
  4. 特点:

    • Java只支持单继承,不支持多继承
    • 一个类可以有多个子类
    • 继承之间可以形成多层继承

10.5.1概述

10.5.1.1定义

​ 继承就是子类继承父类的属性行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。从而提高代码的复用性(减少代码冗余,相同代码重复利用)。使类与类之间产生了关系。

10.5.1.2子类不能继承的内容

说明:

  • 子类中不能继承构造方法,不能继承非虚方法表里的方法

  • 虚方法表就算非 static、final、private修饰的方法

image-20230303195211274

说明:

​ Java的继承,并不是一层一层往上寻找父方法,而是先判断虚方法表中有无此方法,方便直接调用

注意

​ 值得注意的是子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量

10.5.2基本用例

说明:

​ 类继承用法,基础使用

通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:

class 父类 {
    
    
	...
}

class 子类 extends 父类 {
    
    
	...
}

注意:

​ Java是单继承的,一个类只能继承一个直接父类

10.5.4继承后的特点—成员变量

10.5.4.1成员变量不重名

如果子类父类中出现不重名的成员变量,这时的访问是没有影响的。代码如下:

class Fu {
    
    
	// Fu中的成员变量
	int num = 5;
}
class Zi extends Fu {
    
    
	// Zi中的成员变量
	int num2 = 6;
  
	// Zi中的成员方法
	public void show() {
    
    
		// 访问父类中的num
		System.out.println("Fu num="+num); // 继承而来,所以直接访问。
		// 访问子类中的num2
		System.out.println("Zi num2="+num2);
	}
}
class Demo04 {
    
    
	public static void main(String[] args) {
    
    
        // 创建子类对象
		Zi z = new Zi(); 
      	// 调用子类中的show方法
		z.show();  
	}
}

演示结果:
Fu num = 5
Zi num2 = 6

10.5.4.2成员变量重名

如果子类父类中出现重名的成员变量,这时的访问是有影响的。代码如下:

class Fu1 {
    
    
	// Fu中的成员变量。
	int num = 5;
}
class Zi1 extends Fu1 {
    
    
	// Zi中的成员变量
	int num = 6;
  
	public void show() {
    
    
		// 访问父类中的num
		System.out.print9ln("Fu num=" + num);
		// 访问子类中的num
		System.out.println("Zi num=" + num);
	}
}
class Demo04 {
    
    
	public static void main(String[] args) {
    
    
      	// 创建子类对象
		Zi1 z = new Zi1(); 
      	// 调用子类中的show方法
		z1.show(); 
	}
}
演示结果:
Fu num = 6
Zi num = 6

说明:

  • 子父类中出现了同名的成员变量时,子类会优先访问自己对象中的成员变量。如果此时想访问父类成员变量如何解决呢?我们可以使用super关键字。

  • 例如

    class Fu {
           
           
    	// Fu中的成员变量。
    	int num = 5;
    }
    
    class Zi extends Fu {
           
           
    	// Zi中的成员变量
    	int num = 6;
    
    	public void show() {
           
           
            int num = 1;
    
            // 访问方法中的num
            System.out.println("method num=" + num);  //method num=1
            // 访问子类中的num
            System.out.println("Zi num=" + this.num); // Zi num=6
            // 访问父类中的num
            System.out.println("Fu num=" + super.num); // Fu num=5
    	}
    }
    
    class Demo04 {
           
           
    	public static void main(String[] args) {
           
           
          	// 创建子类对象
    		Zi1 z = new Zi1(); 
          	// 调用子类中的show方法
    		z1.show(); 
    	}
    }
    

    需要注意的是:super代表的是父类对象的引用,this代表的是当前对象的引用。

10.5.5继承后的特点—成员方法

10.5.5.1成员方法不重名

​ 如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。代码如下:

class Fu {
    
    
	public void show() {
    
    
		System.out.println("Fu类中的show方法执行");
	}
}
class Zi extends Fu {
    
    
	public void show2() {
    
    
		System.out.println("Zi类中的show2方法执行");
	}
}
public  class Demo05 {
    
    
	public static void main(String[] args) {
    
    
		Zi z = new Zi();
     	//子类中没有show方法,但是可以找到父类方法去执行
		z.show(); 
		z.show2();
	}
}

10.5.5.2成员方法重名

如果子类父类中出现重名的成员方法,则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法。

代码如下:

class Fu {
    
    
	public void show() {
    
    
		System.out.println("Fu show");
	}
}
class Zi extends Fu {
    
    
	//子类重写了父类的show方法
	public void show() {
    
    
		System.out.println("Zi show");
	}
}
public class ExtendsDemo05{
    
    
	public static void main(String[] args) {
    
    
		Zi z = new Zi();
     	// 子类中有show方法,只执行重写后的show方法
		z.show();  // Zi show
	}
}

10.5.7方法重写

10.5.7.1概念

方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现

10.5.7.2使用场景与案例

​ 发生在子父类之间的关系。
​ 子类继承了父类的方法,但是子类觉得父类的这方法不足以满足自己的需求,子类重新写了一个与父类同名的方法,以便覆盖父类的该方 法。

10.5.7.3@Override重写注解

  • @Override:注解,重写注解校验!

  • 这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。

  • 建议重写都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错!

10.5.7.4注意事项

  1. 方法重写是发生在子父类之间的关系
  2. 子类方法覆盖父类方法,必须要保证权限大于等于父类权限
  3. 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。

10.5.8继承后的特点—构造方法

笔记小结

概述:子类构造方法的第一行都隐含了一个**super()**去调用父类无参数构造方法,**super()**可以省略不写。

10.5.8.1概述

当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?
首先我们要回忆两个事情,构造方法的定义格式和作用。

  1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
  2. 构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。(先有爸爸,才能有儿子
  3. 子类重写父类方法时,访问权限子类必须大于等于父类
  4. 子类重写父类方法时,返回值类型子类必须小于等于父类
  5. 建议:重写的方法尽量和父类保持一致。

继承后子类构方法器特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法

10.5.8.2案例-人际关系

按如下需求定义类:

  1. 人类
    成员变量: 姓名,年龄
    成员方法: 吃饭
  2. 学生类
    成员变量: 姓名,年龄,成绩
    成员方法: 吃饭

代码如下:

class Person {
    
    
    private String name;
    private int age;

    public Person() {
    
    
        System.out.println("父类无参");
    }

    // getter/setter省略
}

class Student extends Person {
    
    
    private double score;

    public Student() {
    
    
        //super(); // 调用父类无参,默认就存在,可以不写,必须再第一行
        System.out.println("子类无参");
    }
    
     public Student(double score) {
    
    
        //super();  // 调用父类无参,默认就存在,可以不写,必须再第一行
        this.score = score;    
        System.out.println("子类有参");
     }

}

public class Demo07 {
    
    
    public static void main(String[] args) {
    
    
        Student s1 = new Student();
        System.out.println("----------");
        Student s2 = new Student(99.9);
    }
}

输出结果:
父类无参
子类无参
----------
父类无参
子类有参

小结

  • 子类构造方法执行的时候,都会在第一行默认先调用父类无参数构造方法一次。
  • 子类构造方法的第一行都隐含了一个**super()**去调用父类无参数构造方法,**super()**可以省略不写。

10.5.9继承的特点

  1. Java只支持单继承,不支持多继承。
// 一个类只能有一个父类,不可以有多个父类。
class A {
    
    }
class B {
    
    }
class C1 extends A {
    
    } // ok
// class C2 extends A, B {} // error
  1. 一个类可以有多个子类。
// A可以有多个子类
class A {
    
    }
class C1 extends A {
    
    }
class C2 extends  A {
    
    }
  1. 可以多层继承。
class A {
    
    }
class C1 extends A {
    
    }
class D extends C1 {
    
    }

说明:

​ 顶层父类是Object类。所有的类默认继承Object,作为父类。

10.6多态

笔记小结:

  1. 概述:

    • 定义:指同一个方法或者同一个类,在不同的情况下具有不同表现形式

    • 注意:满足多态的前提是有继承或者实现关系

  2. 格式:父new子

    父类类型 变量名 = new 子类/实现类构造器;
    变量名.方法名();
    
    // 多态前提,有继承关系,子类对象是可以赋值给父类类型的变量.
    
  3. 使用场景

    • 父new子
    • 父new接口
  4. 特点

    • 调用成员变量时:编译看左边,运行看左边
    • 调用成员方法时:编译看左边,运行看右边
  5. 内存图

    image-20230813145339502

  6. 多态优点:

    • 在多态形式下,右边对象可以实现解耦合,便于扩展和维护
    • 定义方法的时候,使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性与便利
  7. 多态缺点:无法直接访问子类特有的成员,也就是多态的写法无法访问子类独有功能,也就是说,父类不能调用子类的特有方法

  8. 引用类型转换:

    • 含义:将一个对象的引用从一个类型转换为另一个类型

    • 向上转型:将一个子类对象的引用转换为其超类接口类型的引用

      • 例如

        父类类型  变量名 = new 子类类型();
        Animal a = new Cat();
        
    • 向下转型:将一个超类或接口类型的引用转换为一个子类对象的引用

      • 例如

        子类类型 变量名 = (子类类型) 父类变量名;
        Aniaml a = new Cat();
        Cat c =(Cat) a;  
        
  9. instanceof关键字

    • 含义:用于测试一个对象是否是一个类的实例,或者是该类的子类或接口的实例。简单来说就是类型校验
    变量名 instanceof 数据类型 
    /*
    如果变量属于该数据类型或者其子类类型,返回true。
    如果变量不属于该数据类型或者其子类类型,返回false。*/
    

10.6.1概述

10.6.1.1定义

多态是指同一行为,具有多个不同表现形式。

多态是继封装、继承之后,面向对象的第三大特性。

多态是出现在继承或者实现关系中的。

10.6.1.2前提【重点】

  1. 继承或者实现关系

  2. 方法的重写【意义体现:不重写,无意义】

  3. 父类引用指向子类对象【格式体现】

说明:

​ 父类类型:指类对象继承类类型,或者实现父接口类型。

10.6.1.3格式

父类类型 变量名 = new 子类/实现类构造器;
变量名.方法名();

10.6.1.4使用场景

​ 如果没有多态,在下图中register方法只能传递学生对象,其他的Teacher和administrator对象是无法传递给register方法方法的,在这种情况下,只能定义三个不同的register方法分别接收学生,老师和管理员。

image-20230811093510641

有了多态之后,方法的形参就可以定义为共同的父类Person。

注意:

  • 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。
  • 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象
  • 而且多态还可以根据传递的不同对象来调用不同类中的方法。

image-20230811094030529

10.6.2基本用例

步骤一:创建父类

  • 创建Person实体类
public class Person {
    
    
    private String name;
    private int age;

    空参构造
    带全部参数的构造
    get和set方法

    public void show(){
    
    
        System.out.println(name + ", " + age);
    }
}

步骤二:创建子类

1.创建Administrator实体类,并继承Person

public class Administrator extends Person {
    
    
    @Override
    public void show() {
    
    
        System.out.println("管理员的信息为:" + getName() + ", " + getAge());
    }
}

2.创建Student实体类,并继承Person

public class Student extends Person{
    
    

    @Override
    public void show() {
    
    
        System.out.println("学生的信息为:" + getName() + ", " + getAge());
    }
}

3.创建Teacher实体类,并继承Person

public class Teacher extends Person{
    
    

    @Override
    public void show() {
    
    
        System.out.println("老师的信息为:" + getName() + ", " + getAge());
    }
}

步骤三:演示

  • 创建Test用于演示register方法

注意:

register方法的参数需要为Person父类,这样才能在参数中传入子类对象

public class Test {
    
    
    public static void main(String[] args) {
    
    
        //创建三个对象,并调用register方法

        Student s = new Student();
        s.setName("张三");
        s.setAge(18);


        Teacher t = new Teacher();
        t.setName("王建国");
        t.setAge(30);

        Administrator admin = new Administrator();
        admin.setName("管理员");
        admin.setAge(35);



        register(s);
        register(t);
        register(admin);


    }



    //这个方法既能接收老师,又能接收学生,还能接收管理员
    //只能把参数写成这三个类型的父类
    public static void register(Person p){
    
    
        p.show();
    }
}

10.6.3运行特点

  • 调用成员变量时:编译看左边,运行看左边

  • 调用成员方法时:编译看左边,运行看右边

调用成员变量时:编译看左边,运行看左边

说明:

  • 编译看左边:javac编译代码的时候,会看左边的父类中有没有这个变量,如果编译成功,如果没有编译失败。

  • 运行看左边:Java运行代码的时候,若父类与子类都存在相同名字的变量,那么则调用左边,也就是父类中的变量

调用成员方法时:编译看左边,运行看右边

说明:

  • 编译看左边:javac编译代码的时候,会看左边的父类中有没有这个变量,如果编译成功,如果没有编译失败。

  • 运行看右边:Java运行代码的时候,若父类与子类都存在相同名字的方法,那么则运行右边,也就是子类中的方法

代码示例:

Fu f = new Zi()//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);
//编译看左边的父类中有没有show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();

说明:

  • 成员变量:在子类的对象中,会把父类的成员变量也继承下的。父: name子: name
  • 成员方法:如果子类对方法进行了重写,那么在虚方法表中是会把父类的方法进行覆盖

10.6.4调用成员的内存图解(重点)

详细链接:https://www.bilibili.com/video/BV17F411T7Ao?p=130image-20230303205114437

结论:

  • 调用成员变量的特点:编译看左边,运行也看左边

  • 调用成员方法的特点:编译看左边,运行看右边

10.6.5多态的优势

  • 在多态形式下,右边对象可以实现解耦合,便于扩展和维护
Person p =new Student ();
p.work();//业务逻辑发生改变时,后续代码无需修改
  • 定义方法的时候,使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性与便利。
ArrayList list = new ArrayList();
list.add(student);
list.add("ok");
System.out.println(list);

说明:

​ 当不指定ArrayList的泛型时,添加的数据类型将不受到限制

10.6.6多态的弊端

​ 我们已经知道多态编译阶段是看左边父类类型的,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了

class Animal{
    
    
    public  void eat()System.out.println("动物吃东西!")}
class Cat extends Animal {
    
      
    public void eat() {
    
      
        System.out.println("吃鱼");  
    }  
   
    public void catchMouse() {
    
      
        System.out.println("抓老鼠");  
    }  
}  

class Dog extends Animal {
    
      
    public void eat() {
    
      
        System.out.println("吃骨头");  
    }  
}

class Test{
    
    
    public static void main(String[] args){
    
    
        Animal a = new Cat();
        a.eat();
        a.catchMouse();//编译报错,编译看左边,Animal没有这个方法
    }
}

10.6.7引用类型转换

10.6.7.1概述

​ 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。

回顾基本数据类型转换

  • 自动转换: 范围小的赋值给范围大的.自动完成:double d = 5;
  • 强制转换: 范围大的赋值给范围小的,强制转换:int i = (int)3.14

​ 多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种。

10.6.7.2向上转型(自动转换)

向上转型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。
当父类引用指向一个子类对象时,便是向上转型。

  • 使用格式:
父类类型  变量名 = new 子类类型();
如:Animal a = new Cat();

**原因是:父类类型相对与子类来说是大范围的类型,Animal是动物类,是父类类型。Cat是猫类,是子类类型。Animal类型的范围当然很大,包含一切动物。**所以子类范围小可以直接自动转型给父类类型的变量。

10.6.7.3向下转型(强制转换)

向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。
一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。

基本用例

/* 格式:
子类类型 变量名 = (子类类型) 父类变量名; */
Aniaml a = new Cat();
Cat c =(Cat) a;  

10.6.7.4案例-转型演示

​ 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。

步骤一:定义父类

  • 定义Animal作为父类
abstract class Animal {
    
      
    abstract void eat();  
}  

步骤二:定义子类

1.创建Cat实体类,并继承Animal

class Cat extends Animal {
    
      
    public void eat() {
    
      
        System.out.println("吃鱼");  
    }  
    public void catchMouse() {
    
      
        System.out.println("抓老鼠");  
    }  
}  

2.创建Dog实体类,并继承Animal

class Dog extends Animal {
    
      
    public void eat() {
    
      
        System.out.println("吃骨头");  
    }  
    public void watchHouse() {
    
      
        System.out.println("看家");  
    }  
}

步骤三:演示

public class Test {
    
    
    public static void main(String[] args) {
    
    
        // 向上转型  
        Animal a = new Cat();  
        a.eat(); 				// 调用的是 Cat 的 eat

        // 向下转型  
        Cat c = (Cat)a;       
        c.catchMouse(); 		// 调用的是 Cat 的 catchMouse
    }  
}

10.6.7.5转型的异常

转型的过程中,一不小心就会遇到这样的问题,请看如下代码:

public class Test {
    
    
    public static void main(String[] args) {
    
    
        // 向上转型  
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat

        // 向下转型  
        Dog d = (Dog)a;       
        d.watchHouse();        // 调用的是 Dog 的 watchHouse 【运行报错】
    }  
}

​ 这段代码可以通过编译,但是运行时,却报出了 ClassCastException ,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。

10.6.7.6instanceof关键字(重点)

​ 为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下:

变量名 instanceof 数据类型 
如果变量属于该数据类型或者其子类类型,返回true。
如果变量不属于该数据类型或者其子类类型,返回false

所以,转换前,我们最好先做一个判断,代码如下:

public class Test {
    
    
    public static void main(String[] args) {
    
    
        // 向上转型  
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat

        // 向下转型  
        if (a instanceof Cat){
    
    
            Cat c = (Cat)a;       
            c.catchMouse();        // 调用的是 Cat 的 catchMouse
        } else if (a instanceof Dog){
    
    
            Dog d = (Dog)a;       
            d.watchHouse();       // 调用的是 Dog 的 watchHouse
        }
    }  
}

10.6.7.7instanceof新特性

​ JDK14的时候提出了新特性,把判断和强转合并成了一行

//新特性
//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
//如果不是,则不强转,结果直接是false
if(a instanceof Dog d){
    
    
    d.lookHome();
}else if(a instanceof Cat c){
    
    
    c.catchMouse();
}else{
    
    
    System.out.println("没有这个类型,无法转换");
}

11.抽象类

笔记小结:

  1. 概述:

    • 定义:没有方法体的方法称为抽象方法,包含抽象方法的类就是抽象类

    • 抽象方法:没有方法体的方法

      // 抽象方法
      public abstract void abstractMethod();
      
    • 抽象类:包含抽象方法的类

      public abstract class AbstractClass {
               
               
          // 抽象方法
          public abstract void abstractMethod();
      }
      
    • 注意:抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类。

  2. abstract介绍

    • 使用

      // 被继承
      
    • 特征:

      1. 抽象类得到了拥有抽象方法的能力,也就是说自己的一套规范
      2. 抽象类失去了创建对象的能力,也就是说不能创建对象
    • 细节:

      • 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象
      • 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的
      • 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
      • 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错
      • 抽象类存在的意义是为了被子类继承
    • 意义:抽象类存在的意义是为了被子类继承,否则抽象类将毫无意义

    • 抽象类和接口区别,主要在于构造函数、成员变量、继承关系(单继承,多实现)

11.1概述

11.1.1定义

​ Java抽象类(Abstract Class)是一种特殊的类,它不能被实例化,只能被继承。抽象类用于定义一些基本行为,而具体的行为由其子类来实现。

11.1.2抽象方法

没有方法体的方法,也就是没有"{ }"的方法

11.1.3抽象类

包含抽象方法的类

11.1abstract关键字

11.1.1abstract含义

​ abstract是抽象的意思,用于修饰方法方法和类,修饰的方法是抽象方法,修饰的类是抽象类

11.1.2抽象方法

​ 使用abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。

基本用例:

/* 格式:
修饰符 abstract 返回值类型 方法名 (参数列表);*/
public abstract void run()

11.1.3抽象类

​ 如果一个类包含抽象方法,那么该类必须是抽象类。注意:抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类。

/* 格式:
abstract class 类名字 { 
  
} */
public abstract class Animal {
    
    
    public abstract void run()}

11.2基本用例

要求:继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。

// 父类,抽象类
abstract class Employee {
    
    
	private String id;
	private String name;
	private double salary;
	
	public Employee() {
    
    
	}
	
	public Employee(String id, String name, double salary) {
    
    
		this.id = id;
		this.name = name;
		this.salary = salary;
	}
	
	// 抽象方法
	// 抽象方法必须要放在抽象类中
	abstract public void work();
}

// 定义一个子类继承抽象类
class Manager extends Employee {
    
    
	public Manager() {
    
    
	}
	public Manager(String id, String name, double salary) {
    
    
		super(id, name, salary);
	}
	// 2.重写父类的抽象方法
	@Override
	public void work() {
    
    
		System.out.println("管理其他人");
	}
}

// 定义一个子类继承抽象类
class Cook extends Employee {
    
    
	public Cook() {
    
    
	}
	public Cook(String id, String name, double salary) {
    
    
		super(id, name, salary);
	}
	@Override
	public void work() {
    
    
		System.out.println("厨师炒菜多加点盐...");
	}
}

// 测试类
public class Demo10 {
    
    
	public static void main(String[] args) {
    
    
		// 创建抽象类,抽象类不能创建对象
		// 假设抽象类让我们创建对象,里面的抽象方法没有方法体,无法执行.所以不让我们创建对象
//		Employee e = new Employee();
//		e.work();
		
		// 3.创建子类
		Manager m = new Manager();
		m.work();
		
		Cook c = new Cook("ap002", "库克", 1);
		c.work();
	}
}

说明:

​ 此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法

11.3特征

抽象类的特征总结起来可以说是 有得有失

有得:抽象类得到了拥有抽象方法的能力。

有失:抽象类失去了创建对象的能力。

其他成员(构造方法,实例方法,静态方法等)抽象类都是具备的。

11.4细节

不需要背,只要当idea报错之后,知道如何修改即可。

关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

    理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

  2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

    理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

  3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

    理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

  4. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。

    理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

  5. 抽象类存在的意义是为了被子类继承

    理解:抽象类中已经实现的是模板中确定的成员,抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。

11.5意义

​ 抽象类存在的意义是为了被子类继承,否则抽象类将毫无意义。抽象类可以强制让子类,一定要按照规定的格式进行重写

11.6.抽象类和接口的区别

抽象类 接口
方法实现 可以有实现的方法和非抽象方法 只有方法签名,无方法实现
构造函数 可以构造函数 法定义构造函数
成员变量 可以成员变量 只能定义常量,成员变量
继承关系 子类只能继承一个抽象类 类可以实现多个接
功能实现 提供对类的部分实现 定义契约和行为规范

12.接口

笔记小结:

  1. 概述

    • 含义:接口是一种规范或契约,它只定义了方法签名、常量以及嵌套类型的声明,没有方法实现或属性

    • 格式:

      //接口的定义格式:
      interface 接口名称{
               
               
          // 抽象方法
      }
      // 接口的声明:interface
      // 接口名称:首字母大写,满足“驼峰模式”
      
    • 特点:

      1. 抽象方法:会自动加上public abstract修饰
      2. 常量:会自动加上 public static final修饰
  2. 基本实现

    • 实现方式:使用 implements关键字

    • 格式:

      /**接口的实现:
          在Java中接口是被实现的,实现接口的类称为实现类。
          实现类的格式:*/
      class 类名 implements 接口1,接口2,接口3...{
               
               
      }
      
    • 要求:接口体现的是一种规范,接口对实现类是一种强制性的约束。需要强制重写或定义为抽象类

  3. 接口与接口的多继承

    • 含义:一个接口可以继承另一个或多个接口,这被称为接口的多继承

    • 格式:

      public interface SportMan extends Law , Abc {
               
               
          void run();
      }
      
    • 补充:接口和类之间的关系

      1. 类和类是继承关系,只能单继承,不能多继承
      2. 类与接口是实现关系,可以单实现,还可以多实现
      3. 接口与接口是继承关系,可以单继承,还可以多继承
  4. JDK中接口新增:

    • JDK7以前:接口中只能定义抽象方法。

    • JDK8以后:新增默认方法(default method

      格式:public default 返回值类型 方法名(参数列表) {
               
                  }
      // 例如
      public default void show(){
               
               
          System.out.println("InterA接口中的默认方法 ---- show");
      }
      
    • 特点:

      1. 默认方法不是抽象方法,所以不强制被重写
      2. 解决接口升级的问题
    • 注意:

      1. 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
      2. public可以省略,static不能省略
    • JDK9以后:新增private修饰符

    • 格式:

      - 格式 : private返回值类型方法名(参数列表){
               
               }
      - 范例: private void show() {
               
                }
      
      - 格式: private static返回值类型方法名(参数列表){
               
               }
      - 范例: private static void method(){
               
                }
      
  5. 接口的多态

    • 含义:当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口多态
  6. 接口的细节

    • 实现类可以同时继承A类也可以实现接口,不过需要实现所有方法
    • 实现类可以同时继承抽象类也可以实现接口,不过需要重写所有方法
    • 实现类实现了两个接口,且两个接口存在相同抽象方法,此时只需重写一次
    • 当实现了接口,子类实现类中的方法跟父类方法同名是,看需求选择重写
    • 做空重写:只需要重写实现类中的部分接口,可先创建类进行重写,在将此类继承,实现部分

12.1概述

12.1.1定义

​ 我们已经学完了抽象类,抽象类中可以用抽象方法,也可以有普通方法,构造方法,成员变量等。那么什么是接口呢?接口是更加彻底的抽象,JDK7之前,包括JDK7,接口中全部是抽象方法。接口同样是不能创建对象的

12.1.2格式

//接口的定义格式:
interface 接口名称{
    
    
    // 抽象方法
}

// 接口的声明:interface
// 接口名称:首字母大写,满足“驼峰模式”

12.1.3特点

在JDK7,包括JDK7之前,接口中的只有包含:抽象方法和常量

12.1.3.1抽象方法

注意:接口中的抽象方法默认会自动加上public abstract修饰,程序员无需自己手写!!
按照规范:以后接口中的抽象方法建议不要写上public abstract。因为没有必要啊,默认会加上。

12.1.3.2常量

​ 在接口中定义的成员变量默认会加上: public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量。这里是使用public static final修饰后,变量值就不可被修改,并且是静态化的变量可以直接用接口名访问,所以也叫常量。常量必须要给初始值。常量命名规范建议字母全部大写,多个单词用下划线连接。

12.2基本用例

public interface InterF {
    
    
    // 抽象方法!
    //    public abstract void run();
    void run();

    //    public abstract String getName();
    String getName();

    //    public abstract int add(int a , int b);
    int add(int a , int b);


    // 它的最终写法是:
    // public static final int AGE = 12 ;
    int AGE  = 12; //常量
    String SCHOOL_NAME = "黑马程序员";

}

12.3基本的实现

12.3.1概述

​ 类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字

12.3.2格式

/**接口的实现:
    在Java中接口是被实现的,实现接口的类称为实现类。
    实现类的格式:*/
class 类名 implements 接口1,接口2,接口3...{
    
    

}

12.3.3要求和意义

  1. 必须重写实现的全部接口中所有抽象方法。
  2. 如果一个类实现了接口,但是没有重写完全部接口的全部抽象方法,这个类也必须定义成抽象类。
  3. 意义:接口体现的是一种规范,接口对实现类是一种强制性的约束,要么全部完成接口申明的功能,要么自己也定义成抽象类。这正是一种强制性的规范。

12.3.4案例-基本实现

假如我们定义一个运动员的接口(规范),代码如下:

/**
   接口:接口体现的是规范。
 * */
public interface SportMan {
    
    
    void run(); // 抽象方法,跑步。
    void law(); // 抽象方法,遵守法律。
    String compittion(String project);  // 抽象方法,比赛。
}

接下来定义一个乒乓球运动员类,实现接口,实现接口的实现类代码如下:

package com.itheima._03接口的实现;
/**
 * 接口的实现:
 *    在Java中接口是被实现的,实现接口的类称为实现类。
 *    实现类的格式:
 *      class 类名 implements 接口1,接口2,接口3...{
 *
 *
 *      }
 * */
public class PingPongMan  implements SportMan {
    
    
    @Override
    public void run() {
    
    
        System.out.println("乒乓球运动员稍微跑一下!!");
    }

    @Override
    public void law() {
    
    
        System.out.println("乒乓球运动员守法!");
    }

    @Override
    public String compittion(String project) {
    
    
        return "参加"+project+"得金牌!";
    }
}

测试代码

public class TestMain {
    
    
    public static void main(String[] args) {
    
    
        // 创建实现类对象。
        PingPongMan zjk = new PingPongMan();
        zjk.run();
        zjk.law();
        System.out.println(zjk.compittion("全球乒乓球比赛"));

    }
}

12.3.5案例-多实现

类与接口之间的关系是多实现的,一个类可以同时实现多个接口。

首先我们先定义两个接口,代码如下:

/** 法律规范:接口*/
public interface Law {
    
    
    void rule();
}

/** 这一个运动员的规范:接口*/
public interface SportMan {
    
    
    void run();
}

然后定义一个实现类:

/**
 * Java中接口是可以被多实现的:
 *    一个类可以实现多个接口: Law, SportMan
 *
 * */
public class JumpMan implements Law ,SportMan {
    
    
    @Override
    public void rule() {
    
    
        System.out.println("尊长守法");
    }

    @Override
    public void run() {
    
    
        System.out.println("训练跑步!");
    }
}

说明:

​ 从上面可以看出类与接口之间是可以多实现的,我们可以理解成实现多个规范,这是合理的

12.3接口与接口的多继承

​ Java中,接口与接口之间是可以多继承的:也就是一个接口可以同时继承多个接口

注意:

  • 类与接口是实现关系
  • 接口与接口是继承关系

接口继承接口就是把其他接口的抽象方法与本接口进行了合并。

案例演示:

public interface Abc {
    
    
    void go();
    void test();
}

/** 法律规范:接口*/
public interface Law {
    
    
    void rule();
    void test();
}

 *
 *  总结:
 *     接口与类之间是多实现的。
 *     接口与接口之间是多继承的。
 * */
public interface SportMan extends Law , Abc {
    
    
    void run();
}

12.4JDK8以后接口中新增的方法

  • 允许在接口中定义默认方法,需要使用关键字default修饰

    作用:解决接口升级的问题

  • 接口中默认方法的定义格式:

    • 格式: public default返回值类型方法名(参数列表){}
    • 范例:public default void show(){ }
  • 接口中默认方法的注意事项:

    • 默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候去掉default关键字
    • public可以省略,default不能省略
    • 如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写

示例:

public interface InterA {
    
    
     /*接口中默认方法的定义格式:
            格式:public default 返回值类型 方法名(参数列表) {   }

        接口中默认方法的注意事项:
            1.默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候去掉default关键字
            2.public可以省略,default不能省略
            3.如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写*/


    public abstract void method();

    public default void show(){
    
    
        System.out.println("InterA接口中的默认方法 ---- show");
    }
}

接口中,静态的关键字可以不能省略。

接口中,静态的关键字修饰可以又方法体

接口中,静态的关键字是为了解决接口升级的问题

  • 允许在接口中定义定义静态方法,需要用static修饰
  • 接口中静态方法的定义格式:
    • 格式: public static返回值类型方法名(参数列表){}
    • 范例:public static void show(){ }
  • 接口中静态方法的注意事项:
    • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
    • public可以省略,static不能省略

示例:

在接口中,被static修饰的方法,不能被重写,可以直接调用

重写(子类把从父类继承下来的虚方法表里面的方法进行覆盖了,这才叫重写。)

12.5JDK9新增的方法

基本用例

/*格式:
private返回值类型方法名(参数列表){} */
private void show() {
    
     }

/*格式:
 private static返回值类型方法名(参数列表){} */
private static void method(){
    
     }

12.6接口的应用

12.6.1接口的灵活使用

​ 接口代表规则,是行为的抽象。想要让哪个类拥有一个行为,就让这个类实现对应的接口就可以了

image-20230304192545514

若想让某种javaBean实现某种功能,则实现某种接口即可

12.6.2接口的多态

​ 当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口多态

image-20230304193304751

说明:

​ 如果一个方法中,当参数为接口时,那么在调用方法时就可传递这个接口的所有实现类对象

基本用例:

public class Main {
    
    
    public static void main(String[] args) {
    
    
        test(new IHomeServiceImpl());
    }
    private static void test(IHomeService IHomeService){
    
    
        IHomeServiceImpl home1= (IHomeServiceImpl) IHomeService;
        home1.test();
    }

说明:

​ IHomeService为接口,那么当这个接口需要什么对象时,new相应对象即可拿到这个接口对应的实现类对象

12.7适配器设计模式

请查看接口的细节 第5个

12.8接口的细节

关于接口的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

  1. 当两个接口中存在相同抽象方法的时候,该怎么办?

​ 只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的。

  1. 实现类能不能继承A类的时候,同时实现其他接口呢?
  • 继承的父类,就好比是亲爸爸一样
  • 实现的接口,就好比是干爹一样
  • 可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。
  1. 实现类能不能继承一个抽象类的时候,同时实现其他接口呢?

实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写

  1. 实现类Zi,实现了一个接口,还继承了一个Fu类。假设在接口中有一个方法,父类中也有一个相同的方法。子类如何操作呢?
  • 处理办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。
  • 处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。
  1. 如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?(重点)
  1. 可以在接口跟实现类中间,新建一个中间类(适配器类)
  2. 让这个适配器类去实现接口,对接口里面的所有的方法做空重写
  3. 让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。
  4. 因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象

13.枚举

笔记小结:

  1. 含义:它是一种特殊的数据类型,用于定义一组固定的常量

  2. 普通枚举常量

enum Weekday {
     
     
  MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
  1. 数的枚举常量
enum DayOfWeek {
     
     
    MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6), SUNDAY(7);
    private int value;
    DayOfWeek(int value) {
     
     
        this.value = value;
    }
    public int getValue() {
     
     
        return this.value;

  1. 实现接口枚举常量
public enum BasicOperation implements Operation {
     
     
    PLUS("+") {
     
     
        public int apply(int x, int y) {
     
      return x + y; }
    }

    private final String symbol;

    BasicOperation(String symbol) {
     
     
             this.symbol = symbol;
    }
}
  1. 匿名内部类的方式
enum Operation {
     
     
    PLUS {
     
     
        public int apply(int x, int y) {
     
     
            return x + y;
        }
    }
    public abstract int apply(int x, int y);
}

13.1概述

​ Java中枚举是一种特殊的数据类型,用于定义一组固定的常量。枚举类型定义了一个枚举集合,可以在其中定义枚举常量,并且可以通过名称来访问它们。枚举在Java中是一个独立的类,可以包含属性、方法和构造函数等元素

​ 枚举常量通常用于表示一组有限的可能取值,例如一周中的星期几、一年中的季节、颜色等等。使用枚举类型可以提高代码的可读性、可维护性和可扩展性

13.2基本用例

步骤一:定义枚举

  • 创建枚举Weekday
enum Weekday {
    
    
  MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

说明:

​ 常量的定义用作为分隔符进行分割

步骤二:演示

// 调用
Weekday today = Weekday.MONDAY;

说明:

​ 调用的方式类跟静态常量的调用方法相同

13.3枚举常量

/* 格式
enum 枚举名 {
  枚举常量1,枚举常量2,……
}	*/
enum Weekday {
    
    
  MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

// 使用
Weekday today = Weekday.MONDAY;
System.out.println(day); // 输出 MONDAY

13.4带有参数的枚举常量

/* 格式 
enum 枚举名 {
  枚举常量1(值),枚举常量2(值),……
}	*/
enum DayOfWeek {
    
    
    MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6), SUNDAY(7);
    private int value;
    DayOfWeek(int value) {
    
    
        this.value = value;
    }
    public int getValue() {
    
    
        return this.value;
    }
}
// 使用
DayOfWeek day = DayOfWeek.MONDAY;
int value = day.getValue();
System.out.println("Today is " + day + ", value is " + value);// 输出Today is MONDAY, value is 1

13.5实现接口的枚举常量

/* 格式 
public interface Operation {
    int apply(int x, int y);
}	*/

public enum BasicOperation implements Operation {
    
    
    PLUS("+") {
    
    
        public int apply(int x, int y) {
    
     return x + y; }
    },
    MINUS("-") {
    
    
        public int apply(int x, int y) {
    
     return x - y; }
    },
    TIMES("*") {
    
    
        public int apply(int x, int y) {
    
     return x * y; }
    },
    DIVIDE("/") {
    
    
        public int apply(int x, int y) {
    
     return x / y; }
    };
    
    private final String symbol;

    BasicOperation(String symbol) {
    
    
        this.symbol = symbol;
    }

    @Override public String toString() {
    
    
        return symbol;
    }
}

// 使用
int result = BasicOperation.PLUS.apply(1, 2);  // 3

13.6匿名内部类的方式

// 例如
enum Operation {
    
    
    PLUS {
    
    
        public int apply(int x, int y) {
    
    
            return x + y;
        }
    },
    MINUS {
    
    
        public int apply(int x, int y) {
    
    
            return x - y;
        }
    },
    TIMES {
    
    
        public int apply(int x, int y) {
    
    
            return x * y;
        }
    },
    DIVIDE {
    
    
        public int apply(int x, int y) {
    
    
            return x / y;
        }
    };

    public abstract int apply(int x, int y);
}

// 使用
int result = Operation.PLUS.apply(1, 2) // 返回值为3

14.常用类

笔记小结:

  • StringStringBuilderStringJoiner类,见各个小节
  • 字符串原理:请参见字符串原理小节内容。
  • 注意:不断的使用String s = “常量”进行字符串拼接会消耗大量的堆内存空间,建议使用StringJoiner类进行字符串的拼接

14.1String类

笔记小结:

  1. 概述:Java 程序中所有的双引号字符串都是 String 类的对象。String对象不需要导包
  2. 特点:
    • 不可变性:频繁修改字符串的操作会导致内存占用和性能问题,因为频繁创建String对象并进行重新赋值
    • 线程安全性:多个线程可以同时访问同一个String对象,而不会出现线程安全问题
    • 存储在常量池中:Java中有一个字符串常量池,用于存储字符串常量
  3. 构造方法
    1. String()
    2. String(char[] chs)
    3. String(byte[] bys)
    4. String s = “常量”
  4. 构造方法创建对象与直接赋值区别
    1. 创建对象:不存在复用,对象每new一次就会在内存中开辟一个新的空间
    2. 直接赋值:存在复用,若对象的值是常量池中已存在的,则JVM不会在堆内存中开辟新的空间
  5. 字符串比较
    1. == 的作用:基本数据类型比较引用数据类型比较地址值
    2. equals方法的作用:比较字符串内容是否相同
    3. equalsIgnoreCase方法的作用:比较字符串内容是否相同,不区分大小
  6. 案例:
    1. 手机号屏蔽:字符.substring()
    2. 敏感词替换:字符串.replace()

14.1.1概述

​ String 类代表字符串,Java 程序中的所有字符串文字(例如“abc”)都被实现为此类的实例。也就是说,Java 程序中所有的双引号字符串,都是 String 类的对象。String 类在 java.lang 包下,所以使用的时候不需要导包

14.1.2特点

  • 不可变性:String对象一旦创建,其内容不可变。这意味着当对一个String对象进行修改时,实际上是创建了一个新的String对象,并将原对象的内容复制到新对象中。因此,频繁修改字符串的操作会导致内存占用和性能问题
  • 线程安全性:由于String对象不可变,所以多个线程可以同时访问同一个String对象,而不会出现线程安全问题
  • 存储在常量池中:Java中有一个字符串常量池,用于存储字符串常量。当创建一个字符串时,如果该字符串已经存在于常量池中,则返回常量池中的字符串对象,否则创建一个新的对象并添加到常量池中
  • 支持操作符+和+=:可以通过+和+=操作符将两个字符串连接起来,形成一个新的字符串。但是需要注意的是,每次使用这些操作符都会创建一个新的String对象
  • 支持Unicode编码:String类中的字符编码采用Unicode编码,这使得String可以支持多种语言和字符集
  • 支持常用的字符串操作:String类提供了许多常用的字符串操作,例如字符串比较、查找、替换、切割、大小写转换等

14.1.3构造方法

  • 常用的构造方法

    方法名 说明
    public String() 创建一个空白字符串对象,不含有任何内容
    public String(char[] chs) 根据字符数组的内容,来创建字符串对象
    public String(byte[] bys) 根据字节数组的内容,来创建字符串对象
    String s = “abc”; 直接赋值的方式创建字符串对象,内容就是abc
  • 示例代码

    public class StringDemo01 {
          
          
        public static void main(String[] args) {
          
          
            //public String():创建一个空白字符串对象,不含有任何内容
            String s1 = new String();
            System.out.println("s1:" + s1);
    
            //public String(char[] chs):根据字符数组的内容,来创建字符串对象
            char[] chs = {
          
          'a', 'b', 'c'};
            String s2 = new String(chs);
            System.out.println("s2:" + s2);
    
            //public String(byte[] bys):根据字节数组的内容,来创建字符串对象
            byte[] bys = {
          
          97, 98, 99};
            String s3 = new String(bys);
            System.out.println("s3:" + s3);
    
            //String s = “abc”:	直接赋值的方式创建字符串对象,内容就是abc
            String s4 = "abc";
            System.out.println("s4:" + s4);
        }
    }
    

说明:

  • 字符数组常用在更换 某个字符,此时需要构造方法
  • 字节数组常用在把字节信息进行转换,此时需要构造方法

14.1.4构造和直接赋值方式创建对象区别

  • 通过构造方法创建

    ​ 通过 new 创建的字符串对象,每一次 new 都会申请一个内存空间,虽然内容相同,但是地址值不同

    image-20230303094222966

说明:

​ 不存在复用,因为每次new出来的对象会在内存中开辟一个新的空间

  • 直接赋值方式创建

    ​ 以“ ”方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一个 String 对象,并在字符串池中维护

    image-20230303093958531

说明:

​ 当使用双引号直接赋值时,系统会检查该字符串在串池中是否存在。若不存在,则创建新的、若存在则复用

14.1.5字符串的比较

14.1.5.1==号的作用

  • 比较基本数据类型:比较的是具体的值
  • 比较引用数据类型:比较的是对象地址值

14.1.5.2equals方法的作用

  • 方法介绍

    public boolean equals(String s)     比较两个字符串内容是否相同、区分大小写
    
  • 基本用例

    public class StringDemo02 {
          
          
        public static void main(String[] args) {
          
          
            //构造方法的方式得到对象
            char[] chs = {
          
          'a', 'b', 'c'};
            String s1 = new String(chs);
            String s2 = new String(chs);
    
            //直接赋值的方式得到对象
            String s3 = "abc";
            String s4 = "abc";
    
            //比较字符串对象地址是否相同
            System.out.println(s1 == s2); // false
            System.out.println(s1 == s3); // false
            System.out.println(s3 == s4); // true
            System.out.println("--------");
    
            //比较字符串内容是否相同
            System.out.println(s1.equals(s2)); // true
            System.out.println(s1.equals(s3)); // true
            System.out.println(s3.equals(s4)); // true
        }
    }
    

14.1.5.3equalsIgnoreCase方法的作用

​ equalsIgnoreCase 是 Java 中的一个字符串方法,用于比较两个字符串是否相等,但忽略它们的大小写。它与 equals 方法类似,但不考虑大小写的区别。

方法介绍:

public boolean equalsIgnoreCase(String anotherString)

14.1.5案例—手机号屏蔽

说明:

​ 需求:以字符串的形式从键盘接受一个手机号,将中间四位号码屏蔽

代码实现:

public class Test8手机号屏蔽 {
    
    
    public static void main(String[] args) {
    
    
        /*以字符串的形式从键盘接受一个手机号,将中间四位号码屏蔽
        最终效果为:131****9468*/

        //1.键盘录入一个手机号码
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入手机号码");
        String phoneNumber = sc.next();//13112349408

        //2.截取手机号码中的前三位
        String star = phoneNumber.substring(0, 3);

        //3.截取手机号码中的最后四位
        //此时我用substring方法,是用1个参数的,还是两个参数的?1个参数的会更好
        //因为现在我要截取到最后,所以建议使用1个参数的。
        String end = phoneNumber.substring(7);

        //4.拼接
        String result = star + "****" + end;

        System.out.println(result);

    }
}

补充:在 Java 中,substring 是字符串类中的一个方法,用于截取一个字符串的子串

这个方法有两种不同的用法:

  1. 截取从指定位置开始到字符串末尾的子串:

    public String substring(int beginIndex)
    

    其中,beginIndex 表示截取子串的起始位置。返回从 beginIndex 开始到字符串末尾的子串。

    例如:

    rustCopy codeString str = "Hello, world!";
    String substr = str.substring(7);  // 截取 "world!"
    System.out.println(substr);
    

    输出结果为:

    world!
    
  2. 截取从指定位置开始到指定位置结束的子串:

    public String substring(int beginIndex, int endIndex)
    

    其中,beginIndexendIndex 分别表示截取子串的起始位置和结束位置。返回从 beginIndex 开始到 endIndex - 1 结束的子串。

    例如:

    rustCopy codeString str = "Hello, world!";
    String substr = str.substring(7, 12);  // 截取 "world"
    System.out.println(substr);
    

    输出结果为:

    world
    

​ 需要注意的是,substring 方法返回的是一个新的字符串,而不是在原字符串上进行修改。如果 beginIndexendIndex 超出了字符串的范围,会抛出 IndexOutOfBoundsException 异常。

14.1.6案例—敏感词替换

需求1:键盘录入一个 字符串,如果字符串中包含(TMD),则使用***替换

public class Test9敏感词替换 {
    
    
    public static void main(String[] args) {
    
    
        //1.定义一个变量表示骂人的话
        String talk = "后裔你玩什么啊,TMD";


        //2.把这句话中的敏感词进行替换
        String result = talk.replace("TMD", "***");

        //3.打印
        System.out.println(talk);
        System.out.println(result);
    }
}

需求2:如果要替换的敏感词比较多怎么办?

public class Test10多个敏感词替换 {
    
    
    public static void main(String[] args) {
    
    
        //实际开发中,敏感词会有很多很多

        //1.先键盘录入要说的话
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入要说的话");
        String talk = sc.next();//后裔你玩什么啊,TMD,GDX,ctmd,ZZ

        //2.定义一个数组用来存多个敏感词
        String[] arr = {
    
    "TMD","GDX","ctmd","ZZ","lj","FW","nt"};

        //3.把说的话中所有的敏感词都替换为***

        for (int i = 0; i < arr.length; i++) {
    
    
            //i 索引
            //arr[i] 元素 --- 敏感词
            talk = talk.replace(arr[i],"***");
        }

        //4.打印结果
        System.out.println(talk);//后裔你玩什么啊,***,***,***,***

    }
}

补充:在 Java 中,replace 是字符串类中的一个方法,用于替换一个字符串中的某些字符或子串。****

这个方法有两种不同的用法:

  1. 用新的字符串替换掉所有的旧字符串:

     public String replace(CharSequence target, CharSequence replacement)
    

    其中,target 表示要被替换的旧字符串,replacement 表示用于替换的新字符串。返回一个新的字符串,其中所有的 target 都被替换成了 replacement

    例如:

     rustCopy codeString str = "Hello, world!";
    String newStr = str.replace("o", "0");  // 替换所有的 "o" 为 "0"
    System.out.println(newStr);
    

    输出结果为:

     Hell0, w0rld!
    
  2. 用新的字符串替换掉某个位置开始的一段子串:

     public String replace(int startIndex, int endIndex, String newStr)
    

    其中,startIndexendIndex 分别表示要被替换的子串的起始位置和结束位置(不包括 endIndex 所在的字符),newStr 表示用于替换的新字符串。返回一个新的字符串,其中从 startIndex 开始到 endIndex - 1 结束的子串都被替换成了 newStr

    例如:

     rustCopy codeString str = "Hello, world!";
    String newStr = str.replace(7, 12, "JAVA");  // 替换 "world" 为 "JAVA"
    System.out.println(newStr);
    

    输出结果为:

     Hello, JAVA!
    

需要注意的是,replace 方法返回的是一个新的字符串,而不是在原字符串上进行修改。如果 startIndexendIndex 超出了字符串的范围,会抛出 IndexOutOfBoundsException 异常。

14.2StringBuilder类

笔记小结:

  1. 概述:可以看成是一个容器,创建之后里面的内容是可变的

  2. 常用方法:append()、length()、reverse()、toString()

  3. 链式编程:

    • 含义:对StringBuilder对象进行方法连续操作

    • 格式:

      StringBuilder sb = new StringBuilder();
      sb.append("aaa").append("bbb").append("ccc").append("ddd");
      
    
    

14.2.1概述

​ StringBuilder 可以看成是一个容器,创建之后里面的内容是可变的。当我们在拼接字符串和反转字符串的时候会使用到

14.2.2成员方法

image-20230303101857878

14.2.3基本用例-基本使用

public class StringBuilderDemo3 {
    
    
    public static void main(String[] args) {
    
    
        //1.创建对象
        StringBuilder sb = new StringBuilder("abc");

        //2.添加元素
        /*sb.append(1);
        sb.append(2.3);
        sb.append(true);*/

        //反转
        sb.reverse();

        //获取长度
        int len = sb.length();
        System.out.println(len);


        //打印
        //普及:
        //因为StringBuilder是Java已经写好的类
        //java在底层对他做了一些特殊处理。
        //打印对象不是地址值而是属性值。
        System.out.println(sb);
    }
}

14.2.4链式编程

public class StringBuilderDemo4 {
    
    
    public static void main(String[] args) {
    
    
        //1.创建对象
        StringBuilder sb = new StringBuilder();

        //2.添加字符串
        sb.append("aaa").append("bbb").append("ccc").append("ddd");

        System.out.println(sb);//aaabbbcccddd

        //3.再把StringBuilder变回字符串
        String str = sb.toString();
        System.out.println(str);//aaabbbcccddd

    }
}

注意:

需要用tostring将它变为字符串,因为此时为StringBuilder容器而不是字符串

14.3StringJoiner类

笔记小结:

  1. 概述:可以看成是一个容器,创建之后里面的内容是可变的
  2. 构造方法:
    • StringJoiner(间隔符号)
    • StringJoiner(间隔符号,开始符号,结束符号)
  3. 常用方法:
    • add()
    • length()
    • toString()

14.3.1概述

  • StringJoiner跟StringBuilder一样,也可以看成是一个容器,创建之后里面的内容是可变的。
  • 作用:提高字符串的操作效率,而且代码编写特别简洁,但是目前市场上很少有人用。
  • JDK8出现的

14.3.2构造方法

image-20230303102934390

14.3.3成员方法

image-20230303103013492

14.3.4基本用例-基本使用

//1.创建一个对象,并指定中间的间隔符号
StringJoiner sj = new StringJoiner("---");
//2.添加元素
sj.add("aaa").add("bbb").add("ccc");
//3.打印结果
System.out.println(sj);//aaa---bbb---ccc
//1.创建对象
StringJoiner sj = new StringJoiner(", ","[","]");
//2.添加元素
sj.add("aaa").add("bbb").add("ccc");
int len = sj.length();
System.out.println(len);//15
//3.打印
System.out.println(sj);//[aaa, bbb, ccc]
String str = sj.toString();
System.out.println(str);//[aaa, bbb, ccc]

14.4字符串原理(重点)

说明:

详细视频讲解:https://www.bilibili.com/video/BV17F411T7Ao/?p=107

14.4.1扩展底层原理1:字符串存储的内存原理

  • 直接赋值会复用字符串常量池中的
  • new出来不会复用,而是开辟一个新的空间

14.4.2扩展底层原理2:==号比较的到底是什么?

  • 基本数据类型比较数据值
  • 引用数据类型比较地址值

14.4.3扩展底层原理3:字符串拼接的底层原理

  • 如果没有变量参与,都是字符串直接相加,编译之后就是拼接之后的结果,会复用串池中的字符串。
  • 如果有变量参与,每一行拼接的代码,都会在内存中创建新的字符串,浪费内存。

常量拼接

image-20230303103749967

变量拼接

image-20230303103923437

image-20230303104322048

说明:

​ 会非常的消耗堆内存,一个加号会在堆中创建两个对象

image-20230303104701222

说明:JDK8之后将字符串拼接进行了优化处理

  • 在 JDK8 中,Java 对字符串拼接进行了优化处理。在之前的版本中,字符串拼接通常会使用 + 运算符,这会创建大量的临时字符串对象,导致内存的浪费和垃圾回收的频繁发生,降低程序的性能。
  • 为了解决这个问题,JDK8 中引入了新的字符串拼接方式,即使用 StringBuilder 类或 StringJoiner 类进行拼接。这些类在内部使用可变长度的字符数组来存储字符串,可以避免创建大量的临时字符串对象,提高了程序的性能和效率。

例如,以下代码使用 StringBuilder 类进行字符串拼接:

String name = "John";
int age = 30;
String city = "New York";

String message = new StringBuilder("My name is ")
.append(name)
.append(", I am ")
.append(age)
.append(" years old, and I live in ")
.append(city)
.toString();

System.out.println(message);

输出结果为:

My name is John, I am 30 years old, and I live in New York

​ 需要注意的是,这种优化并不意味着在所有情况下都应该使用 StringBuilderStringJoiner 类进行字符串拼接。在一些简单的情况下,直接使用 + 运算符可能更加方便和易读。开发者应该根据具体情况选择合适的方式进行字符串拼接。

14.4.4扩展底层原理4:StringBuilder提高效率原理图

  • 所有要拼接的内容都会往StringBuilder中放,不会创建很多无用的空间,节约内存

image-20230303105957054

说明:

StringBuilder是一个内容可变的容器

image-20230303105922348

14.4.5扩展底层原理5:StringBuilder源码分析

  • 默认创建一个长度为16的字节数组
  • 添加的内容长度小于16,直接存
  • 添加的内容大于16会扩容(原来的容量*2+2)
  • 如果扩容之后还不够,以实际长度为准

image-20230303110350838

说明:

StrinBuilder会创建默认为16的字节数组

image-20230303110618625

image-20230303110556080

15.内部类

笔记小结:

  1. 概述
    • 定义:定义在另一个类中的类
    • 作用:实现更好的封装性,内部的事务脱离外部的事务无法使用
  2. 分类:
    1. 成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
    2. 静态内部类,类定义在了成员位置 (类中方法外称为成员位置,static修饰的内部类)
    3. 局部内部类,类定义在方法内
    4. 匿名内部类没有名字的内部类,可以在方法中,也可以在类中方法外。
  3. 成员内部类:请见成员内部类小节
  4. 静态内部类:请见成员内部类小节
  5. 局部内部类:请见成员内部类小节
  6. 匿名内部类:请见成员内部类小节。只使用一次类时,可以使用匿名内部类

15.1概述

15.1.1定义

​ 将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。可以把内部类理解成寄生,外部类理解成宿主。

​ Java内部类是定义在另一个类中的类,它可以访问包含它的外部类的所有成员和方法,包括私有成员和方法。

15.1.2作用

一个事物内部还有一个独立的事物,内部的事物脱离外部的事物无法独立使用

  1. 人里面有一颗心脏。
  2. 汽车内部有一个发动机。
  3. 为了实现更好的封装性。

15.2分类

  1. 成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
  2. 静态内部类,类定义在了成员位置 (类中方法外称为成员位置,static修饰的内部类)
  3. 局部内部类,类定义在方法内
  4. 匿名内部类,没有名字的内部类,可以在方法中,也可以在类中方法外。

15.3成员内部类

笔记小结:

  1. 特点:无static修饰的内部类,属于外部类对象

  2. 使用:

// 前提需要new对象 
外部类.内部类。 // 访问内部类的类型都是用 外部类.内部类
  1. 获取内部类对象方式
  • 外部直接创建成员内部类的对象

      外部类.内部类 变量 = new 外部类().new 内部类();
    
  • 在外部类中定义一个方法提供内部类的对象

      public class Outer {
           
           
        String name;
        private class Inner{
           
           
            static int a = 10;
        }
        public Inner getInstance(){
           
           
            return new Inner();
        }
    }
    
  1. 细节:
  • 成员内部类可以被一些修饰符所修饰,例如: private,默认,protected等

  • 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量

  • 创建内部类对象时,对象中有一个隐含的Outer.this记录外部类对象的地址值

  1. 内存图:

    1. 方法区中的字节码文件,会加载外部内字节码文件和内部类的字节码文件,它是两个独立的字节码文件

      image-20230306085103657

    2. 当new Inner()时会在堆内存中记录隐藏的this,而这个this是外部类的地址

    3. 补充:a,就近原则、this.a,是内部类中的a、Outer.this.a,是外部类中的a

15.3.1特点

  • 无static修饰的内部类,属于外部类对象的。
  • 宿主:外部类对象。

15.3.2使用格式

 // 前提是new对象
外部类.内部类。 // 访问内部类的类型都是用 外部类.内部类

15.3.3获取成员内部类对象

方式一:外部直接创建成员内部类的对象

/* 格式:
	外部类.内部类 变量 = new 外部类().new 内部类();*/
public class Test {
    
    
    public static void main(String[] args) {
    
    
        //  宿主:外部类对象。
       // Outer out = new Outer();
        // 创建内部类对象。
        Outer.Inner oi = new Outer().new Inner();
        oi.method();
    }
}

class Outer {
    
    
    // 成员内部类,属于外部类对象的。
    // 拓展:成员内部类不能定义静态成员。
    public class Inner{
    
    
        // 这里面的东西与类是完全一样的。
        public void method(){
    
    
            System.out.println("内部类中的方法被调用了");
        }
    }
}

方式二:在外部类中定义一个方法提供内部类的对象

public class Outer {
    
    
    String name;
    private class Inner{
    
    
        static int a = 10;
    }
    public Inner getInstance(){
    
    
        return new Inner();
    }
}

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Outer o = new Outer();
        System.out.println(o.getInstance());


    }
}

说明:

  • 当成员内部类被private修饰时。在外部类编写方法,对外提供内部类对象
  • 当成员内部类被非私有修饰时,直接创建对象。Outer.Inner oi = new Outer().new Inner();

15.3.4细节

编写成员内部类的注意点:

  1. 成员内部类可以被一些修饰符所修饰,比如: private,默认,protected,public,static等
  2. 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。
  3. 创建内部类对象时,对象中有一个隐含的Outer.this记录外部类对象的地址值。(请参见"成员内部类"的内存图)

详解:

  • ​ 内部类被private修饰,外界无法直接获取内部类的对象,只能通过“成员内部类”中的方式二获取内部类的对象

  • 被其他权限修饰符修饰的内部类一般“成员内部类”中的方式一直接获取内部类的对象

  • ​ 内部类被static修饰是成员内部类中的特殊情况,叫做静态内部类下面单独学习。

  • ​ 内部类如果想要访问外部类的成员变量,外部类的变量必须用final修饰,JDK8以前必须手动写final,JDK8之后不需要手动写,JDK默认加上。

15.3.5内存图(重点)

image-20230811093523242

说明:

  • 方法区中的字节码文件,会加载外部内字节码文件和内部类的字节码文件,它是两个独立的字节码文件

    image-20230306085103657

  • 当new Inner()时会在堆内存中记录隐藏的this,而这个this是外部类的地址

  • 补充:a,就近原则、this.a,是内部类中的a、Outer.this.a,是外部类中的a

15.3.6案例–面试题

注意:

​ 内部类访问外部类对象的格式是:外部类名.this

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Outer.inner oi = new Outer().new inner();
        oi.method();
    }
}

class Outer {
    
    	// 外部类
    private int a = 30;

    // 在成员位置定义一个类
    class inner {
    
    
        private int a = 20;

        public void method() {
    
    
            int a = 10;
            System.out.println(???);	// 10   答案:a
            System.out.println(???);	// 20	答案:this.a
            System.out.println(???);	// 30	答案:Outer.this.a
        }
    }
}

说明:

​ 外部类成员变量和内部类成员变量重名时,在内部类如何访问?使用System.out.println(outer.this.变量名);

15.4静态内部类

笔记小结:

  1. 特点:有static修饰,属于外部类本身的

  2. 使用:

外部类名.内部类名.方法名();  
  1. 获取内部类对象方式
外部类.内部类  变量 = new  外部类.内部类构造器;

15.4.1特点

  • 静态内部类是一种特殊的成员内部类。

  • 有static修饰,属于外部类本身的。

总结:

​ 静态内部类与其他类的用法完全一样。只是访问的时候需要加上外部类.内部类

补充:

  • 静态内部类可以直接访问外部类的静态成员

  • 静态内部类不可以直接访问外部类的非静态成员,如果要访问需要创建外部类的对象。

  • 静态内部类中没有隐含的Outer.this

15.4.2使用格式

外部类名.内部类名.方法名();  

15.4.3获取成员内部类对象

/* 格式:
	外部类.内部类  变量 = new  外部类.内部类构造器;	*/
// 外部类:Outer01
class Outer01{
    
    
    private static  String sc_name = "黑马程序";
    // 内部类: Inner01
    public static class Inner01{
    
    
        // 这里面的东西与类是完全一样的。
        private String name;
        public Inner01(String name) {
    
    
            this.name = name;
        }
        public void showName(){
    
    
            System.out.println(this.name);
            // 拓展:静态内部类可以直接访问外部类的静态成员。
            System.out.println(sc_name);
        }
    }
}

public class InnerClassDemo01 {
    
    
    public static void main(String[] args) {
    
    
        // 创建静态内部类对象。
        // 外部类.内部类  变量 = new  外部类.内部类构造器;
        Outer01.Inner01 in  = new Outer01.Inner01("张三");
        in.showName();
    }
}

说明:

​ 注意区分成员内部类的创建方式 Outer.inner oi = new Outer().new inner();

15.5局部内部类

笔记小结:

  1. 特点:定义在方法中的类

  2. 使用格式

 class 外部类名 {
     
     
	数据类型 变量名;

	修饰符 返回值类型 方法名(参数列表) {
     
     
    		// …
		class 内部类 {
     
     
			// 成员变量
			// 成员方法
		}
	}
}

15.5.1特点

局部内部类 :定义在方法中的类

15.5.2使用格式

class 外部类名 {
    
    
	数据类型 变量名;
	
	修饰符 返回值类型 方法名(参数列表) {
    
    
		// …
		class 内部类 {
    
    
			// 成员变量
			// 成员方法
		}
	}
}

15.6匿名内部类(重点)

笔记小结:

  1. 特点:
  • 定义一个没有名字的内部类

  • 这个类实现了父类,或者父类接口

  • 匿名内部类会创建这个没有名字的类的对象

  1. 使用格式:new 接口 或者 new 父类
new 父类名或者接口名(){
     
     
  // 方法重写
  @Override 
  public void method() {
     
     
      // 执行语句
  }
};
  1. 使用方式

  2. new 父类或方法后 直接.调用

      new Swim() {
           
           
        @Override
        public void swimming() {
           
           
            System.out.println("自由泳...");
        }
    }.swimming();
    
  3. new 父类或方法后 直接赋值

      // 接口 变量 = new 实现类(); // 多态,走子类的重写方法
    Swim s2 = new Swim() {
           
           
        @Override
        public void swimming() {
           
           
            System.out.println("蛙泳...");
        }
    };
    s2.swimming();
    
  4. 意义:定义一个只要使用一次的类,就可考虑使用匿名内部类。匿名内部类的本质作用是为了简化代码

匿名内部类 :是内部类的简化写法。他是一个隐含了名字的内部类。开发中,最常用到的内部类就是匿名内部类了。

15.6.1特点

  • 定义一个没有名字的内部类
  • 这个类实现了父类,或者父类接口
  • 匿名内部类会创建这个没有名字的类的对象

15.6.2使用格式

前提:

​ 匿名内部类必须继承一个父类或者实现一个父接口

new 父类名或者接口名(){
    
    
    // 方法重写
    @Override 
    public void method() {
    
    
        // 执行语句
    }
};

注意:

使用前提,要么继承父类,要么实现接口

15.6.3获取匿名内部类

概述:

1.直接new接口

new Swim() {
    
    
    @Override
    public void swimming() {
    
    
        System.out.println("自由泳...");
    }
}.swimming();

2.重写子类方法

// 接口 变量 = new 实现类(); // 多态,走子类的重写方法
Swim s2 = new Swim() {
    
    
    @Override
    public void swimming() {
    
    
        System.out.println("蛙泳...");
    }
};

基本用例:

interface Swim {
    
    
    public abstract void swimming();
}

public class Demo07 {
    
    
    public static void main(String[] args) {
    
    
        // 使用匿名内部类
		new Swim() {
    
    
			@Override
			public void swimming() {
    
    
				System.out.println("自由泳...");
			}
		}.swimming();

        // 接口 变量 = new 实现类(); // 多态,走子类的重写方法
        Swim s2 = new Swim() {
    
    
            @Override
            public void swimming() {
    
    
                System.out.println("蛙泳...");
            }
        };

        s2.swimming();
        s2.swimming();
    }
}

15.6.4应用场景

之前我们使用接口时,似乎得做如下几步操作:

  1. 定义子类
  2. 重写接口中的方法
  3. 创建子类对象
  4. 调用重写后的方法
interface Swim {
    
    
    public abstract void swimming();
}

// 1. 定义接口的实现类
class Student implements Swim {
    
    
    // 2. 重写抽象方法
    @Override
    public void swimming() {
    
    
        System.out.println("狗刨式...");
    }
}

public class Test {
    
    
    public static void main(String[] args) {
    
    
        // 3. 创建实现类对象
        Student s = new Student();
        // 4. 调用方法
        s.swimming();
    }
}

现在:通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递

interface Swim {
    
    
    public abstract void swimming();
}

public class Demo07 {
    
    
    public static void main(String[] args) {
    
    
        // 普通方式传入对象
        // 创建实现类对象
        Student s = new Student();
        
        goSwimming(s);
        // 匿名内部类使用场景:作为方法参数传递
        Swim s3 = new Swim() {
    
    
            @Override
            public void swimming() {
    
    
                System.out.println("蝶泳...");
            }
        };
        // 传入匿名内部类
        goSwimming(s3);

        // 完美方案: 一步到位
        goSwimming(new Swim() {
    
    
            public void swimming() {
    
    
                System.out.println("大学生, 蛙泳...");
            }
        });

        goSwimming(new Swim() {
    
    
            public void swimming() {
    
    
                System.out.println("小学生, 自由泳...");
            }
        });
    }

    // 定义一个方法,模拟请一些人去游泳
    public static void goSwimming(Swim s) {
    
    
        s.swimming();
    }
}

15.6.5意义

​ 实际上,如果我们希望定义一个只要使用一次的类,就可考虑使用匿名内部类。匿名内部类的本质作用是为了简化代码。

16.包装类

笔记小结:

  1. 概述:

    • 定义:每个基本数据类型都有一个对应的包装类,这些包装类提供了一些有用的方法

    • 作用:包装类是一种用来将基本数据类型(例如int,char,float等)转换对象类型的类

  2. 方法:

    • 将基本数据类型转换为对象类型:valueOf(int)、valueOf(string)
    • 进制转换:tobinarystring(int i)、tooctalstring(int i)、toHexstring(int i)
    • 解析为包装类:parseXXX(string s)
  3. 装箱与拆箱:基本类型与对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“

  4. 自动装箱与自动拆箱:注意,从JDK5以后,将自动完成拆箱与装箱。我们在使用时,必要时需要类型判断

  5. 基本类型与字符串之间的转换:

    • 基本类型 —>String:XXX.valueOf( )

      // 例如
      String s2 = String.valueOf(123);
      
    • String —>基本类型 :XXX.praseXXX( )

      // 例如
      int y = Integer.parseInt("123");
      

image-20230319092232038

16.1概述

​ Java提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类,如下:

基本类型 对应的包装类(位于java.lang包中)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

16.2包装类方法

例如:Interger类

  • Integer类概述

    包装一个对象中的原始类型 int 的值

  • Integer类构造方法及静态方法

方法名 说明
public Integer(int value) 根据 int 值创建 Integer 对象(过时)
public Integer(String s) 根据 String 值创建 Integer 对象(过时)
public static Integer valueOf(int i) 返回表示指定的 int 值的 Integer 实例
public static Integer valueOf(String s) 返回保存指定String值的 Integer 对象
static string tobinarystring(int i) 得到二进制
static string tooctalstring(int i) 得到八进制
static string toHexstring(int i) 得到十六进制
static int parseInt(string s) 将字符串类型的整数转成int类型的整数
  • 示例代码
//public Integer(int value):根据 int 值创建 Integer 对象(过时)
Integer i1 = new Integer(100);
System.out.println(i1);

//public Integer(String s):根据 String 值创建 Integer 对象(过时)
Integer i2 = new Integer("100");
//Integer i2 = new Integer("abc"); //NumberFormatException
System.out.println(i2);
System.out.println("--------");

//public static Integer valueOf(int i):返回表示指定的 int 值的 Integer 实例
Integer i3 = Integer.valueOf(100);
System.out.println(i3);

//public static Integer valueOf(String s):返回保存指定String值的Integer对象 
Integer i4 = Integer.valueOf("100");
System.out.println(i4);


/*
    public static string tobinarystring(int i) 得到二进制
    public static string tooctalstring(int i) 得到八进制
    public static string toHexstring(int i) 得到十六进制
    public static int parseInt(string s) 将字符串类型的整数转成int类型的整数
 */

//1.把整数转成二进制,十六进制
String str1 = Integer.toBinaryString(100);
System.out.println(str1);//1100100

//2.把整数转成八进制
String str2 = Integer.toOctalString(100);
System.out.println(str2);//144

//3.把整数转成十六进制
String str3 = Integer.toHexString(100);
System.out.println(str3);//64

//4.将字符串类型的整数转成int类型的整数
//强类型语言:每种数据在java中都有各自的数据类型
//在计算的时候,如果不是同一种数据类型,是无法直接计算的。
int i = Integer.parseInt("123");
System.out.println(i);
System.out.println(i + 1);//124
//细节1:
//在类型转换的时候,括号中的参数只能是数字不能是其他,否则代码会报错
//细节2:
//8种包装类当中,除了Character都有对应的parseXxx的方法,进行类型转换
String str = "true";
boolean b = Boolean.parseBoolean(str);
System.out.println(b);

16.3装箱与拆箱

基本类型与对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“:

  • 装箱:从基本类型转换为对应的包装类对象。
  • 拆箱:从包装类对象转换为对应的基本类型。

用Integer与 int为例:(看懂代码即可)

基本数值---->包装对象

Integer i = new Integer(4);//使用构造函数函数
Integer iii = Integer.valueOf(4);//使用包装类中的valueOf方法

包装对象---->基本数值

int num = i.intValue();

补充:为什么需要拆箱与包箱

基本类型在内存中占据固定大小的空间,而引用类型在内存中只存储指向对象的引用

16.4自动装箱与自动拆箱

​ 由于我们经常要做基本类型与包装类之间的转换,从Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。例如:

Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
//加法运算完成后,再次装箱,把基本数值转成对象。

16.5基本类型与字符串之间的转换

16.5.1基本类型转换为String

  • 转换方式

    • 方式一:直接在数字后加一个空字符串
    • 方式二:通过String类静态方法valueOf()
  • 示例代码

    public class IntegerDemo {
          
          
        public static void main(String[] args) {
          
          
            //int --- String
            int number = 100;
            //方式1
            String s1 = number + "";
            System.out.println(s1);
            //方式2
            //public static String valueOf(int i)
            String s2 = String.valueOf(number);
            System.out.println(s2);
            System.out.println("--------");
        }
    }
    

16.5.2String转换为基本类型

除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型:

  • public static byte parseByte(String s):将字符串参数转换为对应的byte基本类型。
  • public static short parseShort(String s):将字符串参数转换为对应的short基本类型。
  • public static int parseInt(String s):将字符串参数转换为对应的int基本类型。
  • public static long parseLong(String s):将字符串参数转换为对应的long基本类型。
  • public static float parseFloat(String s):将字符串参数转换为对应的float基本类型。
  • public static double parseDouble(String s):将字符串参数转换为对应的double基本类型。
  • public static boolean parseBoolean(String s):将字符串参数转换为对应的boolean基本类型。

代码使用(仅以Integer类的静态方法parseXxx为例)如:

  • 转换方式
    • 方式一:先将字符串数字转成Integer,再调用valueOf()方法
    • 方式二:通过Integer静态方法parseInt()进行转换
  • 示例代码
public class IntegerDemo {
    
    
    public static void main(String[] args) {
    
    
        //String --- int
        String s = "100";
        //方式1:String --- Integer --- int
        Integer i = Integer.valueOf(s);
        //public int intValue()
        int x = i.intValue();
        System.out.println(x);
        //方式2: 
        //public static int parseInt(String s)
        int y = Integer.parseInt(s);
        System.out.println(y);
    }
}

注意:

如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException异常。

16.5.3底层原理

建议:获取Integer对象的时候不要自己new,而是采取直接赋值或者静态方法valueOf的方式

因为在实际开发中,-128~127之间的数据,用的比较多。如果每次使用都是new对象,那么太浪费内存了。

所以,提前把这个范围之内的每一个数据都创建好对象,如果要用到了不会创建新的,而是返回已经创建好的对象。

//1.利用构造方法获取Integer的对象(JDK5以前的方式)
/*Integer i1 = new Integer(1);
        Integer i2 = new Integer("1");
        System.out.println(i1);
        System.out.println(i2);*/

//2.利用静态方法获取Integer的对象(JDK5以前的方式)
Integer i3 = Integer.valueOf(123);
Integer i4 = Integer.valueOf("123");
Integer i5 = Integer.valueOf("123", 8);

System.out.println(i3);
System.out.println(i4);
System.out.println(i5);

//3.这两种方式获取对象的区别(掌握)
//底层原理:
//因为在实际开发中,-128~127之间的数据,用的比较多。
//如果每次使用都是new对象,那么太浪费内存了
//所以,提前把这个范围之内的每一个数据都创建好对象
//如果要用到了不会创建新的,而是返回已经创建好的对象。
Integer i6 = Integer.valueOf(127);
Integer i7 = Integer.valueOf(127);
System.out.println(i6 == i7);//true

Integer i8 = Integer.valueOf(128);
Integer i9 = Integer.valueOf(128);
System.out.println(i8 == i9);//false

//因为看到了new关键字,在Java中,每一次new都是创建了一个新的对象
//所以下面的两个对象都是new出来,地址值不一样。
/*Integer i10 = new Integer(127);
        Integer i11 = new Integer(127);
        System.out.println(i10 == i11);

        Integer i12 = new Integer(128);
        Integer i13 = new Integer(128);
        System.out.println(i12 == i13);*/

17.常用API

笔记小结:见各个小结

17.1API帮助文档

笔记小结:

  • 概述:API,简称应用程序编程接口。简单说就是JDK提供功能类

17.1.1概述

​ API (Application Programming Interface) :应用程序编程接口。指的就是 JDK 中提供的各种功能的 Java,这些类将底层的实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习这些类如何使用即可,我们可以通过帮助文档来学习这些API如何使用。

17.1.2使用

步骤一:打开

  • 打开帮助文档

image-20230813145135197

步骤二:索引

1.找到索引选项卡中的输入框

image-20230813145239953

2.在输入框中输入Random

image-20230813145248221

步骤三:介绍

1.看类在哪个包下

image-20230813145256726

2.看类的描述

image-20230813145303139

3.看构造方法

image-20230813145312149

4.看成员方法

image-20230813145320137

17.2Math类

笔记小结:

  1. 概述:使用Math类完成基本的数学运算

  2. 方法

    public static int abs(int a)					// 返回参数的绝对值
    public static double ceil(double a)				// 返回大于或等于参数的最小整数
    public static double floor(double a)			// 返回小于或等于参数的最大整数
    public static int round(float a)				// 按照四舍五入返回最接近参数的int类型的值
    public static int max(int a,int b)				// 获取两个int值中的较大值
    public static int min(int a,int b)				// 获取两个int值中的较小值
    public static double pow (double a,double b)	// 计算a的b次幂的值
    public static double random()					// 返回一个[0.0,1.0)的随机值
    

17.2.1概述

Math类包含执行基本数字运算的方法,我们可以使用Math类完成基本的数学运算

17.2.2常用成员方法

public static int abs(int a)					// 返回参数的绝对值
public static double ceil(double a)				// 返回大于或等于参数的最小整数
public static double floor(double a)			// 返回小于或等于参数的最大整数
public static int round(float a)				// 按照四舍五入返回最接近参数的int类型的值
public static int max(int a,int b)				// 获取两个int值中的较大值
public static int min(int a,int b)				// 获取两个int值中的较小值
public static double pow (double a,double b)	// 计算a的b次幂的值
public static double random()					// 返回一个[0.0,1.0)的随机值

17.2.3基本用例

public class MathDemo01 {
    
    

    public static void main(String[] args) {
    
    

        // public static int abs(int a)         返回参数的绝对值
        System.out.println("-2的绝对值为:" + Math.abs(-2));
        System.out.println("2的绝对值为:" + Math.abs(2));

        // public static double ceil(double a)  返回大于或等于参数的最小整数
        System.out.println("大于或等于23.45的最小整数位:" + Math.ceil(23.45));
        System.out.println("大于或等于-23.45的最小整数位:" + Math.ceil(-23.45));

        // public static double floor(double a) 返回小于或等于参数的最大整数
        System.out.println("小于或等于23.45的最大整数位:" + Math.floor(23.45));
        System.out.println("小于或等于-23.45的最大整数位:" + Math.floor(-23.45));

        // public static int round(float a)     按照四舍五入返回最接近参数的int
        System.out.println("23.45四舍五入的结果为:" + Math.round(23.45));
        System.out.println("23.55四舍五入的结果为:" + Math.round(23.55));

        // public static int max(int a,int b)   返回两个int值中的较大值
        System.out.println("23和45的最大值为: " + Math.max(23, 45));

        // public static int min(int a,int b)   返回两个int值中的较小值
        System.out.println("12和34的最小值为: " + Math.min(12 , 34));

        // public static double pow (double a,double b)返回a的b次幂的值
        System.out.println("2的3次幂计算结果为: " + Math.pow(2,3));

        // public static double random()返回值为double的正值,[0.0,1.0)
        System.out.println("获取到的0-1之间的随机数为: " + Math.random());
    }

}

/*
* 
    -2的绝对值为:2
    2的绝对值为:2
    大于或等于23.45的最小整数位:24.0
    大于或等于-23.45的最小整数位:-23.0
    小于或等于23.45的最大整数位:23.0
    小于或等于-23.45的最大整数位:-24.0
    23.45四舍五入的结果为:23
    23.55四舍五入的结果为:24
    23和45的最大值为: 45
    12和34的最小值为: 12
    2的3次幂计算结果为: 8.0
    获取到的0-1之间的随机数为: 0.7322484131745958
* */

17.3System类

笔记小结:

  1. 概述:包含了系统操作的一些常用的方法

  2. 常用成员方法

    public static long currentTimeMillis()			// 获取当前时间所对应的毫秒值(当前时间为0时区所对应的时间即就是英国格林尼治天文台旧址所在位置)
    public static void exit(int status)				// 终止当前正在运行的Java虚拟机,0表示正常退出,非零表示异常退出
    public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos, int length); // 进行数值元素copy
    

17.3.1概述

​ System类所在包为java.lang包,因此在使用的时候不需要进行导包。并且System类被final修饰了,因此该类是不能被继承的。

​ System包含了系统操作的一些常用的方法。比如获取当前时间所对应的毫秒值,再比如终止当前JVM等等。

17.3.2常用成员方法

public static long currentTimeMillis()			// 获取当前时间所对应的毫秒值(当前时间为0时区所对应的时间即就是英国格林尼治天文台旧址所在位置)
public static void exit(int status)				// 终止当前正在运行的Java虚拟机,0表示正常退出,非零表示异常退出
public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos, int length); // 进行数值元素copy

17.3.3基本用例

public class SystemDemo01 {
    
    

    public static void main(String[] args) {
    
    

        // 获取当前时间所对应的毫秒值
        long millis = System.currentTimeMillis();

        // 输出结果
        System.out.println("当前时间所对应的毫秒值为:" + millis);

    }

}
public class SystemDemo01 {
    
    

    public static void main(String[] args) {
    
    
        
        // 输出
        System.out.println("程序开始执行了.....");
        
        // 终止JVM
        System.exit(0);
        
        // 输出
        System.out.println("程序终止了..........");
        
    }
    
}
public class SystemDemo01 {
    
    

    public static void main(String[] args) {
    
    

        // 定义源数组
        int[] srcArray = {
    
    23 , 45 , 67 , 89 , 14 , 56 } ;

        // 定义目标数组
        int[] desArray = new int[10] ;

        // 进行数组元素的copy: 把srcArray数组中从0索引开始的3个元素,从desArray数组中的1索引开始复制过去
        System.arraycopy(srcArray , 0 , desArray , 1 , 3);

        // 遍历目标数组
        for(int x = 0 ; x < desArray.length ; x++) {
    
    
            if(x != desArray.length - 1) {
    
    
                System.out.print(desArray[x] + ", ");
            }else {
    
    
                System.out.println(desArray[x]);
            }

        }

    }

}

17.4Runtime类

笔记小结:

  1. 概述:Runtime表示Java中运行时对象,可以获取到程序运行时设计到的一些信息

  2. 常用成员方法:

    public static Runtime getRuntime()		//当前系统的运行环境对象
    public void exit(int status)			//停止虚拟机
    public int availableProcessors()		//获得CPU的线程数
    public long maxMemory()				    //JVM能从系统中获取总内存大小(单位byte)
    public long totalMemory()				//JVM已经从系统中获取总内存大小(单位byte)
    public long freeMemory()				//JVM剩余内存大小(单位byte)
    public Process exec(String command) 	//运行cmd命令
    

17.4.1概述

Runtime表示Java中运行时对象,可以获取到程序运行时设计到的一些信息

17.4.2常用成员方法

public static Runtime getRuntime()		//当前系统的运行环境对象
public void exit(int status)			//停止虚拟机
public int availableProcessors()		//获得CPU的线程数
public long maxMemory()				    //JVM能从系统中获取总内存大小(单位byte)
public long totalMemory()				//JVM已经从系统中获取总内存大小(单位byte)
public long freeMemory()				//JVM剩余内存大小(单位byte)
public Process exec(String command) 	//运行cmd命令

17.4.3基本用例

public class RunTimeDemo1 {
    
    
    public static void main(String[] args) throws IOException {
    
    
        /*
            public static Runtime getRuntime() 当前系统的运行环境对象
            public void exit(int status) 停止虚拟机
            public int availableProcessors() 获得CPU的线程数
            public long maxMemory() JVM能从系统中获取总内存大小(单位byte)
            public long totalMemory() JVM已经从系统中获取总内存大小(单位byte)
            public long freeMemory() JVM剩余内存大小(单位byte)
            public Process exec(string command) 运行cmd命令
        */

        //1.获取Runtime的对象
        //Runtime r1 =Runtime.getRuntime();

        //2.exit 停止虚拟机
        //Runtime.getRuntime().exit(0);
        //System.out.println("看看我执行了吗?");


        //3.获得CPU的线程数
        System.out.println(Runtime.getRuntime().availableProcessors());//8
        //4.总内存大小,单位byte字节
        System.out.println(Runtime.getRuntime().maxMemory() / 1024 / 1024);//4064
        //5.已经获取的总内存大小,单位byte字节
        System.out.println(Runtime.getRuntime().totalMemory() / 1024 / 1024);//254
        //6.剩余内存大小
        System.out.println(Runtime.getRuntime().freeMemory() / 1024 / 1024);//251

        //7.运行cmd命令
        //shutdown :关机
        //加上参数才能执行
        //-s :默认在1分钟之后关机
        //-s -t 指定时间 : 指定关机时间
        //-a :取消关机操作
        //-r: 关机并重启
        Runtime.getRuntime().exec("shutdown -s -t 3600");


    }
}

17.5Object类(重点)

笔记小结:

  1. 概述:所有类都直接或者间接的继承自该类

  2. 常用成员方法:

    public String toString()				//返回该对象的字符串表示形式(可以看做是对象的内存地址值)
    public boolean equals(Object obj)		//比较两个对象地址值是否相等;true表示相同,false表示不相同
    protected Object clone()    			//对象克隆
    
  3. 使用:请查看各小节

17.5.1概述

​ Object类所在包是java.lang包。Object 是类层次结构的根,每个类都可以将 Object 作为超类。所有类都直接或者间接的继承自该类;换句话说,该类所具备的方法,其他所有类都继承了。

17.5.2常用成员方法

public String toString()				//返回该对象的字符串表示形式(可以看做是对象的内存地址值)
public boolean equals(Object obj)		//比较两个对象地址值是否相等;true表示相同,false表示不相同
protected Object clone()    			//对象克隆

17.5.3基本用例

常见方法介绍

我们要学习的Object类中的常见方法如下所示:

public String toString()				//返回该对象的字符串表示形式(可以看做是对象的内存地址值)
public boolean equals(Object obj)		//比较两个对象地址值是否相等;true表示相同,false表示不相同
protected Object clone()    			//对象克隆

案例演示

接下来我们就来通过一些案例演示一下这些方法的特点。

案例-演示toString方法

笔记小结

  1. 在通过输出语句输出一个对象时,默认调用的就是toString()方法
  2. 输出地址值一般没有意义,我们可以通过重写toString方法去输出对应的成员变量信息(快捷键:atl + insert , 空白处 右键 -> Generate -> 选择toString)
  3. toString方法的作用:以良好的格式,更方便的展示对象中的属性值
  4. 一般情况下Jdk所提供的类都会重写Object类中的toString方法

实现步骤:

  1. 创建一个学生类,提供两个成员变量(name , age);并且提供对应的无参构造方法和有参构造方法以及get/set方法
  2. 创建一个测试类(ObjectDemo01),在测试类的main方法中去创建学生对象,然后调用该对象的toString方法获取该对象的字符串表现形式,并将结果进行输出

如下所示:

Student类

public class Student {
    
    

    private String name ;       // 姓名
    private String age ;        // 年龄

    // 无参构造方法和有参构造方法以及get和set方法略
    ...
        
}

ObjectDemo01测试类

public class ObjectDemo01 {
    
    

    public static void main(String[] args) {
    
    

        // 创建学生对象
        Student s1 = new Student("itheima" , "14") ;

        // 调用toString方法获取s1对象的字符串表现形式
        String result1 = s1.toString();

        // 输出结果
        System.out.println("s1对象的字符串表现形式为:" + result1);

    }

}

运行程序进行测试,控制台输出结果如下所示:

s1对象的字符串表现形式为:com.itheima.api.system.demo04.Student@3f3afe78

为什么控制台输出的结果为:com.itheima.api.system.demo04.Student@3f3afe78; 此时我们可以查看一下Object类中toString方法的源码,如下所示:

public String toString() {
    
    		// Object类中toString方法的源码定义
	return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

其中getClass().getName()对应的结果就是:com.itheima.api.system.demo04.Student;Integer.toHexString(hashCode())对应的结果就是3f3afe78。

我们常常将"com.itheima.api.system.demo04.Student@3f3afe78"这一部分称之为对象的内存地址值。但是一般情况下获取对象的内存地址值没有太大的意义。获取对象的成员变量的字符串拼接形式才

算有意义,怎么实现呢?此时我们就需要在Student类中重写Object的toString方法。我们可以通过idea开发工具进行实现,具体步骤如下所示:

  1. 在空白处使用快捷键:alt + insert。此时会弹出如下的对话框

1576055135105

  1. 选择toString,此时会弹出如下的对话框

1576055198877

同时选择name和age属性,点击OK。此时就会完成toString方法的重写,代码如下所示:

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

这段代码就是把Student类中的成员变量进行了字符串的拼接。重写完毕以后,再次运行程序,控制台输出结果如下所示:

s1对象的字符串表现形式为:Student{
    
    name='itheima', age='14'}

此时我们就可以清楚的查看Student的成员变量值,因此重写toString方法的意义就是以良好的格式,更方便的展示对象中的属性值

我们再来查看一下如下代码的输出:

// 创建学生对象
Student s1 = new Student("itheima" , "14") ;

// 直接输出对象s1
System.out.println(s1);

运行程序进行测试,控制台输出结果如下所示:

Student{
    
    name='itheima', age='14'}

我们可以看到和刚才的输出结果是一致的。那么此时也就证明直接输出一个对象,那么会默认调用对象的toString方法,因此如上代码的等同于如下代码:

// 创建学生对象
Student s1 = new Student("itheima" , "14") ;

// 调用s1的toString方法,把结果进行输出
System.out.println(s1.toString());

因此后期为了方便进行测试,我们常常是通过输出语句直接输出一个对象的名称。

小结:

  1. 在通过输出语句输出一个对象时,默认调用的就是toString()方法
  2. 输出地址值一般没有意义,我们可以通过重写toString方法去输出对应的成员变量信息(快捷键:atl + insert , 空白处 右键 -> Generate -> 选择toString)
  3. toString方法的作用:以良好的格式,更方便的展示对象中的属性值
  4. 一般情况下Jdk所提供的类都会重写Object类中的toString方法

案例-演示equals方法

笔记小结

  1. 默认情况下equals方法比较的是对象的地址值
  2. 比较对象的地址值是没有意义的,因此一般情况下我们都会重写Object类中的equals方法

实现步骤:

  1. 在测试类(ObjectDemo02)的main方法中,创建两个学生对象,然后比较两个对象是否相同

代码如下所示:

public class ObjectDemo02 {
    
    

    public static void main(String[] args) {
    
    

        // 创建两个学生对象
        Student s1 = new Student("itheima" , "14") ;
        Student s2 = new Student("itheima" , "14") ;

        // 比较两个对象是否相等
        System.out.println(s1 == s2);

    }

}

运行程序进行测试,控制台的输出结果如下所示:

false

因为"=="号比较的是对象的地址值,而我们通过new关键字创建了两个对象,它们的地址值是不相同的。因此比较结果就是false。

我们尝试调用Object类中的equals方法进行比较,代码如下所示:

// 调用equals方法比较两个对象是否相等
boolean result = s1.equals(s2);

// 输出结果
System.out.println(result);

运行程序进行测试,控制台的输出结果为:

false

为什么结果还是false呢?我们可以查看一下Object类中equals方法的源码,如下所示:

public boolean equals(Object obj) {
    
    		// Object类中的equals方法的源码
    return (this == obj);
}

通过源码我们可以发现默认情况下equals方法比较的也是对象的地址值。比较内存地址值一般情况下是没有意义的,我们希望比较的是对象的属性,如果两个对象的属性相同,我们认为就是同一个对象;

那么要比较对象的属性,我们就需要在Student类中重写Object类中的equals方法。equals方法的重写,我们也可以使用idea开发工具完成,具体的操作如下所示:

  1. 在空白处使用快捷键:alt + insert。此时会弹出如下的对话框

1576056718392

  1. 选择equals() and hashCode()方法,此时会弹出如下的对话框

1576057779458

点击next,会弹出如下对话框:

1576057813175

选择neme和age属性点击next,此时就会弹出如下对话框:

1576057892814

取消name和age属性(因为此时选择的是在生成hashCode方法时所涉及到的属性,关于hashCode方法后期再做重点介绍),点击Finish完成生成操作。生成的equals方法和hashCode方法如下:

@Override
public boolean equals(Object o) {
    
    
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Student student = (Student) o;
    return Objects.equals(name, student.name) && Objects.equals(age, student.age);	// 比较的是对象的name属性值和age属性值
}

@Override
public int hashCode() {
    
    
    return 0;
}

hashCode方法我们暂时使用不到,可以将hashCode方法删除。重写完毕以后运行程序进行测试,控制台输出结果如下所示:

true

此时equals方法比较的是对象的成员变量值,而s1和s2两个对象的成员变量值都是相同的。因此比较完毕以后的结果就是true。

小结:

  1. 默认情况下equals方法比较的是对象的地址值
  2. 比较对象的地址值是没有意义的,因此一般情况下我们都会重写Object类中的equals方法

案例-对象克隆

笔记小结:

  1. 浅克隆:
    • 含义:浅克隆是指在克隆对象时,只复制对象本身和其中的基本类型数据,而不复制对象中引用类型的数据,此时复制出来的对象与原对象共享引用类型数据所在的地址。
    • 特点:当原对象中引用类型数据发生改变时,复制出来的对象也会发生变化。
  2. 深克隆:
    • 含义:在克隆对象时,不仅复制对象本身和其中的基本类型数据,还要对对象中的引用类型数据进行递归复制,以确保复制出来的对象与原对象的所有数据完全独立,互不影响
    • 特点:可以保证克隆出来的对象与原对象完全独立,互不影响
  3. 注意:深克隆会比浅克隆更加耗费时间和资源,但可以保证克隆出来的对象与原对象完全独立,互不影响。而浅克隆虽然速度快,但是容易出现数据共享的问题,需要特别注意。

​ 把A对象的属性值完全拷贝给B对象,也叫对象拷贝,对象复制

对象克隆的分类:

  • 深克隆
  • 浅克隆

浅克隆:

​ 不管对象内部的属性是基本数据类型还是引用数据类型,都完全拷贝过来

基本数据类型拷贝过来的是具体的数据,引用数据类型拷贝过来的是地址值

​ Object类默认的是浅克隆

image-20230811225451962

深克隆:

​ 基本数据类型拷贝过来,字符串复用引用数据类型会重新

image-20230811225506205

代码实现:

package com.itheima.a04objectdemo;

public class ObjectDemo4 {
    
    
    public static void main(String[] args) throws CloneNotSupportedException {
    
    
        // protected object clone(int a) 对象克隆 

        //1.先创建一个对象
        int[] data = {
    
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0};
        User u1 = new User(1, "zhangsan", "1234qwer", "girl11", data);

        //2.克隆对象
        //细节:
        //方法在底层会帮我们创建一个对象,并把原对象中的数据拷贝过去。
        //书写细节:
        //1.重写Object中的clone方法
        //2.让javabean类实现Cloneable接口
        //3.创建原对象并调用clone就可以了
        //User u2 =(User)u1.clone();

        //验证一件事情:Object中的克隆是浅克隆
        //想要进行深克隆,就需要重写clone方法并修改里面的方法体
        //int[] arr = u1.getData();
        //arr[0] = 100;

        //System.out.println(u1);
        //System.out.println(u2);


        //以后一般会用第三方工具进行克隆
        //1.第三方写的代码导入到项目中
        //2.编写代码
        //Gson gson =new Gson();
        //把对象变成一个字符串
        //String s=gson.toJson(u1);
        //再把字符串变回对象就可以了
        //User user =gson.fromJson(s, User.class);

        //int[] arr=u1.getData();
        //arr[0] = 100;

        //打印对象
        //System.out.println(user);

    }
}

package com.itheima.a04objectdemo;

import java.util.StringJoiner;



//Cloneable
//如果一个接口里面没有抽象方法
//表示当前的接口是一个标记性接口
//现在Cloneable表示一旦实现了,那么当前类的对象就可以被克降
//如果没有实现,当前类的对象就不能克隆
public class User implements Cloneable {
    
    
    private int id;
    private String username;
    private String password;
    private String path;
    private int[] data;




    public User() {
    
    
    }

    public User(int id, String username, String password, String path, int[] data) {
    
    
        this.id = id;
        this.username = username;
        this.password = password;
        this.path = path;
        this.data = data;
    }

    /**
     * 获取
     *
     * @return id
     */
    public int getId() {
    
    
        return id;
    }

    /**
     * 设置
     *
     * @param id
     */
    public void setId(int id) {
    
    
        this.id = id;
    }

    /**
     * 获取
     *
     * @return username
     */
    public String getUsername() {
    
    
        return username;
    }

    /**
     * 设置
     *
     * @param username
     */
    public void setUsername(String username) {
    
    
        this.username = username;
    }

    /**
     * 获取
     *
     * @return password
     */
    public String getPassword() {
    
    
        return password;
    }

    /**
     * 设置
     *
     * @param password
     */
    public void setPassword(String password) {
    
    
        this.password = password;
    }

    /**
     * 获取
     *
     * @return path
     */
    public String getPath() {
    
    
        return path;
    }

    /**
     * 设置
     *
     * @param path
     */
    public void setPath(String path) {
    
    
        this.path = path;
    }

    /**
     * 获取
     *
     * @return data
     */
    public int[] getData() {
    
    
        return data;
    }

    /**
     * 设置
     *
     * @param data
     */
    public void setData(int[] data) {
    
    
        this.data = data;
    }

    public String toString() {
    
    
        return "角色编号为:" + id + ",用户名为:" + username + "密码为:" + password + ", 游戏图片为:" + path + ", 进度:" + arrToString();
    }


    public String arrToString() {
    
    
        StringJoiner sj = new StringJoiner(", ", "[", "]");

        for (int i = 0; i < data.length; i++) {
    
    
            sj.add(data[i] + "");
        }
        return sj.toString();
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
    
    
        //调用父类中的clone方法
        //相当于让Java帮我们克隆一个对象,并把克隆之后的对象返回出去。

        //先把被克隆对象中的数组获取出来
        int[] data = this.data;
        //创建新的数组
        int[] newData =new int[data.length];
        //拷贝数组中的数据
        for (int i = 0; i < data.length; i++) {
    
    
            newData[i] = data[i];
        }
        //调用父类中的方法克隆对象
            User u=(User)super.clone();
        //因为父类中的克隆方法是浅克隆,替换克隆出来对象中的数组地址值
        u.data =newData;
        return u;
    }
}

17.6Objects类

笔记小结:

  1. 概述:用于操作对象

  2. 方法:

    // objects是一个对象工具类,提供了一些操作对象的方法
    equals(对象1,对象2):先做非空判断,比较两个对象
    isNull(对象):判断对象是否为空
    nonNull(对象):判断对象是否不是空
    

17.6.1概述

java.util.Objects 是 Java 7 中新增的一个工具类,包含了一些常用的静态方法,主要用于操作对象

17.6.2常用成员方法

// objects是一个对象工具类,提供了一些操作对象的方法
equals(对象1,对象2):先做非空判断,比较两个对象
isNull(对象):判断对象是否为空
nonNull(对象):判断对象是否不是空

image-20230317084530396

17.7BigInteger类

笔记小结:

  1. 概述:获取一个随机的大整数。获取一个指定的大整数,可以超出long的取值范围
  2. 字符串中的数字必须是整数。字符串中的数字必须要跟进制吻合
  3. 使用细节:
    • 静态方法获取BigInteger的对象,内部有优化
    • 在内部对常用的数字: -16 ~ 16 进行了优化
    • 提前把-16~16 先创建好BigInteger的对象,如果多次获取不会重新创建新的

17.7.1概述

​ 平时在存储整数的时候,Java中默认是int类型,int类型有取值范围:-2147483648 ~ 2147483647。

说明:

​ 如果数字过大,我们可以使用long类型,但是如果long类型也表示不下怎么办呢?

​ 就需要用到BigInteger,可以理解为:大的整数。

​ 有多大呢?理论上最大到42亿的21亿次方

​ 基本上在内存撑爆之前,都无法达到这个上限。

BigInteger所在包是在java.math包下,因此在使用的时候就需要进行导包。我们可以使用BigInteger类进行大整数的计算

17.7.2常用成员方法

参考资料:

常用API-07-BigInteger基本使用和原理解析_哔哩哔哩_bilibili

17.7.2.1构造方法

public BigInteger(int num, Random rnd) 		//获取随机大整数,范围:[0 ~ 2的num次方-1]
public BigInteger(String val) 				//获取指定的大整数
public BigInteger(String val, int radix) 	//获取指定进制的大整数
    
// 下面这个不是构造,而是一个静态方法获取BigInteger对象
public static BigInteger valueOf(long val) 	//静态方法获取BigInteger的对象,内部有优化

小结:

  • 如果BigInteger表示的数字没有超出long的范围,可以用静态方法获取。
  • 如果BigInteger表示的超出long的范围,可以用构造方法获取。
  • 对象一旦创建,BigInteger内部记录的值不能发生改变。
  • 只要进行计算都会产生一个新的BigInteger对象

17.7.2.2成员方法

public BigInteger add(BigInteger val)					//加法
public BigInteger subtract(BigInteger val)				//减法
public BigInteger multiply(BigInteger val)				//乘法
public BigInteger divide(BigInteger val)				//除法
public BigInteger[] divideAndRemainder(BigInteger val)	 //除法,获取商和余数
public  boolean equals(Object x) 					    //比较是否相同
public  BigInteger pow(int exponent) 					//次幂、次方
public  BigInteger max/min(BigInteger val) 				//返回较大值/较小值
public  int intValue(BigInteger val) 					//转为int类型整数,超出范围数据有误

17.7.3基本用例

package com.itheima.a06bigintegerdemo;

import java.math.BigInteger;

public class BigIntegerDemo1 {
    
    
    public static void main(String[] args) {
    
    
        /*
            public BigInteger(int num, Random rnd) 获取随机大整数,范围:[0~ 2的num次方-11
            public BigInteger(String val) 获取指定的大整数
            public BigInteger(String val, int radix) 获取指定进制的大整数

            public static BigInteger valueOf(long val) 静态方法获取BigInteger的对象,内部有优化

            细节:
            对象一旦创建里面的数据不能发生改变。
        */


        //1.获取一个随机的大整数
        /* Random r=new Random();
            for (int i = e; i < 100; i++) {
            BigInteger bd1 = new BigInteger(4,r);
            System.out.println(bd1);//[@ ~ 15]}
            }
        */

        //2.获取一个指定的大整数,可以超出long的取值范围
        //细节:字符串中必须是整数,否则会报错
        /* BigInteger bd2 = new BigInteger("1.1");
            System.out.println(bd2);
        */

        /*
            BigInteger bd3 = new BigInteger("abc");
            System.out.println(bd3);
         */

        //3.获取指定进制的大整数
        //细节:
        //1.字符串中的数字必须是整数
        //2.字符串中的数字必须要跟进制吻合。
        //比如二进制中,那么只能写日和1,写其他的就报错。
        BigInteger bd4 = new BigInteger("123", 2);
        System.out.println(bd4);

        //4.静态方法获取BigInteger的对象,内部有优化
        //细节:
        //1.能表示范围比较小,只能在long的取值范围之内,如果超出long的范围就不行了。
        //2.在内部对常用的数字: -16 ~ 16 进行了优化。
        //  提前把-16~16 先创建好BigInteger的对象,如果多次获取不会重新创建新的。
        BigInteger bd5 = BigInteger.valueOf(16);
        BigInteger bd6 = BigInteger.valueOf(16);
        System.out.println(bd5 == bd6);//true


        BigInteger bd7 = BigInteger.valueOf(17);
        BigInteger bd8 = BigInteger.valueOf(17);
        System.out.println(bd7 == bd8);//false


        //5.对象一旦创建内部的数据不能发生改变
        BigInteger bd9 =BigInteger.valueOf(1);
        BigInteger bd10 =BigInteger.valueOf(2);
        //此时,不会修改参与计算的BigInteger对象中的借,而是产生了一个新的BigInteger对象记录
        BigInteger result=bd9.add(bd10);
        System.out.println(result);//3

    }
}

package com.itheima.a06bigintegerdemo;

import java.math.BigInteger;

public class BigIntegerDemo2 {
    
    
    public static void main(String[] args) {
    
    
        /*
            public BigInteger add(BigInteger val) 加法
            public BigInteger subtract(BigInteger val) 减法
            public BigInteger multiply(BigInteger val) 乘法
            public BigInteger divide(BigInteger val) 除法,获取商
            public BigInteger[] divideAndRemainder(BigInteger val) 除法,获取商和余数
            public boolean equals(Object x) 比较是否相同
            public BigInteger pow(int exponent) 次幂
            public BigInteger max/min(BigInteger val) 返回较大值/较小值
            public int intValue(BigInteger val) 转为int类型整数,超出范围数据有误
        */

        //1.创建两个BigInteger对象
        BigInteger bd1 = BigInteger.valueOf(10);
        BigInteger bd2 = BigInteger.valueOf(5);

        //2.加法
        BigInteger bd3 = bd1.add(bd2);
        System.out.println(bd3);

        //3.除法,获取商和余数
        BigInteger[] arr = bd1.divideAndRemainder(bd2);
        System.out.println(arr[0]);
        System.out.println(arr[1]);

        //4.比较是否相同
        boolean result = bd1.equals(bd2);
        System.out.println(result);

        //5.次幂
        BigInteger bd4 = bd1.pow(2);
        System.out.println(bd4);

        //6.max
        BigInteger bd5 = bd1.max(bd2);


        //7.转为int类型整数,超出范围数据有误
        /* BigInteger bd6 = BigInteger.valueOf(2147483647L);
         int i = bd6.intValue();
         System.out.println(i);
         */

        BigInteger bd6 = BigInteger.valueOf(200);
        double v = bd6.doubleValue();
        System.out.println(v);//200.0
    }
}

17.7.4底层原理

​ 对于计算机而言,其实是没有数据类型的概念的,都是0101010101,数据类型是编程语言自己规定的,所以在实际存储的时候,先把具体的数字变成二进制,每32个bit为一组,存储在数组中。

数组中最多能存储元素个数:21亿多

数组中每一位能表示的数字:42亿多

理论上,BigInteger能表示的最大数字为:42亿的21亿次方。

但是还没到这个数字,电脑的内存就会撑爆,所以一般认为BigInteger是无限的。

存储方式如图所示:

image-20230811225204102

17.8BigDecimal类

笔记小结:

  1. 概述:表示较大的小数和解决小数运算精度失真问题
  2. 获取对象:
    • BigDecimal bd1 = new BigDecimal(“较大的小数”);
    • BigDecimal bd2 = BigDecimal.valueof(0.1);
  3. 操作:
    • 加: add
    • 减: subtract
    • 乘:multiply
    • 除:divide (四舍五入: RoundingMode.HALF_UP)

17.8.1概述

首先我们来分析一下如下程序的执行结果:

public class BigDecimalDemo01 {
    
    

    public static void main(String[] args) {
    
    
        System.out.println(0.09 + 0.01);
    }

}

这段代码比较简单,就是计算0.09和0.01之和,并且将其结果在控制台进行输出。那么按照我们的想法在控制台输出的结果应该为0.1。那么实际的运行结果是什么呢?我们来运行一下程序,控制台的输出

结果如下所示:

0.09999999999999999

这样的结果其实就是一个丢失精度的结果。为什么会产生精度丢失呢?

在使用float或者double类型的数据在进行数学运算的时候,很有可能会产生精度丢失问题。我们都知道计算机底层在进行运算的时候,使用的都是二进制数据; 当我们在程序中写了一个十进制数据 ,在

进行运算的时候,计算机会将这个十进制数据转换成二进制数据,然后再进行运算,计算完毕以后计算机会把运算的结果再转换成十进制数据给我们展示; 如果我们使用的是整数类型的数据进行计算,那

么在把十进制数据转换成二进制数据的时候不会存在精度问题; 如果我们的数据是一个浮点类型的数据,有的时候计算机并不会将这个数据完全转换成一个二进制数据,而是将这个将其转换成一个无限的

趋近于这个十进数的二进制数据; 这样使用一个不太准确的数据进行运算的时候, 最终就会造成精度丢失;为了提高精度,Java就给我们提供了BigDecimal供我们进行数据运算。

BigDecimal所在包是在java.math包下,因此在使用的时候就需要进行导包。我们可以使用BigDecimal类进行更加精准的数据计算。

17.8.2常用方法

17.8.2.1构造方法

1576134383441

17.8.2.2成员方法

image-20230317211454064

17.8.3使用

public class BigDecimalDemo01 {
    
    

    public static void main(String[] args) {
    
    

        // 创建两个BigDecimal对象
        BigDecimal b1 = new BigDecimal("0.3") ;
        BigDecimal b2 = new BigDecimal("4") ;

        // 调用方法进行b1和b2的四则运算,并将其运算结果在控制台进行输出
        System.out.println(b1.add(b2));         // 进行加法运算
        System.out.println(b1.subtract(b2));    // 进行减法运算
        System.out.println(b1.multiply(b2));    // 进行乘法运算
        System.out.println(b1.divide(b2));      // 进行除法运算

    }

}

17.8.4底层原理

把数据看成字符串,遍历得到里面的一个字符,把这些字符在ASCII码表上的值,都存储到数组中

image-20230811225140265

17.9Java JDK7

笔记小结:详细见各个小节

image-20230318214149494

17.9.1SimpleDateFormat类

笔记小结:

  1. 概述:在Date对象与String对象之间转换

  2. 常用方法:

    • 构造方法:SimpleDateFormat(String pattern)

      • 格式规则

        标识字母(区分大小写) 含义
        y
        M
        d
        H
        m
        s
    • 常用成员方法:String format(Date date)、Date parse(String source)

17.9.1.1概述

java.text.SimpleDateFormat 是日期/时间格式化类,我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来回转换。

  • 格式化:按照指定的格式,把Date对象转换为String对象。
  • 解析:按照指定的格式,把String对象转换为Date对象。

17.9.1.2常用成员方法

17.9.1.2.1构造方法

​ public SimpleDateFormat(String pattern)`:用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat。参数pattern是一个字符串,代表日期时间的自定义格式

格式规则

常用的格式规则为:

标识字母(区分大小写) 含义
y
M
d
H
m
s

备注:更详细的格式规则,可以参考SimpleDateFormat类的API文档。

17.9.1.2.2成员方法
public String format(Date date)://将Date对象格式化为字符串

public Date parse(String source)://将字符串解析为Date对象

17.9.1.3基本用例

package com.itheima.a01jdk7datedemo;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class A03_SimpleDateFormatDemo1 {
    
    
    public static void main(String[] args) throws ParseException {
    
    
        /*
            public simpleDateFormat() 默认格式
            public simpleDateFormat(String pattern) 指定格式
            public final string format(Date date) 格式化(日期对象 ->字符串)
            public Date parse(string source) 解析(字符串 ->日期对象)
        */

        //1.定义一个字符串表示时间
        String str = "2023-11-11 11:11:11";
        //2.利用空参构造创建simpleDateFormat对象
        // 细节:
        //创建对象的格式要跟字符串的格式完全一致
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = sdf.parse(str);
        //3.打印结果
        System.out.println(date.getTime());//1699672271000


    }

    private static void method1() {
    
    
        //1.利用空参构造创建simpleDateFormat对象,默认格式
        SimpleDateFormat sdf1 = new SimpleDateFormat();
        Date d1 = new Date(0L);
        String str1 = sdf1.format(d1);
        System.out.println(str1);//1970/1/1 上午8:00

        //2.利用带参构造创建simpleDateFormat对象,指定格式
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss");
        String str2 = sdf2.format(d1);
        System.out.println(str2);//1970年01月01日 08:00:00

        //课堂练习:yyyy年MM月dd日 时:分:秒 星期
    }
}

17.9.2Calendar类(重点)

笔记小结:

  1. 概述:该类表示一个“日历类”,可以进行日期运算

  2. 常用成员方法:

    方法名 说明
    public static Calendar getInstance() 获取一个它的子类GregorianCalendar对象。
    public int get(int field) 获取某个字段的值。field参数表示获取哪个字段的值,
    可以使用Calender中定义的常量来表示:
    Calendar.YEAR : 年
    Calendar.MONTH :月
    Calendar.DAY_OF_MONTH:月中的日期
    Calendar.HOUR:小时
    Calendar.MINUTE:分钟
    Calendar.SECOND:秒
    Calendar.DAY_OF_WEEK:星期
    public void set(int field,int value) 设置某个字段的值
    public void add(int field,int amount) 为某个字段增加/减少指定的值

17.9.2.1概述

  • java.util.Calendar类表示一个“日历类”,可以进行日期运算。它是一个抽象类不能创建对象,我们可以使用它的子类:java.util.GregorianCalendar类。
  • 有两种方式可以获取GregorianCalendar对象:
    • 直接创建GregorianCalendar对象
    • 通过Calendar的**静态方法getInstance()**方法获取GregorianCalendar对象【本次课使用】

17.9.2.2常用成员方法

image-20230318215858670

方法名 说明
public static Calendar getInstance() 获取一个它的子类GregorianCalendar对象。
public int get(int field) 获取某个字段的值。field参数表示获取哪个字段的值,
可以使用Calender中定义的常量来表示:
Calendar.YEAR : 年
Calendar.MONTH :月
Calendar.DAY_OF_MONTH:月中的日期
Calendar.HOUR:小时
Calendar.MINUTE:分钟
Calendar.SECOND:秒
Calendar.DAY_OF_WEEK:星期
public void set(int field,int value) 设置某个字段的值
public void add(int field,int amount) 为某个字段增加/减少指定的值

17.9.2.3底层原理

会根据系统的不同时区来获取不同的日历对象。把会把时间中的纪元,年,月,日,时,分,秒,星期,等等的都放到一个数组当中

17.10Java JDK8

笔记小结:见各个小节

17.10.1概述

image-20230318220614239

JDK8时间类类名 作用
ZoneId 时区
Instant 时间戳
ZoneDateTime 带时区的时间
DateTimeFormatter 用于时间的格式化和解析
LocalDate 年、月、日
LocalTime 时、分、秒
LocalDateTime 年、月、日、时、分、秒
Duration 时间间隔(秒,纳,秒)
Period 时间间隔(年,月,日)
ChronoUnit 时间间隔(所有单位)

17.10.2ZoneId 时区

笔记小节:

  1. 概念:该类是表示时区的类

  2. 常用成员方法

    image-20230318220735008

17.10.2.1概述

​ 在 Java 中,ZoneId 是表示时区的类。时区可以通过 UTC(协调世界时)的偏移量来计算,ZoneId 提供了许多方法来获取时区信息、转换时区、比较时区

17.10.2.2常用成员方法

image-20230318220735008

17.10.2.3基本用例

/*
        static Set<string> getAvailableZoneIds() 获取Java中支持的所有时区
        static ZoneId systemDefault() 获取系统默认时区
        static Zoneld of(string zoneld) 获取一个指定时区
        */

//1.获取所有的时区名称
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
System.out.println(zoneIds.size());//600
System.out.println(zoneIds);// Asia/Shanghai

//2.获取当前系统的默认时区
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId);//Asia/Shanghai

//3.获取指定的时区
ZoneId zoneId1 = ZoneId.of("Asia/Pontianak");
System.out.println(zoneId1);//Asia/Pontianak

17.10.3Instant 时间戳

笔记小节:

  1. 概念:该类用于表示时间戳

  2. 常用成员方法:

        static Instant now() 获取当前时间的Instant对象(标准时间)
        static Instant ofXxxx(long epochMilli) 根据(/毫秒/纳秒)获取Instant对象
        ZonedDateTime atZone(ZoneIdzone) 指定时区
        boolean isxxx(Instant otherInstant) 判断系列的方法
        Instant minusXxx(long millisToSubtract) 减少时间系列的方法
        Instant plusXxx(long millisToSubtract) 增加时间系列的方法
    

17.10.3.1概述

​ Instant是Java 8中的一个类,用于表示时间戳(即从1970年1月1日UTC开始经过的秒数),它包含一个以秒为单位的时间戳和一个以纳秒为单位的时间戳,精确到纳秒级别。它通常用于处理时间和日期之间的转换或计算时间差。

17.10.3.2常用成员方法

    static Instant now() 获取当前时间的Instant对象(标准时间)
    static Instant ofXxxx(long epochMilli) 根据(/毫秒/纳秒)获取Instant对象
    ZonedDateTime atZone(ZoneIdzone) 指定时区
    boolean isxxx(Instant otherInstant) 判断系列的方法
    Instant minusXxx(long millisToSubtract) 减少时间系列的方法
    Instant plusXxx(long millisToSubtract) 增加时间系列的方法

17.10.3.3基本用例

//1.获取当前时间的Instant对象(标准时间)
Instant now = Instant.now();
System.out.println(now);

//2.根据(秒/毫秒/纳秒)获取Instant对象
Instant instant1 = Instant.ofEpochMilli(0L);
System.out.println(instant1);//1970-01-01T00:00:00z

Instant instant2 = Instant.ofEpochSecond(1L);
System.out.println(instant2);//1970-01-01T00:00:01Z

Instant instant3 = Instant.ofEpochSecond(1L, 1000000000L);
System.out.println(instant3);//1970-01-01T00:00:027

//3. 指定时区
ZonedDateTime time = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(time);


//4.isXxx 判断
Instant instant4=Instant.ofEpochMilli(0L);
Instant instant5 =Instant.ofEpochMilli(1000L);

//5.用于时间的判断
//isBefore:判断调用者代表的时间是否在参数表示时间的前面
boolean result1=instant4.isBefore(instant5);
System.out.println(result1);//true

//isAfter:判断调用者代表的时间是否在参数表示时间的后面
boolean result2 = instant4.isAfter(instant5);
System.out.println(result2);//false

//6.Instant minusXxx(long millisToSubtract) 减少时间系列的方法
Instant instant6 =Instant.ofEpochMilli(3000L);
System.out.println(instant6);//1970-01-01T00:00:03Z

Instant instant7 =instant6.minusSeconds(1);
System.out.println(instant7);//1970-01-01T00:00:02Z

17.10.4ZoneDateTime 带时区的时间

笔记小节:

  1. 概念:该类是带时区的时间类

  2. 常用成员方法:

        static ZonedDateTime now() 获取当前时间的ZonedDateTime对象
        static ZonedDateTime ofXxxx(。。。) 获取指定时间的ZonedDateTime对象
        ZonedDateTime withXxx(时间) 修改时间系列的方法
        ZonedDateTime minusXxx(时间) 减少时间系列的方法
        ZonedDateTime plusXxx(时间) 增加时间系列的方法
    

17.10.4.1概述

ZonedDateTime 是 Java 8 中提供的带时区的时间类,它继承自 LocalDateTime 类,可以表示带时区的日期时间信息。

17.10.4.2常用成员方法

    static ZonedDateTime now() 获取当前时间的ZonedDateTime对象
    static ZonedDateTime ofXxxx(。。。) 获取指定时间的ZonedDateTime对象
    ZonedDateTime withXxx(时间) 修改时间系列的方法
    ZonedDateTime minusXxx(时间) 减少时间系列的方法
    ZonedDateTime plusXxx(时间) 增加时间系列的方法

17.10.4.3基本用例

//1.获取当前时间对象(带时区)
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);

//2.获取指定的时间对象(带时区)1/年月日时分秒纳秒方式指定
ZonedDateTime time1 = ZonedDateTime.of(2023, 10, 1,
                                       11, 12, 12, 0, ZoneId.of("Asia/Shanghai"));
System.out.println(time1);

//通过Instant + 时区的方式指定获取时间对象
Instant instant = Instant.ofEpochMilli(0L);
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
ZonedDateTime time2 = ZonedDateTime.ofInstant(instant, zoneId);
System.out.println(time2);


//3.withXxx 修改时间系列的方法
ZonedDateTime time3 = time2.withYear(2000);
System.out.println(time3);

//4. 减少时间
ZonedDateTime time4 = time3.minusYears(1);
System.out.println(time4);

//5.增加时间
ZonedDateTime time5 = time4.plusYears(1);
System.out.println(time5);

17.10.5DateTimeFormatter 用于时间的格式化和解析

笔记小节:

  1. 概念:该类是用于日期时间格式化和解析的类

  2. 常用成员方法:

    	static DateTimeFormatter ofPattern(格式) 获取格式对象
        String format(时间对象) 按照指定方式格式化
    

17.10.5.1概述

DateTimeFormatter是Java 8中用于日期时间格式化和解析的类

17.10.5.2常用成员方法

	static DateTimeFormatter ofPattern(格式) 获取格式对象
    String format(时间对象) 按照指定方式格式化

17.10.5.3基本用例

//获取时间对象
ZonedDateTime time = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));

// 解析/格式化器
DateTimeFormatter dtf1=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm;ss EE a");
// 格式化
System.out.println(dtf1.format(time));

17.10.6LocalDateTime、LocalDate、LocalTime(重点)

笔记小节:

  1. 概念:该类是Java 8中的日期时间API中的三个类,用于处理本地日期时间日期时间

  2. 常用成员方法:

    image-20230319084442661

17.10.6.1概述

17.10.6.1.1定义

​ LocalDateTime、LocalDate、LocalTime 是Java 8中的日期时间API中的三个类,用于处理本地日期、时间和日期时间。它们都是线程安全的,可以在多线程环境中使用。

17.10.6.1.2体系结构

image-20230319084507961

17.10.6.2常用成员方法

image-20230319084442661

17.10.6.3基本用例

//1.获取当前时间的日历对象(包含 年月日)
LocalDate nowDate = LocalDate.now();
//System.out.println("今天的日期:" + nowDate);
//2.获取指定的时间的日历对象
LocalDate ldDate = LocalDate.of(2023, 1, 1);
System.out.println("指定日期:" + ldDate);

System.out.println("=============================");

//3.get系列方法获取日历中的每一个属性值//获取年
int year = ldDate.getYear();
System.out.println("year: " + year);
//获取月//方式一:
Month m = ldDate.getMonth();
System.out.println(m);
System.out.println(m.getValue());

//方式二:
int month = ldDate.getMonthValue();
System.out.println("month: " + month);


//获取日
int day = ldDate.getDayOfMonth();
System.out.println("day:" + day);

//获取一年的第几天
int dayofYear = ldDate.getDayOfYear();
System.out.println("dayOfYear:" + dayofYear);

//获取星期
DayOfWeek dayOfWeek = ldDate.getDayOfWeek();
System.out.println(dayOfWeek);
System.out.println(dayOfWeek.getValue());

//is开头的方法表示判断
System.out.println(ldDate.isBefore(ldDate));
System.out.println(ldDate.isAfter(ldDate));

//with开头的方法表示修改,只能修改年月日
LocalDate withLocalDate = ldDate.withYear(2000);
System.out.println(withLocalDate);

//minus开头的方法表示减少,只能减少年月日
LocalDate minusLocalDate = ldDate.minusYears(1);
System.out.println(minusLocalDate);


//plus开头的方法表示增加,只能增加年月日
LocalDate plusLocalDate = ldDate.plusDays(1);
System.out.println(plusLocalDate);

//-------------
// 判断今天是否是你的生日
LocalDate birDate = LocalDate.of(2000, 1, 1);
LocalDate nowDate1 = LocalDate.now();

MonthDay birMd = MonthDay.of(birDate.getMonthValue(), birDate.getDayOfMonth());
MonthDay nowMd = MonthDay.from(nowDate1);

System.out.println("今天是你的生日吗? " + birMd.equals(nowMd));//今天是你的生日吗?

17.10.7ChronoUnit 时间间隔(所有单位)(重点)

笔记小节:

  1. 概念:该类是 Java 8 中的一个枚举类型,包含了所有可能的时间间隔单位

  2. 常用成员方法:

    between(Temporal startInclusive, Temporal endExclusive): 计算两个时间点之间的时间差,返回 long 类型的结果;
    getDuration(): 获取表示该时间单位的时长的 Duration 对象;
    toString(): 返回时间单位的名称。
    

17.10.7.1概述

​ ChronoUnit 是 Java 8 中的一个枚举类型,包含了所有可能的时间间隔单位,包括年、月、周、日、小时、分钟、秒、毫秒、微秒、纳秒等。可以使用 ChronoUnit 来表示两个时间点之间的时间间隔,并且可以执行一些简单的算术运算(如加、减)

17.10.7.2常用成员方法

between(Temporal startInclusive, Temporal endExclusive): 计算两个时间点之间的时间差,返回 long 类型的结果;
getDuration(): 获取表示该时间单位的时长的 Duration 对象;
toString(): 返回时间单位的名称。

17.10.7.3基本用例

System.out.println("相差的年数:" + ChronoUnit.YEARS.between(birthDate, today));
System.out.println("相差的月数:" + ChronoUnit.MONTHS.between(birthDate, today));
System.out.println("相差的周数:" + ChronoUnit.WEEKS.between(birthDate, today));
System.out.println("相差的天数:" + ChronoUnit.DAYS.between(birthDate, today));
System.out.println("相差的时数:" + ChronoUnit.HOURS.between(birthDate, today));
System.out.println("相差的分数:" + ChronoUnit.MINUTES.between(birthDate, today));
System.out.println("相差的秒数:" + ChronoUnit.SECONDS.between(birthDate, today));
System.out.println("相差的毫秒数:" + ChronoUnit.MILLIS.between(birthDate, today));
System.out.println("相差的微秒数:" + ChronoUnit.MICROS.between(birthDate, today));
System.out.println("相差的纳秒数:" + ChronoUnit.NANOS.between(birthDate, today));
System.out.println("相差的半天数:" + ChronoUnit.HALF_DAYS.between(birthDate, today));
System.out.println("相差的十年数:" + ChronoUnit.DECADES.between(birthDate, today));
System.out.println("相差的世纪(百年)数:" + ChronoUnit.CENTURIES.between(birthDate, today));
System.out.println("相差的千年数:" + ChronoUnit.MILLENNIA.between(birthDate, today));
System.out.println("相差的纪元数:" + ChronoUnit.ERAS.between(birthDate, today));

17.11Concurrent类

17.11.1概述

​ 在Java中,线程池(ThreadPool)是一种用于管理和复用线程的机制,它通过维护一组预先创建的线程,可以有效地处理多线程任务,提高程序的性能和资源利用率。

Java提供了一个内置的线程池框架——Executor框架,用于创建和管理线程池。Executor框架位于java.util.concurrent包中,主要包含以下几个关键组件:

  1. Executor接口:定义了执行任务的基本方法,是线程池框架的顶层接口。它只有一个方法void execute(Runnable command),用于执行传入的Runnable任务。
  2. ExecutorService接口:继承自Executor接口,是更完整的线程池接口,提供了更丰富的功能。它扩展了Executor接口,增加了任务提交、任务执行控制、获取执行结果等方法。可以通过调用submit()方法提交任务,返回一个表示异步计算结果的Future对象。
  3. ThreadPoolExecutor类:是ExecutorService接口的一个具体实现,也是最常用的线程池类。它提供了灵活的线程池配置选项,包括线程池大小、任务队列、拒绝策略等。通过创建ThreadPoolExecutor对象,可以自定义线程池的各种参数。

可以通过Executors类中的静态方法来创建不同类型的线程池,例如:

  • Executors.newFixedThreadPool(int nThreads):创建固定大小的线程池。
  • Executors.newCachedThreadPool():创建缓存线程池,可以根据需要自动调整线程数量。
  • Executors.newSingleThreadExecutor():创建单个线程的线程池。

17.11.2基本用例

private static final ExecutorService ex = Executors.newFixedThreadPool(500) //定义一个线程池

    @Test
    public void testSize() {
    
    
    Runnable runnable = () -> {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            long code = redisIdWorker.nextId("code");
            System.out.println(code);
        }
    };

    for (int i = 0; i < 300; i++) {
    
    
        ex.submit(runnable);
    }
}

补充:

  • Executors.newFixedThreadPool(500)创建了一个固定大小为500的线程池。
  • Runnable接口只包含一个抽象方法run(),该方法没有参数和返回值。当一个类实现了Runnable接口,并实现了run()方法,该类的实例就可以被传递给Thread类的构造函数,并作为线程的任务来执行。

知识加油站

1.成员变量和局部变量

成员变量和局部变量的区别

  • 类中位置不同:成员变量(类中方法外)局部变量(方法内部或方法声明上)
  • 内存中位置不同:成员变量(堆内存)局部变量(栈内存)
  • 生命周期不同:成员变量(随着对象的存在而存在,随着对象的消失而消失)局部变量(随着方法的调用而存在,醉着方法的调用完毕而消失)
  • 初始化值不同:成员变量(有默认初始化值)局部变量(没有默认初始化值,必须先定义,赋值才能使用)

2.DateTimeFormatter 和 SimpleDateFormat 区别

DateTimeFormatterSimpleDateFormat 都是用于格式化和解析日期时间字符串的类,但它们有几个主要的区别:

  1. 线程安全性:SimpleDateFormat 不是线程安全的,因此在多线程环境下使用它可能会导致并发问题,而 DateTimeFormatter 是线程安全的,可以在多线程环境下安全使用。
  2. API 设计:DateTimeFormatter 是在 Java 8 中引入的,是新的 API,而 SimpleDateFormat 则是在 Java 1.1 中引入的,是旧的 API。DateTimeFormatter 设计更加清晰,易于使用,而且支持更多的格式化选项,比如使用解析器解析文本。而 SimpleDateFormat 使用的是预定义的格式化字符串,需要自己手动解析日期时间字符串。
  3. 日期时间处理:DateTimeFormatter 支持 LocalDateTimeZonedDateTimeOffsetDateTime 等 Java 8 新增的日期时间类,而 SimpleDateFormat 只支持 java.util.Datejava.util.Calendar
  4. 性能:DateTimeFormatterSimpleDateFormat 更快,因为它是基于解析器的,不需要进行解析字符串,也不需要进行格式检查。

因此,对于 Java 8 及更高版本,建议使用 DateTimeFormatter 进行日期时间格式化和解析操作。而对于 Java 7 及更低版本,只能使用 SimpleDateFormat。如果必须在多线程环境下使用 SimpleDateFormat,可以使用 ThreadLocal 来实现线程安全。

补充:

  • 线程安全,也就意味着是否要抛出异常.

  • 通常来说,使用DateTimeFormatter 时,将字符串转换为 时间类型,需要指定时区偏移量

3.Java键盘录入

笔记小结:

  1. 键盘录入分为两套:

    • next()、nextInt()、nextDouble()配套使用
    • nextLine()单独使用
  2. 注意:

    • 如果用了这三个其中一个,就不要用nextLine()
    • 如果想要整数,那么先接收,再使用Integer.parseInt进行类型转换
Scanner sc = new Scanner(System.in);
String s = sc.next();//键盘录入123
System.out.println("此时为字符串" + s);//此时123是字符串
int i = sc.nextInt();//键盘录入123
System.out.println("此时为整数:" + i);
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();//键盘录入123
System.out.println("此时为字符串" + s);//此时123是字符串
int i = Integer.parseInt(s);//想要整数再进行转换
System.out.println("此时为整数:" + i);

3.1next()、nextLine()

3.1.1概述

​ 这两种方法,可以接受任意数据,但是都会返回一个字符串

说明:

​ 比如:键盘录入abc,那么会把abc看做字符串返回。键盘录入123,那么会把123看做字符串返回。

3.1.2基本用例

Scanner sc = new Scanner(System.in);
String s = sc.next();//录入的所有数据都会看做是字符串
System.out.println(s);
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();//录入的所有数据都会看做是字符串
System.out.println(s);

3.2nextInt()

3.2.1概述

​ 只能接受整数。

说明:

​ 比如:键盘录入123,那么会把123当做int类型的整数返回、 键盘录入小数或者其他字母,就会报错。

3.2.1基本用例

Scanner sc = new Scanner(System.in);
int s = sc.nextInt();//只能录入整数
System.out.println(s);

3.3nextDouble()

3.3.1概述

​ 能接收整数和小数,但是都会看做小数返回

​ 说明:

​ 录入字母会报错

3.3.2基本用例

Scanner sc = new Scanner(System.in);
double d = sc.nextDouble();//录入的整数,小数都会看做小数。
						//录入字母会报错
System.out.println(d);

3.4总结

1.next(),nextInt(),nextDouble()在接收数据的时候,会遇到空格、回车、制表符其中一个就会停止接收数据。

Scanner sc = new Scanner(System.in);
double d = sc.nextDouble();
System.out.println(d);
//键盘录入:1.1 2.2//注意录入的时候1.1和2.2之间加空格隔开。
//此时控制台打印1.1
//表示nextDouble方法在接收数据的时候,遇到空格就停止了,后面的本次不接收。
Scanner sc = new Scanner(System.in);
int i = sc.nextInt();
System.out.println(i);
//键盘录入:1 2//注意录入的时候1和2之间加空格隔开。
//此时控制台打印1
//表示nextInt方法在接收数据的时候,遇到空格就停止了,后面的本次不接收。
Scanner sc = new Scanner(System.in);
String s = sc.next();
System.out.println(s);
//键盘录入:a b//注意录入的时候a和b之间加空格隔开。
//此时控制台打印a
//表示next方法在接收数据的时候,遇到空格就停止了,后面的本次不接收。

2.next(),nextInt(),nextDouble()在接收数据的时候,会遇到空格,回车,制表符其中一个就会停止接收数据。但是这些符号 + 后面的数据还在内存中并没有接收。如果后面还有其他键盘录入的方法,会自动将这些数据接收。

Scanner sc = new Scanner(System.in);
String s1 = sc.next();
String s2 = sc.next();
System.out.println(s1);
System.out.println(s2);
//此时值键盘录入一次a b(注意a和b之间用空格隔开)
//那么第一个next();会接收a,a后面是空格,那么就停止,所以打印s1是a
//但是空格+b还在内存中。
//第二个next会去掉前面的空格,只接收b
//所以第二个s2打印出来是b

3.nextLine()方法是把一整行全部接收完毕。

Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
System.out.println(s);
//键盘录入a b(注意a和b之间用空格隔开)
//那么nextLine不会过滤前面和后面的空格,会把这一整行数据全部接收完毕。

4.混用引起的后果

Scanner sc = new Scanner(System.in);//①
int i = sc.nextInt();//②
String s = sc.nextLine();//③
System.out.println(i);//④
System.out.println(s);//⑤

说明:

​ 当代码运行到第二行,会让我们键盘录入,此时录入123。但是实际上我们录的是123+回车。而nextInt是遇到空格,回车,制表符都会停止。所以nextInt只能接受123,回车还在内存中没有被接收。此时就被nextLine接收了。所以,如果混用就会导致nextLine接收不到数据

猜你喜欢

转载自blog.csdn.net/D_boj/article/details/132224349
今日推荐