Java重温之路(上卷)

文章目录

第1章 java概述

1、java 百度百科

  1. Java 是一个通用术语,用于表示 Java 软件及其组件,包括“Java 运行时环境 (JRE)”、“Java 虚拟机 (JVM)”以及“插件”。
  2. Java具有大部分编程语言所共有的一些特征,被特意设计用于互联网的分布式环境。Java具有类似于C++语言的形式和感觉,但它要比C++语言更易于使用,而且在编程时彻底采用了一种以对象为导向的方式。

2、JDK,JRE 基本介绍

(1) JDK 的全称(Java Development Kit Java开发工具包)
JDK=JRE + java 的开发工具【java, javac,javadoc,javap 等】

(2) JDK 是提供给Java开发人员使用的,其中包含了java的开发工具,也包括了JRE。所以安装了JDK,就不用在单独安装JRE了。

(2) JRE (Java Runtime Environment Java运行环境)
JRE=JVM + Java的核心类库[类]
包括.Java虚拟机(JVMJava Virtual Machine)和Java程序所需的核心类库等,如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。

(3) JDK、JRE和JVM的包含关系
JDK=JRE+开发工具集(例如Javac,java编译工具等)
JRE=JVM+Java SE标准类库(java核心类库)
如果只想运行开发好的.class文件只需要.JRE

3、 java下载安装配置

官网下载JDK:

https://www.oracle.com/java/technologies/downloads/archive/#JavaSE

配置环境变量:

目的:在任意磁盘目录下执行java命令
(1)我的电脑--属性--高级系统设置--环境变量
(2)增加JAVA HOME 环境变量,指向jdk的安装目录C:\Program Files (x86)\Java\jdk1.8.0_361
(3)编辑path 环境变量,增加%JAVA HOME%\bin和%JAVA_HOME%\jre\bin
(4)打开DOS命令行,任意目录下敲入javac/java。如果出现javac的参数信息,配置成功。

注意:配置用户环境变量只对本用户生效,配置系统环境变量对所有用户生效

4、Java开发预热

4.1 Java开发注意事项和细节说明

(1) Java源文件以.java为扩展名。源文件的基本组成部分是类(class)。
(2) Java应用程序的执行入口是main()方法。它有固定的书写格式:public static void main(Stringl] args){...}
(3) Java语言严格区分大小写。
(4) 一个源文件中最多只能有一个public类。其它类的个数不限。
(5) 如果源文件包含一个public类,则文件名必须按该类名命名!
(6) 一个源文件中最多只能有一个public类。其它类的个数不限,也可以将main方法写在非public类中,
    然后指定运行非public类,这样入口方法就是非public的main方法

4.2 注释类型

1) 单行注释 //
2) 多行注释 /* */
3) 文档注释 /** *

4.3 文档注释

由JDK的javadoc所解析,生成一套网页文件形式体现该程序的说明文档,一般写在类。

//执行命令
javadoc -d 文件夹路径名下 -xx -yy Demo.java
//例如:javadoc -d D:\temp -author -version Comment.java

4.4 Java API接口文档

API (Application Programming Interface,应
用程序编程接口)是Java提供的基本编程接口(java提供的类还有相关的方法)。中文在线文档:https://www.matools.com/api/java8

在这里插入图片描述

4.5 DOS命令简介

Dos: Disk Operating System 磁盘操作系统。

1)查看当前目录是有什么内容 dir
	dir   dir E:\kugou\temp
2)切换到其他盘下  盘符号 cd(change directory)
	cd /D c:
3)切换到当前盘的其他目录下(使用相对路径和绝对路径演示), ..\表示上一级目录
	E:\>cd E:\Kugou        E:\>cd ..\..\Kugouu
4)切换到上一级 
	cd ..
5)切换到根目录      
	 cd \
6)查看指定的目录下所有的子级目录   
	tree
7) 清屏   
	cls 
8)退出DOS   
	exit
9)其他指令
md 创建目录   rd 删除目录  copy 拷贝文件 del 删除文件  .......

第2章 变量、数据类型、运算符

1、变量

变量是程序的基本组成单位,变量有三个基本要素(类型+名称+值)
变量相当于内存中一个数据存储空间的表示,你可以把变量看做是一个房间的门牌号,通过门牌号我们可以找到房
间,而通过变量名可以访问到变量(值)

1.1、使用

1)声明变量    int a;
2)赋值       a= 60;
3)直接声明赋值    String str = "code SE";

1.2 注意细节

1.变量表示内存中的一个存储区域[不同的变量,类型不同,占用的空间大小不同,比如: int 4个字节,double 就是8个字节
2、该区域有自己的名称[变量名]和类型[数据类型]
3.变量必须先声明,后使用,即有顺序
4.该区域的数据/值可以在同一类型范围内不断变化
5.变量在同一个作用域内不能重名
6.变量=变量名+值+数据类型,变量三要素

1.3 java程序中 + 的使用

1.当左右两边都是数值型时,则做加法运算
2.当左右两边有一方为字符串,则做拼接运算
3.运算顺序,是从左到右

System.out.printIn(100+ 98);//198
System.out.println("100"+ 98)://10098
System.out.println(100+3+"hello");//103hello
System.out.println("hello" +100+3); //hello1003

2、数据类型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.1 整数类型

Java的整数类型就是用于存放整数值的,比如1,2 ,50,345678等等

byte n1 = 10;  //1个字节
short n2 = 10;  //2个字节
int n3 = 10;      //4 个字节
long n4 = 10;   //8 个字

整型的注意细节:

1. Java各整数类型有固定的范围和字段长度,不受具体OS[操作系统]的影响,以保证java程序的可移植性。
2. Java的整型常量(具体值)默认为int型,声明long型常量须后加I或L
3. java程序中变量常声明为int型,除非不足以表示大数,才使用long
4. bit:计算机中的最小存储单位。byte:计算机中基本存储单元,1byte = 8 bit.

2.3 浮点类型

Java 的浮点类型可以表示一个小数,比如 123.4 ,7.8 ,0.12

说明:(1) 关于浮点数在机器中存放形式的简单说明,浮点数=符号位+指数位+尾数位 (2) 尾数部分可能丢失,造成精度损失(小数都是近似值)

浮点类型注意细节:

1.与整数类型类似,Java浮点类型也有固定的范围和字段长度,不受具体OS的影响。[float 4个字节double是8个字节]
2. Java的浮点型常量(具体值)默认为double型,声明float型常量,须后加‘f或‘F'
3.浮点型常量有两种表示形式:
  1)十进制数形式:如:5.12 512.0f.512(必须有小数点)
  2)科学计数法形式:如:5.12e2 [5.12*10的2次方]5.12E-2 [5.12/10的2次方]
4.通常情况下,应该使用double型,因为它比float型更精确。
public class FloatDetail {
    public static void main(String[] args) {
        //Java 的浮点型常量(具体值)默认为 double 型,声明 float 型常量,须后加‘f’或‘F'
        float num2 = 1.1F; //对的
        double num3 = 1.1; //对
        double num4 = 1.1f; //对
        //十进制数形式:如:5.12 512.0f .512 (必须有小数点)
        double num5 = .123; //0.123
        System.out.println(num5);
        
        //科学计数法形式:如:5.12e2 [5.12 * 10 的 2 次方 ] 5.12E-2 []
        System.out.println(5.12e2);//512.0
        System.out.println(5.12E-2);//0.0512
        
        //通常情况下,应该使用 double 型,因为它比 float 型更精确。
        //[举例说明]double num9 = 2.1234567851;float num10 = 2.1234567851F;
        double num9 = 2.1234567851;
        float num10 = 2.1234567851F;
        System.out.println(num9);
        System.out.println(num10);
        
        //浮点数使用陷阱: 2.7 和 8.1 / 3 比较
        //看看一段代码
        double num11 = 2.7;
        double num12 = 2.7; //8.1 / 3; //2.7
        System.out.println(num11);//2.7
        System.out.println(num12);//接近 2.7 的一个小数,而不是 2.7
        //得到一个重要的使用点: 当我们对运算结果是小数的进行相等判断是,要小心
        //应该是以两个数的差值的绝对值,在某个精度范围类判断
        if (num11 == num12) {
            System.out.println("num11 == num12 相等");
        }
        //正确的写法
        if (Math.abs(num11 - num12) < 0.000001) {
            System.out.println("差值非常小,到我的规定精度,认为相等...");
        }
    }
}

2.4 字符类型(char)

字符类型可以表示单个字符,字符类型是char,char 是两个字节(可以存放汉字),多个字符我们用字符串 String。

char c1 = 'a';
char c2 = '好';
char c3 ='\t';
char c4 = 97; //a

细节:

1.字符常量是用单引号(‘’)括起来的单个字符。例如:char c1 = 'a';char c2 ='中"; char c3 = '9";
2. Java中还允许使用转义字符′来将其后的字符转变为特殊字符型常量。例如:char c3 =‘\n'; '\n'表示换行符
3.在java中,char的本质是一个整数,在输出时,是unicode码对应的字符。http://tool.chinaz.com/Tools/Unicode.aspx
4.可以直接给char赋一个整数,然后输出时,会按照对应的unicode字符输出[97-》a]
5. char类型是可以进行运算的,相当于一个整数,因为它都对应有Unicode码.
public static void main(String[] args) {
      //在 java 中,char 的本质是一个整数,在默认输出时,是 unicode 码对应的字符
       //要输出对应的数字,可以(int)字符
       char c1 = 97;
       char c2 = 'b'; //输出'b'对应的数
       char c3 = '中';
       char c4 = 20013;
       System.out.println(c1); // a
       System.out.println((int) c2); //20013
       System.out.println((int) c3); //98
       System.out.println(c4); //中

       //char 类型是可以进行运算的,相当于一个整数,因为它都对应有 Unicode 码.
       System.out.println('a' + 10);//107
}

ASSCII编码、Unicode编码、UFT-8编码介绍:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.5 布尔类型(boolean)

布尔类型也叫boolean类型,booolean类型数据只允许取值true和false,无null。
boolean类型占1个字节。
boolean类型适于逻辑运算,一般用于程序流程控制。

3、基本数据类型转换

3.1 自动类型转换

在这里插入图片描述
在这里插入图片描述

3.2 强制转换

是自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型。使用时要加上强制转换符(),但可能造成精度降低或溢出,格外要注意。
在这里插入图片描述

3.3 基本数据类型和 String 类型的转换

在程序开发中,经常需要将基本数据类型转成String类型。或者将String类型转成基本数据类型。

  • 基本类型转String类型 , 语法:将基本类型的值+""即可
  • String类型转基本数据类型,语法:通过基本类型的包装类调用parseXX方法即可
    上代码:
public class ToString1 {
    public static void main(String[] args) {
        int n1 = 100;
        float n2 = 3.3f;
        double n3 = 3.1415926;
        boolean b1 = false;

        //基本数据类型->String
        String str1 = n1 + "";
        String str2 = n2 + "";
        String str3 = n3 + "";
        String str4 = b1 + "";

        System.out.println("str1=" + str1 + "\t" + "str2=" + str2 + "\t" + "str3=" + str3 + "\t" + "str4=" + str4);
        //str1=100	str2=3.3	str3=3.1415926	str4=false

        //String->对应的基本数据类型
        //使用 基本数据类型对应的包装类的相应方法,得到基本数据类型
        String s5 = "123";
        int num1 = Integer.parseInt(s5);
        double num2 = Double.parseDouble(s5);
        float num3 = Float.parseFloat(s5);
        long num4 = Long.parseLong(s5);
        byte num5 = Byte.parseByte(s5);
        boolean b = Boolean.parseBoolean("true");
        short num6 = Short.parseShort(s5);

        System.out.println("num1=" + num1 + "\t" + "num2=" + num2 + "\t" + "num3=" + num3 + "\t" + "num4=" + num4 + "\t" + "num5=" + num5 + "\t" + "b=" + b + "\t" + "num46=" + num6);
        //num1=123	num2=123.0	num3=123.0	num4=123	num5=123	b=true	num46=123

        //得到 s5 字符串的第一个字符 '1'==> s5.charAt(0);

        System.out.println(s5.charAt(0));//1

    }
}

注意:
1)在将String类型转成基本数据类型时,要确保String类型能够转成有效的数据,比如我们可以把“123",个整数,但是不能把"hello"转成一个整数
2)如果格式不正确,就会抛出异常,程序就会终止

4、运算符

4.1 算术运算符

运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。

(1) 算术运算符 (2) 赋值运算符 (3) 关系运算符 [比较运算符] 
(4) 逻辑运算符 (5) 位运算符 [需要二进制基础] (6) 三元运算符

运算符:
在这里插入图片描述

4.2关系运算符(比较运算符)

4.2.1介绍
1)关系运算符的结果都是boolean型,也就是要么是true,要么是false
2)关系表达式经常用在if结构的条件中或循环结构的条件中
在这里插入图片描述

4.3逻辑运算符

4.3.1 介绍
用于连接多个条件(多个关系表达式),最终的结果也是一个boolean值

4.3.2逻辑运算符一览
分为两组
1)短路与&& ,短路或,取反!
2)逻辑 与 &,逻辑 或 |,逻辑 异或
在这里插入图片描述
4.3.3 说明逻辑运算规则:

1) a&b : & 叫逻辑与:规则:当 a 和 b 同时为 true ,则结果为 true, 否则为 false 
2) a&&b : && 叫短路与:规则:当 a 和 b 同时为 true ,则结果为 true,否则为 false 
3) a|b : | 叫逻辑或,规则:当 a 和 b ,有一个为 true ,则结果为 true,否则为 false
4) a||b : || 叫短路或,规则:当 a 和 b ,有一个为 true ,则结果为 true,否则为 false 
5) !a : 叫取反,或者非运算。当 a 为 true, 则结果为 false, 当 a 为 false 是,结果为 true 
6) a^b: 叫逻辑异或,当 a 和 b 不同时,则结果为 true, 否则为 false

4.3.4 &&&基本规则

名称语法特点
短路与&&条件1&&条件2两个条件都为true,结果为true,否则 false
逻辑与&条件1&条件2两个条件都为true,结果为true,否则false

public class LogicOperator1 {
    public static void main(String[] args) {
        //&&短路与和&
        int age = 50;
        if (age > 30 && age < 100) {
            System.out.println("OK");
        }

        //&逻辑与使用
        if (age > 20 & age < 90) {
            System.out.println("ok200");
        }

        //区别
        int a = 10;
        int b = 20;
        //对于&&短路与而言,如果第一个条件为 false ,后面的条件不再判断
        // 对于&逻辑与而言,如果第一个条件为 false ,后面的条件仍然会判断
        if (a < 1 & ++b < 50) {
            System.out.println("ok300");
        }
        System.out.println("a=" + a + " b=" + b);// 10  21
    }
}

注意&&和&使用区别

1) &&短路与:如果第一个条件为false,则第二个条件不会判断,最终结果为false,效率高
2)&逻辑与:不管第一个条件是否为 false,第二个条件都要判断,效率低
3)开发中,使用的基本是使用短路与&&,效率高

4.3.5 |||基本规则

名称语法特点
短路或||条件1||条件2两个条件中只要有一个成立,结果为true,否则为false
逻辑或|条件1|条件2只要有一个条件成立,结果为true,否则为false
public class LogicOperator2 {
    public static void main(String[] args) {
        // || 规则: 两个条件中只要有一个成立,结果为 true,否则为 false
        // | 规则: 两个条件中只要有一个成立,结果为 true,否则为 false
        int age = 50;
        if (age > 20 || age < 30) {
            System.out.println("ok100");
        }
        
        if (age > 20 | age < 30) {
            System.out.println("ok200");
        }

        //看看区别
        // (1)||短路或:如果第一个条件为 true,则第二个条件不会判断,最终结果为 true,效率高
        // (2)| 逻辑或:不管第一个条件是否为 true,第二个条件都要判断,效率低
        int a = 4;
        int b = 9;
        if (a > 1 || ++b > 4) { // 可以换成 | 测试
            System.out.println("ok300");
            System.out.println("a=" + a + " b=" + b);//4 9
        }
    }
}

注意||和│使用区别

1)||短路或:如果第一个条件为true,则第二个条件不会判断,最终结果为true,效率高
2)│逻辑或:不管第一个条件是否为true,第二个条件都要判断,效率低
3)开发中,我们基本使用Ⅱ

4.3.6 ! 取反

名称语法特点
! 非(取反)!条件如果条件本身成立,结果为 false,否则为 true

4.3.7 ^逻辑异或
a^b:叫逻辑异或,当a和 b 不同时,则结果为true,否则为false

System.out.println((2<4)^(9>10));//true

4.4 赋值运算符

赋值运算符就是将某个运算后的值,赋给指定的变量。

4.4.21 赋值运算符的分类

基本赋值运算符=a = 10;
复合赋值运算符+= ,-= ,*= , /= ,%=a += b;【等价a = a+ b; 】 a -=b;【等价a=a - b; 】

4.4.2 赋值运算符特点

1)运算顺序从右往左int num= a+b+c;
2)赋值运算符的左边只能是变量,右边可以是变量、表达式、常量值int num= 20; int num2=78*34 -10; int num3=a;
3)复合赋值运算符等价于下面的效果比如: a+=3;等价于a=a+3;其他类推
4)复合赋值运算符会进行类型转换。byte b= 2; b+=3; b++;

4.5 三元运算符

基本语法

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

运算规则:
1.如果条件表达式为true,运算后的结果是表达式1;
2如果条件表达式为false,运算后的结果是表达式2:

4.6 标识符、关键字、保留字

标识符在这里插入图片描述
关键字
定义:被Java语言赋予了特殊含义,用做专门用途的字符串(单词)
特点:关键字中所有字母都为小写
在这里插入图片描述
在这里插入图片描述
保留字
Java保留字:现有Java版本尚未使用,但以后版本可能会作为关键字使用。自己命名标识符时要避免使用这些保留字byValue、cast、future、generic、 inner、operator、 outer、rest、var 、goto 、 const

4.7 进制

在这里插入图片描述
在这里插入图片描述

进制转换详解:

待续...

4.8 补码、反码和位运算

待续...

第3章 程序控制结构

三大流程控制语句: (1) 顺序控制 (2) 分支控制 (3) 循环控制

待续....

第4章 数组、排序和查找

1、为什么需要数组

一个养鸡场有 6 只鸡,它们的体重分别是 3kg,5kg,1kg,3.4kg,2kg,50kg 。请问这六只鸡的总体重是多少?平均体重是多少? 请你编一个程序。
思路:
定义 6 个变量 , 加起来 总体重, 求出平均体重.引出 -> 数组

2、数组介绍

数组可以存放多个同一类型的数据。数组也是一种数据类型,是引用类型。即:数(数据)组(一组)就是一组数据
数组快速入门体验:

public class Array01{
    
    public static void main(String[] args) {
        /*
            它们的体重分别是 3kg,5kg,1kg,3.4kg,2kg,50kg 。
            请问这六只鸡的总体重是多少?平均体重是多少?
            思路分析
            1. 定义六个变量 double , 求和 得到总体重
            2. 平均体重 = 总体重 / 6
            3. 分析传统实现的方式问题. 6->600->566
            4. 引出新的技术 -> 使用数组来解决.
        */
        
        // double hen1 = 3;
        // double hen2 = 5;
        // double hen3 = 1;
        // double hen4 = 3.4;
        // double hen5 = 2;
        // double hen6 = 50;
        // double totalWeight = hen1 + hen2 + hen3 + hen4 + hen5 + hen6;
        // double avgWeight = totalWeight / 6;
        // System.out.println("总体重=" + totalWeight
        // + "平均体重=" + avgWeight);
        // 比如,我们可以用数组来解决上一个问题 => 体验
   
        // 定义一个数组
        // 解读
        // 1. double[] 表示 是 double 类型的数组, 数组名 hens
        // 2. {3, 5, 1, 3.4, 2, 50} 表示数组的值/元素,依次表示数组的
        // 第几个元素
      
        double[] hens = {3, 5, 1, 3.4, 2, 50, 7.8, 88.8, 1.1, 5.6, 100};
        // 遍历数组得到数组的所有元素的和, 使用 for
        // 解读
        // 1. 我们可以通过 hens[下标] 来访问数组的元素
        // 下标是从 0 开始编号的比如第一个元素就是 hens[0]
        // 第 2 个元素就是 hens[1] , 依次类推
        // 2. 通过 for 就可以循环的访问 数组的元素/值
        // 3. 使用一个变量 totalWeight 将各个元素累积
        System.out.println("===使用数组解决===");
        // 提示: 可以通过 数组名.length 得到数组的大小/长度
        // System.out.println("数组的长度=" + hens.length);
        double totalWeight = 0;
        for (int i = 0; i < hens.length; i++) {
        // System.out.println("第" + (i+1) + "个元素的值=" + hens[i]);
            totalWeight += hens[i];
        }
        System.out.println("总体重=" + totalWeight + "平均体重=" + (totalWeight / hens.length));
    }
}

3、数组的使用

3.1 使用方式1——动态初始化

 先声明数组
语法:数据类型 数组名[ ]; 也可以 数据类型[ ] 数组名;
int a[ ]; 或者 int[ ] a;
 创建数组
语法: 数组名=new 数据类型[大小];
a=new int[10];

在这里插入图片描述


import java.util.Scanner;

public class Array02{
    public static void main(String[] args) {
        // 演示 数据类型 数组名[]=new 数据类型[大小]
        // 循环输入 5 个成绩,保存到 double 数组,并输出

        // 步骤
        // 1. 创建一个 double 数组,大小 5
        //(1) 第一种动态分配方式
        // double scores[] = new double[5];
        //(2) 第 2 种动态分配方式, 先声明数组,再 new 分配空间
        double scores[]; // 声明数组, 这时 scores 是 null
        scores = new double[5]; // 分配内存空间,可以存放数据
        // 2. 循环输入
        // scores.length 表示数组的大小/长度
        //
        Scanner myScanner = new Scanner(System.in);
        for (int i = 0; i < scores.length; i++) {
            System.out.println("请输入第" + (i + 1) + "个元素的值");
            scores[i] = myScanner.nextDouble();
        }
        // 输出,遍历数组
        System.out.println("======数组的元素/值的情况如下:=======");
        for (int i = 0; i < scores.length; i++) {
            System.out.println("第" + (i + 1) + "个元素的值=" + scores[i]);
        }
    }
}

3.2 使用方式2——静态初始化

在这里插入图片描述

3.3 数组使用注意事项和细节

  1. 数组是多个相同类型数据的组合,实现对这些数据的统一管理
  2. 数组中的元素可以是任何数据类型,包括基本类型和引用类型,但是不能混用。
  3. 数组创建后,如果没有赋值,有默认值
    int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000,boolean false,String null
  4. 使用数组的步骤
    1. 声明数组并开辟空间
    2. 给数组各个元素赋值
    3. 使用数组
  5. 数组的下标是从 0 开始的
  6. 数组下标必须在指定范围内使用,否则报:下标越界异常,比如:
    int [] arr=new int[5]; 则有效下标为 0-4
  7. 数组属引用类型,数组型数据是对象(object)
public class ArrayDetail {
	public static void main(String[] args) {
		//1. 数组是多个相同类型数据的组合,实现对这些数据的统一管理
		//int[] arr1 = {1, 2, 3, 60,"hello"};//String ->int
		double[] arr2 = {1.1, 2.2, 3.3, 60.6, 100};//int ->double
		//2. 数组中的元素可以是任何数据类型,包括基本类型和引用类型,但是不能混用
		String[] arr3 = {"北京","jack","milan"};
		//3. 数组创建后,如果没有赋值,有默认值
		//int 0,short 0, byte 0, long 0,
		//float 0.0,double 0.0,char \u0000,
		//boolean false,String null
		
		short[] arr4 = new short[3];
		System.out.println("=====数组 arr4=====");
		for(int i = 0; i < arr4.length; i++) {
		System.out.println(arr4[i]);
		}
		//6. 数组下标必须在指定范围内使用,否则报:下标越界异常,比如
		//int [] arr=new int[5]; 则有效下标为 0-4
		//即数组的下标/索引 最小 0 最大 数组长度-1(4)
		int [] arr = new int[5];
		//System.out.println(arr[5]);//数组越界
	}
}
import java.util.Scanner;
//一维数组
public class Array1 {
    public static void main(String[] args) {
        //使用方式1:静态初始化
        double[] arr = {12, 23, 34, 15.5, 66.66};
        for (int i = 0; i < arr.length; i++) {
            System.out.println("第" + (i + 1) + "个元素的值为:" + arr[i]);
        }

        //使用方式2:动态初始化
        int[] arr1 = new int[6];
        Scanner myScanner = new Scanner(System.in);
        for (int i = 0; i < arr1.length; i++) {
            System.out.println("请输入数组的"+(i+1)+"元素的值:");
            arr1[i] = myScanner.nextInt();
        }
        System.out.println("输入完毕:"+arr1);
        for (int i = 0; i < arr1.length; i++) {
            System.out.println("第" + (i + 1) + "个元素的值为:" + arr1[i]);
        }
    }
}

4、数组应用案例

(1)创建一个 char 类型的 26 个元素的数组,分别 放置’A’-‘Z’。使用 for 循环访问所有元素并打印出来。提示:char 类型数据运算 ‘A’+2 -> ‘C’

public class ArrayExercise01 {
	public static void main(String[] args) {
		/*
		思路分析
		1. 定义一个 数组 char[] chars = new char[26]
		2. 因为 'A' + 1 = 'B' 类推,所以老师使用 for 来赋值
		3. 使用 for 循环访问所有元素
		*/
		char[] chars = new char[26];
		for( int i = 0; i < chars.length; i++) {//循环 26 次
			//chars 是 char[]
			//chars[i] 是 char
			chars[i] = (char)('A' + i); //'A' + i 是 int , 需要强制转换
		}
		//循环输出
		System.out.println("===chars 数组===");
		for( int i = 0; i < chars.length; i++) {//循环 26 次
			System.out.print(chars[i] + " ");
		}
	}
}

(2) 请求出一个数组 int[]的最大值 {4,-1,9, 10,23},并得到对应的下标。

public class ArrayExercise02 {
	public static void main(String[] args) {
	
		//思路分析
		//1. 定义一个 int 数组 int[] arr = {4,-1,9, 10,23};
		//2. 假定 max = arr[0] 是最大值 , maxIndex=0;
		//3. 从下标 1 开始遍历 arr, 如果 max < 当前元素,说明 max 不是真正的
		// 最大值, 我们就 max=当前元素; maxIndex=当前元素下标
		//4. 当我们遍历这个数组 arr 后 , max 就是真正的最大值,maxIndex 最大值
		// 对应的下标
		int[] arr = {4,-1,9,10,23};
		int max = arr[0];//假定第一个元素就是最大值
		int maxIndex = 0; //
		for(int i = 1; i < arr.length; i++) {//从下标 1 开始遍历 arr
			if(max < arr[i]) {//如果 max < 当前元素
				max = arr[i]; //把 max 设置成 当前元素
				maxIndex = i;
			}
		}
		//当我们遍历这个数组 arr 后 , max 就是真正的最大值,maxIndex 最大值下标
		System.out.println("max=" + max + " maxIndex=" + maxIndex);
	}
}

5、数组赋值机制

  1. 基本数据类型赋值,这个值就是具体的数据,而且相互不影响。
    int n1 = 2; int n2 = n1;
  2. 数组在默认情况下是引用传递,赋的值是地址。
    看一个案例,并分析数组赋值的内存图(重点, 难点)。
    int[] arr1 = {1,2,3};
    int[] arr2 = arr1;
    在这里插入图片描述

6、数组拷贝

public class ArrayCopy {
	
	public static void main(String[] args) {
		//案例:将 int[] arr1 = {10,20,30}; 拷贝到 arr2 数组,
		//要求数据空间是独立的.
		int[] arr1 = {10,20,30};
		//创建一个新的数组 arr2,开辟新的数据空间
		//大小 arr1.length;
		int[] arr2 = new int[arr1.length];
		//遍历 arr1 ,把每个元素拷贝到 arr2 对应的元素位置
		for(int i = 0; i < arr1.length; i++) {
			arr2[i] = arr1[i];
		}
		//修改 arr2, 不会对 arr1 有影响.
		arr2[0] = 100;
		//输出 arr1
		System.out.println("====arr1 的元素====");
		for(int i = 0; i < arr1.length; i++) {
			System.out.println(arr1[i]);//10,20,30
		}
		System.out.println("====arr2 的元素====");
		for(int i = 0; i < arr2.length; i++) {
			System.out.println(arr2[i]);//
		}
	}
}

7、数组反转

要求:把数组的元素内容反转。
arr {11,22,33,44,55,66} {66, 55,44,33,22,11}

方式 1:通过找规律反转

public class ArrayReverse {
	public static void main(String[] args) {
		//定义数组
		int[] arr = {11, 22, 33, 44, 55, 66};
		//规律
		//1. 把 arr[0] 和 arr[5] 进行交换 {66,22,33,44,55,11}
		//2. 把 arr[1] 和 arr[4] 进行交换 {66,55,33,44,22,11}
		//3. 把 arr[2] 和 arr[3] 进行交换 {66,55,44,33,22,11}
		//4. 一共要交换 3 次 = arr.length / 2
		//5. 每次交换时,对应的下标 是 arr[i] 和 arr[arr.length - 1 -i]
		
		int temp = 0;
		int len = arr.length; //计算数组的长度
		for( int i = 0; i < len / 2; i++) {
			temp = arr[len - 1 - i];//保存
			arr[len - 1 - i] = arr[i];
			arr[i] = temp;
		}
		System.out.println("===翻转后数组===");
		for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + "\t");//66,55,44,33,22,11
		}
	}
}

方式 2:使用逆序赋值方式

public class ArrayReverse02 {
	public static void main(String[] args) {
		//定义数组
		int[] arr = {11, 22, 33, 44, 55, 66};
		//使用逆序赋值方式
		//思路
		//1. 先创建一个新的数组 arr2 ,大小 arr.length
		//2. 逆序遍历 arr ,将 每个元素拷贝到 arr2 的元素中(顺序拷贝)
		//3. 建议增加一个循环变量 j -> 0 -> 5
		int[] arr2 = new int[arr.length];
		//逆序遍历 arr
		for(int i = arr.length - 1, j = 0; i >= 0; i--, j++) {
			arr2[j] = arr[i];
		}
		//4. 当 for 循环结束,arr2 就是一个逆序的数组 {66, 55, 44,33, 22, 11}
		//5. 让 arr 指向 arr2 数据空间, 此时 arr 原来的数据空间就没有变量引用
		// 会被当做垃圾,销毁
		arr = arr2;
		System.out.println("====arr 的元素情况=====");
		//6. 输出 arr 看看
		for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + "\t");
		}
	}
}

8、数组添加/扩容

import java.util.Scanner;

public class ArrayAdd02 {
	public static void main(String[] args) {
	/*
	要求:实现动态的给数组添加元素效果,实现对数组扩容。ArrayAdd.java
	1.原始数组使用静态分配 int[] arr = {1,2,3}
	2.增加的元素 4,直接放在数组的最后 arr = {1,2,3,4}
	3.用户可以通过如下方法来决定是否继续添加,添加成功,是否继续?y/n
	思路分析
	1. 定义初始数组 int[] arr = {1,2,3}//下标 0-2
	2. 定义一个新的数组 int[] arrNew = new int[arr.length+1];
	3. 遍历 arr 数组,依次将 arr 的元素拷贝到 arrNew 数组
	4. 将 4 赋给 arrNew[arrNew.length - 1] = 4;把 4 赋给 arrNew 最后一个元素
	5. 让 arr 指向 arrNew ; arr = arrNew; 那么 原来 arr 数组就被销毁
	6. 创建一个 Scanner 可以接受用户输入
	7. 因为用户什么时候退出,不确定,老师使用 do-while + break 来控制
	*/
	Scanner myScanner = new Scanner(System.in);
	//初始化数组
	int[] arr = {1,2,3};
	do {
		int[] arrNew = new int[arr.length + 1];
		//遍历 arr 数组,依次将 arr 的元素拷贝到 arrNew 数组
		for(int i = 0; i < arr.length; i++) {
			arrNew[i] = arr[i];
		}
		System.out.println("请输入你要添加的元素");
		int addNum = myScanner.nextInt();
		//把 addNum 赋给 arrNew 最后一个元素
		arrNew[arrNew.length - 1] = addNum;
		//让 arr 指向 arrNew,
		arr = arrNew;
		//输出 arr 看看效果
		System.out.println("====arr 扩容后元素情况====");
			for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + "\t");
		}
		//问用户是否继续
		System.out.println("是否继续添加 y/n");
		char key = myScanner.next().charAt(0);
			if( key == 'n') { //如果输入 n ,就结束
			break;
		}
	}while(true);
		myScanner.close();
		System.out.println("你退出了添加...");
	}
}

9、排序的介绍

排序是将多个数据,依指定的顺序进行排列的过程。
排序的分类:
内部排序:
指将需要处理的所有数据都加载到内部存储器中进行排序。包括(交换式排序法、选择式排序法和插入式排序法);

外部排序法:
数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。包括(合并排序法和直接合并排序法)。

10、 冒泡排序法

冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从后向前(从下标较大的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
在这里插入图片描述

public class BubbleSort {
	public static void main(String[] args) {
		/**
		数组 [24,69,80,57,13]
		第 1 轮排序: 目标把最大数放在最后
		第 1 次比较[24,69,80,57,13]
		第 2 次比较[24,69,80,57,13]
		第 3 次比较[24,69,57,80,13]
		第 4 次比较[24,69,57,13,80]
		*/
		int[] arr = {24, 69, 80, 57, 13, -1, 30, 200, -110};
		int temp = 0; //用于辅助交换的变量
		//将多轮排序使用外层循环包括起来即可
		//先死后活 => 4 就是 arr.length - 1
		for( int i = 0; i < arr.length - 1; i++) {//外层循环是 4 次
			for( int j = 0; j < arr.length - 1 - i; j++) {//4 次比较-3 次-2 次-1 次
				//如果前面的数>后面的数,就交换
				if(arr[j] > arr[j + 1]) {
					temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;
				}
			}
			System.out.println("\n==第"+(i+1)+"轮==");
			for(int j = 0; j < arr.length; j++) {
				System.out.print(arr[j] + "\t");
			}
		}
	}
}

11、 查找

11.1 介绍:
在 java 中,我们常用的查找有两种:

  1. 顺序查找
  2. 二分查找【二分法,我们放在算法讲解】

11.2 案例演示:

import java.util.Scanner;
public class SeqSearch {
	public static void main(String[] args) {
		/*
		案例:有一个数列:白眉鹰王、金毛狮王、紫衫龙王、青翼蝠王猜数游戏:
		从键盘中任意输入一个名称,判断数列中是否包含此名称【顺序查找】
		要求: 如果找到了,就提示找到,并给出下标值
		思路分析
		1. 定义一个字符串数组
		2. 接收用户输入, 遍历数组,逐一比较,如果有,则提示信息,并退出
		*/
		//定义一个字符串数组
		String[] names = {"白眉鹰王", "金毛狮王", "紫衫龙王", "青翼蝠王"};
		Scanner myScanner = new Scanner(System.in);
		System.out.println("请输入名字");
		String findName = myScanner.next();
		//遍历数组,逐一比较,如果有,则提示信息,并退出
		int index = -1;
		for(int i = 0; i < names.length; i++) {
			//比较 字符串比较 equals, 如果要找到名字就是当前元素
			if(findName.equals(names[i])) {
				System.out.println("恭喜你找到 " + findName);
				System.out.println("下标为= " + i);
				//把 i 保存到 index
				index = i;
				break;//退出
			}
		}
		if(index == -1) { //没有找到
		System.out.println("sorry ,没有找到 " + findName);
		}
	}
}

12、 二维数组

连接

第5章 面向对象(初级)

1、认识对象和类

●概念关系

1)类是抽象的,概念的,代表一类事物,比如人类,猫类…, 即它是数据类型.
2)对象是具体的,实际的,代表一个具体事物, 即是实例.
3)类是对象的模板,对象是类的一个个体,对应一个实例

●对象的创建

1)先声明再创建
Cat cat ;//声明
cat = new Cat();//创建
2)直接创建
Cat cat = new Cat();

● Java对象内存的结构分析

1)栈:一般存放基本数据类型(局部变量)
2)堆:存放对象(Person person,数组等)
3)方法区:常量池(常量,比如字符串),类加载信息

对象在内存中存在形式:
在这里插入图片描述

2、属性(成员变量/字段)

●基本介绍
从概念或叫法上看:成员变量=属性=field(字段) (即成员变量是用来表示属性的)

  1. 属性的定义语法同变量,示例:访问修饰符 属性类型 属性名;
    有四种访问修饰符 public, proctected, 默认, private 。
  2. 属性的定义类型可以为任意类型,包含基本类型或引用类型
  3. 属性如果不赋值,有默认值,规则和数组一致。具体说: int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000,
    boolean false,String null

3、方法(成员方法)

格式:
访问修饰符  返回数据类型  方法名(形参列表..){  //方法体
    语句;
    return返回值;
}

●解读:
1)形参列表:表示成员方法输入
2)返回数据类型:表示成员方法输出, void表示没有返回值
3)方法主体:表示为了实现某一功能代码块
4) return语句不是必须的。

调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数

方法的调用执行机制:
在这里插入图片描述

4、成员方法传参机制和方法递归调用

待续。。。

5、方法重载(OverLoad)

●基本介绍
java中允许同一个类中,多个同名方法的存在,但要求形参列表不一致!比如: System.out.println(” );out是PrintStream类型
●重载的好处
1)减轻了起名的麻烦
2)减轻了记名的麻烦

●注意事项和使用细节

1)方法名:必须相同
2)形参列表:必须不同(形参类型或个数或顺序,至少有一样不同,参数名无要求)
3)返回类型:无要求
public class MyOverLoad1 {
    public static void main(String[] args) {
        MyCalculator mc = new MyCalculator();
        System.out.println(mc.calculate(1, 2));
        System.out.println(mc.calculate(1.1, 2));
        System.out.println(mc.calculate(1, 2.1));
    }

}

class MyCalculator {
    //下面的四个 calculate 方法构成了重载
    //两个整数的和
    public int calculate(int n1, int n2) {
        System.out.println("calculate(int n1, int n2) 被调用");
        return n1 + n2;
    }
    //错误
    //public int calculate(int a1, int a2) {
    //    System.out.println("calculate(int a1, int a2) 被调用");
    //    return a1 + a2;
    // }

    //一个整数,一个 double的和
    public double calculate(int n1, double n2) {
        return n1 + n2;
    }

    //一个 double ,一个 Int的和
    public double calculate(double n1, int n2) {
        System.out.println("calculate(double n1, int n2) 被调用..");
        return n1 + n2;
    }

    //三个 int 的和
    public int calculate(int n1, int n2, int n3) {
        return n1 + n2 + n3;
    }

}

6、可变参数

●基本概念
java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。就可以通过可变参数实现
●基本语法
访问修饰符 返回类型 方法名(数据类型…形参名){}

1)可变参数的实参可以为0个或任意多个
2)可变参数的实参可以为数组
3)可变参数的本质就是数组
4)可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
5)一个形参列表中只能出现一个可变参数
//可变参数
public class VarParamete {
    public static void main(String[] args) {
        ChangeParamete c  =new ChangeParamete();
       int total =  c.sum(12,13,14,15);
       System.out.println(total);
    }
}
class ChangeParamete{
    public int sum (int ...nums){
        System.out.println("方法参数个数为:"+nums.length);

        int res = 0;
        for (int i = 0; i < nums.length; i++) {
            res+=nums[i];
        }
        return res;
    }
}

7、作用域

1.在java编程中,主要的变量就是属性(成员变量)和局部变量
2.的局部变量一般是指在成员方法中定义的变量
3.java中作用域的分类:
全局变量:也就是属性,作用域为整个类体Cat类:cry eat等方法使用属性
局部变量:也就是除了属性之外的其他变量,作用域为定义它的代码块中!
4.全局变量(属性)可以不赋值,直接使用,因为有默认值,局部变量必须赋值后,才能使用,因为没有默认值。

●注意事项和细节使用

1.属性和局部变量可以重名,访问时遵循就近原则。
2.在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名。
3.属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。
局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁。
即在一次方法调用过程中。
4.作用域范围不同
全局变量/属性:可以被本类使用,或其他类使用(通辽对家调用)局部变量:只能在本类中对应的方法中使用
5.修饰符不同
全局变量/属性可以加修饰符局部变量不可以加修饰符

构造方法/构造器

●基本语法:

[修饰符]方法名(形参列表){
    方法体;
}

说明:

1)构造器的修饰符可以默认,也可以是 public protected private。
2)构造器没有返回值
3)方法名和类名字必须一样
4)参数列表和成员方法一样的规则
5)构造器的调用,由系统完成

●基本介绍

构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。它有几个特点:
1)方法名和类名相同
2)没有返回值
3)在创建对象时,系统会自动的调用该类的构造器完成对象的初始化。

构造器使用注意细节:
在这里插入图片描述

this

//this
public class This1 {
    public static void main(String[] args) {
        Dog dog1 = new Dog("大壮", 3);
        System.out.println("dog1的hashcode=" + dog1.hashCode());
        //dog1 调用了 info()方法
        dog1.info();
        System.out.println("============");
        Dog dog2 = new Dog("大黄", 2);
        System.out.println("dog2的hashcode=" + dog2.hashCode());
        dog2.info();
    }
}

class Dog { //类
    String name;
    int age;

    //public Dog(String dName, int dAge) {//构造器
    //    name = dName;
    //    age = dAge;
    // }
    //如果我们构造器的形参,能够直接写成属性名,就更好了
    //但是出现了一个问题,根据变量的作用域原则
    //构造器的 name 是局部变量,而不是属性
    //构造器的 age 是局部变量,而不是属性

    //==> 引出 this 关键字来解决
    public Dog(String name, int age) {//构造器
        //this.name 就是当前对象的属性 name
        this.name = name;
        //this.age 就是当前对象的属性 age
        this.age = age;
        System.out.println("this.hashCode=" + this.hashCode());
    }

    public void info() {//成员方法,输出属性 x 信息
        System.out.println("this.hashCode=" + this.hashCode());
        System.out.println(name + "\t" + age + "\t");
    }

}

1) this 关键字可以用来访问本类的属性、方法、构造器
2) this 用于区分当前类的属性和局部变量
3) 访问成员方法的语法:this.方法名(参数列表);
4) 访问构造器语法:this(参数列表); 
   注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一条语句)
5) this 不能在类定义的外部使用,只能在类定义的方法中使用

在这里插入图片描述

第5章 面向对象(中级)

1、IDEA(集成开发环境安装)

待续...

2、包

●包的三大作用
1.区分相同名字的类
2.当类很多时,可以很好的管理类[看Java API文档]
3.控制访问范围

●包基本语法

package com.code;说明:
1. package关键字 表示打包
2. com.code:表示包名

●命名规则:
只能包含数字、字母、下划线、小圆点…但不能用数字开头,不能是关键字或保留字

●常用的包
一个包下,包含很多的类,java 中常用的包有:

1) java.lang.* //lang 包是基本包,默认引入,不需要再引入.
2) java.util.* //util 包,系统提供的工具包, 工具类,使用 Scanner
3) java.net.* //网络包,网络开发
4) java.awt.* //是做 java 的界面开发,GUI
com.code.pKg: import011.java 
//语法:import 包;
//我们引入一个包的主要目的是要使用该包下的类
//比如import java.util.Scanner;就只是引入一个类Scanner。
import java.util.*;//表示将java.util包所有都引入
package hsp.base;
//package 的作用是声明当前类所在的包,需要放在类(或者文件)的最上面
//一个类中最多只有一句package

import java.util.Arrays;//Arrays包
import java.util.Scanner;//Scanner包

public class Pkg1 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        double[] arr={12.3,22,33,44,5,-20};
       Arrays.sort(arr);//Arrays一个排序方法
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

1. package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package
2. import指令位置放在package的下面,在类定义前面,可以有多句且没有顺序要求。
`

3、访问修饰符

3.1基本介绍
java提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围)

1)公开级别:用public修饰,对外公开
2)受保护级别:用protected修饰,对子类和同一个包中的类公开
3)默认级别:没有修饰符号,向同一个包的类公开
4)私有级别:用private修饰,只有类本身可以访问,不对外公开
访问级别访问控制修饰符同类同包子类不同包
公开public
受保护protected
默认无修饰符
私有private
package hsp.pack1;

//A.java
public class A {
    public int n1 = 100;
    protected String n2 = "code";
    double n3 = 30.5;
    private boolean n4 = false;

    public void m1() { //在同一类中,可以访问 public protected 默认 private 修饰属性和方法
        System.out.println("n1=" + n1 + " n2=" + n2 + " n3=" + n3 + " n4=" + n4);
    }

    protected void m2() {
    }

    void m3() {
    }

    private void m4() {
    }

    public void hi() {
        //在同一类中,可以访问 public protected 默认 private 修饰属性和方法
        m1();
        m2();
        m3();
        m4();
    }

}
//
package hsp.pack1;

//B.java
public class B {

    public void say() {
        A a = new A();
        //在同一个包下,可以访问 public , protected 和 默认修饰属性或方法,不能访问 private 属性或方法
        System.out.println("n1=" + a.n1 + " n2=" + a.n2 + " n3=" + a.n3 + " n4=XXX");

        a.m1();
        a.m2();
        a.m3();
        //a.m4(); 错误的
    }

}
//
package hsp.pack1;

//TestMain .java
public class TestMain {
    public static void main(String[] args) {
        A a = new A();
        a.m1();
        B b = new B();
        b.say();
    }
}
class Person{}

●使用的注意事项:

1)修饰符可以用来修饰类中的属性,成员方法以及类
2)只有默认的和public才能修饰类!,并且遵循上述访问权限的特点。
3)成员方法的访问规则和属性完全一样.

4、面向对象编程三大特征

封装、继承和多态

4.1 封装

封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作[方法],才能对数据进行操作。

封装的实现步骤 (三步)

1)将属性进行私有化private【不能直接修改属性】
2)提供一个公共的(public)set方法,用于对属性判断并赋值
     public void setXxx (类型参数名) {//Xxx表示某个属性
     //加入数据验证的业务逻辑
     属性=参数名;
     }
3)提供一个公共的(public)get方法,用于获取属性的值
	public 数据类型  getXxxO{ //权限判断,Xxx某个属性
		return XX;
}
package hsp.base;

public class Encapsulation1 {

    public static void main(String[] args) {
        Person person = new Person("codeSE", "1234", 1000);
        person.getPersonalInfo();

        person.setName("Green");
        person.setPws("123456");
        person.setSalary(20000);
        person.getPersonalInfo();

    }
}

class Person {
    public String name;//公开
    private String pws;//私有
    private double salary;//私有

    public Person() {
    }

    public Person(String name, String pws, double salary) {
        //在构造器可以调用方法,做限制
        setName(name);
        setPws(pws);
        setSalary(salary);
    }

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

    public void setPws(String pws) {
        if (pws.length() == 6) {
            this.pws = pws;
            System.out.println("密码正确");
        } else {
            System.out.println("密码错误:必须六位数,默认111111");
            this.pws = "111111";
        }

    }

    public void setSalary(double salary) {
        if (salary >= 10000) {
            this.salary = salary;
            System.out.println("工资过万,直接设置");

        } else {
            System.out.println("工资必须过万,默认10000元");
            this.salary = 10000;
        }

    }

    public String getName() {
        return name;
    }

    public String getPws() {
        return pws;
    }

    public double getSalary() {
        return salary;
    }

    public void getPersonalInfo() {
        System.out.println("获取信息:" + getName() + "\t" + getPws() + "\t" + getSalary());
    }
}

4.2 继承

4.2.1 概念、语法

继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中
抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends 来声明继承父类即可。
在这里插入图片描述
● 继承的基本语法

 子类  extends  父类{

 }
 
1)子类就会自动拥有父类定义的属性和方法
2)父类又叫超类,基类。
3)子类又叫派生类。
public class Personal {
    String name;
    int age;
    double height;

    //公共方法
    public void setAge(int age){
        this.age = age;
    }
    public String showInfo(){
        return "姓名:"+name+"年龄:"+age+"身高:"+this.height;
    }

}
//
public class Xiaoming extends Personal{
    public void test(){
        System.out.println(name+"输出了!");
    }
}

//
public class Xiaohua extends Personal{
    public void test(){
        System.out.println(name+"输出了!");
    }
}
//
public class TestMain {
    public static void main(String[] args) {
        Xiaoming xiaoming = new Xiaoming();
        xiaoming.name = "小明";
        xiaoming.height = 175;
        xiaoming.setAge(20);
        String info1= xiaoming.showInfo();
        System.out.println(info1);
        xiaoming.test();
        System.out.println("========================================================================");

        Xiaohua xiaohua = new Xiaohua();
        xiaohua.name="小花";
        xiaoming.height = 170;
        xiaohua.setAge(18);
        String info2 = xiaohua.showInfo();
        System.out.println(info2);
        xiaohua.test();
    }
}


4.2.2 继承在构造函数中的使用

public class Father {
    public int n1= 10;
    protected double n2 = 23.2;
    boolean n3 = false;
    private String n4 = "code";

    //public Father(){
    //   System.out.println("父类Father()无参构造器被调用....");
    //}

    public Father(int n1, String n4) {
        this.n1 = n1;
        this.n4 = n4;
        System.out.println("父类Father(int n1, String n4)有参构造器被调用....");
    }
}
//
public class Son extends Father{
    int s1 = 200;
    public Son(){
        //super();
        //1、如果什么都不写,默认调用就是super(),即:调用父类无参构造器
        //2、写了super(参数),才是指定了父类的构造器
        super(1000,"VB");//指定父类构造器
        System.out.println("子类Son()无参构造器被调用....");
    }
    public Son(int s1){
        super(2000,"CB");//指定父类构造器
        this.s1 = s1;
        System.out.println("子类Son(int s1)有参构造器被调用....");

    }
   //主函数
    public static void main(String[] args) {
        Son son1 = new Son();//默认都会调父类Father()无参构造器  //但是父类没有无参构造器,必须用super来指定
        Son son2 = new Son(100);//默认都会调父类Father()无参构造器 //但是父类没有无参构造器,必须用super来指定

        /*
         * 父类Father()无参构造器被调用....
         * 子类Son()无参构造器被调用....
         *  父类Father()无参构造器被调用....
         *  子类Son(int s1)有参构造器被调用....
         * */

        /*
        * 父类Father(int n1, String n4)有参构造器被调用....
        * 子类Son()无参构造器被调用....
        *  父类Father(int n1, String n4)有参构造器被调用....
        *  子类Son(int s1)有参构造器被调用....
        * */

    }
}
1) 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 
	但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
2) 子类必须调用父类的构造器, 完成父类的初始化
3) 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,
	如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,
	否则,编译不会通过。
4) 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
5) super 在使用时,必须放在构造器第一行 (super 只能在构造器中使用)
6) super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
7) java 所有类都是 Object 类的子类, Object 是所有类的基类. 
8) 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
9) 子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制
10) 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系

4.2.3 继承关系本质理解

在这里插入图片描述

public class ExtendTheory {
    public static void main(String[] args) {
        Son1 son = new Son1();//内存的布局
        
        // (1)首先看子类是否有该属性
        // (2)如果子类有这个属性,并且可以访问,则返回信息
        // (3)如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息..)
        // (4)如果父类没有就按照(3)的规则,继续找上级父类,直到Object...
        System.out.println(son.name);//返回就是大头儿子
        System.out.println(son.age);//返回的就是39
        System.out.println(son.hobby);//返回的就是旅游
        
		//注意:如果向上访问遇到私有的属性(方法),即使还有也会终止查找	
    }
}
class GrandPa1 {//爷类
    String e = "大头爷爷";
    String hobby = "旅游";
}

class Father1 extends GrandPa1 {//父类
    String name = "大头爸爸";
    int age = 39;
}

class Son1 extends Father1 {//子类
    String name = "大头儿子";
}

4.2.4 super

super 代表父类的引用,用于访问父类的属性、方法、构造器

1.访问父类的属性,但不能访问父类的private属性
	super.属性名;
2.访问父类的方法,不能访问父类的private方法
	super.方法名(参数列表);
3.访问父类的构造器(这点前面用过)
	super(参数列表);只能放在构造器的第一句,只能出现一句!
package hsp.extend.supers;

public class Base {
    public int n1 = 999;
    public int age = 111;

    public void cal() {
        System.out.println("Base 类的 cal() 方法...");
    }

    public void eat() {
        System.out.println("Base 类的 eat().....");
    }
}
//
package hsp.extend.supers;

public class A extends Base {
    //4 个属性
    public int n1 = 100;
    protected int n2 = 200;
    int n3 = 300;
    private int n4 = 400;

    public A() {
    }

    public A(String name) {
    }

    public A(String name, int age) {
    }

    public void cal() {
        System.out.println("A 类的 cal() 方法...");
    }

    public void test100() {

    }

    protected void test200() {
    }

    void test300() {
    }

    private void test400() {
    }
}


//
package hsp.extend.supers;

public class B extends A {

    public int n1 = 888;

    public void test() {
        //super 的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用 super 去访问爷爷类的成员;
        // 如果多个基类(上级类)中都有同名的成员,使用 super 访问遵循就近原则。A->B->C
        System.out.println("super.n1=" + super.n1);
        super.cal();
    }

    //访问父类的属性 , 但不能访问父类的 private 属性 [案例]super.属性名
    public void hi() {
        System.out.println(super.n1 + " " + super.n2 + " " + super.n3);
    }

    public void cal() {
        System.out.println("B 类的 cal() 方法...");
    }

    public void sum() {
        System.out.println("B 类的 sum()");
        //希望调用父类-A 的 cal 方法,这时,因为子类 B 没有 cal 方法,因此我可以使用下面三种方式
        // 找 cal 方法时(cal() 和 this.cal()),顺序是:
        // (1)先找本类,如果有,则调用
        // (2)如果没有,则找父类(如果有,并可以调用,则调用)
        // (3)如果父类没有,则继续找父类的父类,整个规则,就是一样的,直到 Object 类
        // 提示:如果查找方法的过程中,找到了,但是不能访问, 则报错, cannot access
        // 如果查找方法的过程中,没有找到,则提示方法不存在
        // cal();
        this.cal(); //等价 cal
        // 找 cal 方法(super.call()) 的顺序是直接查找父类,其他的规则一样
        super.cal();//跳过本类,

        //n1 和 this.n1 查找的规则是
        //(1) 先找本类,如果有,则调用
        // (2) 如果没有,则找父类(如果有,并可以调用,则调用)
        // (3) 如果父类没有,则继续找父类的父类,整个规则,就是一样的,直到 Object 类
        // 提示:如果查找属性的过程中,找到了,但是不能访问, 则报错, cannot access
        // 如果查找属性的过程中,没有找到,则提示属性不存在
        System.out.println(n1);
        System.out.println(this.n1); //找 n1 (super.n1) 的顺序是直接查找父类属性,其他的规则一样
        System.out.println(super.n1);
    }

    //访问父类的方法,不能访问父类的 private 方法: super.方法名(参数列表);
    void ok() {
        super.test100();
        super.test200();
        super.test300();
        //super.test400();//不能访问父类 private 方法
    }

    //访问父类的构造器:super(参数列表);只能放在构造器的第一句,只能出现一句!
    public B() {
        //super();
        super("jack", 10);
        //super("jack");
    }
}
//
package hsp.extend.supers;

public class TestMain1 {

    public static void main(String[] args) {
        B b = new B();//子类对象
        b.sum();
        b.test();
    }

}

super 给编程带来的优点/细节

1、调用父类的构造器的好处(分工明确,父类属性由父类初始化,子类的属性由子类初始化)
2.当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。
  如果没有重名,使用super、this、直接访问是一样的效果!
3. super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,
   也可以使用super去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,
   使用super访问遵循就近原则。A->B->C,当然也需要遵守访问权限的相关规则

super 和 this 的比较
在这里插入图片描述

4.2.5 方法重写/覆盖(override)

简单的说:方法覆盖(重写)就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法

package hsp.extend.overirde;

public class Animal {

    public void running(){
        System.out.println("动物在跑");
    }

    public Object eat(){
        return null;
    }
    public String sleep(){
        return "睡觉";
    }

    public void swimming(){

    }
}
//
package hsp.extend.overirde;

public class Dog extends Animal {

    String name = "dog";

    public void running() {//重写running方法
        System.out.println(this.name + "在跑");
    }

    public String eat() {//重写eat方法
        return null;
    }
    //编译报错,返回类型大于父类
//    public Object sleep(){
//        return "睡觉";
//    }

    //编译报错:缩小了父类的方法权限范围
//    protected void swimming(){
//
//    }

}
//
package hsp.extend.overirde;

public class TestMain {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.running();
    }
}

注意事项和使用细节
方法重写也叫方法覆盖,需要满足下面件:

1.子类的方法的参数方法名称,要和父类方法的参数方法名称【完全一样】。
2.子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子比如父类返回类型是 Object ,子类方法返回类型是String
public object getInfo(){}   public string getInfo(){}   //ok可以
3.子类方法不能缩小父类方法的访问权限:public > protected >默认>private
void sayOk(){ }    public void sayok(){ } //err 不行

在这里插入图片描述

4.3 多态

4.3.1 方法或对象多种形态

方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
先来一段代码预热:

package hsp.ploy;

public class AB {
    public static void main(String[] args) {
        A a = new A();
        B b = new B();

        //方法重载体现多态
        b.sum(11,22);
        b.sum(11,22,33);

        //方法重写体现多态
        a.say();
        b.say();

    }
}

class A {
    public void say() {
        System.out.println("父类方法");
    }
}

class B extends A {
    public void say() {
        System.out.println("子类方法");

    }

    public int sum(int a, int b) {
        return a + b;
    }

    public int sum(int a, int b, int c) {
        return a + b + c;
    }
}

4.3.2 对象的多态 (核心,难点,重点)

(1)一个对象的编译类型和运行类型可以不一致
(2)编译类型在定义对象时,就确定了,不能改变
(3)运行类型是可以变化的.
(4)编译类型看定义时=号的左边,运行类型看=号的右边
Animal animal = new Dog; 【animal编译类型是Animal,运行类型Dog】
animal = new Cat();【animal的运行类型变成了Cat,编译类型仍然是 Animal】

●●什么是多态,多态具体体现有哪些?●●
多态:方法或对象具有多种形态,是OOP的第三大特征,是建立在封装和继承基础之上
●●多态具体体现:●●

1.方法多态
(1)重载体现多态
(2)重写体现多态
2.对象多态
(1)对象的编译类型和运行类型可以不一致,编译类型在定义时,就确定,不能变化
(2)对象的运行类型是可以变化的,可以通过getClasss()来查看运行类型
(3)编译类型看定时时=号的左边,运行类型看=号右边

package hsp.ploy;
public class Animal {
    public void running(){
        System.out.println("动物在跑");
    }

}
//
public class Dog extends Animal {

    public void running() {
        System.out.println("狗在吠!");

    }
}
//
package hsp.ploy;
public class Cat extends Animal{
    public void running() {
        System.out.println("猫在叫!");
    }
}
//
package hsp.ploy;
public class TestMain {
    public static void main(String[] args) {

        //体验对象多态特点
        // animal 编译类型就是 Animal , 运行类型 Dog
        Animal animal = new Dog(); //因为运行时,执行到该行时,animal 运行类型是 Dog,所以 running就是 Dog 的 running
        animal.running(); //狗在吠!

        // animal 编译类型 Animal,运行类型就是 Cat
        animal = new Cat();
        animal.running(); //猫在叫
    }
}

4.3.3 多态注意事项和细节

✔️●多态的前提是: 两个对象(类)存在继承关系

√多态的向上转型

1)本质:父类的引用指向了子类的对象
2)语法:父类类型	引用名= new子类类型();
3)特点:编译类型看左边,运行类型看右边。可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成员;最终运行效果看子类的具体实现!

√多态向下转型

1)语法:子类类型	引用名 = (子类类型) 	父类引用;
2)只能强转父类的引用,不能强转父类的对象
3)要求父类的引用必须指向的是当前目标类型的对象
4)当向下转型后,可以调用子类类型中所有的成员
package hsp.ploy1;

public class Animal {
    String name = "动物";
    int age = 10;
    public void running(){
        System.out.println("动物在跑");
    }
    public void eating(){
        System.out.println("动物在吃");
    }

}

//
package hsp.ploy1;

public class Cat extends Animal {
    String name = "猫";

    public void eating() {//方法重写
        System.out.println("猫吃鱼");
    }

    public void catchMouse() {//Cat 特有方法
        System.out.println("猫抓老鼠");
    }
}

//
package hsp.ploy1;

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

        //向上转型:父类的引用指向了子类的对象
        //语法:父类类型引用名=new子类类型();
        Animal animal = new Cat();
        Object obj = new Cat();//可以吗?可以Object也是Cat的父类

        //向上转型调用方法的规则如下:
        //(1)可以调用父类中的所有成员(需遵守访问权限)
        //(2)但是不能调用子类的特有的成员
        //(3)因为在编译阶段,能调用哪些成员,是由编译类型来决定的
        //(4)最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法,然后调用。

        // animal.catchMouse();错误
        animal.eating();//猫吃鱼
        animal.running();//动物在跑!


        //多态的向下转型
        //(1)语法:子类类型引用名=(子类类型)父类引用;
        Cat cat = (Cat) animal;//cat的编译类型Cat,运行类型是Cat
        cat.catchMouse();//猫抓老鼠
        //(2)要求父类的引用必须指向的是当前目标类型的对象
        //Dog dog = (Dog) animal;//不行,相当于猫强制转为狗,两者没有关系


        //属性没有重写之说!属性的值看编译类型
        Animal a1 = new Cat();//向上转型
        System.out.println(a1.name);//动物
        Cat c1 = new Cat();
        System.out.println(c1.name);//猫

        //instanceOf比较操作符,用于判断对象的运行类型是否为XX类型或XX类型的子类型

        Cat c2 = new Cat();
        System.out.println(c2 instanceof Cat);//true
        System.out.println(c2 instanceof Animal);//true

        //aa编译类型Animal,运行类型是Cat
        // Cat是Animal子类

        Animal aa = new Cat();
        System.out.println(aa instanceof Animal);//true
        System.out.println(aa instanceof Cat);//true

        Object obj1 = new Object();
        System.out.println(obj1 instanceof Animal);//false
        String str = "hello";
        //System.out.println(str instanceof Animal);//错误
        System.out.println(str instanceof Object);//true
    }
}

4.3.4 java的动态绑定机制(非常非常重要)

在这里插入图片描述

package hsp.ploy1;

public class AB {
    public static void main(String[] args) {
        //a 的编译类型 A, 运行类型 B
        A a = new B();//向上转型
        System.out.println(a.sum());//?40 -> 30
        System.out.println(a.sum1());//?30-> 20

    }
}

class A {

    public int i = 10;

    //动态绑定机制:
    public int sum() {//父类 sum()
        return getI() + 10;//20 + 10    方法动态绑定,去调了子类
    }

    public int sum1() {//父类 sum1()
        return i + 10;//10 + 10         属性无动态绑定机制
    }

    public int getI() {//父类 getI
        return i;
    }
}

class B extends A {
    public int i = 20;

    // public int sum() {
    //      return i + 20;
    // }
    public int getI() {//子类 getI()
        return i;
    }
    // public int sum1() {
    //return i +10;
    // }
}

4.3.5 多态的应用

  1. 多态数组
    数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
package hsp.ploy2;

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

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

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

    public String say() {
        return name + "\t" + age;
    }
}

//
package hsp.ploy2;

public class Student extends Person{
    private double score;

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

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
    //重写父类
    @Override
    public String say() {
        return "学生 " + super.say() + " score=" + score;
    }
    //特有的方法
    public void study() {
        System.out.println("学生 " + getName() + " 正在学 java...");
    }
}

//
package hsp.ploy2;

public class Teacher extends Person{
    private double salary;

    public Teacher(String name, int age, double salary) {
        super(name, age);
        this.salary = salary;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
    //重写父类
    @Override
    public String say() {
        return "老师 " + super.say() + " salary=" + salary;
    }
    //特有方法
    public void teach() {
        System.out.println("老师 " + getName() + " 正在讲 java 课程...");
    }
}

//
package hsp.ploy2;

//多态数组
public class PloyArray {
    public static void main(String[] args) {
        //应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象、
        // 2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象 say 方法
        Person[] persons = new Person[5];
        persons[0] = new Person("jack", 20);
        persons[1] = new Student("mary", 18, 100);
        persons[2] = new Student("smith", 19, 30.1);
        persons[3] = new Teacher("scott", 30, 20000);
        persons[4] = new Teacher("king", 50, 25000);

        //循环遍历多态数组,调用say()方法
        for (int i = 0; i < persons.length; i++) {
            String str =  persons[i].say();//动态绑定机制:persons[i]编译类型,运行类型根据JVM判断
            System.out.println(str);

            if(persons[i] instanceof Student){
                Student student = (Student) persons[i];  //向下转型
                student.study();
            } else if (persons[i] instanceof Teacher) {
                Teacher teacher = (Teacher) persons[i];  //向下转型
                teacher.teach();
            }else if (persons[i] instanceof Person) {

            } else {
                System.out.println("类型错误,检查...");
            }
        }
    }
}

  1. 多态参数
    方法定义的形参类型为父类类型,实参类型允许为子类类型
package hsp.ploy3;

//员工类
public class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

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

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
    public double getMoney(){
        return 12 * this.salary;
    }
}
//
package hsp.ploy3;

//普通员工类
public class Worker extends Employee{
    public Worker(String name, double salary) {
        super(name, salary);
    }

    @Override
    public double getMoney() {
        return super.getMoney();
    }
    public void work() { //普通员工没有其它收入,则直接调用父类方法
        System.out.println("普通员工 " + getName() + " is working");
    }
}

//
package hsp.ploy3;
//经理类
public class Manger extends Employee{
    private double  bonus;//奖金

    public Manger(String name, double salary, double bonus) {
        super(name, salary);
        this.bonus = bonus;
    }

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    @Override
    public double getMoney() {
        return super.getMoney() + this.bonus;
    }
    public void manage() {
        System.out.println("经理 " + getName() + " is managing");
    }
}

//
package hsp.ploy3;

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

        Worker green = new Worker("Green", 3000);
        Manger tim = new Manger("Tim", 6000, 100000);
        TestMain TestMain = new TestMain();
        TestMain.showEmpAnnual(green);//36000.0
        TestMain.showEmpAnnual(tim);//172000.0
        TestMain.testWork(green);//普通员工 Green is working
        TestMain.testWork(tim);//经理 Tim is managing
    }

    //showEmpAnnual(Employee e)
    //实现获取任何员工对象的年工资,并在 main 方法中调用该方法 [e.getAnnual()]
    public void showEmpAnnual(Employee e) {
        System.out.println(e.getMoney());//动态绑定机制. }
    }
    //添加一个方法,testWork,如果是普通员工,则调用 work 方法,如果是经理,则调用 manage 方法
    public void testWork(Employee e) {
        if (e instanceof Worker) {
            ((Worker) e).work();//有向下转型操作
        } else if (e instanceof Manger) {
            ((Manger) e).manage();//有向下转型操作
        } else {
            System.out.println("不做处理...");
        }
    }
}

5.Object 类详解

5.1 equals 方法

== 是一个比较运算符

1. ==: 既可以判断基本类型,又可以判断引用类型
2. ==: 如果判断基本类型,判断的是值是否相等。示例:int i=10; double d=10.0;
3. ==: 如果判断引用类型,判断的是地址是否相等,即判定是不是同一个对象
4. equals:是Object类中的方法,只能判断引用类型
5.默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。比如lnteger,String【看看String 和 Integer的equals源代码】
package hsp.object1;

public class Equals1 {
    public static void main(String[] args) {
        A a = new A();
        A b = a;
        A c = b;
        System.out.println(a == c);//true   同一对象
        System.out.println(b == c);//true   同一对象
        B bObj = a;
        System.out.println(bObj == c);//true

        int num1 = 10;
        double num2 = 10.0;
        System.out.println(num1 == num2);//true  //基本数据类型,判断值是否相等

        /*
        equals源码
        //把 Object 的 equals 方法重写了,变成了比较两个字符串值是否相同
        public boolean equals(Object anObject) {
        if (this == anObject) { //如果是同一个对象
            return true;
        }
        if (anObject instanceof String) { //判断类型
            String anotherString = (String)anObject; //向下转型
            int n = value.length;
            if (n == anotherString.value.length) { //如果长度相同
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i]) //然后一个一个的比较字符
                        return false;
                    i++;
                }
                return true;//如果两个字符串的所有字符都相等,则返回 true
            }
        }
        return false;//不是是同一个对象,直接false
    }
     */


        /*  Object 类的 equals
        //即 Object 的 equals 方法默认就是比较对象地址是否相同
        //也就是判断两个对象是不是同一个对象.
        public boolean equals(Object obj) {
            return (this == obj);
        }
        */

        Integer integer1 = new Integer(1000);
        Integer integer2 = new Integer(1000);
        System.out.println(integer1 == integer2);//false
        System.out.println(integer1.equals(integer2));//true
        String str1 = new String("hspedu");
        String str2 = new String("hspedu");
        System.out.println(str1 == str2);//false
        System.out.println(str1.equals(str2));//true
    }
}
class B {}
class A extends B {}

重写equals方法
应用实例: 判断两个 Person 对象的内容是否相等,如果两个 Person 对象的各个属性值都一样,则返回 true,反之 false。

package hsp.object1;


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

        Person person1 = new Person("jack", 10, '男');
        Person person2 = new Person("jack", 10, '男');
        System.out.println(person1.equals(person2));//true
        
        Person_ p1 = new Person_();
        p1.name = "codeSE";
        Person_ p2 = new Person_();
        p2.name = "codeSE";
        System.out.println(p1==p2); //false
        System.out.println(p1.name .equals(p2.name));//true
        System.out.println(p1.equals(p2));//false
        String s1 = new String("asdf");
        String s2 = new String("asdf");
        System.out.println(s1.equals(s2));//true
        System.out.println(s1==s2); //false


        int it = 65;
        float fl = 65.0f;
        System.out.println("65 和 65.0f 是否相等?" + (it == fl));//true
        char ch1 = 'A'; char ch2 = 12;
        System.out.println("65 和‘A’是否相等?" + (it == ch1));//true
        System.out.println("12 和 ch2 是否相等?" + (12 == ch2));//true
        String str1 = new String("hello");
        String str2 = new String("hello");
        System.out.println("str1 和 str2 是否相等?"+ (str1 == str2)); //false
        System.out.println("str1 是否 equals str2?"+(str1.equals(str2)));//false
//        System.out.println("hello"== new java.sql.Date()); //编译错误
    }
}

class Person {
    private String name;
    private int age;
    private char gender;


    @Override //重写 Object 的 equals 方法
    public boolean equals(Object obj) {
        //判断如果比较的两个对象是同一个对象,则直接返回 true
        if (this == obj) {
            return true;
        }
        //类型判断
        if (obj instanceof Person) {//是 Person,我们才比较

            Person p = (Person) obj;//进行 向下转型, 因为我需要得到 obj 的 各个属性
            return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
        }
        return false; //如果不是 Person ,则直接返回 false
    }

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

}

class Person_{
    public String name;
}

5.2 hashCode 方法

在这里插入图片描述

1) 提高具有哈希结构的容器的效率!
2) 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
3) 两个引用,如果指向的是不同对象,则哈希值是不一样的
package hsp.object1;

public class HashCode1 {
    public static void main(String[] args) {
        AA a1 = new AA();
        AA a2 = new AA();
        AA a3 = a1;
        System.out.println("A.hashCode()=" + a1.hashCode());//10568834
        System.out.println("A2.hashCode()=" + a2.hashCode());//21029277
        System.out.println("A3.hashCode()=" + a3.hashCode());//10568834
    }
}
class AA {}

5.3 toString 方法

  1. 基本介绍
    默认返回:全类名+@+哈希值的十六进制,【查看 Object 的 toString 方法】
    子类往往重写 toString 方法,用于返回对象的属性信息
  2. 重写 toString 方法,打印对象或拼接对象时,都会自动调用该对象的 toString 形式.
  3. 当直接输出一个对象时,toString 方法会被默认的调用, 比如 System.out.println(monster); 就会默认调用
    monster.toString()
package hsp.object1;

public class ToString1 {
    /*
        Object 的 toString() 源码
        (1)getClass().getName() 类的全类名(包名+类名 )
        (2)Integer.toHexString(hashCode()) 将对象的 hashCode 值转成 16 进制字符串
        public String toString() {
            return getClass().getName() + "@" + Integer.toHexString(hashCode());
        }
    */

    public static void main(String[] args) {
        Monster monster = new Monster("蜘蛛精", "吐丝的", 1234.50);
        System.out.println(monster.toString() + " hashcode=" + monster.hashCode());//hsp.object1.Monster@a14482 hashcode=10568834
        System.out.println("==当直接输出一个对象时,toString 方法会被默认的调用==");
        System.out.println(monster); //等价 monster.toString()  //hsp.object1.Monster@a14482

        System.out.println("-------------重写toString方法后----------------------");
        System.out.println(monster.toString() + " hashcode=" + monster.hashCode());//Monster{name='蜘蛛精', work='吐丝的', count=1234.5} hashcode=10568834

        System.out.println(monster); //Monster{name='蜘蛛精', work='吐丝的', count=1234.5}

    }
}

class Monster{
    private String name;
    private String work;
    private double count;

    public Monster(String name, String work, double count) {
        this.name = name;
        this.work = work;
        this.count = count;
    }
    //重写 toString 方法, 输出对象的属性


    @Override
    public String toString() { //重写后,一般是把对象的属性值输出,也可以自己定制
        return "Monster{" +
                "name='" + name + '\'' +
                ", work='" + work + '\'' +
                ", count=" + count +
                '}';
    }
}

5.4 finalize 方法

  1. 当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作
  2. 什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来
    销毁该对象,在销毁该对象前,会先调用 finalize 方法
  3. 垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制
package hsp.object1;

public class Finalize1 {
    public static void main(String[] args) {
        Ping ping = new Ping("小猪的食物");
        ping = null;
        //ping就变成一个垃圾,垃圾回收机制会回收(销毁)该对象,在销毁前会调用finalize方法,
        // 所以可以在 finalize 中,写自己的业务逻辑代码(比如释放资源:数据库连接,或者打开文件..)
        //如果不重写 finalize,那么就会调用 Object 类的 finalize, 即默认处理

        System.gc();//主动调用垃圾回收器
        System.out.println("退出了");
    }
}

class Ping{
    private String food;

    public Ping(String food) {
        this.food = food;
    }

    @Override  //重写finalize
    protected void finalize() throws Throwable {
        System.out.println("销毁了"+this.food+"释放资源...");
    }
}

6. 断点调试(debug)

断点调试是指在程序的某一行设置一个断点,调试时,程序运行到这一行就会停住,然后你可以一步一步往下调试,调试过程中可以看各个变量当前的值,出错的话,调试到出错的代码行即显示错误,停下。进行分析从而找到这个Bug
2.断点调试是程序员必须掌握的技能。
3.断点调试也能帮助我们查看java底层源代码的执行过程,提高程序员的Java水平。

● 断点调试的快捷键

F7(跳入)
F8(跳过)
shift+F8(跳出)
F9(resume,执行到下一个断点)
F7:跳入方法内
F8:逐行执行代码
shift+F8:跳出方法
在这里插入图片描述

第5章 面向对象(高级)

1、类变量和类方法

1.1 什么是类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。

1.2 定义类变量定义语法:

访问修饰符  static   数据类型  变量名;【推荐】
static  访问修饰符   数据类型  变量名;

1.3 如何访问类变量

类名.类变量名
对象名.类变量名【静态变量的访问修饰符的访问权限和范围和普通属性是一样的】
推荐使用:类名.类变量名
public class Static1 {
    public static void main(String[] args) {
        //类变量是随着类加载而创建,不用实例化可直接访问
        System.out.println(A.name);//code SE
        A a = new A();
        System.out.println(a.name);//code SE
        //System.out.println(A.salary);//错误
    }
}

class A {
    public double salary =4500;//普通属性/普通成员变量/非静态属性/非静态成员变量/实例变量
    public static int count = 0; //静态属性/静态成员/静态成员变量
    public static String name = "code SE";//类变量,必须遵守相关访问权限

}

●类变量使用注意事项和细节

1.什么时候需要用类变量
当我们需要让某个类的所有对象都共享一个变量时, 就可以考虑使用类变量(静态变量)。
2.类变量与实例变量(普通属性)区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
3.加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
4.类变量可以通过类名.类变量名或者对象名.类变量名来访问,但java设计者推荐
我们使用类名类变量名方式访问。【前提是 满足访问修饰符的访问权限和范围】
5.实例变量不能通过类名.类变量名方式访问。
6.类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,
就可以使用类变量了。
7.类变量的生命周期是随类的加载开始,随着类消亡而销毁。
类方法

类方法也叫静态方法。

形式如下:
访问修饰符	static	数据返回类型	方法名(){ } 【推荐】
static	访问修饰符	数据返回类型	方法名(){ }

使用方式:
类名.类方法名或者对象名.类方法名 【前提是满足访问修饰符的访问权限】
package hsp.static1;

public class Static2 {
    public static void main(String[] args) {
        Student s1 = new Student("Green");
        Student.total(4500);
        //s1.total(4500);
        Student s2 = new Student("Green");
        Student.total(4500);
        //s2.total(4500);
        Student.showHeight();//所有人总身高:9000.0

        double sum =  MyUntil.sum(12.3,45.6);
        System.out.println(sum);//57.900000000000006
    }
}

class Student {
    private String name;
    private static double heights;

    public Student(String name) {
        this.name = name;
    }
    //static静态方法  可直接访问静态属性
    public static void total(double height) {
        Student.heights += height;

    }
    public static void showHeight(){
        System.out.println("所有人总身高:"+Student.heights);
    }
}

class MyUntil{
    public static double sum(double n1,double n2){
        return  n1+n2;
    }
}

类方法使用注意事项和细节
1)类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:
类方法中无this的参数
普通方法中隐含着this的参数
2)类方法可以通过类名调用,也可以通过对象名调用。
3)普通方法和对象有关,需要通过对象名调用,比如对象名方法名(参数),不能通过类名调用。
4)类方法中不允许使用和对象有关的关键字,比如this和super, 普通方法(成员方法)可以。
5)类方法(静态方法)中只能访问静态变量或静态方法。
6)普通成员方法,既可以访问非静态成员,也可以访问静态成员。

小结:静态方法, 只能访问静态的成员,非静态的方法,可以访向静态成员和非静态成员(必须遵守访问权限)

package hsp.static1;

public class Static3 {

}

class AA{
    private int n1 = 100;
    private static int n2 = 1000;

    //普通方法
    public void run(){}
    //静态方法
    public static void eat(){
        //静态方法中不允许使用和对象有关的关键字,比如 this 和 super。
        //System.out.println(this.n1);//错

        //静态方法中 只能访问 静态变量 或静态方法
        //System.out.println(n1);//错
        System.out.println(n2);
        //run();//错
        sleep();

    }
    static void sleep(){}
    void jump(){
        //普通成员方法,既可以访问 非静态成员,也可以访问静态成员
        System.out.println(n1);
        System.out.println(n2);
        run();
        sleep();

    }
}

2、main方法

解释main方法的形式: public static void main(String[] args){ }

  1. main方法时虚拟机调用
  2. java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
  3. java虚拟机在执行main(方法时不必创建对象, 所以该方法必须是static
  4. 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所
    运行的类的参数,接收参数
  5. java 执行的程序 参数1 参数2 参数3
    在这里插入图片描述
    特别提示:
    1)在main(方法中,我们可以直接调用main方法所在类的静态方法或静态属性。
    2)但是,不能直接访问该类中的非静态成员,必须创建该类的一一个实例对象后,才能通过这个对象去访问类中的非静
    态成员

3、代码块

3.1基本介绍
代码化块又称为初始化块,属于类中的成员【即是类的一部分】,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。

3.2基本语法
[修饰符]{
代码
}
说明注意:
1)修饰符可选,要写的话,也只能写static
2)代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的, 叫普通代码块/非静态代码块。
3)逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
4);号可以写上,也可以省略。

3.3代码块的好处
1)相当于另外种形式的构造器(对构造器的补充机制),可以做初始化的操作
2)场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性

package hsp.codeBlock;

public class CodeBlock01 {
    public static void main(String[] args) {
        Movie m1 = new Movie("花木兰");
        Movie m2 = new Movie("长江七号",40.5);
        Movie m3 = new Movie("哪吒",55,800);
        /*输出结果如下:
        * 即将开始......马上进入
        * 花木兰
        * 即将开始......马上进入
        * 长江七号40.5
        * 即将开始......马上进入
        * 哪吒55.0800
        * */

    }
}

class Movie{
    private String name;
    private double price;
    private int count;

    //代码块   不管调用哪个构造器,创建对象,都会先调用代码块的内容
    //代码块调用的顺序优先于构造器
    {
        System.out.println("即将开始......马上进入");
    }

    public Movie() {
    }

    public Movie(String name) {
        this.name = name;
        System.out.println(this.name);
    }

    public Movie(String name, double price) {
        this.name = name;
        this.price = price;
        System.out.println(this.name+this.price);

    }

    public Movie(String name, double price, int count) {
        this.name = name;
        this.price = price;
        this.count = count;
        System.out.println(this.name+this.price+ this.count);

    }
}

3.4 代码块使用注意事项和细节

1) static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而
执行,并且只会执行一次。如果是普通代码块,每创建 一个对象,就执行。
2)类什么时候被加载【重要!】
	①创建对象实例时(new)
	②创建子类对象实例,父类也会被加载
	③使用类的静态成员时(静态属性,静态方法)
案例演示: A类extends B类的静态块
3)普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会调用次。
  如果只是使用类的静态成员时,普通代码块并不会执行。

小结:
1. static代码块是类加载时,执行,只会执行1次。
2. 普通代码块是在创建对象时调用的,每创建一次,就调用1次。

代码演示:

package hsp.codeBlock;

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


        //类被加载的情况举例
        //1. 创建对象实例时(new)
        BB bb1 = new BB();
        //2. 创建子类对象实例,父类也会被加载, 而且,父类先被加载,子类后被加载
        BB bb2 = new BB();
        //3. 使用类的静态成员时(静态属性,静态方法),父类的静态代码块和子类的静态代码块会执行
        //System.out.println(Cat.n1);
        //static 代码块,是在类加载时,执行的,而且只会执行一次.
         CC dd1 = new CC(); //CC 的静态代码 1 被执行...    CC 的普通代码块...
         CC dd2 = new CC(); // CC 的普通代码块...
        //普通的代码块,在创建对象实例时,会被隐式的调用。
        // 被创建一次,就会调用一次。
        // 如果只是使用类的静态成员时,普通代码块并不会执行
        System.out.println(CC.n1);//8888, 静态模块块一定会执行
    }
}


class AA {
    //静态代码块
    static {
        System.out.println("AA 的静态代码 1 被执行...");//
    }
}

class BB extends AA {
    //静态代码块
    static {
        System.out.println("BB 的静态代码 1 被执行...");//
    }

}
class CC {
    public static int n1 = 888888;//静态属性

    //静态代码块
    static {
        System.out.println("CC 的静态代码 1 被执行...");//
    }

    //普通代码块, 在 new 对象时,被调用,而且是每创建一个对象,就调用一次
    {
        System.out.println("CC 的普通代码块...");
    }
}
class Animal {
    //静态代码块
    static {
        System.out.println("Animal 的静态代码 1 被执行...");//
    }
}

class Cat extends Animal {
    public static int n1 = 9999;//静态属性

    //静态代码块
    static {
        System.out.println("Cat 的静态代码 1 被执行...");//
    }
}

4)创建一个对象时,在一个类调用顺序是:(重点,难点 ) :
	①调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,
	如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
	②调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,
	如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
	③调用构造方法。

代码演示:

package hsp.codeBlock;

public class CodeBlock03 {
    public static void main(String[] args) {
        A a = new A();
        /*
        * (1) A 静态代码块
        * (2) getN1 被调用...
        * (3)A 普通代码块
        * (4)getN2 被调用...
        * (5)A() 构造器被调用
         * */
    }
}

class A {
    {
        System.out.println("A 普通代码块 01");
    }

    private int n2 = getN2();//普通属性的初始化

    static { //静态代码块
        System.out.println("A 静态代码块 01");
    }

    //静态属性的初始化
    private static int n1 = getN1();

    public static int getN1() {
        System.out.println("getN1 被调用...");
        return 100;
    }

    public int getN2() { //普通方法/非静态方法
        System.out.println("getN2 被调用...");
        return 200;
    }

    //无参构造器
    public A() {
        System.out.println("A() 构造器被调用");
    }
}
5)构造器的最前面其实隐含了super(和调用普通代码块,静态相关的代码块,属性初始化,
在类加载时,就执行完毕因此是优先于构造器和普通代码块执行的
class A {
	public A( {/构造器
		//这里有隐藏的执行要求
		//(1) super();
		//(2)调用普通代码块的
		System.out.println(" ok");
	}
}

代码演示:

package hsp.codeBlock;

public class CodeBlock04 {
    public static void main(String[] args) {
        BBB bbb = new BBB();
        /*
        * AAA 的普通代码块
        * AAA() 构造器被调用....
        * BBB 的普通代码块...
        * BBB() 构造器被调用....
        * */
    }

}
class AAA { //父类 Object
    {
        System.out.println("AAA 的普通代码块");
    }

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

}
class BBB extends AAA {
    {
        System.out.println("BBB 的普通代码块...");
    }

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

 6)我们看一下创建一 个子类对象时(继承关系), 他们的静态代码块,静态属性初始化,普通代码块,
 普通属性初始化,构造方法的调用顺序如下:
	①父类的静态代码块和静态属性(优先级样,按定义顺序执行)
	②子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
	③父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
	④父类的构造方法
	⑤子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
	⑥子类的构造方法//面试题

7)静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。

代码演示:

package hsp.codeBlock;

public class CodeBlock05 {
    public static void main(String[] args) {
        //(1) 进行类的加载
        //1.1 先加载 父类 AAAA
        // 1.2 再加载 BBBB
        //(2) 创建对象
        //2.1 从子类的构造器开始
        new BBBB();
        new CCCC();
    }
}

class AAAA { //父类
    private static int n1 = getVal01();

    static {
        System.out.println("AAAA 的一个静态代码块..");//(2)
    }

    {
        System.out.println("AAAA 的第一个普通代码块..");//(5)
    }

    public int n3 = getVal02();//普通属性的初始化

    public static int getVal01() {
        System.out.println("getVal01");//(1)
        return 10;
    }

    public int getVal02() {
        System.out.println("getVal02");//(6)
        return 10;
    }

    public AAAA() {//构造器
        //隐藏
        //super()
        //普通代码和普通属性的初始化......
        System.out.println("AAAA 的构造器");//(7)
    }
}

class BBBB extends AAAA { //
    private static int n3 = getVal03();

    static {
        System.out.println("BBBB 的一个静态代码块..");//(4)
    }

    public int n5 = getVal04();

    {
        System.out.println("BBBB 的第一个普通代码块..");//(9)
    }

    public static int getVal03() {
        System.out.println("getVal03");//(3)
        return 10;
    }

    public int getVal04() {
        System.out.println("getVal04");//(8)
        return 10;
    }

    public BBBB() {//构造器
        //隐藏了
        //super()
        //普通代码块和普通属性的初始化...

        System.out.println("BBBB 的构造器");//(10)
    }

}

class CCCC {
    private int n1 = 100;
    private static int n2 = 200;

    private void m1() {
    }

    private static void m2() {

    }

    static {
        //静态代码块,只能调用静态成员
        //System.out.println(n1);错误
        System.out.println(n2);//ok
        //m1();//错误
        m2();
    }

    {
        //普通代码块,可以使用任意成员
        System.out.println(n1);
        System.out.println(n2);//ok
        m1();
        m2();
    }
}

4、单例模式

4.1 什么是单例模式
单例(单个的实例)
1.所谓类的单例设计模式,就是采取一 定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法
2.单例模式有两种方式: 1)饿汉式 2)懒汉式

4.2 单例模式应用实例

饿汉式和懒汉式单例模式的实现。
步骤如下:
1)构造器私有化 ==> 防止直接new
2)类的内部创建对象
3)向外暴露一一个静态的公共方法。getnstance

package hsp.singleModel;

//单例模式-饿汉式
public class Single01 {
    public static void main(String[] args) {
        // GirlFriend xh = new GirlFriend("小红");
        // GirlFriend xb = new GirlFriend("小白");

        //通过方法可以获取对象
        GirlFriend instance = GirlFriend.getInstance();
        System.out.println(instance);//GirlFriend{name='玉玉'}
        GirlFriend instance2 = GirlFriend.getInstance();
        System.out.println(instance2);//GirlFriend{name='玉玉'}
        System.out.println(instance == instance2);//true
    }
}

class GirlFriend{
    private String name;
    //public static int n1 = 100;
    //为了能够在静态方法中,返回 gf 对象,需要将其修饰为 static
    //对象,通常是重量级的对象, 饿汉式可能造成創建了對象,但是沒有使用.
    private static GirlFriend gf = new GirlFriend("玉玉");
    //如何保障我们只能创建一个 GirlFriend 对象
    //步骤[单例模式-饿汉式]
    //1. 将构造器私有化
    //2. 在类的内部直接创建对象(该对象是 static)
    //3. 提供一个公共的 static 方法,返回 gf 对象
    private GirlFriend(String name) {
        System.out.println("构造器被调用...");
        this.name = name;
    }
    public static GirlFriend getInstance() {
        return gf;
    }

    @Override
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + '\'' +
                '}';
    }
}
package hsp.singleModel;

public class Single02 {
    public static void main(String[] args) {
        //new Dog("大黃");
        //System.out.println(Dog.n1);
        Dog instance = Dog.getInstance();
        System.out.println(instance);
        
        //再次調用 getInstance
        Dog instance2 = Dog.getInstance();
        System.out.println(instance2);
        System.out.println(instance ==instance2);

}

//希望程序执行过程中只能创建一次对象
class Dog {
    private String name;
    public static int n1 = 10;
    private static Dog dog; //默认是 null,  【注意此处需要高版本的jdk才支持】

    //步驟:
    //1.仍然光构造器私有化
    //2.定义一个static 静态属性对象
    //3.提供一个 public 的 static 方法,可以返回一个 Dog 对象
    //4.懒汉式,只有当用戶使用 getInstance 时,才返回 dog 对象, 
    //	后面再次调用时,返回上一次创建的 dog 对象
    //5. 这样就保证了单例
    private Dog(String name) {
        System.out.println("构造器调用...");
        this.name = name;
    }

    public static Dog getInstance() {
        if (dog == null) {//如果还没有,就创建 dog 对象
            dog = new Dog("喵喵喵");
        }
        return dog;
    }
}

4.3 饿汉式VS懒汉式
1.二者最主要的区别在于创建对象的时机不同:
饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建。
2.饿汉式不存在线程安全问题,懒汉式存在线程安全问题。
3.饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。
在javaSE标准类中,java.lang.Runtime就是经 典的单例模式。

5.final关键字

5.1 基本介绍

final中文意思:最后的,最终的.
final可以修饰类、属性、方法和局部变量.

在某些情况下,程序员可能有以下需求,就会使用到final:
1)当不希望类被继承时,可以用final修饰

2)当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰。【访问修饰符 final 返回类型 方法名】

3)当不希望类的的某个属性的值被修改,可以用final修饰【 public final double MAX_TEAT=0.99】

4)当不希望某个局部变量被修改,可以使用final修饰【final double MAX_TEAT=0.99】

package hsp.final_;

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

    }
}
//要求 A 类不能被其他类继承
//可以使用 final 修饰 A 类
//final class A { }
//class B extends A {}

class C {
    //如果我们要求 say 不能被子类重写
    //可以使用 final 修饰 say 方法
    public final void say() {}
}
class D extends C {
    // @Override
    //public void say() {
        //System.out.println("重写了 C 类的 hi 方法..");
    //}
}

//当不希望类的的某个属性的值被修改,可以用 final
class E {
    public final double MAX_TEXT = 0.88;//常量
}
//当不希望某个局部变量被修改,可以使用 final 修饰
class F{
   public void getAge(){
       final int age =18;
       //age=20;
   }
}

5.2 final 使用注意事项和细节

1)final修饰的属性又叫常量,一般用XX _XX _XX来命名
2) final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择-个位置赋初值即可】:
①定义时:如public final double MAX_TEXT=0.99;
②在构造器中
③在代码块中。
3)如果final修饰的属性是静态的,则初始化的位置只能是
①定义时②在静态代码块不能在构造器中赋值。
4) final类不能继承,但是可以实例化对象。
5)如果类不是final类,但是含有final方法, 则该方法虽然不能重写,但是可
以被继承。
查看下面代码:

package hsp.final_;

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

    }
}

class AA {
    /*
    1. 定义时:如 public final double TAX_RATE=0.08;
    2. 在构造器中
    3. 在代码块中
    */
    public final double TAX_RATE = 0.01;//1.定义时赋值
    public final double TAX_RATE2;
    public final double TAX_RATE3;


    public AA() {//构造器中赋值
        TAX_RATE2 = 1.1;
    }

    {//在代码块赋值
        TAX_RATE3 = 2.2;
    }

}

class BB {
    /*
    如果 final 修饰的属性是静态的,则初始化的位置只能是
    1 定义时 2 在静态代码块 不能在构造器中赋值。
    */
    public static final double TAX_RATE = 99.9;
    public static final double TAX_RATE2;

    static {
        TAX_RATE2 = 3.3;
    }
}

//final 类不能继承,但是可以实例化对象
final class CC {

}

//如果类不是 final 类,但是含有 final 方法,则该方法虽然不能重写,但是可以被继承
//即,仍然遵守继承的机制.
class DD {
    public final void cal() {
        System.out.println("cal()方法");
    }
}

class EE extends DD {
}

6)一般来说,如果一一个类已经是final类了,就没有必要再将方法修饰成final方法。
7) final不能修饰构造方法(即构造器)
8) final和static往往搭配使用,效率更高,不会导致类加载.底层编译器做了优化处理。

class Demo{
public static final int i=16; //
	static{
		System.out.println("codeSE教育~"); 
	}
}

9)包装类(Integer,Double,Float, Boolean等都是final),String也是final类。

6、抽象类

基本介绍

当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,用abstract 来修饰该类就是抽象类。
1)用abstract关键字来修饰一个类时,这个类就叫抽象类

访问修饰符 abstract  类名{

}

2)用abstract关键字来修饰一个方法时,这个方法就是抽象方法

访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体

3)抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类()
4)抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多

抽象类使用的注意事项和细节

1)抽象类不能被实例化
2)抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
3)一旦类包含了abstract方法,则这个类必须声明为abstract
4) abstract 只能修饰类和方法,不能修饰属性和其它的。

package hsp.abstract_;

public class Abstract01 {
    public static void main(String[] args) {
    //抽象类,不能被实例化
    //new A();
    }
}

//抽象类不一定要包含 abstract 方法。也就是说,抽象类可以没有 abstract 方法
//还可以有实现的方法。
abstract class A {
    public void hi() {
        System.out.println("hi");
    }
}

//一旦类包含了 abstract 方法,则这个类必须声明为 abstract
abstract class B {
    public abstract void hi();
}

//abstract 只能修饰类和方法,不能修饰属性和其它的
class C {
// public abstract int n1 = 1;
}

5)抽象类可以有任意成员【抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等
6)抽象方法不能有主体,即不能实现.
7)如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。
8)抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。

代码演示:

//抽象类的本质还是类,所以可以有类的各种成员
abstract class D {
public int n1 = 10;
public static String name = "韩顺平教育";
public void hi() {
System.out.println("hi");
}
public abstract void hello();
public static void ok() {
System.out.println("ok");
}
}

案例:

package hsp.abstract_;

public class Abstract03 {
    public static void main(String[] args) {
        Manager manager = new Manager("小李", 1001, 20000);
        manager.work();
        CommonEmployee commonEmployee = new CommonEmployee("老王", 2002, 3000);
        commonEmployee.work();
    }
}
abstract  class Employee {
    private String name;
    private int id;
    private double salary;

    public Employee(String name, int id, double salary) {
        this.name = name;
        this.id = id;
        this.salary = salary;
    }
    //将 work 做成一个抽象方法
    public abstract void work();

    public String getName() {
        return name;
    }

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}
class Manager extends Employee {
    private double bonus;
    public Manager(String name, int id, double salary) {
        super(name, id, salary);
    }
    public double getBonus() {
        return bonus;
    }
    public void setBonus(double bonus) {
        this.bonus = bonus;
    }
    @Override
    public void work() { //实现父类的抽象方法
        System.out.println("经理 " + getName() + " 工作中...");
    }
}
class CommonEmployee extends Employee{
    public CommonEmployee(String name, int id, double salary) {
        super(name, id, salary);
    }

    @Override
    public void work() { //实现父类的抽象方法
        System.out.println("员工 " + getName() + " 工作中...");
    }
}

7、 抽象类最佳实践-模板设计模式

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

模板设计模式能解决的问题

1)当功能内部一部分实现是确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
2)编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式。
代码演示:

package hsp.abstract_;

// 抽象类-模板设计模式
public abstract class Template {
    public static void main(String[] args) {
        AA aa = new AA();
        aa.calculateTime(); //这里还是需要有良好的 OOP 基础,对多态
        BB bb = new BB();
        bb.calculateTime();
    }

}

abstract class Template1 {
    public abstract void job();//抽象方法

    public void calculateTime() {//实现方法,调用 job 方法
        //得到开始的时间
        long start = System.currentTimeMillis();
        job(); //动态绑定机制
        //得的结束的时间
        long end = System.currentTimeMillis();
        System.out.println("任务执行时间 " + (end - start));
    }
}

class AA extends Template1 {
    //计算任务
    //1+....+ 800000
    @Override
    public void job() { //实现 Template 的抽象方法 job
        long num = 0;
        for (long i = 1; i <= 800000; i++) {
            num += i;
        }
    }

}

class BB extends Template1 {
    public void job() {//这里也去,重写了 Template 的 job 方法
        long num = 0;
        for (long i = 1; i <= 80000; i++) {
            num *= i;
        }
    }
}

8、接口

在这里插入图片描述

8.1 基本介绍

接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。

语法:
interface接口名{ //属性
	//抽象方法
}
class 类名 implements  接口{
	自己属性;
	自己方法;
	必须实现的接口的抽象方法
}

★小结:接口是更加抽象的抽象的类,抽象类里的方法可以有方法体,接口里的所有方法都没有方法体【jdk7.0】。接口体现了程序设计的多态和高内聚低偶合的设计思想。
特别说明:Jdk8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现

package hsp.interface_;

public class Interface01 {
    public static void main(String[] args) {
        Iphone15 iphone15 = new Iphone15();
        Mate60 mate60 = new Mate60();
        iphone15.insert();
        iphone15.extract();
        mate60.insert();
        mate60.extract();
    }
}
interface USB{ //接口定义规范
    public void insert();
    public void extract();
}

//实现两个接口
class Iphone15 implements USB{

    @Override
    public void insert() {
        System.out.println("iphone 15 插入充电....");
    }

    @Override
    public void extract() {
        System.out.println("iphone 15 拔出充电....");

    }
}
//实现两个接口
class Mate60 implements USB{

    @Override
    public void insert() {
        System.out.println("华为 Mate60 插入充电....");
    }

    @Override
    public void extract() {
        System.out.println("华为 Mate60 拔出充电....");

    }
}

package hsp.interface_;

public class Interface02 {
    public static void main(String[] args) {
        MysqlDB mysqlDB = new MysqlDB();
        test(mysqlDB);
        OracleDB oracleDB = new OracleDB();
        test(oracleDB);
    }
    public static void test(DBInterface db) {
        db.connect();
        db.close();
    }
}
 interface DBInterface { //数据库接口
    public void connect();//连接方法
    public void close();//关闭连接
}
class MysqlDB implements DBInterface{

    @Override
    public void connect() {
        System.out.println("连接mysql");
    }

    @Override
    public void close() {
        System.out.println("关闭mysql");
    }
}
class OracleDB implements DBInterface{

    @Override
    public void connect() {
        System.out.println("连接oracle");
    }

    @Override
    public void close() {
        System.out.println("关闭oracle");

    }
}

8.2 注意事项和细节

1)接口不能被实例化
2)接口中所有的方法是public方法, 接口中抽象方法,可以不用abstract修饰
void aaa();
实际上是abstract void aa( );
3)一个普通类实现接口,就必须将该接口的所有方法都实现。
4)抽象类实现接口,可以不用实现接口的方法。
代码演示:

package hsp.interface_;

public class Interface03 {
    public static void main(String[] args) {
        //new Animal();  //'Animal' 为 abstract;无法实例化
    }
}

//1.接口不能被实例化
//2.接口中所有的方法是 public 方法, 接口中抽象方法,可以不用 abstract 修饰
//3.一个普通类实现接口,就必须将该接口的所有方法都实现,可以使用 alt+enter 来解决
//4.抽象类去实现接口时,可以不实现接口的抽象方法
interface Animal {
    void say();

    void hi();
}

class Cat implements Animal {
    @Override
    public void say() {
    }

    @Override
    public void hi() {
    }
}
//抽象类可以不实现接口方法
abstract class Tiger implements Animal {
}

5)一个类同时可以实现多个接口[举例]
6)接口中的属性,只能是final的,而且是public static final 修饰符。比如:
int a=1;实际上是public static final int a= 1; (必须初始化)
7)接口中属性的访问形式:接口名.属性名
8)接口不能继承其它的类,但是可以继承多个别的接口
interface A extends B,C{ }
9)接口的修饰符只能是public和默认,这点和类的修饰符是一样的。

package hsp.interface_;

public class InterFace04 {
}

interface IA {
    //接口中的属性,只能是 final 的,而且是 public static final 修饰符
    int n1 = 10; //等价 public static final int n1 = 10;

    void hi();
}

interface IB {
    void say();
}

//接口不能继承其它的类,但是可以继承多个别的接口
interface IC extends IA, IB {
}


//一个类同时可以实现多个接口
class Pig implements IB,IA {
    @Override
    public void hi() {
    }
    @Override
    public void say() {
    }
}
class Dog implements IC{

    @Override
    public void hi() {
        
    }

    @Override
    public void say() {

    }
}

8.3 按口和继承解决的问题不同

➢继承的价值主要在于:解决代码的复用性和可维护性。
➢接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。即更加的灵活…
➢接口比继承更加灵活
接口比继承更加灵活,继承是满足is - a的关系,而接口只需满足like - a的关系。
➢接口在一定程度上实现代码解耦【即:接口规范性+动态绑定机制】
代码演示:

package hsp.interface_;


public class ExtendsVsInterface {
    public static void main(String[] args) {
        Person person = new Person("小明");
        person.eatting();
        person.flying();
        person.swimming();
    }
}
class Fish{
    private String name;
    public Fish(String name) {
        this.name = name;
    }
    public void eatting() {
        System.out.println(name + " 会吃虾...");
    }
    public String getName() {
        return name;
    }
}
//接口
interface Fishable {
    void swimming();
}
interface Birdable {
    void flying();
}
//继承并实现
class Person extends Fish implements Fishable,Birdable{

    public Person(String name) {
        super(name);
    }

    @Override
    public void flying() {
        System.out.println(getName() + " 通过学习,可以像鸟儿一样飞翔...");
    }

    @Override
    public void swimming() {
        System.out.println(getName() + " 通过学习,可以像鱼儿一样游泳...");
    }
}

8.4 接口的多态特性

package hsp.interface_;

public class InterfacePolyParameter {
    public static void main(String[] args) {
        //接口的多态体现
        //接口类型的变量 if01 可以指向 实现了 IF 接口类的对象实例
        IF if01 = new Monster();
        if01 = new Car();
        //继承体现的多态
        //父类类型的变量 a 可以指向 继承 AAA 的子类的对象实例
        AAA a = new BBB();
        a = new CCC();
    }
}
interface IF {}
class Monster implements IF{}
class Car implements IF{}
class AAA {
}
class BBB extends AAA {}
class CCC extends AAA {}



/**
 * 演示多态传递现象
 */
class InterfacePolyPass {
    public static void main(String[] args) {
        //接口类型的变量可以指向,实现了该接口的类的对象实例
        IG ig = new Teacher();
        //如果 IG 继承了 IH 接口,而 Teacher 类实现了 IG 接口
        //那么,实际上就相当于 Teacher 类也实现了 IH 接口. //这就是所谓的 接口多态传递现象. IH ih = new Teacher();
    }
}
interface IH {
    void hi();
}
interface IG extends IH{ }//接口继承接口
class Teacher implements IG {

    @Override
    public void hi() {

    }
}
package hsp.interface_;

public class InterfacePolyArr {
    public static void main(String[] args) {
        //多态数组 -> 接口类型数组
        TypeC[] typeCs = new TypeC[2];
        typeCs[0] = new Phone();
        typeCs[1] = new Camera();
        /*
        给 TypeC 数组中,存放 Phone 和 相机对象,Phone 类还有一个特有的方法 call(),
        请遍历 TypeC 数组,如果是 Phone 对象,除了调用 TypeC 接口定义的方法外,
        还需要调用 Phone 特有方法 call
        */
        for (int i = 0; i < typeCs.length; i++) {
            typeCs[i].work();//动态绑定.. //需要进行类型的向下转型
            if (typeCs[i] instanceof Phone) {//判断他的运行类型是 Phone
                ((Phone) typeCs[i]).call();
            }

        }
    }

}

interface TypeC {
    void work();
}

class Phone implements TypeC {
    public void call() {
        System.out.println("手机可以打电话...");
    }

    @Override
    public void work() {
        System.out.println("手机工作中...");
    }
}

class Camera implements TypeC {

    @Override
    public void work() {
        System.out.println("相机工作中...");

    }
}

9、内部类

定义类在局部位置(方法中/代码块) : (1) 局部内部类 (2) 匿名内部类
定义在成员位置 :(1) 成员内部类 (2) 静态内部类

基本介绍

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员[思考:类的五大成员是哪些?[属性、方法、构造器、代码块、内部类],内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。

package hsp.innerclass;

public class Inner01 { //外部其他类
    public static void main(String[] args) {

    }
}

class Outer { //外部类
    private int n1 = 100;//属性

    public Outer(int n1) {//构造器
        this.n1 = n1;
    }

    public void m1() {//方法
        System.out.println("m1()");
    }

    {//代码块
        System.out.println("代码块...");
    }

    class Inner { //内部类, 在 Outer

    }
}

定义在外部类局部位置上(比如方法内):
1)局部内部类(有类名)
2)匿名内部类(没有类名,重点!!!)
定义在外部类的成员位置上:
1)成员内部类(没用static修饰)
2)静态内部类(使用static修饰)

局部内部类的使用

说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
1.可以直接访问外部类的所有成员,包含私有的
2.不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final
3.作用域:仅仅在定义它的方法或代码块中。
4.局部内部类—访问---->外部类的成员[访问方式:直接访问]
5.外部类—访问---->局部内部类的成员
访问方式:创建对象,再访问(注意:必须在作用域内)记住:
(1)局部内部类定义在方法中/代码块
(2)作用域在方法体或者代码块中
(3)本质仍然是一个类
6.外部其他类—不能访问----->局部内部类(因为局部内部类地位是一个局部变量)
7.如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

package hsp.innerclass;

/*
* 局部内部类的使用
* */
public class Inner02 {
    public static void main(String[] args) {
        Outer02 outer02 = new Outer02();
        outer02.m1();//99 , code SE


    }
}
class Outer02{ //外部类
    private int n1 = 99;
    private void m2(){
        System.out.println("code SE");
    }
    public void m1(){
        //局部内部类是定义在外部类的局部位置,通常在方法中
        //不能添加访问修饰符,但是可以使用final修饰
        //作用域:仅仅在定义它的方法或者代码块中
        final class Inner02{ //内部类(本质也是一个类)
            private int n1 =100;
            //可以直接访问外部类的所有成员,包含私有
            public void f1(){
                //如果外部类和局部内部类的成员重名时,默认遵循就近原则,
                // 如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
                System.out.println("内部类的n1="+n1);
                System.out.println("外部类的n1="+Outer02.this.n1);

                m2();
            }
        }
        //外部类在方法中,可以创建内部类的对象,然后调用方法即可
        Inner02 inner02 = new Inner02();
        inner02.f1();
    }
}

匿名内部类

匿名内部类的使用(重要!!!)
(1)本质是类(2)内部类(3)该类没有名字(4)同时还是一个对象
说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名
1.匿名内部类的基本语法

new  类或接口  (参数列表) {
	类体
};
package hsp.innerclass;


public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.method();
    }
}

class Outer04 { //外部类
    private int n1 = 10;//属性

    public void method() {//方法
        //基于接口的匿名内部类
        //1.需求: 想使用 IA 接口,并创建对象
        //2.传统方式,是写一个类,实现该接口,并创建对象
        //3.需求是 Tiger/Dog 类只是使用一次,后面再不使用
        //4. 可以使用匿名内部类来简化开发
        //5. tiger 的编译类型 ? IA
        //6. tiger 的运行类型 ? 就是匿名内部类 Outer04$1
        IA tiger = new IA() {
            @Override
            public void cry() {
                System.out.println("老虎叫你...");
            }
        }; // ; 是一个语句
        System.out.println("tiger 的运行类型="+tiger.getClass());
        tiger.cry();
        tiger.cry();
        tiger.cry();


        /*
         * 看底层 会分配 类名 Outer04$1
         *
         class Outer04$1 implements IA {
            @Override
            public void cry() {
                System.out.println("老虎叫唤...");
            }
          }

         * jdk 底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1 实例,并且把地址返回给 tiger
         * 匿名内部类使用一次,就不能再使用
         *
         * */


        //演示基于类的匿名内部类
        //1. father 编译类型 Father
        //2. father 运行类型 Outer04$2
        //3. 底层会创建匿名内部类
        /*
        class Outer04$2 extends Father{
            @Override
            public void test() {
                System.out.println("匿名内部类重写了 test 方法");
            }
        }
        */
        //4. 同时也直接返回了 匿名内部类 Outer04$2 的对象
        //5. 注意("jack") 参数列表会传递给 构造器
        Person person = new Person("jack"){
            @Override
            public void test() {
                System.out.println("匿名内部类重写了 test 方法");
            }
        };
        System.out.println("father 对象的运行类型=" + person.getClass());//Outer04$2
        person.test();


        //基于抽象类的匿名内部类
        new Animal(){
            @Override
            void eat() {
                System.out.println("基于抽象类的匿名内部类");
            }
        }.eat();
    }

}

interface IA {//接口

    public void cry();
}
class Person{
    public Person(String name) {
        System.out.println("接收到 name=" + name);
    }
    public void test(){}
}
abstract class Animal { //抽象类
    abstract void eat();
}

2、匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法。
3.可以直接访问外部类的所有成员,包含私有的
4.不能添加访问修饰符,因为它的地位就是一个局部变量。
5.作用域:仅仅在定义它的方法或代码块中。
6.匿名内部类—访问---->外部类成员[访问方式:直接访问]
7.外部其他类—不能访问----->匿名内部类(因为匿名内部类地位是一个局部变量)
8.如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
代码如下:

package hsp.innerclass;

public class AnonymousInnerClass02 {
    public static void main(String[] args) {
        Outer05 outer05 = new Outer05();
        outer05.f1();
        //外部其他类---不能访问----->匿名内部类
        System.out.println("main outer05 hashcode=" + outer05);

        /*输出
        * 匿名内部类重写了 hi 方法 n1=88 外部内的 n1=99
        * Outer05.this hashcode=hsp.innerclass.Outer05@a14482
        * main outer05 hashcode=hsp.innerclass.Outer05@a14482
        * */
    }
}

class Outer05 {
    private int n1 = 99;

    public void f1() {
        //创建一个基于类的匿名内部类
        //不能添加访问修饰符,因为它的地位就是一个局部变量
        //作用域 : 仅仅在定义它的方法或代码块中
        People p = new People() {
            private int n1 = 88;

            @Override
            public void hi() {
                //可以直接访问外部类的所有成员,包含私有的
                //如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,
                //默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.this.成员)去访问
                System.out.println("匿名内部类重写了 hi 方法 n1=" + n1 +
                        " 外部内的 n1=" + Outer05.this.n1);
                //Outer05.this 就是调用 f1 的 对象
                System.out.println("Outer05.this hashcode=" + Outer05.this);
            }
        };
        p.hi();//动态绑定, 运行类型是 Outer05$1
        //也可以直接调用, 匿名内部类本身也是返回对象
        // class 匿名内部类 extends People {}
        // new People(){
        // @Override
        // public void hi() {
        // System.out.println("匿名内部类重写了 hi 方法,哈哈...");
        // }
        // @Override
        // public void ok(String str) {
        // super.ok(str);
        // }
        // }.ok("jack");
    }
}

class People {
    public void hi() {
        System.out.println("People hi()");
    }
    public void ok(String str) {
        System.out.println("People ok() " + str);
    }
}

匿名内部类的最佳实践

package hsp.innerclass;

public class InnerClassExercise01 {
    public static void main(String[] args) {
        //当做实参直接传递,简洁高效
        f1(new IL() {
            @Override
            public void show() {
                System.out.println("这是一副名画~~...");
            }
        });
        
        //传统方法
        f1(new Picture());
    }

    //静态方法,形参是接口类型
    public static void f1(IL il) {
        il.show();
    }
}

//接口
interface IL {
    void show();
}

//类->实现 IL => 编程领域 (硬编码)
class Picture implements IL {
    @Override
    public void show() {
        System.out.println("这是一副名画 XX...");
    }
}

成员内部类的使用

说明:成员内部类是定义在外部类的成员位置,并且没有static修饰

1)可以直接访问外部类的所有成员,包含私有的
2) 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
3) 作用域
java和外部类的其他成员一样,为整个类体比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法.
4) 成员内部类—访问---->外部类成员(比如:属性)[访问方式:直接访问]
5)外部类—访问------>成员内部类(说明)访问方式:创建对象,再访问
6)外部其他类—访问---->成员内部类
7)如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

代码演示:

package hsp.innerclass;

public class AnonymousInnerClass03 {
    public static void main(String[] args) {
        Outer08 outer08 = new Outer08();
        outer08.t1();
        //外部其他类,使用成员内部类的三种方式
        //老韩解读
        // 第一种方式
        // outer08.new Inner08(); 相当于把 new Inner08()当做是 outer08 成员
        // 这就是一个语法,
        Outer08.Inner08 inner08 = outer08.new Inner08();
        inner08.say();
        // 第二方式 在外部类中,编写一个方法,可以返回 Inner08 对象
        Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
        inner08Instance.say();
    }
}

class Outer08 { //外部类
    private int n1 = 666;
    public String name = "codeSE";

    private void hi() {
        System.out.println("hi()方法...");
    }

    //1.注意: 成员内部类,是定义在外部内的成员位置上
    //2.可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
    public class Inner08 {//成员内部类
        private double sal = 12.34;
        private int n1 = 999;

        public void say() {
            //可以直接访问外部类的所有成员,包含私有的
            //如果成员内部类的成员和外部类的成员重名,会遵守就近原则.
            // 可以通过 外部类名.this.属性 来访问外部类的成员
            System.out.println("n1 = " + n1 + " name = " + name + " 外部类的 n1=" + Outer08.this.n1);
            hi();
        }
    }

    //方法,返回一个 Inner08 实例
    public Inner08 getInner08Instance() {
        return new Inner08();
    }

    //写方法
    public void t1() {
        //使用成员内部类
        //创建成员内部类的对象,然后使用相关的方法
        Inner08 inner08 = new Inner08();
        inner08.say();
        System.out.println(inner08.sal);
    }
}

静态内部类的使用

说明:静态内部类是定义在外部类的成员位置,并且有static修饰
1)可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
2)可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
3)作用域:同其他的成员,为整个类体
4)静态内部类—访问---->外部类(比如:静态属性)[访问方式:直接访问所有态成员]
5)外部类—访问------>静态内部类访问方式:创建对象,再访问
6)外部其他类—访问----->静态内部类
7)如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问

代码:

package hsp.innerclass;

public class StaticInnerClass01 {
    public static void main(String[] args) {
        Outer10 outer10 = new Outer10();
        outer10.m1();

        //外部其他类 使用静态内部类
        //方式 1
        //因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
        Outer10.Inner10 inner10 = new Outer10.Inner10();
        inner10.say();
        //方式 2
        //编写一个方法,可以返回静态内部类的对象实例.
        Outer10.Inner10 inner101 = outer10.getInner10();
        System.out.println("============");
        inner101.say();
        Outer10.Inner10 inner10_ = Outer10.getInner10_();
        System.out.println("************");
        inner10_.say();

        /*输出
        * 教育你一下 外部类 name= 小玉玉
        * 教育你一下 外部类 name= 小玉玉
        * ============
        * 教育你一下 外部类 name= 小玉玉
        * ************
        * 教育你一下 外部类 name= 小玉玉
        * */
    }
}

class Outer10 { //外部类
    private int n1 = 10;
    private static String name = "小玉玉";

    private static void cry() {
    }

    static class Inner10 {
        private static String name = "教育你一下";

        public void say() {
            //如果外部类和静态内部类的成员重名时,静态内部类访问的时,
            //默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.成员)
            System.out.println(name + " 外部类 name= " + Outer10.name);
            cry();
        }
    }

    public void m1() { //外部类---访问------>静态内部类 访问方式:创建对象,再访问
        Inner10 inner10 = new Inner10();
        inner10.say();
    }

    public Inner10 getInner10() {
        return new Inner10();
    }

    public static Inner10 getInner10_() {
        return new Inner10();
    }
}

第6章 枚举和注解

基本介绍:
1)枚举对应英文(enumeration,简写enum)
2)枚举是一组常量的集合。
3)可以这里理解:枚举属于一种特殊的类,里面只包含一组有限的特定的对象。

枚举的二种实现方式
1)自定义类实现枚举
2)使用enum关键字实现枚举

自定义类实现枚举

1.不需要提供setXxx方法,因为枚举对象值通常为只读.
2.对枚举对象/属性使用final + static 共同修饰,实现底层优化.
3.枚举对象名通常使用全部大写,常量的命名规范.
4.枚举对象根据需要,也可以有多个属性
代码:

package hsp.enum_;

public class Enumeration01 {
    public static void main(String[] args) {
        System.out.println(Season.AUTUMN);
        System.out.println(Season.SPRING);
    }
}

//演示字定义枚举实现
class Season {//类
    private String name;
    private String desc;//描述
    //定义了四个对象, 固定.
    public static final Season SPRING = new Season("春天", "温暖");
    public static final Season WINTER = new Season("冬天", "寒冷");
    public static final Season AUTUMN = new Season("秋天", "凉爽");
    public static final Season SUMMER = new Season("夏天", "炎热");

    //1. 将构造器私有化,目的防止 直接 new
    //2. 去掉 setXxx 方法, 防止属性被修改
    //3. 在 Season 内部,直接创建固定的对象
    //4. 优化,可以加入 final 修饰符
    private Season(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }

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

小结:
进行自定义类实现枚举,有如下特点:

1)构造器私有化
2)本类内部创建一组对象[四个春夏秋冬]
3)对外暴露对象(通过为对象添加 public final static修饰符)4)可以提供get方法,但是不要提供 set

enum 关键字实现枚举

代码:

package hsp.enum_;

public class Enumeration02 {
    public static void main(String[] args) {
        System.out.println(Season2.AUTUMN);
    }
}
//使用 enum 关键字来实现枚举类
enum Season2 {//类

    //使用了 enum 来实现枚举类
    //1. 使用关键字 enum 替代 class
    //2. public static final Season SPRING = new Season("春天", "温暖") 直接使用
    // SPRING("春天", "温暖") 解读 常量名(实参列表)
    //3. 如果有多个常量(对象), 使用 ,号间隔即可
    //4. 如果使用 enum 来实现枚举,要求将定义常量对象,写在前面
    //5. 如果我们使用的是无参构造器,创建常量对象,则可以省略 ()
    SPRING("春天", "温暖"),WINTER("冬天", "寒冷"),
    AUTUMN("秋天", "凉爽"),SUMMER("夏天", "炎热"),WHAT();

    private String name;
    private String desc;//描述
    Season2(){//无参构造器

    }
    private Season2(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }

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

enum关键字实现枚举注意事项

l)当我们使用enum关键字开发一个枚举类时,默认会继承Enum类,而且是一个final类[如何证明],使用javap 工
具来演示
2)传统的 public static final Season2 SPRING = new Season2(“春天”, “温暖”);简化成 SPRING(“春天”, “温暖”),这里必须知道,它调用的是哪个构造器.
3)如果使用无参构造器创建枚举对象,则实参列表和小括号都可以省略
4)当有多个枚举对象时,使用,间隔,最后有一个分号结尾
5)枚举对象必须放在枚举类的行首.
在这里插入图片描述

enum 常用方法说明

说明:使用关键字 enum 时,会隐式继承 Enum 类, 这样我们就可以使用 Enum 类相关的方法。[
在这里插入图片描述

  1. toString:Enum 类已经重写过了,返回的是当前对象
    名,子类可以重写该方法,用于返回对象的属性信息
  2. name:返回当前对象名(常量名),子类中不能重写
  3. ordinal:返回当前对象的位置号,默认从 0 开始
  4. values:返回当前枚举类中所有的常量
  5. valueOf:将字符串转换成枚举对象,要求字符串必须
    为已有的常量名,否则报异常!
  6. compareTo:比较两个枚举常量,比较的就是编号!
package hsp.enum_;

public class Enumeration03 {
    public static void main(String[] args) {
        Season3 season3 = Season3.AUTUMN;

        //输出枚举对象的名字
        System.out.println(season3.name());//AUTUMN

        //ordinal() 输出的是该枚举对象的次序/编号,从 0 开始编号
        System.out.println(season3.ordinal());//2

        //从反编译可以看出 values 方法,返回 Season3[]
        System.out.println(season3.hashCode());//10568834
        Season3[] values = season3.values();
        System.out.println("===遍历取出枚举对象(增强 for)====");
        for (Season3 season: values) {//增强 for 循环
            System.out.println(season);
        }

        //valueOf:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常
        //执行流程
        //1. 根据你输入的 "AUTUMN" 到 Season2 的枚举对象去查找
        //2. 如果找到了,就返回,如果没有找到,就报错
        Season3 autumn1 = Season3.valueOf("AUTUMN");
        System.out.println("autumn1=" + autumn1);//autumn1=Season{name='秋天', desc='凉爽'}
        System.out.println(season3 == autumn1);//true

        //compareTo:比较两个枚举常量,比较的就是编号
        //Season3.AUTUMN - Season3.SPRING ,编号相减
        System.out.println(Season3.AUTUMN.compareTo(Season3.SPRING));//2
    }
}
//enum 常用方法应用实例
enum Season3 {//类
    SPRING("春天", "温暖"),WINTER("冬天", "寒冷"),
    AUTUMN("秋天", "凉爽"),SUMMER("夏天", "炎热"),WHAT();

    private String name;
    private String desc;//描述
    Season3(){//无参构造器

    }
    private Season3(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }

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

enum 实现接口

1)使用enum关键字后,就不能再继承其它类了,因为enum 会隐式继承Enum,而Java是单继承机制。
2)枚举类和普通类一样,可以实现接口,如下形式。
enum 类名 implements 接口1,接口2

package hsp.enum_;

public class Enumeration04 {
    public static void main(String[] args) {
        Music.CLASSICMUSIC.playing();
    }
}

class A {
}

//1.使用 enum 关键字后,就不能再继承其它类了,因为 enum 会隐式继承 Enum,而 Java 是单继承机制
//enum Season3 extends A {
//
//}
//2.enum 实现的枚举类,仍然是一个类,所以还是可以实现接口的.
interface IPlaying {
    public void playing();
}

enum Music implements IPlaying {
    CLASSICMUSIC;

    @Override
    public void playing() {
        System.out.println("播放好听的音乐...");
    }
}

注解

11.12注解的理解
l)注解(Annotation)也被称为元数据(Metadata),用于修饰解释包、类、方法、属性、构造器、局部变量等数据信息。
2)和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。
3)在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在.JavaEE中注解占据了更重要的角
色,例如用来配置应用程序的任何切面,代替java EE旧版中所遗留的繁冗代码和XML配置等。

基本的 Annotation介绍
使用Annotation时要在其前面增加@符号,并把该Annotation当成一个修饰符使用。用于修饰它支持的程序元素
三个基本的 Annotation:

  1. @Override:限定某个方法,是重写父类方法,该注解只能用于方法
    2)@Deprecated:用于表示某个程序元素(类,方法等)已过时
  2. @SuppressWarnings: 抑制编译器警告
package hsp.annotation_;

public class Override01 {
}
class Father{//父类
    public void fly(){
        System.out.println("Father fly...");
    }
    public void say(){}
}
class Son extends Father{
    //1. @Override 注解放在 fly 方法上,表示子类的 fly 方法时重写了父类的 fly
    //2. 这里如果没有写 @Override 还是重写了父类 fly
    //3. 如果你写了@Override 注解,编译器就会去检查该方法是否真的重写了父类的
    // 方法,如果的确重写了,则编译通过,如果没有构成重写,则编译错误
    @Override
    public void fly(){

    }
}

Override使用说明
1.@Override表示指定重写父类的方法(从编译层面验证),如果父类没有fly方法,则会报错
2.如果不写@Override注解,而父类仍有public void fly00,仍然构成重写3.@Override只能修饰方法,不能修饰其它类,包,属性等等
4.查看@Override注解源码为@Target(ElementType.METHOD),说明只能修饰方法
5.@Target是修饰注解的注解,称为元注解,记住这个概念.

@Deprecated: 用于表示某个程序元素(类, 方法等)已过时

@Deprecated的说明
1.用于表示某个程序元素(类,方法等)已过时2.可以修饰方法,类,字段,包,参数等等
3.@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD,
PACKAGE, PARAMETER, TYPE))
4.@Deprecated的作用可以做到新旧版本的兼容和过渡

package hsp.annotation_;

public class Annotationo1 {
    public static void main(String[] args) {
        A a = new A();
        a.hi();
    }
}
//1. @Deprecated 修饰某个元素, 表示该元素已经过时
//2. 即不在推荐使用,但是仍然可以使用
//3. 查看 @Deprecated 注解类的源码
@Deprecated
class A {
    @Deprecated
    public int n1 = 10;
    @Deprecated
    public void hi(){
    }
}

@SuppressWarnings: 抑制编译器警告

@SuppressWarnings 注解的案例

  1. unchecked是忽略没有检查的警告
  2. rawtypes是忽略没有指定泛型的警告(传参时没有指定泛型的警告错误)
  3. unused是忽略没有使用某个变量的警告错误
  4. @SuppressWarnings可以修饰的程序元素为,查看@Target
    5)生成@SupperssWarnings 时,不用背,直接点击左侧的黄色提示,就
    可以选择(注意可以指定生成的位置)
package hsp.annotation_;

import java.util.ArrayList;
import java.util.List;

//1. 当我们不希望看到这些警告的时候,可以使用 SuppressWarnings 注解来抑制警告信息
//2. 在{""} 中,可以写入你希望抑制(不显示)警告信息
//3. 可以指定的警告类型有:
 //all,抑制所有警告
 //boxing,抑制与封装/拆装作业相关的警告
 //cast,抑制与强制转型作业相关的警告
 //dep-ann,抑制与淘汰注释相关的警告
 //deprecation,抑制与淘汰的相关警告
 //fallthrough,抑制与 switch 陈述式中遗漏 break 相关的警告
 //finally,抑制与未传回 finally 区块相关的警告
 //hiding,抑制与隐藏变数的区域变数相关的警告
 //incomplete-switch,抑制与 switch 陈述式(enum case)中遗漏项目相关的警告
 //javadoc,抑制与 javadoc 相关的警告
 //nls,抑制与非 nls 字串文字相关的警告
 //null,抑制与空值分析相关的警告
 //rawtypes,抑制与使用 raw 类型相关的警告
 //resource,抑制与使用 Closeable 类型的资源相关的警告
 //restriction,抑制与使用不建议或禁止参照相关的警告
 //serial,抑制与可序列化的类别遗漏 serialVersionUID 栏位相关的警告
 //static-access,抑制与静态存取不正确相关的警告
 //static-method,抑制与可能宣告为 static 的方法相关的警告
 //super,抑制与置换方法相关但不含 super 呼叫的警告
 //synthetic-access,抑制与内部类别的存取未最佳化相关的警告
 //sync-override,抑制因为置换同步方法而遗漏同步化的警告
 //unchecked,抑制与未检查的作业相关的警告
 //unqualified-field-access,抑制与栏位存取不合格相关的警告
 //unused,抑制与未用的程式码及停用的程式码相关的警告
//4. 关于 SuppressWarnings 作用范围是和你放置的位置相关
// 比如 @SuppressWarnings 放置在 main 方法,那么抑制警告的范围就是 main
// 通常我们可以放置具体的语句, 方法, 类.
//@SuppressWarnings({"all"})
public class Annotation02 {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("jack");
        list.add("tom");
        list.add("mary");
        int i;
        System.out.println(list.get(1));
    }
    public void f1() {
         @SuppressWarnings({"rawtypes"})
        List list = new ArrayList();
        list.add("jack");
        list.add("tom");
        list.add("mary");
        // @SuppressWarnings({"unused"})
        int i;
        System.out.println(list.get(1));
    }
}

JDK5 的元 Annotation(元注解, 了解)

第7章 异常

1 基本概念

Java语言中,将程序执行中发生的不正常情况称为“异常"。(开发过程中的语法错误和逻辑错误不是异常)

●执行过程中所发生的异常事件可分为两大类

  1. Error(错误): Java虚拟机无法解决的严重问题。如: JVM系统内部错误、资源耗尽等严重情况。比如: StackOverflowError[栈溢出]和OOM(out of
    memory), Error 是严重错误,程序会崩溃。
  2. Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等,Exception分为两大类:运行时异常[程序运行时,发生的异常]和编译时异常[编程时,编译器检查出的异常]。
package hsp.exception_;

public class Exception01 {
    public static void main(String[] args) {
        int num1 = 10;
        int num2 = 0;
        //int res = num1 / num2;
        //System.out.println("程序继续运行....");

        try {
            int res = num1 / num2;
        } catch (Exception e) {
            //e.printStackTrace();
            System.out.println("出现异常的原因:" + e.getMessage());//输出异常信息
        }
        System.out.println("程序继续运行....");
    }
}

异常体系图:
在这里插入图片描述
小结:
1.异常分为两大类:运行时异常和编译时异常.
2.运行时异常,编译器检查不出来。一般是指编程时的逻辑错误,是程序员应该避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常
3.对于运行时异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响
4.编译时异常,是编译器要求必须处置的异常。

2.运行时异常

2.1 常见的运行时异常包括

  1. NullPointerException 空指针异常
  2. ArithmeticException 数学运算异常
  3. ArrayIndexOutOfBoundsException 数组下标越界异常
  4. ClassCastException 类型转换异常
  5. NumberFormatException 数字格式不正确异常[]

常见的运行时异常举例:

package hsp.exception_;

public class Exception02 {
    public static void main(String[] args) {
        //1) NullPointerException 空指针异常
        String name = null;
        System.out.println(name.length());

        //2) ArithmeticException 数学运算异常
        String desc = "描述信息";
        int n = Integer.parseInt(desc);
        System.out.println(n);

        //3) ArrayIndexOutOfBoundsException 数组下标越界异常
        //用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引
        int[] arr = {1,2,4};
        for (int i = 0; i <= arr.length; i++) {
            System.out.println(arr[i]);
        }

        //4) ClassCastException 类型转换异常
        //当试图将对象强制转换为不是实例的子类时,抛出该异常。
        A b = new B(); //向上转型
        B b2 = (B)b;//向下转型,这里是 OK
        C c2 = (C)b;//这里抛出 ClassCastException

        //5) NumberFormatException 数字格式不正确异常
        //当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,
        // 抛出该异常 => 使用异常我们可以确保输入是满足条件数字
        String names = "中国";
        //将 String 转成 int
        int num = Integer.parseInt(names);//抛出 NumberFormatException
        System.out.println(num);//1234


    }
}

class A {}
class B extends A {}
class C extends A {}

3. 编译异常

编译异常是指在编译期间,就必须处理的异常,否则代码不能通过编译。
3.2常见的编译异常

SQLException//操作数据库时,查询表可能发生异常IOException//操作文件时,发生的异常
FileNotFoundException //当操作一个不存在的文件时,发生异常
ClassNotFoundException//加载类,而该类不存在时,异常
EOFException//操作文件,到文件未尾,发生异常
IllegalArguementException//参数异常

案例说明

package hsp.exception_;

import java.io.FileInputStream;
import java.io.IOException;

public class Exception03 {
    public static void main(String[] args) {
        try {
            FileInputStream fis;
            fis = new FileInputStream("d:\\1.jpg");
            int len;
            while ((len = fis.read())!=-1){
                System.out.println(len);
            }
            fis.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
}

4.异常处理

基本介绍
异常处理就是当异常发生时,对异常处理的方式。
异常处理的方式

  1. try-catch-finally
    程序员在代码中捕获发生的异常,自行处理
  2. throws
    将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM
    在这里插入图片描述在这里插入图片描述
    12.9 try-catch异常处理
    12.9.1 try-catch方式处理异常说明

1)Java提供try和catch块来处理异常。try块用于包含可能出错的代码。catch块用于处理try块中发生的异常。可以根据需要在程序中有多个try…catch块。
2)基本语法

try {
//可疑代码
//将异常生成对应的异常对象,传递给catch块
}
catch(异常){
//对异常的处理
}
//如果没有finally,语法是可以通过

注意细节:
1)如果异常发生了,则异常发生后面的代码不会执行,直接进入到catch块.
2)如果异常没有发生,则顺序执行try的代码块,不会进入到catch.
3)如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等),则使用- finally {}

package hsp.try_catch_;

public class TryCatch01 {
    public static void main(String[] args) {
        //1. 如果异常发生了,则异常发生后面的代码不会执行,直接进入到 catch 块
        //2. 如果异常没有发生,则顺序执行 try 的代码块,不会进入到 catch
        //3. 如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等)则使用如下代码- finally
        try {
            String name = "中华名族";
            int n = Integer.parseInt(name);
            System.out.println("转换为:"+n);
        } catch (NumberFormatException e) {
            System.out.println("输出的异常信息:"+e.getMessage());// 输出的异常信息:For input string: "中华名族"

        }finally {
            System.out.println("finally部分的执行.");
        }

        System.out.println("程序继续...");
    }
}

4)可以有多个catch语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前,比如(Exception在后,NullPointerException在前),如果发生异常,只会匹配一个catch.

package hsp.try_catch_;

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

        //1.如果 try 代码块有可能有多个异常
        //2.可以使用多个 catch 分别捕获不同的异常,相应处理
        //3.要求子类异常写在前面,父类异常写在后面
        try {
            Person1 person = new Person1();
            //person = null;
            System.out.println(person.getName());//NullPointerException
            int n1 = 10;
            int n2 = 0;
            int res = n1 / n2;//ArithmeticException
        } catch (NullPointerException e) {
            System.out.println("空指针异常:" + e.getMessage());
        } catch (ArithmeticException e) {
            System.out.println("算术异常:" + e.getMessage());
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
        }
    }
}

class Person1 {
    String name = "codeSE";

    public String getName() {
        return name;
    }
}

5)可以进行try-finally 配合使用,这种用法相当于没有捕获异常,因此程序会直接崩掉/退出。应用场景,就是执行一段代码,不管是否发生异常,都必须执行某个业务逻辑

try{
	int n1 = 10;
	int n2 = 0;
	System.out.println(n1 / n2);
}finally {
	System.out.println("执行了 finally..");
}
System.out.println("程序继续执行..");

try-catch-finally执行顺序小结:

1)如果没有出现异常,则执行try块中所有语句,不执行catch块中语句,如果有finally,最后还需要执行finally里面的语句
2)如果出现异常,则try块中异常发生后,try块剩下的语句不再执行。将执行catch块中的语句,如果有finally,最后还需要执行finally里面的语句!

package hsp.try_catch_;

import java.util.Scanner;

public class TryCatch03 {
    public static void main(String[] args) {
        //如果用户输入的不是一个整数,就提示他反复输入,直到输入一个整数为止
        //思路
        //1. 创建 Scanner 对象
        //2. 使用无限循环,去接收一个输入
        //3. 然后将该输入的值,转成一个 int
        //4. 如果在转换时,抛出异常,说明输入的内容不是一个可以转成 int 的内容
        //5. 如果没有抛出异常,则 break 该循环
        Scanner scanner = new Scanner(System.in);
        int num = 0;
        String inputStr = "";
        while (true) {
            System.out.println("请输入一个整数:"); //
            inputStr = scanner.next();
            try {
                num = Integer.parseInt(inputStr); //这里是可能抛出异常
                break;
            } catch (NumberFormatException e) {
                System.out.println("你输入的不是一个整数:");
            }
        }
        System.out.println("你输入的值是=" + num);
    }
}

5.throws异常处理

5.1基本介绍
1)如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
2)在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。

package hsp.throws_;

import java.io.FileInputStream;
import java.io.FileNotFoundException;

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

    }
    //文件流对象
    // FileNotFoundException 编译异常
    //使用 throws 抛出异常,让调用f1方法的调用者处理
    //throws 后面的异常类可以是方法中产生的异常类型,也可以是他的父类
    //throws 后面也可以是异常列表,即可以抛出多个异常
    public void f1() throws FileNotFoundException {
        FileInputStream fileInputStream = new FileInputStream("d://1.png");
    }
}

注意事项和使用细节

package hsp.throws_;

import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class Throws02 {
    public static void main(String[] args) {
        f2();
    }

    public static void f2() /*throws ArithmeticException*/ {
    //1.对于编译异常,程序中必须处理,比如 try-catch 或者 throws
    //2.对于运行时异常,程序中如果没有处理,默认就是 throws 的方式处理
        int n1 = 10;
        int n2 = 0;
        double res = n1 / n2;
    }

    public static void f1() throws FileNotFoundException {

        //1. 因为 f3() 方法抛出的是一个编译异常
        //2. 即这时,就要 f1() 必须处理这个编译异常
        //3. 在 f1() 中,要么 try-catch-finally ,或者继续 throws 这个编译异常
        f3(); // 抛出异常
    }

    public static void f3() throws FileNotFoundException {
        FileInputStream fis = new FileInputStream("d://aa.txt");
    }

    public static void f4() {

    //1. 在 f4()中调用方法 f5() 是 OK
    //2. 原因是 f5() 抛出的是运行异常
    //3. 而 java 中,并不要求程序员显示处理,因为有默认处理机制
        f5();
    }

    public static void f5() throws ArithmeticException {
    }
}

class Father { //父类
    public void method() throws RuntimeException {
    }
}

class Son extends Father {//子类

    //3. 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,
    // 所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常类型的子类型
    //4. 在 throws 过程中,如果有方法 try-catch , 就相当于处理异常,就可以不必 throws
    @Override
    public void method() throws ArithmeticException {
    }
}

6.自定义异常

基本概念
当程序中出现了某些“错误”,但该错误信息并没有在Throw
able子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息
12.11.2自定义异常的步骤
1)定义类:自定义异常类名(程序员自己写)继承Exception或RuntimeException
2)如果继承Exception,属于编译异常
3)如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException)

package hsp.throws_;

public class Throws03 {
    public static void main(String[] args) /*throws AgeException*/ {
        int age = 180;
        //要求范围在 18 – 120 之间,否则抛出一个自定义异常
        if(!(age >= 18 && age <= 120)) {
            //通过构造器,设置信息
            throw new AgeException("年龄需要在 18~120 之间");
        }
        System.out.println("你的年龄范围正确.");
    }
}
//自定义一个异常
//1. 一般情况下,我们自定义异常是继承 RuntimeException
//2. 即把自定义异常做成 运行时异常,好处时,我们可以使用默认的处理机制
//3. 即比较方便
class AgeException extends RuntimeException {
    public AgeException(String message) {//构造器
        super(message);
    }
}

throw 和 throws 的区别
在这里插入图片描述

第8章 常用类

1.包装类

1.1包装类的分类
1)针对八种基本数据类型相应的引用类型—包装类
2)有了类的特点,就可以调用类中的方法。

基本数据类型包装类
booleanBoolean
charCharacter
bpteByte
shortShort
intBoolean
longBoolean
floatBoolean
doubleDouble

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.2 包装类和基本数据的转换

1)jdk5前的手动装箱和拆箱方式,装箱:基本类型->包装类型,反之,拆箱
2)jdk5 以后(含jdk5)的自动装箱和拆箱方式
3)自动装箱底层调用的是valueOf方法,比如Integer.valueOf

package hsp.wrapperType;

public class Integer01 {
    public static void main(String[] args) {
        //int <--> Integer 的装箱和拆箱
        //jdk5 前是手动装箱和拆箱
        //手动装箱 int->Integer
        int n1 = 100;
        Integer integer = new Integer(n1);
        Integer integer1 = Integer.valueOf(n1);
        //手动拆箱
        //Integer -> int
        int i = integer.intValue();
        
        
        //jdk5 后,就可以自动装箱和自动拆箱
        int n2 = 200;
        //自动装箱 int->Integer
        Integer integer2 = n2; //底层使用的是 Integer.valueOf(n2)
        //自动拆箱 Integer->int
        int n3 = integer2; //底层仍然使用的是 intValue()方法
    }
}

package hsp.wrapperType;
//其他的数据类型转换类似,现以包装类型和 String 类型的相互转换为例
public class WrapperVSString {
    public static void main(String[] args) {
        //包装类(Integer)->String
        Integer i = 100;//自动装箱

        //方式 1
        String str1 = i + "";
        //方式 2
        String str2 = i.toString();
        //方式 3
        String str3 = String.valueOf(i);

        //String -> 包装类(Integer)
        String str4 = "12345";
        Integer i2 = Integer.parseInt(str4);//使用到自动装箱
        Integer i3 = new Integer(str4);//构造器

        System.out.println("ok~~");
    }
}

1.2 Integer 类和 Character 类的常用方法

package hsp.wrapperType;

public class WrapperMethod {
    public static void main(String[] args) {
        System.out.println(Integer.MIN_VALUE); //返回最小值
        System.out.println(Integer.MAX_VALUE);//返回最大值
        System.out.println(Character.isDigit('a'));//判断是不是数字
        System.out.println(Character.isLetter('a'));//判断是不是字母
        System.out.println(Character.isUpperCase('a'));//判断是不是大写
        System.out.println(Character.isLowerCase('a'));//判断是不是小写
        System.out.println(Character.isWhitespace('a'));//判断是不是空格
        System.out.println(Character.toUpperCase('a'));//转成大写
        System.out.println(Character.toLowerCase('A'));//转成小写
    }
}

1.3 String 类

  1. String对象用于保存字符串,也就是一组字符序列
    2)字符串常量对象是用双引号括起的字符序列。例如:“你好”、“12.97"、"boy"等
    3)字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字节4) String类较常用构造器(其它看手册);
    在这里插入图片描述
package hsp.string_;

public class String01 {
    public static void main(String[] args) {
        //1.String 对象用于保存字符串,也就是一组字符序列
        //2. "codeSE" 字符串常量, 双引号括起的字符序列
        //3. 字符串的字符使用 Unicode 字符编码,一个字符(不区分字母还是汉字)占两个字节
        //4. String 类有很多构造器,构造器的重载,  常用的有 :
        // String s1 = new String();
        //String s2 = new String(String original);
        //String s3 = new String(char[] a);
        //String s4 = new String(char[] a,int startIndex,int count)
        //String s5 = new String(byte[] b)

        //5. String 类实现了接口 Serializable【String 可以串行化:可以在网络传输】
        // 接口 Comparable [String 对象可以比较大小]

        //6. String 是 final 类,不能被其他的类继承
        //7. String 有属性 private final char value[]; 用于存放字符串内容
        //8. 一定要注意:value 是一个 final 类型, 不可以修改:即 value 不能指向
        // 新的地址,但是单个字符内容是可以变化
        String name = "codeSE";
        name = "tom";
        final char[] value = {'a','b','c'};
        char[] v2 = {'t','o','m'};
        value[0] = 'H';
        //value = v2; 不可以修改 value 地址
    }
}

创建String 对象的两种方式:
1)方式一:直接赋值String s = “codeSE”;
2)方式二:调用构造器 String s = new String(“codeSE”);

两种创建String 对象的区别:
方式一:直接赋值 String s = “codeSE”;
方式二:调用构造器 String s2 = new String(“codeSE”);
1.方式一:先从常量池查看是否有"codeSE"数据空间,如果有,直接指向;如果没有则重新创建,然后指向。S最终指向的是常量池的空间地址
2.方式二:先在堆中创建空间,里面维护了value属性,指向常量池的hsp空间。如果常量池没有"codeSE",重新创建,如果有,直接通过value指向。最终指向的是堆中的空间地址。

在这里插入图片描述
1) String是个final类, 代表不可变的字符序列 2)字符串是不可变的。一个字符串对象一-旦被分配,其内容是不可变的

1.4 String 类的常见方法

String类是保存字符串常量的。每次更新都需要重新开辟空间,效率较低,因
此java设计者还提供了StringBuilder和StringBuffer来增强String的功能,
并提高效率。

方法名功能描述
equals区分大小写,判断内容是否相等
equalslgnoreCase忽略大小写的判断内容是否相等
length获取字符的个数,字符串的长度
indexOf获取字符在字符串中第1次出现的索引索引从开始,如果找不到,返回-1
lastIndexOf获取字符在字符串中最后1次出现的索引,索引从开始,如找不到,返回-1
substring截取指定范围的子串
trim去前后空格
charAt获取某索引处的字符,注意不能使用Str[index]这种方式
package hsp.string_;

public class String02 {
    public static void main(String[] args) {
        //1. equals  比较内容是否相同,区分大小写
        String str1 = "hello";
        String str2 = "Hello";
        System.out.println(str1.equals(str2));//false

        // 2.equalsIgnoreCase 忽略大小写的判断内容是否相等
        String username = "johN";
        if ("john".equalsIgnoreCase(username)) {
            System.out.println("Success!");
        } else {
            System.out.println("Failure!");
        }
        // 3.length 获取字符的个数,字符串的长度
        System.out.println("这个人".length());//3

        // 4.indexOf 获取字符在字符串对象中第一次出现的索引,索引从 0 开始,如果找不到,返回-1
        String s1 = "wer@terwe@g";
        int index = s1.indexOf('@');
        System.out.println(index);// 3
        System.out.println("weIndex=" + s1.indexOf("we"));//0

        // 5.lastIndexOf 获取字符在字符串中最后一次出现的索引,索引从 0 开始,如果找不到,返回-1
        s1 = "wer@terwe@g@";
        index = s1.lastIndexOf('@');
        System.out.println(index);//11
        System.out.println("ter 的位置=" + s1.lastIndexOf("ter"));//4

        // 6.substring 截取指定范围的子串
        String name = "hello,张三";
        //下面 name.substring(6) 从索引 6 开始截取后面所有的内容
        System.out.println(name.substring(6));//张三
        //name.substring(0,5)表示从索引 0 开始截取,截取到索引 5-1=4 位置
        System.out.println(name.substring(2, 5));//llo
    }
}

方法描述功能
toUpperCase转成大写
toLowerCase转成小写
concat拼接字符串
replace替换字符串中的字符
split分割字符串,对于某些分割字符,我们需要转义\\等
compareTo比较两个字符串的大小
toCharArray转换成字符数组
format格式字符串:%s字符串,%c字符,%d整型,%2f浮占型
package hsp.string_;

public class String03 {
    public static void main(String[] args) {
        // 1.toUpperCase 转换成大写
        String s = "heLLo";
        System.out.println(s.toUpperCase());//HELLO
        
        // 2.toLowerCase
        System.out.println(s.toLowerCase());//hello
        
        // 3.concat 拼接字符串
        String s1 = "宝玉";
        s1 = s1.concat("林黛玉").concat("薛宝钗").concat("together");
        System.out.println(s1);//宝玉林黛玉薛宝钗 together
        
        // 4.replace 替换字符串中的字符
        s1 = "宝玉 and 林黛玉 林黛玉 林黛玉";
        //在 s1 中,将所有的 林黛玉 替换成  薛宝钗
        // 解读: s1.replace() 方法执行后,返回的结果才是替换过的. 
        // 注意对 s1 没有任何影响
        String s11 = s1.replace("宝玉", "codeSE");
        System.out.println(s1);//宝玉 and 林黛玉 林黛玉 林黛玉
        System.out.println(s11);//codeSE and 林黛玉 林黛玉 林黛玉

        // 5.split 分割字符串, 对于某些分割字符,我们需要 转义比如 | \\等
        String poem = "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦";
        //解读:
        // 1. 以  , 为标准对 poem 进行分割 , 返回一个数组
        // 2. 在对字符串进行分割时,如果有特殊字符,需要加入 转义符 \
        String[] split = poem.split(",");
        poem = "E:\\aaa\\bbb";
        split = poem.split("\\\\");
        System.out.println("==分割后内容===");
        for (int i = 0; i < split.length; i++) {
            System.out.println(split[i]);
        }
        // 6.toCharArray
        s = "happy";
        char[] chs = s.toCharArray();
        for (int i = 0; i < chs.length; i++) {
            System.out.println(chs[i]);
        }
        // 7.compareTo 比较两个字符串的大小,如果前者大,
        // 则返回正数,后者大,则返回负数,如果相等,返回 0
        // 解读
        // (1) 如果长度相同,并且每个字符也相同,就返回 0
        // (2) 如果长度相同或者不相同,但是在进行比较时,可以区分大小
        // 就返回 if (c1 != c2) {
        // return c1 - c2;
        // }
        // (3) 如果前面的部分都相同,就返回 str1.len - str2.len
        String a = "jcck";// len = 3
        String b = "jack";// len = 4
        System.out.println(a.compareTo(b)); // 返回值是 'c' - 'a' = 2 的值
        // 8.format 格式字符串
        /* 占位符有:
         * %s 字符串 %c 字符 %d 整型 %.2f 浮点型
         *
         */
        String name = "codeSE";
        int age = 20;
        double score = 123.1513;
        char gender = '男';
        //将所有的信息都拼接在一个字符串.
        String info =
        "我的姓名是" + name + "年龄是" + age + ",成绩是" + score + "性别是" + gender + "。希望大家喜欢我! ";
        System.out.println(info);

        //解读
        //1. %s , %d , %.2f %c 称为占位符
        //2. 这些占位符由后面变量来替换
        //3. %s 表示后面由 字符串来替换
        //4. %d 是整数来替换
        //5. %.2f 表示使用小数来替换,替换后,只会保留小数点两位, 并且进行四舍五入的处理
        //6. %c 使用 char 类型来替换
        String formatStr = "我的姓名是%s 年龄是%d,成绩是%.2f 性别是%c.希望大家喜欢我!";
        String info2 = String.format(formatStr, name, age, score, gender);
        System.out.println(info2);
    }
}

1.5 StringBuffer

1.5.1基本介绍
● java.lang.StringBuffer代表可变的字符序列, 可以对字符串内容进行增删。
●很多方法与String相同,但StringBuffer是可变长度的。
●StringBuffer是一个容器。

1.5.2 String VS StringBuffer

  1. String保存的是字符串常量,里面的值不能更改,每次String类的更新实际
    上就是更改地址,效率较低//private final char value[];
  2. StringBuffer保存的是字符串变量,里面的值可以更改,每次
    StringBuffer的更新实际上可以更新内容,不用每次更新地址,效率较高
    //char[] value; // 这个放在堆.
package hsp.string_;

public class StringBuffer01 {
    public static void main(String[] args) {
        //解读
        //1. StringBuffer 的直接父类 是 AbstractStringBuilder
        //2. StringBuffer 实现了 Serializable, 即 StringBuffer 的对象可以串行化
        //3. 在父类中 AbstractStringBuilder 有属性 char[] value,不是 final
        // 该 value 数组存放 字符串内容,引出存放在堆中的
        //4. StringBuffer 是一个 final 类,不能被继承
        //5. 因为 StringBuffer 字符内容是存在 char[] value, 所有在变化(增加/删除)
        // 不用每次都更换地址(即不是每次创建新对象), 所以效率高于 String

        StringBuffer s1 = new StringBuffer("codeSE");
    }
}

String 和 StringBuffer 相互转换

package hsp.string_;

public class StringAndStringBuffer {
    public static void main(String[] args) {
        //看 String——>StringBuffe
        String str = "hello code";

        //方式 1 使用构造器
        //注意: 返回的才是 StringBuffer 对象,对 str 本身没有影响
        StringBuffer stringBuffer = new StringBuffer(str);
        //方式 2 使用的是 append 方法
        StringBuffer stringBuffer1 = new StringBuffer();
        stringBuffer1 = stringBuffer1.append(str);

        //看StringBuffer ->String
        StringBuffer stringBuffer3 = new StringBuffer("中国人");
        //方式 1 使用 StringBuffer 提供的 toString 方法
        String s = stringBuffer3.toString();
        //方式 2: 使用构造器来搞定
        String s1 = new String(stringBuffer3);
    }
}

1.6 StringBuffer 类常见方法

package hsp.string_;

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

        StringBuffer s = new StringBuffer("hello");

        //增
        s.append(',');// "hello,"
        s.append("张三丰");//"hello,张三丰"
        s.append("赵敏").append(100).append(true).append(10.5);//"hello,张三丰赵敏100true10.5"
        System.out.println(s);//"hello,张三丰赵敏100true10.5"
        //删
        /*
         * 删除索引为>=start && <end 处的字符
         * 解读: 删除 11~14 的字符 [11, 14)
         */
        s.delete(11, 14);
        System.out.println(s);//"hello,张三丰赵敏true10.5"
        //改
        //使用 周芷若 替换 索引 9-11 的字符 [9,11)
        s.replace(9, 11, "周芷若");
        System.out.println(s);//"hello,张三丰周芷若true10.5"

        //查找指定的子串在字符串第一次出现的索引,如果找不到返回-1
        int indexOf = s.indexOf("张三丰");
        System.out.println(indexOf);//6

        //插
        //在索引为 9 的位置插入 "赵敏",原来索引为 9 的内容自动后移
        s.insert(9, "赵敏");
        System.out.println(s);//"hello,张三丰赵敏周芷若true10.5"

        //长度
        System.out.println(s.length());//22
        System.out.println(s);//"hello,张三丰赵敏周芷若true10.5"
    }
}

1.7 StringBuilder 类

1.7.1基本介绍

1)一个可变的字符序列。此类提供个与StringBuffer兼容的API, 但不保证同
步(StringBuilder不是线程安全)。该类被设计用作StringBuffer的一个简易
替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer要快。
2)在StringBuilder上的主要操作是append和insert方法,可重载这些方法,
以接受任意类型的数据。

package hsp.string_;

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

        //解读:
        //1. StringBuffer 的直接父类 是 AbstractStringBuilder
        //2. StringBuffer 实现了 Serializable, 即 StringBuffer 的对象可以串行化
        //3. 在父类中 AbstractStringBuilder 有属性 char[] value,不是 final
        // 该 value 数组存放 字符串内容,引出存放在堆中
        //4. StringBuffer 是一个 final 类,不能被继承
        //5. 因为 StringBuffer 字符内容是存在 char[] value, 所有在变化(增加/删除)
        // 不用每次都更换地址(即不是每次创建新对象), 所以效率高于 String
        StringBuffer stringBuffer1 = new StringBuffer("hello");

        //1. StringBuilder 继承 AbstractStringBuilder 类
        //2. 实现了 Serializable ,说明 StringBuilder 对象是可以串行化(对象可以网络传输,可以保存到文件)
        //3. StringBuilder 是 final 类, 不能被继承
        //4. StringBuilder 对象字符序列仍然是存放在其父类 AbstractStringBuilder 的 char[] value;
        // 因此,字符序列是堆中
        //5. StringBuilder 的方法,没有做互斥的处理,即没有 synchronized 关键字,因此在单线程的情况下使用
        // StringBuilder
        StringBuilder stringBuilder2 = new StringBuilder();

    }
}


1.8 StringBuilder 常用方法

StringBuilder和StringBuffer均代表可变的字符序列,方法是一样的,所
以使用和StringBuffer一样,不重复演示.

1.9 String、 StringBuffer 和StringBuilder的比较

  1. StringBuilder和StringBuffer非常类似,均代表可变的字符序列,而且方法
    也一样
  2. String:不可变字符序列,效率低,但是复用率高。
  3. StringBuffer: 可变字符序列、效率较高(增删)、线程安全,看源码
  4. StringBuilder: 可变字符序列、效率最高、线程不安全
  5. String使用注意说明:
    string s= “a”; //创建了一个字符串
    s += “b”; //实际上原来的" a"字符串对象已经丢弃了,现在又产生了一个字符串s+ “b” (也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能。
    =>结论:如果我们对String做大量修改,不要使用String
package hsp.string_;

public class StringVsStringBufferVsStringBuilder {
    public static void main(String[] args) {
        long startTime = 0L;
        long endTime = 0L;

        StringBuffer buffer = new StringBuffer("");
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 80000; i++) {//StringBuffer 拼接 20000 次
            buffer.append(String.valueOf(i));
        }
        endTime = System.currentTimeMillis();
        System.out.println("StringBuffer 的执行时间:" + (endTime - startTime));//20ms

        StringBuilder builder = new StringBuilder("");
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 80000; i++) {//StringBuilder 拼接 20000
            builder.append(String.valueOf(i));
        }
        endTime = System.currentTimeMillis();
        System.out.println("StringBuilder 的执行时间:" + (endTime - startTime));//10ms

        String text = "";
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 80000; i++) {//String 拼接 20000
            text = text + i;
        }
        endTime = System.currentTimeMillis();
        System.out.println("String 的执行时间:" + (endTime - startTime));//4585ms
    }
}

String、 StringBuffer 和StringBuilder的选择
使用的原则,结论:
1.如果字符串存在大量的修改操作,-般使用StringBuffer或StringBuilder
2.如果字符串存在大量的修改操作,并在单线程的情况,使用StringBuilder
3.如果字符串存在大量的修改操作,并在多线程的情况,使用StringBuffer
4.如果我们字符串很少修改,被多个对象引用,使用String,比如配置信息等
StringBuilder 的方法使用和StringBuffer一 样.

2、Math类

2.1 基本介绍
Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。

package math_;

public class MathMethod01 {
    public static void main(String[] args) {
        //看看 Math 常用的方法(静态方法)
        //1.abs 绝对值
        int abs = Math.abs(-9);
        System.out.println(abs);//9

        //2.pow 求幂
        double pow = Math.pow(2, 4);//2 的 4 次方
        System.out.println(pow);//16

        //3.ceil 向上取整,返回>=该参数的最小整数(转成 double);
        double ceil = Math.ceil(3.9);
        System.out.println(ceil);//4.0

        //4.floor 向下取整,返回<=该参数的最大整数(转成 double)
        double floor = Math.floor(4.001);
        System.out.println(floor);//4.0

        //5.round 四舍五入 Math.floor(该参数+0.5)
        long round = Math.round(5.51);
        System.out.println(round);//6

         //6.sqrt 求开方
        double sqrt = Math.sqrt(9.0);
        System.out.println(sqrt);//3.0
        //7.random 求随机数
        // random 返回的是 0 <= x < 1 之间的一个随机小数
        // 思考:请写出获取 a-b 之间的一个随机整数,a,b 均为整数 ,比如 a = 2, b=7
        // 即返回一个数 x 2 <= x <= 7
        // Math.random() * (b-a) 返回的就是 0 <= 数 <= b-a
        // (1) (int)(a) <= x <= (int)(a + Math.random() * (b-a +1) )
        // (2) 使用具体的数给小伙伴介绍 a = 2 b = 7
        // (int)(a + Math.random() * (b-a +1) ) = (int)( 2 + Math.random()*6)


        //max , min 返回最大值和最小值
        int min = Math.min(1, 9);
        int max = Math.max(45, 90);
        System.out.println("min=" + min);//min=1
        System.out.println("max=" + max);//max=90
    }
}

3、Arrays

Arrays里面包含了一系列静态方法,用于管理或操作数组(比如排序和搜索)。

  1. toString返回数组的字符串形式
    Arrays.toString(arr)
  2. sort排序(自然排序和定制排序) Integer arr[] = {1, -1, 7, 0, 89};
  3. binarySearch通过二分搜索法进行查找,要求必须排好序
    int index = Arrays.binarySearch(arr, 3);
  4. copyOf数组元素的复制
    Integer[] newArr = Arrays.copyOf(arr, arr.length);
  5. fill数组元素的填充
    Integer[] num = new Integer[]{9,3,2};
    Arrays.fill(num, 99);
  6. equals比较两个数组元素内容是否完全-致
    boolean equals = Arrays.equals(arr, arr2);
  7. asList将一组值,转换成list
    List< Integer> asList = Arrays.asList(2,3,4,5,6,1);
    System.out.println(“asList=” + asList);
package hsp.arrays_;

import java.util.Arrays;
import java.util.Comparator;

public class ArraysMethod01 {
    public static void main(String[] args) {
        Integer[] integers = {1, 20, 90};
        //遍历数组
        // for(int i = 0; i < integers.length; i++) {
        // System.out.println(integers[i]);
        // }
        //直接使用 Arrays.toString 方法,显示数组
         System.out.println(Arrays.toString(integers));//[1, 20, 90]
        //演示 sort 方法的使用
        Integer arr[] = {1, -1, 7, 0, 89};
        //进行排序
        //老韩解读
        //1. 可以直接使用冒泡排序 , 也可以直接使用 Arrays 提供的 sort 方法排序
        //2. 因为数组是引用类型,所以通过 sort 排序后,会直接影响到 实参 arr
        //3. sort 重载的,也可以通过传入一个接口 Comparator 实现定制排序
        //4. 调用 定制排序 时,传入两个参数 (1) 排序的数组 arr
        // (2) 实现了 Comparator 接口的匿名内部类 , 要求实现 compare 方法
        //5. 先演示效果,再解释
        //6. 这里体现了接口编程的方式 , 看看
        // 源码分析
        //(1) Arrays.sort(arr, new Comparator()
        //(2) 最终到 TimSort 类的 private static <T> void binarySort(T[] a, int lo, int hi, int start, // Comparator<? super T> c)()
        //(3) 执行到 binarySort 方法的代码, 会根据动态绑定机制 c.compare()执行我们传入的
        // 匿名内部类的 compare ()
        // while (left < right) {
        // int mid = (left + right) >>> 1;
        // if (c.compare(pivot, a[mid]) < 0)
        // right = mid;
        // else
        // left = mid + 1;
        // }
        //(4) new Comparator() {
        // @Override
        // public int compare(Object o1, Object o2) {
        // Integer i1 = (Integer) o1;
        // Integer i2 = (Integer) o2;
        // return i2 - i1;
        // }
        // }
        //(5) public int compare(Object o1, Object o2) 返回的值>0 还是 <0
        // 会影响整个排序结果, 这就充分体现了 接口编程+动态绑定+匿名内部类的综合使用
        // 将来的底层框架和源码的使用方式,会非常常见
        //Arrays.sort(arr); // 默认排序方法
        //定制排序
        Arrays.sort(arr, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Integer i1 = (Integer) o1;
                Integer i2 = (Integer) o2;
                return i2 - i1;
            }
        });
        System.out.println("===排序后===");
        System.out.println(Arrays.toString(arr));//[89, 7, 1, 0, -1]
    }
}

package hsp.arrays_;

import java.util.Arrays;
import java.util.Comparator;

public class ArrayMethod02 {
    public static void main(String[] args) {
        int[] arr = {1, -1, 8, 0, 20};
        //bubble01(arr);
        bubble02(arr, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                int i1 = (Integer) o1;
                int i2 = (Integer) o2;
                return i2 - i1;// return i2 - i1;
            }
        });
        System.out.println("==定制排序后的情况==");
        System.out.println(Arrays.toString(arr));//[20, 8, 1, 0, -1]
    }

    //使用冒泡完成排序
    public static void bubble01(int[] arr) {
        int temp = 0;
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                //从小到大
                if (arr[j] > arr[j + 1])
                    temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }

    //结合冒泡 + 定制
    public static void bubble02(int[] arr, Comparator c) {
        int temp = 0;
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                //数组排序由 c.compare(arr[j], arr[j + 1])返回的值决定
                if (c.compare(arr[j], arr[j + 1]) > 0) {
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }
}

package hsp.arrays_;

import java.util.Arrays;
import java.util.List;

public class ArrayMethod003 {
    public static void main(String[] args) {
        Integer[] arr = {1, 2, 90, 123, 567};
        // binarySearch 通过二分搜索法进行查找,要求必须排好
        //1. 使用 binarySearch 二叉查找
        //2. 要求该数组是有序的. 如果该数组是无序的,不能使用 binarySearch
        //3. 如果数组中不存在该元素,就返回 return -(low + 1); // key not found.
        int index = Arrays.binarySearch(arr, 567);
        System.out.println("index=" + index);//index=4

        //copyOf 数组元素的复制
        //1. 从 arr 数组中,拷贝 arr.length 个元素到 newArr 数组中
        //2. 如果拷贝的长度 > arr.length 就在新数组的后面 增加 null
        //3. 如果拷贝长度 < 0 就抛出异常 NegativeArraySizeException
        //4. 该方法的底层使用的是 System.arraycopy()
        Integer[] newArr = Arrays.copyOf(arr, arr.length);
        System.out.println("==拷贝执行完毕后==");
        System.out.println(Arrays.toString(newArr));//[1, 2, 90, 123, 567]
        //ill 数组元素的填充
        Integer[] num = new Integer[]{9, 3, 2};
        //1. 使用 99 去填充 num 数组,可以理解成是替换原理的元素
        Arrays.fill(num, 99);
        System.out.println("==num 数组填充后==");
        System.out.println(Arrays.toString(num));//[99, 99, 99]
        //equals 比较两个数组元素内容是否完全一致
        Integer[] arr2 = {1, 2, 90, 123};
        //解读
        //1. 如果 arr 和 arr2 数组的元素一样,则方法 true;
        //2. 如果不是完全一样,就返回 false
        boolean equals = Arrays.equals(arr, arr2);
        System.out.println("equals=" + equals);//equals=false
        //asList 将一组值,转换成 list
        //老韩解读
        //1. asList 方法,会将 (2,3,4,5,6,1)数据转成一个 List 集合
        //2. 返回的 asList 编译类型 List(接口)
        //3. asList 运行类型 java.util.Arrays#ArrayList, 是 Arrays 类的
        // 静态内部类 private static class ArrayList<E> extends AbstractList<E>
        // implements RandomAccess, java.io.Serializable
        List asList = Arrays.asList(2, 3, 4, 5, 6, 1);
        System.out.println("asList=" + asList);//asList=[2, 3, 4, 5, 6, 1]
        System.out.println("asList 的运行类型" + asList.getClass());//asList 的运行类型class java.util.Arrays$ArrayList
    }
}

4、 System 类

System 类常见方法和案例

  1. exit退出当前程序
  2. arraycopy :复制数组元素,比较适合底层调用,一般使用
    Arrays.copyOf完成复制数组.
    int[] src={1,2,3};
    int[] dest = new int[3];
    System.arraycopy(src, 0, dest, 0, 3);
  3. currentTimeMillens:返回当前时间距离1970-1-1的毫秒数
  4. gc:运行垃圾回收机制System.gc);

5、日期类

5.1 第一代日期类

  1. Date: 精确到毫秒,代表特定的瞬间
  2. SimpleDateFormat: 格式和解析日期的类
    SimpleDateFormat格式化和解析日期的具
    体类。它允许进行格式化(日期->文本)、
    解析(文本->日期)和规范化.
package date_;

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

public class Date01 {
    public static void main(String[] args) throws ParseException {
        //1. 获取当前系统时间
        //2. 这里的 Date 类是在 java.util 包
        //3. 默认输出的日期格式是国外的方式, 因此通常需要对格式进行转换
        Date d1 = new Date(); //获取当前系统时间
        System.out.println("当前日期=" + d1);
        Date d2 = new Date(9234567); //通过指定毫秒数得到时间
        System.out.println("d2=" + d2); //获取某个时间对应的毫秒数

        //1. 创建 SimpleDateFormat 对象,可以指定相应的格式
        //2. 这里的格式使用的字母是规定好,不能乱写
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy 年 MM 月 dd 日 hh:mm:ss E");
        String format = sdf.format(d1); // format:将日期转换成指定格式的字符串
        System.out.println("当前日期=" + format);

        //1. 可以把一个格式化的 String 转成对应的 Date
        //2. 得到 Date 仍然在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换
        //3. 在把 String -> Date , 使用的 sdf 格式需要和你给的 String 的格式一样,否则会抛出转换异常
        String s = "1996 年 01 月 01 日 10:20:30 星期一";
        Date parse = sdf.parse(s);
        System.out.println("parse=" + sdf.format(parse));
    }
}

5.2第二代日期类

1)第二代日期类,主要就是Calendar类(日历)。
public abstract class Calendar extends Obiect implements Serializable,
Cloneable, Comparable < Calendar >
2) Calendar类是一个抽象类, 它为特定瞬间与-组诸如YEAR、MONTH、
DAY OF MONTH、HOUR 等日历字段之间的转换提供了一些方法,并为操
作日历字段(例如获得下星期的日期)提供了一些方法。

package date_;

import java.util.Calendar;

public class Date02 {
    public static void main(String[] args) {
        //1. Calendar 是一个抽象类, 并且构造器是 private
        //2. 可以通过 getInstance() 来获取实例
        //3. 提供大量的方法和字段提供给程序
        //4. Calendar 没有提供对应的格式化的类,因此需要程序员自己组合来输出(灵活)
        //5. 如果我们需要按照 24 小时进制来获取时间, Calendar.HOUR ==改成=> Calendar.HOUR_OF_DAY
        Calendar c = Calendar.getInstance(); //创建日历类对象//比较简单,自由
        System.out.println("c=" + c);
        //2.获取日历对象的某个日历字段
        System.out.println("年:" + c.get(Calendar.YEAR));
        // 这里为什么要 + 1, 因为 Calendar 返回月时候,是按照 0 开始编号
        System.out.println("月:" + (c.get(Calendar.MONTH) + 1));
        System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
        System.out.println("小时:" + c.get(Calendar.HOUR));
        System.out.println("分钟:" + c.get(Calendar.MINUTE));
        System.out.println("秒:" + c.get(Calendar.SECOND));
        //Calender 没有专门的格式化方法,所以需要程序员自己来组合显示
        System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" + c.get(Calendar.DAY_OF_MONTH) + " " + c.get(Calendar.HOUR_OF_DAY) + ":" + c.get(Calendar.MINUTE) + ":" + c.get(Calendar.SECOND));
    }
}

5.3 第三代日期类

前面两代日期类的不足分析
JDK 1.0中包含了一-个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar也存在问题是:
1)可变性:像日期和时间这样的类应该是不可变的。
2)偏移性: Date中的年份是从1900开始的,而月份都从0开始。
3)格式化:格式化只对Date有用,Calendar则不行。
4)此外,它们也不是线程安全的;不能处理闰秒等(每隔2天,多出1s)

LocalDate(日期/年月日)、 LocalTime(时间/时分秒)、 LocalDateTime(日期时
间/年月日时分秒) JDK8加入
LocalDate只包含日期,可以获取日期字段
LocalTime只包含时间,可以获取时间字段
LocalDateTime包含日期+时间,可以获取日期和时间字段

package date_;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

//第三代日期
public class LocalDate01 {
    public static void main(String[] args) {

        //1. 使用 now() 返回表示当前日期时间的 对象
        LocalDateTime ldt = LocalDateTime.now(); //LocalDate.now();//LocalTime.now()
        System.out.println(ldt);//2023-04-07T08:55:50.828
        //2. 使用 DateTimeFormatter 对象来进行格式化
        // 创建 DateTimeFormatter 对象
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String format = dateTimeFormatter.format(ldt);
        System.out.println("格式化的日期=" + format);
        System.out.println("年=" + ldt.getYear());
        System.out.println("月=" + ldt.getMonth());
        System.out.println("月=" + ldt.getMonthValue());
        System.out.println("日=" + ldt.getDayOfMonth());
        System.out.println("时=" + ldt.getHour());
        System.out.println("分=" + ldt.getMinute());
        System.out.println("秒=" + ldt.getSecond());
        LocalDate now = LocalDate.now(); //可以获取年月日
        LocalTime now2 = LocalTime.now();//获取到时分秒
        //提供 plus 和 minus 方法可以对当前时间进行加或者减
        //看看 890 天后,是什么时候 把 年月日-时分秒
        LocalDateTime localDateTime = ldt.plusDays(890);
        System.out.println("890 天后=" + dateTimeFormatter.format(localDateTime));
        //看看在 3456 分钟前是什么时候,把 年月日-时分秒输出
        LocalDateTime localDateTime2 = ldt.minusMinutes(3456);
        System.out.println("3456 分钟前 日期=" + dateTimeFormatter.format(localDateTime2));
    }
}

DateTimeFormatter 格式日期类

类似于SimpleDateFormat
Date TimeFormat dtf = DateTimeFormatter.ofPattern(格式);
String str = dtf.format(日期对象);

package hsp.date_;


import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateTimeFormatter01 {
    public static void main(String[] args) {
        LocalDateTime ldt = LocalDateTime.now();
        //关于DateTimeFormatter的各个格式参数,需要看jdk8的文档
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH小时mm分钟ss秒");
        String strDate = dtf.format(ldt);
        System.out.println(strDate);
    }
}

Instant 时间戳

类似于Date
提供了一系列和Date类转换的方式
Instant-- > Date:
Date date = Date.from(instant);
Date- > Instant:
Instant instant = date.tolnstant(O;

package hsp.date_;

import java.time.Instant;
import java.util.Date;

public class Instant01 {
    public static void main(String[] args) {
        //1.通过 静态方法 now() 获取表示当前时间戳的对象
        Instant now = Instant.now();
        System.out.println(now);
        //2. 通过 from 可以把 Instant 转成 Date
        Date date = Date.from(now);
        //3. 通过 date 的 toInstant() 可以把 date 转成 Instant 对象
        Instant instant = date.toInstant();
        System.out.println(instant);

    }
}

第9章 集合

1、 集合的好处

前面我们保存多个数据使用的是数组,那么数组有不足的地方。

1.1 数组

1)长度开始时必须指定,而且一旦指定, 不能更改
2)保存的必须为同一类型的元素
3)使用数组进行增加/删除元素的示意代码-比较麻烦

写出Person数组扩容示意代码。
Person[] pers = new Person[1]; //大小是1
per[0]=new Person();
//增加新的Person对象?
Person[] pers2 = new Person[pers.length+ 1]; //新创建数组
for0{} //拷贝pers数组的元素到pers2
pers2[pers2.length-1]=new Person0://添加新的对象

1.2 集合
1)可以动态保存任意多个对象,使用比较方便!
2)提供了一系列方便的操作对象的方法: add、remove、set、 get等
3)使用集合添加,删除新元素的示意代码简洁了
1.3集合框架体系图
在这里插入图片描述
在这里插入图片描述

package hsp.collection_;

import java.util.ArrayList;
import java.util.HashMap;

public class Collection01 {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        //1. 集合主要是两组(单列集合 , 双列集合)
        //2. Collection 接口有两个重要的子接口 List Set , 他们的实现子类都是单列集合
        //3. Map 接口的实现子类 是双列集合,存放的 K-V
        //Collection
        //Map
        ArrayList arrayList = new ArrayList();
        arrayList.add("jack");
        arrayList.add("tom");
        HashMap hashMap = new HashMap();
        hashMap.put("NO1", "北京");
        hashMap.put("NO2", "上海");

        System.out.println(arrayList);//[jack, tom]
        System.out.println(hashMap);//{NO2=上海, NO1=北京}

    }
}

3、 Collection接口和常用方法

3.1 Collection 接口实现类的特点

public interface Collection< E> extends Iterable

  1. collection实现子类可以存放多个元素,每个元素可以是0bject
  2. 有些Collection的实现类, 可以存放重复的元素,有些不可以
  3. 有些Collection的实现类, 有些是有序的(List),有些不是有序(Set)
  4. Collection接口没有直接的实现子类, 是通过它的子接口Set和List 来
    实现的
    Collection接口常用方法,以实现子类ArrayList来演示.
package hsp.collection_;

import java.util.ArrayList;
import java.util.List;

public class CollectionMethod {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        //add 添加单个元素
        list.add("red");
        list.add("red");
        list.add("yellow");
        list.add(200);
        list.add(false);
        System.out.println(list);

        //remove 删除指定元素
        list.remove(false);//通过值删除
        list.remove(0);//通过索引删除
        System.out.println(list);

        //contains 查找元素是否存在
        System.out.println(list.contains(200));

        //size:获取元素个数
        System.out.println(list.size());
        // isEmpty:判断是否为空
        System.out.println(list.isEmpty());//F
        // clear:清空
        //list.clear();
        //System.out.println("list=" + list);

        // addAll:添加多个元素
        ArrayList list2 = new ArrayList();
        list2.add("红楼梦");
        list2.add("三国演义");
        list.addAll(list2);
        System.out.println("list=" + list);

        // containsAll:查找多个元素是否都存在
        System.out.println(list.containsAll(list2));//T
        // removeAll:删除多个元素
        list.add("聊斋");
        list.removeAll(list2);
        System.out.println("list=" +list);



    }
}

3.2 Collection 接口遍历元素方式 1-使用 Iterator(迭代器)

  1. Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。
    2)所有实现了Collection接口的集合类都有一个iterator(方法, 用以返回
    一个实现了Iterator接口的对象, 即可以返回一个迭代器。
  2. Iterator的结构.[看图]
  3. lterator仅用3遍历焦合,lterator本身并不存放对象
    在这里插入图片描述在这里插入图片描述
    3.3 Collection 接口遍历对象方式2-for循环增强
    增强for循环,可以代替iterator迭代器, 特点:增强for就是简化版的iterator ,
    本质样。只能用于遍历集合或数组。
    基本语法
    for(元素类型元素名:集合名或数组名) {
    访问元素
    案例演示(看老师演示遍历Book,并使用Debug源码来证明)
    CollectionForjava
    for (Object object : col) {
    System. outprintln(object);
package hsp.collection_;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionIterator01 {
    public static void main(String[] args) {
        Collection col = new ArrayList();
        col.add(new Book("三国演义", "罗贯中", 10.1));
        col.add(new Book("小李飞刀", "古龙", 5.1));
        col.add(new Book("红楼梦", "曹雪芹", 12.3));
        System.out.println("col=" + col);

        //1. 先得到 col 对应的 迭代器
        Iterator iterator = col.iterator();
        //2. 使用 while 循环遍历
        // while (iterator.hasNext()) {//判断是否还有数据
        // //返回下一个元素,类型是 Object
        // Object obj = iterator.next();
        // System.out.println("obj=" + obj);
        // }
        System.out.println("===第一次遍历===");

        //一个快捷键,快速生成 while => itit
        //显示所有的快捷键的的快捷键 ctrl + j
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            System.out.println("obj=" + obj);
        }

        //3. 当退出 while 循环后 , 这时 iterator 迭代器,指向最后的元素
        // iterator.next();//NoSuchElementException

        //4. 如果希望再次遍历,需要重置我们的迭代器
        iterator = col.iterator();
        System.out.println("===第二次遍历===");
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            System.out.println("obj=" + obj);
        }

        //增强for循环
        System.out.println("===使用增强for遍历===");

        for (Object obj : col) {
            System.out.println("obj=" + obj);
        }

    }
}

class Book {
    private String name;
    private String author;
    private double price;

    public Book(String name, String author, double price) {
        this.name = name;
        this.author = author;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                '}';
    }
}

4、List 接口和常用方法

4.1 List 接口基本介绍
List接口是Collection接口的子接口List

  1. List集合类中元素有序(即添加顺序和取出顺序致)、 且可重复
  2. List集合中的每个元素都有其对应的顺序索引,即支持索引。
  3. List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
  4. JDK API中List接口的实现类,常用的有: ArrayList、 LinkedList和Vector.

List list = new ArrayList();

package hsp.collection_list;

import java.util.ArrayList;
import java.util.List;

public class List01 {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        //1. List 集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
        List list1 = new ArrayList();
        list1.add("codeSE");
        list1.add("red");
        list1.add("blue");
        list1.add("blue");

        list1.add("tom");
        System.out.println("list1=" + list1);
        //2. List 集合中的每个元素都有其对应的顺序索引,即支持索引
        // 索引是从 0 开始的
        System.out.println(list1.get(3));//blue
    }
}

4.2 List 接口的常用方法

package hsp.collection_list;

import java.util.ArrayList;
import java.util.List;

public class ListMethod {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("张三丰");
        list.add("李白");

        // void add(int index, Object ele):在 index 位置插入 ele 元素
        //在 index = 1 的位置插入一个对象
        list.add(1, "codeSE");
        System.out.println("list=" + list);

        // boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
        List list2 = new ArrayList();
        list2.add("萧何");
        list2.add("韩信");
        list2.add("韩信");
        list2.add("韩信");
        list.add("codeSE");
        list.addAll(2, list2);
        System.out.println("list=" + list);

        // Object get(int index):获取指定 index 位置的元素
        System.out.println("取指定 index 位置:" + list.get(2));//萧何

        // int indexOf(Object obj):返回 obj 在集合中首次出现的位置
        System.out.println(list.indexOf("韩信"));//3

        // int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
        System.out.println("list=" + list);
        System.out.println(list.lastIndexOf("codeSE"));//7

        // Object remove(int index):移除指定 index 位置的元素,并返回此元素
        list.remove(0);
        System.out.println("list=" + list);

        // Object set(int index, Object ele):设置指定 index 位置的元素为 ele , 相当于是替换.
        list.set(1, "玛丽");
        System.out.println("list=" + list);

        // List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合
        // 注意返回的子集合 【fromIndex ,subList)
        List returnlist = list.subList(0, 3);
        System.out.println("returnlist=" + returnlist);
    }
}

List 的三种遍历方式: 【ArrayList, LinkedList,Vector】通用

1)方式:使用iterator
Iterator iter = coliterator0:
while(iter.hasNext0){
Object。= iter.next);

2)方式二:使用增强for
for(Object o:col){
}

3)方式三:使用普通for
forint i=0; <list.size0;i++){
Object object = list.get(i);
System.out.println(object);
}

说明:使用LinkedList完成 使用方式和ArrayList一样

package hsp.collection_list;

import java.util.*;

public class ListFor {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
//        List list = new ArrayList();
//        List list = new Vector();
        List list = new LinkedList();
        list.add("张三丰");
        list.add("李白");
        list.add("萧何");
        list.add("CodeSE");
        list.add("韩信");
        list.add("韩信");
        list.add("codeSE");

        //1. 迭代器
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println(next + "\t");
        }

        //2. 增强 for
        for (Object o : list) {
            System.out.println(o + "\t");
        }

        //3. 使用普通 for
        for (int i = 0; i < list.size(); i++) {
            System.out.println("对象=" + list.get(i));
        }
    }
}

5、ArrayList 底层结构和源码分析

5.1 ArrayList 的注意事项

  1. permits all elements, including null,ArrayList可以加入null,并且多个
  2. ArrayList是由数组来实现数据存储的
  3. ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高)看源码.在多线程情况下,不建议使用ArrayList

5.2 ArrayList 的底层操作机制源码分析(重点,难点.)

  1. ArrayList中维护了-个0bject类型的数组elementData. [debug看源码]
    transient Object[] elementData; //transient表示瞬间,短暂的,表示该属性不会被序列号
    2)当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0, 第1次添加, 则扩容elementData为10, 如需要再次扩容,则扩容elementData为1.5倍。
    3)如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,
    则直接扩容elementData为1.5倍。
    建议:自己去debug -把我们的ArrayList的创建和扩容的流程.

待续...

8 ArrayList 和LinkedList 比较

8.1 ArrayList 和LinkedList的比较

底层结构增删的效率改查的效率
ArrayList可变数组较低、数组扩容较高
LinkedList双向链表较较高,通过链表追加较低

| 如何选择ArrayList和inkedList:
1)如果我们改查的操作多, 选择ArrayList
2)如果我们增删的操作多,选择LinkedList
3)般来说, 在程序中,80%-90%都是查询, 因此大部分情况下会选择ArrayList
4)在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外-个模块是LinkedList,也就是说,要根据业务来进行选择

9、Set接口和常用方法

9.1 Set 接口基本介绍

1)无序(添加和取出的顺序不一致) ,没有索引后面演示]
2)不允许重复元素,所以最多包含一个null
3) JDK API中Set接口的实现类有:

9.2 Set接口的常用方法
和List接口一样, Set接口也是Collection的子接口,因此,常用方法和Collection接口一样.

9.3 Set接口的遍历方式
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口
1.可以使用迭代器
2.增强for
3.不能使用索引的方式来获取.

package hsp.set_;

import java.util.HashSet;
import java.util.Iterator;

public class SetMethods01 {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        //Set 的接口实现类 HashSet类

        //1.以Set 接口的实现类HashSet来讲解Set接口的方法
        //2.set接口的实现类的对象(Set接口对象),不能存放重复的元素,可以添加一个nulll
        // 3.set接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
        //4.注意:取出的顺序的顺序虽然不是添加的顺序,但是他的固定.

        HashSet set = new HashSet();

        set.add("code SE");
        set.add("中国");
        set.add(true);
        set.add(null);
        set.add("code SE");//再次添加

        System.out.println(set);

        //遍历
        //方式1:
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next);
        }

        //方式2:增强for
        for (Object s:set) {
            System.out.println(s);

        }
    }
}

10、Set 接口实现类-HashSet

10.1 HashSet 的全面说明

  1. HashSet实现了Set接口
  2. HashSet实际上是HashMap,看下源码.
    public HashSet() {
    map = new HashMap<>(;
    }
  3. 可以存放null值,但是只能有一个null
  4. HashSet不保证元素是有序的,取决于hash后,再确定索引的结果(即,不保证存放元素的顺序和取出顺序一致)
  5. 不能有重复元素/对象.在前面Set接口使用已经讲过
package hsp.set_;

import java.util.HashSet;

public class Set02 {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        HashSet set = new HashSet();
        //说明
        //1. 在执行 add 方法后,会返回一个 boolean 值
        //2. 如果添加成功,返回 true, 否则返回 false
        //3. 可以通过 remove 指定删除哪个对象

        System.out.println(set.add("john"));//T
        System.out.println(set.add("lucy"));//T
        System.out.println(set.add("john"));//F
        System.out.println(set.add("jack"));//T
        System.out.println(set.add("Rose"));//T
        set.remove("john");
        System.out.println("set=" + set);//set=[Rose, lucy, jack]

        set = new HashSet();
        System.out.println("set=" + set);//set=[]

        //4 Hashset 不能添加相同的元素/数据?
        set.add("lucy");//添加成功
        set.add("lucy");//加入不了
        set.add(new Cat("tom"));//OK
        set.add(new Cat("tom"));//Ok
        System.out.println("set=" + set);//set=[Cat{name='tom'}, lucy, Cat{name='tom'}]

        //在加深一下. 非常经典的面试题
        set.add(new String("codeSE"));//OK
        set.add(new String("codeSE"));//加入不了
        System.out.println("set=" + set);//set=[Cat{name='tom'}, codeSE, lucy, Cat{name='tom'}]

    }
}
class Cat {
    String name;

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

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

10.3 HashSet 底层机制说明
待续...

11、Set 接口实现类-LinkedHashSet

11.1 LinkedHashSet 的全面说明

  1. LinkedHashSet是 HashSet的子类
  2. LinkedHashSet底层是一个 LinkedHashMap,底层维护了一个数组+双向链表
  3. LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
  4. LinkedHashSet 不允许添重复元素
    在这里插入图片描述

12、Map 接口和常用方法

12.1 Map 接口实现类的特点 [很实用]

  1. Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
  2. Map 中的key和 value可以是任何引用类型的数据,会封装到HashMap$Node对象中
    3)Map 中的key 不允许重复,原因和HashSet一样,前面分析过源码.4) Map中的value可以重复
  3. Map 的key可以为null, value也可以为null,注意key为null,只能有一个,value为null ,可以多个.
    6)常用String类作为Map的key
  4. key 和 value之间存在单向一对一关系,即通过指定的key总能找到对应的value
package hsp.map_;

import java.util.HashMap;

public class Map01 {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        // Map 接口实现类的特点, 使用实现类 HashMap
        //1. Map 与 Collection 并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
        //2. Map 中的 key 和 value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中
        //3. Map 中的 key 不允许重复,原因和 HashSet 一样,前面分析过源码
        // 4. Map 中的 value 可以重复
        //5. Map 的 key 可以为 null, value 也可以为 null ,注意 key 为 null,
        HashMap map = new HashMap();
        map.put("n1", "张三丰");
        map.put("n1", "张三丰");//相同的k,等于替换
        map.put("n2", "张翠山");
        map.put("n3", "张无忌");
        map.put("n4", "张无忌");
        map.put(null,null);
        map.put(null,"codeSE");//相同的k,等于替换
        map.put("n5", null);
        map.put("n5", null);

        map.put(1,"周芷若");
        map.put(new Object(),"赵敏");

        System.out.println("map="+map);//map={null=codeSE, 1=周芷若, n1=张三丰, java.lang.Object@a14482=赵敏, n2=张翠山, n3=张无忌, n4=张无忌, n5=null}

        // 通过 get 方法,传入 key ,会返回对应的 value
        System.out.println(map.get(null));//codeSE
    }
}

在这里插入图片描述

12.2 Map 接口常用方法

package codeSE.map_;

import java.util.HashMap;
import java.util.Map;

public class MapMethod {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        //演示 map 接口常用方法
        Map map = new HashMap();
        map.put("邓超", new Book("", 100));//OK
        map.put("邓超", "孙俪");//替换
        map.put("王宝强", "马蓉");//OK
        map.put("宋喆", "马蓉");//OK
        map.put("刘令博", null);//OK
        map.put(null, "刘亦菲");//OK
        map.put("鹿晗", "关晓彤");//OK
        map.put("codeSE", "codeSE 的老婆");
        System.out.println("map=" + map);

        // remove:根据键删除映射关系
        map.remove(null);
        System.out.println("map=" + map);
        // get:根据键获取值
        Object val = map.get("鹿晗");
        System.out.println("值:" + val);
        // size:获取元素个数
        System.out.println("元素个数:" + map.size());
        // isEmpty:判断个数是否为 0
        System.out.println(map.isEmpty());//F
        // clear:清除 k-v
        //map.clear();
        System.out.println("map=" + map);
        // containsKey:查找键是否存在
        System.out.println("结果=" + map.containsKey("codeSE"));
    }
}

class Book {
    private String name;
    private int num;

    public Book(String name, int num) {
        this.name = name;
        this.num = num;
    }
}

12.3 Map 接口遍历方法
1)containsKey:查找键是否存在
2) keySet:获取所有的键
3)entrySet:获取所有关系k-v
values:获取所有的值

package hsp.map_;

import java.util.*;

public class MapFor01 {
    @SuppressWarnings({"All"})
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("n1", "张三丰");
        map.put("n1", "张三丰");//相同的k,等于替换
        map.put("n2", "张翠山");
        map.put("n3", "张无忌");
        map.put("n4", "张无忌");
        map.put(null,null);
        map.put(null,"codeSE");//相同的k,等于替换
        map.put("n5", null);
        map.put("n5", null);
        map.put(1,"周芷若");
        map.put(new Object(),"赵敏");
        System.out.println("源数据="+map);

        //第一种: 先取出 所有的 Key , 通过 Key 取出对应的 Value
        Set keyset = map.keySet();
        //(1) 增强 for
        System.out.println("-----第一种方式-------");
        for (Object key : keyset) {
            System.out.println(key + "-" + map.get(key));
        }
        //(2) 迭代器
        System.out.println("----第二种方式--------");
        Iterator iterator = keyset.iterator();
        while (iterator.hasNext()) {
            Object key = iterator.next();
            System.out.println(key + "-" + map.get(key));
        }

        //第二组: 把所有的 values 取出
        Collection values = map.values();
        //这里可以使用所有的 Collections 使用的遍历方法
        //(1) 增强 for
        System.out.println("---取出所有的 value 增强 for----");
        for (Object value : values) {
            System.out.println(value);
        }
        //(2) 迭代器
        System.out.println("---取出所有的 value 迭代器----");
        Iterator iterator2 = values.iterator();
        while (iterator2.hasNext()) {
            Object value = iterator2.next();
            System.out.println(value);
        }


        //第三组: 通过 EntrySet 来获取 k-v
        Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
            //(1) 增强 for
        System.out.println("----使用 EntrySet 的 for 增强(第 3 种)----");
        for (Object entry : entrySet) {
            //将 entry 转成 Map.Entry
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }
        //(2) 迭代器
        System.out.println("----使用 EntrySet 的 迭代器(第 4 种)----");
        Iterator iterator3 = entrySet.iterator();
        while (iterator3.hasNext()) {
            Object entry = iterator3.next();
            //System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
            //向下转型 Map.Entry
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }
    }
}

13、Map接口实现类-HashMap

13.1 HashMap小结

  1. Map接口的常用实现类:HashMap、Hashtable和Properties。
  2. HashMap是 Map 接口使用频率最高的实现类。
    3)HashMap 是以 key-val对的方式来存储数据(HashMap$Node类型)[案例 Entry ]
  3. key不能重复,但是值可以重复,允许使用null键和null值。
    5)如果添加相同的key,则会覆盖原来的key-val ,等同于修改.(key不会替换,val会替换)
    6)与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的. (jdk8的
    hashMap底层数组+链表+红黑树)
  4. HashMap没有实现同步,因此是线程不安全的方法没有做同步互斥的操作,没有synchronized
    13.2 HashMap底层机制及源码剖析
    待续。。。。

14、Map 接口实现类-Hashtable

14.1 HashTable 的基本介绍
1)存放的元素是键值对:即K-V
2) hashtable的键和值都不能为null, 否则会抛出NullPointerException
3) hashTable使用方法基本上和HashMap-样
4) hashTable是线程安全的(synchronized), hashMap是线程不安全的
5)简单看下底层结构

package hsp.map_;

import java.util.Hashtable;
import java.util.Map;

public class HashTable01 {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        Map hashtable = new Hashtable();
        hashtable.put("name","codeSE");
        hashtable.put("age",20);
        hashtable.put("age",18);//替换,可以
        hashtable.put("grender","男");
        hashtable.put(null,"codeSE");//异常
        hashtable.put("salary",null);//异常
        hashtable.put(null,null);//异常

        System.out.println("hashtable="+hashtable);

    }
}

14.2 Hashtable 和 HashMap 对比

版本线程安全(同步)效率允许null键,值null
HashMap1.2不安全可以
HashTable1.0安全较低不可以

15、Map接口实现类-Properties

15.1基本介绍

1.Properties类继承自Hashtable类井且实现了Map接口,也是使用一种键值对的形式来保存数据。
2.他的使用特点和Hashtable类似
3. Properties 还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改
说明:工作后xxx.properties 文件通常作为配置文件,这个知识点在I0流举例,有兴趣可先看文章htst://ww.cblos.cm/xudone-bhunt/o/3758136.html
15.2基本使用

package hsp.map_;

import java.util.Properties;

public class Properties01 {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        //解读
        //1. Properties 继承 Hashtable
        //2. 可以通过 k-v 存放数据,当然 key 和 value 不能为 null

        Properties properties = new Properties();

        //增加
        //properties.put(null, "abc");//抛出 空指针异常
        //properties.put("abc", null); //抛出 空指针异常
        properties.put("john", 100);//k-v
        properties.put("lucy", 100);
        properties.put("lic", 100);
        properties.put("lic", 88);//如果有相同的 key , value 被替换
        System.out.println("properties=" + properties);

        //通过 k 获取对应值
        System.out.println(properties.get("lic"));//88
        //删除
        properties.remove("lic");
        System.out.println("properties=" + properties);
        //修改
        properties.put("john", "约翰");
        System.out.println("properties=" + properties);

    }
}

16、总结-开发中如何选择集合实现类(记住)

在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:
1)先判断存储的类型(一组对象【单列】或一组键值对【双列】)
2) 一组对象【单列】: Collection接口
(1)允许重复: List
增删多: LinkedList [底层维护了一个双向链表]
改查多: ArrayList [底层维护Object类型的可变数组]
(2)不允许重复: Set
无序: HashSet [底层是HashMap,维护了一一个哈希表即(数组+链表+红黑树)]
排序: TreeSet [老韩举例说明]
插入和取出顺序一致: LinkedHashSet , 维护数组+双向链表
3) 一组键值对【双列】: Map
键无序: HashMap [底层是哈希表jdk7: 数组+链表,jdk8:数组+链表+红黑树]
键排序: TreeMap
键插入和取出顺序一致: LinkedHashMap
读取文件Properties

第10章 泛型

1 泛型的引出

1.1 看一个需求

package hsp.genericity_;

import java.util.ArrayList;

public class Animal {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(new Dog("阿黄", "黄色", 4));
        list.add(new Dog("阿狗", "灰色色", 10));
        list.add(new Dog("阿花", "红色", 4));
        //假如我们不小心,添加了一只猫
        list.add(new Cat("阿猫", "白色", 1));

        //遍历
        for (Object o : list) {
            //向下转型 Object ->Dog
            Dog dog = (Dog) o;
            System.out.println(dog.getName() + "-" + dog.getAge());
        }
    }
}

class Dog {
    private String name;
    private String color;
    private int age;

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

    public String getName() {
        return name;
    }

    public String getColor() {
        return color;
    }

    public int getAge() {
        return age;
    }
}

class Cat {
    private String name;
    private String color;
    private int age;

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

    public String getName() {
        return name;
    }

    public String getColor() {
        return color;
    }

    public int getAge() {
        return age;
    }
}

1.2使用传统方法的问题分析
1)不能对加入到集合ArrayList中的数据类型进行约束(不安全)
2)遍历的时候,需要进行类型转换,如果集合中的数据量较大,对效率有影响
1.3泛型快速体验_用泛型来解决前面的问题

package hsp.genericity_;

import java.util.ArrayList;

public class Animal {
    public static void main(String[] args) {
        //使用传统的方法来解决===> 使用泛型
        //解读
        //1. 当我们 ArrayList<Dog> 表示存放到 ArrayList 集合中的元素是 Dog 类型 
        //2. 如果编译器发现添加的类型,不满足要求,就会报错
        //3. 在遍历的时候,可以直接取出 Dog 类型而不是 Object
        //4. public class ArrayList<E> {} E 称为泛型,那么 Dog->E
        ArrayList<Dog> list = new ArrayList<Dog>();
        list.add(new Dog("阿黄", "黄色", 4));
        list.add(new Dog("阿狗", "灰色色", 10));
        list.add(new Dog("阿花", "红色", 4));

        //list.add(new Cat("阿猫", "白色", 1)); //这时被约束后,Cat就添加不了

        //遍历
        for (Dog dog : list) {
            System.out.println(dog.getName() + "-" + dog.getAge());
        }
    }
}

class Dog {
    private String name;
    private String color;
    private int age;

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

    public String getName() {
        return name;
    }

    public String getColor() {
        return color;
    }

    public int getAge() {
        return age;
    }
}

class Cat {
    private String name;
    private String color;
    private int age;

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

    public String getName() {
        return name;
    }

    public String getColor() {
        return color;
    }

    public int getAge() {
        return age;
    }
}

2、泛型的理解和好处

2.1泛型的好处
1)编译时, 检查添加元素的类型, 提高了安全性
2)减少了类型转换的次数,提高效率[说明]
不使用泛型:
Dog -加入-> Object -取出-> Dog //放入到ArrayList会先转成Object,在取出时,还需要转换成Dog
使用泛型:
Dog-> Dog -> Dog //放入时,和取出时,不需要类型转换,提高效率
3)不再提示编译警告

3 、泛型介绍

3.1
int a =10;
理解:泛(广泛)型(类型) => Integer, String,Dog
1)泛型又称参数化类型, 是Jdk5.0出现的新特性,解决数据类型的安全性问题
2) 在类声明或实例化时只要指定好需要的具体的类型即可。
3) Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生
ClassCastException异常。同时,代码更加简洁、健壮
4)泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方法的返回值的类型,或者是参数类型。

package hsp.genericity_;

public class Genericity01 {
    public static void main(String[] args) {
        //注意,特别强调: E 具体的数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E 是什么类
        Person<String> person = new Person<String>("中国人");
        person.show(); //class java.lang.String

        Person<Integer> person1 = new Person<>(100);
        person1.show();//class java.lang.Integer
    }
}
//泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,
// 或者是某个方法的返回值的类型,或者是参数类型
class Person<E> {
    E s;//E 表示 s 的数据类型, 该数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E 是什么类型

    public Person(E s) {//E 也可以是参数类型
        this.s = s;
    }

    public E f() {//返回类型使用 E
        return s;
    }

    public void show() {
        System.out.println(s.getClass());//显示 s
    }
}

4泛型的语法

4.1泛型的声明 .

interface接口<T>{}和class类<K,V>{}
//比如: List,ArrayList
说明:
1)其中,T,K,V不代表值,而是表示类型。
2)任意字母都可以。常用T表示,是Type的缩写

4.2泛型的实例化
要在类名后面指定类型参数的值(类型)。如:

1) List<String> strList = new ArrayList <String>0;
2) Iterator<Customer> iterator = customers.iterator();
package hsp.genericity_;

import java.util.*;

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

        HashSet<Student> studentHashSet = new HashSet<Student>();
        studentHashSet.add(new Student("小花", 20));
        studentHashSet.add(new Student("小华", 30));
        studentHashSet.add(new Student("小白", 50));
        for (Student student : studentHashSet) {
            System.out.println(student);
        }

        HashMap<String, Student> studentHashMap = new HashMap<String, Student>();
        studentHashMap.put("1001", new Student("小明明", 20));
        studentHashMap.put("1002", new Student("小华华", 45));
        studentHashMap.put("1003", new Student("小花花", 50));

        //迭代器 EntrySet
        Set<Map.Entry<String, Student>> entries = studentHashMap.entrySet();
        Iterator<Map.Entry<String, Student>> iterator = entries.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String,Student> next = iterator.next();
            System.out.println(next.getKey()+"----"+next.getValue());
        }
    }
}

class Student {
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

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

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

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

4.4 泛型使用的注意事项和细节

1.interface List <T> {} ,public class HashSet<E>{}..等等
说明: T, E只能是引用类型
看看下面语句是否正确?:
List < Integer> list = new ArrayList< Integer> 0; //OK
List<int> list2 = new ArrayList <int> 0://错误

2.在给泛型指定具体类型后,可以传入该类型或者其子类类型
3.泛型使用形式
List<Integer> list1 = new ArrayList <Integer> 0;
List<Integer> list2 = new ArrayList< > 0; 

3.如果我们这样写List list3 = new ArrayList(); 默认给它的泛型是<E> ,E就是Object 
package hsp.genericity_;

import java.util.ArrayList;
import java.util.List;

public class GenericityDetail {
    public static void main(String[] args) {
        //1.给泛型指向数据类型是,要求是引用类型,不能是基本数据类型
        List<Integer> list = new ArrayList<Integer>(); //OK
        //List<int> list2 = new ArrayList<int>();//错误
        //2. 说明
        //因为 E 指定了 A 类型, 构造器传入了 new A()
        //在给泛型指定具体类型后,可以传入该类型或者其子类类型
        Pig<A> aPig1 = new Pig<A>(new A());
        aPig1.fn();
        Pig<A> aPig2 = new Pig<A>(new B());
        aPig2.fn();
        //3. 泛型的使用形式
        ArrayList<Integer> list1 = new ArrayList<Integer>();
        List<Integer> list2 = new ArrayList<Integer>();
        //在实际开发中,我们往往简写
        //编译器会进行类型推断, 推荐使用下面写法
        ArrayList<Integer> list3 = new ArrayList<>();
        List<Integer> list4 = new ArrayList<>();
        ArrayList<Pig> pigs = new ArrayList<>();

        //4. 如果是这样写 泛型默认是 Object
        ArrayList arrayList = new ArrayList();//等价 ArrayList<Object> arrayList = new ArrayList<Object>()
        
        /*Tiger tiger = new Tiger();
        class Tiger {//类
            Object e;
            public Tiger() {}
            public Tiger(Object e) {
                this.e = e;
            }
        }
        */
    }
}
class A{
}
class B extends A{}
class Tiger<T>{
    T t;

    public Tiger(T t) {
        this.t = t;
    }
}
class Pig<T>{
    T t;

    public Pig(T t) {
        this.t = t;
    }
    public void fn(){
        System.out.println(t.getClass());//运行类型
    }
}

5 自定义泛型

5.1自定义泛型类(难度)
➢基本语法
class类名<T, R… >{ //…表示可以有多个泛型
成员
}
注意细节
1)普通成员可以使用泛型(属性、方法)
2)使用泛型的数组,不能初始化
3)静态方法中不能使用类的泛型
4)泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
5)如果在创建对象时,没有指定类型,默认为0bject

package hsp.genericity_;

import java.util.Arrays;

/*
 * 自定义泛型
 * */
public class CustomGenericity01 {
    public static void main(String[] args) {
        Monkey<String, Integer, Boolean> monkey = new Monkey<>("猴子",100, false );
        Monkey<Double,Integer,String> monkey1 = new Monkey<>(12.2,25,"code");
        Monkey<String, Integer, Boolean> monkey2 = new Monkey<>("候类","猴子",100, false);

        //T=Double, R=String, M=Integer
        Monkey<Double,String,Integer> g = new Monkey<>("大猴子");
        g.setR(10.9); //OK
        //g.setR("yy"); //错误,类型不对
        System.out.println(g);
        Monkey g2 = new Monkey("大猴子~~");//OK T=Object R=Object M=Object
        g2.setT("yy"); //OK ,因为 T=Object "yy"=String 是 Object 子类
        System.out.println(g2);
    }
}

//解读
// 1. Monkey 后面泛型,所以我们把 Monkey 就称为自定义泛型类
// 2, T, R, M 泛型的标识符, 一般是单个大写字母
// 3. 泛型标识符可以有多个.
// 4. 普通成员可以使用泛型 (属性、方法)
// 5. 使用泛型的数组,不能初始化
// 6. 静态方法中不能使用类的泛型
class Monkey<R, M,T> {
    String name;
    //属性使用到泛型
    R r;
    M m;
    T t;
    //因为数组在 new 不能确定 T 的类型,就无法在内存开空间
    T[] ts;

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

    public Monkey(R r, M m, T t) {//构造器使用泛型
        this.r = r;
        this.m = m;
        this.t = t;
    }

    public Monkey(String name, R r, M m, T t) {//构造器使用泛型
        this.name = name;
        this.r = r;
        this.m = m;
        this.t = t;
    }

    //因为静态是和类相关的,在类加载时,对象还没有创建
    //所以,如果静态方法和静态属性使用了泛型,JVM 就无法完成初始化
    //     static R r2;
    //     public static void m1(M m){
    //
    //     }
    //方法使用泛型
    public String getName() {
        return name;
    }

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

    public R getR() {
        return r;
    }

    public void setR(R r) {//方法使用到泛型
        this.r = r;
    }

    public M getM() {//返回类型可以使用泛型.
        return m;
    }

    public void setM(M m) {
        this.m = m;
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    @Override
    public String toString() {
        return "Monkey{" +
                "name='" + name + '\'' +
                ", r=" + r +
                ", m=" + m +
                ", t=" + t +
                ", ts=" + Arrays.toString(ts) +
                '}';
    }
}

5.2 自定义泛型接口
➢基本语法
interface 接口名<T, R…> {}
注意细节
1)接口中,静态成员也不能使用泛型(这个和泛型类规定一样)
2)泛型接口的类型,在继承接口或者实现接口时确定
3)没有指定类型, 默认为0bject

package hsp.genericity_;

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

    }
}

/*
 * 泛型接口使用的说明
 * 1. 接口中,静态成员也不能使用泛型
 * 2. 泛型接口的类型, 在继承接口或者实现接口时确定
 * 3. 没有指定类型,默认为 Object
 */
interface USB<U, R> {
    int n = 10;

    //U name; 不能这样使
    //普通方法中,可以使用接口泛型
    R get(U u);

    void hi(R r);

    void run(R r1, R r2, U u1, U u2);

    //在 jdk8 中,可以在接口中,使用默认方法, 也是可以使用泛型
    default R method(U u) {
        return null;
    }
}
//在继承接口 指定泛型接口的类型
interface IA extends USB<String, Double> {
}
//当我们去实现 IA 接口时,因为 IA 在继承 IUsu 接口时,指定了 U 为 String R 为 Double
//,在实现 USB 接口的方法时,使用 String 替换 U, 是 Double 替换
class AA implements IA{
    @Override
    public Double get(String s) {
        return null;
    }

    @Override
    public void hi(Double aDouble) {

    }

    @Override
    public void run(Double r1, Double r2, String u1, String u2) {

    }
}
//实现接口时,直接指定泛型接口的类型
//给 U 指定 Integer 给 R 指定了 Float
//所以,当我们实现 USB 方法时,会使用 Integer 替换 U, 使用 Float 替换 R
class BB implements USB<Integer, Float> {
    @Override
    public Float get(Integer integer) {
        return null;
    }

    @Override
    public void hi(Float aFloat) {

    }

    @Override
    public void run(Float r1, Float r2, Integer u1, Integer u2) {

    }
}
//没有指定类型,默认为 Object
//建议直接写成 IUsb<Object,Object>
class CC implements USB {
    等价 class CC implements IUsb<Object,Object>
    @Override
    public Object get(Object o) {
        return null;
    }

    @Override
    public void hi(Object o) {

    }

    @Override
    public void run(Object r1, Object r2, Object u1, Object u2) {

    }
}

5.3 自定义泛型方法
15.6.3自定义泛型方法.
➢基本语法
修饰符<T.R…>返回类型方法名(参数列表) { }
➢注意细节
1.泛型方法,可以定义在普通类中,也可以定义在泛型类中
2.当泛型方法被调用时,类型会确定
3. public void eat(E e) {}, 修饰符后没有<T,R…> ,eat方法不是泛型方法,而是使用了泛型

package hsp.genericity_;

import java.util.ArrayList;

public class GenericityMethod01 {
    public static void main(String[] args) {
        Car car = new Car();
        car.fly("宝马", 100);//当调用方法时,传入参数,编译器,就会确定类型
        System.out.println("============================");
        car.fly(300, 100.1);//当调用方法时,传入参数,编译器,就会确定类型
        //测试
        //T->String, R-> ArrayList
        Fish<String, ArrayList> fish = new Fish<>();
        fish.hello(new ArrayList(),12.12);
    }
}

//泛型方法,可以定义在普通类中, 也可以定义在泛型类中
class Car {//普通类

    public void run() {//普通方法
    }

    //说明 泛型方法
    //1. <T,R> 就是泛型
    //2. 是提供给 fly 使用的
    public <T, R> void fly(T t, R r) {//泛型方法
        System.out.println(t.getClass());//String
        System.out.println(r.getClass());//Integer
    }
}
class Fish<T, R> {//泛型类

    public void run() {//普通方法
    }

    public <U, M> void eat(U u, M m) {//泛型方法
    }

    //说明
    //1. 下面 hi 方法不是泛型方法
    //2. 是 hi 方法使用了类声明的 泛型
    public void hi(T t) {
    }

    //泛型方法,可以使用类声明的泛型,也可以使用自己声明泛型
    public <K> void hello(R r, K k) {
        System.out.println(r.getClass());//ArrayList
        System.out.println(k.getClass());//Float
    }
}

6泛型的继承和通配符

61泛型的继承和通配符说明GenericExtends.java
1)泛型不具备继承性
List list = new ArrayList 0; //对吗?
2) <?> :支持任意泛型类型
3) <? extends A>:支持A类以及A类的子类,规定了泛型的上限
4) <? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限

package hsp.genericity_;

import java.util.ArrayList;
import java.util.List;
//泛型的继承和通配符
public class GenericWildcard01 {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        Object o = new String("xx");
        //泛型没有继承性
        //List<Object> list = new ArrayList<String>();
        //举例说明下面三个方法的使用
        List<Object> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        List<AAA> list3 = new ArrayList<>();
        List<BBB> list4 = new ArrayList<>();
        List<CCC> list5 = new ArrayList<>();
        //如果是 List<?> c , 可以接受任意的泛型类型
        printCollection1(list1);
        printCollection1(list2);
        printCollection1(list3);
        printCollection1(list4);
        printCollection1(list5);

        //List<? extends AA> c: 表示 上限,可以接受 AA 或者 AA 子类
        // printCollection2(list1);//×
        // printCollection2(list2);//×
        printCollection2(list3);//√
        printCollection2(list4);//√
        printCollection2(list5);//√

        //List<? super AA> c: 支持 AA 类以及 AA 类的父类,不限于直接父类
        printCollection3(list1);//√
        //printCollection3(list2);//×
        printCollection3(list3);//√
        //printCollection3(list4);//×
        //printCollection3(list5);//×

    }

    //说明: List<?> 表示 任意的泛型类型都可以接受
    public static void printCollection1(List<?> c) {
        for (Object object : c) { // 通配符,取出时,就是 Object
            System.out.println(object);
        }
    }

    // ? extends AAA 表示 上限,可以接受 AAA 或者 AAA 子类
    public static void printCollection2(List<? extends AAA> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }


    // ? super 子类类名 AAA:支持 AAA 类以及 AAA 类的父类,不限于直接父类,
    //规定了泛型的下限
    public static void printCollection3(List<? super AAA> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }


}

class AAA {

}

class BBB extends AAA {
}

class CCC extends BBB {
}

7.JUnit

7.1 为什么需要 JUnit
1.一个类有很多功能代码需要测试,为了测试,就需要写入到main方法中
2.如果有多个功能代码测试,就需要来回注销,切换很麻烦
3.如果可以直接运行一个方法,就方便很多,并且可以给出相关信息,就好了-> JUnit
7.2 基本介绍
1.JUnit是一个Java语言的单元测试框架
2.多数Java的开发环境都已经集成了JUnit作为单元测试的工具

public class JUnit_ {
public static void main(String[] args) {
	//传统方式
	//new JUnit_().m1();
	//new JUnit_().m2();
	}
	@Test
	public void m1() {
		System.out.println("m1 方法被调用");
	}
	@Test
	public void m2() {
		System.out.println("m2 方法被调用");
	}
	@Test
	public void m3() {
		System.out.println("m3 方法被调用");
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

梦境之冢

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

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

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

打赏作者

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

抵扣说明:

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

余额充值