JAVA基础知识笔记

一个很好的在线运行java的环境

https://www.bejson.com/runcode/java/

一、java基础知识

1、DOS和Java特性

1、window中常见的Dos命令

输入cmd

  • mkdir 创建目录
  • cls 清除屏幕
  • exit 退出当前Dos命令窗口
  • dir 列出当前目录下所有的子文件/子目录
  • cd 命令
    *cd命令表示:change directory【改变目录】
    *使用方法:cd 目录的路径
  • cd… 回到上级目录
  • cd/ 回到跟目录
  • 怎么切换盘符?
    c:回车
    d:回车
  • del 删除一个或多个文件
  • ipconfig 查看本机IP地址
  • ping IP地址 或 ping 域名:查看计算机是否可以正常通信

2、文件扩展名必须要以 .java结尾的扩展名

3、Java语言发展史

1
2
3
4
5
6
7
8
9
java语言诞生于1995年。
-什么是JDK?
Java开发工具包
Java开发必须安装的一个工具包,可从官网下载。
*Java目前包括三大块:
-JavaSE(Java标准版)
-JavaEE(Java企业版)
-JavaME(Java微型版)
其中JavaEE是基础,主攻方向是JavaEE方向。

4、Java语言特性【开源、免费、纯面向对象、跨平台】

简单性: 相对而言,例如Java中不在支持多继承,C++设计支持多继承的,多继承比较复杂。
C++中有指针,Java中屏蔽了指针的概念。所以Java是简单的。
Java语言的底层是C++实现的,不是c语言。

面向对象性: Java是纯面向对象的,更符合人的思维模式,更容易理解。

什么是面向对象?

封装:封装把一个对象的属性私有化、同时提供一些可以被外界访问的属性的方法。
继承
java是多单继承的,
子类拥有父类非private的属性和方法
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展
子类可以用自己的方式实现父类的方法
多态:父类的引用指向子类

可移植性:
什么是可移植性?(跨平台性)

1
2
3
4
Java程序可以做到一次编译,到处运行。也就是说Java程序可以在windows操作系统上运行,不做任何修改,同样的Java可以直接放到Linux操作系统上运行,这个被称为java程序的可移植性,或者叫做跨平台性。windows操作系统内核和Linux操作系统的内核肯定不同,他们这两个操作系统执行指令的方式也是不一样的。
结论: 显然Java程序不能直接和操作系统打交道。因为Java程序只有一份,操作系统执行原理都不同。

SUN团队让Java程序运行在一台虚拟的计算机当中,这个虚拟计算机叫做Java虚拟机,简称JVM。Java虚拟机再和底层的操作系统打交道。

多线程性
健壮性: 和自动垃圾回收机制有关,自动垃圾回收机制简称GC机制。
Java语言运行过程中产生的垃圾是自动回收的,不需要程序员关心。
安全性

2、Java——集成开发环境

1 、什么是集成开发环境【简称:IDE】

  • 集成开发环境讲究一站式开发,使用这个工具即可。有提示功能,有自动纠错功能。
  • 集成开发环境可以让软件开发变得更简单。更高效
  • 没有IDE工具:
    需要安装JDK、需要配置环境变量、需要手动的将java源文件编译生成class字节码文件。
    java源程序出错之后没有提示。
    没有自动提示功能等。
  • 有IDE工具:
    不需要独立安装JDK【IDE中已经集成】。
    不需要手动配置环境变量。
    不需要使用javac命令对java源文件进行编译。
    并且java源程序编写语法错误马上提示。
    使用IDE工具有很多代码不需要写,自动生成了。

2、 java有哪些比较主流的IDE呢?

  • eclipse(myeclipse)【最多】
  • Intellij IDEA【上升趋势最大】
  • Netbeans
  • JBuilder

3 、IDE常见的知识

workspace: 工作区
在workspace工作区中有一个文件夹:.metadata:储存IDE工作状态。
企业级开发:JavaEE开发的专用布局 - Java Enterprise。
普通java开发:JavaSE的专用布局 - Java
工作区workspace中基本的单元是:Project(工程/项目)
重点窗口介绍:

  • Package Explore / Navigator / Project Explore:可以看到java源文件
  • Console:控制台窗口

3、java程序的编译和运行、JDK、JRE、JVM的关系、环境变量、HelloWorld程序

1、Java的加载与执行

*Java程序的运行包括两个非常重要的阶段
-编译阶段
-运行阶段

*编译阶段

编译阶段主要的任务是检查Java源程序是否符合Java语法

符合Java语法则能够生成正常的字节码文件(.class)
不符合Java语法规则则无法生成字节码文件

字节码文件不是单纯的二进制,这种文件无法在操作系统中直接执行。
javac.exe(Java编译程序,在JDK中自带)
怎么使用?在哪用?
在DOS命令窗口中使用。
**javac的使用规则:**javac java源文件的路径

class文件是最终要执行的文件,所以class文件生成之后,java源文件删除也不影响java程序执行。
*编译结束后,可以将class文件拷贝到其他操作系统当中运行。【跨平台性】

*运行阶段
JDK安装之后,除了自带的javac.exe之外,还有另外一个工具,叫做java.exe,其主要负责运行阶段。

java.exe在哪里用?怎么用?
-在DOS窗口使用
-使用方法 输入 java 类名

java.exe命令会启动Java虚拟机(JVM),JVM会启动类加载器ClassLoader
ClassLoader会去硬盘上搜索A.class文件,找到该文件则将该字节码文件装载到JVM中。
JVM将A.class字节码文件解释成二进制。
然后操作系统执行二进制和底层硬件平台进行交互。

2、JDK、JRE、JVM的关系

JDK:开发者提供的开发工具箱,是给程序开发者用的,它包括完整的jre、Java运行环境,还包括了其他供开发者使用的工具包。

JRE: 运行时所必须的包依赖的环境都在jre中

JVM: 当我们运行一个程序时,JVM负责将字节码转换为特定机器代码,JVM提供了内存管理、垃圾回收和安全机制等,他独立于硬件和操作系统,正是java程序可以一次编写多处执行的原因

JDK目录的介绍:
JDK/bin: 该目录下存放很多命令,例如javac.exe和java.exe
javac.exe负责编译
java.exe负责运行

开发HelloWorld程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//public表示一个公开的
//class表示一个类
//HelloWorld表示一个类名
public class HelloWorld{//表示定义一个公开的类,起名HelloWorld
//类体中不允许直接编写Java语句(除声明变量之外)
/*
public表示公开的
static表示静态的
void表示空
main表示方法名是main
(String[] args)是一个main方法的形式参数列表
需要记住的是:以下的方法是一个程序的主方法。是程序的执行入口,是一个固定编写方式。
*/
public static void main (String[] args){//表示定义一个公开的静态的主方法
//方法体
//方法体

//Java语句以";"终止,分号必须是半角分号
System.out.println("Hello World!");//向控制台输出语句
}
}

将HelloWorld.java源程序通过javac工具编译:
首先需要解决的问题是:javac命令是否可用
打开DOS命令窗口,直接输入javac

*重要
PATH环境变量的配置
*注意:path环境变量和java语言没有关系,path环境变量是属于windows操作系统的一个知识点。path环境变量是专门给windows操作系统指路的。·

4、公开类、标识符、关键字

1、public class与class的区别
*一个java源文件中可以定义多个class

*一个java源文件当中public的class不是必须的

*一个class会定义生成一个xxx.class字节码文件

*一个java源文件当中定义公开的类的话,public class只能有一个,且该类名称必须和java源文件名称一致。

*每一个class当中都可以编写main方法,都可以设定程序的入口,想执行B.class中的main方法:java B, 想执行X.class当中的main方法 :java X
注意:当在命令窗口中执行java Hello,那么要求Hello.class当中必须有主方法。没有主方法会出现运行阶段的错误。

2、标识符
1 什么是标识符

-在java源程序中凡是程序员有权利自己命名的单词都是标识符。
-标识符在EditPlus编辑器中以黑色高亮显示

2 标识符的命名规则?

*一个合法的标识符只能由“数字、字母、下划线_、美元符号$”组成,不能含有其他的符号。
*不能以数字开头
*严格区分大小写
*关键字不能做标识符
*理论上无长度限制,但是最好不要太长

3 标识符的命名规范?(只是一种规范,不属于语法,编译器不报错)

*最好见名知意

*遵守驼峰命名规则
SystemService
UserService
CustomerService

*类名、接口名:首字母大写,后面每个单词首字母大写。
*变量名、方法名:首字母小写,后面每个单词首字母大写。
*常量名:全部大写。

4 合法和不合法的标识符

合法 不合法
_123Test 123Test
HelloWorld Hello-World
A_B_C Hello World
$ABC HelloWorld#
public1 public

3、关键字及含义

在Java中关键字都是小写

abstract 表明类或者成员方法具有抽象属性

assert 断言,用来进行程序调试

boolean 基本数据类型之一,声明布尔类型的关键字

break 提前跳出一个块

byte 基本数据类型之一,字节类型

case 用在switch语句之中,表示其中的一个分支 catch 用在异常处理中,用来捕捉异常

char 基本数据类型之一,字符类型

class 声明一个类

const 保留关键字,没有具体含义

continue 回到一个块的开始处

default 默认,例如,用在switch语句中,表明一个默认的分支。Java8 中也作用于声明接口函数的默认实现

do 用在do-while循环结构中

double 基本数据类型之一,双精度浮点数类型

else 用在条件语句中,表明当条件不成立时的分支

enum 枚举

extends 表明一个类型是另一个类型的子类型。对于类,可以是另一个类或者抽象类;对于接口,可以是另一个接口

final 用来说明最终属性,表明一个类不能派生出子类,或者成员方法不能被覆盖,或者成员域的值不能被改变,用来定义常量

finally 用于处理异常情况,用来声明一个基本肯定会被执行到的语句块

float 基本数据类型之一,单精度浮点数类型

for 一种循环结构的引导词

goto 保留关键字,没有具体含义

if 条件语句的引导词

implements 表明一个类实现了给定的接口

import 表明要访问指定的类或包

instanceof 用来测试一个对象是否是指定类型的实例对象

int 基本数据类型之一,整数类型

interface 接口

long 基本数据类型之一,长整数类型

native 用来声明一个方法是由与计算机相关的语言(如C/C++/FORTRAN语言)实现的

new 用来创建新实例对象

package 包

private 一种访问控制方式:私用模式

protected 一种访问控制方式:保护模式

public 一种访问控制方式:共用模式

return 从成员方法中返回数据

short 基本数据类型之一,短整数类型

static 表明具有静态属性

strictfp 用来声明FP_strict(单精度或双精度浮点数)表达式遵循IEEE 754算术规范

super 表明当前对象的父类型的引用或者父类型的构造方法

switch 分支语句结构的引导词

synchronized 表明一段代码需要同步执行

this 指向当前实例对象的引用

throw 抛出一个异常

throws 声明在当前定义的成员方法中所有需要抛出的异常

transient 声明不用序列化的成员域

try 尝试一个可能抛出异常的程序块

void 声明当前成员方法没有返回值

volatile 表明两个或者多个变量必须同步地发生变化

while 用在循环结构中

二、数据类型【编程基础】

1、字面值、变量、数据类型基础、编码方式

1、字面值
关于字面值

— 10/100
— 3.14
—“abc”
—‘a’
—true、false

字面值 就是数据。
字面值 是Java源程序的组成部分之一。包括标识符和它的关键字,他们都是Java源程序的组成部分。

数据在现实世界中是分类别的,所以数据在计算机编程语言中也是有类型的:【数据类型】

10、100 属于整数型
3.14 属于浮点型
true、false 属于布尔型
“abc”、“张广荣” 属于字符串型
‘A’、‘人’ 属于字符型

注意:
Java语言中所有字符串型字面值必须用双引号括起来,双引号是半角。
Java语言中所有字符型字面值必须用单引号括起来,单引号是半角。

2、变量

1 什么是变量?

变量本质上说是内存中的一块空间,这块空间“有数据类型”、“有名字”、“有字面值”。
变量包含三部分:数据类型、名称、字面值【数据】
变量是内存中存储数据的最基本的单元。

2 数据类型的作用?

不同的数据类型底层会分配不同大小的空间。
数据类型是指导程序在运行阶段应该分配多大的内存空间。

3 变量要求:变量中存储的具体的“数据”必须和变量的“数据类型”一致,当不一致的时候编译报错

4 声明/定义变量的语法格式:

数据类型 变量名
数据类型:概念在第三部分。
eg:int 整数型
变量名:只要是合法的标识符就行。规范中要求:首字母小写,后面每个单词首字母大写。

5 变量声明之后怎么赋值?

语法格式: 变量名=字面值
要求:字面值的数据类型必须和变量的数据类型一致。
=等号是一个运算符,叫做赋值运算符,赋值运算符优先运算等号右边的表达式,表达式执行结束之后的结果赋值给等式左边的变量。

6 声明和赋值可以放到一起完成。

int i=10

7 变量赋值之后,可以重新赋值,变量的值可以变化:

int i=10;
System.out.println(i);
i=20;
System.out.println(i);
i=100;
System.out.println(i);

8 有了变量的概念之后,内存空间得到了重复的使用:

int i=10;
System.out.println(i);
System.out.println(i);
System.out.println(i);

9 通常访问一个变量包括两种访问形式:

第一种:读取变量中保存的具体数据 get/获取
第二种:修改变量中保存的具体数据 set/设置
i=10; //set
System.out.println(i); //get

10 变量在一行可以声明多个

11 Java中的变量必须先声明,再赋值,才能访问。

int i; //程序执行到这里,内存空间并没有开辟出来,变量i并没有初始化。所以没有赋值之前是无法访问的。

12 关于Java变量的作用域

什么是作用域?
变量的作用域,其实描述的就是变量的有效范围。在什么范围之内是可以被访问的,只要出了这个范围该变量就无法访问了。

变量的作用域只要记住一句话:出了大括号就不认识了。

13 关于变量的分类:

关于变量声明的位置来分类:
局部变量: 在方法体中声明的变量。
成员变量: 在方法体【类体之内】声明的变量叫做成员变量。
在不同的作用域当中,变量名可以相同。
在同一个作用域当中,变量名不能重名。

3、数据类型

1 数据类型的作用是什么?

程序当中有很多数据,每一个数据都是有相关类型的,不同数据类型的数据占用空间大小不同。
数据类型的作用是指导JVM在运行程序的时候給该数据分配多少空间。

2 Java中的数据类型包括两种

基本数据类型
引用数据类型

3 关于基本数据类型

基本数据类型包括四大类八小种:
第一类:整数型 (byte,short,int,long)
第二类:浮点型 (float,double)
第三类:布尔型 (boolean)
第四类:字符型 (char)

4 字符串“abc”不属于基本数据类型,属于 “引用数据类型” ,字符属于基本数据类型:

字符串使用双引号 “ ”
字符使用单引号 ‘ ’

5 八钟基本数据类型各自占用空间大小是多少?

基本数据类型 占用空间大小 【单位:字节】 取值范围
byte 1 -128~127
short 2 ·32768~32767
int 4 -2147483648~2147483647
long 8 ·263~263-1
float 4 有效位6~7位
double 8 有效位15位
boolean 1 true,false
char 2 0~65535
6 计算机在任何情况下都只能识别二进制。例如:只认识010101010100101…

文字与二进制通过字符编码的方式进行对照转换,最先出现的是ASCII码【采用一个字节编码】

支持简体中文的编码方式是GB2312< GBK< GB18030
支持繁体中文:< big5 >
后来出现了一种方式统一了全球所有文字,容量较大,这种编码方式叫做:Unicode编码
unicode编码方式有多种具体的实现:

  • UTF-8
  • UTF-16
  • UTF-32

Java语言采用的编码方式是unicode编码方式,所以“标识符”可以用中文。

2、char、转义、整数型、精度损失

关于八种基本数据类型的默认值

数据类型 默认值
byte,short,int,long 0
float,double 0.0
boolean false【在C语言中true是1,false是0】
char \u0000

成员变量没有手动赋值系统会默认赋值【局部变量则不会】
八钟基本数据类型的默认值都是一切向0看齐。

1、char型

一个中文占用2个字节,char类型正好是2个字节
所以Java中的char类型变量可以存储一个中文字符
“ab”是字符串不能用单引号括起来

2、转义字符 (\)

\ :反斜杠在Java语言中具有转义功能

\n : 换行符
System.out.println与 print 的区别:println输出之后换行,print表示输出,但是不换行。
\t : 制表符 ,即Tab键
\ ’ : 普通的单引号
\ \ : 普通的反斜杠
\ “ : 普通的双引号

怎样在控制台输出“反斜杠字符”?

1
2
char k='\\';
System.out.println();

第一个反斜杠具有转义功能,将后面的反斜杠转义为p普通的反斜杠字符
结论:因此在Java中两个反斜杠代表一个普通的反斜杠字符

怎样在控制台输出一个普通的单引号?

1
2
char a='\''; / / 反斜杠具有转义功能,将第二个单引号转换成普通的单引号字符,第一个单引号和最后一个单引号配对。
System.out.println(a);

3、整数型

1 Java语言当中的“整数型字面值”被默认当做int处理。要让这个“这个整数型字面值”被当做long类型处理的话,需要在“整数型字面值”后面添加1/L,建议使用大写的L。

2 Java语言当中的整数型字面值有三种表现方式:

第一种方式:十进制【是一种缺省默认的方式】 int i=10
第二种方式:八进制【在编写八进制整数型字面值的时候需要以0开始】 int i=010
第三种方式:十六进制【在编写十六进制整数型字面值的时候需要以0x开始】 int i=0x10

long y=2147483648L

long y=2147483648
2147483648是一个过大整数,被当做int类型4个字节处理,但是这个字面值超过int类型范围
解决方法:
在字面值后面添加L,将其当做long类型处理,y是类型变量,以上程序不存在类型转换。

4、精度损失

计算机二进制有三种表示形式:

原码
反码
补码

计算机在任何情况下底层表示和存储数据的时候采用了补码形式。

正数的补码:和原码相同。
负数的补码:负数的绝对值对应的二进制所有二进制位取反,再加1

当一个整数字面值没有超过byte,short,char的取值范围,这个字面值可以直接赋值给byte,short,char类型的变量。这种机制被SUN允许了,目的是为了方便程序员编程。

char cc=65535 //通过
char cc=65536 //编译报错

3、浮点型、布尔型、类型转换、算数运算符、关系运算符、逻辑运算符

1、浮点型

float 单精度【4个字节】
double 双精度【8个字节,精度较高】

在Java语言当中,所有的浮点字面值【3.0】,默认被当做double类型来处理。
要想该字面值当做float类型来处理,需要在字面值后面添加F/f

注意:
double和float在计算机内部二进制存储的时候存储的都是近似值。
在现实世界当中有一些数字是无限循环的,列如:3.3333333…
计算机资源是有限的,用有限的资源存储无限的数据只能存储近似值。

2、布尔型

关于布尔型数据类型:boolean

在Java语言当中boolean类型只有两个值:true、false,没有其他值。
不像C语言当中,0和1可以表示假和真。

在底层存储的时候boolean类型占用1个字节,因为实际存储的时候false底层是0,true底层是1.

布尔类型在实际开发当中非常需要,经常使用逻辑运算条件控制语句当中。

3、类型转换

关于基本数据类型之间的互相转换:

转换规则:

1 八种基本数据类型当中除布尔类型之外剩下的7种类型之间都可以互相转换。

2 小容量向大容量转换,称为自动类型转换,容量从小到大排序:
byte < short ,char < int < long < float < double
注: 任何浮点类型不管占用多少个字节,都比整数型容量大。
char 和 short可表示的种类数量相同,但是char可以取更大的正整数 。

3 大容量转换成小容量,叫做强制类型转换,需要加强制类型转换符,程序才能编译通过,但是在运行阶段可能会损失精度,所以谨慎使用。

4 当整数字面值没有超出byte,short,char的取值范围,可以直接赋值给byte,short,char类型的变量。

5 byte,short,char混合运算的时候,各自先转换成int类型再做运算。

6 多种数据类型混合运算,先转换成容量最大的那种类型再做运算。

4、算数运算符

关于Java编程中的运算符之:算数运算符

+ 求和
- 相减
* 乘积
/
% 取余数【取模】
++ 自加1
- - 自减1

小结:++运算符可以出现在变量前,也可以出现在变量后,,无论是变量前还是变量后只要++运算结束,该变量中的值一定会自加1

++a:先自增1,在进行运算。
a++:先运算,再自增1。

5、关系运算符

> 大于
>= 大于等于
< 小于
<= 小于等于
== 等于
!= 不等于

= 是赋值运算符;
== 是关系运算符
关系运算符的结果一定是布尔类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class OperatorTest
{
public static void main(String[] args){
int a=10;
int b=10;

System.out.println(a>b); //false
System.out.println(a>=b); //ture
System.out.println(a<b); //false
System.out.println(a<=b); //true
System.out.println(a==b); //true
System.out.println(a!=b); //false
}
}

6、逻辑运算符

逻辑运算符 解释
& 逻辑与 (两边的算子都是true,结果才是true)
逻辑非(取反,!false就是true,!true就是假,这是一个单目运算符)
^ 逻辑异或(两边的算子只要不一样,结果就是true)
&& 短路与(第一个表达式执行结果是false,会发生短路与)

| 逻辑或(两边的算子只要有一个是true,结果就是true)
|| 短路或(第一个表达式执行结果是true,会发生短路或)

1 逻辑运算符要求两边的算子都是布尔类型,并且逻辑运算符最终的运算结果也是一个布尔类型。

2 短路与和逻辑与最终的运算结果是相同的,只不过短路与存在短路现象。
3 短路或和逻辑或最终的运算结果是相同的,只不过短路或存在短路现象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
** 逻辑与和短路与的区别** 
public class Test01
{
public static void main(String[] args){
int x=10;
int y=8;
System.out.println(x < y& ++x < y); //进行了++x的操作
System.out.println(x); //x=11
}
}

public class Test02
{
public static void main(String[] args){
int x=10;
int y=8
System.out.println(x < y& ++x < y); //因为x<y是false,直接短路,后面的操作就不在执行
System.out.println(x); //x=10
}
}

从某个角度来看,短路与更智能。由于后面的表达式可能不执行,所以执行效率较高。这种方式在实际开发中使用较多。短路与比逻辑与使用的多。短路与更常用。

但是,在某些特殊的逻辑业务中,要求运算符两边的算子必须全部执行,此时必须使用逻辑与,不能使用短路与,使用短路与可能导致右边的表达式不执行。

4、赋值运算符、字符串连接运算符、三目运算符/三元运算符/条件运算符

1、赋值运算符

java中赋值类运算符包括两种:

基本的赋值运算符:=
扩展的赋值运算符:+=、-+、*=、/+、%=

赋值类的运算符优先级:先执行等号右边的表达式,将执行结果赋值给左边的变量。

注意以下代码:

byte x=10;
x += 5;//并不等同于:x =x+5,而等同于:x= (byte)(x+5)

int y=10;
y += 5; //等同于:y= (int)(y+5)

long a=10L;
int b=20;
b += a; // 等同于:b = (int)(b+a)

重要结论:扩展类的赋值运算符不改变运算结果类型,假设最初这个变量的类型是byte类型,无论怎么追加或追减,最终该变量的数据类型还是byte类型。

2、字符串连接运算符

关于java中的 “+” 运算符:

1 +运算符在java语言中当中有两个作用:

加法运算
字符串的连接运算

2 数字+数字 →数字【求和】
数字+“字符串” →“字符串”【字符串连接】

3 在一个表达式当中可以出现多个“+”,在没有添加小括号的前提下,遵循自左向右的顺序依次运算。

1
2
3
4
5
6
7
8
9
10
public class Test{
public static void main(String[] args){
int a=10;
int b=20;
System.out.println("10+20=30"); //在控制台输出“10+20=30”
System.out.println("10+20="+a+b); //以动态方式输出“10+20=1020”
System.out.println("10+20="+(a+b)); //输出10+20=30
}
}
123456789

String是引用数据类型,s是变量名,“abc”是String类型的字面值

String s=“abc”

3、三元运算符

三元运算符/三目运算符/条件运算符
1、语法规则:

布尔表达式?表达式1:表达式2

2、三元运算符的执行原理

当布尔表达式的结果是true的时候,选择表达式1作为整个表达式的执行结果
当布尔表达式的结果是false的时候,选择表达式2作为整个表达式的执行结果

三、流程控制语句

1、控制语句、java输入语句、if语句

Java控制语句可以分为7种:

控制选择结构语句

  • if、if else
  • switch

控制循环结构语句

  • for
  • while
  • do while

改变控制语句顺序

  • break
  • continue

1、if语句

if语句属于选择结构,if语句又被称为分支语句/条件控制语句

1 if语句的语法结构:四种编写方式

第一种
if(布尔表达式){
java语句;
java语句;

}

第二种
if(布尔表达式){
java语句;
java语句;

} else {
java语句;
java语句;

}

第三种
if(布尔表达式){
java语句
java语句

}else if(布尔表达式){
java语句
java语句

}
else if(布尔表达式){
java语句
java语句

}else if(布尔表达式){
java语句
java语句

}…

第四种
if(布尔表达式){
java语句
java语句

}else if(布尔表达式){
java语句
java语句

}
else if(布尔表达式){
java语句
java语句

}else {
java语句
java语句

}…

2 重点 :对于java语句中的if语句来说,只要有一个分支执行,整个if语句全部结束。

3 注意:以上的第二种编写方式和第四种编写方式都带有else分支,这两种方式可以保证100%会有分支执行。

4 所有的控制语句都是可以嵌套使用的,只要合理嵌套就行。

注意:嵌套使用的时候,代码格式要保证完美。【该缩进的时候必须缩进,大部分情况下使用大括号包围的需要缩进】

5 if语句的分支中只有一条java语句的时候,大括号可以不写。

1
System.out.println();  负责向控制台输出

6 接收用户键盘的输入语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class KeyInputTest
{
publid static void main(String[] args){
java.util.Scanner s = new java.util.Scanner(System.in); //第一步,创建键盘扫描器对象

输入字符:
String UserInputContent = s.next();
//第二步:调用Scanner对象的next方法开始接收用户的键盘输入
//程序执行到这里会停下来,等待用户的输入
//当用户输入的时候,并且最终敲回车的时候,输入的信息会自动赋值给userInputContent
//程序执行到这里,用户输入的信息已经到内存中了,
System.out.println("您输入了:" + UserInputContent).; //将内存的数据输入到控制台

输入数字
int num = s.nextInt(); //接收数字
System.out.println("您输入的数字是:" + num);
}
}

正确语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class IfTest01
{
public static void main(String[] args){
boolean sex=true;
if (sex){
System.out.println("男");
}else{
System.out.println("女");
}

sex=false;

if(sex) System.out.println("男"); else System.out.println("女");

}
}

错误语句:

1
2
3
4
5
6
7
8
9
10
11
public class IfTest02
{
public static void main(String[] args){
boolean sex=true;
if(sex)
System.out.println("男");
System.out.println("hehe"); //不加大括号的话只包括第一条语句,第二条语句不是if语句的分支java语句
else //else缺少if
System.out.println("女");
}
}

2、switch控制语句

关于switch语句:

1、witch语句也属于选择结构,也是分支语句

2、switch语句的语法结构:
一个比较完整的switch语句应该这样编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
switch (int或String类型的字面值或变量){
case int或String类型的字面值或变量:
java语句;
java语句;
...
break;
case int或String类型的字面值或变量:
java语句;
java语句;
...
break;
case int或String类型的字面值或变量:
java语句;
java语句;
...
break;
default:
java语句;
...
}

3、switch语句的执行原理:

switch语句后面小括号当中的“数据”进行一一匹配,匹配成功的分支执行按照自上而下的顺序依次匹配。

4、匹配成功的分支执行,分支当中最后有“break;”语句的话,整个switch语句终止。

5、匹配成功的分支执行,分支当中没有“break;”语句的话,直接进入下一个分支执行(不进行匹配),这种现象被称为 case穿透 现象。【提供break;语句可以避免穿透】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
case穿透:
int i=10
switch(i){
case 1
java语句;
case 2
java语句;
case 3
java语句;
break//输入1,输出的结果是前三条java语句
case 4:
java语句;
System.out.println("Test Code!");
break;
}

6、所有分支都没有匹配成功,当有default的语句的话,会执行default分支当中的程序

7、switch后面和case后面只能是int或者String类型的数据,不能是探测其他类型。

  • 当然byte,short,char也可以直接写到switch和case后面,因为它们可以进行自动类型转换。byte,short,char可以自动转换成int类型。
  • JDK6的,switch和case后面只能探测int类型。
  • JDK7之后包括7版本在内,引入新特性,switch关键字和case关键字后面可以探测 int 和string类型的数据。

8、case可以合并:

1
2
3
4
5
6
7
8
9
10
int i=10
switch(i){
case 1case 2case 3//输入1、2、3输出的结果相同
java语句;
break
case 4:
java语句;
System.out.println("Test Code!");
break;
}

10、switch语句例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class SwitchTest01
{
public static void main(String[] args){
long a = 10L;
int b = a; //编译错误

long x = 10L;
switch(x){} //编译报错,switch语句不能用long

解决编译错误:
long x = 10L;
switch((int)x){}

byte b = 10;
switch(b){} //编译通过

short s = 10;
switch(s){} //编译通过

char c = 'A';
switch(c){} //编译通过

char cc = 12;
switch(cc){} //编译通过

//编译报错
//switch(ture){}

String username = "zhangsan";
switch(username){}
}
}

11、switch确实可以探测String类型,这是Java7的新特性。

简单计算器系统实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class SwitchTest02()
{
public static void main(String[] args){
java.util.Scanner s = new java.util.Scanner(System.in);
System.out.println("欢迎使用简单计算器系统:");
System.out.print("请输入第一个数字:");
int num1 = s.nextInt();
System.out.print("请输入运算符");
String operator = s.next();
System.out.print("请输入第二个数字:");
int num2 = s.nextInt();

int result = 0;
switch(operator){
case "+" :
result = sum1 + sum2;
break;
case "-" :
result = sum1 - sum2;
break;
case "*" :
result = sum1 * sum2;
break;
case "/" :
result = sum1 / sum2;
break;
case "%" :
result = sum1 % sum2;
break;
default :
System.out.println("输入运算符号错误");
}
System.out.println("运算结果为:"+ num1 + operator + num2 + "=" + result );
}
}

假设系统给定考生成绩,判断该考生的成绩等级:

  • 有效成绩范围:【0-100】
  • 考试成绩可以有小数
  • 考试成绩和等级之间的对照关系:
    【90-100】 A
    【80-90】 B
    【70-80】 C
    【60-70】 D
    【0-60】 E
  • 以上需求必须采用switch语句完成,不能采用if
    窍门:(int)(成绩 / 10)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SwitchTest03()
{
public static void main(String[] args){
double score = 数字:
int grade = (int)(score/10);
switch(grade){
case 9:case 10:
System.out.println("A");
break;
case 8:
System.out.println("B");
case 7:
System.out.println("C");
case 6:
System.out.println("D");
default:
System.out.println("E");
}
}
}

3、循环结构、for控制语句

1、循环结构

在程序当中总有一些需要反复的执行的代码,假设没有循环结构,那么这段需要重复执行的代码自然是需要重复编写的。代码无法得到重复使用。所以多数编程语言都是支持循环结构的。将来把需要反复执行的代码片段放到“循环体”中,再联合“计数器”,共同控制这段需要反复执行的代码。

基本上的所有编程语言支持的循环包括三种:

  • for 循环
  • while 循环
  • do…while 循环

2、for循环

1 语法结构

1
2
3
4
5
 for (初始化表达式 ; 布尔表达式 ;更新表达式) {

循环体:由java语言构成,是需要重复执行的代码片段

}

2 for循环的执行过程 / 执行原理?

2.1 初始化表达式、布尔表达式、更新表达式不是必须的!【但是两个分号是必须的】

2.2 初始化表达式最先执行,并且在整个for循环当中只执行一次。

2.3 布尔表达式必须是true/false,不能是其他值。

2.4 for的执行过程:

1、先执行初始化表达式,并且该表达式只执行一次。

2、判断布尔表达式的结果是true还是false:

  • 布尔表达式为true:
    1、执行循环体
    2、执行更新表达式
    3、判断布尔表达式的结果是true还是false
    布尔表达式true
    1、执行循环体
    2、执行更新表达式
    3、判断布尔表达式的结果是true还是false
  • 布尔表达式为false
    循环结束

循环语句和条件判断语句嵌套使用【for和if的嵌套】

找出1~100所有的奇数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ForTest01
{
public static void main(String[] args){
第一种方法
for(int i = 1;i<=100;i+=2){
System.out.println("奇数-->" + i);
}

第二种方法
for(int i=1;i<=100;i++){
if(i%2 != 0){
System.out.println(i)
}
}
}
}
12345678910111213141516

求1~100所有奇数的和

1
2
3
4
5
6
7
8
9
public class ForTest02
{
public static void main(String[] args){
int sum=0;
for(int i=1;i<=100;i+=2){
sum+=i;
}
System.out.println("sum=" + sum);
}

3 for循环也可以嵌套for循环

内层循环中的变量名和外层循环中的变量名不能重名

使用for循环输出九九乘法表

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ForTest03
{
public static void main(String[] args){
for(int i=1;i<=9;i++){
for(int j=1;j<=i;j++){
int a=i*j;
System.out.print(i +"*"+j+"="+a );
}
System.out.println(); //换行
//System.out.print("\n");
}
}
}

编写for循环找出1~100中的所有素数

素数: 又被称为质数,能够被1和自身整除,不能被其他数字整除的数字称为素数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ForTest04 {

public static void main(String[] args) {
for(int i=2;i<=100;i++){
boolean flag=true; //标记i是否能被其他数整除
for(int j=2;j<i;j++){ //此刻i只能被1和本身整除,所以应从2~i-1
if(i%j==0){
flag=false; //如果i能被其他数整除,就跳出循环
break;
}
}
if (flag)
System.out.print(i+" ");
}
}
}

4、while控制语句、do…while控制语句、break、continue控制循环语句

1、while循环的语法结构

1
2
3
4
	while(布尔表达式){
循环体;
}
123

2、while循环的执行原理:

判断布尔表达式的结果,如果结果为true就继续执行循环体,直到布尔表达式的结果为false,循环结束。

3、while循环的循环次数::0~N次

注意:while循环的循环体可能执行次数为0次。

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

public static void main(String[] args){
int i = 10;
int j = 3;
while(i >j){
System.out.println("死循环");
}
//编译通过
System.out.println("Hello World!");

//编译错误
/*
while(10 > 3){
System.out.println("死循环");
}
*/

//编译错误:无法访问语句
//System.out.println("Hello World!");
}
}
}
1234567891011121314151617181920212223

1、do…while循环控制语句

1、do…while循环的语法结构

1
2
3
do{
循环体;
}while(布尔表达式);

2、do…while循环的执行原理

先执行循环体,然后判断布尔表示是是否为true,true则执行,false则循环结束。

3、do…while循环的执行次数

do…while循环的循环体代码片段执行次数是:1~N次【至少一次】

4、使用do…while循环的注意事项

do…while循环语句最终有一个 “分号” 别丢了。

1
2
3
4
5
6
7
8
9
10
11
12
public class DoWhileTest{
public static void main(String[] args){
int i=10;
do{
System.out.println(i);
}while(i>100); //输出10,先执行在进行判断

while(i>100){
System.out.println("i-->"+i);
} //无输出
}
}

2、break控制语句

  • break是java语言当中的关键字,被翻译为“中断/折断”。
  • break + “;”可以成为一个单独的完整的java语句: break;
  • break语句使用在switch语句当中,用来终止switch的语句执行。
  • break语句同样可以使用在循环语句当中,用来终止循环的执行。
  • break终止哪个循环呢?
  • break;语句使用在for、while、do…while循环语句当中用来跳出循环,终止循环的执行。因为当程序循环到某个条件时,后续的循环就没必要执行了,在执行也是耗费资源,所以可以终止循环,这样可以提高程序的执行效率。
  • 在默认情况下:break语句终止的是离它最近的循环语句。
  • 当然也可以指定终止某个循环,需要给循环起名,采用这种语法: break 循环名称;

以下以for循环为例解释 break; 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class BreakTest{
public static void main(String[] args){
for(int i=0;i<10;i++){
if(i==5)
break;
}
System.out.println(i); //0 1 2 3 4

for(int j=0;j<3;j++){
for(int i=0;i<10;i++){
if(i==5)
break;//这里的break语句终止的是内层for循环,因为这个for离它最近。
//这里的break语句不会影响到外层的for循环
}
System.out.println(i); //0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
}

//给for循环起名
for1:for(int j=0;j<3;j++){
for2:for(int i=0;i<10;i++){
if(i==5){
break for1;//终止for1循环
}
System.out.println(i); //0 1 2 3 4
}
}
}
}

3、continue控制语句

1、continue表示:继续/go on/ 下一个

2、continue也是一个continue关键字加一个分号构成一个单独的完整的java语句,主要出现循环语句当中用来控制循环的执行。

3、break和continue的区别:

  • break表示循环不执行了。
  • continue表示直接进入下一次循环继续执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ContinueTest{
public static void main(String[] args){
for(int i=0;i<10;i++){
if(i==5)
break;
}
System.out.println(i); //0 1 2 3 4

for(int i=0;i<10;i++){
if(i==5)
continue; //只要这个语句执行,当前本次循环停止,直接进入下一次循环“继续”执行
}
System.out.println(i); //0 1 2 3 4 6 7 8 9
}
}

4、continue也有这样的语法:

continue 循环名称;

四、构造方法

1、方法的基础知识、语法及含义、方法调用规则

1、方法

在Java语言当中应该有这样一个机制:

  • 某个功能代码只需要写一遍
  • 要使用这个功能,只需要给这个功能传递具体的数据
  • 这个功能完成之后返回一个最终的结果。

这样代码就可以重复使用了,提高代码的复用性。【这就是 “方法”

使用这个方法我们称为 “调用/invoke”

2、方法的本质

方法就是一段代码片段,并且这段代码片段可以完成某个特定的功能,并且可以被重复使用。
方法,对应的英语单词:Method,在C语言中叫做函数:Function、

方法定义在类体中,在一个类当中可以定义多个方法,方法编写的位置没有先后顺序,可以随意。

方法体中不能再定义方法!!!!!!

3、方法的基础语法

1、语法结构

1
2
3
[修饰符列表] 返回值类型 方法名(形式参数列表){
方法体;
}

2、对以上语法结构进行解释说明:

2.1 关于修饰符列表
  • 可选项,不是必须的
  • 目前统一写成public static
  • 方法的修饰符列表当中”有static关键字”的话,怎么调用这个方法?
    类名.方法名(实际参数列表);
2.2 返回值类型

1、什么是返回值?

  • 一个方法是可以完成某个特定功能的,这个功能结束之后大多数都是需要返回最终执行结果的,执行结果可能是一个具体存在的数据。而这个具体存在的数据就是返回值。

2、返回值类型?

  • 返回值就是一个具体存在的数据,数据都是有类型的,此处需要指定的是返回值的具体类型。

3、返回值类型都可以指定哪些类型呢?

  • java任意一种类型都可以,包括基本数据类型和所有的引用数据类型。

4、也可能这个方法执行结束之后不返回任何数据,java中规定,当一个方法执行结束之后不返回任何数据的话,返回值类型位置必须编写:*void 关键字*。

5、返回值类型可以是:

  • byte,short,int,long,float,double,boolean,char,string,void…

6、返回值类型若不是 void,表示这个方法执行结束之后必须返回一个具体的数值。当方法执行结束的时候,没有返回任何数据的话,编译器报错。怎么返回值呢?代码怎么写呢?“return 值;”并且要求“值”的数据类型必须和“方法的返回值类型”一致,不然编译器报错。

7、返回类型是void的时候,在方法体当中不能编写“return 值;”这样的语句。但是要注意可以编写“return;”这样的语句。

8、只要带有关键字的语句执行,return语句所在的方法结束。【不是JVM结束,是return所在的方法结束】

2.3 方法名
  • 只要是合法的标识符就行
  • 方法名最好见名知意
  • 方法名最好是动词
  • 方法名首字母要求是小写,后面每个单词首字母大写
2.4 形式参数列表:简称形参
  • 形参是局部变量:int a;double b;…
  • 形参的个数可以是0~N个
  • 多个形参之间用“逗号”隔开
  • 形参中起决定性作用的是形参的数据类型,形参的名字就是局部变量的名字。
  • 方法在调用的时候,实际给这个方法传递的真实数据被称为:实际参数,简称实参
  • 实参列表和形参列表必须满足:
    1 数量相同
    2 类型对应相同
2.5 方法体必须由大括号括起来,方法体中的代码有顺序

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//public表示公开的
//class表示定义类
//MethodTest是一个类名
public class MethodTest{ //表示定义一个公开的类,起名MethodTest,由于是公开的类,所以源文件名必须为:MethodTest
//类体
//类体中不能出现直接编写java语句,除声明变量之外
//方法出现在类体当中

//方法
//public表示公开的
//static表示静态的
//void表示方法执行结束后不返回任何数据
//main是方法名:主方法
//(String[] args):形式参数列表,其中String[]是一种引用数据类型,args是一个局部变量的变量名
//所以以下只有args这个局部变量的变量名是随意的
//主方法就需要这样固定编写,这是程序的入口。【SUN公司规定的,必须这样写】
public static void main(String[] args){

//这里的程序是一定会执行的
//main方法是JVM负责调用的,是一个入口的位置
//从这里作为起点开始执行程序
//既然是这样,我们就可以在这里编写java语句来调用其他方法
//调用MethodTest的sum方法,传递两个实参
MethodTest.sum(10,20);//(10,20)实参列表

//注意:方法体当中的代码是有顺序的,遵循自上而下的顺序以此执行
//上一行代码的程序执行不结束,无法执行下一行代码。

//一个方法可以被重复使用,重复调用
int a=100;
MethodTest.sum(a,500);// (a,500)实参列表

//再次调用方法
int k=90;
int f=10;
MethodTest.sum(k,f);//(k,f)实参列表
}

//自定义方法,不是程序的入口
//方法的作用:计算两个int类型数据的和,不要求返回结果,但是要求将结果直接输出到控制台
//修饰符列表:public static
//返回值类型:void
//方法名:sum
//形式参数列表:(int x,int y)
//方法体:主要任务是求和之后输出计算结果
public static void sum(int x, int y){
System.out.println(x+'+'+ y +'='+ (x+y))
}
}

3、方法怎么调用?

方法只定义不去调用是不会执行的,只有在调用的时候才会执行。
语法规则:《方法的修饰符列表当中有static》

  • 类名.方法名(实参列表);<这是一条java语句,表示调用某个类的某个方法,传递这样的实参。>

2、java的方法调用、调用的实参与形参类型、方法返回值类型不是void、深入return语句

1、Java语言的方法

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MethodTest01(){
public static void sum(int a,int b){
System.out.println(a+"+"+b+"="+(a+b));
//调用doSome方法
MethodTest.doSome();
}
//主方法
public static void main(String[] args){
//调用sum方法
MethodTest.sum(1,2);
}

public static void doSome(){
System.out.println("do some!");
}
}

以上代码了看出:方法的调用不一定在main方法当中。只要是程序可以执行到的位置,都可以调用其他的方法。

1、方法调用的时候实参和形参要求个数对应相同,数据类型对应相同。

类型不同的时候要求能够进行相应的自动类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MethodTest02(){
//主方法
public static void main(String[] args){
MethodTest02.sum();//编译错误:参数数量不同
MethodTest02.sum(true,false);//编译错误:实参和形参的类型不是对应相同的
MethodTest02.sum(10L,20L);//编译通过
MethodTest02.sum(10,20);//编译通过:存在自动类型转换:int-->long
MethodTest02.sum(3.0,20);//编译错误:参数类型不是对应相同的
MethodTest02.sum((long)3.0,20);//编译通过
}

public static void sum(long a,long b){
System.out.println(a+"+"+b+"="+(a+b));

}
}

2、方法调用

1、方法的修饰符列表当中有static关键字,完整的调用方法是:类名 . 方法名(实参列表)
2、但是,有时候 “ 类名.” 可以省略,什么情况下可以省略呢?

  • 对于方法的修饰符列表当中有static关键字的:“类名.” 可以省略不写。
  • 调用同个类中的方法可省略不写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MethodTest03(){

public static void main(String[] args){
System.out.println("main begin");
m1();
System.out.println("main over");
}

public static void m1(){
System.out.println("m1 begin");
m2();
System.out.println("m2 over");
}

public static void m2(){
System.out.println("m2 begin");
m3();
System.out.println("m2 over");
}

public static void m3(){
System.out.println("m3 begin");
System.out.println("m3 over");
}
}

输出结果:
输出结果

方法中的代码是自上而下的顺序依次执行的

1、方法的返回值类型不是void的时候

  • 返回值类型不是void的时候:要求方法必须保证百分百的执行“return 值;”这样的语句来完成值的返回。没有这个语句编译器会报错。
  • 一个方法有返回值的时候,当我们调用这个方法的时候,方法返回了一个值,对于调用者来说,这个返回值可以选择接收,也可以选择不接收。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class MethodTest04(){

public static void main(String[] args){
//调用方法
divide(10,3);//这里没有接收这个方法的返回数据

//这里接收返回值
//采用变量接收
//变量的数据类型需要和返回值的数据类型相同,或者可以自动类型转换
int i = divide(10,3);
}

` /*
需求:
请定义并实现一个方法,该方法可以计算两个int类型数据的商,
要求将最终计算结果返回给调用者。
*/

//编译错误:缺少返回值
/*public static int divide(int a, int b){
return;
}

//编译错误:方法定义的时候要求返回一个int类型,此时返回布尔类型,类型不兼容
public static int divide(int a, int b){
return true;
}

//可以:但是具体方法体中编写的代码无法满足当前的需求
public static int divide(int a, int b){
return 1;
}

public static int divide(int a, int b){
int c=a/b;
return c;
}*/

public static int divide(int a, int b){
return a/b;
}
}

2、深入return语句

  • 带有return关键字的java语句只要执行,所在的方法执行结束。
  • 在“同一个作用域”当中,return语句下面不能编写任何代码,因为这些代码永远都执行不到,所以编译报错。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MethodTest05(){

public static void main(String[] args){
int reValue=m();
System.out,println(reValue);
}

//编译报错:缺少返回语句,以下程序编译器认为无法百分百保证“return 1;”会执行
/*public static int m(){
int a=10;
if(a>3){
return 1;
}
}
*/

public static int m(){
int a=10;
if(a>3){
return 1;
//这里不能编写代码,编译错误,因为无法执行访问的语句
//System.out.println("Hello");
}
//这里的代码可以执行
System.out.println("Hello");
return 0;
}

}

在返回值类型是void的方法当中使用“return;”语句。“return;”语句当中出现在返回值为void的方法当中主要是为了终止方法的执行。

return;直接终止方法;break只是终止循环。
return更强

3、方法执行JVM内存分析

1、方法在执行过程当中,在JVM中的内存是如何分配的呢,内存是如何变化的?

  • 方法只定义,不调用,是不会执行的,并且在JVM中也不会给该方法分配“运行所属”的内存空间。只有在调用这个方法的时候,才会动态的给这个方法分配所属的空间
  • 在JVM内存划分上有这样三块主要的内存空间:
    1、方法区内存
    2、堆内存
    3、栈内存
  • 关于栈数据结构
    1、栈:stack,是一种数据结构
    2、数据结构反应的是数据的存储形态。
    3、数据结构是独立的学科,不属于任何编程语言的范畴,只不过在大多数编程语言当中要使用数据结构。
    4、作为程序员需要提前精通:数据结构+算法【计算机专业必修的一门语言】
    5、常见的数据结构:数组、队列、栈、链表、二叉树、哈希表…
  • 方法代码片段存在哪里?方法执行的时候执行过程的内存在哪里分配?
    1、方法片段属于.class字节码文件的一部分,字节码文件在类加载的时候,将其放到了方法区当中。所以JVM中的三块主要的内存空间中的方法区内存最先有数据,存放了代码片段。
    2、代码片段虽然在方法区内存当中只有一份,但是可以被重复调用。每一次调用这个方法的时候,需要给该方法分配独立的活动场所,在栈内存中分配。【栈内存中分配方法运行的所属空间】
  • 方法在调用的时候,会给该方法分配独立的内存空间,在栈中分配,此时发生压栈动作,方法执行结束之后,给该方法分配的内存空间全部释放,此时发生弹栈动作。
    压栈:给方法分配内存。
    弹栈:释放该方法的内存空间。
  • 局部变量在栈中存储。局部变量在运行阶段内存在栈中分配。

在这里插入图片描述
栈内存:
1) 是为java方法提供运行空间的
2) 方法一旦被调用就会在栈中创建对应的栈帧,而方法的整个执行过程就是方法对应的栈帧从入栈到出栈的过程。换言之,就是方法被调用进栈(压栈 入栈),方法执行结束出栈(弹栈)。
3) 栈是先进后出后进先出(先被调用的方法最后结束,后被调用的方法最先结束)
4) 栈中的变量都是属于方法的,所以都是局部变量,且局部变量必须初始化值
5) 栈生命周期与其所属线程的生命周期一致,可以认为栈具有自动销毁机制。

堆内存:
1) 是为实体对象来开辟空间的,换言之就是实体对象的空间都在堆中开辟。凡是被new出来的都是对象。
2) 堆中的变量是对象变量,因为是属于对象的,且是随着对象的创建而产生随着对象的销毁而销毁。
3) 堆中的变量(对象变量)都有默认值:

  • 整数:0
  • 浮点型:0.0
  • 布尔型:false
  • char型:’ ’
  • 对象:null

4) 堆没有自动销毁机制,它里面的垃圾由垃圾回收器负责收集(通过收集算法判断哪个对象属于垃圾然后再清理)
5) 堆中的空间都有一个16进制的首地址作为地址进行区分。

方法区:
方法区中存放已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
常量是存放在方法区中的运行时常量池中的。

重点:方法调用的时候,在参数传递的时候,实际上传递得是变量中保存的那个“值”传过去了。

4、方法重载机制overload、方法递归

1、方法重载(overload)

  • 功能虽然不同,但是“功能相似”的时候,方法重载机制可以让相似的方法就像在用一个方法一样。【Java支持这种机制而有些语言则不支持,例如JavaScript】
  • 功能相似的时候,方法名可以相同。

1、什么时候考虑使用方法重载?

  • 功能相似的时候,尽可能让方法名相同【但是功能不同/不相似的时候,尽可能让方法名不同】

2、什么条件满足之后构成了方法重载

  • 在同一个类当中
  • 方法名相同
  • 参数列表不同:
    1、数量不同:public static void m1(){} 与 public static void m1(int a){}
    2、顺序不同: public static void m2(int a,double b){} 与 public static void m2(double a,int b){}
    3、类型不同:public static void m3(int x){} 与 public static void m3(double x){}
1
2
3
方法重复,编译错误。
public static void m4(int a,int b){}
public static void m4(int b,int a){}

3、方法重载和什么有关系,和什么没有关系

  • 方法重载和方法名+参数列表有关系
  • 方法重载和返回值类型无关
  • 方法重载和修饰符列表无关

2、方法递归

  • 什么是递归?
    即:方法自身调用自身。
1
2
3
a(){
a();
}
  • 递归是很耗栈内存的,递归算法可以不用的时候尽量别用。
  • 以下程序运行的时候发生这样的一个错误【不是异常,是错误error】:
    java.lang.StackOverflowError
    栈内存溢出错误
    错误发生无法晚挽回,只有一个结果。就是JVM停止工作。
  • 递归必须有结束条件,没有结束条件一定会发生栈内存溢出错误。
  • 递归即使有了结束条件,即便结束条件是正确的,也可能会发生栈内存溢出错误,因为递归太深了。

注意:

  • 递归可以不使用尽量别用。
  • 但是有些情况下该功能的实现必须依靠递归方式。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//递归计算前n个数的和:
public class RecursionTest01{
publci static void main(String[] args){
int n=4;
int reValue=sum(n);
System.out.println(reValue);
}

public static int sum(int n){
if(n==1){
return 1;
}
return n+sum(n-1);
}
}

//递归计算n的阶乘:
public class RecursionTest02{
publci static void main(String[] args){
int n=5;
int reValue=method(n);
System.out.println(reValue);
}
public static int method(int n){
if(n==1){
return 1;
}
return n*method(n-1);
}
}

五、面向对象及封装

1、面向对象和面向过程的区别、面向对象、类和对象概念、类的定义

1、面向对象和面向过程的区别

  • 面向过程: 主要关注点是:实现的具体过程,因果关系【集成显卡的开发思路】
    优点: 对于业务逻辑比较简单的程序,可以达到快速开发,前期投入成本较低。
    缺点: 采用面向过程的方式开发很难解决非常复杂的业务逻辑,另外面向过程的方式导致软件元素之间“耦合度”非常高,只要其中一环出现了问题,整个系统受到影响,导致最终的软件扩展力差。另外,由于没有独立体的概念,所以无法达到组件复用。
  • 面向对象: 主要关注点是:主要关注对象【独立体】能完成哪些功能。【独立显卡的开发思路】
    优点: 耦合度低,扩展能力强。更容易解决现实世界当中更复杂的业务逻辑,组件复用性强。
    缺点: 前期投入成本高,需要进行独立体的抽取,大量的系统分析与设计。
  • C语言是纯面向过程的,C++半面向对象、Java纯面向对象

2、面向对象的三大特征

  • 封装
  • 继承
  • 多态

注:所有面向对象的编程语言都有这三大特征。

采用面向对象的方式开发一个软件,生命周期当中:【整个生命周期中贯穿使用OO面向对象方式】

  • 面向对象的分析:OOA
  • 面向对象的设计:OOD
  • 面向对象的编程:OOP

3、类的对象的概念

1、什么是类?

  • 类在现实世界当中是不存在的,是一个模板,是一个概念。是人类大脑思考抽象的结果。
  • 类代表了一种事物。
  • 在现实世界中,对象A与对象B之间具有共同特征,进行抽象总结出一个模板,这个模板被称为类。

2、什么是对象?

  • 对象是实际存在的个体。现实世界当中实际存在。

4、软件开发的过程:

  • 程序员先观察现实世界,从现实世界当中寻找对象
  • 寻找了N个对象之后,发现所有的对象都有共同特征
  • 程序员在大脑中形成一个模板【类】
  • Java程序员可以通过java代码来表述这个类
  • Java程序中有了类的定义
  • 然后通过类就可以创建对象
  • 有了对象之后,可以让对象直接协作起来形成一个系统。

类–【实例化】->对象

对象又被称为实例/instance

对象–【抽象】–>类

重点:

  • 类描述的是对象的共同特征。
  • 共同特征例如:身高特征
    这个身高特征在访问的时候,必须先创建对象,通过对象去访问这个特征。因为这个特征具体的某个对象上之后,值不同。有的对象身高1.80,有的对象身高2.80.

5、一个类主要描述什么信息呢?

一个类主要描述的是:状态+动作
状态信息:名字、身高、性别、年龄
动作信息:吃、喝、玩、乐

  • 状态–>一个类的属性
  • 动作–>一个类的方法
1
2
3
4
类{
属性; //描述对象的状态信息
方法; //描述对象的动作信息
}

注意:状态和动作当具体到某个对象上之后,发现最终的结果可能不一样。

4、类的定义【开始编写代码】

语法结构:

1
2
3
4
[修饰符列表] class 类名{
属性;
方法;
}

重点:属性通常是采用一个变量的形式来完成定义的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
定义一个类,类名Student
Student是一个类。代表了所有的学生对象,是一个学生模板
public class Student{//定义一个公开的类

//属性【描述的是对象的状态信息】
//属性通常采用变量的方式来定义
//在类体当中,方法体之外定义的变量被称为“成员变量”
//成员变量没有赋值,系统赋默认值:一切向0看齐。
int no;//学号
String name;//姓名
boolean sex;//性别
int age;//年龄
String address;//住址
//方法
//方法描述的是对象的动作信息
//当前例子就只描述属性,不描述方法。

}

2、对象的创建和使用、内存分析

1、对象的创建和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class OOTest01{
publci static void main(String[] args){
int i=10;
Student s = new Student();

int StuNo=s.no;
System.out.println("学号 =" + StuNo);//0【默认值】
System.out.println("学号 =" + s.no);//0【默认值】
s.no=10;
System.out.println("学号 =" + s.no);//10

//再通过类实例化一个全新的对象
//stu是一个引用
//stu同时也是一个局部变量
//Student是变量的数据类型
Student stu = new Student();
System.out.println("学号 =" + stu.no);// 0

System.out.println("学号 =" + Student.no);//编译错误,不能直接采用“类名”的方式访问
//因为no是实例变量,对象级别的变量,变量存储在java对象的内部,必须先有对象,通过对象才能访问no这个实例变量,不能直接通过“类名”访问
}
}
//学生类
//学生类是一个模板
//描述了所有学生的共同特征【状态+行为】
//当前类只描述学生的状态信息【属性】
public class Student{
//类体=属性+方法

//属性【存储数据采用变量的形式】
//由于变量定义在类体当中,方法体之外,这种变量称为成员变量
//所有学生都有学号信息
//但是每一一个学生的学号都是不同的
//所以要访问这个学号必须先创建对象,通过对象去访问学号信息
//学号信息不能直接通过"类"去访问,所以这种成员变量又被叫做:实例变量
//对象又被称为实例,实例变量又被称为对象变量。【对象级别的变量】
//不创建对象,这no变量的内存空间是不存在的,只有创建了对象,这个no变量内存空间才会创建。
int no;
String name;//姓名
boolean sex;//性别
int age;//年龄
String address;//住址
  • 通过一个类可以实例化N个对象,
  • 实例化对象的语法:new 类名()
  • new是java语言当中的一个运算符
  • new运算符的作用是创建对象,在JVM堆内存当中开辟新的内存空间
  • 方法区内存:在类加载的时候,class字节码代码片段被加载到该内存空间当中。
  • 栈内存(局部变量):方法代码片段执行的时候,会给该方法分配内存空间,在栈内存中压栈。
  • 堆内存:new的对象在堆内存中存储

对于Student s = new Student ();

  • student是一个引用数据类型
  • s是一个变量名
  • new Student()是一个学生对象
  • s是一个局部变量【在栈内存中存储】:引用
  • 什么是对象? new运算符在堆内存中开辟的内存空间称为对象。
  • 什么是引用? 引用是一个变量,只不过这个变量中保存了另一个java对象的内存地址。
  • java语言当中,程序员不能直接操作堆内存,java中没有指针,不像C语言
  • java语言当中,程序员只能通过“引用”去访问堆内存当中对象内部的实例变量。

2、访问实例变量的语法格式:

读取数据:引用.变量名
修改数据:引用.变量名 = 值
int StuNo=s.no;

局部变量在栈内存中存储
成员变量中的实例变量在堆内存的java对象内部存储

实例变量是一个对象一份,100个对象有100份

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Customer
{
int id;
}

public class OOTest02
{
public static void main(String[] args){
Customer c= new Customer();
System.out.println(c.id); //0

c=null;
//以下程序编译可以通过,因为符合语法
//运行出现空指针异常
//空引用访问“实例”相关的数据一定会出现空指针异常
//java.lang.NullPointerException
System.out.println(c.id);
}
}

“实例”相关的数据表示:这个访问的时候必须有对象的参与。这种数据就是实例相关的数据。

重点: 实例变量必须先创建对象,通过引用的方式访问,不能直接使用类名.的方式访问

3、内存分析

1、JVM ( Java虚拟机)主要包括三块内存空间.分别是:栈内存、堆内存、方法区内存。

2、堆内存和方法区内存各有1个。一个线程一个栈内存。

3、方法调用的时候,该方法所需要的内存空间在栈内存中分配,称为压栈。方法执行结束之后,该方法所属的内存空间释放,称为弹栈。

4、栈中主要存储的是方法体当中的局部变量。

5、方法的代码片段以及整个类的代码片段都被存储到方法区内存当中,在类加载的时候这些代码片段会载入。

6、在程序执行过程中使用new运算符创建的java对象.存储在堆内存当中。对象内部有实例变量,所以实例变量存储在堆内存当中。

7、变量分类:

  • 局部变量[方法体中声明]
  • 成员变量[方法体外声明]
    实例变量[前边修饰符没有static]
    静态变量[前边修饰符中有static]

8、静态变量存储在方法区内存当中。[先背会]

9、三块内存当中变化最频繁的是栈内存,最先有数据的是方法区内存,垃圾回收器主要针对的是堆内存。

10、垃圾回收器[自动垃圾回收机制、GC机制]什么时候会考虑将某个java对象的内存回收呢?

  • 当堆内存当中的java对象成为垃圾数据的时候.会被垃圾回收器回收。

11、什么时候堆内存中的java对象会变成垃圾呢?

  • 没有更多的引用指向它的时候。这个对象无法被访问,因为访问对象只能通过引用的方式访问。

3、面向对象的封装性、static

1、封装的好处:

  • 1、封装之后,对于那个事物来说,看不到这个事物比较复杂的一面,只能看到该事物简单的一面。复杂性封装,对外提供简单的操作入口。照相机就是一个很好的案例,照相机的实现原理比较复杂,但是对于使用照相机的人来说,操作起来是非常方便的。还有像电视机也是封装的,电视机内存实现非常复杂,但是对于使用者来说不需要关心内部的实现原理,只需要会操作遥控器即可。
  • 2、封装之后会形成真正的“对象”,真正的“独立体”。
  • 3、封装就意味着以后的程序可以重复使用。并且这个事物应该适应性比较强,在任何场合都可以使用。【可重用性
  • 4、封装之后,对于事物本身,提高了安全性【安全级别高

2、封装的步骤:

1、所有属性私有化,使用private关键字进行修饰,private表示私有的,修饰的所有数据只能在本类中访问

2、对外提供简单的操作入口,也就是说外部程序要想访问age属性,必须通过这些简单的入口进行访问:

  • 对外提供两个公开的方法,分别是set方法和get方法
  • 想修改age属性,调用set方法
  • 想读取age属性,调用get方法

3、set方法的命名规范:public void set + 属性名首字母大写(形参){ };

1
2
3
public  void setAge(int a){ 
age = a;
}

4、get方法的命名规范:public 返回值类型 get + 属性名首字母大写(形参){ };

1
2
3
public int getAge(){
return age;
}

5、需要背会的:

  • setter和getter方法没有static关键字
  • 有static关键字修饰方法怎么调用?类名.方法名(实参);
  • 没有static关键字修饰方法怎么调用?引用.方法名(实参);

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class UserTest
{
public static void main(String[] args){
User user =new User();
//编译报错,age属性私有化,在外部程序中不能直接访问
//从此之后age属性非常的安全,但是有点太安全了。
//对于目前程序来说,age属性彻底在外部访问不到了。
//System.out.println(user.age);

//修改
user.setAge(-100);//对不起,您提供的年龄不合法
//读取
System.out.println(user.getAge());//0
}
}
public class User
{
//属性私有化
private int age;
}

public void setAge(int a){
//编写业务逻辑代码进行安全控制
//age = a;

if(a<0||a>150){
System.out.println("对不起,您提供的年龄不合法");
return;
}
//程序可以进行到这里的话,说明a是合法的,则进行赋值运算
age = a;
}

public int getAge(){
return age;
}

4、java构造方法、构造器、对象与引用的概念、参数传递

1、关于java类中中的构造方法:

1、构造方法又被称为构造函数/构造器/Constructor

2、构造方法语法结构:
[修饰符列表] 构造方法名(形式参数列表){ 构造方法体; }

3、回顾普通方法的语法结构:
[修饰符列表] 返回值类型 方法名(形式参数列表){ 方法体; }

4、对于构造方法来说,“返回值类型”不需要指定,并且也不能写void。只要写上void,那么这个方法就成为普通方法了。

5、对于构造方法来说,构造方法的方法名必须与类名保持一致。

6、构造方法的作用?

  • 构造方法存在的意义是,通过构造方法的调用,可以创建对象。

7、构造方法应该怎么调用?

  • 普通方法是这样调用的:方法修饰符中有static的时候:类名.方法名(实参列表)、方法修饰符列表中没有static的时候:引用.方法名(实参列表)
  • new 构造方法名(实参列表)

8、构造方法调用执行之后,有返回值吗?

  • 每一个构造方法实际上执行结束之后都有返回值,但是这个“return 值;”这样的语句不需要写。构造方法结束的时候java程序自动返回值。
  • 并且返回值类型是构造方法所在类的类型。由于构造方法的返回值类型就是类本身,所以返回值类型不需要编写。

9、注释和取消注释:Ctrl + /、 多行注释:ctrl + shift +/\

10、当一个类中没有定义任何构造方法的话,系统默认给该类提供一个无参数的构造方法,这个构造方法被称为缺省构造器。

11、当一个类显示的将构造方法定义出来了,那么系统则不再默认为这个类提供缺省构造器。建议开发中手动的为当前类提供无参数的构造方法。因为无参数的构造方法太常用了。

2、构造方法的作用

  • 1、创建对象
  • 2、创建对象的同时,初始化实例变量的内存空间。

成员变量之实例变量,属于队象级别的变量,这种变量必须先有对象才能有实例变量。

实例变量没有手动赋值的时候,系统默认赋值,那么这个系统默认赋值是在什么时候完成的呢?是在类加载的时候么?

  • 不是的,因为类加载的时候只加载了代码片段,还没来得及创建对象。所以此时实例变量并没有初始化。
  • 实际上,实例变量的内存空间是在构造方法执行过程当中完成开辟的,完成初始化的。
  • 系统在默认赋值的时候,也是在构造方法执行过程当中完成的赋值。

3、对象与引用的概念

  • 对象:目前在使用new运算符在堆内存中开辟的内存空间称为对象。
  • 引用:是一个变量,不一定是局部变量,还可能是成员变量。引用保存了内存地址,指向了堆内存当中的对象。
  • 所有访问实例相关的数据,都需要通过“引用.”的方式访问,因为只有通过引用才能找到对象。
  • 只有一个空的引用,访问对象的实例相关的数据会出现空指针异常。

4、参数传递

主要研究和学习的是方法在调用的时候,涉及到参数传递的问题,到底是怎样传递数据的呢?

  • 值传递

int i = 10;
int j = i ; i传递给j,实际上只是将i变量中保存的10传递给了j,j实际上是一个全新的内存空间

User u = 0x1234;
User u2 = u;
u传递给u2,实际上是将0x1234这个值赋给u2了,u和u2实际上是两个不同的局部变量,但是他们这两个变量指向堆内存中的同一个java对象。

java语言当中方法调用的时候涉及到的参数传递的问题

参数传递实际上传递的是变量中保存的具体值

例一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test01()
{
public static void main(String[] args){

//int i = 10;
//add(i)等同于:add(10)
int i = 10;
add(i);//add方法调用的时候,给add方法传递了一个变量i,实际上传递的是变量中保存的具体值
System.out.println("main-->"+ i );
}

public static void add(int i){
i++;
System.out.println("add-->"+ i);
}
}

编译结果:
编译结果
例二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test02(){
public static void main(String[] args){
User u = new User(20);

//User u =0x1234;
//add(u)等同于:add(0x1234)
add(u);//传递u给add方法的时候,实际上传递的是U变量中保存的值,只不过这个值是一个java对象的内存地址
System.out.println("main-->"+ u.age);
}

public static void add(User u ){
u.age++;
System.out.println("add-->"+ u.age);
}
}

class User
{
int age;
public User(int i){
age = i;
}
}

编译结果:
在这里插入图片描述
最终结论:

方法调用的时候,涉及到参数问题,传递的时候,java只遵循一种语法机制,就是将变量中保存的“值”传递过去了,只不过有时候这个值是一个字面值10,有的时候是另一个java对象的内存地址0x1234。

5、抽象类

抽象类为什么无法实例化,无法创建对象?
抽象类是:类和类之间有共同特征,将这些具有共同特征的类再进一步形成了抽象类。由于类本身是不存的,所以抽象类无法创建对象

1、什么是抽象类

类和类之间有共同特征,将这些具有共同特征的类再进一步形成了抽象类。由于类本身是不存的,所以抽象类无法创建对象【无法实例化】

2、抽象类属于什么类型?

抽象类也属于引用数据类型

3、抽象类怎么定义?

语法:

1
2
3
4
[修饰符列表] abstract class 类名{
类体;
}
123

4、抽象类是无法实例化的,无法创建对象的,所以抽象类是用来被子类继承的。

5、final与abstract不能联合使用,这两个关键字是对立的

6、抽象类的子类可以是抽象类

7、抽象类虽然无法实例化,但是抽象类有构造方法,这个构造方法是供子类使用的。

8、抽象类关联到一个概念:抽象方法。

抽象方法表示没有实现的方法,没有方法体的方法。

例如:public abstract void doSome(); 没有花括号

抽象方法特点是:

  • 特点1:没有方法体,以分号结尾。
  • 特点2:前面修饰符列表中有abstract关键字

9、抽象类中不一定有抽象方法,抽象方法必须在抽象类中。

重要结论:【必须背会】

一个非抽象的类继承抽象类,必须将抽象类中的抽象方法实现了。
这是java语法上强行规定的,必须的,否则编译器报错。
抽象类则不需要。

这里的覆盖或者重写,也可以叫做实现。(对抽象)

面向抽象编程

面向抽象编程,不要面向具体编程,降低程序的耦合度,提高程序的扩展力
这种编程思想符合OCP原则。

6、接口

1、接口

  • 接口也是一种引用数据类型。
  • 接口是完全抽象的。(抽象类是半抽象。)或者也可以说接口是特殊的抽象类。

1、接口的语法

1
2
3
4
[修饰符列表] interface 接口名 {
}
[修饰符列表] class 类名{
}

2、接口支持多继承,一个接口可以继承多个接口

1
2
3
4
5
6
7
8
9
10
11
interface A{

}

interface B{

}

interface C extends A,B{

}

3、接口中只包含两部分内容

  • 常量(不能修改)
  • 抽象方法

接口没有其它内容,只有以上两部分。

4、接口中的所有元素都是public修饰的

5、接口中的抽象方法定义时:public abstract 修饰符可以省略

6、接口中的方法都是抽象方法,所以接口中的方法不能有结构体和方法体

7、接口中的常量的public 、static、final可以省略。

8、类实现接口实现方法

基础语法: 类和类之间叫做继承,类和接口之间叫做实现。

语法结构:

  • 继承使用extends关键字完成。
  • 实现使用implements关键字完成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface MyMath{
double PI=3.1415;
int sum(int a,int b);
int sub(int a,int b);
}

//正确写法
abstract class MyMathImp1 implements MyMath{
}

错误:MyMathImp1不是抽象的,并且未覆盖MyMath中的抽象方法
/*class MyMathImp1 implements MyMath{
}*/

修正:
class MyMathImp1 implements MyMath{
public int sum(int a,int b){
return a + b;
}

public int sub(int a,int b){
return a - b;
}
}

9、当一个非抽象的类实现接口的话,必须将接口中所有的抽象方法全部实现(重点)

10、面向接口的编程

1
2
3
4
5
//多态:父类型引用指向子类型的对象
//调用接口里面的方法(面向接口的编程)
MyMath mm = new MyMathImp1();
mm.sum(10,20);
1234

11、一个类可以实现多个接口

这种机制弥补了java中的一个缺陷:java中的类和类只支持单继承。实际上单继承是为了简单而出现的,现实世界中存在多继承,java中的接口弥补了单继承带来的缺陷。

12、继承和实现同时存在(extends和implements)

extends 关键字在前 ,implements关键字在后。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test()
{
public static void main(String[] args){
Flyable f = new cat();//多态
f.fly();//输出结果:***

}
class Animal{
}

interface Flyable{
void fly();
}

class Cat extends Animal implements Flyable{
public void fly(){
System.out.println("***")
}
}

13、使用接口写代码时,可以使用多态(父类型引用指向子类型对象)

2、接口在开发中的作用(解耦合)

注意:接口在开发中的作用,类似于多态在开发中的作用。

多态:面向抽象编程,不要面向具体编程。降低程序的耦合度,提高程序的扩展力。

接口是不是完全的?

是的,而我们以后正好要求面向抽象编程。
面向抽象编程这句话以后可以修改为:面向接口编程。有了接口就有了可插拨。可插拔表示扩展力很强。不是焊接死的。

面向接口编程,可以降低程序的耦合度,提高程序的扩展力。符合OCP开发原则。
接口的使用离不开多态机制。(接口+多态才可以达到降低耦合度)

接口可以解耦合,解开的是调用者和实现者的耦合,
任何接口都有调用者和实现者。

  • 调用者面向接口调用。
  • 实现者面向接口编写实现。

7、接口和抽象类的区别

仅语法上的区别

  1. 抽象类是半抽象的
    接口是完全抽象的

  2. 抽象类中有构造方法
    接口中没有构造方法

  3. 接口和接口之间支持多继承
    类和类之间只能单继承

  4. 一个类可以同时实现多个接口
    一个抽象类只能继承一个类(单继承)

  5. 接口中只允许出现常量和抽象方法。

    接口一般都是对“行为”的抽象

8、Java中类和类之间的关系

一、继承关系

继承指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力。 在Java中继承关系通过关键字extends明确标识,在设计时一般没有争议性。在UML类图设计中,继承用一条带空心三角箭头的实线表示,从子类指向父类,或者子接口指向父接口。
img

二、实现关系

实现指的是一个class类实现interface接口(可以是多个)的功能,实现是类与接口之间最常见的关系。 在Java中此类关系通过关键字implements明确标识,在设计时一般没有争议性。在UML类图设计中,实现用一条带空心三角箭头的虚线表示,从类指向实现的接口。
在这里插入图片描述

三、依赖关系

简单的理解,依赖就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是类B的变化会影响到类A。 比如某人要过河,需要借用一条船,此时人与船之间的关系就是依赖。表现在代码层面,为类B作为参数被类A在某个method方法中使用。在UML类图设计中,依赖关系用由类A指向类B的带箭头虚线表示。
在这里插入图片描述

四、关联关系

关联体现的是两个类之间语义级别的一种强依赖关系,比如我和我的朋友,这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的。关联可以是单向、双向的。表现在代码层面,为被关联类B以类的属性形式出现在关联类A中,也可能是关联类A引用了一个类型为被关联类B的全局变量。 在UML类图设计中,关联关系用由关联类A指向被关联类B的带箭头实线表示,在关联的两端可以标注关联双方的角色和多重性标记。
在这里插入图片描述

五、聚合关系

聚合是关联关系的一种特例,它体现的是整体与部分的关系,即has-a的关系。此时整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。 比如计算机与CPU、公司与员工的关系等,比如一个航母编队包括海空母舰、驱护舰艇、舰载飞机及核动力攻击潜艇等。表现在代码层面,和关联关系是一致的,只能从语义级别来区分。在UML类图设计中,聚合关系以空心菱形加实线箭头表示。
在这里插入图片描述

六、组合关系

组合也是关联关系的一种特例,它体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合。它同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束, 比如人和人的大脑。表现在代码层面,和关联关系是一致的,只能从语义级别来区分。在UML类图设计中,组合关系以实心菱形加实线箭头表示。
在这里插入图片描述

9、is-a、is-like-a、has-a

1、has a

关联关系:聚合
凡是能够使用 has a 来描述的,统一以属性的方式存在

例如 Customer has a FoodMenu(顾客有一个食物菜单)

2、is a

继承关系:继承
凡是满足is a的表达式都可以设置为继承

例如 Cat is a Animal(猫是一种动物)

3、is like a

实现关系:接口
满足 is like a 的表达式也是一种继承

实现关系通常是:类实现接口

但是在继承中,仅覆盖了父类方法即为Is-a;若在覆盖父类方法基础上有新增方法,则为Is-like-a。

例子:

1.假设你确定两件对象之间是is-a的关系,那么此时你应该使用继承。比方菱形、圆形和方形都是形状的一种。那么他们都应该从形状类继承。

2.假设你确定两件对象之间是has-a的关系,那么此时你应该使用聚合。比方电脑是由显示器、CPU、硬盘等组成的。那么你应该把显示器、CPU、硬盘这些类聚合成电脑类。

3.假设你确定两件对象之间是like-a的关系,那么此时你应该使用组合。比方空调继承于制冷机,但它同一时候有加热功能。那么你应该把让空调继承制冷机类,并实现加热接口

10、访问控制权限、包和import

1、关于java语言当中的包机制:

  • 1、包又称为package, java中 引入package这种语法机制主要是为了方便程序的管理。
    不同功能的类被分门别类放到不同的软件包当中,查找比较方便,管理比较方便,易维护。
  • 2、怎么定义package呢?
    在java源程序的第一行上编写package语句。
    package只能编写一 个语句.
    语法结构:
    package 包名;
  • 3、包名的命名规范:
    公司域名倒序+项目名+模块名+功能名;
    采用这种方式重名的几率较低。因为公司域名具有全球唯一性。
    例如:
    com.bjpowernode . oa.user . service;
    org . apache. tomcat.core ;
  • 4、包名要求全部小写,包名也是标识符,必须遵守标识符的命名规则
  • 5、一个包对应的是一个目录
  • 6、使用了package机制之后,应该怎样编译?怎么运行呢?
    使用package机制后,类名不在是Test ,而是包名.类名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.bjpowernode;

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

//创建Test对象
//以下代码编译错误:当省略包名之后,会在当前包下找Test
//实际上编译器去找:com.bjpowernode.Test了,这个类不存在。
//Test tt = new Test();
//System.out.println(tt);

//修改以上错误
//包名不要省略
//结论:什么时候前边的包名可以省略呢? Test和Test1在同一个包下的时候不需要加包名。
com.bjpowernode.javase.day.Test tt = new com.bjpowernode.javase.day.Test();

}
}

2、import

1、import 语句用来完成导入其他类,同一个包下的类不需要导入,不在同一个包下需要手动导入。

2、import语法格式:
import 类名;
import 包名.* ;

1
2
3
4
5
6
7
8
9
10
package com.bjpowernode;
import com.bjpowernode.javase.day.Test; //导入包

public class Test2()
{
public static void main(String[] args){
Test x = new Test();
System.out.println(x);//com.bjpowernode.javase.day.Test
}
}

/3、java.lang.*不需要手动引入,系统自动引入
lang:language语言包,是java语言的核心类,不需要手动引入。

1
2
3
4
5
String s ="abc";
System.out.println(s);

//Date d = new Date();//编译错误,因为没有Date类
需要 import java.util.Date;

4、什么时候需要import?

  • 不是java.lang包下,并且不在同一个包下的时候,需要使用import进行引入

3、访问控制权限

访问控制权限修饰符:

1、访问控制权限修饰符来控制元素的访问范围

2、访问控制权限修饰符包括:

  • public: 表示公开的,在任何位置都可以访问
  • protected: 同包,子类
  • 缺省default(系统默认状态): 同包
  • private: 表示私有的,只能在本类中访问

3、访问控制权限修饰符可以修饰类、变量、方法…

4、当某个数据只希望子类使用,使用protected进行修饰

5、修饰符的范围:
private < 缺省 < protected < public

6、类只能采用public和缺省的修饰符进行修饰【内部类除外】

六、关键字

1、this关键字

关于java语言当中的this关键字

  • 1 this是一个关键字,翻译为:这个
  • 2 this是一个引用,this是一个变量,this变量中保存了内存地址指向了自身,this存储在JVM堆内存java对象内部。
  • 3 创建100个java对象,每一个对象都有this,也就是说有100个不同的this
  • 4 this可以出现在“实例方法”中,this指向当前正在执行这个动作的对象。(this代表当前对象)
  • 5 this在大多数情况下都是可以省略不写的
  • 6 this不能使用带有static的方法中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class CustomerTest
{
public static void main(String[] args){
//创建Customer对象
Customer c1 = new Customer();
c1.name = "zhangsan";

//c1购物
c1.shopping();

Customer c2 = new Customer();
c2.name = "lisi";
c2.shopping();

//调用doSome方法(修饰符列表上有static)
//采用"类名."的方式访问,显然这个方法在执行的时候不需要对象的参加
Customer.doSome();

//调用doOther方法
Customer.doOther();
}
}

public class Customer
{
//姓名
String name;

//构造方法
public Customer(){

}

//不带有static关键字的方法
//顾客购物的行为
//每一个顾客购物最终的结果是不一样的
//所以购物这个行为是属于对象级别的行为
//由于每一个对象在执行购物这个行为的时候最终结果不同,所以购物这个动作必须有对象的参与
//重点:没有static关键字的方法被称为“实例方法”
//重点:没有static关键字的变量被称为“实例变量”
//注意:当一个行为/动作执行的过程中是需要对象参与的,那么这个方法一定要定义为“实例方法”,不要带static关键字
public void shopping(){
System.out.println(this.name + "在购物!");//完整写法
System.out.println(name+"在购物!");//this可以省略不写
}

public static void doSome(){
//这个执行过程中没有“当前对象”,因为带有static的方法是通过类名的方式访问的。
//或者说这个"上下文"当中没有"当前对象",自然也不存在this(this代表的是当前正在执行的这个动作的对象)

//以下程序为什么编译错误呢?
//doSome方法调用不是对象去调用,是一个类名去调用,执行过程中没有“当前对象”。
//name是一个"实例变量"。以下代码的含义是:访问当前对象的name。
//System.out.println(name);

//static方法调用不需要对象,直接使用类名,所以执行过程中没有当前对象,所以不能使用this
//System.out.println(this);
}

public static void doOther(){
//假设想访问name这个实例变量的话应该怎么做?
//System.out.println(name); //编译报错

//可以采用以下方案,但是以下方案,绝对不是访问当前对象的name
//创建对象
Customer c = new Customer();
System.out.println(c.name);//这里访问的name是c引用指向对象的name
}
}

编译结果:
在这里插入图片描述

实例方法必须有对象的存在

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
实例方法
public void doOther(){
System.out.println("do other!");
}

run是实例方法,调用run方法的一定是有对象存在的。一定先创造一个对象才能调用run方法

public void run(){
System.out.println("run !");

doOther是一个实例方法,实例方法调用必须有对象的存在
以下代码的含义就是:调用当前对象的doOther方法
doOther();//this大部分情况下可以省略
this.doOther();//完整写法
}

最终结论:

  • 在带有static的方法当中不能直接访问实例变量和实例方法。
  • 因为实例变量和实例方法都需要对象的存在。
  • 而static的方法当中是没有this的,也就是说当前对象不存在。
  • 自然也是无法访问当前对象的实例变量和实例方法。

this.什么时候不能省略?

用来区分局部变量和实例变量的时候,“this.”不能省略。

1
2
3
4
5
6
7
8
public class User(){
private int id; //实例变量

}

public int setId(int id){
this.id = id; // 等号前面的是实例变量,等号后面的是局部变量
}

this可以使用在哪里?

  • 1 实例方法中,代表当前对象【语法格式:this.
  • 2 构造方法中,通过当前的构造方法调用其他的构造方法【语法格式:this(实参);

重点记忆:this()这种语法只能出现在构造函数第一行。

什么时候程序在运行的时候出现空指针异常呢?

  • 空引用访问实例相关的数据,因为实例相关的数据就是对象相关的数据,这些数据在访问的时候必须有对象的参与,当空引用的时候,对象不存在,访问这些实例数据一定会出现空指针异常。

实例相关的数据包括:

  • 实例变量【对象需要存在】
  • 实例方法【对象需要存在】

2、static关键字

  • 1 static:静态的
  • 2 static修饰的方法是静态方法
  • 3 static修饰的变量是静态变量
  • 4 所有static修饰的元素都称为静态的,都可以使用类名.的方式访问,当然也可以采用引用.【但不建议】
  • 5 static修饰的所有元素都是类级别的特征,和具体的对象无关
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Chinese(){
//所有中国人这个类的对象国籍都一样,这种特征属于类级别的特征,
//可以提升为整个模板的特征,可以在变量前添加static关键字修饰
//静态变量,静态变量在类加载的时候初始化,不需要创建对象,内存就开辟了。
//静态变量存储在方法区内存当中。
static String country = "中国";
}

public class ChineseTest
{
public static void main(String[] args){
Chinese zhangsan = new Chinese();
System.out.println(Chinese.country);
}
}

什么时候成员变量声明为实例变量呢 ?

  • 所有对象都有这个属性,但是这个属性的值会随着这个对象的变化而变化【不同对象的这个属性具体的值不同】

什么时候成员变量声明为静态变量呢 ?

  • 所有对象都有这个属性,并且所有对象这个属性的值是一样的,建议定义为静态变量,节省内存的开销。

静态变量在类加载的时候初始化,内存在方法区中开辟。访问的时候不需要创建对象,直接使用类名.静态变量名的方式访问。

所有静态的数据都是可以采用类名.,也可以采用引用.,但是建议采用类名.的方式访问。
采用引用.的方式访问的时候,即使引用的是null,也不会出现空指针异常,因为访问静态的数据不需要对象的存在。

可以使用static关键字来定义“静态代码块”:

1、语法格式:

1
2
3
static{
java语句;
}

2、静态代码块在类加载时执行,并且只执行一次。

3、静态代码块在一个类中可以编写多个,并且遵循自上而下的顺序依次执行。·

4、静态代码块的作用是什么?怎么用?用在哪?什么时候用?

  • 这当然和具体的需求有关,例如项目中要求在类加载的时候执行代码完成日志的记录。那么这段记录日志的代码就可以编写到静态代码当中,完成日志记录
  • 静态代码块是java为程序员准备的一个特殊的时刻,这个特殊的时刻被称为类加载时刻。若希望在此刻执行一段特殊的程序,这段程序可以直接放到静态代码当中。

5、通常在静态代码块当中完成预备工作,先完成数据的准备工作,例如:初始化连接池,解析XML配置文件…

方法什么时候定义为静态的?

方法描述的是动作,当所有的对象执行这个动作的时候,最终产生影响是一样的。那么这个动作已经不再属于某一个对象动作了,可以将这个动作提升为类级别的动作,模板级别的动作。

静态方法中无法直接访问实例变量和实例方法。

大多数方法都定义为实例方法,一般一个行为或者一个动作在发生的时候,都需要对象的参与。但是也有例外,例如:大多数“工具类”中的方法都是静态方法,因为工具就是方便编程,为了方便方法的调用,自然不需要new对象是最好的。

3、final关键字

1、final是一个关键字,表示最终的,不可变的

2、final修饰的类无法被继承

3、final修饰的方法无法被覆盖

4、final修饰的变量一旦被赋值之后,不可重新赋值【不可二次赋值】

5、final修饰的实例变量,必须手动赋值,不能采用系统默认值 final变量必须手动赋值,不能采用系统默认值

1
2
3
4
5
6
7
8
9
10
//final int age; //编译错误
final int age = 10;



final int num;

public FinalTest(){
this.num = 200;
}

6、final修饰的引用,一旦指向某个对象之后,不能在指向其他对象,那么被指向的对象无法被垃圾回收器回收。

final修饰的引用虽然指向某个对象之后不能指向其他对象,但是所指向的对象内部的内存是可以被修改的。

1
2
3
4
5
6
7
8
9
10
11
User u = new User(100);

u = new User(200);

final User user = new User(30);
//user = new User (50); //final修饰的引用,一旦指向某个对象之后,不能在指向其他对象,那么被指向的对象无法被垃圾回收器回收。

System.out.println(user.id); //30
user.id = 50;//final修饰的引用虽然指向某个对象之后不能指向其他对象,但是所指向的对象内部的内存是可以被修改的。

System.out.println(user.id); //50

7、final修饰的实例变量,一般和static联合使用,被称为变量。

final修饰的实例变量是不可变的,这种变量一般和static联合使用,被称为常量

常量定义的语法格式【常量名全部大写,每个单词之间使用下划线连接】
public static final 类型 常量名 = 值

4、super关键字

super的使用

super.属性名:【访问父类的属性】
super.方法名(实参):【访问父类的方法】
super(实参):【调用父类的构造方法】

super应用

1、super是一个关键字,全部小写
2、

  • super能出现在实例方法中。
  • super的语法是:super.super()
  • super不能使用在静态方法中
  • super.大部分情况下是可以省略的
  • super.什么时候不可以省略?
    父类和子类中有同名属性,或者说有同样的方法,想在子类中访问父类的,super.不可以省略。
  • super()只能出现在构造方法的第一行,通过当前的构造方法去调用“父类”中的构造方法,目的是:创建子类对象的时候,先初始化父类型特征。

3、super():
表示通过子类的构造方法调用父类的构造方法。
模拟现实世界中的这种场景:要想有儿子,需要先有父亲

4、当一个构造方法第一行:
既没有this()又没有super()的话,默认会有一个super();表示通过当前子类的构造方法调用父类的无参数构造方法。所以必须保证父类的无参数构造方法是存在的。

5、注意:this()和super()不能共存,他们都是只能出现在构造方法的第一行。

6、无论怎样,父类的构造方法是一定会执行的。

super.什么时候不能省略?

父中有,子中又有,如果想在子中访问“父的特征”,super.不能省略。

  • this.name:当前对象的name属性
  • super.name:当前对象的父类型特征中的name属性

七、继承、多态

1、继承、方法覆盖

1、继承

关于java语言当中的继承:

  • 1 继承是面向的对象三大特征之一,三大特征是封装、继承、多态。
  • 2 继承“基本”的作用是:代码复用。但是继承最“重要”的作用是:有了继承才有了以后的“方法覆盖”和“多态机制”
  • 3 继承语法格式:
1
2
3
[修饰符列表] class 类名 extends 父类名{
类体 = 属性 + 方法
}
  • 4 java语言当中的继承只支持单继承,一个类不同同时继承很多类,只能继承一个类。【在C++中支持多继承】。
  • 5 关于继承中的一些术语:
    B类继承A类,其中:
    A类称为:父类、基类、超类、superclass
    B类称为:子类、派生类、subclass
  • 6 在Java语言当中子类继承父类都继承那些数据?
    私有的不支持继承
    构造方法不支持继承
    其他数据都可以被继承
  • 7 虽然java语言当中只支持单继承,但是一个类也可以间接继承其他类,例如:
1
2
3
4
5
6
7
C extends B{
}
B extends A{
}
A extends T{
}
C类直接继承B类,但是C类间接继承T、A类。
  • 8 java语言中假设一个类没有显示的继承任何类,该类默认继承JavaSE库中提供的java.lang.Object类。java语言中任何一个类中都有Object类的特征。

2、方法覆盖

1 方法覆盖又称方法重写(override[官方]、overwrite)

2 什么时候使用方法覆盖?

当父类中的方法已经无法满足当前子类的业务需求,子类有必要将父类中继承过来的方法进行重新编写,这个编写过程称为方法重写/方法覆盖。

3 什么条件满足之后会发生重写呢?

代码满足什么条件之后,就构成方法的覆盖呢?

  • 方法重写发生在具有继承关系的父子类之间
  • 返回值类型相同,方法名相同,形参列表相同
  • 访问权限不能更低,可以更高。
  • 抛出异常不能更多,可以更少

建议方法重写的时候尽量复制粘贴,容易导致没有产生覆盖。

注意

  • 私有方法不能继承,所以不能覆盖
  • 构造方法不能继承,所以不能覆盖
  • 静态方法不存在覆盖
  • 覆盖只针对方法,不谈属性

2、多态

1、基础语法

向上转型(upcasting)

子类型–>父类型
又被称为:自动类型转换

向下转型(downcasting)

父类型–>子类型
又被称为:强制类型转换。【需要加强制类型转换符】

无论是向上转型还是向下转型,两种类型之间必须要有继承关系。
没有继承关系,程序是无法编译通过的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
使用多态语法机制
1.Animal和Cat之间存在继承关系,Animal是父类,Cat是子类
2.Cat is a Animal
3.new Cat()创建的对象的类型是Cat,a2这个引用的数据类型是Animal,可见它们进行了类型转换
子类型转化为父类型,称为向上转型,或者称为自动类型转换。
4.Java中允许这种语法:父类型引用指向子类型对象。

Aniaml a1 = new Animal();
Aniaml a2 = new Cat();

a1.move(); //动物在移动
a2.move();// 猫在走猫步
//a2.catchMouse(); //编译不通过,Animal类中没有catchMouse方法
//因为编译阶段编译器检查a2的类型是Animal类型,
//从Animal.class字节码文件当中查找catchMouse()方法,
//最终没有找到该方法,导致静态绑定失败,没有绑定成功,也就是说编译失败了。
  1. java程序永远都分为编译阶段和运行阶段
  2. 先分析编译阶段,在分析运行阶段,编译无法通过,根本是无法运行的。
  3. 编译阶段编译器检查a2这个引用的数据类型为Animal,由于Animal.class字节码当中有move()方法,所以编译通过了。这个过程我们称为静态绑定,编译阶段绑定。只有静态绑定成功了才有后续的运行。
  4. 在程序运行阶段,JVM堆内存当中真实创建的对象是Cat对象,那么以下程序在运行阶段一定会调用Cat对象的move()方法,此时发生了程序的动态绑定,运行阶段绑定。
  5. 无论是Cat类有没有重写move的方法,运行阶段一定调用的是Cat对象的move方法,因为底层真实对象就是Cat对象
  6. 父类型引用指向子类型对象这种机制导致程序存在编译阶段绑定和运行阶段绑定两种不同的形态/状态,这种机制可以称为一种多态语法机制。
1
2
3
4
5
6
7
8
9
10
11
12
Animal a3 = new Bird();
/**
* 1.以下程序编译是没有问题的,因为编译器检查到a3的数据类型是Animal
* Animal和Cat之间存在继承关系,并且Anima1是父类型,Cat是子类型,
*父类型转换成子类型叫做向下转型,语法合格。

* 2、程序虽然编译通过了,但是程序在运行阶段会出现异常,因为JVM雄内存
*当中真实存在的对象是Bi rd类型, Bi rd对象无法转换成Cat对象,因为两种
*类型之间不存在任何继承关系,此时出现了著名的异常:java.lang . ClassCastException
类型转换异常,这种异常总是在“向下转型"的时候会发生。
*/
Cat c3 = (Cat)a3;

怎么解决 ClassCastException异常呢?

使用instanceof运算符可以避免出现以上异常。

instanceof运算符怎么使用?

语法格式:
引用 instanceof 数据类型名

以上运算符的执行结果类型是布尔类型,结果可能是true/false

关于运算结果true/false:

假设:(a instanceof Animal)

  • true 表示:a这个引用指向的对象是一个Animal类型
  • false表示:a这个引用指向的对象不是一个Animal类型。

Java规范中要求:在进行强制类型转换之前,建议采用instanceof运算符进行判断,避免ClassCastException异常的发生。 这是一种好习惯。

1
2
3
4
5
6
7
8
9
if (a3 instanceof Cat){ //a3是一个Cat类型的对象
Cat c3 = (Cat)a3;
c3.catchMouse();
}else if(a3 instanceof Bird){//a3是一个Bird类型的对象
Bird b2 = (Bird)a3;
b2.fly();
}

}

2、多态在实际开发中的作用

    1. 降低耦合度【解耦合】,提高程序的扩展力【软件开发的一个很重要的目标】
    1. 能使用多态尽量使用多态
    1. 父类型引用指向子类型对象

八、数组

1、数组Array

1、数组相关基础知识

  • 1、Java语言中的数组是一种引用数据类型。不属于基本数据类型。数组的父类是Object。
  • 2、数组实际上是一个容器,可以同时容纳多个元素。(数组是一个数据的集合)。
    数组:字面意思是“一组数据”。
  • 3、数组当中可以存储基本数据类型的数据,也可以存储“引用数据类型” 的数据。
  • 4、数组因为是引用类型,所以数组对象堆内存当中的。(数组是存储在堆当中的)。
  • 5、数组当中如果存储的是“java对象” 的话,实际上存储的是java对象的“引用(内存地址)”,数组不能直接存储java对象。
  • 6、数组的分类:一维数组、二维数组、三维数组、多维数组…(一维数组较多,二维数组偶尔使用)。
  • 7、数组一旦创建,在java规定,长度不可变。(数组长度不可变)。
  • 8、所有数组对象都有length属性(java自带的),用来获取数组中元素的个数。
  • 9、java中的数组要求数组中的元素类型统一。
  • 10、数组在内存方面存储的时候,数组中的元素内存地址(存储的每一个元素都是有规则的挨着排列的)是连续的。内存地址连续。这是数组存储元素的特点。数组实际上是一种简单的数据结构。
  • 11、所有的数组都是拿“第一个小方框的内存地址”作为整个数组对象的内存地址。(数组中首元素的内存地址作为整个数组对象的内存地址)
  • 12、数组中的每一个元素都是有下标的,下标从0开始,以1递增。最后一个元素的下标:length - 1
    下标非常重要,因为我们对数组中的元素进行“存取”的时候都需要下标;来进行。

2、数组的优缺点

优点:

查询、检索某个下标上的元素时效率极高。可以说查询效率最高的一个数据结构。

为什么检索效率高?

  • 第一:每一个元素的内存地址在空间存储上是连续的。
  • 第二:每一个元素类型相同,所以占用空间大小一样。
  • 第三:如果知道一个元素内存地址,知道每一个元素占用空间大小,又知道下标,所以通过一个数学表达式就可以计算出某个下标上的元素的内存地址。直接通过内存地址定位元素,所以数组的检索效率是最高的。

缺点:

  • 第一:由于为了保证数组中每个元素的内存地址连续,所以在数组上随即删除或者增加元素的时候效率较低,因为随机增删元素会涉及到后面元素统一向前或者向后位移的操作。
  • 第二:数组不能存储大量数据,因为很难在空间上找到一块特别大的连续的内存

注意:对于数组中最后一个元素的增删是没有效率影响的

2、一维数组

1、怎样声明一个一维数组

语法格式:
int[] array;
double[] array;
boolean[] array;
String[] array;
Object[] array;

2、怎样初始化一个一维数组呢?

包括两种方式:静态初始化一维数组、动态初始化一维数组

静态初始化一维数组int[] array = {100,200,300};String[] str = {"as","cs" ,"ds"};
也可以使用 int array[] = {1,2,3},这是C++风格,不建议在java中使用

动态初始化一维数组
int[] array = new int [5]; 这里的5表示数组元素个数,初始化一个5个长度的int类型数组,每个元素默认值为0

String[] names = new String[6]; 初始化6个长度的String类型数组,每个元素默认值null

3、对一维数组中的元素访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ArrayTest01 {
public static void main(String[] args) {
int[] a = {1,2,3,4,5};

//取(读)
System.out.println(a.length);
System.out.println("第一个元素是" + a[0]);
System.out.println("最后一个元素是" + a[4]);
System.out.println("最后一个元素是" + a[a.length - 1]);

//存(改)
a[0] = 0;//把第一个元素修改

a[a.length-1] = 999;//把最后一个元素修改

System.out.println("第一个元素是" + a[0]);
System.out.println("最后一个元素是" + a[4]);
System.out.println("最后一个元素是" + a[a.length - 1]);
}
}

输出结果:
在这里插入图片描述

4、 一维数组的遍历

遍历写法:

提示:下标越界会出现异常:

1
2
3
4
5
6
7
8
9
10
     接上面代码

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

//颠倒遍历
for (int i = a.length - 1;i >= 0; i--){
System.out.println("颠倒顺序输出-->" + a[i]);
}

结果:
在这里插入图片描述

5、静态存储Object类

1
2
3
4
5
6
7
8
9
10
11
12
13
Object o1 = new Object();
Object o2 = new Object();
Object o3 = new Object();

Object[] object= {o1,o2,o3};

/*或
Object[] object= {new Object(),new Object(),new Object()};
*/

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

6、动态初始化一维数组

1
2
3
4
5
6
7
8
9
10
11
12
public class ArrayTest {
public static void main(String[] args) {
//声明定义一个数组,采用动态初始化的方式创建
int[] a = new int[4];//创建长度为4的int数组,数组中每个元素的默认值为0

//遍历数组
for (int i = 0; i < a.length; i++){
System.out.println("数组中的下标为" + i +"的元素是" + a[i]);
}

}
}

输出结果:
在这里插入图片描述

7、什么时候采用静态初始化方法/动态初始化方法?

当你创建数组的时候,确定数组中存储哪些具体的元素的时候,采用静态初始化。
当你创建数组的时候,不确定数组中存储哪些数据,你可以采用动态初始化的方式,预先分配内存空间。

8、方法的参数为数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ArrayTest {
public static void main(String[] args) {
//调用方法时传一个数组
int[] a = {1,2,3,4};
printArray(a);

//不能printArray({1,2,3});没有这个语法
/* 或
printArray(new int[] {1,2,3});
*/
}

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

9、数组中存储引用数据类型

对于数组来说,实际上只能存储java对象的“内存地址”。数组中存储的每个元素都是“引用”

提示:不能存放别的引用数据类型

如果继承该引用数据类型的数据类型可以使用该数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ArrayTest2 {
public static void main(String[] args) {

Animal a1 = new Animal();
Animal a2 = new Animal();
Animal[] animals = {a1,a2};
for (int i = 0; i < animals.length; i++){
animals[i].move();
}
}
}

class Animal{
public void move(){
System.out.println("Animal move");
}
}

运行结果:
在这里插入图片描述

10、输出数组的方法

错误示范System.out.println(array);  //这样输出的是数组的首地址,而不能打印出数组数据

正确写法:
int[] array= {1,2,3,4,5,6};

方式一:for循环

1
2
3
4
for(int i=0;i<array.length;i++)
{
System.out.println(array[i]);
}

输出为:(自动换行格式的)

方式二:for each

1
2
for(int a:array)
System.out.println(a);

输出格式同方式一。

方式三:Arrays类中的toString方法(注意,是Arrays不是Array,Arrays类位于java.util包下)
需要导入import java.util.Arrays

1
2
int[] array= {1,2,3,4,5,6};
System.out.println(Arrays.toString(array));

11、数组扩容

java中对数组的扩容是:

先新建一个大容量的数组,然后将小容量数组中的数据一个一个拷贝到大数据当中。

结论:数组扩容效率较低。因为涉及到拷贝的问题。所以在以后的开发中请注意:尽可能少的进行数组的拷贝。可以在创建数组对象的时候预估计以下多长合适,最好预估准确,这样可以减少数组的扩容次数,提高效率。

第一种方法,建立一个新的数组,通过for循环来进行拷贝扩容

1
2
3
4
5
6
int[] b=new int[a.length*2];//a.length 长度;a数组的长度。即数组中数据的个数
for(int i=0;i<a.length;i++){
b[i]=a[i];
}
System.out.println(Arrays.toString(b));//这个函数就是将数组b进行遍历输出
//如果不明白遍历什么意思,建议先学习遍历后再来看此篇文章

第二种方法:固定的写法。System.Arrays.copy(a,0,b,0,a.length);

1
2
3
4
5
6
7
int[] c=new int[20];
System.arraycopy(a,0,c,0,a.length);
//a,需要复制的内容。第一个0(零):在a中开始复制的内容的位置
//c,要复制的载体,在这里写c就是将a中需要复制的内容赋值给c
//第二个0(零):在c中开始复制的位置
//a.length:要复制的元素量
System.out.println(Arrays.toString©);

方法三:利用函数方法直接扩容

1
2
3
4
5
6
7
8
//原理,利用Arrays中的函数进行扩容

int[] d=Arrays.copyOf(a,22);//此函数的作用就是复制a的值。定义d的长度

// Arrays.copyOf(a,22); a,需要复制的内容(a数组),22:定义d数组的长度
System.out.println(Arryas.toString(d));
}
}

12、数组拷贝

数组拷贝的方法System.arraycopy( Object src,int srcPos,Object dest, int destPos,int length)

Object src:源数组
int srcPos:源数组起点下标
Object dest:目标数组
int destPos:目标数组起点下标
int length:拷贝的源数组的长度

数组中存储的元素是引用也可以拷贝。且拷贝的是对象的内存地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ArrayTest3 {
public static void main(String[] args) {
int[] src = {1,11,22,33,44};//拷贝源

int[] dest = new int[20];//拷贝目标

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

System.out.println();

System.arraycopy(src,1,dest,2,2);//进行拷贝

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

运行结果:
在这里插入图片描述

3、 排序

1、冒泡排序算法

思想:

1、比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2、对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3、针对所有的元素重复以上的步骤,除了最后一个。
4、持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

举例:
(1)要排序数组:[10,1,35,61,89,36,55]

(2)第一趟排序:

第一次排序:10和1比较,10大于1,交换位置      [1,10,35,61,89,36,55]

第二趟排序:10和35比较,10小于35,不交换位置  [1,10,35,61,89,36,55]

第三趟排序:35和61比较,35小于61,不交换位置  [1,10,35,61,89,36,55]

第四趟排序:61和89比较,61小于89,不交换位置  [1,10,35,61,89,36,55]

第五趟排序:89和36比较,89大于36,交换位置   [1,10,35,61,36,89,55]

第六趟排序:89和55比较,89大于55,交换位置   [1,10,35,61,36,55,89]

第一趟总共进行了六次比较,排序结果:[1,10,35,61,36,55,89]

(3)第二趟排序:

第一次排序:1和10比较,1小于10,不交换位置  [1,10,35,61,36,55,89]

第二次排序:10和35比较,10小于35,不交换位置 [1,10,35,61,36,55,89]

第三次排序:35和61比较,35小于61,不交换位置 [1,10,35,61,36,55,89]

第四次排序:61和36比较,61大于36,交换位置   [1,10,35,36,61,55,89]

第五次排序:61和55比较,61大于55,交换位置   [1,10,35,36,55,61,89]

第二趟总共进行了5次比较,排序结果:[1,10,35,36,55,61,89]

(4)第三趟排序:

1和10比较,1小于10,不交换位置  [1,10,35,36,55,61,89]

第二次排序:10和35比较,10小于35,不交换位置 [ 1,10,35,36,55,61,89]

第三次排序:35和36比较,35小于36,不交换位置 [1,10,35,36,55,61,89]

第四次排序:36和61比较,36小于61,不交换位置   [1,10,35,36,55,61,89]

第三趟总共进行了4次比较,排序结果:[1,10,35,36,55,61,89]

到目前位置已经为有序的情形了。
在这里插入图片描述

平均时间复杂度:O(n²)

最好情况:O(n)
最坏情况:O(n²)

空间复杂度:O(1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class BubbleSort {
public static void main(String[] args) {
int temp ; //临时变量,存储数据
int[] a = {2,4,1,7,9,6,3};
for (int i = a.length-1 ; i > 0 ; i-- ){
for (int j = 0 ; j < a.length-1 ; j++){
if (a[j] > a[j+1]){
temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}

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

2、选择排序

思想:
简单选择排序采用最简单的选择方式,从头至尾顺序扫描序列找出最小的一个关键字,和第一个关键字交换,接着从剩下的关键字中继续这种选择和交换,最终使序列有序。

图解:
图片来源于百度
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SelectSort {
public static void main(String[] args) {
int[] a = {2, 4, 6, 3, 1, 5};
for (int i = 0; i < a.length - 1; i++) {
int min = i;
for (int j = i + 1; j < a.length; j++) {
if (a[min] > a[j]) {
min = j;
}
}
int temp;
temp = a[i];
a[i] = a[min];
a[min] = temp;
}


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

4、查找

1、二分查找法

思想:

假设数据是按升序排序的,对于给定值x,从序列的中间位置开始比较,如果当前位置值等于x,则查找成功;若x小于当前位置值,则在数列的前半段中查找;若x大于当前位置值则在数列的后半段中继续查找,直到找到为止。

示例:
假如有一组数为3,12,24,36,55,68,75,88要查给定的值24.可设三个变量front,mid,end分别指向数据的上界,中间和下界,mid=(front+end)/2.

1、开始令front=0(指向3),end=7(指向88),则mid=3(指向36)。因为mid>x,故应在前半段中查找。

2、令新的end=mid-1=2,而front=0不变,则新的mid=1。此时x>mid,故确定应在后半段中查找。

3、令新的front=mid+1=2,而end=2不变,则新的mid=2,此时a[mid]=x,查找成功。

如果要查找的数不是数列中的数,例如x=25,当第三次判断时,x>a[mid],按以上规律,令front=mid+1,即front=3,出现front>end的情况,表示查找不成功。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static int binarySearch(Integer[] srcArray, int des) {
//定义初始最小、最大索引
int start = 0;
int end = srcArray.length - 1;
//确保不会出现重复查找,越界
while (start <= end) {
//计算出中间索引值
int mid = (end + start)/2 ;
if (des == srcArray[mid]) {
return mid;
//判断下限
} else if (des < srcArray[mid]) {
end = mid - 1;
//判断上限
} else {
start = mid + 1;
}
}
//若没有,则返回-1
return -1;
}

5、一维数组的应用

一维数组模拟栈数据结构

要求:

  • 1、这个栈可以存储java中任何引用数据类型。
  • 2、在栈中提供push方法模拟压栈。(栈满会有提示信息)
  • 3、在栈中提供pop方法模拟弹栈。(栈空了也要有提示信息)
  • 4、编写测试程序,new栈对象,调用push、pop方法来模拟压栈弹栈动作。

MyStack类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class MyStack {

/**属性私有化需要get和set方法*/
private Object[] elements;

/**栈帧,永远指向栈顶元素。
栈顶初始默认值应该是-1,因为刚开始栈是空的没有元素。*/
private int index ;


public MyStack(){
//一维数组动态初始化
//默认初始化容量为10
this.elements = new Object[10];
//给index初始化
this.index = -1;
}

/**
* 压栈的方法
* @param obj 被压入元素
*/
public void push(Object obj){
if (this.index >= this.elements.length - 1){
System.out.println("栈已满,压栈失败!");
return;
}
//程序能走到这里,说明栈没满
//向栈中加1个元素,栈帧向上移动一个位置。
this.index++;
this.elements[index] = obj;
//所有System.out.println()方法执行时。如果输出引用的话,自动调用引用的toString()方法
System.out.println("压栈" + obj + "元素成功,栈帧指向" + index);
}

/**
* 弹栈的方法,从数组中取出一个元素
* @return
*/
public void pop(){
if (index < 0 ){
System.out.println("弹栈失败,栈已空");
return;
}
System.out.print("弹栈" + elements[index] + "元素成功,");
index--;
System.out.println("栈帧指向" + index);
}

/**set 和 get也许用不上,但是必须写上,这是规则
封装:第一步:属性私有化,第二步:对外提供set和get方法。*/
public Object[] getElements() {
return elements;
}

public void setElements(Object[] elements) {
this.elements = elements;
}
}

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960

StackTest类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SatckTest {
public static void main(String[] args) {

//创建一个栈对象,初始化容量是10个
MyStack stack = new MyStack();
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());

//压这个元素失败了
stack.push(new Object());

stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
}
}
1234567891011121314151617181920212223242526272829303132

输出结果:
在这里插入图片描述

6、二维数组的应用

为某个酒店编写程序:酒店管理系统,模拟订房、退房、打印所有房间状态等功能

要求:

  • 1、该系统的用户是:酒店前台。
  • 2、酒店中所有房间使用一个二维数组来模拟:Room[][] rooms;
  • 3、酒店中的每一个房间应该是一个java对象:Room。
  • 4、每一个房间Room应该有:房间编号、房间类型属性、房间是否空闲
  • 5、系统应该对外提供哪些功能:
    可以预定房间:用户输入房间编号,订房。
    可以退房:用户输入房间编号,退房。
    可以查看所有房间的状态:用户输入某个指令应该可以查看所有房间状态。

HotelSystem: 酒店前台系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.company;

import java.util.Scanner;

public class HotelSystem {

public static void main(String[] args) {
//创建酒店对象
Hotel hotel = new Hotel();

/*
首先输出一个欢迎界面
*/
System.out.println("************************");
System.out.println(" 欢迎使用本酒店管理系统 ");
System.out.println(" 请输入对于的功能编号: ");
System.out.println(" 1 查看房间列表 ");
System.out.println(" 2 订房 ");
System.out.println(" 3 退房 ");
System.out.println(" 0 退出系统 ");
System.out.println("************************");
Scanner s = new Scanner(System.in);
boolean flag = true;
//循环一直可以使用
while (flag){
System.out.print("请输入功能编号:");
int i = s.nextInt();
switch (i){
case 0:
System.out.println("已退出系统,欢迎下次光临!");
flag = false;
break;
case 1:
hotel.print();
break;

case 2:
System.out.print("请输入订房编号");
Scanner s1 = new Scanner(System.in);
int i1 = s1.nextInt();
hotel.order(i1);
break;
case 3:
System.out.print("请输入退房编号");
Scanner s2 = new Scanner(System.in);
int i2 = s2.nextInt();
hotel.exit(i2);
break;

default:
System.out.println("输入功能编号有误,请重新输入");

}
}
}
}

Hotel类: 酒店对象,包含二维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.company;

/*
酒店对象,酒店中有二维数组,二维数组模拟酒店房间
*/

public class Hotel {
/**
* 二维数组,模拟大厦所有房间
*/
private Room[][] rooms;

/**
* 盖楼通过构造方法盖楼
*/
public Hotel() {
//一共有几层,每层的房间类型是什么,每个房间的编号是什么
//一层为单人间、二层为标准间、三层为总统套房
/*房间编号的规律
* 1楼:101 102 103...
* 2楼:201 202 203...
* 3楼:301 302 302...*/

rooms = new Room[3][10];

for (int i = 0; i < rooms.length ; i++){
for (int j =0 ; j < rooms[i].length; j++ ){
if (i==0){
rooms[i][j] = new Room((i+1)*100+j+1,"单人间",true);
}else if (i == 1){
rooms[i][j] = new Room((i+1)*100+j+1,"标准间",true);
}else{
rooms[i][j] = new Room((i+1)*100+j+1,"总统套房",true);
}
}
}

}

public void print(){
for (int i = 0; i < rooms.length ; i++) {
for (int j = 0; j < rooms[i].length; j++) {
Room room = rooms[i][j];
System.out.print(room.toString() + " | ");
}
System.out.println();
}
}

/**
* 订房方法
* @param roomNo 调用此方法是需要传递一个房间的编号过来。这个房间编号是前台输入过来的。
*/
public void order(int roomNo){
//订房最主要的是将房间对象的status改为false。
//Room对象的status修改为false
Room room = rooms[(roomNo / 100) - 1][(roomNo % 100) - 1];
//修改为占用
room.setStatus(false);
}

public void exit(int roomNo){
Room room = rooms[(roomNo / 100) - 1][(roomNo % 100) - 1];
room.setStatus(true);
System.out.println(roomNo + "已退房!");
}

}

Room类: 房间信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.company;

import java.util.Objects;

public class Room {
/**
* 房间编号
*/
private int no;

/**
* 房间类型:标准间 单人间 总统套房
*/
private String type;
/**
* 房间状态:
* true:表示空闲,可以预定。
* false:表示占用,不能预定
*/
private boolean status;

public Room() {
}

public Room(int no, String type, boolean status) {
this.no = no;
this.type = type;
this.status = status;
}

public int getNo() {
return no;
}

public void setNo(int no) {
this.no = no;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public boolean isStatus() {
return status;
}

public void setStatus(boolean status) {
this.status = status;
}

/**
* equals方法重写
* @param obj
* @return 房间号是否相等
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !(obj instanceof Room)) {
return false;
}
Room room = (Room) obj;

return this.getNo() == room.getNo();
}

@Override
public int hashCode() {
return Objects.hash(no, type, status);
}

/**
* toString方法重写
* @return 输出房间信息
*/
@Override
public String toString() {
return "房间号为" + no +
", 类型为'" + type + '\'' +
", 状态为" + (status?"空闲":"占用") ;
}
}

运行截图:
在这里插入图片描述

九、异常处理

1、异常的基本概念

1、什么是异常, java提供异常处理机制有什么用?

异常就是程序执行过程中的不正确情况。

以下程序执行过程中发生了不正常的情况,而这种不正常的情况叫做:异常
java语言是很完善的语言,提供了异常的处理方式,以下程序执行过程中出现了不正常情况,
java把该异常信息打印输出到控制台,供程序员参考。程序员看到异常信息之后,可以对
程序进行修改,让程序更加的健壮。

1
2
3
4
5
6
7
8
9
public class ExceptionTest01 {
public static void main(String[] args) {

int a = 10;
int b = 0;
int c=a/b;
System. out.println(a + "/”+ b + "="+ c);
}
}

在这里插入图片描述
观察到异常信息之后,对程序进行修改,更加健壮

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ExceptionText {
public static void main(String[] args) {

int a=10;
int b=0;
if(b == 0){
System.out.println("除数不能为0");
return;
}
int c= a/b;
System. out.println(a + "/"+ b + "=" + c);
}
}

2、上述程序出现了异常信息:

1
2
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ExceptionText.main(ExceptionText.java:6)

这个信息被我们称为:异常信息。这个信息是JVM打印的

2、java中异常以类和对象形式存在

1、异常在Java中以类的形式存在,每一个异常类都可以创建异常对象

1
2
3
4
5
6
7
8
9
10
11
12
public class ExceptionText {
public static void main(String[] args) {

//java.lang.NumberFormatException: 数字格式化异常
NumberFormatException nfe = new NumberFormatException("数字格式化异常");
System.out.println(nfe);

NullPointerException npe = new NullPointerException("空指针异常发生了");
// java.lang.NullPointerException:空指针异常发生了
System.out.println(npe);
}
}

在这里插入图片描述

2、异常对应的现实是怎样的?

火灾(异常类)
2008年8月8日,小明家着火了(异常对象)
2008年8月9日,小刚家着火了(异常对象)
2008年9月8日,小红家着火了(异常对象)

类是模板,对象是实际存在的个体

2、异常的继承结构(UML图)

1、Object下有Throwable(可抛出的)

不管是错误还是异常都是可以抛出的。

2、Throwable下有两个分支:Error(不可处理的,直接退出JVM)和Exception(可处理的)

所有的错误只要发生,Java程序只有一个结果,那就是终止程序的执行。退出JVM,错误是不能处理的。

3、Exception下有两个分支

  • Exception的直接子类(Exceptionsubclass):编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常)
  • RuntimeException: 运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管)
    在这里插入图片描述

3、编译时异常和运行时异常区别

1、编译时异常因为什么而得名

编译时异常和运行时异常,都发生在运行阶段。编译阶段异常是不会发生的。编译时异常因为什么而得名?

因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,因此得名。
所有异常都是运行阶段发生的。因为只有程序运行阶段才可以new对象。
因为异常的发生都是new异常对象。

2、编译时异常和运行时异常区别

  • 编译时异常一般发生的概率比较高。
    对于一些发生概率较高的异常,需要在运行之前对其进行预处理。
  • 运行时异常一般发生的概率比较低。

假设java中没有对异常进行划分,没有分为:编译时异常和运行时异常,所有的异常都需要在编写程序阶段对其进行预处理,将会是怎样的效果呢?

如果代码是这样的话,程序肯定是绝对安全的。但是程序员编写程序太累,代码到处都是处理异常的代码

3、编译时异常还有其他名字

  • 受控异常 (CheckException)
  • 受检异常

4、运行时异常还有其他名字

  • 未受检异常(UnCheckException)
  • 非受控异常

5、所有异常都发生在运行阶段的

4、Java两种异常处理的方式及原理

1、异常上抛

在方法声明的位置上,使用throws关键字,抛给上一级

谁调用我,我就抛给谁,抛给上一级。

2、异常捕捉

使用try..catch语句进行异常的捕捉

这件事发生了,谁也不知,因为我给抓住了。

举例:

我是某集团的一个销售员,因为我的失误,导致公司损失了1000块钱。”损失1000块”这可以看做一个异常发生了。我有两种处理方式。

  • 第一种方式:我把这件事告诉我的领导。【异常上抛
  • 第二种方式:我把自己的掏腰包把这个钱补上。【异常的捕捉

同样的异常发生之后,如果选择上抛,抛给了调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式

注意:

Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常的发生,只有只有一个结果。终止java程序的执行。

1、运行时异常编写程序时可以不处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ExceptionTest01 {
public static void main(String[] args) {
int a = 10;
int b = 0;
int c=a/b;
/*
程序执行到此处发生了ArithmeticException异常
底层new一个ArithmeticException异常对象
然后抛出了,由于是main方法调用了c=a/b
所以这个异常ArithmeticException抛给main方法
main方法没有处理,将这个异常自动抛给JVM
JVM最终终止程序的执行

ArithmeticException 继承 RuntimeException,属于运行时异常。
在编写程序阶段不需要对这种异常进行预先的处理。

*/
System. out.println(a + "/”+ b + "="+ c);

//这里的HelloWorld没有输出,没有执行。
System.out.println("Hello World!");//
}
}

2、方法声明上使用throws

以下代码报错的原因是什么?

因为doSome()方法声明位置上使用了:throws ClassNotFoundException
而ClassNotFoundException是编译时异常。必须编写代码时处理,没有处理编译器报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ExceptionText1 {
public static void main(String[] args) {
//main方法中调用doSome()方法
//因为doSome()方法声明位置上有:throws ClassNotFoundException
//我们在调用doSome()方法的时候必须对这种异常进行预先的处理。
//如果不处理,编译器就报错
//报错信息: 未报告的异常错误java.lang.ClassNotFoundException
doSome();
}

/**
* doSome方法在方法声明的位置上使用了:throws ClassNotFoundException
* 这个代码表示doSome()方法在执行过程中,有可能会出现ClassNotFoundException异常。
* 叫做类没找到异常。这个异常直接父类是: Exception,所以ClassNotFoundException属于编译异常。
* @throws ClassNotFoundException
*/
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!");
}
}

在这里插入图片描述

3、异常处理的具体方式

第一种处理方式:在方法声明的位置上继续使用:throws,来完成异常的继续上抛。抛给调用者
上抛类似于推卸责任。(继续把异常传递给调用者)
这种处理异常的态度:上报

1
2
3
4
5
6
7
8
9
public class ExceptionText1 {
public static void main(String[] args) throws ClassNotFoundException {
doSome();
}

public static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!");
}
}

第二种方式:try ... catch进行异常捕捉
捕捉等于把异常拦下了,异常真正的解决了。(调用者不知道的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ExceptionText1 {
public static void main(String[] args) throws ClassNotFoundException {
try {
//尝试
doSome();
}catch (ClassNotFoundException e){
//catch是捕捉异常之后走的分支
e.printStackTrace;
}
}

public static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!");
}
}

一般不建议在main方法上使用throws,因为这个异常如果真正发生了,一定会抛给JVM。JVM只有终止。一般采用try...catch进行捕捉。

4、异常捕捉和上报的联合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.io.FileInputStream;

public class ExceptionText2 {
public static void main(String[] args) {
System.out.println("main begin ");
m1();
System.out.println("main over");
}

private static void m1(){
System.out.println("m1 begin");
m2();
System.out.println("m1 over");
}

private static void m2(){
System.out.println("m2 begin");
m3();
System.out.println("m3 over");
}
private static void m3(){
//调用SUN jdk中某个类的构造方法
//这个类是IO流的
//创建一个输入流对象,该流指向一个文件。
new FileInputStream("C:\\Users\\Administrator\\Desktop\\Java自学\\学习.txt");
}
}

在这里插入图片描述
编译报错的原因:

  • 第一:这里调用了一个构造方法:FileInputStream(String name)
  • 第二:这个构造方法的声明位置上有:throws FileNotFoundException
  • 第三:通过类的继承结构看到:FileNotFoundException父类是IOException,IOException的父类是Exception。

最终得知,FileNotFoundException是编译时异常。编译时异常要求程序员编写阶段必须对它进行处理,不处理编译器就报错

进行异常处理后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class ExceptionText2 {
public static void main(String[] args) {
System.out.println("main begin ");
m1();
System.out.println("main over");
}

private static void m1(){
System.out.println("m1 begin");
try {
m2();
} catch (FileNotFoundException e) {
//这个分支中可以使用e引用,e引用保存的内存地址是new出来异常对象的内存地址
//catch是捕捉异常之后走的分支
System.out.println("文件不存在,可能路径错误,也可能文件被删除了!");
}
System.out.println("m1 over");
}

//也可以 throws Exception
private static void m2() throws FileNotFoundException {
System.out.println("m2 begin");
//编译器报错的原因是:m3()方法声明位置上有:throws FileNotFoundException
//我们这里调用m3()没有对异常进行预处理,所以编译报错
m3();
System.out.println("m3 over");
}
private static void m3() throws FileNotFoundException {
//调用SUN jdk中某个类的构造方法
//这个类是IO流的
//创建一个输入流对象,该流指向一个文件。
new FileInputStream("C:\\Users\\Administrator\\Desktop\\Java自学\\学习.txt");
}
}

上抛异常也可以 throws Exception

注意:只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。
另外需要注意,try语句块中的某一行出现异常,该行后面的代码不会执行。

5、上报和捕捉怎么选择?

  • 如果希望调用者来处理,选择throws上报。
  • 其他情况可以使用捕捉的方式

5、try…catch语句

1、catch后面的小括号的类型可以是具体的异常类型,也可以是该异常类的父类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class ExceptionText2 {
public static void main(String[] args) {
try{
FileInputStream fis = new FileInputStream("C:\\Users\\Administrator\\Desktop\\Java自学\\学习.txt");
System.out.println("以上出现异常,这里的代码无法执行!");
}catch(FileNotFoundException e){
System.out.println("文件不存在!");
}

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

}

在这里插入图片描述
也可以写为多态:

1
2
3
4
5
6
try{
//创建输入流
FileInputStream fis = new FileInputStream("C:\\Users\\Administrator\\Desktop\\Java自学\\学习.txt");
}catch(IOException e){ //多态:IOException e = new FileNotFoundException();
System.out.println("文件不存在!");
}

1
2
3
4
5
try{
FileInputStream fis = new FileInputStream("C:\\Users\\Administrator\\Desktop\\Java自学\\学习.txt");
}catch(Exception e){ //多态:Exception e = new FileNotFoundException();
System.out.println("文件不存在!");
}

2、catch可以写多个

建议catch的时候,精确的一个一个处理。这样有利于程序的调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionText2 {
public static void main(String[] args) {
try{
//创建输入流
FileInputStream fis = new FileInputStream("C:\\Users\\Administrator\\Desktop\\Java自学\\学习.txt");
//读文件
fis.read();
}catch(FileNotFoundException e){ //所有异常都走这个分支
System.out.println("文件不存在!");
}catch(IOException e){
System.out.println("读文件报错了!");
}
}
}

3、catch后面异常中可以加 | 号(JDK8新特性,7不支持)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionText2 {
public static void main(String[] args) {
try{
//创建输入流
FileInputStream fis = new FileInputStream("C:\\Users\\Administrator\\Desktop\\Java自学\\学习.txt");

System.out.println(100/0);//这个异常是运行时异常,编写程序时可以处理,也可以不处理

}catch(FileNotFoundException | ArithmeticException | NullPointerException e){ //所有异常都走这个分支
System.out.println("文件不存在?数字异常?空指针异常?都有可能!");
}
}
}

4、catch写多个的时候,从上到下,必须遵守从小到大

6、获取异常对象的方法:getMessage和printStackTrace()

1、getMessage

获取异常简单的描述信息。

语法格式:String msg = exception.getMessage();

2、printStackTrace()

打印异常追踪的堆栈信息,比较适合于程序的调试阶段

语法格式:exception.printStackTrace();

3、两个方法的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ExceptionText3 {
public static void main(String[] args) {
//这里只是为了测试getMessage()方法和printStackTrace()方法
//这里只是new了异常对象,但是没有将异常对象抛出。JVM会认为这是一个普通的java对象。
NullPointerException e = new NullPointerException("空指针异常Exception ");

//获取异常简单描述信息:这个信息实际上就是构造方法上面String参数
String msg = e.getMessage();//空指针异常Exception
System.out.println(msg);

//打印异常堆栈信息
//java后台打印异常堆栈追踪信息的时候,采用了异步线程的方式打印的。
e.printStackTrace();

System.out.println("Hello World!");
}
}

在这里插入图片描述

4、查看异常的追踪信息

我们应该怎么看,可以快速的调试程序呢?

异常信息追踪信息,从上往下一行一行看。
但是需要注意的是:SUN写的代码就不用看了。主要问题是出现在自己编写的代码上。

即从运行结果中的at ExceptionText3.m3(ExceptionText3.java:27)这一行看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class ExceptionText3 {
public static void main(String[] args) {
try {
m1();
} catch (FileNotFoundException e) {
//打印异常堆栈追踪信息
//在实际开发中建议使用这个。养成好习惯
e.printStackTrace();
}

//这里程序不耽误执行,很健壮
System.out.println("Hello World!");
}

private static void m1() throws FileNotFoundException {
m2();
}

private static void m2() throws FileNotFoundException {
m3();
}

private static void m3() throws FileNotFoundException {
new FileInputStream("C:\\Users\\Administrator\\Desktop\\学习a.txt");
}
}

在这里插入图片描述
因为27行出现了问题导致23行
23行出问题导致19行
19行出问题导致7行
应该先查看27行的的代码。27行是错误的根源,从根上看问题

防止误导再加一条!!!!:

尽量不要使用e.printStackTrace(),而是使用log打印。

反例:

1
2
3
4
5
try{
// do what you want
}catch(Exception e){
e.printStackTrace();
}

正例:

1
2
3
4
5
try{
// do what you want
}catch(Exception e){
log.info("你的程序有异常啦,{}",e);
}

理由:

  • printStackTrace()打印出的堆栈日志跟业务代码日志是交错混合在一起的,通常排查异常日志不太方便。

  • e.printStackTrace()语句产生的字符串记录的是堆栈信息,如果信息太长太多,字符串常量池所在的内存块没有空间了,即内存满了,那么,用户的请求就卡住啦~

7、finally关键字

关于try…catch中的finally子句

1、在finally子句中的代码是最后执行的,并且是一定会执行的,即使try语句块中的代码出现了异常。

  • finally子句必须和try一起出现,不能单独编写。

2、finally语句通常使用在哪些情况下呢?

通常在finally语句块中完成资源的释放/关闭
因为finally中的代码比较有保障
即使try语句块中的代码出现异常,finally中的代码也会正常执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionText3 {
public static void main(String[] args) {
FileInputStream fis = null;//声明位置放到try外面,这样在finally中才能用
try{
//创建输入流对象
fis = new FileInputStream("C:\\Users\\Administrator\\Desktop\\学习.txt");
//开始读文件...

String s = null;
//这里一定会出现空指针异常!
s.toString();
System.out.println("hello world!");

//流使用完需要关闭,因为流是占用资源的
//即使以上程序出现异常,流也必须要关闭
//放在这里有可能流关不了
//fis.close();
}catch(FileNotFoundException e){
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}catch(NullPointerException e){
e.printStackTrace();
}finally {
System.out.println("hello 浩克");
//流的关闭放在这里比较保险
//finally中的代码是一定会执行的
//即使try中出现了异常!
if(fis != null) {//避免空指针异常
try {
//close方法有异常采用捕捉的方式
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

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

fis.close未被注释前:
在这里插入图片描述
修改后:
在这里插入图片描述

3、try和finally,也可以没有catch

  • try不能单独使用。
  • try 可以和finally联合使用。

以下代码的执行顺序:

先执行try…
再执行finally
最后执行return(return语句只要执行方法必然结束)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ExceptionText4 {
public static void main(String[] args) {
try {
System.out.println("try...");
return;
}finally {
//finally中的语句会执行。能执行到。
System.out.println("finally...");
}

//这里不能写语句,因为这个代码是无法执行到的。
//System.out.println("Helloword");
}
}

运行结果:
在这里插入图片描述

4、退出JVM,finally语句不执行

1
2
3
4
5
6
7
8
9
10
11
public class ExceptionText5 {
public static void main(String[] args) {
try {
System.out.println("try..");
//退出JVM
System.exit(0);//退出JVM之后,finally语句中的代码就不执行了!
}finally {
System.out.println("finally..");
}
}
}

运行结果:
在这里插入图片描述

5、关于finally的一道面试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ExceptionText5 {
public static void main(String[] args) {
int result = m();
System.out.println(result);//结果为100
}

public static int m(){
int i = 100;
try {
//这行代码出现在int i = 100的下面,所以最终结果必须是返回100
//return语句还必须保证是最后执行的。一但执行,整个方法结束。
return i;
}finally {
i++;
}
}
}

最后的结果为 100
在这里插入图片描述
解释: Java语法中有这样一条规则:

  • 方法体中的代码必须遵循自上而下顺序依次逐行执行(亘古不变的语法!)

Java中还有一条语法规则:

  • return语句一但执行,整个方法必须结束(亘古不变的语法!)

反编译之后的结果:

1
2
3
4
5
6
7
8
9
10
public static int m(){
int i = 100;
int j = i;
i++;
return j;
Exception exception;
exception;
i++;
throws exception;
}

8、final、finalize和finally的区别

1、final

final是一个关键字。表示最终的,不可变的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
   //final修饰的变量无法重新赋值。

final int i = 100;
//i = 200; 不能变

//final修饰的类无法继承

final class A {
//常量
public static final double MATH_PI = 3.1415926;
}

//final修饰的方法无法覆盖

class B {
public final void doSome(){

}
}

2、finalize

finalize()方法JVM的GC垃圾回收器负责调用

当一个java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用finalize()方法。如果希望在对象销毁时机执行一段代码的话,这段代码要写到finalize()方法中(留遗嘱)

1
2
3
4
5
  - finalize()是Object类中的一个方法。作为方法名出现
- 所以finalize是一个标识符。

Object obj;

3、finally

finally是一个关键字,和try联合使用,使用在异常处理机制中。

1
2
3
4
5
6
7
   - 在finally语句块中的代码是一定会执行的。

try {

}catch{
System.out.println("finally...")
}

9、如何自定义异常及手动抛出异常

1、自定义异常的方法

需要两步:

  • 第一步:编写一个类继承Exception或者RunTimeException
  • 第二步:提供两个构造方法,一个无参数的,一个带有String参数的。

MyException类

1
2
3
4
5
6
7
8
public  class MyException extends Exception{//编译时异常
public MyException() {
}

public MyException(String message) {
super(message);
}
}

main类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class FinalsText {
public static void main(String[] args) {

//创建异常对象(只new了异常对象,并没有手动抛出)
MyException e = new MyException("用户名不能为空");

//打印异常堆栈信息
e.printStackTrace();

//获取异常简单描述信息
String msg = e.getMessage();
System.out.println(msg);
}

}

运行结果:
在这里插入图片描述

2、使用的时候需要手动抛出异常(throw)

语法格式:throw + 异常类的对象

10、异常与方法覆盖

在方法覆盖时重写之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,可以更少(更小)的异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Animal{
public void doSome(){

}
public void doOther() throws Exception{

}
}

class Cat extends Animal{

//编译错误
/*public void doSome throws Exception() {

}*/

//编译正常
/* public void doOther(){

}*/

//编译正常
/* public void doOther() throws Exception {

}*/

//编译正常
public void doOther() throws NullPointerException{

}
}

十、反射机制

1、Java之反射机制详解

1、反射机制

反射机制在java.lang.reflect.*包下
反射机制相关的重要的类有

  • java.lang.Class:代表字节码文件,代表一个类型,代表整个类。
  • java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。
  • java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法
  • java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(局部变量)

2、反射机制有什么用

通过java语言中的反射机制可以操作字节码文件。有点类似于黑客(可以读和修改字节码文件。)通过反射机制可以操作代码片段。(class文件)让程序更加灵活

3、获取Class文件的三种方式

1、Class.forName()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ReflectTest01 {
public static void main(String[] args) {

/* Class.forName()
1、静态方法
2、方法的参数是一个字符串
3、字符串需要的是一个完整的类名
4、完整类名必须带有包名。java.lang包也不能省略*/

try {
Class c1 = Class.forName("java.lang.String");//c1代表的是String.class文件,或者说c1代表String类型
Class c2 = Class.forName("java.util.Date");//c2代表Date类型
Class c3 = Class.forName("java.lang.Integer");//c3代表Integer类型
Class c4 = Class.forName("java.lang.System");//c4代表System类型}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

如果只希望一个类的静态代码块执行,其他代码一律不执行,可以使用:Class.forName("完整类名");
这个方法的执行会导致类加载,类加载时,静态代码块执行

2、getClass()方法

java中任何一个对象都有一个方法:getClass()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ReflectTest01 {
public static void main(String[] args) {

/* Class.forName()
1、静态方法
2、方法的参数是一个字符串
3、字符串需要的是一个完整的类名
4、完整类名必须带有包名。java.lang包也不能省略*/

Class c1 = null;
try {
c1 = Class.forName("java.lang.String");//c1代表的是String.class文件,或者说c1代表String类型
}catch (ClassNotFoundException e) {
e.printStackTrace();
}

String s = "abc";
Class x = s.getClass();//x代表String.class字节码文件,x代表String类型
System.out.println(c1 == x); //true(==判断的是对象的内存地址)
}
}

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

3、java类型的.class属性

java语言中任何一种类型,包括基本数据类型,它都有.class属性

1
2
Class z = String.class; //z代表String类型
Class k = Date.class;//k代表Date类型

4、通过反射实例化对象

获取.class能干什么?

通过Class的newInstance()方法来实例化对象
注意:newInstance()方法内部实际上调用了无参数的构造方法,必须保证无参数构造存在才可以。

使用反射机制的方式来创建对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class User {
public User() {
System.out.println("无参数的构造方法");
}
}
12345
public class ReflectTest02 {
public static void main(String[] args) {

try {
//通过反射机制,获取Class,通过Class;来实例化对象
Class c = Class.forName("User");//c代表User类型

//newInstance() 这个方法会调用User这个类的无参数构造方法,完成对象的创建。
//重点是:newInstance()调用的是无参构造,必须保证无参构造是存在的!!!!
Object obj = c.newInstance();

System.out.println(obj);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}

在这里插入图片描述

5、通过读属性文件实例化对象

验证反射机制的灵活性。java代码只写一遍,在不改变java源代码的基础之上,可以做到不同对象的实例化。非常之灵活(符合OCP开闭原则:对扩展开放,对修改关闭)

classinfo.properties文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
className = User
1
import java.io.FileReader;
import java.util.Properties;

public class ReflectTest03 {
public static void main(String[] args) throws Exception{
//通过IO流读取classinfo.properties文件
FileReader reader = new FileReader("classinfo.properties");
//创建属性类对象Map
Properties pro = new Properties();
//加载
pro.load(reader);
//关闭流
reader.close();

//通过key获取value
String calssName = pro.getProperty("className");
System.out.println(calssName);

//通过反射机制实例化对象
Class c = Class.forName(calssName);
Object obj = c.newInstance();
System.out.println(obj);
}
}
123456789101112131415161718192021222324

6、资源绑定器

java.util包下提供了一个资源绑定器,便于获取配置文件中的内容。使用以下方法的时候,属性配置文件×××.properties必须放到类路径下

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.ResourceBundle;

public class ThreadTest04 {
public static void main(String[] args) {
//资源绑定器,只能绑定×××.properties文件。并且这个文件必须在src/类路径下。文件扩展名必须是properties
//并且在写路径的时候,路径的后面扩展名不能写
ResourceBundle bundle = ResourceBundle.getBundle("classinfo3");

String className = bundle.getString("className");
System.out.println(className);

}
}

7、类加载器

1、什么是加载器

专门负责加载类的命令/工具。ClassLoader

2、JDK中自带的类加载器

  • 启动类加载器rt.jar
  • 扩展类加载器ext/*.jar
  • 应用类加载器classpath

假如执行以下代码:

1
String s = "abc";

代码在开始执行以前,会将所需要类全部加载到JVM当中。

通过类加载器加载,看到以上代码类加载器会找string. class文件,找到就加载,那么是怎么进行加载的呢?

  • 首先通过 “启动类加载器” 加载。
    注意:启动类加载器专门加载: C: \Program Files\Java\jdk1.8.0_ 101\jre\lib\rt.jar
    rt. jar中都是JDK最核心的类库
  • 如果通过 “启动类加载器”加载不到 的时候,会通过 “扩展类加载器” 加载。
    注意:扩展类加载器专门加载: C: \Program Files\Java\jdk1.8.0_ 101\jre\lib\ext\*.jar
  • 如果 “扩展类加载器” 没有加载到,那么会通过 “应用类加载器” 加载。
    注意:应用类加载器专门加载:classpath中的类。

3、双亲委派机制

java中为了保证类加载的安全,使用了双亲委派机制优先从启动类加载器中加载,这个称为 “父”,”父”无法加载到,再从扩展类加载器中加载,这个称为**“母”。双亲委派。*如果都加载不到*,才会考虑从**应用类加载器中加载。直到加载到为止。

8、Field

1、获取Field

反射属性Field,Field翻译为字段,其实就是属性/成员。
4个Field,分别采用了不同的访问控制权限修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Student {
//4个Field,分别采用了不同的访问控制权限修饰符
public int no;
private String name;
protected int age;
boolean sex;
}
1234567
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class ReflectTest05 {
public static void main(String[] args) throws Exception{
//获取整个类
Class studentClass = Class.forName("Student");
//获取类中所有的public修饰的Field
Field[] fields = studentClass.getFields();
System.out.println(fields.length);
//取出这个Field的名
Field f = fields[0];
String fieldName = f.getName();
System.out.println(fieldName);

//获取所有的Field
Field[] fields1 = studentClass.getDeclaredFields();
System.out.println(fields1.length);

for (Field field : fields1) {
//获取属性的修饰符
int i = field.getModifiers();//返回的修饰符是一个数字,每个数字都是修饰符的代号
System.out.println(i);
//将代号转化为字符串
String modifiersString = Modifier.toString(i);
System.out.println(modifiersString);
//获取属性的类型
Class fieldType = field.getType();
String fName = fieldType.getName();
System.out.println(fName);
//获取属性的名字
System.out.println(field.getName());
}
}
}

在这里插入图片描述

2、反编译Field

通过反射机制,反编译一个类的属性Field

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class ReflectTese06 {
public static void main(String[] args) throws Exception{
//创建这个是为为了拼接字符串。
StringBuilder s = new StringBuilder();

Class studentClass = Class.forName("Student");

s.append(Modifier.toString(studentClass.getModifiers())+ " class "+ studentClass.getSimpleName() + "Student{\n");

Field[] fields = studentClass.getDeclaredFields();

for (Field field : fields) {
s.append("\t");
s.append(Modifier.toString(field.getModifiers()));
s.append(" ");
s.append(field.getType().getSimpleName());
s.append(" ");
s.append(field.getName());
s.append(";\n");
}
s.append("}");
System.out.println(s);

}
}

运行结果:
在这里插入图片描述

3、通过反射机制访问java对象属性(重点掌握)

给属性赋值set
获取属性的值get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.lang.reflect.Field;

public class ReflectTest07 {
public static void main(String[] args) throws Exception {
//不使用反射机制怎样访问一个对象的属性
Student s = new Student();

//给属性赋值
s.no = 1111;

//读属性值
System.out.println(s.no);

//使用反射机制,访问一个对象的属性
Class studentClass = Class.forName("Student");
Object obj = studentClass.newInstance();//obj就是Student对象。(底层调用无参数构造方法)

//获取no属性(根据属性的名称来获取Field)
Field noField = studentClass.getDeclaredField("no");

//给obj对象(student对象)的no属性赋值
/*虽然使用了反射机制,但是三要素还是缺一不可:
要素1:obj对象。
要素2:no属性;
要素3:2222值。
注意:反射机制让代码复杂了,但是为了一个“灵活”,这也是值得的。
*/
noField.set(obj,2222);//给obj对象的no属性赋值2222

//读取属性的值
//两个要素:获取obj对象的no属性的值。
System.out.println(noField.get(obj));
}
}

在这里插入图片描述

9、Method

1、可变长度参数

1
2
int... args`这就是可变长度参数
语法是:`类型... (注意:一定是3个点)
  • 1、可变长度参数要求的参数个数是:0~N个
  • 2、可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有一个。
1
2
3
4
5
6
7
8
9
10
11
public class ArgsTest {
public static void main(String[] args) {
m();
m(10);
m(10,20);

}
public static void m(int... args){
System.out.println("m方法执行了!");
}
}

运行结果:
在这里插入图片描述

2、反射Method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class User {

public int id;
private String name;
String address;
protected int sno;
public User() {}

public User(int id, String name, String address, int sno) {
super();
this.id = id;
this.name = name;
this.address = address;
this.sno = sno;
}

public void study() {
System.out.println("study...");
}

public void eat(int a,String b) {
System.out.println("eat...");
}
}

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class Test {
public static void main(String[] args) throws Exception {
// 获取类
Class usrClass = Class.forName("com.lzj.reflect.pojo.User");

// 获取所有的Method(包括私有)
Method[] methods = usrClass.getDeclaredMethods();

// 遍历Method
for(Method method : methods){
// 获取修饰符列表
System.out.println(Modifier.toString(method.getModifiers()));
// 获取方法的返回值类型
System.out.println(method.getReturnType().getSimpleName());
// 获取方法名
System.out.println(method.getName());
// 方法的修饰符列表(一个方法的参数可能会有多个)
Class[] parameterTypes = method.getParameterTypes();
for(Class parameterType : parameterTypes){
System.out.println(parameterType.getSimpleName());
}
}
}
}

3、反编译Method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class Test {
public static void main(String[] args) throws Exception {
StringBuilder s = new StringBuilder();
Class userClass = Class.forName("com.lzj.reflect.pojo.User");
s.append(Modifier.toString(userClass.getModifiers()) + " class "+userClass.getSimpleName()+" {\n");

Method[] methods = userClass.getDeclaredMethods();
for(Method method : methods){

s.append("\t");
s.append(Modifier.toString(method.getModifiers()));
s.append(" ");
s.append(method.getReturnType().getSimpleName());
s.append(" ");
s.append(method.getName());
s.append("(");

Class[] parameterTypes = method.getParameterTypes();
for(Class parameterType : parameterTypes){
s.append(parameterType.getSimpleName());
s.append(",");
}

if(parameterTypes.length > 0) {
s.deleteCharAt(s.length() - 1);
}

s.append("){}\n");
}
s.append("}");
System.out.println(s);
}
}

4、通过反射机制调用方法(重点掌握)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test {

public static void main(String[] args) {
try {
Class<?> clazz = Class.forName("com.lgz.Test");
Method method = clazz.getMethod("run");
method.invoke(clazz.newInstance());

Method methodPrivate = clazz.getDeclaredMethod("eat");
methodPrivate.setAccessible(true);//调private方法
methodPrivate.invoke(clazz.newInstance());

Method methodStatic = clazz.getMethod("work");
methodStatic.invoke(null);

Method method1 = clazz.getMethod("speak", int.class, String.class);
method1.invoke(clazz.newInstance(), 22, "小明");
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
e.printStackTrace();
}

}

public static void work() {
System.out.println("work() 方法被调用...");
}

private void eat() {
System.out.println("eat() 方法被调用...");
}

public void run() {
System.out.println("run() 方法被调用...");
}

public void speak(int age, String name) {
System.out.println("speak() 方法被调用.... age = " + age + " name= " + name);
}
}

10、Constructor

1)得到某个类所有的构造方法

Constructor[] constructors = Class.forName("java.lang.String").getConstructors();
2)得到某一个构造方法

Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
实例

1
2
3
Constructor constructor = String.class.getConstructor(StringBuffer.class);
String str = (String) constructor.newInstance(new StringBuffer("abc"));
System.out.println(str.charAt(2));

打印结果:c

11、获取父类和父接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ReflectTest08 {
public static void main(String[] args) throws Exception{
//String举例
Class stringClass = Class.forName("java.lang.String");

//获取String的父类
Class superClass = stringClass.getSuperclass();
System.out.println(superClass);

//获取String类实现的所有接口(一个类可以实现多个接口)
Class[] interfaces = stringClass.getInterfaces();
for (Class anInterface : interfaces) {
System.out.println(anInterface);
}
}
}

在这里插入图片描述

十一、注解

1、java之注解详解

1、注解如何定义?怎么用?

注解,或者说叫做注释类型,英文单词是:Annotation

注解Annotation是一种引用数据类型。编译之后也是生成XXX.class文件。

自定义注解:

1
2
3
[修饰符列表] @interface 注解类型名{

}

注解怎么使用,用在什么地方?

  • 第一:注解使用时的语法格式是:@ 注解类型名
  • 第二:注解还可以出现在类上、属性上、方法上、变量上等…注解还可以出现在注解类型上。

JDK内置了哪些注释?
java.lang包下的注释类型:

  • 掌握:Deprecated,用@Deprecated注释的程序元素。不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。
  • 掌握:Override,表示一个方法声明打算重写超类的另一个方法声明。
  • 不用掌握:SuppressWarnings,指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。

2、自定义注解

注解以@开头
在idea中new一个自定义注解
在这里插入图片描述

自定义注解的依法格式:

【修饰符列表】 @interface 注解名称{…}

1
2
public @interface FirstAnnotation {
}

使用注解:@注解名称 如:@Override

1
2
3
@MyAnnotation
public class Person {
}

2、Override注解

关于JDK lang包下的Override注解
源代码:

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
  • @Override只能注解方法。
  • @Override这个注解是给编译器参考的,和运行阶段没有关系
  • 凡是java中的方法带有这个注解的,编译器都会进行编译检查,如果这个方法不是重写父类的方法,编译器报错。

4、元注解

1、什么是元注解?

用来标注”注解类型的”注解”。称为元注解

2、常见的元注解有哪些?

  • Target
  • Retention

3、关于Target注解:

这是一个元注解,用来标注”注解类型”的注解”
这个Target注解用来标注”被标注的注解”可以出现在哪些位置上
@Target (ElementType .METHOD):表示w被标注的注解只能出现在方法上.

4、关于Retention注解:

这是一个元注解,用来标注”注解类型”的”注解”
这个Retention注解用来标注”被标注的注解”最终保存在哪里

@Retention(RetentionPolicy .SOURCE):表示该注解只被保留在java源文件中。@Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件
@Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以

5、Deprecated注解

表示已过时的。

源码:

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

6、使用反射获取注解信息

需求:创建一个自定义注解,在Person类上及Person的方法上使用注解,在测试类中通过反射获取标注在Person类和方法上的注解信息。

注意:自定义注解必须用@Retention(RetentionPolicy.RUNTIME)标注,这样自定义注解才能被反射机制读取到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonInfo {
int age();
String name();
}
12345
@PersonInfo(age=20,name="张三")
public class Person {

@PersonInfo(age=30,name="李四")
public void eat(){
System.out.println("run方法执行了.....");
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.lang.reflect.Method;

public class ReflectPersonInfo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//获取Person类对象
Class<?> aClass = Class.forName("cn.itcast.annotation.Person");

//获取标注在Person类上的@PersonInfo注解信息
if(aClass.isAnnotationPresent(PersonInfo.class)){ //判断Person类上是否有@PersonInfo注解标注
//获取注解对象
PersonInfo personInfo = aClass.getAnnotation(PersonInfo.class);

//打印标注在Person类上的@PersonInfo注解信息
System.out.println(personInfo.age());
System.out.println(personInfo.name());
}

System.out.println("----------------------------");

//获取标注在Person类的方法上的@Person注解信息
Method eat = aClass.getDeclaredMethod("eat"); //获取Person类的eat方法对象
if(eat.isAnnotationPresent(PersonInfo.class)){ //判断Person类的eat方法上是否有@PersonInfo注解标注
//获取标注在eat方法上的注解对象
PersonInfo personInfo = eat.getAnnotation(PersonInfo.class);

//打印标注在Person类中的eat()方法上的注解信息
System.out.println(personInfo.name());
System.out.println(personInfo.age());
}
}
}

测试结果:
在这里插入图片描述


JAVA基础知识笔记
https://aotomata.me/2020/04/19/Java基础知识/
作者
吕书科
发布于
2020年4月19日
许可协议