Java01_初级


title: Java基础(第一遍)

[内存分布图](#3.2.6 一维数组的==内存解析==)

[流程图的绘画](数据自动类型转换默认==int double==)

第1章 Java语言概述

1.1软件开发介绍

软件

一系列按照==特定顺序组织==的计算机==数据==与==指令==的集合

  • 系统软件-Windows Linux Mac Android
  • 应用软件

==流程图==

graph TD
A("递归函数")-->B("执行语句")
B-->C{"条件"}
C-->|TRUE|A
C-->|FALSE|D["执行语句"]
D-->E("退出")

人机交互方式

图形化界面(Graphical User Interface ==GUI==)

命令行方式(Command Line Interface ==CLI==)

命令行指令

切换盘符 d:

列出子目录 dir

退出当前目录 cd..

打开文档 cd

创建目录(文件夹)md

一次性退出到根目录 cd\

创建文件 echo name : tom >1.doc name : tom是需要写入的东西,可以不写\

删除文件目录rd(如果里面有内容删除不了,需要进入然后删除文件)

删除文件 del del *.txt(删除同类文件),如果直接del删除文件内的内容

退出 exit

常用快捷键

  • ← →:移动光标
  • ↑ ↓:调阅历史操作命令
  • Delete和Backspace:删除字符

1.2计算机语言介绍

什么是计算机语言

人与计算机交流的方式

语言 = 语法 + 逻辑

C,C++,Java,PHP,Kotlin,Python,Scala .et al

graph LR
登录界面-->HTTP-->数据库
数据库-->登录界面

语言的分代

第一代 机器语言

第二代 汇编语言 使用助记符表示一条机器指令

第三代 高级语言

  1. 面向过程 C
  2. C ++ Cpp
  3. 面向对象 Java
graph TD
A(高级语言)-->B{汇编语言}-->|调用|C[机器指令]-->|调用|D(调用CPU)

Android/why is Java?

市场需求

https://www.tiobe.com/tiobe-index/

C 运行效率高

  • 操作系统
  • 嵌入式设备(汽车,洗衣机)
  • 动画制图

Python 开发效率快,运行速度更低

  • 爬虫
  • ==数据分析==
  • ==AI==
  • 后台
  • 自动化运维

PHP

  • 后台

C# 不开源

Java 社区最活跃

JavaScript 前端

SQL

Swift 与 OC Apple

Go Google (头条替换成Go)

1.3 Java语言概述

1995 SUN(Stanford University Network)

简史

1991 Oak(橡树)

sun

爪哇岛 生产咖啡

2004 JDK 1.5 突破版本 更名JDK 5.0

2009 74亿美元 Oracle收购

2014 JDK8.0 使用量最大

LTS 8 11 长期维护版本

每隔三年发布一个LTS

Java技术体系平台

Java SE (Java Standard Edition)标准版
支持面向桌面级应用(如Windows下的应用程序)的Java平台,提供了完整的Java核心API,此版本以前称为==J2SE==
**Java EE(Java Enterprise Edition)企业版 **
是为开发企业环境下的应用程序提供的一套解决方案。该技术体系中包含的技术如:Servlet 、Jsp等,主要针对于Web应用程序开发。版本以前称为J2EE
Java ME(Java Micro Edition)小型版
支持Java程序运行在移动终端(手机、PDA)上的平台,对Java API有所精简,并加入了针对移动终端的支持,此版本以前称为J2ME
Java Card
支持一些Java小程序(Applets)运行在小内存设备(如智能卡)上的平台

Java语言特点

面向对象

健壮性

  • 去除了指针
  • 增加了内存管理

跨平台(write once run anywhere)

  • JVM Java虚拟机 实现Java语言跨平台
  • 不同系统的JVM不一样(Win、Linux、Mac)

1.4 运行机制及其运行过程

JVM是一个虚拟的计算机

Java舍弃了C的指针

增加了垃圾回收机制//自动的内存管理

java的结构

1.5 Java语言的环境的搭建

java的运行环境

java的三层架构

配置环境变量

需在任何文件路径下能够执行javac与java文件

path:windows在执行命令时要搜寻的路径

  1. 我的电脑–属性–高级系统设置–环境变量
  2. 编辑 path 环境变量,在变量值开始处加上java工具所在目录,后面用 “ ; ”和其他值分隔开即可。
  3. 打开DOS命令行,任意目录下敲入javac。如果出现javac 的参数信息,配置成功。

设置==JAVA_HOME==

新建环境变量JAVA_HOME

JAVA_HOME = C:\Program Files\Java\jdk1.8.0_131

新增Path环境变量,将

D:\developer_tools\Java\jdk1.8.0_131\bin

替换为

%JAVA_HOME%\bin;

测试

java -version

为什么要使用JAVA_HOME?

为了以后默认寻找JAVA_HOME的方便

注意:要把JAVA_HOME放在前面

1.6 开发体验-Hello World

java编译过程

ln为line的意思,属于单行打印

想当于打印与换行符同时进行

class HelloChina{
    public static void main(String[] args){
        System.out.println("Hello world!");
    }
    //ln是相当于print+line,属于单行打印的意思
}
class HelloChina{
    public static void main(String[] args){
        System.out.print("Hello World!");
        //单行注释,此行没有换行符
        System.out.println();
        //相当于换行符
        System.out.println("Hello WOrld!");
    }
}

写完之后注意保存

1.7 Java注释信息

==文档注释的注意事项==

  • 文档注释需要用于==放置于类的前面==用于标记声明类,放置于类后面@不展现
  • 一个java源文件声明多个类编译之后会生成==多个字节码文件==(对应原文件中不同的类)
  • 要想使用java.exe命令解释运行成功,必须保证对应的类中声明==有main()方法。==
    main()的格式是固定的!
  • main()作为程序的==入口!==
  • 如果源文件中的一个类想用==public==修饰,则要求此类的==类名必须与源文件的文件名相同。==
/*
一、测试Java中的三种注释

1. 单行注释 //
2. 多行注释

3. 文档注释(java特有)

二、单行和多行注释的作用:
① 对编写的代码进行解释说明
② 进行代码的调试

三、说明
1. 单行注释和多行注释,被注释掉的信息,是不参与编译的。
2. 多行注释是不能嵌套使用的。

四、文档注释

特点:
注释内容可以被JDK提供的工具 javadoc 所解析,生成一套以网页文件形式体现的该程序的说明文档。
*/

    
/**
文档注释:
放置于类的前面,用于声明类的注释
这是我的第一个Java程序!
@author shkstart
@version 1.0
*/

public class CommentTest{
	/**
	如下的方法是main()方法,作为程序的入口
	*/
    
	/*
	main()的格式是固定的!
	*/
	public static void main(String[] args) {
		//单行注释
		//System.out.println("Hello World!")
	}
}

对编写的代码进行解释说明

注释相当于药的说明书

多行注释不能嵌套使用

文档注释内容可以被JDK提供的工具 javadoc 所解析,生成一套以网页文件形式体现的该程序的说明文档。

javadoc

mydoc是自己起的名字,生成文件目录

打开index首页

1.9 Java API文档

API (Application Programming Interface,应用程序编程接口)是 Java 提供的基本编程接口。

Java语言提供了大量的基础类,因此 Oracle 也为这些基础类提供了相应的API文档,用于告诉开发者如何使用这些类,以及这些类里包含的方法。类似于学习汉字使用的《新华字典》

注意搜索查看

相当于辞典

1.10 良好的编程风格

使用文档注释来注释整个类或整个方法

使用注释方法注释某一个步骤

使用TAB操作进行缩进

运算符两端加空格

块的风格可以做选择

java的编程风格

总结

1.编写-编译-运行三个步骤:
	① 编写:将编写的java程序保存在.java结尾的源文件中。比如:Hello.java
	② 编译:使用javac.exe指令对编写的java源文件进行编译。比如:javac Hello.java
        编译之后,生成.class结尾的字节码文件。
	③ 运行:使用java.exe指令对生成的字节码文件,进行解释运行。比如:java HelloShangHai
2. 在一个java源文件中,是可以声明多个类的。那么编译之后,就会生成对应的类名的多个字节码文件。
3. 要想使用java.exe命令解释运行成功,必须保证对应的类中声明有main()方法。
   main()的格式是固定的!
4. main()作为程序的入口!
5. 如果源文件中的一个类想用public修饰,则要求此类的类名必须与源文件的文件名相同。
   > 结论:一个源文件中,最多只能有一个类声明为public6. 输出语句:
   类型一:System.out.println()  在执行完输出语句之后,会换行
   类型二:System.out.print()   在执行完输出语句之后,不会换行
7. 所有的执行语句,必须以";"结尾
*/
class HelloShangHai {
	public static void main(String[] args) {//args: arguments,参数
		System.out.println("Hello World!");
		System.out.println("Hello World!");//"Hello World!" : 字符串
		System.out.println("Hello World!");
	}
}

class HelloBeiJing{
}

class HelloShenZhen{
	public static void main(String[] args) {
		System.out.println("Hello World!");
	}
}

class HelloGuangZhou{
}

public class Hello{//public修饰与否,影响的是Hello类被调用时的权限的大小

}

第2章 Java基本语法

2.1 关键字和保留字

关键字(Keyword)

  • 定义:被Java语言赋予了特殊含义,用做专门用途的字符串(单词)
  • 特点:关键字中所有字母都为小写

保留字(reserved word)

  • 现有Java版本尚未使用,但以后版本可能会作为关键字使用。自己命名标识符时要避免使用这些保留字
  • goto 、const
关键字 名称 介绍
byte 单字节类型 1个字节(8位)[-128,127]
short 短整型 2个字节(16位)[-2^15^,2^15^-1]
int 整型 4个字节(32位)[-2^31^,2^31^-1]
long 长整型 8个字节(64位)[-2^63^,2^63^-1]
char 单字符类型 2个字节(16位)
float 单精度浮点型 4个字节 科学计数法保留小数点6-7位+F或者f
double 双精度浮点型 8个字节 科学计数法表示小数点15-16位
boolean 布尔类型 true and false

2.2 标识符

Java 对各种变量方法等要素命名时使用的字符序列称为标识符

技巧:凡是自己可以起名字的地方都叫标识符

2.2.1 定义合法标识符规则:

  • 由26个英文字母大小写,0-9,_ 或 $ 组成

  • 数字不可以开头

  • 不可以使用关键字和保留字,但能包含关键字和保留字

class public1//public不行
  • Java中严格区分大小写,长度无限制,在windows中不区分大小写,会发生覆盖
  • 标识符不能包含==空格==

2.2.2 命名的规范

包名:多单词组成时所有字母都小写:==xxxyyyzzz==

类名、接口名:多单词组成时,所有单词的首字母大写:==XxxYyyZzz== 大驼峰

变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写:==xxxYyyZzz== 小驼峰

常量名:所有字母都大写。多单词时每个单词用==下划线连接==:XXX_YYY_ZZZ

如果用户不遵守规范,编译与运行都不受影响

2.3 变量

2.3.1 变量的概念

  • 内存中的一个存储区域
  • 该区域的数据可以在同一类型范围内不断变化
  • 变量是程序中最基本的存储单元。包含变量类型、变量名和存储的值

2.3.2变量的作用

  • 用于在内存中保存数据
/*
测试变量的定义
变量的声明与赋值
格式: 数据类型 变量名 = 变量值;
*/
class VaribleTest{
    public static void main(String[] args){
        int number;//变量的声明
        number = 1;//变量的赋值
        //变量的声明与赋值
        int count = 2;
        System.out.println(number + 1);
    }
}

2.3.3 变量==注意点==

  1. java是强类型的语言,声明的每个变量,一定要指明其变量类型
  2. 变量一定需要在赋值之后才可以使用(java没有初始化)
  3. 变量需要先声明在赋值
  4. 变量都有其作用域,超出作用域范围后,就失效 { }
  5. 在同一个作用域范围内,不能同时存在相同的变量名
/**文档注释示例

*/

2.3.4基本数据类型

<1> 数据类型的分类

按数据类型分

java的数据类型

整型:byte\short\int\long

浮点型:float\double

字符型:char

布尔型:boolean

<2> ==Java整型==

  • Java各整数类型有固定的表数范围和字段长度,不受具体OS的影响,以保证java程序的可移植性。
  • 超过范围编译不通过
  • java的整型常量==默认为 int 型==,声明long型常量须后加‘l’或‘L’,一般默认加==L==
  • java程序中变量==通常声明为int型==除非不足以表示较大的数,才使用long,一般使用int
关键字 名称 介绍
byte 单字节类型 1个字节(8位)[-128,127]
short 短整型 2个字节(16位)[-2^15^,2^15^-1]
int 整型 4个字节(32位)[-2^31^,2^31^-1]
long 长整型 8个字节(64位)[-2^63^,2^63^-1]
char 单字符类型 2个字节(16位)
float 单精度浮点型 4个字节 科学计数法保留小数点6-7位+F或者f
double 双精度浮点型 8个字节 科学计数法表示小数点15-16位
boolean 布尔类型 true and false

<3> Java浮点类型

与整数类型类似,Java 浮点类型也有固定的表数范围和字段长度,不受具体操作系统的影响。

浮点型常量有两种表示形式:

  • 十进制数形式:如:5.12 512.0f .512 (必须有小数点)
  • 科学计数法形式:如:5.12e2 512E2 100E-2

==float==:单精度,尾数可以精确到7位有效数字。很多情况下,精度很难满足需求。

定义float类型的变量在赋值时,需要’F’或者’f’,否则编译错误

float存储范围比long还要大,但是精度降低

double:双精度,精度是float的两倍。通常采用此类型

Java 的浮点型常量==默认为double型==,声明float型常量,须后加‘f’或‘F’

类 型 占用存储空间 表数范围
单精度float 4字节 -3.403E38 ~ 3.403E38
双精度double 8字节 -1.798E308 ~ 1.798E308

<4> 字符类型char

char 型数据用来表示通常意义上“字符” (2字节)

Java中的所有字符都使用Unicode编码,故一个字符可以存储一个字母,一个汉字,或其他书面语的一个字符

字符型变量的三种表现形式:

  • 字符常量是用单引号==‘ ’==括起来的单个字符。
char c1 = 'a';
char c2 = '1';
char c3 = 'zhong';
char c4 = 'd';
char c5 = 'ab';//编译不通过
char c6 = '';//编译不通过
  • 转义字符

    java的转义字符

    char c7 = '\n';//换行符
    char c8 = '\t';//制表符,缩进
  • 直接使用 Unicode 值来表示字符型常量:\uXXXX。其中,XXXX代表一个==十六进制整数==。如:

    \u000a = \n;
    //char类型是可以进行运算的。因为它都对应有Unicode码。

    u是Unicode 通过映射表进行对照

<5> Unicode 编码

Unicode:一种编码,将世界上所有的符号都纳入其中。

每一个符号都给予一个独一无二的编码,使用 Unicode 没有乱码的问题。

Unicode 的==缺点==:

  • Unicode 只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储:无法区别 Unicode 和 ASCII:计算机无法区分三个字节表示一个符号还是分别表示三个符号。
  • 每个字符都用三个或者四个字节存储,浪费了存储空间。

==Unicode向下兼容了ASCII码==

<6> UTF-8

UTF-8 是在互联网上使用最广的一种 Unicode 的实现方式。

UTF-8 是一种变长的编码方式。它可以使用 1-6 个字节表示一个符号,根据不同的符号而变化字节长度。

UTF-8的==编码规则==:

  • 对于单字节的UTF-8编码,该字节的最高位为0,其余7位用来对字符进行编码(等同于ASCII码)。
  • 对于多字节的UTF-8编码,如果编码包含 n 个字节,那么第一个字节的前 n 位为1,第一个字节的第 n+1 位为0,该字节的剩余各位用来对字符进行编码。在第一个字节之后的所有的字节,都是==最高两位为”10”==,其余6位用来对字符进行编码。

<7> ASCII码

在计算机内部,所有数据都使用二进制表示

一共规定了128个字符

//使用ASCII码对应数值
char c9 = 97;//a
char c10 = 65;//A

<8> 布尔类型:boolean

只有两个值

//常常在循环结构,条件判断结构中使用
boolean bo1 = true;
boolean bo2 = false;
if(bo1){
    System.out.println("true");
}
else{
    System.out.println("false");
}

2.4 运算规则

public class StringTest{
    public static void main(String[] args){
        int no = 10;
        String str = "abcdef";
        String str1 = str + "xyz" + no;//abcdefxyz10
        str1 = str1 + "123";//abcdefxyz10123
        char c = '国';
        
        double pi = 3.1416;
        str1 = str1 + pi;//abcdefxyz101233.1416
        boolean b = false;
        str1 = str1 + b;
        str1 = str1 + c;
        
        System.out.println("str1 = " + str1); 
    }
}

2.4.1 数据自动类型转换默认==int double==

  1. 当存储范围小的数据(变量/表达式/常量),赋值给存储范围大的变量时,自动升级为该存储范围大的类型

    double d = 12; 
    //容量大小指的不是数据类型占用空间的大小,指的的存储量的大小 long 与 float
       graph TD
    J(char)-->C
    A(byte)-->C
    B(short)-->C(int)
    C-->D(long)
    D-->F(float)
    F-->G(double)
  2. 当byte与byte,short与short,char与char,byte,short,char之间一起运算了,都会自动升级为int

    byte b1 = 1; 
    byte b2 = 2; 
    int sum = b1 + b2;//sum为int
  3. 当多种基本数据类型的数据一起混合运算,会自动升级为它们中大的类型

    char c = 'a';//97 
    int b = 12; 
    double d = 2.8; 
    double sum = c + b + d;
  4. 当基本数据类型的数据 与 String类型进行“+”拼接时,结果自动转为String类型。

    <1>String: 字符串,使用一对“”表示

    <2>String s1 = “ “;内部可以声明0个,1个或者多个字符

    <3>String只能和==8种基本数据类型==的变量做连接运算

    <4>运算的结果只能是String类型

    String str = 12 + ""; 
    int n1 = 10;
    String s2 = "abc";
    String s3 = n1+s2;
    boolean a = true;
    String s4 = s2 + b1;//结果也是拼接上
    System.out.println(s4);//abctrue
    
    System.out.println(s2+n1+b1);//字符串的拼接
    System.out.rintln(n1+b1+s2);//从左到右计算,而int无法与boolean做运算,发生编译失败
    System.out.println(n1 + (b1 + s2));
  5. boolean类型不和其他基本数据类型发生转换

2.4.2 强制类型转换

当存储范围大的数据(变量、表达式、常量)赋值给存储范围小的变量时,都是需要强制类型转换

强制类型转换,需要使用强转符==()==

注意:可能会溢出,或者损失精度

  • 截断
  • 越界
//例如: 
double d = 1.2; 
int num = (int)d;//强制类型转换

float f1 = 12.9F;//第一种精度损失
int num1 = (int)f1;
System.out.println(num1);//截断输出,输出结果是12

int num2 = 128;//第二种精度损失
byte l2 = (byte)num2;
System.out.println(l2);//输出结果-128
graph LR
double-->float-->long-->int-->short-->byte-->char

当某个数据(变量、表达式、常量)想要让它按照某个大的类型进行计算时,也 可以使用强制类型转换

//例如: 
int a = 1; 
int b = 2; 
double shang = (double)a/b; 

boolean和String都是无法通过强制类型转换为基本数据类型的值。

==练习==

练习1

System.out.println('*'+'\t'+'*');//93
System.out.println('*'+"\t"+'*');//正常,制表符
System.out.println('*'+'\t'+"*");//51*
System.out.println("*"+'\t'+'*');//正常

练习2

int num = 10;
char c = 'a';
String s = "hello";
System.out.println(num + c + s);//107hello
System.out.println(s + c + num);//helloa10
System.out.println(num + ( c + s));//10ahello

练习3

String str1 = 4;					//判断对错:对
String str2 = 3.5f + “”;  			//判断str2对错:对
System.out.println(str2);		    //输出:3.5
System.out.println(3 + 4 + "Hello!");   //输出:7hello!
System.out.println("Hello!"+ 3 + 4);   //输出:hello!34
System.out.println('a' + 1 +"Hello!"); //输出:98hello!
System.out.println("Hello" + 'a' + 1);  //输出:helloa1

2.4.3 关于常量

int num1 = 10;
num1 = 20;
//对于整型常量而言,默认是int类型
int num2 = num1 + 30;//结果是int类型
//byte num2 = num1 + 30;运行不过
//对于浮点型常量而言,默认是double类型
double b1 = b1 + 12.3;

2.4.4 进制

<1> 进制的表示

  • 二进制(binary):以0b或0B开头

  • 十进制(decimal):

  • 八进制(octal):以数字0开头表示。

  • 十六进制(hex):以0x或0X开头表示。此处的A-F不区分大小写。

    如:0x21AF +1= 0X21B0

<2> 原码补码反码

过程 原码 反码(除符号位取反) 补码(+1)
1 0000 0001 0000 0001 0000 0001
-1 1000 0001 1111 1110 1111 1111
0000 0000

<3> 十进制转二进制

无符号数十进制数转二进制算法

十进制数重复除以2,每次的余数记录下当做二进制数位的值,直到商为0为止

举例:十进制数57转为二进制数

除法 余数
57/2 28 1
28/2 14 0
14/2 7 0
7/2 3 1
3/2 1 1
1/2 0 1

把余数列数字反向排列就得到了二进制数111001,由于intel存储的二进制数位数总是8或者8 的倍数,因此前面的空位补0

57的二进制数是00111001

<4> 二进制转十进制

位权表示法

1 0 1 1 0 1 0 1
7 6 5 4 3 2 1 0
1*2^7^ 0*2^6^ 1*2^5^ 1*2^4^ 0*2^3^ 1*2^2^ 0*2^1^ 1*2^0^

算法:位权表示法把n位无符号数二进制整数转换为十进制数(不考虑正负数)
$$
dec = (D_{n-1}*2^{n-1})+(D_{n-2}*2^{n-2})+…+(D_{1}*2^{1}+(D_{0}*2^{0}))
$$

<5> 十进制转十六进制

无符号数十进制数转十六进制数算法:

十进制数重复除以16,每次的余数记录下来作为当前十六进制数位的值

直到商为0为止

举例:十进制数422转为十六进制数

除法 余数
422/16 26 6
26/16 1 A
1/16 0 1

把余数列数字反向排列就得到了十六进制数的1A6H,由于Intel存储的二进制数位数总是8或者8的倍数,因此前面空位补0, 422的十六进制为01A6H

<6> 十六进制转十进制

1 6 A 7 9 4
0001 0110 1010 0111 1001 0100
1*16^5^ 6*16^4^ 10*16^3^ 7*16^2^ 9*16^1^ 4*16^0^

算法:位权表示法把n位无符号数十六进制整数转换为十进制数(不考虑正负数)
$$
dec = (D_{n-1}*16^{n-1})+(D_{n-2}*16^{n-2})+…+(D_{1}*16^{1}+(D_{0}*16^{0}))
$$

<7>二进制转八进制

  • 三个一位

<8>二进制转16进制

  • 四个一位

2.5 运算符

2.5.1 算术运算符

赋值运算符

比较运算符

逻辑运算符

运算符 运算 范例 结果
+ 正号 + 3 3
- 负号 b = 4; -b -4
+ 5 + 5 10
- 6 - 4 2
***** 3 * 4 12
/ 5 / 5 1
% 取模(取余) 7 % 5 2
++ 自增(前):先运算后取值 a = 2; b = ++a; a = 3;b = 3
++ 自增(后):先取值后运算 a = 2; b = a++; a = 3;b = 2
- - 自减(前):先运算后取值 a = 2; b = –a; a = 1;b = 1
- - 自减(后):先取值后运算 a = 2; b = a–; a = 1;b = 2
+ 字符串连接 “He” + ”llo” “Hello”

除法说明

//关于除法的一些说明
int m1 = 12;
int m2 = 5;//2
int m3 = m1/m2;//10
double m4 = (double) m1/m2;//2.4

整型获得数是整型

使用强制类型转换

模数符号说明

//关于模数的符号的了解
int m1 = 12%5;//
int m1 = -12%5;//-2
int m3 = 12%-5;
int m4 = -12%-5;//-2

符号与被模数相同

a++与++a说明

  • (前)++ :先自增1,在赋值

  • (后)++:先赋值,在自增1

  • –与++相同

image-20200724111548518

2.5.2 赋值运算符

class SetValues{
    public static void main(String[] args){
        //赋值运算符:=
        int a = 10;
        int b = 10;
        int a1,a2;
        a1 = a2 = 10;//连续赋值
        System.out.println("a="+a);
        System.out.println("b="+b);
        System.out.println("a1="+a1);
        System.out.println("a2="+a2);
        
        int a3 = 10, a4 = 10;
        System.out.println("a3="+a3);
        System.out.println("a4="+a4);
        
        int m = 10;
        m += 5;//m = m + 5
        System.out.println("m = " + m);
        //+= -= *= %=
    }
short m;
m += 5;
m = m + 5;//不相同
m =(short)(m=5);

两者有点类似

==总结==:开发中如果需要变量自增2,建议使用:+=2,而不是使用+2

int i = 10;
i += 5.0;//这个相同于
//i = (int)5.0;
System.out.println("i=" + i);

i+=与普通的运算式明显不同,可以理解为+=的过程中,会以 i 的类型对+=之后的数发生强制类型转换。

==练习==

<1> 示例1

int m1 = 2;
int n1 = 3;
n1 *= ++m1;
System.out.println("m1="+m1);//3
System.out.println("n1="+n1);//9

<2> 示例2

int n3 = 10;
n3 += (n3++) + (++n3);//n3 = n3 + (n3++) + (++n3)
System.out.println(n3);//32

在进行++或者–运算式,(a++) 这个整体已经自增1,但是赋值的情况下是先赋值,但是在进行四则运算的时候就不一样了。

<3> 示例3

多项运算式的变换

从左往右依次计算

int k = 0,j = 0;
j += k* = j += (k = 1)/(j = 1);
上述正则表达式可以进行简写
j = j + (k = k * (j = j + (k=1)/(j=1)))
System.out.println(j);//0
int k = 0;
int j = 0;
j += ++k + (++k +j);
上述可以改写成
    j = j + (++k + (++k +j))
    j = 0 + (1 + (2+0))
System.out.println(j);//3

2.5.3 比较运算符

运算符 运算 范例 结果
== 相等于 4==3 false
!= 不等于 4!=3 true
< 小于 4<3 false
> 大于 4>3 true
<= 小于等于 4<=3 false
>= 大于等于 4>=3 true
instanceof 检查是否是类的对象 “Hello” instanceof String true

说明

运算的结果是布尔类型

区分赋值符号 == =

System.out.println(a == b);//false
System.out.println(a = b);//b

2.5.4 逻辑运算符

a b a&b 且true a&&b 且true a|b a||b !a a^b 不同为true
true true true true true true false false
true false false false true true false true
false true false false true true true true
false false false false false false true false

逻辑运算符使用

&逻辑与

&&短路与

说明

操作的是boolean类型变量

运算的结果也是boolean类型

区分&与&&

boolean b1 = false;
boolean b2 = true;
int m1 = 10;
if(b1 & (m1++>0)){
    System.out.println("执行if结构");
}else{
    System.out.println("执行else结构");
}
System.out.println("m1="+m1);

int m2 = 10;
if(b1 && (m2++>0)){
    System.out.println("执行if结构");
}else{
    System.out.println("执行else结构");
}
System.out.println("m2="+m2);

int m3 = 10;
if(b2 | (m3++>0)){
    System.out.println("执行if结构");
}else{
    System.out.println("执行else结构");
}
System.out.println("m1="+m1);

int m4 = 10;
if(b2 || (m4++>0)){
    System.out.println("执行if结构");
}else{
    System.out.println("执行else结构");
}
System.out.println("m2="+m2);

& 左边是false,依然执行右边

&&左边是false,不执行右边操作(短路)

|左边是true,依然执行右边

||左边是true,不执行右边操作(短路)

结论

开发中,建议大家使用短路情况

image-20200724111722689

2.5.5 位运算符

位运算符
运算符 运算 范例
<< 左移 3 << 2 = 12 –> 322=12
>> 右移 3 >> 1 = 1 –> 3/2=1
>>> 无符号右移 3 >>> 1 = 1 –> 3/2=1
& 与运算 6 & 3 = 2
| 或运算 6 | 3 = 7
^ 异或运算 6 ^ 3 = 5
~ 取反运算 ~6 = -7

说明:

针对于整数,不管整数还是负数:左移一位乘以2,当最高位左移到边缘时,符号位发生了变化。

针对于整数,不管是正数还是负数,在一定范围内,只要向右移动了一位。就相当于/2

-7>>1;//-4

无符号位移>>>补0,用来取最小四位,如1111 0000 >>>4 = 0000 1111

增加1.5倍的算法,使用位运算

10+15;
10+10>>1;//除以2的倍数

==练习==

如何手动实现整型数值60的二进制到十六进制的转换

//[answer]
String str1 = Integer.toBinaryString(60);
String str2 = Integer.toHexString(60);
int i1 = 60;
int i2 = i1&15;
//通过二进制数0000 1111,获取60二进制数的低四位
String j = (i2 > 9)?(char)(i2-10+'A'):i2 + "";

int temp = i1>>>4;//无符号进位4
//通过无符号数位移获得i1的高四位二进制数
i2 = temp & 15;
String k = (i2 > 9)?(char)(i2-10+""):i2+"";
System.out.print(k+""+j);

==交换两者的值==

int m = 4;
int n = 2;
//方式1
int temp = m;
	   m = n;
	   n = temp;//注意斜的是一样的
//方式2,字符串类型不通用
m = m + n;
n = m - n;
m = m - n;
//方式3,使用位运算
m = m^n;//拿出两者的不同
n = m^n;//把其中一个不同改变
m = m^n;

在使用位运算中

m^n = a;//m^n^n = m
a^n = m;//n^m^m = n
int m = 4 0000 0100
int n = 2 0000 0010
进行异或位运算 1111 1001
在与2进行异或运算 0000 0100

可以预见,在与一个数进行两次异或运算后,会变成自己的本身

2.5.6 三元运算符

说明:

  • 条件表达式是 boolean 类型
  • 如果条件表达式是true,则返回表达式1
  • 如果条件表示式是false,则返回表达式2

要求表达式1和表达式2要一致

使用三元运算符的地方都可以改写为if-else

==关于三元运算符比较两者相同条件下的注意事项==:

int m = 20;
int n = 20;
String max = (m > n)?"m大":"n大";
System.out.println(max)//n

在相同的条件下 n大

可以嵌套

//求a1,a2,a3的最大值
int max1 = (a1 > a2)?a1:a2;
int max2 = (max1 > a3)?max1:a3;
int max1 = (((a1 > a2)?a1:a2;) > a3)?((a1 > a2)?a1:a2;):a3;

在能使用三元运算符的情况下建议使用三元运算符

因为三元运算符执行效率稍微高一点

2.5.7 运算符的优先级

()

2.6 程序控制流程

==2.6.0== 如何获取==键入==的不同类型的数据

使用Scanner来实现

操作步骤

  • 导入包
  • 实例化Scanner
  • 根据需要获取变量的类型,调用相关的方法即可
import java.util.Scanner;
class ScannerTest{
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);//实例化

        System.out.println("请输入姓名");
        String name = scan.next();//获取一个字符串
        System.out.println("name=" + name);
        
        System.out.println("请输入年龄");
        int age = scan.nextInt();//获取一个Int类型
        System.out.println("name=" + age);
        
        System.out.println("请输入体重");
        Double weight = scan.nextDouble();//获取一个浮点类型
        System.out.println("wieght=" + weight);
        
        System.out.println("婚否(true/false)");
        boolean isMarried = scan.nextBoolean();//获取一个布尔类型
        System.out.println("isMarried?" + isMarried);

		System.out.println("请输入您的性别");
        String gender = scan.next();
		char chargender = gender.charAt(0);//获得索引为0的字符
        System.out.println("gender:" + chargender);
    }
}

流程控制语句是用来控制程序中各个语句执行顺序的语句,可以把语句组合成 能完成一定功能的小逻辑模块

其流程控制方式采用结构化程序设计中规定的三种基本流程结构

  • 顺序结构

  • 分支结构

    if...else;
    switch-case;
  • 循环结构

    while;
    do...while;
    for;

    2.6.0 如何使用==equals==判断字符串相等

if(height >= 180 && wealth >= 1 && "是".equals(isHandsome));
import java.util.Scanner;
class Dome10Equals{
	public static void main(String[] args){
		Scanner input = new Scanner(System.in);
		String iftest = input.next();
		boolean a = "是".equals(iftest);//如果字符串相等,则会返回true
		System.out.println(a);
	}
}

2.6.0 如何使用==Math.random()==获得随机数

如何获取一个随机数?

  1. 通过调用==Math.random()==,可以获取一个[0,1)范围的内的double型的随机数

  2. 获取[10,99]范围内的随机数?
    int number = (int)(Math.random() * 90 + 10);

  3. 获取[100,999]范围内的随机数?
    int number = (int)(Math.random() * (999-100+1) + 100);

总结:获取[a,b]范围的随机数的公式
$$
(int)(Math.random() * (b - a + 1) + a);
$$

int a = Math.random();
int b = (int)(Math.random()*90 + 10);//[10,100)
//获得三位随机数
int c = (int)(Math.random()*(1000-100)+100);//[100,1000)

int b = (int)(Math.random()*91 + 10);//[10,100]
//获得三位随机数
int c = (int)(Math.random()*(1001-100)+100);//[100,1000]

2.6.1 顺序结构

()

2.6.2 分支语句

条件表达式必须是布尔表达式(关系表达式或者逻辑表达式)、布尔变量

语句块只有一条执行语句,一对{}可以省掉,但是建议保留

if-else语句结构根据需要可以嵌套使用

当if-else结构是“多选一”时,最后的else是可选的。根据需要可以省掉

只执行一块代码,

执行完毕后,跳出当前if-else语句

if-else不能80<socre<90

因为80与score相比已经是布尔型,布尔型不能跟90运算,所以报错

示例

class Iftest{
    public static void main(String[] args){
        int heatBeats = 80;
        if(heartBeats<60||heartBeats>100){
            System.out.println("you need continue test");
        }
        System.out.println("体检结束");
    }  
}

else根据需要来选

例题1

/**
	岳小鹏参加Java考试,他和父亲岳不群达成承诺:
	如果:
		成绩为100分时,奖励一辆BMW;
		成绩为(80,99]时,奖励一台iphone xs max;
		当成绩为[60,80]时,奖励一个 iPad;
        其它时,什么奖励也没有。
	请从键盘输入岳小鹏的期末成绩,并加以判断
*/
import java.util.Scanner;//导包

class Demo5
{
   public static void main(String[] args)
   {
       Scanner grades = new Scanner(System.in);
       System.out.print("请输入您测试得到的分数:");//给用户提示
       int grade = grades.nextInt();
       if (grade == 100){
           System.out.println("Congratlations ! You get BMW*1");
       }else if(grade>80){
           System.out.println("Congratlations ! You can get iphone xs max*1");
       }else if(grade>60){
           System.out.println("Don't worry! You can get iPad*1");
       }else{
           System.out.println("What a pity! You can't get anything");
       }
   }
}

说明

如果多个表达式彼此之间是“互斥”关系,(即:没有交集),则那个条件在上,那个在下无所谓

如果多个条件表达式彼此之间是“包含”关系,则需要将条件表达式范围小的声明在条件表达式范围大的上面

例题2 ==三个数字排序并输出==

/**
编写程序:由键盘输入三个整数分别存入变量num1、num2、num3,对它们进行排序(使用 if-else if-else),并且从小到大输出。
*/
import java.until.Scanner;

class Demo
{
  public static void main(String[] args)
  {
      Scanner num = new Scanner(System.in);
      System.out.print("请输入num1");
      int num1 = num.nextInt();
      System.out.print("请输入num2");
      int num2 = num.nextInt();
      System.out.print("请输入num3");
      int num2 = num.nextInt();
      
      if(num1>num2)
      {
          int temp = num1;
          num1 = num2;
          num2 = temp;
      }
      if(num2>num3)
      {
          int temp = num2;
          num2 = num3;
          num3 = temp;
      }
      if(num1>num2)
      {
          int temp = num1;
          num1 = num2;
          num2 = temp;
      }
  }
}

2.6.3 分支语句2 Switch-case结构

switch(表达式)
{
    case 常量1:
        语句1;
        break;
    case 常量N:
        语句N;
        break;
    default:
        语句;
        break;
}       

break跳出当前switch-case结构

否则不会跳出代码块,依次执行

执行过程:根据switch中表达式的值,依次匹配各个case中的常量

当与某个常量匹配上时,就进入case中

调用case语句,执行完之后,依然会考虑继续执行器后的case中的结构,直接预见break

示例

public class Switchtest
{
    public static void main(String args[])
    {
        int i = 1;
        switch(i)
        {
            case 0:
                System.out.println("zreo");
                break;
            case 1:
                System.out.println("one");
            default:
                System.out.println("default");
                break;//可写可不写
        }
    }
}

局限性:

switch中的表达式的值可以是如下类型。类型存在限制,

比如==boolean类型==与==double类型==不能使用

byte;
short;
char;
int;
枚举类(jak5.0新增);
String(jdk7.0新增);

表示范围不方便,但是可以通过除法进行判断,如示例1所示

case语句还可以进行合并,如示例1所示

//示例1
//对学生成绩大于60分的,输出“合格”。低于60分的,输出“不合格”
switch(score/10){
    case 0:
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
        System.out.println("不及格");
        break;
    case 6:
    case 7:
    case 8:
    case 9:
    case 10:
        System.out.println("及格");
        break;
}
//更加注意switch结构灵活运用
switch(score/10){
    case 1:
        System.out.println("及格");
    case 0:
        System.out.println("不及格")
}

匹配的case通常不能太多

关于==default==默认值(相当于if-else里面的else)

可选的,位置也是灵活的

但是即使在中间插入default也是最后判断default,一般不这么书写

==建议==在什么情况下使用switch

switch-case可以完全转换为if-else,if-else不一定能够改写成switch-case。比如boolean类型Switch-case结构不能使用

如果判断的具体数值不多。而且符合byte、short、char、int、String、枚举等几种类型。

虽然两个语句都可以使用,建议使用switch语句。因为效率稍高。

其他情况:对区间判断,对结果为 boolean 类型判断,使用if,if的适用范围更广,也就是说,使用switch-case的,都可以改写成if-else。反之不成立

结论:如果多个语句的相同可以自动往下走,考虑合并

例题:日期与天数//Switch-case的不用break的条件

编写程序:从键盘上输入2020年的“month”和“day”,要求通过程序输出输入的日期为2020年的第几天。

import java.util.Scanner;

class Demo11Month 
{
	public static void main(String[] args) 
	{
		Scanner scan = new Scanner(System.in);
		System.out.println("请您输入月份");
		int month = scan.nextInt();//输入几月
		System.out.println("请您输入日子");
		int day = scan.nextInt();//输入几号

		int sumDays = 0;//记录总天数
		switch(month){
			case 12:
				sumDays += 30;
			case 11:
				sumDays += 31;
			case 10:
				sumDays += 30;
			case 9:
				sumDays += 31;
			case 8:
				sumDays += 31;
			case 7:
				sumDays += 30;
			case 6:
				sumDays += 31;
			case 5:
				sumDays += 30;
			case 4:
				sumDays += 31;
			case 3:
				sumDays += 29;
			case 2:
				sumDays += 31;
			case 1:
				sumDays += day;
		}
		System.out.println(month + "月份" + day + "日为本年的第"+sumDays+"天");
		System.out.println("Hello World!");
		}
}

例题:判断case里面嵌套if-else判断是否为闰年

从键盘分别输入年、月、日,判断这一天是当年的第几天

import java.util.Scanner;

class Demo11Month 
{
	public static void main(String[] args) 
	{
        Scanner scan = new Scanner(System.in);
        
		System.out.println("请您输入年份");
		int year = scan.nextInt();//输入年份
		System.out.println("请您输入月份");
		int month = scan.nextInt();//输入几月
		System.out.println("请您输入日子");
		int day = scan.nextInt();//输入几号

		int sumDays = 0;//记录总天数
		switch(month){
			case 12:
				sumDays += 30;
			case 11:
				sumDays += 31;
			case 10:
				sumDays += 30;
			case 9:
				sumDays += 31;
			case 8:
				sumDays += 31;
			case 7:
				sumDays += 30;
			case 6:
				sumDays += 31;
			case 5:
				sumDays += 30;
			case 4:
				sumDays += 31;
			case 3:
                if(year%4==0&&year%100!=0||year%400==0){//判断是否为闰年
                   sumDays += 29; 
                }else{
					sumDays += 28;
                }
			case 2:
				sumDays += 31;
			case 1:
				sumDays += day;
		}
		System.out.println("今天是"+year+"年"+month + "月" + day + "日,为本年的第"+sumDays+"天");
		System.out.println("Hello World!");
		}
}

2.6.4 循环结构1 for

反复执行特定程序

循环语句的四个组成成分

  • 初始化部分(init_statement)
  • 循环条件部分(test_exp)
  • 循环体部分(body_statement)
  • 迭代部分(alter_statement)

for 循环执行演示

for(int i = 1(第一步);i <= 100(第二步);i++(第四步))
{
    System.out.println(i);(第三步)
}

例题 输出十行hello world

for(int i = 1;i<=10;i++){
    System.out.println(i);
}

例题 遍历100以内的偶数

int sum;//记录总和
int count;
for(int i = 0;i<=100;i+=2){
    System.out.println(i);
    sum += i;
    count++;
}
System.out。println("总和为:"+sum);
System.out。println("偶数的个数为:"+count);

例题 遍历1-150倍数打印

编写程序从1循环到150,并在每行打印一个值,

另外在每个3的倍数行上打印出"foo"

在每个5的倍数行上打印"biz"

在每个7的倍数行上打印输出"baz"

class Demo12for{
    public static void main(String[] args){
        for(int i = 1;i<=150;i++){
            if(i%3==0){//判断3
                System.out.print("foo "+i);
            }else if(i%5==0){//判断5
                System.out.print("biz "+i);
            }else if(i%7==0){//判断7
				System.out.print("baz "+i);
			}else{
				System.out.println(i);
            }
        }
        System.out.println("Hello World!");
    }
}

例题 计算最大==公约数==与最小公倍数

输入两个正整数m和n,求其最大公约数和最小公倍数。

比如:12和20的最大公约数是4,最小公倍数是60。

说明: break 关键字的使用

总结:结束循环的方式都有哪些?

  1. 循环条件中不满足,返回false
  2. 使用break关键字
class  ForTest1{
    public static void main(String[] args){
        int m = 12;
        int n = 20;
        
        //获取连个数的较小值
        int min = (m>n)?n:m;
        
        for(int i = min;i>=1;i--)
            if(m%i==0&&n%i==0){//判断最大公约数
                System.out.println("最大公约数:"+i);
                break;
            }
                
        //获取两个数的较大值
        int max = (m>n)?m:n;
        for(int i = max; i <=m*n ;i++){
            if(i%m==0&&i%n==0){//判断最小公倍数
                System.out.printlnm("最小公倍数:"+i);
                break;
            }
        }
        
    }

2.6.5 循环结构2: while循环

for循环和while循环是可以相互转换的

关于初始化条件部分

while循环在执行结束后

初始化条件中涉及到的变量仍然可用

public class WhileLoop{
    public static void main(String args[]){
        int result = 0;
        int i = 1;(第一步)
        while (i <= 100){(第二步)
            result += 1;(第三步)
            i++;(第四步)
        }
        System.out.println("result = " = result);
    }
}

while循环与for循环的i的作用域不同

2.6.6 循环结构3:do-while循环

public class DoWhileLoop
{
    public static void main(String args[])
    {
        int result = 0;
        int i = 1;
        do
        {
            result += i;
            i++;
        }while(i <= 100);
        System.out.println("result = " + result);
    }
}

说明

do-while至少执行一次循环体

从事开发应用频次较低

//遍历100以内的偶数
int i = 0;
int sum =0;
int count = 0;
do{
    if(i%2==0){
        System.out.println(i);
        sum+=i;
        count++;
    }
    i++;
}while(i<=100);

2.6.7 嵌套循环

  1. 将一个循环结构A声明在另一个循环结构B的循环体中,则构成了嵌套循环

A:内层循环

B:外层循环

  1. 外层循环控制行数,内层循环控制列数

  2. 如果外层循环循环次数为m次,内层循环循环次数为n次,则可以理解为内层循环的循环体可以执行m*n次

    class Demo{
           public static void main(String[] args){
               for(int j = 1; j <= 5; j++){
                   for(int i = 1; i <= 6 ;i++){
                       System.out.print("*");
                   }
                   System.out.println();
               }
           }

2.6.8 特殊流程控制语句==(break)==与==(continue)==

使用场景

  • break只能用于switch语句和循环语句中。
  • continue 只能用于循环语句中。

continue:结束当次循环

break:结束当前循环 结束离break最近的for

class Demo{
    public static void main(String[] args){
        for(int i = 1; i <= 10;i++){
            if(i % 4 == 0){
                //break;//当前循环结束
                continue;//本次循环结束
                //System.out.println("今晚迪丽热巴要约我");
            }
            System.out.print(i);
        } 
    }
}

break、continue之后不能声明其他语句,程序永远不会执行其后的语句,否则会报错。

break越级结束指定标签对应的for

class Demo{
    public static void main(String[] args){
        label:for(int i = 1; i <= 10;i++){
            for(int j = 1; j <= 10; j++){
                if(i % 4 == 0){
                //break;//当前循环结束
                continue label;//本次循环结束
                //System.out.println("今晚迪丽热巴要约我");
            }
            System.out.print(i);
            }
        } 
    }
}

2.6.9 循环语句无限循环==for(;;)==与==while(true)==

从键盘读入个数不确定的整数,并且判断读入的正数与负数的个数,输入为0时结束程序

无限循环的存在的原因是并不知道循环多少次

需要根据循环体内部的某些条件来控制循环的结束

for(;;)//while(true){
    Scanner.out.println("");
    int num = scan.nextInt();
    if(num>0)
        positiveNumber++;
    else if(num<0)
        negativeNumber++;
    else
       break;
}

打印**

/*
******
******
******
******
******
*/
class Demo14{
    public static void main (String[] args){
        for(int i = 0 ; i < 5 ; i++){
            for(int j = 0;j < 6 ; j++){
                System.out.print("*");
            }
            System.out.println();
        }
    }
}

打印倾斜**

/*
*
**
***
****
*****
*/
class Demo15{
    public static void main (String[] args){
        for(int i = 0 ; i < 5 ; i++){
            for(int j = 0 ;j <=i ; j++){
                System.out.print("*");
            }
            System.out.println();
        }
    }

类似于数轴

打印倒着的**

/*
**** 
***
**
*
*/
class Demo{
    public static void main(String[] args){
        for(int i = 1; i <= 4; i++){
            for(int j = 1;j <= 5-i;j++){
                System.out.print("*");
            }
            System.out.println();
        }
    }
}

打印上下两种部分**

打印**要注意i与j的数量,分析内层j与外层循环的关系

public class PrintStarTest {
    public static void main(String[] args) {
        /*
           i    j
    *****  1    5
    ****   2    4
    ***    3    3
    **     4    3
    *      5    1   所以内层循环每次都是从j=1打印到6-i次为止
         */
        for(int i = 1; i <= 5 ; i++){
            for(int j = 1; j <= 6-i ; j++){
                System.out.print("*");
            }
            System.out.println();
        }

        /*
        上半部分    i   j(-)    k(*)
        ----*      1    4       1
        ---* *     2    3       2
        --* * *    3    2       3
        _* * * *   4    1       4
        * * * * *  5    0       5

        下半部分   i     j      k
        * * * *   1     4      0
        -* * *    2     3      1
        --* *     3     2      2
        ---*      4     1      3
         */

        //上半部分的打印
        for(int i = 1; i <= 5 ; i++){
            for(int j = 1; j <= 5-i ; j++){
                System.out.print("-");
            }
            for(int k = 1; k <= i ; k++){
                System.out.print("* ");
            }
            System.out.println();
        }
        System.out.println("######################");
        //下半部分的打印
        for(int i = 1; i <= 4 ; i++){
            for(int j = 1; j < i ; j++){
                System.out.print(" ");
            }
            for(int k = 1; k <= 5-i ; k++){
                System.out.print("* ");
            }
            System.out.println();
        }
    }
}

2.6.10 嵌套循环的==经典例题==

九九乘法表

class Demo{
    public static void main(String[] args){
    
    for(int i = 1;i <= 9;i++){
        for(int j = 1;  j<=i ;j++){
            System.out.print(j + " * " + i + " = " +  i * j);
        }
        System.out.println();
    }
    }
}

100以内所有的质数自然数

题目:100以内的所有质数

质数(或素数):只能被1和他本身整除的自然数

比如:2, 3, 5,7,11,13,17,19,23,… 59

class Demo{
    public static void main(String[] args){
        boolean isFlag = true;
        for(int i = 2;i <= 100; i++){    
            for(int j = 2;j < i;j++){
                if(i % j == 0){
					isFlag = false;
                }
            }
            //判断isFlag是否曾经被赋值为false
            if(isFlag){
                System.out.println(i);
            }
			
			isFlag = true;//重置
        }
    }
}

遍历100 000以内的质数,判断执行效率,利用==毫秒数==

public class PrimeNumberTest1 {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        boolean isPrime = true;
        int count = 0;
        for(int i = 2; i < 100000 ; i++){
            for(int j = 2; j < Math.sqrt(i); j++){//优化1 j < i
                if (i % j == 0) {
                    isPrime = false;
                    break;//优化2
                }
            }
            if(isPrime){
                System.out.println(i);
                count++;
            }
            isPrime = true;
        }
        long end = System.currentTimeMillis();
        System.out.println("质数个数为:"+count);
        System.out.println("花费的总时间为(毫秒):" + (end - start));
    }//17298-211
}

关于math.sqrt的理解

image-20200727195449007

对于100以内的数可以说,取半就可以判断是否存在因子,但是50*2=100,

判断了2就无需在判断50,因此判断了10之后,就无需继续往后判断

第3章 数组

3.1 数组的概述

数组(Array),是多个相同类型数据按一定顺序排列的集合,

并使用一个名字命名,

并通过编号的方式对这些数据进行统一管理。

数组的常见概念

  • 数组名
  • 下标(或索引)
  • 元素
  • 数组的长度

3.2 一维数组的使用

3.2.1数组的声明和初始化

格式:数据类型 变量名 = 变量值

int num = 10;
String str = "Hello";

//声明
String[] foods ;
//初始化
foods = new String[]{"拌海蜇","龙须菜","西湖醋鱼"};
//声明并初始化
//静态初始化:数组变量的赋值操作与数组元素的赋值操作同时进行
String[] names = new String[]{"李金鑫""刘志引""徐德三"};
//可以简写为:String[] names = {"李金鑫","刘志引","徐德三"};

//动态初始化:数组变量的赋值操作与数组元素的赋值操作分开进行
double[] prices = new double[5];

简单整理

数组的声明;

double[] prices = new double[]{2,5,6,2}; 静态数组
double[] prices = new double[5]; 动态数组
  
int[] arr = {1,2,3};类型推断

数组一旦初始化(不管是静态还是动态初始化,长度就已经确定了)

数组一旦初始化,(不管是静态还是动态初始化,其长度就是不可变的)

错误

//t[] arr = new double[5]{1,2,3,4,5};
//t[5] arr = new int[5];

3.2.2 如何调用数组的元素

通过角标的方式进行调用

角标从0开始

prices[0] = 12.3;

prices[1] = 23.4;

prices[5]越界报错

out of bounds exception

3.2.3 如何获得数组的长度

属性:length

System.out.println(prices.length)

3.2.4 如何遍历数组元素

for(int i = 0; i < names.length ; i ++){
    System.out.println(names[i]);
}

3.2.5 数组元素的默认初始化值

double[] prices = new double[5];
price[0] = 12.3;
prices[1] = 24.4;
System.out.println(price[2]);

int[] arr = new int[]{1,2,3,4};
SYstem.out.println(arr[0]);

以动态初始化值为例

数组元素类型 元素默认初始值
byte 0
short 0
int 0
long 0L
float 0.0F
double 0.0
char 0 或写为:’\u0000’(表现为空)
boolean false
引用类型 null

3.2.6 一维数组的==内存解析==

栈(stack)特点: 先进后出

堆(heap)

方法区

image-20200727163646089

一个方法对应一个栈针

image-20200727195800509

内存内的运行方式具体如图所示,在内存中,栈主要存储数组的索引,也就是数组的第一个值

然后数组的具体存储在堆中,通过赋值对栈与堆具体的数值不断更新

通过定义数组后,原来的索引失效,java会自动垃圾回收

当main函数执行完毕后,会自行发生垃圾回收

image-20200727202413804

练习1

升景坊单间短期出租4个月,550元/月(水电煤公摊,网费35元/月),空调、卫生间、厨房齐全。屋内均是IT行业人士,喜欢安静。所以要求来租者最好是同行或者刚毕业的年轻人,爱干净、安静。

public class ArrayTest {
      public static void main(String[] args) {
      int[] arr = new int[]{8,2,1,0,3};
      int[] index = new int[]{2,0,3,2,4,0,1,3,2,3,3};
      String tel = "";
      for(int i = 0;i < index.length;i++){
            tel += arr[index[i]];
      }
      System.out.println("联系方式:" + tel);
      }
}

练习2

从键盘读入学生成绩,找出最高分,并输出学生成绩等级。

  1. 成绩>=最高分-10 等级为’A’
  2. 成绩>=最高分-20 等级为’B’
  3. 成绩>=最高分-30 等级为’C’
  4. 其余 等级为’D’

提示:先读入学生人数,根据人数创建int数组,存放学生成绩。

import java.util.Scanner;
/**
 * @author Jinxin Li
 * @create 2020-07-27 21:02
 * 从键盘读入学生成绩,找出最高分,并输出学生成绩等级。
 * 	成绩>=最高分-10    等级为’A’
 * 	成绩>=最高分-20    等级为’B’
 * 	成绩>=最高分-30    等级为’C’
 * 	其余                        等级为’D’
 * 提示:先读入学生人数,根据人数创建int数组,存放学生成绩
 */
public class HomeWork3 {
    public static void main(String[] args) {
       Scanner scan = new Scanner(System.in);
       System.out.print("请输入学生人数");
       int studentsNum = scan.nextInt();

       //定义数组学生成绩
       int arr[] = new int[studentsNum];
       int i = 0;
       System.out.println("请您输入" + studentsNum + "个成绩");
       while(i < studentsNum){
            int studentScore = scan.nextInt();
            arr[i] = studentScore;
            i++;
        }

       //找出最高分
        int max = 0;
        for(int j = 0; j < arr.length; j++ ){
            if(arr[j] > max){
                max = arr[j];
            }
        }
        System.out.println("最高分是" + max);

        //判断输出学生成绩
        for(int j = 0;j < arr.length; j++){
            if(arr[j]>(max - 10)){
                System.out.println("student "+ j + " score is " + arr[j] + " grade is A");
            }else if(arr[j]>(max - 20)){
                System.out.println("student "+ j + " score is " + arr[j] + " grade is B");
            }else if(arr[j]>(max - 30)){
                System.out.println("student "+ j + " score is " + arr[j] + " grade is C");
            }else
                System.out.println("student "+ j + " score is " + arr[j] + " grade is D");
        }

    }
}

3.3 多维数组的使用

多维数组中主要讨论二维数组

3.3.1 二位数组的声明和初始化

静态初始化

int[][] arr = new int[][]{{1,2,3},{4,5},{6,7,8},{9,8,6,4}};

String[][] arr1 = new String[][]{{"陈伟霆","刘诗诗"},{"周笔畅"}};

动态初始化

//1
int[][] arr3 = new int[4][3];
//2
int[][] arr4 = new int[4][];
//继续定义arr4[0]第一个子数组
arr4[0] = new int[3];

/*
与一维数组的定义类似
double[] prices = new double[5];
*/

//也是正确的结构
int arr5[] = new int[5];
int arr6[][] = new int[5][7];
int[] arr7[] = new int[5][7];

关于动态初始化2

arr4[0][0] = 10;
//无指针异常

image-20200728102930202

3.3.2 二维数组元素的调用

通过角标的使用调用二维数组的元素

System.out.println(arr[1][2]);

arr3[4][3] = 10;

3.3.3 二维数组的长度

System.out.println(arr3.length);
//4,二维数组外层数组的长度
System.out.println(arr[0].length);
//3, 二维数组内层数组的长度

3.3.4 二维数组的遍历

for(int i = 0 ; i < arr1.length ; i++){
    for(int j =0 ; j < arr1[i].length; j++)
    {
        System.out.print(arr1[i][j] +"\t");
    }
    System.out.println();
}

3.3.5 二位数组元素的默认初始化值

强类型 = 赋予什么类型,使用什么类型

对于二维数组来说,约定称谓:

外层元素,比如:arr1[0],arr1[1]

存储代表的一维数组的地址值(类型)

内层元素:比如:arr1[0][1] arr1[1][0]

存储与一维数组一样

外层地址打印输出地址+

int[][] arr1 = new int[4][4]
Ststem.out.println(arr1[0]);//外层数组打印输出地址
//引用类型默认初始化类型为null
int[][] arr3 = new int[4][];
System.out.println(arr3[0]);//null
System.out.println(arr3[0][0]);//空指针异常

arr3[0] = new int[4];
arr3[1] = new int[]{1,2,3}
arr3[2] = new int[5];
arr3[3] = new int[6];

3.3.6 二维数组的内存解析

image-20200728182924337

==练习2== 求数组所有元素的和

获取arr数组中所有元素的和。

提示:使用for的嵌套循环即可。

j i j = 0 j = 1 j = 2 j = 3
i = 0 3 5 8 -
i = 1 12 9 - -
i = 2 7 0 6 4
/**
 * @author Jinxin Li
 * @create 2020-07-28 18:31
 * 求数组中元素的和
 */
public class SumArrays {
    public static void main(String[] args) {
        int[][] array = new int[][]{{3,5,8},{12,9},{7,0,6,4}};

        int sum = 0;//定义求和
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array[i].length; j++) {
                sum += array[i][j];
            }
        }
        System.out.println("和为:" + sum);
    }
}

==练习3== 判断是否成立

类型相同,模式相同
声明:int[] x,y[]; 在给x,y变量赋值以后,以下选项允许通过编译的是: 
a )   x[0] = y;        no
b)    y[0] = x;        yes
c)    y[0][0] = x;     no
d)    x[0][0] = y;     no
e)    y[0][0] = x[0];  yes
f)    x = y;           no

提示:
一维数组:int[] x  或者int x[]   
二维数组:int[][] y 或者  int[] y[]  或者 int  y[][]

赋值符号左右两边类型相同

练习4 10行杨辉三角 二维数组

  1. 第一行有 1 个元素, 第 n 行有 n 个元素

  2. 每一行的第一个元素和最后一个元素都是 1

  3. 从第三行开始, 对于非第一个元素和最后一个元素的元素。即:

//从第三行开始,对于非第一个元素,数等于左上角元素+顶上元素
yanghui[i][j] = yanghui[i-1][j-1] + yanghui[i-1][j];

image-20200728114200227

package AfterClass;

/**
 * @author Jinxin Li
 * @create 2020-07-28 14:19
 */
public class YangHuiTriangle {
    public static void main(String[] args) {
        int[][] arr = new int[10][];

        //定义数组形状,赋值为0
        for (int i = 0; i < arr.length; i++){
            for (int j = 0; j <= i; j++) {
                arr[i] = new int[i+1];//每一个内层数组都有i+1的元素
            }
        }

        //为外层赋值
        for(int i = 0; i < arr.length; i++){
            arr[i][0] = 1;
            arr[i][i] = 1;
        }

        //为内层赋值
        for(int i = 2; i <arr.length;i++){
            for (int j = 1; j < arr[i].length-1; j++) {//最后一个数与第一个数不需要赋值
                arr[i][j] = arr[i-1][j-1] + arr[i-1][j];
            }
        }

        //输出数列
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr[i].length; j++) {
                System.out.print(arr[i][j] + "\t");
            }
            System.out.println();
        }
    }
}

练习 5 各不相同的彩票随机数

创建一个长度为6的int型数组,要求数组元素的值都在1-30之间,且是随机赋值。同时,要求元素的值各不相同。 Math.random()

package AfterClass;

/**
 * @author Jinxin Li
 * @create 2020-07-28 8:25
 * 创建一个长度为6的int型数组,
 * 要求数组元素的值都在1-30之间,且是随机赋值。
 * 同时,要求元素的值各不相同。 Math.random()
 */
public class ChapterPractice {
    public static void main(String[] args) {
        int[] arr = new int[20];

        //赋值
        for(int i = 0;i < arr.length; i++){
                arr[i] = (int)(Math.random()*30 + 1);
                for (int j = 0; j < i; j++){
                    if(arr[i] == arr[j]){
                        i--;//利用i--表示返回思想
                        break;
                    }
                }
        }
        //输出
        for(int i = 0;i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
    }
}

练习 6 回形数

打印一个5的回形数

1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9

练习7 赋值与复制

使用简单数组

(1)创建一个名为ArrayTest的类,在main()方法中声明array1和array2两个变量,他们是int[]类型的数组。

(2)使用大括号{},把array1初始化为8个素数:2,3,5,7,11,13,17,19。

(3)显示array1的内容。

(4)赋值array2变量等于array1,修改array2中的偶索引元素,使其等于索引值(如array[0]=0,array[2]=2)。打印出array1。 array2 = array1;

public class ArrayTest {
    public static void main(String[] args) {
        
        int[] arr1,arr2;

        //静态初始化8个素数
        arr1 = new int[]{2,3,5,7,11,13,17,19};
        System.out.println("显示arr1的值");
        for (int i = 0; i < arr1.length; i++) {
            System.out.print(arr1[i] + "\t");
        }
        System.out.println();

        //赋值arr2
        arr2 = arr1;//单纯的传递地址,并没有new出新数列
        System.out.println("显示arr2的值");
        for (int i = 0; i < arr2.length; i++) {
            if(i%2 == 0) {
                arr2[i] = i;
            }
            System.out.print(arr2[i] + "\t");
        }
        System.out.println();

        System.out.println("显示arr1的值");

        for (int i = 0; i < arr1.length; i++) {
            System.out.print( arr1[i] + "\t");
        }
    }
}

结果:

显示arr1的值
2 3 5 7 11 13 17 19
显示arr2的值
0 3 2 7 4 13 6 19
显示arr1的值
0 3 2 7 4 13 6 19
Process finished with exit code 0

可以看出arr2改变之后,arr1也进行了改变,两者赋值给予的是地址

类似于,放置快捷方式

如果是复制才可以进行真正的赋值操作

数组的复制操作

//复制arr1到arr2
arr2 = new int[arr1.length];
for(int i = 0; i <arr2.length; i++){
    arr2[i] = arr1[i]
}

3.4 数组中涉及到的常见算法

3.4.1 数组元素的赋值(杨辉三角,回形数)

见上文

3.4.2 求数值的最大值,最小值,总和,平均数

见上文

3.4.3 数组的赋值、==反转==,==查找==(线性查找,二分法查找)

反转操作

package AfterClass;

/**
 * @author Jinxin Li
 * @create 2020-07-28 19:18
 * 反转数组
 * int[] arr1 = new int[]{3,4,5,3,6,23,6,84,4};
 */
public class ReverseArray {
    public static void main(String[] args) {
        int[] arr1 = new int[]{3,4,5,3,6,23,6,84,4};
//        打印数组
        for (int i = 0; i < arr1.length; i++) {
            System.out.print(arr1[i] + "\t");
        }

        System.out.println();
//        method1
        for (int i = 0; i < arr1.length/2; i++) {//交换到一半即可,除法向上取整数,所以不用担心
            int temp = arr1[i];
            arr1[i] = arr1[arr1.length-1-i];//两端交换,注意-1为最后一位索引,然后逐步往中间靠拢
            arr1[arr1.length-1-i] = temp;
        }

        for (int i = 0; i < arr1.length; i++) {
            System.out.print(arr1[i] + "\t");
        }
        System.out.println();
//        method2
        for (int i = 0,j = arr1.length; i < arr1.length/2; i++,j--) {//两边同时走
            int temp = arr1[i];
            arr1[i] = arr1[j-1];
            arr1[j-1] = temp;
        }
        for (int i = 0; i < arr1.length; i++) {
            System.out.print(arr1[i] + "\t");
        }
    }
}

查找操作-线性查找

package AfterClass;

/**
 * @author Jinxin Li
 * @create 2020-07-28 19:33
 * 线性查找
 */
public class LineSearch {
    public static void main(String[] args) {
        int[] arr3 = new int[]{3,14,5,13,6,23,16,84,4};

//        boolean Flag = true;//定义判断没找到的标志
        int i = 0;//此处把y放在外面
        for (; i < arr3.length; i++) {
            if(arr3[i] == 100){
                System.out.println("您要找的数"+arr3[i]+"的索引是"+i);
//                Flag = false;
                break;
            }

//        if(Flag){
//            System.out.println("未找到您要搜索的数值");
//        }
        }
        if(i == arr3.length){//若没有找到
            System.out.println("未找到您要搜索的数值");
        }
    }
}

二分法需要在有序数组中进行查找

package AfterClass;

/**
 * @author Jinxin Li
 * @create 2020-07-28 19:46
 */
public class BinarySearch {
    public static void main(String[] args) {
        int[] arr4 = new int[]{-99, -54, -2, 0, 2, 33, 43, 256, 999};
        boolean isFlag = true;
        int number = 256;
        int head = 0;//首索引位置
        int end = arr4.length - 1;//尾索引位置
        while(head <= end){
            int middle = (head + end) / 2;
            if(arr4[middle] == number){
                System.out.println("找到指定的元素,索引为:" + middle);
                isFlag = false;
                break;
            }else if(arr4[middle] > number){
                end = middle - 1;
            }else{//arr4[middle] < number
                head = middle + 1;
            }
        }

        if(isFlag){
            System.out.println("未找到指定的元素");
        }
    }
}

3.4.4 数组元素排序算法

排序

假设含有n个记录的序列为{R1,R2,…,Rn},

其相应的关键字序列为{K1,K2,…,Kn}。

将这些记录重新排序为{Ri1,Ri2,…,Rin},

使得相应的关键字值满足条Ki1<=Ki2<=…<=Kin,这样的一种操作称为排序。

通常来说,排序的目的是==快速查找==。

排序算法的优劣

1.时间复杂度:分析关键字的比较次数和记录的移动次数

2.空间复杂度:分析排序算法中需要多少辅助内存

3.稳定性:若两个记录A和B的关键字值相等,但排序后A、B的先后次序保持不变,

则称这种排序算法是稳定的{4,4-1},排完序列发现{4-1,4}称为不稳定的

排序方法分类

内部排序:整个排序过程不需要借助于外部存储器(如磁盘等),所有排序操作都在内存中完成。

外部排序:参与排序的数据非常多,数据量非常大,计算机无法把整个排序过程放在内存中完成,必须借助于外部存储器(如磁盘)。外部排序最常见的是多路归并排序。可以认为外部排序是由多次内部排序组成。

十大内部排序方法

  • 选择排序
  • 直接选择排序、==堆排序==
  • 交换排序
  • ==冒泡排序==**、==快速排序==**
  • 插入排序
  • 直接插入排序、折半插入排序、Shell排序
  • ==归并排序==
  • 桶式排序
  • 基数排序

快速排序

冒泡排序(会写)

package AfterClass;

/**
 * @author Jinxin Li
 * @create 2020-07-28 16:15
 * 冒泡排序
 */
public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = new int[]{23,34,56,234,56,34,45,89,54,69};
        for (int i = 0; i < arr.length-1; i++) {//关于length - 1 为长度
            for (int j = 0; j < arr.length-1-i; j++) {//
                if(arr[j] > arr[j+1]) {
                    int temp = arr[j+1];
                    arr[j+1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + "\t");
        }
    }
}

3.5 Arrays工具类的使用

1 boolean equals(int[] a,int[] b) 判断两个数组是否相等。
2 String toString(int[] a) 输出数组信息。
3 void fill(int[] a,int val**)** 将指定值填充到数组之中。
4 void sort(int[] a) 对数组进行排序。
5 int binarySearch(int[] a,int key) 对排序后的数组进行二分法检索指定的值。
6 copyOf (int[],0,8) 复制
package AfterClass;

import java.util.Arrays;

/**
 * @author Jinxin Li
 * @create 2020-07-28 20:00
 * Array工具类的使用
 */
public class ArraysTools {
    public static void main(String[] args) {
        int[] arr1 = new int[]{1,2,3,4,5};
        int[] arr2 = new int[]{1,2,3,4,5};
        System.out.println(arr1 == arr2);//false,两者地址不同

        //1.equals() 判断两个数组是否相等,顺序也在其中
        System.out.println(Arrays.equals(arr1,arr2));

        //2.toString()
        System.out.println(arr1);//打印数组的地址
        System.out.println(Arrays.toString(arr1));

        //3.fill()填充
        Arrays.fill(arr1,10);//自动补充
        System.out.println(Arrays.toString(arr1));

        //4.sort()//快速排序
        int[] arr3 = new int[]{3,14,5,13,6,23,16,84,4};
        Arrays.sort(arr3);
        System.out.println(Arrays.toString((arr3)));

        //5.binarySearch()
        // 前提:数组必须有序
        int index = Arrays.binarySearch(arr3,23);
        if (index >= 0){
            System.out.println("找到了指定元素,位置为:" + index);
        }else{
            System.out.println("未找到指定元素");
        }


    }
}

3.6 数组使用中的常见异常

package com.atguigu.java1;

/**
 * 测试数组中的常见异常
 * @author shkstart
 * @create 2020-07-28 16:26
 */
public class ArrayExceptionTest {
    public static void main(String[] args) {
        //1. 数组角标越界异常:ArrayIndexOutOfBoundsException
        int[] arr = new int[10];//0-9
        System.out.println(arr[9]);
//        System.out.println(arr[10]);//越界
//        System.out.println(arr[-10]);//越界

        //2. 空指针异常:NullPointerException
        //情况1:
//        int[] arr1 = new int[10];
//        arr1 = null;
//        System.out.println(arr1[0]);

        //情况2:
//        int[][] arr2 = new int[5][];
//        System.out.println(arr2[0][0]);


        //情况3:
        String[] arr3 = new String[5];
        System.out.println(arr3[0].toString());
    }
}

第4章 面向对象编程(上)

4.1面向对象与面向过程

学些面向对象的三个主线(老师总结)

  • Java类及类的成员:属性 方法 构造器 代码块 内部类
  • 面向对象的三大特征: 封装 继承 多态
  • 其他关键字:this super import package static final abstract interface

面向过程与面向对象

面向过程(POP)与面向对象(OOP)

二者都是一种思想,面向对象是相对于面向过程而言的。

二者都是一种思想,面向对象是相对于面向过程而言的。面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做。

面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。

面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。

面向对象的三大特征

  • 封装 (Encapsulation)
  • 继承 (Inheritance)
  • 多态 (Polymorphism)

面向对象:Object Oriented Programming

面向过程:Procedure Oriented Programming

4.2 Java语言的基本元素 类和对象

类(Class)和对象(Object)是面向对象的核心概念。、

4.2.1 类与对象

类:是对一类事物的描述,是抽象的、概念上的定义

对象:是实际存在的该类事物的每个个体,因而也称为实例(instance)

万事万物皆对象

String有两种方式

String str1 = "Hello";
String str2 = new String(orginal:"Hello")

4.2.2 设计类 其实就是设计类内部成员

属性:对应类中的成员变量

成员变量 = field = 域 = 字段

行为:对应类中的成员方法

方法 = 成员方法 = 函数 = 成员函数 = method

//设计类
class Phone{//类
    //属性
    String name;//品牌名
    double price;//价格

    //类中的方法
    public void call(){
        System.out.println("打电话");
    }

    public void sendMessage(String message){
        System.out.println("发出的信息为:" + message);
    }

}

4.3 ==对象的创建和使用==

对类的实例化 = 创建累的对象

//创建Phone的对象

public static void main(String[] args) {
        //创建Phone的对象
        Phone p1 = new Phone();
        System.out.println("品牌名:" + p1.name + ", 价格为:" + p1.price);

        //通过"对象.方法" 或 "对象.属性"的方式调用功能,完成设计
        p1.name = "huawei p40 pro";
        p1.price = 8000.0;

        System.out.println("品牌名:" + p1.name + ", 价格为:" + p1.price);

        p1.call();
        p1.sendMessage("有内鬼,停止交易!");

        System.out.println("##########################");
    //创建第二个对象
        Phone p2 = new Phone();
        p2.name = "xiaomi 10";

        Phone p3 = p1;
        p3.price = 7000.0;
        System.out.println(p1.price);
    }

通过“对象.方法“ 或 ”对象.属性“的方式调用功能

面向对象编程思想落地的实现

步骤1:创建类,设计类的成员:属性,方法

步骤2:创建类的对象(或 类的实例化)

步骤3:调用“对象.方法” 与 “对象.属性”

4.3.1 对象的内存解析

image-20200729102502319

==内存解析== ==对象名==保存栈空间中

==对象实体== 也保存在堆空间中

对象的==属性==也保存在堆空间中

4.3.2 创建类的多个对象

创建类的多个对象。每个对象就拥有一套类的属性

当修改其中某一个对象的属性a,不会影响其他对象。

如果将一个对象的引用赋值给另一个对象的引用。则表示两个引用同时指向了堆空间的同一个对象实体

4.4 类中的属性的声明

4.4.1 变量的分类

  • 1)按数据类型来分:基本数据类型 (8种) vs 引用数据类型(数组、类、接口)
  • 2)按在类中声明的位置: 成员变量 vs 局部变量
  •      成员变量:直接声明在类中。
  •      局部变量:方法内、构造器内、代码块内、方法的形参、构造器的形参等

4.4.2 成员变量(类中属性)与局部变量(方法内)

成员变量与局部变量上文可见,直接上代码

public class PersonTest {//测试类

    public static void main(String[] args) {
        Person p1 = new Person();
        System.out.println(p1.name);
        System.out.println(p1.age);
        System.out.println(p1.gender);

        p1.eat("鱼香肉丝");

        p1.name = "李金鑫";
    }
}


class Person{//类
    //属性(或成员变量)
    String name;
    int age;
    boolean gender;//true:女性  false:男性
    //方法
    public void eat(String food){//food:形参。形参属于局部变量
        System.out.println("我喜欢吃:" + food);
    }

    public void sleep(){
        int minHour = 6; //属于局部变量
        int maxHour = 10;//属于局部变量
        System.out.println("每天睡眠建议不要少于" + minHour + ",但是也不要睡的过多。建议不要超过" + maxHour + "小时");
    }
}

相同点:

都是变量,定义的格式相同:数据类型 变量名 = 变量值

先声明,后使用

变量都有其作用域。超出作用域就失效

不同点:

① 关于权限修饰符的使用(了解)

成员变量声明前可以使用不同的权限修饰符进行修饰。比如:private \ 缺省 \ protected \ public

局部变量不能使用权限修饰符进行修饰。

② 关于变量赋值的说明

成员变量可以显式赋值,也可以使用默认初始化值

局部变量必须在调用之前显式赋值。因为其没有默认初始化值

对于成员变量默认初始化值的情况:

  • 整型,默认值为:0
  • 浮点型:默认值为:0.0
  • char型:默认值为:0 或 ‘\u0000’
  • boolean型:默认值为:false
  • 引用数据类型:默认值为null

③ 在类中声明的位置不同

成员变量:直接声明在类内部

局部变量:声明在方法内、构造器内、代码块内、方法的形参、构造器的形参等

④ 在内存结构中的位置不同

成员变量:声明在堆空间中

局部变量:声明在栈空间中

4.5 类中的方法的声明

4.5.1 方法声明的整体格式

权限修饰符 返回值类型 方法名(参数类型1 参数名1,参数类型2 参数名2,…)

//方法体

说明:

<1> 权限修饰符:可以使用4种不同的权限修饰来修饰方法。比如:private \ 缺省 \ protected \ public

暂时大家在声明方法时,可以默认都声明为:public

可以表明结构被调用时的权限的大小

<2> 返回值类型

  • 没有返回值,使用void表示。比如:Arrays的sort()

    Arrays.sort(arr)

  • 有具体的返回值类型,声明了返回值类型 可以是任意的基本数据类型,或者引用数据类型

    Arrays.binarySearch(int[] arr,int target);
    Arrays.equals(int[] arr,int[] arr1);
    Math.random();
    sqrt(double value)

有具体的返回值类型的方法中,一定会使用return + 变量/常量的方法,满足具体类型的数据

有返回值必有返回类型;一定会使用return

注意点:

返回值会产生自动类型提升

image-20200729113917995

4.5.2 方法名

属于标识符,命名时满足标识符的规范

4.5.3 形参列表

可以再声明方法时,在()括号中存入该方法体在执行过程中必要的数据

互相之间加逗号

举例子

Arrays.binarySearch(int[] arr, int target);
Arrays.equals(int[] arr, int[] arr1);
Math.random();//这个在调用时就不要添加形式参数
sqrt(double value);

4.5.4 方法体

方法执行的结构

此外方法还可以使用一些关键字进行修饰

static

abstract

final

方法还可以抛出异常类型,异常类型见

声明方法时是否需要返回值类型,是否需要形参列表

看题目的要求

具体问题具体分析

4.5.5 return 的使用

<1> 一旦执行此关键字,就结束方法的执行

public void printNumber(){
    for(int i = 1; i <= 100; i++){
        if(i == 10){
            return;//在此处执行return之后,程序直接结束,将不会再执行下面的sout操作
        }
        System.out.println(i);
    }
}

<2> 在有返回值类型的方法中,使用return + 变量的结构,返回需要的数据类型对应的数据

public class UserTest {
    public static void main(String[] args) {
        int[] arr = new int[]{45,63,5,5,34,57};
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));

        User u1 = new User();
        u1.eat("水煮鱼");

        int years = u1.studyYears();
        System.out.println(years);//12

        System.out.println("############");
        u1.printNumber();
    }
}

class User{
    //属性
    String name;
    int age = 1;

    //方法的声明
    public void eat(String food){//food:形参。形参属于局部变量
        System.out.println("我喜欢吃:" + food);
    }

    public void sleep(){
        int minHour = 6; //属于局部变量
        int maxHour = 10;//属于局部变量
        System.out.println("每天睡眠建议不要少于" + minHour + ",但是也不要睡的过多。建议不要超过" + maxHour + "小时");
        return;
    }

    public int studyYears(){
        byte years = 12;
        return years;
    }


    public void printNumber(){
        for(int i = 1;i <= 100;i++){

            if(i == 10){
                return;
            }

            System.out.println(i);

        }

        System.out.println("hello");
    }
}

方法可以调用方法

==练习3==对象数组

==对象数组==题目

定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int)。 创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。

问题一:打印出3年级(state值为3)的学生信息。

问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息

提示:

  1. 生成随机数:Math.random(),返回值类型double;

  2. 四舍五入取整:Math.round(double d),返回值类型long。

public class Practice5 {
    public static void main(String[] args) {
        //1.创建student类型的数组
        Student[] stus = new Student[20];//自己定义的类,引用型,可以给自己赋值

        //2.通过循环给每个数组元素赋值
        for (int i = 0; i < stus.length; i++) {
            stus[i] = new Student();
            stus[i].number = i + 1;
            stus[i].state = (int) (Math.random() * 6 + 1);
            stus[i].score = (int) (Math.random() * 101);
            stus[i].Information();
        }

        //打印三年级学生的信息
        for (int i = 0; i < stus.length; i++) {
            if (stus[i].state == 3) {
                System.out.println("三年级的学生的成绩");
                stus[i].Information();
            }
        }

        System.out.println("学生成绩排序");

        //使用冒泡排序排序学生成绩
        for (int i = 0; i < stus.length-1; i++) {
            for (int j = 0; j < stus.length-1-i; j++) {
                if (stus[j].score > stus[j+1].score) {
                    Student temp = stus[j];
                    stus[j] = stus[j+1];
                    stus[j+1] = temp;
                }
            }
        }
        for (int i = 0; i < stus.length; i++) {
            stus[i].Information();
        }
    }
}

/**
 * 定义学生类
 */
class Student{
    int number;
    int state;
    int score;

    public void Information(){
        System.out.println("number: " + number + " state:" + state + " score:" + score);
    }
}

4.6 再谈方法

4.6.1 方法的重载

重载的概念:

在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可

重载的特点:

与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。

总结:“两同一不同”:相同类中,相同方法名

参数列表不同:参数个数不同,参数的类型不同

方法的重载与权限修饰符,返回值类型,形参名都没有关系

如何确定调用了一个类的那个方法?

  • 通过方法名
  • 通过形参列表的个数和类型

重载实例

/**
 * @author Jinxin Li
 * @create 2020-07-29 20:37
 * 定义三个重载方法max(),
 * 第一个方法求两个int值中的最大值,
 * 第二个方法求两个double值中的最大值,
 * 第三个方法求三个double值中的最大值,并分别调用三个方法。
 */
public class HomeWork2 {
    public static void main(String[] args) {
        HomeWork2 test = new HomeWork2();

        System.out.println(test.max(1, 2));
        System.out.println(test.max(0.5, 8.7));
        System.out.println(test.max(0.7, 10.9, 6.8));
    }

    public int max(int m, int n){
        return m > n ? m : n;
    }

    public double max(double m, double n){
        return m > n ? m : n;
    }

    public double max(double m, double n, double l){
        return  m > n ? (m > l ? m : l) : (n > l ? n : l);
    }
}

4.6.2 对象的使用:==匿名对象的使用==

我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。

如:new Person().shout();

使用情况

如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。

我们经常将==匿名对象作为实参==传递给一个方法调用。

package AfterClass;

/**
 * @author Jinxin Li
 * @create 2020-07-29 19:00
 * 匿名调用
 */
public class ConcealableNameTest {
    public static void main(String[] args) {
        new CallPhone().PhoneCalling();//匿名调用
    }
}

class CallPhone{
    int PhoneNumber = 12231;
    public void PhoneCalling(){
        System.out.println(PhoneNumber);
    }
}

4.6.3 可变个数的形参

测试java中方法的可变形参的使用

public void sum(int i){
    "普通形参的方法".sout
}
public void sum(int ...i){//可变形参的形式
    "可变形参的方法".sout
}

格式:数据类型 … 参数名

说明:

  • 在调用==可变形参==的方式时,可以给可变形参赋值的参数个数为:0个,1个,2个,…
  • 在main函数进行调用时,优先寻找存在指定参数个数的重载方法进行代入,但是一般如果已经存在可变形参的方法,就不会继续编写其重载的其他方法
  • 可变形参的方法在同一个类中,能够与相同方法名的多个方法构成重载
  • 可变形参的方法a 与 参数同类型的数组的形参的方法b,不能在一个类中同时声明,不会发生重载
//可变形参与数组
public void sum(int ...arr){
    System.out.println("可变形参的方法");
    for(int i = 0; i < arr.length;i++){
        System.out.println(arr[i]);
    }
}

public void sum (int[] arr){
    for(int i = 0;i < arr.length;i++){
        System.out.println(arr[i]);
    }
}

image-20200731182018027

可以看到其实可变个数的形参其实在编译器中被视为数组,靠的是数组来进行处理不同个数的形参

  • 可变形参必须在参数列表的最后。(编译器不明白你少个)

其实可以理解,因为在参数列表中,如果你写在前面,则编译器无法判断你写在后面的数是否属于你的可变形参的数量。

  • 可变形参要求只有一个可变形参

4.6.3.0 数组的println(==char[]==)

一般如果对于数组而言,数组打印的是地址


public class PrintArrayTest {
    public static void main(String[] args) {
        int[] arr = new int[]{1,2,3};
        System.out.println(arr);//地址值

        char[] arr1 = new char[]{'a','b','c'};0.00
        System.out.println(arr1); //abc

        System.out.println(123.0);
        System.out.println("abc");
    }
}

而在println中存在char[] 数组类型,println能够直接打印

4.6.4 变量间的传递规则

基本数据类型 传递的是基本数据类型变量保存的数据值。

引用数据类型 传递的是引用数据类型变量保存的地址值。

形参:方法声明时,小括号内声明的参数

实参:方法调用时,实际传递过去的参数

<1> 方法的参数传递机制:值传递机制

交换1:方法内交换两个变量的值

public static void main(String[] args){
    int m = 10;
    int n = 20;
    System.out.println("m = " + m);
    System.out.println("n = " + n);
   
    //交换两个变量的值
    int temp = m;
    m = n;
    n = temp;
    
    System.out.println("m = " + m);
    System.out.println("n = " + n);
}

上面可以看出在main方法中,直接对参数进行

交换2:调用方法,实现变量的交换

//接上述程序
public class ValueTransferTest1 {
    main;
    int m = 10;
    int n = 20;
    
    //定义新的变量
    valueTransferTest1 test = new ValueTransferTest1();
    test.swap(m,n);
    
    sout.m;
    sout.n;
}

//定义交换的方法
public void swap(int m, int n){
    int temp = m;
    m = n;
    n = temp;
}
//结果并没有转换过来
  • 可以得出在上述方法中,参数并没有被转换过来
  • 在方法的使用中,对于基本数据类型来讲,将实参定义传入到方法中,与本来定义的变量不同,仅仅将数值传递过去,完全属于两套变量

==例题3 方法的参数传递==

package AfterClass;

/**
 * @author Jinxin Li
 * @create 2020-07-31 11:14
 */
public class ValueChange {
    public static void main(String args[]) {
        ValueChange test = new ValueChange();//创建新对象
        test.first();
    }
    public void first() {
        int i = 5;
        Value v = new Value();
        v.i = 25;
        second(v, i);
        System.out.println(v.i);
    }
    public void second(Value v, int i) {
        i = 0;
        v.i = 20;
        Value val = new Value();
        v = val;
        System.out.println(v.i + " " + i);
    }
}

class Value {
    int i = 15;
}

4.6.4==对象数组==指定索引进行交换

/**
     * 交换stus数组中指定的index1和index2位置上的元素
     * @param stus
     * @param index1
     * @param index2
     */
    private void swap(Student[] stus,int index1,int index2){
        Student tempStudent = stus[index1];
        stus[index1] = stus[index2];
        stus[index2] = tempStudent;
    }

    //错误的写法
    public void swap(Student s1,Student s2){
        Student tempStudent = s1;
        s1 = s2;
        s2 = tempStudent;
    }

4.6.5 递归(recursion)

一个方法体内调用它自身

递归方法:一个方法体内调用它自身。

  • 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
  • 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
import java.io.File;

/**
 * @author shkstart
 * @create 2020-07-31 11:58
 *
 * 递归方法:一个方法体内调用它自身。
 * 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
 * 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
 */
public class RecurionTest {
    public static void main(String[] args) {
        RecurionTest test = new RecurionTest();
        System.out.println(test.getSum(100));
        System.out.println(test.getSum1(100));

        System.out.println(test.f(10));
    }

    //计算1-100自然数的和,并返回
    public int getSum(int num){
        int sum = 0;
        for(int i = 1;i <= num;i++){
            sum += i;
        }
        return sum;
    }

    //递归举例1:计算1-100自然数的和,并返回
    public int getSum1(int num){
        if(num == 1){
            return 1;
        }else{
            return num + getSum1(num - 1);
        }
    }

    //递归举例2:n!
    public int multiply(int num){
        if(num == 1){
            return 1;
        }else{
            return num * multiply(num - 1);
        }
    }

    //递归举例3:已知有一个数列:f(0) = 1,f(1) = 4,f(n+2)=2*f(n+1) + f(n),
    //其中n是大于0的整数,求f(10)的值。
    public int f(int n){
        if(n == 0){
            return 1;
        }else if(n == 1){
            return 4;
        }else{
            return 2*f(n - 1) + f(n - 2);
        }
    }
    //递归举例4:已知一个数列:f(20) = 1,f(21) = 4,f(n+2) = 2*f(n+1)+f(n),
    //其中n是大于0的整数,求f(10)的值。
    public int func(int n){
        if(n == 20){
            return 1;
        }else if(n == 21){
            return 4;
        }else{
            return func(n + 2) - 2 * func(n + 1);
        }
    }

    //递归举例5:斐波那契数列(Fibonacci)
    //  1  1  2  3  5  8  13  21  34  55
    //  规律:一个数等于前两个数之和: f(n) = f(n - 1) + f(n - 2)

    //递归举例6:汉诺塔

    //递归举例7:遍历指定文件目录下的所有文件名
    public void printFileName(File dir){
        if(dir.isFile()){//是文件
            System.out.println(dir.getAbsolutePath());
        }else{//是文件目录
            File[] files = dir.listFiles();
            for(int i = 0;i < files.length;i++){
                printFileName(files[i]);
            }

        }
    }
    //拓展:计算指定文件目录的大小、删除指定的文件目录

    //递归举例8:快速排序
}

4.7 面向对象特征一:==封装与隐藏==

private体现封装性

4.7.1 封装性的引入

我们在创建了类的对象以后,可以通过”对象.属性”的方式给对象的属性赋值。

此时,对象的属性的赋值需要满足相应的数据类型和取值范围。在此之外,实际问题中可能还有其他的一些限制条件。(比如:legs要求是正数、偶数、0~30)

那么该如何添加限制条件呢?

给属性提供公共(public)的setXxx()方法用于设置属性的值。在方法内,可以添加额外的限制条件。

给属性提供公共(public)的getXxx()方法用于获取属性的值。在方法内,可以添加额外的限制条件。

同时,将类中的属性xxx,设置为私有的(private)

public class AnimalTest {
    public static void main(String[] args) {
        Animal a1 = new Animal();
        a1.name = "大黄";
//        a1.age = 5;
        a1.setAge(5);
//        a1.legs = -4;
        a1.setLegs(4);

//        System.out.println(a1.legs);
        System.out.println(a1.getLegs() + "!!!");

        a1.info();
        a1.eat();
//        a1.sleep();
    }
}

class Animal{//动物类
    //属性
    String name;
    private int age;
    private int legs;//腿的个数

    //方法
    //给legs属性赋值的方法
    public void setLegs(int l){
        if(l >= 0 && l % 2 == 0 && l <= 30){
            legs = l;
        }else{
            System.out.println("输入的数据不合法!");
        }
    }

    //获取legs属性值的方法
    public int getLegs(){
        return legs;
    }

    //提供关于age属性的set和get方法
    public void setAge(int a){
        age = a;
    }

    public int getAge(){
        return age;
    }


    public void eat(){
        System.out.println("动物进食");
        sleep();
    }

    private void sleep(){
        System.out.println("动物休息");
    }

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

4.7.2 封装性的体现

封装性的体现:体现为4种不同的权限:(从小到大)private < 缺省 < protected < public

体现之一:私有化类的属性,提供公共的get和set方法,用于获取和设置此属性的值。

体现之二:私有化类的方法,表示此方法仅在类内部使用。不会暴露给类外使用。

体现之三:单例模式(涉及到私有化构造器)(后面讲)

通过使用4种不同的权限修饰类及类的内部结构,从而体现被修饰的结构在调用时的可见性的大小!

三 4种不同的权限修饰符:private < 缺省 < protected < public

4种不同的权限修饰符可以用来修饰类的内部结构:属性、方法、构造器、内部类。

修饰类的话,仅能使用2种权限:缺省 、 public

修饰符 类内部 同一个包 不同包的子类 同一个工程
private Yes
(缺省) Yes Yes
protected Yes Yes Yes
public Yes Yes Yes Yes

练习==Person==

创建程序,在其中定义两个类:Person和PersonTest类。定义如下:

用setAge()设置人的合法年龄(0~130),用getAge()返回人的年龄。

在PersonTest类中实例化Person类的对象b,

调用setAge()和getAge()方法,体会Java的封装性。

Person
-age:int
+setAge(i: int) +getAge(): int
package AfterClass;

/**
 * @author Jinxin Li
 * @create 2020-07-31 16:05
 */
public class PracticeAge {
    public static void main(String[] args) {
        Person b = new Person();
        System.out.println(b.getAge());

        b.setAge(1);
        System.out.println(b.getAge());
        b.setAge(23);
        System.out.println(b.getAge());
    }

}
class Person{
    private int age;
    public void setAge(int i){
        if(i >= 0 && i <= 130){
            age = i;
        }
    }
    public int getAge(){
        return age;
    }
}

4.8 类的成员之三 构造器(构造方法)

构造器的结构 修饰器+类名 不需要声明返回类型,因为不属于方法

4.8.1 构造器的作用

1.创建类的对象

2.给对象的属性初始化赋值(构造对象)

4.8.2 关于构造器的说明

构造器的默认权限取决于类的权限

如果没有显式声明类的构造器的话,则系统会默认提供一个空参的构造器

构造器声明格式:权限修饰符 类名(形参列表)

Person{
    String name;
    int age;
}

类中可以声明多个构造器,彼此之间构成重载

如果用户一旦显式的声明了类的构造器,则系统不再提供参数的构造器

类中一定会声明构造器

//在前面定义的Person类中添加构造器,利用构造器设置所有人的age属性初始值都为18。
package AfterClass;

/**
 * @author Jinxin Li
 * @create 2020-07-31 16:05
 */
public class PracticeAge {
    public static void main(String[] args) {
        Person b = new Person(9);
        System.out.println(b.getAge());
        b.setAge(23);
        System.out.println(b.getAge());
    }

}
class Person{
    private int age;
    public Person(int c){//声明构造器,不要返回值类型
        age = c;
    }
    public void setAge(int i){
        if(i >= 0 && i <= 130){
            age = i;
        }
    }
    public int getAge(){
        return age;
    }
}

4.8.3 构造器给对象赋值的与其他赋值的先后顺序

  1. 默认赋值
  2. 显式赋值
  3. 构造器中赋值
  4. 创建了对象以后,通过“对象.属性”或者对象.方法“的方式给属性赋值

赋值的先后顺序:

1,2,3,4;

4.9 拓展知识 JavaBean

所谓javaBean,是指符合如下标准的Java类:

  • 类是公共的
  • 有一个无参的公共的构造器
  • 有属性,且有对应的get、set方法

举例

public class User {

    private int id;
    private String name;

    public User(){

    }

    public User(int i,String n){
        id = i;
        name = n;
    }

    public void setId(int i){
        id = i;
    }
    public void setName(String n){
        name = n;
    }
    public int getId(){
        return id;
    }
    public String getName(){
        return name;
    }

}

4.9.1 UML类图

image-20200802205312976

4.11 关键字==this==的使用

this通过this.可以调用当前类的属性,方法,构造器

4.11.1 为什么调用构造器

this

  1. 通常省掉

表示当前属性或方法的调用者,即当前对象或当前正在创建的对象。但是我们通常情况下都省略了此”this.”

  1. 在特殊情况下,此”this.”不能省略:

当方法中或构造器中定义的局部变量(包含:形参)与当前类的属性==同名==时,为了区分二者。在表示属性时,必须显式的加上”this.”,表明调用的是当前类的属性,而非局部变量。

private int a;
public void Demo(int a){
   this.a = a;
}

4.11.2 this调用构造器的说明

为什么要使用this调用本类的构造器

有时候不同构造器之间有一些代码比较冗杂,通过调用,然后添加的方式可以避免

调用格式:this(形参列表)//形参列表与被调用构造器的形参列表相同

① 我们可以在类的构造器中显式的声明”this(形参列表)”的方式,表示调用本类中的其他构造器。

② “this(形参列表)” 必须声明在类的构造器的首行!

③ 在一个类的构造器中,最多只能声明一个”this(形参列表)”的结构。

④ 如果一个类中声明了n个构造器,则最多有 n - 1 个构造器中可以使用”this(形参列表)”的结构。

5.谁调用方法,谁是this

示例:纯this的应用
//main
girl.marry(boy);//girl引用5

//girl对象中的方法
public  void marry(Boys boy){
    System.out.println("我想嫁给" + boy.getName());
    boy.marry(this);//代表的是引用这个方法的对象 = boy.marry(girl)
}
//以下boy对象中的方法
 public void marry(Girl girl){
     System.out.println("我想娶" + girl.getName());
}

==示例==

public class PersonTest {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.setAge(1);
        p1.setName("Tom");

        p1.info();
        p1.eat();

        Person p2 = new Person();
        p2.eat();

        Person p3 = new Person("Tom",12);
    }
}

class Person{
    private String name;
    private int age;//属性
    
    public String getName(){
        return this.name;
    }
    public int getAge(){
        return this.age;
    }  
    //------------------------------------
    public void setName(String name){
        this.name = name;
    }
    public void setAge(int age){
        this.age = age;
    }

    public Person(){
        //声明创建对象过程中,必须要执行的操作
        //...
        //...
        this.age = 18;
    }

    public Person(String name){
        this();//调用其他构造器,可以调用本类内的构造器
        this.name = name;
    }

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

    public void eat(){
        System.out.println("人吃饭");

        this.sleep();//引用方法
    }
    public void sleep(){
        System.out.println("人睡觉");
    }

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

4.10 关键字==package==

package:包

我们编写的java类,通常都放在不同的包下。即:指名类所在的包。目的:便于管理。

使用package声明一个包,放在==java源文件的开头==。

包名,==要全部小写==属于标识符的一种。在定义时需要满足标识符的命名规则、规范、“见名知意”

每”.”一次,就代表一层文件目录。

同一个包内,不允许声明相同名的结构:类、接口。

不同的包下,可以声明相同名的结构:类、接口

4.11 关键字 ==import==

  1. 置于package包声明与java类的定义之间,声明导入的结构

  2. 如果需要引入其他包下的结构(类,接口),此时需要引入import的结构

==如果是java.lang下的结构,则此时可以省掉import java.lang下的结构==

  1. 如果需要导入多个结构,则并列声明即可

  2. java.util.*指的是因为util下的所有结构(类,接口),当五个以上会自动合并

  3. 如果已经导入了a.*结构了,此时如果要使用a包的子包下的结构,则仍然需要导入a的子包。

  4. 如果类中使用了不同包下同名的类,则至少有一个需要使用全类名的方式表示

lang包下不用导入,就可以使用

当使用包下的文件,达到五个时,会变成.*的方式,表示可以导入java.util的所有结构

子包,lang包下子包也需要导入

第5章 面向对象编程(中)

5.1 面向对象特征二:==继承性==

image-20200802203033696

5.1.1 为什么要使用继承性

继承性减少了代码冗杂,提高了代码的复用性

有利于功能拓展

继承让类与类之间产生了练习,提供了多态的前提

注意:不要仅为了获取其他类中的一个功能而去继承

5.1.2 格式

class A extends B{
}
//A : 子类 SubcClass
//B : 父类 SuperClass 超类,基类

5.1.3 具体使用说明

子类可以根据自己的需要,额外定义自己的特征

一个父类可以生命多个子类

直接父类 间接父类

当一个类没有显式的声明父类时。则其默认的父类为:java.lang.object类

子类不能直接访问父类中似有的private的成员变量和方法

image-20200802203937897

不允许有多个父类,但是可以多层继承

image-20200802204005820

5.1.4 继承性与可变形参练习

形参列表的写入方式是以父类形式加载的

package com.practice.afterclass;

/**
 * @author Jinxin Li
 * @create 2020-08-05 18:23
 */
public class InterviewTest2 {
    public static void main(String[] args) {
        Base base = new Sub();
        base.add(1,2, 3);//父类方式加载形参列表

        Sub s = (Sub)base;
        s.add(1,2,3);
    }
}

class Base {
    public void add(int a, int... arr) {
        System.out.println("base");
    }
}
class Sub extends Base {

    public void add(int a, int[] arr) {
        System.out.println("sub_1");
    }
    
    public void add(int a, int b, int c) {
        System.out.println("sub_2");
    }

}

5.2 方法的重写

重载 两同一不同 参数列表不同

测试方法的重写

(override、overwrite)

子类可以更大

权限应该更大

测试方法的重写(override / overwrite)

5.2.1 重写

定义

在子类继承父类以后,子类可以对父类中同名同参数的方法进行覆盖操作。此覆盖操作,就称为方法的重写。

当创建子类对象以后,通过子类对象调用重写的父类的方法时,执行的就是子类重写父类的方法。

细节要求

权限修饰符 返回值类型 方法名(形参列表){}

子类重写的方法与父类被重写的方法:

==方法名、形参列表,权限修饰符,返回类型== 相同

子类可以拥有更大的权限修饰符,且不可以重写父类中private的方法

父类被重写的方法返回值类型为引用数据类型时,子类重写的方法的返回值类型可以与父类此方法的返回值类型的相同

或是父类此方法返回值类型的子类

==属性、构造器==不存在重写的概念

5.2.2 重写重载的虚拟机分配

重载:编译阶段,虚拟机会根据参数的静态类型决定使用哪个重载版本(方法在实际运行时内存中的入口地址),即静态分配

重写:当子类重新定义了父类的方法实现后,父类指针根据赋给他的不同的子类指针,动态的调用属于子类的该函数,在运行期根据子类(实际类型)确定方法的执行版本(方法在实际运行时内存中的入口地址)即动态分派。

5.3 四种权限修饰符

修饰符 类内部 同一个包 不同包的子类 同一个工程
private Yes
(缺省) Yes Yes
protected Yes Yes Yes
public Yes Yes Yes Yes

5.4 关键字super

5.4.1 super的使用

可以调用属性,方法,构造器

<1> super 调用属性 (可以理解为:父类的)

继承之后,父类的属性也已经全被被调用过来

super和this的用法相像,this代表本类对象引用,父类代表父类的空间标识

image-20200803104143178

this.name //现在当前类内寻找name,再从继承的父类中去找

<2> super调用方法

在继承父类以后,可以在子类的方法内,通过super.属性或super.方法。调用父类中的属性方法

通常省略

显式声明的特殊情况

<1> 子类父类中同名属性

<2> 为了调用父类中被重写的方法,则需要使用 super 调用重写前的父类方法

<3> super调用构造器

调用super构造器可以在子类构造中的默认参数里修改父类里的属性

//父类中构造器
 public circle(double radius) {
        this.radius = radius;
    }
//子类中构造器
public Cylinder(double radius) {
        super(radius);//修改了父类中的属性
        length = 1;
    }
  1. this(形参列表)调用本类中重载的构造器
  2. 子类的构造器中,使用super(形参列表) 结构,表示调用父类中指定的构造器
  3. 构造器只能使用this与super之一,放在首行
  4. 当构造器首行没有显式使用this(形参列表)和super(形参列表) 默认super()
  5. 类的构造器首行,一定会使用this或者super构造器
  6. 则最多有n-1个构造器使用了“this(形参列表)”,则剩下的一个一定使用“super”构造器。

==注意== :如果子类构造器中既未显式调用父类或者本类的构造器,且父类中又没有无参的构造器,则编译出错

5.5 子类对象实例化过程

1.从结果来:体现为继承性

创建一个子类的对象时,子类中获取了父类中声明 的 属性 方法

2.从过程中:

从上往下加载类,加载所有类

image-20200803175722803

5.6 面向对象特征三 ==多态性==

new person类的子类的对象成为多态

class person;//父类
class man extends person;//子类
person p1 = new man;//父类声明器,声明子类

多态性 多种形态

子类对象的多态性:父类的引用指向子类的对象,子类的对象赋给父类的引用

5.6.1 多态性的说明

狭义理解多态性

父类引用指向子类对象,调用方法时会调用子类的实现,而不是父类的实现,这叫多态性。

为什么重写属于多态性

[重写的虚拟机分配](#5.2.1 重写)

广义理解多态性

多态性可以分为两类,即==编译时的多态性==跟==运行时的多态性==

函数重载属于编译时的多态性

子类重写方法属于运行时的多态性

严格意义对台不包括编译时多态

调用子类特有方法时,需要强转,但是一般是程序设计有问题,才会需要调用子类特有方法

<1> 态的虚方法调用

编译时只能调用父类中声明过得方法

真正执行的是,子类重写父类的方法

执行看左边 运行看右边(==注意==针对于方法)

子类中特殊的方法不能调用

可以调用子类中的重写的方法,执行的也是重写的方法

class person{
   void eat(){}
   void walk(){}
}

class man extends person{
   void earnmoney(){}
   @override
   void eat(){
       "eat a lot".sout
   }
}

class test{
    main{
      person person1 =  new man;//定义寻找方法路径
      person1.eat();
      person1.earnmoney;//
    }

<2> 多态性(向上转型)与 向下转型

向上转型,是把子类声明成父类的方法,然后通过父类调用子类中重写方法,方便程序的拓展与改用。

向下转型,是把已经把声明成父类的子类通过instanceof判断之后,然后使用(子类类型)父类声明的子类实例的方式把多态性的实例转换为原本的实例。

image-20200804113115128

5.6.2 为什么使用多态性

<1> 多态性

package com.atguigu.java1;

/**

 * @author shkstart

 * @create 2020-08-04 10:35
   *

 * 多态性使用的举例:为什么要有多态性
   */
   public class AnimalTest {
   public static void main(String[] args) {
       AnimalTest test = new AnimalTest();
       test.func(new Dog());
       test.func(new Cat());//通过调用形参的方式,new一个新的子类
   }

   public void func(Animal animal){//Animal animal = new Dog()
       /*
       方法的形参为Animal animal的引用类型,也就是说在引入的类型一般是Animal类型,但是如果引入的实例的类型是一个Animal类型的子类,就会发生多态
       Animal animal = new Dog()
       此时使用animal就可以调用Dog()类中的重写的animal的方法,但是不能调用Dog()类中独特的方法
       若是想要调用Dog()的内部独特的方法,需要将animals向下类型转换,就是
       if(animal instance of Dog()){
       Dog dog = (Dog)animal;
       dog.shout();
       此时才能调用dog类中独特的方法
       }
           */
       animal.eat();
       animal.shout();
   //        animal.protectHome();if(animal instanceof Dog){Dog dog = (Dog)animal;
   ​        dog.protectHome();}else if(animal instanceof  Cat){Cat cat = (Cat)animal;
   ​        cat.catchMouse();}
   }

//    public void func(Dog dog){
//        dog.eat();
//        dog.shout();
//    }
//
//    public void func(Cat cat){
//        cat.eat();
//        cat.shout();
//    }
}

class Animal{
    public void shout(){
        System.out.println("动物叫~~");
    }
    public void eat(){
        System.out.println("动物进食~~");
    }
}

class Dog extends Animal{
    public void shout(){
        System.out.println("汪~汪~汪~");
    }
    public void eat(){
        System.out.println("狗吃骨头~~");
    }public void protectHome(){System.out.println("狗看家");}
}

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

/*
举例二

class Account{
    double balance;

​    public void withdraw(double amt){}
​    public void deposit(double amt){}
}

class CheckAccount extends Account{//信用卡
    double overdraft;

​    public void withdraw(double amt){}
}

class SavingAccount extends Account{}//储蓄卡


class Customer{
    Account acct;

​    public void setAccount(Account acct){ //Account acct  = new CheckAccount();
​        this.acct = acct;
​    }
}

 */

/*
举例三

数据库:mysql 、 oracle 、 sqlsever 、 db2

class DataOperate{

​      public void addData(Connection conn,Statement st){ //
​         conn.do();
​         st.execute();
​      }

}
 */

<2> instanceof 的使用

  • a instanceof A:判断对象a是否是类型A的实例。如果是,返回true。否则,返回false.

  • 如果a instanceof A返回true,则a instanceof SuperA也一定返回true.

    其中,SuperA是A的父类

5.6.3 多态性练习

image-20200805110018431

//父类 GeometricObject

/**
 * @author Jinxin Li
 * @create 2020-08-05 11:01
 */
public class GeometricObject {
    private String color;
    private double weight;

    public GeometricObject(String color, double weight) {
        this.color = color;
        this.weight = weight;
    }

    public GeometricObject() {
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }
    double findArea(){
        return 0;
    }
}
//矩形子类
public class MyRectangle extends GeometricObject{
    private double width;
    private double height;

    public MyRectangle(double width, double height, String color, double weight) {
        super(color, weight);
        this.width = width;
        this.height = height;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    public double getHeight() {
        return height;
    }

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

    @Override
    double findArea() {
        return height*width;
    }
}
//圆形子类

/**
 * @author Jinxin Li
 * @create 2020-08-05 11:05
 */
public class Circle extends GeometricObject {
    private double radius;

    public Circle(String color, double weight, double radius) {
        super(color, weight);
        this.radius = radius;
    }

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

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    @Override
    double findArea() {
        return Math.PI*radius*radius;
    }
}
//测试类
/**
 * @author Jinxin Li
 * @create 2020-08-05 12:55
 */
public class displayGeometricObject {
    public static void main(String[] args) {
        displayGeometricObject testTools = new displayGeometricObject();
        Circle circle1 = new Circle("white",5,4);
        MyRectangle rectangle1 = new MyRectangle(4,6,"white",5);

        testTools.disPlayArea(circle1);
        testTools.disPlayArea(rectangle1);

        System.out.println(circle1.findArea());
        System.out.println(rectangle1.findArea());

        System.out.println(circle1);
        System.out.println(rectangle1);
    }

    public void disPlayArea(GeometricObject obj){
        System.out.println(obj.findArea());
    }

    public boolean equals(GeometricObject obj1,GeometricObject obj2) {
        return obj1.findArea() == obj2.findArea();
    }
    /*
    平时在一个方法内需求使用不同的父类的子类型时(比如,子类型的比较,打印),会发现你设定的参数列表都得添加实参的类型
    这样当,如果同时需求多个子类型时,他们的类型各不同,就需要大量不同形参列表的方法的重载:
    比如(类型1 三角形,类型2 矩形,类型3 五角形)对这个图形的面积进行打印
    但是呢,我们有好多的类型图形,比如类型4 圆形,类型5 半圆形,类型6 六边形
    就需要重载大量的形参列表,造成了代码的冗杂
    可以直接声明父类
    (父类 几何图形,父类 几何图形,父类 几何图形)
    多态性:多种形态的特性,意思是父类型的引用可以指代多种形态,父类型指针可以指向其子类型对象,
    将子类型归类,对其含有的共性进行引用对比,如图形的边长,面积(其中子类可以对图形的算法进行重写)
    起到了拓展和延伸程序的作用,大大增加了代码可读性,减少代码冗杂
     */
}

5.7 object类的使用

5.7.1 clone()的使用==未懂==

package com.atguigu.java2;
//Object类的clone()的使用
public class CloneTest {
	public static void main(String[] args) {
		Animal a1 = new Animal("花花");
		try {
			Animal a2 = (Animal) a1.clone();
			System.out.println("原始对象:" + a1);
			a2.setName("毛毛");
			System.out.println("原始对象:" + a1.getName());
			System.out.println("clone之后的对象:" + a2.getName());
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
	}
}

class Animal implements Cloneable{
	private String name;

	public Animal() {
		super();
	}

	public Animal(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

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

//	@Override
//	public String toString() {
//		return "Animal [name=" + name + "]";
//	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
	
}

5.7.2 equals方法的使用

直接上实例,equals最普通用法就是比较两个字符串是否相同

==注意== ==这个相等符判断的是字符串的地址

public class Test {
    public static void main(String[] args) {
        String s1 = new String("123");//两种声明方式
        String s2 = "123";
        System.out.println(s1.equals(s2));//true
        System.out.println(s1 == s2);//False
    }
}

equals原码

public boolean equals(Object anObject) {
       if (this == anObject) {
           return true;//如果地址相同,直接相同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;
           }
       }
       return false;
   }

虽然有些地方不明白,但是可以大致分析出,具体实现靠的分析出字符串的char单个字符,逐一比较相同返回true的意思

但是在string类型中,其实是把原来的位于object中的equals()重写了。

如果没有重写Object类中的equals()。当调用equals()方法时,仍然比较的是两个

  • 对象的地址值是否相同。(或两个对象引用是否指向了堆空间中的同一个对象)

<1> 重写的规则

比较两个对象的实体内容是否相同(即:对象的属性是否相同)

可以在idea中自动生成equals

<2> 区别 == 与 equals

== 适用范围:基本与引用

equals适用引用数据类型

<3> 示例==问题==

//测试类
public class EqualsTest {
    public static void main(String[] args) {

        Order order1 = new Order(1001,"AA");
        Order order2 = new Order(1001,"AA");
        System.out.println(order1 == order2);//false

        System.out.println(order1.equals(order2));//false  ---> true
    }
}
//
public class Order {

    private int orderId;
    private String orderName;

    public Order(int orderId, String orderName) {
        this.orderId = orderId;
        this.orderName = orderName;
    }

    public Order() {
    }
/*
    //重写Object类的equals()
    //手动定义了equals()
    public boolean equals(Object obj) {
        System.out.println("Order equals()...");
        if(this == obj){
            return true;
        }

        if(obj instanceof Order){
            Order order = (Order)obj;

            */
    /*
    if(this.orderId == order.orderId && this.orderName.equals(order.orderName)){
                return true;
            }else{
                return false;
            }
            //或者
            */
    /*
            return this.orderId == order.orderId && this.orderName.equals(order.orderName);
        }

        return false;

    }
    */
    //自动重写的代码
      @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Order order = (Order) o;
        return orderId == order.orderId &&
                Objects.equals(orderName, order.orderName);//疑惑
    }

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

手动重写要ctrl+o

image-20200804232841999

自动重写

进行向下类型转换 就是由object声明的子类型转换为本身的类型,然后根据两者的属性进行依次比较

5.7.3 toString()方法的使用

打印对象的引用时等同于 调用当前对象的toString的方法。

像String,包装类,file类都重写了object类中的toString方法,用于输出当前对象的实体内容

如果自定义类没有重写tostring()时,默认返回当前对象所属的类型及对象的地址值

如果我们重写,一般要求打印引用类类型的属性值

示例

//测试类-------------------------------------------------
public class toStringTest {
    public static void main(String[] args) {
        Order order1 = new Order(1001,"AA");
        System.out.println(order1.toString());
        //com.atguigu.java2.Order@1540e19d -->Order[orderId = 1001, orderName = AA]
        System.out.println(order1);
        //com.atguigu.java2.Order@1540e19d -->Order[orderId = 1001, orderName = AA]
        //直接打印返回的是类型与地址值

        String str = new String("hello");
        System.out.println(str.toString());

        File file1 = new File("d:\\io\\hello.txt");
        System.out.println(file1.toString());
    }
}
//对象类---------------------------------------------------------
import java.util.Objects;

public class Order {

    private int orderId;
    private String orderName;

    public Order(int orderId, String orderName) {
        this.orderId = orderId;
        this.orderName = orderName;
    }

    public Order() {
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderId=" + orderId +
                ", orderName='" + orderName + '\'' +
                '}';
    }
}

5.7.4 练习9

image-20200805191446857

image-20200804234615713

//测试类
package com.inclass.practice;

/**
 * @author Jinxin Li
 * @create 2020-08-04 15:43
 */
public class CircleTest {
    public static void main(String[] args) {
        Circle c1 = new Circle(1);//创建两个对象
        Circle c2 = new Circle(2);
        System.out.println(c1.equals(c2));
        System.out.println(c1);
        System.out.println(c1.toString());
    }
}
//对象类
package com.inclass.practice;
import java.util.Objects;

/**
 * @author Jinxin Li
 * @create 2020-08-04 13:52
 */
public class Circle {
    private double radius;
    private String color;
    private int weight;


    public Circle() {
        color = "white";
        weight = 1;
        radius = 1;

    }

    public Circle(double radius) {
        this.radius = radius;
        color = "white";
        weight = 1;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Circle circle = (Circle) o;
        return Double.compare(circle.radius, radius) == 0;
    }

    @Override
    public int hashCode() {
        return Objects.hash(radius);
    }

    @Override
    public String toString() {
        return "Circle{" +
                "radius=" + radius +
                ", color='" + color + '\'' +
                ", weight=" + weight +
                '}';
    }
}

5.7.0 Junit单元测试类的使用

package com.atguigu.java3;

import org.junit.Test;

/**
 * @author shkstart
 * @create 2020-08-04 16:17
 *
 * 单元测试方法:
 * 一、添加jar包方式
 * 1.在当前module下-new - directory。命名为:lib
 * 2.将 junit-4.12.jar和hamcrest-core-1.3.jar包复制到lib目录下
 * 3. 选中两个jar包,右键 add as library - 选择当前的module
 *
 * 二、创建单元测试类和方法
 * 1. 单元测试类要求:① 单元测试类是public的 ,并提供public权限的空参的构造器
 * 2. 创建单元测试方法
 *   要求:① 单元测试方法前声明有:@Test。 导包为: import org.junit.Test;
 *        ② 单元测试方法必须是public的、void的、没有形参的方法
 *
 * 3. 在单元测试方法内可以定义变量、调用本类中的一般方法。
 *
 */
public class JUnitTest {

    @Test
    public void test1(){
        System.out.println("hello");

        int m = 10;
        int n = 20;
        System.out.println(m + n);
    }

    @Test
    public void test2(){
        System.out.println("hello,上海!");
        show("应该会比较凉快~");
    }

    public void show(String info){
        System.out.println("据说今晚有台风登陆。。。。");
        System.out.println(info);
    }

}

5.8 包装类

针对八种基本数据类型定义相应的引用类型—包装类(封装类)

有了类的特点,就可以调用类中的方法,Java才是真正的面向对象

基本数据类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
char Character

其中基本数据类型父类为number

包装类的底层

在Integer内部定了一个内部类

Integer有一个数组

5.8.1 重点:掌握基本数据类型 包装类 String类之间的转换

直接上实例

image-20200804235039263

package com.atguigu.java3;

import org.junit.Test;

/**
 * @author Jinxin Li
 * @create 2020-08-04 23:52
 */
public class Test00 {
    @Test
    public void test1() {
        //基本类-->包装类
        int num1 = 10;
        Integer i1 = new Integer(num1);
        System.out.println(i1 + 1);//自动拆箱操作
        System.out.println(i1.toString() + 1);

        boolean b1 = false;
        Boolean b2 = new Boolean(b1);//boolean b2 = new Boolean(b1);自动装箱,自动拆箱
        System.out.println(b2);

        //包装类-->基本类
        int i2 = i1.intValue();
        boolean b3 = b2.booleanValue();
    }


    /*
jdk5.0 中关于基本数据类型与包装类转化时,提供了新特性:自动装箱、自动拆箱
*/
    @Test
    public void test2() {
        int i3 = 10;
        Integer i4 = i3;//自动装箱
        System.out.println(i4.toString());

        int i5 = i4;//自动拆箱
        System.out.println(i5);
        //包装类在前声明自动装箱,基本类型在前声明自动拆箱
    }
    /*
    基本数据类型、包装类 ---> String : 1.使用连接符  2.调用String重载的valueOf(xxx xxx)
     String---> 基本数据类型、包装类:调用包装类Xxx的parseXxx(String s)方法
     */
    @Test
    public void test3(){
        int i1 = 10;
        Integer i2 = 10;
        //1.使用连接符
        String s1 = i1 + "";
        String s2 = i2 + "";
        //2.调用String重载的valueOf(xxx xxx)
        String s3 = String.valueOf(i1);

        //
        String s4 = "123";
//        s4 = "123a";//java.lang.NumberFormatException: For input string: "123a"
//        int i3 = Integer.parseInt(s4);
        int i3 = Integer.parseInt(s4);
        System.out.println(i3 + 0);

        String s5 = "123.1";
        double d1 = Double.parseDouble(s5);
        System.out.println(d1);

        String s6 = "true1";
        s6 = "TrUe";
        boolean b1 = Boolean.parseBoolean(s6);
        System.out.println(b1);
    }

}

5.8.2 Integer缓存数组说明

Byte/Short/Long/Character/Boolean

我们已经知道了基本类型的包装类,下面来看以下程序

public class IntegerTest {
       public void method1() {
        Integer i = new Integer(1);
        Integer j = new Integer(1);
        System.out.println(i == j);//false

        Integer m = 1;
        Integer n = 1;
        System.out.println(m == n);//true
        System.out.println(m.toString());

        Integer x = 128;
        Integer y = 128;
        System.out.println(x == y);//false
    }
}

在Integer内部定义了一个内部类IntegerCache,IntegerCache内声明并初始化了长度为256的Integer[] , 名字为==Cache==

  • 取值范围为:new Integer(-128) 到 new Integer(127)。
  • 当我们通过自动装箱的方式创建Integer对象时,如果对象的值在[-128,127]之间的话,则直接使用内部缓存的
  • Integer[]中的数据。超出此范围的话,则重新new对象。

进一步,Byte \ Short \ Long \Character \ Boolean类型也类似

第6章 面向对象编程(下)

6.1 关键字static

6.1.1 静态变量

每个对象拥有一套非静态的属性,意味着当修改一个对象的非静态属性时,不会影响其他对象此属性的值

每个对象共享类中声明的静态的属性。意味着:当通过对象修改静态属性时,会影响其他对象地调用此属性的调用的值

说明

类中声明的静态的属性,在内存中只有一份。

类中声明的静态的属性,存放在方法区的静态域中。

类中声明的静态的属性,随着类的加载而加载。

静态属性(或静态变量、类变量) 非静态属性(或实例变量)

  • 类              yes                                      no
  • 对象 yes yes

静态方法能够使用类直接调用

==注意==

在静态方法内,调用属性方法,不能调用非静态的属性跟方法

非静态的方法内能够调用静态的属性跟方法

方法的静态不用生成对象就能使用方法

工具类方法都是静态方法

属性:Math.PI

方法:

Math.random()

Math.sqrt()

Math.round()

Arrays.sort()

Arrays.binarySearch()

工具类方法这类方法跟具体的对象没关系,是可以处理同类型的对象的操作,可以通过多态性处理不同类型同父类的操作

static可以修饰的结构有: 属性 方法 代码块 内部类

6.1.2 属性使用static修饰

类中的多个对象此属性的值是否相同、

是否需要共享此属性

类中定义的常量

全局变量也放在静态域内

6.1.3 方法使用static修饰

调用静态属性的方法,通常声明为静态的

工具类中的方法

6.1.4 举例

public class StaticTest {
    public static void main(String[] args) {
//        System.out.println(Chinese.name);
        System.out.println(Chinese.nation);
        Chinese c1 = new Chinese();
        c1.name = "陈毅";
        c1.age = 40;
        c1.nation = "中国";


        Chinese c2 = new Chinese();
        c2.name = "姚明";
        c2.age = 35;
        c2.nation = "CHN";

        System.out.println(c1);
        System.out.println(c2);

//        System.out.println(Chinese.name);
        System.out.println(Chinese.nation);

        Chinese.show();
        c1.show();
        c1.eat("米饭");
//        Chinese.eat("面条");//编译不通过

        //main()内可以直接调用静态的方法
        methodA();
    }

    public static void methodA(){
        System.out.println("A");
    }
}

class Chinese{

    String name;
    int age;

    static String nation;//国籍

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

    public Chinese() {
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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


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

    public static void show(){
        System.out.println("我是一个中国人");
        //在静态方法内,不能调用非静态的属性、方法
//        eat("米饭");
//        System.out.println(name);
        //在静态方法内,可以调用当前类的静态结构:属性、方法。
        System.out.println(nation);
        method();
    }

    public static void method(){

    }
    public void method1(){}

    public void eat(String food){
        //非静态方法内,可以调用非静态的属性、方法
        System.out.println(name + "喜欢吃:" + food);
        method1();
        //非静态方法内,可以调用静态的属性、方法
        System.out.println(nation);
        method();


    }

    public static String getNation() {
        return nation;
    }

    public static void setNation(String nation) {
        Chinese.nation = nation;
    }
}

image-20200806103945426

6.1.5 题目:圆的测试

使用全局变量记录定义圆对象的个数

package com.atguigu.java;

/**
 * @author shkstart
 * @create 2020-08-05 14:07
 *
 * static的应用举例
 */
public class CircleTest {
    public static void main(String[] args) {
        Circle c1 = new Circle();
        Circle c2 = new Circle(3.3);
        Circle c3 = new Circle(3.5);

        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);

        System.out.println("创建的Circle对象的个数为:" + Circle.getTotal());
    }
}

class Circle{
    private double radius;

    private int id;//自动生成id

    private static int init = 1001;//初始化id的因子

    private static int total = init;//记录创建的Circle对象的个数

    public Circle(){
        this.id = init++;
        total++;
    }

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

    @Override
    public String toString() {
        return "Circle{" +
                "radius=" + radius +
                ", id=" + id +
                '}';
    }

    public int getId() {
        return id;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    public static int getTotal(){
        return total;
    }
}

题目:练习1

package com.atguigu.exer;

/**
 * @author shkstart
 * @create 2020-08-05 15:31
 * 编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,
 * 定义封装这些属性的方法。账号要自动生成。
 * 编写主类,使用银行账户类,输入、输出3个储户的上述信息。
 * 考虑:哪些属性可以设计成static属性。
 */
public class AcountTest {
    public static void main(String[] args) {
        Account account1 = new Account();
        Account account2 = new Account("123456",1000);

        System.out.println(account1);
        System.out.println(account2);

        Account.setAnnualInterestRate(0.018);
        Account.setMinBalance(1);
    }
}

class Account{
    private int id;
    private String password = "000000";
    private double balance;//余额
    private static double annualInterestRate;//年利率
    private static double minBalance;//最小余额
    private static int init = 1001;//自动生成id的因子

    public Account(){
        id = init++;
    }

    public Account(String password,double balance){
        //id = init++;
        this();
        this.password = password;
        this.balance = balance;
    }

    public int getId() {
        return id;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public static double getAnnualInterestRate() {
        return annualInterestRate;
    }

    public static void setAnnualInterestRate(double annualInterestRate) {
        Account.annualInterestRate = annualInterestRate;
    }

    public static double getMinBalance() {
        return minBalance;
    }

    public static void setMinBalance(double minBalance) {
        Account.minBalance = minBalance;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", password='" + password + '\'' +
                ", balance=" + balance +
                '}';
    }
}

6.2 单例模式

是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模免去我们自己再思考和摸索。

就像是经典的棋谱,不同的棋局,我们用不同的棋谱。

单例模式(或单子模式、Singleton)

保证在整个的软件系统中,对某个类只能存在一个对象实例

  • 多种设计模式
  • 构造器私有化
  • 内部构造器
  • 将此属性声明为实例
  • 使用类调用getBank方法返回构造器构造的静态对象

三、实现方式:① 饿汉式 ② 懒汉式

对比:

懒汉式好:延迟对象的创建,节省内存空间。线程是不安全的。

饿汉式好:线程是安全的。

不管调用类产生对象几次,都是调用的唯一的静态的对象

6.2.1 饿汉式

用的时候调用

public class SingletonTest {
    public static void main(String[] args) {
//        Bank b1 = new Bank();
//        Bank b2 = new Bank();

        Bank bank = Bank.getBank();
        Bank bank1 = Bank.getBank();

        System.out.println(bank == bank1);
    }
}
//饿汉式
class Bank{
    //1. 构造器私有化
    private Bank(){
    }

    //2. 声明并创建当前类的唯一实例,声明为private的
    //4. 必须将此唯一的实例声明为static的
    private static Bank bank = new Bank();

    //3. 通过方法返回当前类的唯一实例
    public static Bank getBank(){//Bank为当前类型
        return bank;
    }
}

6.2.2 懒汉式

声明当前类唯一实例。没有赋值

已经整好,调用返回

能不整就不整

线程不安全

package com.atguigu.java1;

/**
 * @author shkstart
 * @create 2020-08-05 14:39
 */
public class SingletonTest1 {
    public static void main(String[] args) {
        ChairMan c1 = ChairMan.getInstance();
        ChairMan c2 = ChairMan.getInstance();
        System.out.println(c1 == c2);
    }
}
//懒汉式
class ChairMan{

    //1.构造器私有化
    private ChairMan(){
    }
    //2.声明当前类的唯一实例,声明为private的
    //4. 必须将此唯一的实例声明为static的
    private static ChairMan chairMan = null;
    //3. 通过方法返回当前类的唯一实例
    public static ChairMan getInstance(){
        if(chairMan == null){
            chairMan = new ChairMan();
        }
        return chairMan;
    }
}

runtime 运行时环境(不懂)

6.3 main() 的理解

main()是程序的入口

main()是一个静态的方法,形参为String[]类型

我们可以使用main()从键盘读取数据,只不过是数据的类型只能是String

main方法传数据自己写

与main函数交互方式,两种

注意拿到的都是字符串

package com.atguigu.java1;

/**
 * @author shkstart
 * @create 2020-08-05 15:44
 *
 * main()的理解
 * 1. main()是程序的入口
 * 2. main()是一个静态的方法,形参为String[]类型
 * 3. 我们可以使用main()从键盘读取数据,只不过数据的类型只能是String。
 *     具体使用,比如:java MainDemo Tom 123 "Jerry"*/
 
public class MainTest {
    public static void main(String[] args) {
//        System.out.println("hello");

        Main.main(new String[20]);//类直接调用静态方法
    }
}

class Main{
    public static void main(String[] args) {//静态方法可以直接使用类进行调用
        for(int i = 0;i < args.length;i++){
            args[i] = "args_" + i;
            System.out.println(args[i]);
        }
    }
}

6.4 类的成员之四 代码块(初始化块)

6.4.1 代码块只能使用static修饰。

分类:静态代码块 vs 非静态代码块

6.4.2 静态代码块:

内部可以声明执行语句

随着类的加载而执行

由于类的加载只加载一次,所以静态代码块只执行一次。

静态代码块的执行要早于非静态代码块的执行。

作用:用于初始化类的信息:静态变量

如果多个静态代码块,则按照声明的先后顺序执行

静态代码块内,只能调动当前类的静态结构

6.4.3 非静态代码块:

内部可以声明执行语句

随着对象的创建而执行。即:每创建一个对象,都执行一次非静态代码块

作用:用于初始化对象的信息:实例变量

如果多个非静态代码块,则按照声明的先后顺序执行

非静态代码块内,可以调动当前类的静态结构和非静态结构:属性、方法

没有名字,自动执行

静态代码块的使用

非静态代码块

静态代码块 先于非静态代码块执行

非静态代码块都能调用,但是一般不调用

示例

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

        System.out.println(Person.info);

        Person p1 = new Person();
        Person p2 = new Person();
    }
}

class Person{
    String name;
    int age;

    static String info;

    //非静态代码块
    {
        System.out.println("我是非静态代码块2");
    }
    {
        System.out.println("我是非静态代码块1");
        int num = 10;
        age = 1;
        eat();
    }

    //静态代码块
    static{
        System.out.println("我是静态代码块2");
    }
    static{
        System.out.println("我是静态代码块1");
        int num = 10;
        info = "我是人";
//        eat();

        show();
    }

    public static void show(){}

    public void eat(){
        System.out.println("人吃饭");
        sleep();
    }

    public void sleep(){
        System.out.println("人睡觉");
    }

    public Person(){}

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

6.4.4 属性赋值的位置

赋值的先后顺序

先加载 在调用

先有类的加载

然后进行初始化

先不执行,都加载过来

先加载属性,在赋值,默认初始化已经加载属性了

graph LR
A[方法区域]-->B[堆栈区]-->C[堆区]

<1> 属性可以被赋值的位置:

① 默认初始化

② 显式初始化

③ 构造器中初始化

④ 创建了对象以后,通过”对象.属性” 或 “对象.方法”的方式,进行赋值

⑤ 代码块中赋值

<2> 赋值的先后顺序:

① - ② / ⑤ - ③ - ④

说明: ② 和 ⑤ 执行的先后顺序取决于声明的先后顺序。

package com.atguigu.java2;

import java.net.SocketTimeoutException;


public class OrderTest {
    public static void main(String[] args) {
        Order order = new Order();
        System.out.println(order.orderId);

        Order order1 = new Order(3);
        System.out.println(order1.orderId);
    }
}
class Order{
    {
        orderId = 1;
    }
    int orderId = 2;
    public Order(){}
    public Order(int orderId){
        this.orderId = orderId;
    }
}

6.5 关键字 final

final可以用来修饰:类,方法,变量(属性,局部变量)

6.5.1 final 标记的类不能被继承

要注意final类中所有成员方法都会被隐式地指定为final方法

image-20200808230253108

如图所示,final标记的类A,是不能被B所继承的,因为A为终态类。

在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。

6.5.2 final标记的方法不能被子类重写

使用final方法的==原因==有两个:

第一个原因是把方法锁定,以防任何继承类修改它的含义;明确禁止子类进行重写

第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。

==注==:类的private方法会隐式地被指定为final方法。

示例

比如:String\StringBuffer\StringBuilder

image-20200808231552477

  注:类的private方法会隐式地被指定为final方法。

6.5.3 final修饰属性

final修饰属性,此属性为常量,属性一旦被赋值,就不可以更改

即只能赋值一次

说明

Static final修饰一个属性,全局变量

比如: Math.PI

比如:Math类中的PI

//在游戏中
public static final int UP = 1;
public static final int DOWN = 2;
public static final int LEFT = 3;
public static final int RIGHT = 4;

final修饰局部变量:一旦对此局部变量进行了赋值,就不能修改此值

对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

6.5.4 final修饰的属性可以在那些位置赋值?

显式赋值

final int My_num1 = 0;

代码块可以

{
NUM2 =1}

构造器中可以

可以通过构造器的形参列表进行赋值

表示此类不能被继承

6.5.4 深度理解final

<1> final修饰的变量跟普通变量区别

final修饰的变量会被编译器当做常量来修饰

如代码,当显式赋值时(只有在编译期间能确切知道final变量值的情况下,编译器才会默认进行这样的优化),编译器会默认优化为编译器常量,而引用类与常量相等会值相等而返回true

平时两个引用型变量地址不同所有返回false

public class Test1 {
    public static void main(String[] args)  {
        String a = "hello2";
        final String b = "hello";
        String d = "hello";
        String c = b + 2;
        String e = d + 2;
        System.out.println((a == c));//true
        System.out.println((a == e));//false
    }
}

<2>被final修饰的引用变量指向的对象内容可变么?

public class Test {
    public static void main(String[] args)  {
        final MyClass myClass = new MyClass();     
        System.out.println(++myClass.i);
    }
}
 
class MyClass {
    public int i = 0;
}

可变的,final修饰引用变量,固定了引用变量的地址,可以改变指向变量内容,但是不可以new新的对象

image-20200808235927608

<3> final和static

static 是复制一份常量,在静态域中不可以更改,仅此一份

所有与其同名同类型的变量都是一个值

而final是保证单个变量不可变

public class Test {
    public static void main(String[] args)  {
        MyClass myClass1 = new MyClass();
        MyClass myClass2 = new MyClass();
        System.out.println(myClass1.i);
        System.out.println(myClass1.j);
        System.out.println(myClass2.i);
        System.out.println(myClass2.j);
 
    }
}
 
class MyClass {
    public final double i = Math.random();
    public static double j = Math.random();
}

<4> final固定常量不是为了方法进行修改,因为方法传参是值传递

 关于网上流传的”当你在方法中不需要改变作为参数的对象变量时,明确使用final进行声明,会防止你无意的修改而影响到调用方法外的变量“这句话,我个人理解这样说是不恰当的。

6.6 抽象类与抽象方法

abstract关键字的使用

1.abstract:抽象的

2.abstract用来修饰类、方法

3.abstract修饰类:此时就是抽象类

抽象类,不可以实例化。

开发中,我们都会去提供抽象类的子类。进而创建子类的实例。

4.abstract修饰方法:即为抽象方法

抽象方法所在的类,一定是抽象类。反之,抽象类中可以没有抽象方法。

子类继承抽象的父类以后,如果==重写了抽象的父类中的所有的抽象方法,则此子类可以实例化。==

子类继承抽象的父类以后,没有重写抽象的父类中的所有的抽象方法,则此子类仍然为抽象类。

不能用abstract修饰变量、代码块、构造器

不能用abstract修饰私有方法、静态方法、final的方法,final的类

6.6.1 思考

问题<1>:为什么抽象类不可以使用final关键字声明?

final标记的类不能被继承

==问题<2>:==一个抽象类中可以定义构造器吗?

可以,用来传递参数,实现多态,让子类继承

问题<3>:是否可以这样理解:抽象类就是比普通类多定义了抽象方法,除了不能直接进行类的实例化操作之外,并没有任何的不同

狭义可以

6.7 接口(interface)

定义了一种规范

使用了interface定义接口,接口是与类并列的概念我的解释:

接口可以理解为一种特殊的类,里面全部是由全局常量公共的抽象方法所组成。接口是解决Java无法使用多继承的一种手段,但是接口在实际中更多的作用是制定标准的。或者我们可以直接把接口理解为100%的抽象类,既接口中的方法必须全部是抽象方法。(JDK1.8之前可以这样理解)

6.7.1 接口的声明

接口内可以

在jdk7及以前:全局变量,抽象方法

==全局变量==:public static final 可以不写

==抽象方法==:public abstract可以省掉不写

jdk8中:接口中定义静态方法、默认方法 默认方法要使用default进行定义

jdk9中:接口中定义私有方法

接口中不能定义构造器,不能实例化接口!

类与接口之间是实现(implements)关系,而且是多实现的。

class A extends B implements C,D

如果实现类实现了接口中声明的所有抽象方法,则此类可以实例化

如果实现类没有实现接口中声明的所有抽象方法,则此类仍为抽象类,不能实例化

接口与接口之间存在继承关系,而且是可以多继承的。

6.7.2 接口的使用

接口,定义了一种规范,标准,接口也存在多态性

接口能够让implements接口的方法进行规范性写程序,可以使用多态的方式进入到形参列表声明为其实施 的接口的方法里

USB的接口的题目

package com.test;

/**
 * usb
 * @author Jinxin Li
 * @create 2020-08-09 12:23
 */
public class interfaceTest1 {
    public static void main(String[] args) {

        //1.创建对象进行传输
        Computer computer = new Computer();
        Printer printer = new Printer();
        computer.transferDate(printer);

        //2.匿名对象进行传输
        computer.transferDate(new Flash());

        new Computer().transferDate(new Printer());

        //3.直接使用接口创建对象,但是要对接口中的方法进行重写,重写之后使用大括号进行重写
        USB usb = new USB() {
            @Override
            public void start() {
                System.out.println("USB传输中=======================");
            }

            @Override
            public void stop() {
                System.out.println("USB传输结束======================");
            }
        };
        computer.transferDate(usb);
        //4.传入接口的匿名实现类的匿名对象
        computer.transferDate(new USB() {
            @Override
            public void start() {
                System.out.println("数据传输中");
            }

            @Override
            public void stop() {
                System.out.println("数据传输中");
            }
        });
        
    }
}

interface USB{
    //常量:长 宽
    //抽象方法
    public void start();
    public void stop();
}

class Computer{
    public void transferDate(USB usb){//使用多态性调用不同的连接机器USB usb = new Printer()
        usb.start();
        System.out.println("数据传输中");
        usb.stop();
    }
}

class Printer implements USB{

    @Override
    public void start() {
        System.out.println("开始打印");
    }

    @Override
    public void stop() {
        System.out.println("结束打印");

    }
}
class Flash implements USB{

    @Override
    public void start() {
        System.out.println("开始传输");
    }

    @Override
    public void stop() {
        System.out.println("结束传输");
    }
}

6.7.3 接口 java1.8中的新特性

接口一些说法

接口A

public interface CompareA {
    public static void method1(){
        System.out.println("CompareA:北京");
    }

    public default void method2(){//接口
        System.out.println("CompareA:上海");
    }

    default void method3(){
        System.out.println("CompareA:深圳");
    }

    default void method4(){
        System.out.println("CompareA:广州");
    }
}

接口B

public interface CompareB {
    default void method3(){
        System.out.println("CompareB:深圳");
    }
}

实例

public class SubClassTest {
    public static void main(String[] args) {
        //知识点1:接口中的静态方法不能被实现类直接调用。只能通过接口来进行调用。
        CompareA.method1();
        //编译不通过
//        SubClass.method1();

        //知识点2:通过实现类的对象可以直接调用接口中定义的默认方法。
        //如果实现类重写了接口中的默认方法,则实现类对象调用的就是重写的方法
        SubClass sub1 = new SubClass();
        sub1.method2();

        //知识点3:如果实现类实现的多个接口中,定义了同名同参数的默认方法,则实现类必须要重写
        //接口中的此方法。否则会出现接口冲突。
        sub1.method3(); 

    }
    //知识点4:如果子类继承的父类与实现的接口中定义了同名同参数的方法,则子类在没有重写此方法的
    //情况下,默认调用父类中的方法。---->类优先原则。
    @Test
    public void test1(){
        SubClass sub1 = new SubClass();
        sub1.method4();
    }
}

6.8 面向对象之内部类

概念

将一个类A声明在另一个类B的内部,则构成了内部类结构

分类

成员内部类:静态的成员内部类 非静态的成员内部类

内部类,一方面作为类:

可以定义属性、方法、构造器、代码块等

可以被final、abstract 修饰

另一方面作为外部类的成员

可以被4种权限修饰符修饰。

可以被static修饰

可以调用外部类的属性、方法等

需要大家掌握的3个知识点

  • 如何创建成员内部类的对象?(静态的成员内部类、非静态的成员内部类)
  • 在成员内部类中,如何调用外部类的结构
  • 掌握局部内部类的常见使用场景:见 InnerClassTest1.java
public class InnerClassTest {
    public static void main(String[] args) {
        //创建静态的成员内部类的对象
       Person.Cat cat = new Person.Cat();

       //创建非静态的成员内部类的对象
        Person p1 = new Person();
        Person.Dog dog = p1.new Dog(); //new p1.Dog():错误的

        dog.info("花花");
        dog.eat();
        System.out.println(dog.getClass());
    }
}

class Person{
    String name = "Tom";
    int age;

    //内部类
    public class Dog{
        String name = "旺财";

        public void eat(){
            swim();
            Person.this.swim();
        }

        public void info(String name){
            System.out.println(name);
            System.out.println(this.name);
            System.out.println(Person.this.name);
        }

        public void swim(){
            System.out.println("狗也会游泳");
        }
    }

    static class Cat{
        
    }

    public void method(){
        //内部类 方法内部类
        class AA{

        }
    }

    public void swim(){
        System.out.println("人游泳");
    }

}

6.9 题目

第7章 异常处理

7.1 异常概述 异常体系结构

7.1.1 异常的体系结构

image-20200809172631592

异常:在Java语言中,将程序执行中发生的不正常情况称为“异常”。

(开发过程中的语法错误和逻辑错误不是异常)

Java程序在执行过程中所发生的异常事件可分为两类:

==Error==:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。

比如:StackOverflowError和OOM。一般不编写针对性的代码进行处理。

Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。

例如:

空指针访问

试图读取不存在的文件

网络连接中断

数组角标越界

用户输入格式不对

public class ErrorTest {
    public static void main(String[] args) {
//        main(args);//StackOverflowError:栈溢出的错误
        //OutOfMemoryError: Java heap space:堆空间溢出的错误
        byte[] arr = new byte[1024 * 1024 * 1024];//1GB
    }
}

image-20200808101952194

OutOfMemoryError堆空间溢出错误

exception可以处理

7.2 常见异常

7.2.1 异常的体系结构

java.lang.Throwable最高异常类

java.lang.Error:错误。一般不编写针对性的代码进行处理。

java.lang.Exception:异常。可以使用针对性的代码进行处理。

编译时异常:编译过程中可能出现的异常类型

运行时异常(RuntimeException):编译过程(javac.exe)可以正常通过,运行时报出的异常。\

7.2.2 常见的异常有哪些?

import java.util.Date;
import java.util.Scanner;

public class ExceptionTest {

    --|InputMismatchException:输入不匹配的异常
    public static void main(String[] args) {
        System.out.println("请输入一个整型数据:");
        Scanner scanner = new Scanner(System.in);
        int number = scanner.nextInt();
        System.out.println(number);

        scanner.close();
    }
    -----------------------------------------------
    --|ArrayIndexOutOfBoundsException 数组角标越界|--
    -----------------------------------------------
    public void test1(){
        int[] arr = new int[10];
        System.out.println(arr[10]);
        System.out.println("hello");
    }
    -----------------------------------
    --|NullPointerException 空指针异常|--
    ----------------------------------- 
    public void test2(){
//        Date date = new Date();
//        date = null;
//        System.out.println(date.toString());

        String str = null;
        System.out.println(str.toString());
    }
    ---------------------------------
    --|ArithmeticException:算术异常|--
    ---------------------------------        
    public void test3(){
        int m = 10;
        int n = 0;
        System.out.println( m / n);
    }
    ----------------------------------
    --|ClassCastException:类转换异常|--
    ----------------------------------
    public void test4(){
        Object obj = new Date();
        String str = (String) obj;
    }
   -----------------------------------------
   --|NumberFormatException:数值格式化的异常|--
   -----------------------------------------
    public void test5(){
        String str = "123a";
        int num = Integer.parseInt(str);
        System.out.println(num);
    }

    ==######################如下的是编译时异常###############################==
    --FileNotFoundException--
    --IOException--
    public void test6(){
//        File file = new File("hello.txt");
//        FileInputStream fis = new FileInputStream(file);
//        int data = fis.read();
//        while(data != -1){
//            System.out.print((char)data);
//            data = fis.read();
//        }
//
//        fis.close();
    }

    //ClassNotFoundException
    public void test7(){
//        Class clazz = Class.forName("java.lang.String");

    }
}

7.3 异常处理机制

7.3.1 java程序的异常处理:抓抛模型

过程一:抛 (生成异常对象、并抛出)

java程序在执行过程中,一旦出现异常,就会在异常出现的位置生成一个相应异常类型的对象。

并将此对象抛出。

一旦程序出现异常,就不再执行异常之后的代码了。

生成异常对象有两种方式:① 系统自动生成 ② 使用throw + 异常对象

过程二:抓 (异常处理的过程)

可以理解为异常处理的方式。

7.3.2 异常处理有两种方式

方式一:try-catch-finally

方式二:throws

<1> ==try-catch-finally==的使用格式

try{
    可能存在异常的代码
}catch(异常类型1 变量名1){
    异常的处理方式1
}catch(异常类型2 变量名2){
    异常的处理方式2
}...
finally{
    一定会被执行的代码
}

<2> 说明:

  1. finally是可选的。

  2. 将可能存在异常的代码声明在try中。一旦执行过程中出现异常,此异常对象就会抛出。进而匹配之后的catch结构

  3. 一旦与某一个catch结构匹配,进入catch的大括号中执行异常处理的代码。一旦执行完此catch结构,不再执行其后的其他catch结构。

  4. 一旦程序通过try-catch的方式处理了异常,则程序让可以继续向下执行。

  5. 如果声明了多个catch,多个catch对应异常类型不存在子父类关系的话,则哪个类型声明在上面,哪个类型声明在下面都可以。

==如果多个catch对应异常类型满足子父类的关系,则必须将子类类型声明在父类类型的上面。==

  1. 在try中声明的变量,在出了try结构以后就失效了。
  2. try-catch-finally结构是可以嵌套使用的

<3> catch中常见的异常处理的方式:

① 通过打印,指名异常的类型信息

② getMessage()

③ printStackTrace() (推荐)

<4> 对于异常的处理方式

针对于编译时异常,我们必须要进行异常的处理,否则编译不通过。

我们针对编译时异常进行处理以后,相当于将一个编译时异常转变为在运行时才可能出现的异常。

==针对于运行时异常,其实我们可以不使用异常处理。==

public class TryCatchFinallyTest {
    @Test
    public void test1() {
        int[] arr = null;
        try {
            arr = new int[10];
            System.out.println(arr[10]);//创建了一个ArrayIndexOutOfBoundsException类型的对象。
            System.out.println("hello1");
        } catch (NullPointerException e) {
            System.out.println("出现空指针的异常了....");
        } catch (ArrayIndexOutOfBoundsException e) {
//            System.out.println("出现角标越界的异常了....");
//            System.out.println(e.getMessage());
            e.printStackTrace();
        } catch (RuntimeException e) {
            System.out.println("出现了运行时的异常了....");
        }

        System.out.println("hello2");
        System.out.println(arr[1]);
    }

    @Test
    public void test2() {
        try {
            Object obj = new Date();
            String str = (String) obj;

        } catch (ClassCastException e) {
            e.printStackTrace();
        }
        System.out.println("hello");
    }

    //###############针对于编译时异常来说########################
    @Test
    public void test3() {

        try {
            File file = new File("hello1.txt");
            FileInputStream fis = new FileInputStream(file);
            int data = fis.read();
            while (data != -1) {
                System.out.print((char) data);
                data = fis.read();
            }

            fis.close();
        }catch(FileNotFoundException e){
//            System.out.println("出现文件找不到的异常了....");
            e.printStackTrace();
        }catch(IOException e){
//            System.out.println("出现了IO的异常了...");
            e.printStackTrace();
        }
    }

    @Test
    public void test4(){
        try{
            Class clazz = Class.forName("java.lang.String");

            //。。。。

        }catch(ClassNotFoundException e){
            e.printStackTrace();
        }
    }
}

7.4 finally的使用

finally是可选的。

将一定会被执行的代码声明在finally中

即使try、catch中存在未被处理的异常,或try、catch有return返回值结构,我们说,finally也是一定要被执行的。

7.4.1 开发中哪些代码会放在finally中?

IO流、Socket、数据库连接等资源,都需要手动的关闭。那么需要保证此关闭操作一定要被执行。否则,会

出现内存泄漏。

面试题:final \ finally \ finalize的区别

package com.atguigu.java;


import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Scanner;

public class FinallyTest {
    public void test1(){
        try{
            int[] arr = new int[10];
            System.out.println(arr[10]);

            int m = 10;
            int n = 0;
            System.out.println( m / n);
        }catch(ArithmeticException e){
            e.printStackTrace();

//            int[] arr = new int[10];
//            System.out.println(arr[10]);
        }finally{
            System.out.println("hello----1");
        }


        System.out.println("hello----2");
    }

    public int method1(){
        try{
            int m = 10;
            int n = 0;
            System.out.println( m / n);
            return 1;
        }catch(ArithmeticException e){
            return 2;
        }finally{
            return 3;
        }
    }

    @Test
    public void test2(){
        int num = method1();
        System.out.println(num);
    }
    @Test
    public void test3(){
        FileInputStream fis = null;
        try {
            File file = new File("hello.txt");
            fis = new FileInputStream(file);

            int data = fis.read();
            while (data != -1) {
                System.out.print((char) data);
                data = fis.read();
            }
        }catch(FileNotFoundException e){
            e.printStackTrace();
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            //必须手动关闭资源
            try {
                if(fis != null)
                    fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    @Test
    public void test4(){
        Scanner scanner = null;
        try{
            System.out.println("请输入一个整型数据:");
            scanner = new Scanner(System.in);
            int number = scanner.nextInt();
            System.out.println(number);

        }finally{
            if(scanner != null)
                scanner.close();
        }
    }
}

==finally执行影响力比return还要高==

==JVM垃圾回收器不能自动回收流文件==

7.4 throws异常处理的机制2

7.4.1 如何选择处理异常的方式

在开发中,如何选择异常处理的方式

throws抛出之后当前方法不再执行

接口声明,继承接口的才能抛出

运行时异常不用特已处理,也可以抛出处理一样的

编译时异常一定要处理

7.4.2 异常处理的方式二:在方法的声明处,使用:throws + 异常类型

在方法内,执行过程中,一旦出现异常,就会生成一个指定异常类型的对象。使用“throws + 异常类型”

方式处理异常的话,就会将此异常对象抛给方法的调用者。 方法的调用者需要继续考虑如何处理异常?

比如:method1()中出现的异常,method1选择了“throws + 异常类型”的方式处理,则将异常抛给了其调用

者:method2()

7.4.3 在开发中,如何选择异常处理的方式?

① 如果程序中涉及到一定要被执行的代码(比如:流、Socket、数据库连接等),我们需要选择

try-catch-finally方式处理异常。

② 如果父类被重写的方法声明时,没有使用throws的方式抛出异常。则子类重写的方法内部如果有异常,

只能使用try-catch-finally的方式处理,不能使用throws的方式了。

③ 如果一个方法method1()内,先后调用了另外了几个方法。比如:method2(),method3(),method4().

此时method2(),method3(),method4()是递进关系的调用。此时,method2(),method3(),method4()

中,我们选择使用throws的方式处理异常。而在method1()中统一的使用try-catch-finally的方式处理异常。

package com.atguigu.java1;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ThrowsTest {

    public static void main(String[] args) {
        ThrowsTest test = new ThrowsTest();
        test.method3();
    }

    public void method3(){
        try{
            method2();

        }catch(Exception e){
            e.printStackTrace();
//            System.out.println("出现异常了");
        }
        System.out.println("hello");
    }

    public void method2() throws Exception {
        method1();
    }


    public void method1() throws FileNotFoundExceptionIOException {
        File file = new File("hello.txt");
        FileInputStream fis = new FileInputStream(file);
        int data = fis.read();
        while(data != -1){
            System.out.print((char)data);
            data = fis.read();
        }

        fis.close();
    }
}

7.5 手动抛出异常

package com.atguigu.java1;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * @author shkstart
 * @create 2020-08-08 15:11
 *
 * 子类重写的方法声明的异常类型不大于父类被重写的方法声明的异常类型。
 */
public class ThrowsTest1 {
    public static void main(String[] args) {
        SuperClass s = new SubClass();

        try {
            s.method();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class SuperClass{
    public void method() throws IOException {
//        File file = new File("hello.txt");
//        FileInputStream fis = new FileInputStream(file);
//        int data = fis.read();
//        while(data != -1){
//            System.out.print((char)data);
//            data = fis.read();
//        }
//
//        fis.close();
    }

//    public void method1(){
//
//    }
}
class SubClass extends SuperClass{
    public void method() throws FileNotFoundException {

    }

//    public void method1() throws IOException{
//
//    }
}

throw的使用:

在方法内部,可以手动的生成一个异常类的对象,并将此对象抛出。使用“throw + 异常对象”

面试题:throw 和 throws 的区别

throw: 在生成异常对象并抛出的环节使用。表示手动抛出一个new的异常对象。

写在方法内部

throws:是针对于生成异常对象并抛出之后,考虑如何处理异常的一种方式。

写在方法的声明处。

package com.atguigu.java1;

public class ThrowTest {
    public static void main(String[] args) {
        try {
            Student s1 = new Student();
            s1.regist(-1001);

            System.out.println(s1);
        }catch(Exception e){
//            e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }
}
class Student{
    private int id;

    public void regist(int id) throws Exception{
        if(id > 0){
            this.id = id;
        }else{
//            throw new RuntimeException("输入的学号不能为0或负数");
//            throw new Exception("输入的学号不能为0或负数");

            //编译不通过
//            throw new String("输入的学号不能为0或负数");

            throw new MyException("输入的学号不能为0或负数");//手动抛出异常
        }
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                '}';
    }
}

7.6 用户自定义异常

自定义异常类

  1. 需要继承于现有的异常体系结构。比如:继承于RuntimeException 、 Exception

  2. 需要提供一个==序列版本号==:serialVersionUID

  3. 提供重载的构造器


public class MyException extends Exception {
    static final long serialVersionUID = -338751694229948L;

    public MyException(String message) {
        super(message);
    }
    public MyException() {
    }
}

7.5 练习

编译时必须处理


文章作者: Jinxin Li
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Jinxin Li !
  目录