Java
Java
AiernsJava编程知识
1. 基本背景与流程
1.1 JAVA技术体系平台
- JavaSE(Java Standard Edition标准版)
- JavaEE(Java Enterprise Edition企业版)
- JavaME(Java Micro Edition小型版)
Java语言的8大特性:
- 简单:比C/C++简单
- 面向对象:关注的是有功能的对象。
- 分布式:基于网络的多主机协作。
- 健壮性:强类型(所有数据都有类型),异常处理,GC(垃圾自动收集),指针(Pointer)的安全化:引用(Reference)。
- 安全:所有程序.class必须由ClassLoader类加载器加载。
- 跨平台:不同平台有不同的JVM。
- 性能好:Java是编译程序,比解释程序好。
- 多线程
1.2 JVM、JRE、JDK
- JVM(Java Virtual Machine ):Java虚拟机,是运行所有Java程序的假想计算机,是Java程序的运行环境之一,也是Java 最具吸引力的特性之一。我们编写的Java代码,都运行在JVM 之上。
- **JRE ** (Java Runtime Environment) :是Java程序的运行时环境,包含
JVM
和运行时所需要的核心类库
。 - JDK (Java Development’s Kit):是Java程序开发工具包,包含
JRE
和开发人员使用的工具。
想要运行一个已有的Java程序,那么只需安装JRE
即可。
想要开发一个全新的Java程序,那么必须安装JDK
,其内部包含JRE
。
1.3 环境搭建
1、JDK下载
官方网站www.oracle.com下载JDK8。
2、JDK安装
安装到纯英文路径并复制路径。
3、配置环境变量
环境变量->系统变量->编辑Path->在最前面键入路径;
1.4 Java程序结构
1 | 类{ |
- 类>多个方法>多个语句
- 类中包含方法,方法中也可以有内部类
- 类是java程序的基本单位
- 方法是一个类最基本的功能单位,表达类或对象的功能,方法必须隶属于类
- 语句是最小执行单位,必须隶属于方法
- 主类是包含主方法(main)的类,可以有多个,只有主类可以当作程序运行
- 公共类是有public关键字的类,公共类要与源文件同名,公共类只能有一个
1.5 运行案例(经典HelloWorld案例)
JAVA开发三步骤:编写、编译、运行。
1、编写Java文件
1 | public class HelloWorld { |
2、编译Java源文件生成.class字节码文件
进入Java文件所在目录,在地址中输入cmd即可定位在该目录使用DOS命令行。
1 | javac HelloWorld.java |
可以发现在该目录生成了HelloWorld.class字节码文件。
3、运行Java程序
1 | java HelloWorld |
1.6 注意事项
Java语言是一门严格区分大小写的语言
字符编码问题
1 | javac -encoding utf-8 Review01.java |
- 当类是public时,源文件名必须与类名一致。无论是否public都尽量保持同名。
- 一个源文件可以有多个类,编译后生成多个class文件。但是一个源文件只有一个public类。
- main方法不是必须在public类中,一般是public。
1.7 注释
单行注释 //
多行注释 /* */
文档注释(java特有)/** */
1.8 DOS指令
1 | //切换到目标目录-绝对 |
1.9 Java字符
1、特殊的转义字符
1 | \n:换行 |
2、 Unicode编码值
在JVM内存中,一个字符占2个字节,Java使用Unicode字符集来表示每一个字符,即每一个字符对应一个唯一的Unicode编码值。
范围是0~65535.
字符 | Unicode编码值 |
---|---|
‘0’ | 48 |
‘1’ | 49 |
‘A’ | 65 |
‘B’ | 66 |
‘a’ | 97 |
1 | char c2 = 97; |
\u字符的Unicode编码值的十六进制型
1 | char c = '\u0041'; //十进制Unicode值65,对应十六进制是41,但是\u后面必须写4位 |
1.10 快捷键
- Ctrl+D 复制一行
- Ctrl+Y 删除一行
- Alt+回车 快速修正常见问题
- 锁定小键盘后,按1到行尾,按7到行首。
- Ctrl+P 显示提醒方法的参数列表
- alt+insert 快速构造 构造器 get/set —–> alt+n 不选
- ctrl+alt+insert 新建类
- 快速创建数组 arr.fori
- 重写方法快捷键:Ctrl+O
- shift+tab 切换类型
- 按两次shift , 勾上include non- .. ,可以搜索.
- 生成构造器 Alt+ Insert
- 查看构造器和方法形参列表 Ctrl+P
Debug模式:
- f7 进入语句内部(如果有方法调用,会进入)
- alt+shift+f7 强行进入最细节
- f8 一行一行地执行
- shift+f8 直接结束掉当前方法
- f9 直接执行
1.11 经典报错
- 0 / 0 报错ArithmeticError
- NullPointerException 空指针异常,
- 0.0 / 0 不报错 显示NaN —>Not a number
- 1.0 / 0 不报错 显示Infinity
- NumberFormatException 数字格式化异常
- ArrayIndexOutOfBoundsException 数组下标越界异常
- ClassCastException 类型转换异常
- RuntimeException 运行时异常
2. Java基础语法
2.1 关键字(keyword)
关键字:是指在程序中,Java已经定义好的单词,具有特殊含义。全部是小写字母。
保留字:是指在现阶段还不是关键字, 但是将来有可能会成为关键字。
2.2 标识符(identifier)
程序员自己命名的部分可以称为标识符。
即给类、变量、方法、包等命名的字符序列,称为标识符。
1、标识符的命名规则
- 标识符只能使用26个英文字母、数字、_和$。
- 不能使用关键字。
- 数字不能开头。
- 不能包含空格,长度无限制。
- 严格区分大小写。
2、标识符的命名规范
- 包名:全部小写,单词间.分割。
例:java.lang
- 类名、接口名:每个单词首字母大写。
例:HelloWorld,String
- 变量、方法名:第二个单词开始首字母大写。
例:name,bookName
- 常量名:全部大写
例:MAX_VALUE
2.3 数据类型(data type)
Java的数据类型分为两大类:
- 基本数据类型:包括
整数
、浮点数
、字符
、布尔
。 空间中保存数据本身.—>存放在栈中(寿命短) - 引用数据类型:包括
数组
、类
、接口
、枚举
、注解
。空间中保存其他数据的地址. —->存放在堆中(寿命长)
1、基本数据类型存储范围
min | max | |
---|---|---|
byte | 0x80 | 0x7F |
char | 0x0000 | 0xFFFF |
short | 0x8000 | 0x7FFF |
int | 0x8000 0000 | 0x7FFF FFFF |
long | 0x8000 0000 0000 0000 | 0x7FFF FFFF FFFF FFFF |
2、数据类型的作用
1.决定空间大小
2.决定空间中可以保存什么数据
3.决定数据能做什么
2.4 变量(variable)
变量是什么?
在内存中的一块固定类型的空间,此空间可以保存一个数据,且此空间数据在其数据范围内可随意变化.
1 | 数据类型 变量名; |
注意:变量的数据类型可以是基本数据类型,也可以是引用数据类型。
1、变量赋值
1 | int age = 18; |
- long类型:如果赋值的常量整数超过int范围,那么需要在数字后面加L。
- float类型:如果赋值为常量小数,那么需要在小数后面加F。
- char类型:使用单引号’’
- String类型:使用双引号””
1 | char c = '尚';//使用单引号 |
2、变量输出
1 | //输出变量的值 |
3、变量注意事项
- 先声明后使用,只有声明好变量才有空间可用。
- 必须初始化再使用。
- 必须要有数据类型和变量名,数据类型的作用是3个。变量名的作用是定位空间。
- 同一作用域内变量不可以重复声明。
- 变量不可以超出作用范围使用,由其声明语句所隶属的{}范围决定。
- 变量有其数据范围。
4、变量分类
1)按数据类型分:
基本数据类型(primitive):空间中保存数据本身(itself)。
- 整数 byte short char int long
- 浮点数 float double
- 布尔型 boolean 占用1字节
引用数据类型(reference):空间中保存对象地址(address)。地址:某个字节的编号,占用8字节,编号为0的内存地址为null。
- 类 class
- 接口 interface
- 枚举 enum
- 注解 @interface
- 数组 []
2)按声明语句位置分:
- 局部变量(local):声明在方法中的变量,范围小,寿命短。
- 成员变量(member):声明在类中方法外的变量。此时变量与方法平级,都是类成员。范围大(全局),寿命长。
2.5 常量(literal value)
常量也有内存空间和数据类型,但是空间中的数据不能改变.常量通常有2种,一种是字面量,另一种是final修饰的量.
常见常量:
类型 | 举例 |
---|---|
整数字面量 | 12,-23, 1567844444557L |
浮点字面量 | 12.34F,12.34 |
字符字面量 | ‘a’,’0’,‘尚’ |
布尔字面量 | true,false |
字符串字面量 | ”HelloWorld“ |
- 整数常量,超过int范围的必须加L
- 小数常量,不加F就是默认double类型,如果数据类型是float,在初始化时一定要加F。
- char常量,使用’’
- String字符串常量,使用””
1 | public class ConstantDemo { |
2.6 最终变量(final)
最终变量习惯上也称为常量,常量名通常所有字母都大写,每一个单词之间使用下划线分割,从命名上和变量名区分开来。
1 | public class FinalVariableDemo { |
2.7 基本类型转换(Conversion)
转换方式:自动类型转换和强制类型转换
1、自动类型转换
- 当把存储范围小的值(常量值、变量的值、表达式计算的结果值)赋值给了存储范围大的变量时。
1 | int i = 'A';//char自动升级为int,其实就是把字符的编码值赋值给i变量了 |
- 小范围与大范围混合运算,按最大类型运算。
1 | int i = 1; |
- 当byte,short,char数据类型进行算术运算时,按照int类型处理。
1 | byte b1 = 1; |
- 在java中,boolean只有true和false的表现形式,没有1或者0,也不能用1或0转换为Boolean。
2、强制类型转换
取值范围大的转换成范围小的类型。
转换格式:
1 | 数据类型 变量名 = (数据类型)被强转数据值; //()中的数据类型必须<=变量的数据类型,一般都是= |
(1)当把存储范围大的值(常量值、变量的值、表达式计算的结果值)赋值给了存储范围小的变量时,需要强制类型转换,提示:有风险,可能会损失精度或溢出
1 | int i = (int)3.14;//强制类型转换,损失精度 |
(2)当某个值想要提升数据类型时,也可以使用强制类型转换
1 | int i = 1; |
提示:这个情况的强制类型转换是没有风险的。
3、基本数据类型与字符串类型的转换
- 任何类型与String类型进行”+”运算时,结果一定是String类型。
1 | System.out.println("" + 1 + 2);//12 |
- String类型不能进行强制类型转换。
1 | String str = "123"; |
练习:
1 | String str1 = 4; //报错 |
2.8 进制
四种类型的进制来表示10。
(1)十进制:正常表示
System.out.println(10); // 10
(2)二进制:0b或0B开头
System.out.println(0B10); // 2
(3)八进制:0开头
System.out.println(010); // 8
(4)十六进制:0x或0X开头
System.out.println(0X10); // 16
2.9 运算符和标点符号
分类 | 运算符 |
---|---|
算术运算符(7个) | +、-、*、/、%、++、– |
赋值运算符(12个) | =、+=、-=、*=、/=、%=、>>=、<<=、>>>=、&=、|=、^=等 |
关系运算符(6个) | >、>=、<、<=、==、!= |
逻辑运算符(6个) | &、|、^、!、&&、|| |
条件运算符(2个) | (条件表达式)?结果1:结果2 |
位运算符(7个) | <<, >>, >>>, &, |, ^, ~ |
Lambda运算符(1个) | ->(后面学) |
1、位运算符
位运算符 | 符号解释 |
---|---|
& |
按位与,当两位相同时为1时才返回1 |
` | ` |
~ |
按位非,将操作数的每个位(包括符号位)全部取反 |
^ |
按位异或。当两位相同时返回0,不同时返回1 |
<< |
左移运算符 |
>> |
右移运算符 |
>>> |
无符号右移运算符 |
- byte,short,char在计算时按照int类型处理
如何区分&,|,^是逻辑运算符还是位运算符?
如果操作数是boolean类型,就是逻辑运算符,如果操作数是整数,那么就位运算符。
2、算术运算符
算术运算符 | 符号解释 |
---|---|
+ |
取自己int a = 5; +a => 5, int b = -5; +b => -5 |
- |
取相反数 int a = 5; -n => -5, int b = -8, -b => 8 |
+ |
加法运算,字符串连接运算,正号 |
- |
减法运算,负号 |
* |
乘法运算 |
/ |
除法运算,整数/整数结果还是整数, 5 / 3 => 1 |
% |
求余运算,余数的符号只看被除数, 13 % 5 => 3 |
++ 、 -- |
自增自减运算 |
1 | int a = 10; |
- M%N,如果N为负数,忽略负号,如果M是负数,结果也是负数。
3、关系运算符/比较运算符
关系运算符 | 符号解释 |
---|---|
< |
比较符号左边的数据是否小于右边的数据,如果小于结果是true。 |
> |
比较符号左边的数据是否大于右边的数据,如果大于结果是true。 |
<= |
比较符号左边的数据是否小于或者等于右边的数据,如果大于结果是false。 |
>= |
比较符号左边的数据是否大于或者等于右边的数据,如果小于结果是false。 |
== |
比较符号两边数据是否相等,相等结果是true。 |
!= |
不等于符号 ,如果符号两边的数据不相等,结果是true。 |
- 比较大小的运算只能适用于基本数据类型中的数值型之间。
- ==和!=可以适用于所有数据类型之间。包括对象和布尔。
1 | boolean b1 = 1 <= a < 10;//错误,自左向右依次判断, 1<=a判断完成后,转为true即boolean类型,而boolean型不能参与比较大小运算。 |
- 累操作比常规操作更方便。累操作不会改变类型。
1 | byte b1 = 50; |
4、逻辑运算符
逻辑运算符,是用来连接两个布尔类型值的运算符(!
除外),运算结果也是boolean值true
或者false
逻辑运算符 | 符号解释 | 符号特点 |
---|---|---|
& |
逻辑与,且 | 有false 则false |
` | ` | 逻辑或 |
^ |
逻辑异或 | 相同为false ,不同为true |
! |
非 | 非false 则true ,非true 则false |
&& |
双与,短路与 | 左边为false,则右边就不看 |
` | ` |
&&和&区别,||和|区别:
**
&&
和&
**区别:&&
和&
结果一样,&&
有短路效果,左边为false,右边不执行;&
左边无论是什么,右边都会执行。
**
||
和|
**区别:||
和|
结果一样,||
有短路效果,左边为true,右边不执行;|
左边无论是什么,右边都会执行。
1
2
3
4
5
6
7
8
9
10
11
12public class LogicOperator{
public static void main(String[] args){
/*
表示条件,成绩必须在[0,100]之间
成绩是int类型变量score
*/
int score = 56;
System.out.println(0<=score && score<=100);
/*
错误:
System.out.println(0<=score<=100);
*/
5、条件运算符
格式:
1 | 条件表达式?表达式1:表达式2 |
- 表达式1和表达式2必须是一样的数据类型
1 | //找到两者的最大值 |
6、赋值运算符
运算符 | 符号解释 |
---|---|
= | 将右边的常量值/变量值/表达式的值,赋值给左边的变量,. 左面必须是变量 |
+= | 相加,最后将结果赋值给左边的变量 |
-= | 相减,最后将结果赋值给左边的变量 |
*= | 相乘,最后将结果赋值给左边的变量 |
/= | 相除,最后将结果赋值给左边的变量 |
%= | 相模,最后将结果赋值给左边的变量 |
<<= | 将左边变量的值左移右边常量/变量值/表达式的值的相应位,最后将结果赋值给左边的变量 |
>>= | 将左边变量的值右移右边常量/变量值/表达式的值的相应位,最后将结果赋值给左边的变量 |
>>>= | 将左边变量的值无符号右移右边常量/变量值/表达式的值的相应位,最后将结果赋值给左边的变量 |
&= | 将左边变量的值和右边的常量值/变量值/表达式的值进行按位与,最后将结果赋值给左边的变量 |
|= | 将左边变量的值和右边的常量值/变量值/表达式的值进行按位或,最后将结果赋值给左边的变量 |
^= | 将左边变量的值和右边的常量值/变量值/表达式的值进行按位异或,最后将结果赋值给左边的变量 |
1 | public static void main(String[] args){ |
7、运算符优先级
口诀:
单目运算排第一;
乘除余二加减三;
移位四,关系五;
等和不等排第六;
位与、异或和位或;
短路与和短路或;
依次从七到十一;
条件排在第十二;
赋值一定是最后;
8、标点符号
- 小括号()用于强制类型转换、表示优先运算表达式、方法参数列表
- 大括号{}用于数组元素列表、类体、方法体、复合语句代码块边界符
- 中括号[]用于数组
- 分号;用于结束语句
- 逗号,用于多个赋值表达式的分隔符和方法参数列表分隔符
- 英文句号.用于成员访问和包目录结构分隔符
- 英文省略号…用于可变参数
- @用于注解
- 双冒号::用于方法引用
3. 流程控制语句
3.1 顺序结构
顺序结构就是程序从上到下逐行地执行。表达式语句都是顺序执行的。并且上一行对某个变量的修改对下一行会产生影响。
3.2 分支语句
1、条件判断:if
1 | if (条件表达式){ |
2、条件判断:if..else
1 | if(关系表达式) { |
练习:接收一个整数,判断能否被7整除。
1 | public class TestIfElse { |
3、多分支条件判断
1 | if (判断条件1) { |
4、if..else嵌套
1 | if(){ |
5、switch..case多分支选择
1 | //如果case中没有break,会形成fall through.穿透 |
- swtich表达式值的类型只能是4种基本数据类型(byte,short,int,char)、2种引用数据类型(JDK1.5之后的枚举,JDK1.7之后的String)
- case后面必须是常量且不能重复,多个case绝不允许出现相同的常量。
switch语句具有穿透性。一旦匹配成功,不会在判断下一个case的值,直接向后运行,直到遇到break或者整个switch语句结束,switch语句执行终止。
switch case的效率比for循环高,因为它的底层是表格,里面存储常量,不需要判断,直接跳转。
1 | import java.util.Scanner; |
3.3 循环语句
for适用于循环次数确定的循环。
while和do while适用于次数不确定。
组成部分
1. 初始化语句(init_statement)
2. 循环条件语句(test_statement)
3. 循环体(body)
4. 迭代语句(alter_statement)
1、while循环
1 | [初始化语句;] |
while中循环条件必须是boolean类。
1 | while(1 == 1) //报错,不可能到达。死循环 |
2、do…while循环
1 | [初始化语句;] |
注意:
while(循环条件)中循环条件必须是boolean类型
do{}while();最后有一个分号
do…while结构的循环体语句是至少会执行一次,这个和for和while是不一样的
3、for循环
1 | for (初始化语句A; 循环条件语句B; 迭代语句C) { / |
注意:
- 循环条件必须是boolean类型
死循环:
1 | while (true) { |
增强型for循环
1 | //增强for循环,利用tmp让修改内容的位置在栈中而不是直接修改堆中的内容,更安全适用于只读访问. |
4、循环语句的区别
- 从循环次数角度分析
- do…while循环至少执行一次循环体语句
- for和while循环先循环条件语句是否成立,然后决定是否执行循环体,至少执行零次循环体语句
5、循环嵌套
1 | for(初始化语句A; 循环条件语句B; 迭代语句C) {//外循环控制行 |
6、特殊流程控制break
使用场景:终止switch/当前循环/指定标签
1 | for (int i = 0; i < 10; i++) { |
使用标签中断指定循环
1 | l1: for(int i = 0;i<5;i++){ |
命名的标签可以中断
1 | boolean flag = true; |
7、特殊流程控制continue
使用场景:提前结束本次循环,继续下一次的循环
1 | for (int i = 0; i < 100; i++){ |
8、特殊流程控制return
return 并不是用于循环的, 它在方法中表示方法的结束, 而不管此时语句正在循环中还是哪里, 都会直接结束.
9、死循环与无限循环
1 | while(true){};//死循环 |
3.4 方法(Method)
方法也叫函数(function),是java程序中的独立的功能单位,方法是一个类中最基本的功能单元。
1、方法的特点
(1)必须先声明后使用
类,变量,方法等都要先声明后使用
(2)不调用不执行,调用一次执行一次。
2、方法的声明
声明方法的位置==必须在类中方法外==,方法不可以嵌套。
声明方法的位置:
1 | 类{ |
声明方法的格式:
1 | 【修饰符】 返回值类型 方法名(数据类型1 形参1, 数据类型2 形参2 ....){ |
一个完整的方法 = 方法头 + 方法体。
方法头就是 【修饰符】 返回值类型 方法名(【形参列表 】),也称为方法签名,通常调用方法时只需要关注方法头就可以,从方法头可以看出这个方法的功能和调用格式。
方法头可能包含5个部分,但是有些部分是可能缺省的:
- 修饰符:可选的。方法的修饰符也有很多,例如:public、protected、private、static、abstract、native、final、synchronized等。其中根据是否有static,可以将方法分为静态方法和非静态方法。其中静态方法又称为类方法,非静态方法又称为实例方法.
- 返回值类型: 表示方法运行的结果的数据类型,方法执行后将结果返回到调用者
- 基本数据类型
- 引用数据类型
- 无返回值类型:void
- 方法名:标识符,给方法起一个名字,见名知意,能准确代表该方法功能的名字
- 参数列表:表示完成方法体功能时需要外部提供的数据列表
- throws异常列表
关于方法体中return语句的说明:
- return语句的作用是结束方法的执行,并将方法的结果返回去
- 如果返回值类型不是void,方法体中必须保证一定有 return 返回值; 语句,并且要求该返回值结果的类型与声明的返回值类型一致或兼容。
- 如果返回值类型为void时,方法体中可以没有return语句,如果要用return语句提前结束方法的执行,那么return后面不能跟返回值,直接写return ; 就可以。
- return语句后面就不能再写其他代码了,否则会报错:Unreachable code
3、形参和实参
形参:在定义方法时方法名后面括号中声明的变量称为形式参数
实参:在调用方法时方法名后面括号中的使用的值/变量/表达式称为实际参数
4.值传递
1、对于基本类型参数,在方法体内对参数进行重新赋值,并不会改变原有变量的值。
2、对于引用类型参数,在方法体内对参数进行重新赋予引用,并不会改变原有变量所持有的引用。
3、方法体内对参数进行运算,不影响原有变量的值。
4、方法体内对参数所指向对象的属性进行操作,将改变原有变量所指向对象的属性值。
3.5 方法的重载
方法重载(overload):指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可,与修饰符和返回值类型无关。
参数列表:数据类型个数不同,数据类型不同(按理来说数据类型顺序不同也可以,但是很少见,也不推荐,逻辑上容易有歧义)。
重载方法调用:JVM通过方法的参数列表,调用匹配的方法。
- 先找个数、类型最匹配的
再找个数和类型可以兼容的,如果同时多个方法可以兼容将会报错
连环调用,对方法重载时调用重载前方法,便于维护。能连环调用绝不单独调用。
1 | class OverLoad_2 { |
3.6 类限定
限定使用某个类的方法。类名.方法名
1 | public class JavaBeanTest { |
3.7 方法的递归调用
递归调用:方法自己调用自己的现象就称为递归。
递归的分类:
- 递归分为两种,直接递归和间接递归。
- 直接递归称为方法自身调用自己。
- 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。
注意事项:
- 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
- 在递归中虽然有限定条件,但是递归深度不能太深,否则效率低下,或者也会发生栈内存溢出。
- 能够使用循环代替的,尽量使用循环代替递归
- 递归一定有参数,参数控制就类似于迭代语句。
1 | public class FibonacciTest { |
4. 数组Array
4.1 数组的特点
1、数组的长度一旦确定就不能修改
2、创建数组时会在内存中开辟一整块连续的空间。
3、存取元素的速度快,因为可以通过[下标],直接定位到任意一个元素。
4.2 数组的分类
按照维度分:
- 一维数组
- 二维数组
按照元素类型分:
- 基本数据类型的元素:存储数据值
- 引用数据类型的元素:存储对象(本质上存储对象的首地址)
无论数组的元素是基本数据类型还是引用数据类型,数组本身都是引用数据类型。
4.3 数组的声明
1 | //推荐 |
- 数组的声明,就是要确定:
(1)数组的维度:在Java中数组的标点符号是[],[]表示一维,[][]表示二维
(2)数组的元素类型:即创建的数组容器可以存储什么数据类型的数据。元素的类型可以是任意的Java的数据类型。例如:int, String, Student等
(3)数组名:就是代表某个数组的标识符,数组名其实也是变量名,按照变量的命名规范来命名。数组名是个引用数据类型的变量,因为它代表一组数据。
4.4 一维数组
1、一维数组的静态初始化
数组初始化
初始化就是确定数组元素的个数和元素的值
静态数组初始化
静态初始化就是用静态数据为数组初始化,此时数组的长度由静态数据的个数决定。
静态初始化格式1:
1 | 数据类型[] 数组名 = {元素1,元素2,元素3...};//必须在一个语句中,不能分开两个语句写 |
1 | int[] arr = {1,2,3,4,5};//正确 |
静态初始化格式2:
1 | 数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3...}; |
1 | int[] arr = new int[]{1,2,3,4,5};//正确 |
2、一维数组的使用
- 数组长度
1 | 数组名.length |
- 数组索引
1 | 数组名[下标];//从0开始 |
1 | public class Test03ArrayUse { |
3、越界异常
1 | public class Test04ArrayIndexOutOfBoundsException { |
4、一维数组的遍历
1 | for(int i=0; i<arr.length; i++){ |
5、一堆数组动态初始化
先确定元素的个数(即数组的长度),而元素此时只是默认值,并不是真正的数据。元素真正的数据需要后续单独一个一个赋值。
1 | //第一种 |
- new:关键字,创建数组使用的关键字。因为数组本身是引用数据类型,所以要用new创建数组对象。
- 注意:数组有定长特性,长度一旦指定,不可更改。
1 | int[] arr = new int[5]; |
1 | public class ArrayInitTest { |
6、数组元素的默认值
当我们使用动态初始化方式创建数组时,元素只是默认值。
7、一维数组内存分析
Java虚拟机要运行程序,必须要对内存进行空间的分配和管理。
Java虚拟机的内存划分:
区域名称 | 作用 |
---|---|
程序计数器 | 程序计数器是CPU中的寄存器,它包含每一个线程下一条要执行的指令的地址 |
本地方法栈 | 当程序中调用了native的本地方法时,本地方法执行期间的内存区域 |
方法区 | 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 |
堆内存 | 存储对象(包括数组对象),new来创建的,都存储在堆内存。 |
虚拟机栈 | 用于存储正在执行的每个Java方法的局部变量表等。局部变量表存放了编译期可知长度的各种基本数据类型、对象引用,方法执行完,自动释放。 |
1. 一维数组在内存中的存储
1 | public static void main(String[] args){ |
程序执行流程:
1.main方法进入方法栈执行。
2.创建数组,JVM会在堆内存中开辟空间,存储数组。
3.数组在内存中会有自己的内存地址,以十六进制表示。
4.数组中有3个元素,默认值0。
5.JVM将数组的内存首地址赋值给引用类型变量arr。
6.变量arr中保存的是数组内存中的地址,而不是一个具体是数值,因此是引用数据类型。
思考:打印arr为什么是[I@5f150435,它是数组的地址吗?
答:它不是数组的地址。
问?不是说arr中存储的是数组对象的首地址吗?
答:arr中存储的是数组的首地址,但是因为数组是引用数据类型,打印arr时,会自动调用arr数组对象的toString()方法,该方法默认实现的是对象类型名@该对象的hashCode()值的十六进制值。
问?对象的hashCode值是否就是对象内存地址?
答:不一定,因为这个和不同品牌的JVM产品的具体实现有关。例如:Oracle的OpenJDK中给出了5种实现,其中有一种是直接返回对象的内存地址,但是OpenJDK默认没有选择这种方式。
两个变量指向同一个数组,本质上代表同一个数组。
1 | public static void main(String[] args) { |
4.5 数组的常见算法
1、数组统计
求总和、均值、统计偶数等。
2、数组找最值
1、找最值并记录下标
思路1:假设第一个最大/最小,然后用max/min与后面元素依次比较。
1 | public class ArrayFindMaxTest{ |
思路2:用maxIndex时刻记录目前比对的最大/小的下标,那么arr[maxIndex]就是目前的最大值
1 | class ArrayFindMaxTest2{ |
3、数组查找
1、顺序查找
1 | import java.util.Scanner; |
2、二分查找
不考虑重复元素:
1 | import java.util.Scanner; |
考虑重复元素:
1 | import java.util.Scanner; |
4、数组反转
1 | //数组对称位置的元素互换 |
5、数组排序
1、排序算法
2、直接选择排序
又称为简单选择排序,从待排序序列中选出最小/最大元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找最小/最大元素,放到已排序序列的末尾。直到全部待排序的数据元素的个数为0。选择排序不稳定。
1 | //直接选择排序 |
3、冒泡排序
原理:比较两个相邻的元素,将值大的元素交换至右端。
思路:依次比较相邻的两个数,将小数放到前面,大数放到后面。
1 | //冒泡排序 BubbleSort |
4、冒泡排序优化
1 | //多加一个flag来控制比较次数,如果某一次排序过程中,整个数组顺序都无变动,说明已经是有序状态。 |
5、快速排序
分区\递归
1 | public static void QuickSort(int left, int right, int... arr) { |
6、idea中的排序调用
1 | arrays. |
6、对象数组
数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用数据类型是,我们称为对象数组。
注意:对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建,数组的元素的默认值就是null,所以很容易出现空指针异常NullPointerException。
1、对象数组的声明和使用
案例:
(1)定义矩形类,包含长、宽属性,area()求面积方法,perimeter()求周长方法,String getInfo()返回圆对象的详细信息的方法
(2)在测试类中创建长度为5的Rectangle[]数组,用来装3个矩形对象,并给3个矩形对象的长分别赋值为10,20,30,宽分别赋值为5,15,25,遍历输出
1 | class Rectangle { |
4.6 二维数组
1. 二维数组的概念
如果需要同时存储多组同类型的数据,就需要使用二维数组。
二维数组:本质上就是元素为一维数组的一个数组。
二维数组的标记:[][]
1 | int[][] arr; //arr是一个二维数组,可以看成元素是int[]一维数组类型的一维数组 |
二维数组声明的语法格式:
1 | //推荐 |
例如:
1 | public class ArrayDefine { |
面试:
1 | int[] x,y[];//x是一维数组,y是二维数组f |
2. 二维数组的静态初始化
静态初始化:用静态数据(编译时已知)为数组初始化
1 | //以下格式要求声明与静态初始化必须一起完成。 |
如果是静态初始化,右侧的[ ] [ ]不可以写数字,行数和列数由{ }中的元素决定。
3. 二维数组的使用
- 二维数组的长度/行数:二维数组名.length
- 二维数组的某一行:二维数组名[行下标],此时相当于获取其中一组数据。它本质上是一个一维数组。行下标的范围:[0, 二维数组名.length-1]。此时把二维数组看成一维数组的话,元素是行对象。
- 某一行的列数:二维数组名[行下标].length,因为二维数组的每一行是一个一维数组。
- 某一个元素:二维数组名[行下标][列下标],即先确定行/组,再确定列。
1 | //用二维数组存储3个组的每个学员的成绩 |
4. 二维数组的遍历
1 | for(int i=0; i<二维数组名.length; i++){ //二维数组对象.length |
5. 二维数组的动态初始化
如果二维数组的每一个数据,甚至是每一行的列数,需要后期单独确定,那么就只能使用动态初始化方式了。动态初始化方式分为两种格式:
(1)规则二维表:每一行的列数是相同的
1 | //(1)确定行数和列数 |
1 | public class TwoDimensionalArrayDynamicInit { |
(2)不规则:每一行的列数不一样
1 | //(1)先确定总行数 |
1 | /** |
6. 空指针异常
1 | public class Test26NullPointerException { |
因为此时数组的每一行还未分配具体存储元素的空间,此时arr[0]是null,此时访问arr[0][0]会抛出NullPointerException
空指针异常。
4.7 练习
1 | //打印一个10行的杨辉三角 |
4.8 可变参数
//数组形参定义方法
public static void test(int a ,String[] books);
//以可变个数形参来定义方法
public static void test(int a ,String … books);
说明
1.可变参数:方法参数部分指定类型的参数个数是可变多个
2.声明方式:方法名(参数的类型名…参数名)
3.可变参数方法的使用与方法参数部分使用数组是一致的
4.方法的参数部分有可变形参,需要放在形参声明的最后
5.每个形参列表都只能有一个可变参数
1 | public void test(String[] msg){ |
4.9 取子数组
1 | /** |
5. 面向对象编程Oriented Programming
3条主线:
1)类和成员的研究
真成员(可以被继承)
- 属性 : 描述特征
- 方法 : 描述行为
- 内部类 :
伪成员(不能被继承)
- 构造器 : 初始化
- 语句块 : 初始化
2)三大特征
- 封装 Encapsulation
- 继承 Inheritance
- 多态 Polymorphism
3)其他关键字
this, pacakge, import, super, static, abstract, extends, implements, interface, enum, native…
5.1 面向对象编程
1、编程语言概述
所有的计算机程序一直都是围绕着两件事在进行的,程序设计就是用某种语言编写代码来完成这两件事。
- 如何表示和存储数据
- 基本数据类型的常量和变量:表示和存储一个个独立的数据
- 对象:表示和存储与某个具体事物相关的多个数据(例如:某个学生的姓名、年龄、联系方式等)
- 数据结构:表示和存储一组对象,数据结构有数组、链表、栈、队列、散列表、二叉树、堆……
- 基于这些数据都有什么操作行为,其实就是实现什么功能
- 数据的输入和输出
- 基于一个或两个数据的操作:赋值运算、算术运算、比较运算、逻辑运算等
- 基于一组数据的操作:统计分析、查找最大值、查找元素、排序、遍历等
2、程序设计方法(面向过程/对象)
面向过程的程序设计思想(Process-Oriented Programming),简称POP
关注的焦点是过程:过程就是操作数据的步骤,如果某个过程的实现代码在很多地方重复出现,那么就可以把这个过程抽象为一个函数,这样就可以大大简化冗余代码,也便于维护。
代码结构:以函数为组织单位。独立于函数之外的数据称为全局数据,在函数内部的称为局部数据。
面向对象的程序设计思想( Object Oriented Programming),简称OOP
- 关注的焦点是类:面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,用类来表示。某个事物的一个具体个体称为实例或对象。
- 代码结构:以类为组织单位。每种事物都具备自己的属性(即表示和存储数据,在类中用成员变量表示)和行为/功能(即操作数据,在类中用成员方法表示)。
3、类和对象
1.什么是类
类是一类具有相同特性的事物的抽象描述,是一组相关属性和行为的集合。
- 属性:就是该事物的状态信息。
- 行为:就是在你这个程序中,该状态信息要做什么操作,或者基于事物的状态能做什么。
2.什么是对象
对象是一类事物的一个具体个体。即对象是类的一个实例,必然具备该类事物的属性和行为。
例如:做一个养宠物的小游戏
类:人、猫、狗等
1 | public class Dog{ |
3.类和对象的关系
类是对一类事物的描述,是抽象的。
对象是一类事物的实例,是具体的。
类是对象的模板,对象是类的实体。
4、如何定义类
1、类的定义格式
关键字:class
1 | [修饰符] class 类名{ |
1 | public class student{ |
2、对象的创建
关键字:new
1 | 类名 变量名 = new 类名()// 通过new操作符在内存中创建对象实体 |
那么,引用变量中存储的是什么呢?答:对象地址
1 | public class TestStudent{ |
5.2 成员变量
1、如何声明成员变量
1 | [修饰符] class 类名{ |
示例:
1 | public class Person{ |
位置要求:必须在类中,方法外
类型要求:可以是Java的任意类型,包括基本数据类型、引用数据类型(类、接口、数组等)
修饰符:成员变量的修饰符有很多,例如:public、protected、private、static、volatile、transient、final等
其中static可以将成员变量分为两大类,静态变量和非静态变量。其中静态变量又称为类变量,非静态变量又称为实例变量或者属性。
2、对象的实例变量
1、实例变量的特点
(1)实例变量的值是属于某个对象的
- 必须通过对象才能访问实例变量
- 每个对象的实例变量的值是独立的
(2)实例变量有默认值
分类 | 数据类型 | 默认值 |
---|---|---|
基本类型 | 整数(byte,short,int,long) | 0 |
浮点数(float,double) | 0.0 | |
字符(char) | ‘\u0000’ | |
布尔(boolean) | false | |
数据类型 | 默认值 | |
引用类型 | 数组,类,接口 | null |
2、实例变量的访问
1 | 对象.实例变量 |
1 | public class PersonTest { |
3、实例变量的内存分析
Java对象保存在内存中时,由以下三部分组成:
- 对象头
- Mark Word:记录了和当前对象有关的GC、锁等信息。
- 指向类的指针:每一个对象需要记录它是由哪个类创建出来的,而Java对象的类数据保存在方法区,指向类的指针就是记录创建该对象的类数据在方法区的首地址。该指针在32位JVM中的长度是32bit,在64位JVM中长度是64bit。
- 数组长度(只有数组对象才有)
- 实例数据
- 即实例变量的值
- 对齐填充
- 因为JVM要求Java对象占的内存大小应该是8bit的倍数,如果不满足该大小,则需要补齐至8bit的倍数,没有特别的功能。
4、实例变量与局部变量的区别
1、声明位置和方式
(1)实例变量:在类中方法外
(2)局部变量:在方法体{}中或方法的形参列表、代码块中
2、在内存中存储的位置不同
(1)实例变量:堆
(2)局部变量:栈
3、生命周期
(1)实例变量:和对象的生命周期一样,随着对象的创建而存在,随着对象被GC回收而消亡,
而且每一个对象的实例变量是独立的。
(2)局部变量:和方法调用的生命周期一样,每一次方法被调用而在存在,随着方法执行的结束而消亡,
而且每一次方法调用都是独立。
4、作用域
(1)实例变量:通过对象就可以使用,本类中“this.xx“,没有歧义还可以省略this.”,其他类中“对象.xx”
(2)局部变量:出了作用域就不能使用
5、修饰符
(1)实例变量:public,protected,private,final,volatile,transient等
(2)局部变量:final
6、默认值
(1)实例变量:有默认值
(2)局部变量:没有,必须手动初始化。其中的形参比较特殊,靠实参给它初始化。
5.3 封装encapsulation
1、封装概述
随着我们系统越来越复杂,类会越来越多,那么类之间的访问边界必须把握好,面向对象的开发原则要遵循“高内聚、低耦合”,而“高内聚,低耦合”的体现之一:
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合:仅对外暴露少量的方法用于使用
隐藏对象内部的复杂性,只对外公开简单和可控的访问方式,从而提高系统的可扩展性、可维护性。通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
2、如何实现封装
实现封装就是指控制类或成员的可见范围。这就需要依赖访问控制修饰符,也称为权限修饰符来控制。
修饰符 | 本类 | 本包 | 其他包子类 | 其他包非子类 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
外部类:public和缺省
成员变量、成员方法、构造器、成员内部类:public,protected,缺省,private
3、成员变量/属性私有化问题
成员变量(field)私有化之后,提供标准的get/set方法,我们把这种成员变量也称为属性(property)。
get/set操作的就是事物的属性,哪怕它没有对应的成员变量。
1、成员变量封装的目的
- 隐藏类的实现细节
- 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。
- 便于修改,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问方式不变的话,外部根本感觉不到它的修改。例如:Java8->Java9,String从char[]转为byte[]内部实现,而对外的方法不变,我们使用者根本感觉不到它内部的修改。
2、成员变量的实现步骤
- 使用private修饰成员变量
1 | private 数据类型 变量名; |
代码如下:
1 | class Person { |
- 提供
getXxx
方法 /setXxx
方法,可以访问成员变量,代码如下:
1 | class Person{ |
- 测试
1 | public class PersonPackagingTest { |
4、实例方法使用当前对象的成员
在实例方法中还可以使用当前对象的其他成员。在Java中当前对象用this表示。
- this:在实例方法中,表示调用该方法的对象。
- 如果没有歧义,完全可以省略this。
1、使用this.
案例:矩形类
1 | public class ThisTest { |
2、省略this.
1 | public class ThisTest { |
练习:
1 | public class PackagingBoyGirl { |
5.4 构造器(构造方法)
new完对象后,所有成员变量都是默认值,如果我们需要赋别的值,需要挨个为它们再赋值,太麻烦了。Java给我们提供了构造器(Constructor),直接为当前对象的某个或所有成员变量直接赋值。
1、构造器的作用
在创建对象时为对象进行初始化工作的特殊方法.
2、构造器的语法格式
构造器又称为构造方法。
首字母大写
1 | 【修饰符】 class 类名{ |
例子:
1 | public class Constructor { |
JavaBean
JavaBean是一种Java语言写成的可重用组件。
所谓javaBean,是指符合如下标准的Java类:
类是公共的
有一个无参的公共的构造器
有属性,且有对应的get、set方法
用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
1 | public class JavaBean{ |
5.5 包(package)
1、包的作用
(1)可以避免类重名:有了包之后,类的全名称就变为:包.类名
(2)可以控制某些类型或成员的可见范围
如果某个类型或者成员的权限修饰缺省的话,那么就仅限于本包使用。
(3)分类组织管理众多的类
2、声明包
关键字package
1 | package 包名 |
注意:
(1)必须在源文件的代码首行
(2)一个源文件只能有一个声明包的package语句
包的命名规范和习惯:
(1)所有单词都小写,每一个单词之间使用.分割
(2)习惯用公司的域名倒置开头
例如:com.aierns.xxx;
3、跨包使用类
==注意:==只有public的类才能被跨包使用
(1)使用类型的全名称
例如:java.util.Scanner input = new java.util.Scanner(System.in);
(2)使用import 语句之后,代码中使用简名称
import语句告诉编译器到哪里去寻找类。
import语句的语法格式:
1 | import 包.类名; |
注意:
使用java.lang包下的类,不需要import语句,就直接可以使用简名称
import语句必须在package下面,class的上面
当使用两个不同包的同名类时,例如:java.util.Date和java.sql.Date。一个使用全名称,一个使用简名称
5.6 继承(inheritance)
1、Java中的继承
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。
多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类、超类(superclass)或者基类。
继承描述的是事物之间的所属关系,这种关系是:is-a
的关系。例如,图中猫属于动物,狗也属于动物。可见,父类更通用或更一般,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
2、继承的好处
- 提高代码的复用性。
- 提高代码的扩展性。
- 表示类与类之间的is-a关系
3、继承的语法格式
通过 extends
关键字,可以声明一个子类继承另外一个父类。
1 | 【修饰符】 class 父类 { |
父类
1 | /* |
子类
1 | /* |
测试类
1 | public class AnimalTest { |
4、继承的特点
- 子类会继承父类所有成员(构造器除外)
- Java只支持单继承,不支持多重继承(只能继承一个)
- Java支持多层继承(继承体系)
- 一个父类可以同时拥有多个子类
5、IDEA中如何查看继承关系
1、子类和父类是一种相对的概念。例如:B类对于A类来说是子类,但是对于C类来说是父类。
2、查看继承父系快捷键
选择A类名,按Ctrl+H就会显示A类的继承树。
例如:在类继承目录树中选中某个类,比如C类,按Ctrl+ Alt+U就会用图形化方式显示C类的继承祖宗。
6、权限修饰符限制问题
- 外部类要跨包使用必须是public,否则仅限于本包使用
- 成员的权限修饰符问题
(1)本包下使用:成员的权限修饰符可以是public、protected、缺省
(2)跨包使用时,如果类的权限修饰符缺省,成员权限修饰符>类的权限修饰符也没有意义
- 父类成员变量私有化
子类虽会继承父类私有(private)的成员变量,但子类不能对继承的私有成员变量直接进行访问,可通过继承的get/set方法进行访问。
7、继承注解
1 | * // 注解(annotation) : 是特殊的注释, 特殊在于可以被编译器和JVM识别. |
8、this与super的区别
1 | * this : 表示方法的调用者对象的引用 |
1 | * this.属性 和 this.方法() 都有追溯性, 优先从本类中开始查找, 如果本类没有, 向上追溯 |
9、子类构造器
子类中所有的构造器默认都会调用父类中空参数的构造器
子类构造器中的第1行, 默认就是super(); 作用就是调用父类构造器. 它必须在第一行, 达到了先调用父类构造器的效果.
子类构造器中的第1行一定要么是super(…)要么是 this(…)
super(…) 直接调用父类构造器,this(…) 间接调用父类构造器
结论 :
子类构造器中的第一行必须要有对父类构造器的调用. 第一行的作用是保证先父后子.
5.7 方法重写(Override)
父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于子类,该怎么办呢?我们可以进行方法重写 (Override)
1、方法重写
1 | public class Phone { |
1 | //smartphone:智能手机 |
1 | public class TestOverride { |
2、在子类中如何调用父类被重写的方法
1 | package com.atguigu.inherited.method; |
3、IDEA重写方法快捷键:Ctrl+O
1 | //smartphone:智能手机 |
@Override:写在方法上面,用来检测是不是满足重写方法的要求。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留,这样编译器可以帮助我们检查格式,另外也可以让阅读源代码的程序员清晰的知道这是一个重写的方法。
4、重写方法的要求
1.必须保证父子类之间重写方法的方法签名必须一致(返回值类型 方法名, 参数列表)。
注意:如果返回值类型是基本数据类型和void,那么必须是相同.如果返回值类型是引用型时,子类重写方法可以返回父类方法的返回值的子类类型.
2.子类方法的权限必须【大于等于】父类方法的权限修饰符。
注意:public > protected > 缺省 > private
父类私有方法不能重写
跨包的父类缺省的方法也不能重写
3.父类不能被static,final,private修饰
4.子类重写方法抛出的受检异常异常类型要小于等于父类方法抛出的受检异常.
5、方法的重载和方法的重写
方法的重载:方法名相同,形参列表不同。不看返回值类型。
方法的重写:必须保证父子类之间重写方法的方法签名必须一致(返回值类型 方法名, 参数列表)
(1)同一个类中
1 | public class TestOverload { |
(2)父子类中
1 | public class TestOverloadOverride { |
5.8 多态(polymorphism)
多态是继封装、继承之后,面向对象的第三大特性。
跑的动作,小猫、小狗和大象,跑起来是不一样的。可见,同一行为,对于不同的事物,可以体现出来的不同的形态。那么此时就会出现各种子类的类型。
子类对象有多种父类形态,把子类对象作为父类对象使用.
1、多态解决什么样的问题
案例:
(1)声明一个Dog类,包含public void eat()方法,输出“狗狗啃骨头”
(2)声明一个Cat类,包含public void eat()方法,输出“猫咪吃鱼仔”
(3)声明一个Person类,
- 包含宠物属性
- 包含领养宠物方法 public void adopt(宠物类型 pet)
- 包含喂宠物吃东西的方法 public void feed(),实现为调用宠物对象.eat()方法
1 | public class Dog { |
1 | public class Cat { |
1 | public class Person { |
2、多态的形式和体现
1. 多态引用
Java规定父类类型的变量可以接收子类类型的对象。
1 | 父类类型 变量名 = 子类对象; |
父类类型:指子类继承的父类类型,或者实现的父接口类型。
所以说继承是多态的前提
2. 多态引用的表现
表现:编译时类型与运行时类型不一致,编译时看“父类”,运行时看“子类”。
3. 多态引用的好处与弊端
弊端:编译时,只能调用父类声明的方法,不能调用子类扩展的方法;
好处:运行时,看“子类”,如果子类重写了方法,一定是执行子类重写的方法体;变量引用的子类对象不同,执行的方法就不同,实现动态绑定。代码编写更灵活、功能更强大,可维护性和扩展性更好了。
4. 多态演示
让Dog和Cat都继承Pet宠物类。
1 | public class Pet { |
1 | public class Cat extends Pet { |
1 | public class Dog extends Pet { |
1 | public class TestPet { |
3、应用多态解决问题
- 声明变量是父类类型,变量赋值子类对象
- 方法的形参是父类类型,调用方法的实参是子类对象
- 实例变量声明父类类型,实际存储的是子类对象
1 | public class OnePersonOnePet { |
1 | public class TestOnePersonOnePet { |
数组元素是父类类型,元素对象是子类对象
1
2
3
4
5
6
7
8
9
10
11
12
13public class OnePersonManyPets {
private Pet[] pets;//数组元素类型是父类类型,元素存储的是子类对象
public void adopt(Pet[] pets) {
this.pets = pets;
}
public void feed() {
for (int i = 0; i < pets.length; i++) {
pets[i].eat();//pets[i]实际引用的对象类型不同,执行的eat方法也不同
}
}
}
1 | public class TestPets { |
- 方法返回值类型声明为父类类型,实际返回的是子类对象
1 | public class PetShop { |
1 | public class TestPetShop { |
4、向上转型与向下转型
对象的类型转换是和基本数据类型的转换是不同的。基本数据类型是把数据值copy了一份,相当于有两种数据类型的值。而对象的赋值不会产生两个对象。
因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间,就会出现类型转换的现象。
但是,使用父类变量接收了子类对象之后,我们就不能调用子类拥有,而父类没有的方法了。这也是多态给我们带来的一点”小麻烦”。所以,想要调用子类特有的方法,必须做类型转换,使得编译通过。
向上转型:
左父右子 ,向上转型(自动完成).
- 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了
- 但是,运行时,仍然是对象本身的类型,所以执行的方法是子类重写的方法体。
- 此时,一定是安全的,而且也是自动完成的
向下转型:
左子右父,向下转型: (子类类型)父类变量
- 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了
- 但是,运行时,仍然是对象本身的类型
- 不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过isInstanceof关键字进行判断
1 | public class ClassCastTest { |
5、instanceof关键字
为了避免ClassCastException的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验,只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。
1 | 变量/匿名对象 instanceof 数据类型 |
那么,哪些instanceof判断会返回true呢?
- 变量/匿名对象的编译时类型 与 instanceof后面数据类型是直系亲属关系才可以比较
- 变量/匿名对象的运行时类型<= instanceof后面数据类型,才为true
1 | public class TestInstanceof { |
6、虚方法
指在编译阶段和类加载阶段都不能确定方法的调用入口地址,在运行阶段才能确定的方法,即可能被重写的方法。
当我们通过“对象xx.方法”的形式调用一个虚方法时,要如何确定它具体执行哪个方法呢?
(1)静态分派:先看这个对象xx的编译时类型,在这个对象的编译时类型中找到能匹配的方法
1 | 匹配的原则:看实参的编译时类型与方法形参的类型的匹配程度 |
(2)动态绑定:再看这个对象xx的运行时类型,如果这个对象xx的运行时类重写了刚刚找到的那个匹配的方法,那么执行重写的,否则仍然执行刚才编译时类型中的那个匹配的方法
1 | class MyClass{ |
1 | public class TestVirtualMethod { |
7、成员变量没有多态一说
1 | //成员变量没有多态一说 |
8、什么具有多态性
只有非静态方法具有多态性,其他的都不具有.
5.9 权限访问修饰符
修饰符 | 作用对象 |
---|---|
private | 属性\方法 |
default | 属性\方法\类\接口 |
protected | 属性\方法 |
public | 属性\方法\类\接口 |
即private和protected不能修饰类\接口
范围 | private | default | protected | public |
---|---|---|---|---|
同包同类 | √ | √ | √ | √ |
同包不同类 | √ | √ | √ | |
不同包的子类 | √ | √ | ||
不同包的非子类 | √ |
protected主要用于继承,如果是不同包,需要是父类的子类才可以访问到protected修饰的属性或方法
5.10 实例初始化
- 子类继承父类时,不会继承父类的构造器。只能通过super()或super(实参列表)的方式调用父类的构造器。
- super();:子类构造器中一定会调用父类的构造器,默认调用父类的无参构造,super();可以省略。
- super(实参列表);:如果父类没有无参构造或者有无参构造但是子类就是想要调用父类的有参构造,则必须使用super(实参列表);的语句。
- super()和super(实参列表)都只能出现在子类构造器的首行
初始化相关代码
(1) 实例变量直接初始化
(2) 非静态代码块
(3) 构造器
5.11 关键字和API
1. this和super关键字
this: 当前方法的调用者对象
super: 引用父类声明的成员
1. this和super的使用格式
- this
- this.成员变量:表示当前对象的某个成员变量,而不是局部变量
- this.成员方法:表示当前对象的某个成员方法,完全可以省略this.
- this()或this(实参列表):调用另一个构造器协助当前对象的实例化,只能在构造器首行,只会找本类的构造器,找不到就报错
- super
- super.成员变量:表示当前对象的某个成员变量,该成员变量在父类中声明的
- super.成员方法:表示当前对象的某个成员方法,该成员方法在父类中声明的
- super()或super(实参列表):调用父类的构造器协助当前对象的实例化,只能在构造器首行,只会找直接父类的对应构造器,找不到就报错
2. 避免子类和父类声明重名的成员变量
因为,子类会继承父类所有的成员变量,所以:
- 如果重名的成员变量表示相同的意义,就无需重复声明
- 如果重名的成员变量表示不同的意义,会引起歧义
3. 解决成员变量重名问题
- 如果实例变量与局部变量重名,可以在实例变量前面加this.进行区别
- 如果子类实例变量和父类实例变量重名,并且父类的该实例变量在子类仍然可见,在子类中要访问父类声明的实例变量需要在父类实例变量前加super.,否则默认访问的是子类自己声明的实例变量(==就近原则==)
- 如果父子类实例变量没有重名,只要权限修饰符允许,在子类中完全可以直接访问父类中声明的实例变量,也可以用this.实例访问,也可以用super.实例变量访问
变量前面没有super.和this.
- 在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的==局部变量==,
- 如果不是局部变量,先从当前执行代码的==本类去找成员变量==
- 如果从当前执行代码的本类中没有找到,会往上找==父类声明的成员变量==(权限修饰符允许在子类中访问的)
变量前面有this.
- 通过this找成员变量时,先从当前执行代码的==本类去找成员变量==
- 如果从当前执行代码的本类中没有找到,会往上找==父类声明的成员变量(==权限修饰符允许在子类中访问的)
变量前面super.
- 通过super找成员变量,直接从当前执行代码的直接父类去找成员变量(权限修饰符允许在子类中访问的)
- 如果直接父类没有,就去父类的父类中找(权限修饰符允许在子类中访问的)
4. 解决成员方法重写后调用问题
- 如果子类没有重写父类的方法,只有权限修饰符运行,在子类中完全可以直接调用父类的方法;
- 如果子类重写了父类的方法,在子类中需要通过super.才能调用父类被重写的方法,否则默认调用的子类重写的方法
2. native关键字
native只能修饰方法,表示这个方法的方法体代码不是用Java语言实现的,而是由C/C++语言编写的。
3. final关键字
1. final修饰类
被final修饰的是终极类,不能被继承,没有子类
2. final修饰方法
表示这个方法不能被子类重写
3. final修饰变量
final修饰某个变量(成员变量或局部变量),表示它的值就不能被修改,即常量,常量名建议使用大写字母。
5.12 Object根父类
类 java.lang.Object
是类层次结构的根类,即所有类的父类。每个类都使用 Object
作为超类。
1. Object类的其中5个方法
- toString()
方法签名 : public String toString();
直接使用System.out.println(对象),默认会自动调用
- getClass()
方法签名 : public final Class<?> getClass();
- equals()
方法签名 : public boolean equals(Object obj) : 用于判断当前对象this与指定对象obj是否“相等”
自反性: x.equals(x)返回true
传递性: x.equals(y)为true, y.equals(z)为true,然后x.equals(z)也应该为true
一致性: 只要参与equals比较的属性值没有修改,那么无论何时调用结果应该一致
对称性: x.equals(y)与y.equals(x)结果应该一样
非空对象与null的equals一定是false
- hashCode()
public int hashCode() : 返回每个对象的hash值
==如果重写equals,那么通常会一起重写hashCode()方法==
①如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的;
②如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false;
③如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是false;
- finalize()
protected void finalize():用于最终清理内存的方法
每一个对象的finalize()只会被调用一次,哪怕它多次被标记为垃圾对象。
面试题:对finalize()的理解?
当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法,不是由程序员手动调用。
这个方法与C语言的析构函数不同,C语言的析构函数被调用,那么对象一定被销毁,内存被回收,而finalize方法的调用不一定会销毁当前对象,因为可能在finalize()中出现了让当前对象“复活”的代码
每一个对象的finalize方法只会被调用一次。
子类可以选择重写,一般用于彻底释放一些资源对象,而且这些资源对象往往时通过C/C++等代码申请的资源内存
5.13 对象关联
1 | * 对象关联: 一个对象拥有另一个对象,为了让一个对象更方便的使用另一个对象 |
5.14 静态Static
1 | static可以修饰属性,方法,代码块,内部类 |
5.15 抽象类
1 | 抽象类和抽象方法(abstract关键字) |
1 | 1.子类继承父类中的类变量和静态方法了吗?继承到了 |
5.16 接口
1 | 具体的类:对事物的描述 |