一、面向对象简述
Ⅰ、简述
面向对象
是软件开发中的一类编程风格、开发范式
。
Ⅱ、两大核心
①
类(Class)
具有相同特征的事物的抽象描述,是抽象的
、概念上的定义。
②对象(Object)
实际存在的该类事物的每个个体
,是具体的
,因而也称为实例(instance)
。
Ⅲ、内容主线
① Java类及类的成员:
(重点)属性
、方法
、构造器
;(熟悉)代码块、内部类
② 面向对象的特征:封装、继承、多态、(抽象)
③关键字
的使用:this
、super
、package
、import
、static
、final
、interface
、abstract
等
Ⅳ、POP VS OOP
开发范式 | 描述 | 区别 | 联系 | |||
面向过程编程(POP) | 以函数为组织单位。 是一种“执行者思维”,适合解决简单问题。扩展能力差、后期维护难度较大。 | 如何开车 | 我们千万不要把面向过程和面向对象对立起来。他们是相辅相成的。面向对象离不开面向过程! | |||
面向对象编程(OOP) | 以类为组织单位。 每种事物都具备自己的属性和行为/功能。是一种“设计者思维”,适合解决复杂问题。代码扩展性强、可维护性高。 | 如何造车 |
Ⅴ、面向对象完成具体功能操作的三步流程【重要】
Ⅰ、
创建类
,并设计类的内部成员(属性、方法
)
Ⅱ、创建类的对象
。比如:Phone p1 = new Phone();
Ⅲ、通过对象
,调用
其内部声明的属性
或方法
,完成相关的功能
二、面向对象核心
Ⅰ、类
1、类的定义
具有
相同特征
的事物的抽象描述,是抽象的
、概念上的定义。
2、面向对象程序设计
面向对象程序设计的重点是
类的设计
,类的设计,其实就是类的成员的设计
,是一组相关属性
和行为
的集合,这也是类最基本的两个成员
。
3、类的成员之一属性(成员变量)
①、属性分类
分类标准 | 描述 | ||
按 数据类型 | 基本数据类型 (8种) | ||
引用数据类型 [数组、类、接口、枚举、注解、记录] | |||
按 声明位置 | 成员变量(或属性) | static修饰 | 静态变量、类变量 |
非static修饰 | 非静态变量、实例变量 | ||
局部变量 [方法内、方法形参、构造器内、构造器形参、代码块内等] |
②、成员变量 VS局部变量
成员变量 vs 局部变量 | |||
相 同 点 | 变量声明的格式相同:数据类型 变量名 = 变量值 | ||
变量都有其有效的作用域,出了作用域,就失效了 | |||
变量必须 先声明,后赋值,再使用。 | |||
不 同 点 | ① 类中声明的位置的不同: | 属性:声明在 类内,方法外 的变量 | |
局部变量:声明在 方法、构造器内部 的变量 | |||
② 在内存中 分配的位置不同 (难) : | 属性:随着对象的创建,存储在堆空间中。 | ||
局部变量:存储在栈空间中 | |||
③ 生命周期: | 属性:随着对象的创建而创建,随着对象的消亡而消亡。 | ||
局部变量:声明方法、构造器内部的变量 | |||
④ 作用域: | 属性:在整个类的内部都是有效的 | ||
局部变量:仅限于声明此局部变量所在的方法(或构造器、代码块)中 | |||
⑤ 是否可以有权限修饰符进行修饰:(难) 都有哪些权限修饰符: public、 protected、 缺省、 private。 (用于表明所修饰的结构可调用的范围的大小) | 属性:是可以使用权限修饰符进行修饰的。 | ||
局部变量:不能使用任何权限修饰符进行修饰的。 | |||
⑥ 是否有默认值:(重点) | 属性:都有默认初始化值。 意味着:如果没有给属性进行显式初始化赋值,则会有默认初始化值。 | 注意:对于方法的形参而言,在调用方法时,给此形参赋值即可。 | |
局部变量:都没有默认初始化值。 意味着,在使用局部变量之前,必须要显式的赋值,否则报错。 |
4、类的成员之二行为(成员方法)
①、简述
成员方法(简述) | |
简述 | 方法是 类或对象行为特征的抽象 ,用来完成某个功能操作。 |
优势 | 实现代码重用,减少冗余,简化代码 |
特点 | ① Java里的方法 不能独立存在 ,所有的方法必须定义在类里。 |
② Java中的方法 不调用,不执行。每调用一次,就执行一次。 | |
③ 方法内可以调用本类中的(其它)方法或属性 | |
④ 方法内不能定义方法。 |
②、声明格式
权限修饰符
[其它修饰符,如:static、final、abstract]返回值类型
方法名
(形参列表)[throws 异常类型]
{
//方法头
//方法体
}
注:[]中的内部不是必须的
成员方法(声明格式) | ||||
权限修饰符 | private | 本类内部 | ||
缺省 | 本类内部 、 本包内 | |||
protected | 本类内部 、 本包内、其他包的子类 | |||
public | 本类内部 、 本包内、其他包的子类、其他包的分子类 | |||
返回值类型 | 描述当调用完此方法时,是否需要返回一个结果。 我们在声明方法时要不要提供返回值类型呢? ① 根据方法具体实现的功能来决定。换句话说,具体问题具体分析 ② 根据题目要求 | 无返回值类型:使用void表示即可。 比如:System.out.println(x)的println(x)方法、Arrays的sort() | ||
有具体的返回值类型:需要指明返回的数据的类型。 可以是基本数据类型,也可以引用数据类型 需要在方法内部配合使用"return + 返回值类型的变量或常量" | ||||
方法名 | 属于标识符。需要满足标识符的规定和规范。“见名知意”。 | |||
形参列表 | 形参,属于局部变量,且可以声明多个。 我们在声明方法时,是否需要形参列表呢? ① 根据方法具体实现的功能来决定。换句话说,具体问题具体分析 ② 根据题目要求 | 格式 | (形参类型1 形参1,形参类型2 形参2,.......) | |
分类 | 无形参列表 | 不能省略一对()。 比如:Math.random()、new Scanner(System.in).nextInt() | ||
有形参列表 | 根据方法调用时,需要的不确定的变量的类型和个数,确定形参的类型和个数。 比如:Arrays类中的binarySearch()方法、sort()方法、equals()方法 | |||
方法体 | 当我们调用一个方法时,真正执行的代码。体现了此方法的功能。 |
③、方法详述
(再谈)方法 | ||||
方法重载 | 定义 | 在同一个类中,允许存在一个以上的同名方法,只要它们的 参数列表不同 即可。满足这样特征的多个方法,彼此之间构成方法的重载。 | ||
特点 | 在一个类中,允许存在多个相同名字的方法,只要他们的 形参列表不同 即可。 | |||
方法的重载与形参的名、权限修饰符、返回值类型 都没有关系。 | ||||
在同一个类中 不允许定义两个相同的方法。 | ||||
判断两个方法是否相同: 方法名相同且形参列表相同。 [ 形参列表相同指的是 参数个数和类型都相同,与形参名没关系 ] | ||||
编译器是如何确定调用的某个具体的方法呢? [ 先通过方法名确定了一波重载的方法,进而通过不同的形参列表,确定具体的某一个方法。] | ||||
总结 | “两同一不同”: 两 同:同一个类、相同的方法名 一不同:参数列表不同。① 参数个数不同 ② 参数类型不同 | |||
可变个数形参的方法 | 使用场景 | 在调用方法时,可能会出现方法形参的类型是确定的,但是参数的个数不确定。此时,我们就可以使用可变个数形参的方法 | ||
格式 | (参数类型 ... 参数名) | |||
特点 参考此表下面【示例】代码 | ① 可变个数形参的方法在调用时,针对于可变的形参赋的实参的个数可以为:0个、1个或多个 | |||
② 可变个数形参的方法与同一个类中,同名的多个方法之间可以构成重载 | ||||
③ 特例:可变个数形参的方法与同一个类中方法名相同,且与可变个数形参的类型相同的数组参数不构成重载。 | ||||
④ 可变个数的形参必须声明在形参列表的最后 | ||||
⑤ 可变个数的形参最多在一个方法的形参列表中出现一次(如果可以就与④中的声明矛盾了) | ||||
方法的值传递机制 | ① 对于方法内声明的 局部变量 来说:如果出现赋值操作 | 如果是 基本数据类型 的变量,则将此变量保存的 数据值 传递出去。 | ||
如果是 引用数据类型 的变量,则将此变量保存的 地址值 传递出去。 | ||||
② 方法的参数的传递机制: 值传递机制 | 形参 | 在定义方法时,方法名后面括号()中声明的变量称为形式参数,简称形参。 | ||
实参 | 在调用方法时,方法名后面括号()中的使用的值/变量/表达式称为实际参数,简称实参。 | |||
规则: 实参给形参赋值的过程 | 如果形参是 基本数据类型 的变量,则将实参保存的 数据值 赋给形参。 | |||
如果形参是 引用数据类型的变量,则将实参保存的 地址值 赋给形参。 | ||||
③ Java中的参数传递机制是什么? | 值传递(不是引用传递) | |||
递归方法 | 简述 | 方法自己调用自己的现象就称为递归 | ||
分类 | 直接递归: 方法自身调用自己 | |||
间接递归: 可以理解为A()方法调用B()方法,B()方法调用C()方法,C()方法调用A()方法 | ||||
使用说明 | 递归方法包含了一种 `隐式的循环`。 | |||
递归方法会`重复执行`某段代码,但这种重复执行无须循环控制。 | ||||
递归一定要向 `已知方向`递归,否则这种递归就变成了无穷递归,停不下来,类似于 `死循环`,最终发生 `栈内存溢出``。 | ||||
注意事项 | ① 递归调用会占用大量的系统堆栈,内存耗用多,在递归调用层次多时速度要比循环`慢的多`,所以在使用递归时要慎重。 | |||
② 在要求高性能的情况下尽量避免使用递归,递归调用既花时间又`耗内存`。考虑使用循环迭代 |
方法的值传递机制
可变个数形参的方法 特点
① ② ③ ④ ⑤【示例】代码说明参考
特点 ①
public class ArgsTest {
public static void main(String[] args) {
ArgsTest test = new ArgsTest();
test.print();
test.print(1);
test.print(1,2);
test.print(new int[]{1, 2, 3});
}
public void print(int ... nums){
System.out.println("1111");
}
}
特点 ②
public void print(int ... nums){
System.out.println("1111");
}
public void print(int i){
System.out.println("2222");
}
public void print(int i,int j){
System.out.println("3333");
}
特点 ③
下述二者等同,不可同时出现,可参考 ① 中方法调用 test.print(new int[]{1, 2, 3});
public void print(int ... nums){//jdk5.0之后 int ... nums
System.out.println("1111");
}
// public void print(int[] nums){//jdk5.0之前 int[] nums
// System.out.println("222");
// }
特点 ④
public void print(int i,int ... nums){//正确
System.out.println("3333");
}
public void print(int ... nums,String i){//错误
System.out.println("3333");
}
public void print(int ... nums,int i){//错误
System.out.println("3333");
}
特点 ⑤
public void print(int ... nums,int ... i){//错误
System.out.println("3333");
}
public void print(int ... nums,boolean ... i){//错误
System.out.println("3333");
}
5、类的成员之三构造器(构造方法)
构造器(构造方法) | ||||
简述 | 构造器是Java类中的一个方法 | |||
作用 | 在new对象的时候为实例变量赋值,即初始化成员变量 | |||
语法格式 | [修饰符] class 类名{ [修饰符] 构造器名 (){ //无参构造 // 实例初始化代码 } [修饰符] 构造器名(参数列表){ //有参构造 // 实例初始化代码 } } | |||
特点 | 构造器名与它所在的类名必须相同 | |||
它没有返回值,所以不需要返回值类型,也不需要void | ||||
构造器的修饰符只能是 权限修饰符,不能被其他任何修饰。 比如,不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值。 |
6、类的成员之四代码块
代码块 | ||||
作用 | 用来初始化类或对象的信息(即初始化类或对象的成员变量)。只能使用static进行修饰。 | |||
分类 | 静态代码块 [ 使用 static 修饰 ] | 非静态代码块 [ 没有使用 static 修饰 ] | ||
作用 | 用来 初始化类的信息 | 用来 初始化对象的信息 | ||
特点 | 随着 类的加载 而执行 | 随着 对象的创建 而执行 | ||
由于类的加载只会执行一次,进而静态代码块的执行,也 只会执行一次 | 每创建当前类的一个实例,就会执行一次 | |||
内部可以声明变量、调用属性或方法、编写输出语句等操作。 | 内部可以声明变量、调用属性或方法、编写输出语句等操作。 | |||
如果声明有多个静态代码块,则按照声明的先后顺序执行 | 如果声明有多个非静态代码块,则按照声明的先后顺序执行 | |||
静态代码块内部 只能调用 静态的结构(即静态的属性、方法),不能调用非静态的结构(即非静态的属性、方法) | 非静态代码块内部 可以调用 静态的结构(即静态的属性、方法), 也可以调用 非静态的结构(即非静态的属性、方法) | |||
静态代码块的执行要 先于 非静态代码块的执行 |
7、类的成员之五内部类
简介
将一个类A定义在另一个类B里面,里面的那个类A就称为
内部类(InnerClass)
,类B则称为外部类(OuterClass)
。
为何需要内部类
具体来说,当一个
事物A
的内部,还有一个部分需要一个完整的结构B
进行描述,而这个内部的完整的结构B
又只为外部事物A
提供服务,不在其他地方单独使用,那么整个内部的完整结构B
最好使用内部类
。
总的来说,遵循高内聚、低耦合
的面向对象开发原则。
举例说明
Thread
类内部声明了State类
,表示线程的生命周期
HashMap
类中声明了Node类
,表示封装的key和value
内部类的分类
成员内部类
:直接声明在外部类的里面
。
- 使用
static
修饰的:静态
的成员内部类- 不 使用
static
修饰的:非静态
的成员内部类
局部内部类
:声明在方法内、构造器内、代码块内
的内部类
匿名
的局部内部类- 非
匿名
的局部内部类
成员内部类语法格式:
[
修饰符
]class
外部类
{
[其他修饰符
] [static
]class
内部类
{
}
}
非匿名局部内部类语法格式:
[
修饰符
]class 外部类
{
[修饰符
]返回值类型
方法名
(形参列表
){
[final/abstract
]class
内部类
{
}
}
}
匿名局部内部类语法格式:
new 父类([实参列表]){
重写方法…
}
new 父接口(){
重写方法…
}
内部类的理解
成员内部类
: 直接声明在外部类的里面
。从
类的角度
看
- 内部可以声明
属性
、方法
、构造器
、代码块
、内部类
等结构- 此
内部类
可以声明父类
,可以实现接口
- 可以使用
final
修饰- 可以使用
abstract
修饰从
外部类的成员的
角度看
- 在
内部可以调用外部类
的结构。比如:属性、方法
等- 除了使用
public
、缺省权限修饰
之外,还可以使用private、protected
修饰- 可以使用
static
修饰
成员内部类
:
- 和成员内部类不同的是,它
前面不能有权限修饰符
等- 局部内部类如同局部变量一样,
有作用域
- 局部内部类中
是否能访问外部类的非静态的成员
,取决于 所在的方法
8、类中属性赋值过程
赋值位置:
***********************
①默认初始化
②显式初始化
或 ⑤代码块中初始化
③构造器中初始化
④ 有了对象以后,通过"对象.属性
"或"对象.方法
"的方法进行赋值
***********************
执行的先后顺序:① - ②/⑤ - ③ - ④
技巧:由父及子,静态先行
;
注意:(静态)变量
和(静态)代码块
的也是有执行顺序的,与代码书写的顺序
一致。在(静态)代码块
中可以使用(静态)变量
,但是被使用的(静态)变量
必须在(静态)代码块
前面声明。
1.父类
静态变量和静态代码块(先声明的先执行
);
2.子类
静态变量和静态代码块(先声明的先执行
);
3.父类
的变量和代码块(先声明的先执行
);
4.父类
的构造函数;
5.子类
的变量和代码块(先声明的先执行
);
6.子类
的构造函数。
关于字节码文件中的的简单说明:(通过插件
jclasslib
bytecode
viewer
查看)
init
方法在字节码文件中可以看到。每个方法都对应着一个类的构造器。(类中声明了几个构造器就会有几个init
)- 编写的代码中的构造器在编译以后就会以方法的方式呈现
init
方法内部的代码包含了实例变量的显示赋值
、代码块中的赋值
和构造器中的代码
。init
方法用来初始化当前创建的对象的信息的。
给
实例变量赋值
的位置
很多,开发中如何选?
显示赋值
:比较适合于每个对象的属性值相同的场景
构造器中赋值
:比较适合于每个对象的属性值不相同的场景
9、JavaBean
所谓
JavaBean
,是一种Java语言写成的可重用组件
,是指符合如下标准的Java类:
1、类是公共的
2、有一个无参的公共的构造器
3、有属性
,且有对应的get、set方法
10、UML类图
UML(Unified Modeling Language,统一建模语言),用来描述 软件模型 和 架构 的图形化语言
。
常用的UML工具软件有PowerDesinger
、Rose
和Enterprise Architect
。
UML工具软件不仅可以绘制软件开发中所需的各种图表,还可以生成对应的源代码。
在软件开发中,使用 UML类图 可以更加直观地描述类内部结构(类的属性和操作)以及类之间的关系(如关联、依赖、聚合等)
。
+
表示public
类型,
-
表示private
类型,
#
表示protected
类型
方法的写法:
方法的类型(+、-) 方法名(参数名: 参数类型):返回值类型
斜体表示抽象方法或类
。
11、类的定义格式及实例化格式
类的定义使用关键字:
class
。格式如下:
[修饰符] class 类名{
属性声明;
方法声明;
}
等价描述:
(类的实例化 <=> 创建类的对象 <=> 创建类的实例
)
格式:
类的类型 对象名 = 通过new创建的对象实体
语法格式 | [修饰符] class 类名{{ 属性声明; 方法声明; } | |||
类的实例化格式 | 等价描述: (类的实例化 <=> 创建类的对象 <=> 创建类的实例) 类的类型 对象名 = 通过new创建的对象实体 |
Ⅱ、对象
简述、创建方式、类的实例化剖析、内存解析
对象 | ||||
定义 | 实际存在的某类事物的 `每个个体`,是`具体的`,因而也称为 `实例(instance)`。 | |||
如何创建 | ① new 对象 | 方式1:给创建的对象命名 类名 对象名 = new 类名(); //把创建的对象用一个引用数据类型的变量保存起来,这样就可以反复使用这个对象了 | ||
方式2: new 类名() //也称为匿名对象 (anonymous object) | ||||
不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做 匿名对象。 如:new Person().shout(); 使用情况: 如果一个对象只需要进行一次方法调用,那么就可以使用匿名对象。 我们经常将匿名对象作为实参传递给一个方法调用。 | ||||
② Object基类的clone( )方法克隆对象 | ||||
类的实例化剖析、内存解析 | 对象在内存中的分配涉及到的内存结构(理论) | 栈(stack) | 指虚拟机栈,用于存储方法内定义的变量,即局部变量。 局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。 | |
堆(heap) | 此内存区域的唯一目的就是存放对象实例,所有的对象实例以及数组都要在堆上分配。即:new 出来的结构(比如:数组实体、对象的实体)。包括对象中的属性。 | |||
方法区(method area) | 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。即:存放类的模板。比如:Person类的模板 | |||
类中对象的内存解析 | 创建类的一个对象 | 参考《01-创建类的一个对象.png》 | ||
创建类的多个对象 | 参考《02-创建类的多个对象1.png》 参考《02-创建类的多个对象2.png》 | |||
对象调用方法的过程 | 参考《03-对象调用方法的过程.png》 | |||
对象数组 | 参考《04-对象数组内存解析.png》 |
01-创建类的一个对象,属性赋值
02-1创建类的多个对象,属性赋值
02-2创建类的多个对象,属性赋值
03-对象调用方法的过程
04-对象数组内存解析
三、面向对象特征
Ⅰ、封装性
简述、如何实现数据封装、封装性体现
封装性 | ||||
定义 | 所谓封装,就是把客观事物封装成抽象概念的类,并且类可以把自己的数据 和方法只向可信的类或者对象开放 ,向 没必要开放的类或者对象 隐藏信息。 | |||
高内聚 低耦合 | 面向对象的开发原则要遵循“ 高内聚、低耦合 ”,封装是这种原则的体现 内聚: 指一个模块内各个元素彼此结合的紧密程度,意味着重用和独立 耦合: 指一个软件结构内不同模块之间互连程度的度量,意味着多米诺效应牵一发动全身。 | |||
如何实现数据封装(权限修饰符) | Java规定了4种权限修饰,分别是:private、缺省、protected、public 我们可以使用4种权限修饰来修饰类及类的内部成员。当这些成员被调用时,体现可见性的大小。 | |||
1、4种权限具体使用: > 类: 只能使用public、缺省修饰 > 类的内部成员(成员变量、成员方法、构造器、成员内部类): 可以使用4种权限修饰进行修饰。 2、开发中4种权限使用频率的情况: > 比较高: public、private > 比较低: 缺省、protected | ||||
1、开发中,一般成员实例变量都习惯使用private修饰,再提供相应的public权限的get/set方法访问。 2、对于final的实例变量,不提供set()方法。 3、对于static final的成员变量,习惯上使用public修饰。 | ||||
封装性的体现 (私有化属性或方法) | ① 私有化(private)类的属性,提供公共(public)的get和set方法,对此属性进行获取或修改 ② 将类中不需要对外暴露的方法,设置为private ③ 单例模式中构造器private的了,避免在类的外部创建实例。 |
Ⅱ、继承性
概述、语法格式、优点、特点、方法重写介绍
继承性 | ||||
概述 | 在Java中,继承是面向对象编程的一个基本特性。它允许我们定义一个新类,它从另一个已经存在的类继承其属性和方法。被继承的类称为父类或超类,新定义的类称为子类。 B类继承A类: 类A: 父类、superClass、超类、基类 类B: 子类、subClass、派生类 | |||
语法格式 | class A{ //属性、方法 } class B extends A{ } | |||
优点 | 继承的出现减少了代码冗余,提高了代码的复用性 | |||
继承的出现,更有利于功能的扩展 | ||||
继承的出现让类与类之间产生了`is-a`的关系,为多态的使用提供了前提 继承描述事物之间的所属关系,这种关系是:`is-a` 的关系。 可见,父类更通用、更一般,子类更具体。 注意:不要仅为了获取其他类中某个功能而去继承! | ||||
特点 | ① 子类会继承父类所有的实例变量和实例方法 | |||
② 由于封装性的影响,子类不能直接访问父类中私有的(private)的成员变量和方法 | ||||
③ 子类在继承父类以后,还可以扩展自己特有的功能(体现:增加特有的属性、方法) "不要为了继承而继承。在继承之前,判断一下是否有 is-a 的关系" | ||||
④ 默认的父类Java中声明的类,如果没有显式的声明其父类时,则默认继承于java.lang.Object | ||||
⑤ Java的单继承性 Ⅰ、Java支持 多层继承(继承体系)。 概念:直接父类、间接父类 Ⅱ、Java中的子父类的概念是 相对的 Ⅲ、Java中一个父类可以声明多个子类。反之,一个子类只能有一个父类 (Java的单继承性) | ||||
方法重写 | 概述 | 子类对父类继承过来的方法进行的覆盖、覆写的操作,就称为方法的重写。 父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于自己当前的类,该怎么办呢? 子类可以对从父类中继承来的方法进行改造,我们称为 方法的重写(override、overwrite),也称为方法的重置、覆盖。 在程序执行时,子类的方法将覆盖父类的方法。 @Override使用说明: 写在方法上面,用来检测是不是满足重写方法的要求。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留,这样编译器可以帮助我们检查格式,另外也可以让阅读源代码的程序员清晰的知道这是一个重写的方法。 | ||
方法声明的格式: | 权限修饰符 返回值类型 方法名 (形参列表) [throws 异常类型] { //方法体 } | |||
重写遵循规则 | ① 子类重写的方法必须和父类被重写方法的 方法名和形参列表相同。 | |||
② 子类重写的方法的权限修饰符 不能小于父类被重写的方法的权限修饰符 注意: Ⅰ、 父类私有方法不能重写,即子类不能重写父类中声明为private权限修饰的方法。 Ⅱ、 跨包的父类缺省的方法也不能重写 | ||||
③ 关于返回值类型: Ⅰ、 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型必须是void。 Ⅱ、父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须与被重写的方法的返回值类型相同。 Ⅲ、父类被重写的方法的返回值类型是引用数据类型(比如类),则子类重写的方法的返回值类型可以与被重写的方法的返回值 类型相同 或 是被重写的方法的返回值类型的子类 | ||||
④ 子类重写的方法抛出的异常:类型可以与父类被重写的方法抛出的 异常类型相同,或是父类被重写的方法抛出的 异常类型的子类。 | ||||
补充说明:方法体:没有要求。 但是子类重写的方法的方法体必然与父类被重写方法的方法体不同。 子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。 |
Ⅲ、多态性
概述、优点、缺点、向上转型 VS 向下转型
多态性 | ||||
概述 | ① 对象的多态性:父类的引用指向子类的对象 | |||
② 格式 父类类型 变量名 = 子类对象; | ||||
③ 多态性的应用:虚拟方法调用 (简单理解就是表面看是调用父类的方法,实际执行的是子类重写父类的方法) | ||||
④ 在多态的场景下,调用方法时: 编译时,认为方法是左边声明的父类的类型的方法(即被重写的方法) 执行式,实际执行的是子类重写父类的方法。 简称为:编译看左边,运行看右边。 | ||||
⑤ 多态的适用性:适用于方法,不适用于属性。(多态应用只关注方法) | ||||
优点 | 极大的减少了代码的冗余,不需要定义多个重载的方法。 开发中: 使用父类做方法的形参,是多态使用最多的场合。 即使增加了新的子类,方法也无需改变,提高了扩展性,符合开闭原则 | |||
缺点 | 在多态的场景下,我们创建了子类的对象,也加载了子类特有的属性和方法。但是由于声明为父类的引用,导致我们没有办法直接调用子类特有的属性和方法。 | |||
向上转型 VS 向下转型 | 向上转型(即多态) | 当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型(即多态) 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了; 但是运行时,仍然是对象本身的类型,所以执行的方法是子类重写的方法体。 此时,一定是安全的,而且也是自动完成的 | ||
向下转型 | 当左边的变量的类型(子类)<右边对象/变量的编译时类型(父类),我们就称为向下转型 此时,编译时 按照左边变量的类型处理,就可以调用子类特有的变量和方法了;但是运行时,仍然是对象本身的类型 不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过 isInstanceof 关键字进行判断 | |||
向下转型注意点 ( instanceof 的使用 ): 1. 建议在向下转型之前,使用instanceof进行判断,避免出现类型转换异常 2. 格式: a instanceOf A : 判断对象a是否是类A的实例。 3. 如果a instanceOf A 返回true,则: a instanceOf superA 返回也是true。其中,A 是superA的子类。 |
举例:
Person p2 = new Man();
Person int id = 1001; public void eat(){System.out.println("父类方法")}
Man int id = 1002; public void eat(){System.out.println("子类方法")}
测试 System.out.println(p2.id); //1001
测试 System.out.println(p2.eat()); //子类方法
上述代码:
属性执行父类的:1001
方法执行子类的:子类方法
举例:
class Account{
public void withdraw(){} //取钱
}
class CheckAccount extends Account{ //信用卡
//存在方法的重写
public void withdraw(){} //取钱
}
class SavingAccount extends Account{ //储蓄卡
//存在方法的重写
}
class Customer{
Account account;
public void setAccount(Account account){
this.account = account;
}
public Account getAccount(){
return accout;
}
}
class CustomerTest{
main(){
Customer cust = new Customer();
cust.setAccount(new CheckAccount());//多态体现
cust.getAccount().withdraw();
}
}
实例:
父类:Person
子类:Man 独有方法 Smoke()
子类:Woman
Person p1 = new Man();
p1.Smoke();//不能调用,因为此方法是子类 Man 中独有的方法,若调用,必须向下转型
Man m1 = (Man) p1;//向下转型
m1.Smoke();//调用成功
如果a instanceOf A 返回true,则:a instanceOf superA 返回也是true。其中,A 是superA的子类。
实例:
父类:Person
子类:Man 独有方法 Smoke()
子类:Woman
Person p1 = new Man();
Person p2 = new Woman();
p1.Smoke();//不能调用,因为此方法是子类 Man 中独有的方法,若调用,必须向下转型
Man m1 = (Man) p1;//向下转型
m1.Smoke();//调用成功
//为防止转换过程中出现类型转换异常,用 instanceof 判断一下
if(p2 instanceof Man){
Man m2 = (Man)p2;
m2.Smoke();
}
四、Object 介绍
Object | ||||
说明 | 明确:java.lang.Object | |||
任何一个Java类(除Object类)都直接或间接的继承于Object类 | ||||
Object类称为java类的 根父类 | ||||
Object类中声明的结构 (属性、方法等)具有 通用性。 | Object类中没有声明属性 | |||
Object类提供了一个空参的构造器 | ||||
重点关注:Object类中声明的方法 | ||||
常用方法(11个) | 重点方法:equals() \ toString() | |||
了解方法: clone() \ finalize() | ||||
其他方法:getClass() \ hashCode() \ notify() \ notifyAll() \ wait() \ wait(xx) \ wait(xx,yy) | ||||
equals() | 适用性 | 任何引用数据类型(数组、类、接口、枚举、注解、记录)都可以使用 | ||
子类使用说明 | 自定义的类在 没有重写 Object中equals()方法的情况下,调用的就是Object类中声明的equals(),比较 两个对象的 引用地址 是否相同。(或比较两个对象是否指向了堆空间中的 同一个对象实体 ) | |||
对于像 String、File、Date和包装类 等,它们都重写了Object类中的equals()方法,用于比较两个对象的 实体内容是否相等 。 | ||||
开发中使用说明 | 实际开发中,针对于自定义的类,常常会判断两个对象是否equals(),而此时主要是判断两个对象的属性值是否相等。所以: 我们要 重写Object类的equals()方法。 | |||
== VS equals() | ==: 运算符 | 使用范围: 基本数据类型、引用数据类型 | ||
1、基本数据类型:判断 数据值 是否相等 2、引用数据类型:比较两个引用变量的 地址值是否相等。(或比较两个引用是否指向同一个对象实体) | ||||
equals(): 方法 | 使用范围: 只能使用在引用数据类型上。 | |||
对于类来说: 1、重写equals()比较两个对象的 实体内容 是否相等; 2、没重写equals()方法就等同于 == 了,比较两个引用变量的 地址值是否相等。(或比较两个引用是否指向同一个对象实体) | ||||
toString() | Object类中toString()的定义 | public String toString() { //打印的地址值【名字+@+hashCode值】return getClass().getName() + "@" + Integer.toHexString(hashCode()); } | ||
开发中的使用场景 | 平时我们在调用 System.out.println() 打印对象引用变量时,其实就调用了对象的toString() | |||
子类使用说明 | 自定义的类: 在没有重写Object类的toString()的情况下,默认返回的是当前对象的 地址值 。 | |||
String、File、Date或包装类等 Object的子类,它们都重写了Object类的toString(),在调用toString()时,返回当前对象的 实体内容 。 | ||||
开发中使用说明 | 习惯上,开发中对于自定义的类在调用 toString() 时,也希望显示其 对象的实体内容,而非地址值 。这时候,就需要重写Object类中的toString()方法 |
五、设计模式(单例)
六、接口
接口
就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。
继承
是一个"是不是"的is-a
关系,
而接口实现
则是 "能不能"的has-a
关系。
定义接口的关键字:
interface
接口不是类
,而是一种引用数据类型
内部结构说明
:1、可以声明:
属性
:必须使用public static final
修饰
方法:
①在JDK8.0 之前,接口中只允许出现:
(1)公共的静态的常量
:其中public static final
可以省略
(2)公共的抽象的方法
:其中public abstract
可以省略
理解:
接口是从多个相似类中抽象出来的规范,不需要提供具体实现
。
②在JDK8.0 时,接口中允许声明默认方法
和静态方法
:
(3)公共的默认的方法
:其中public
可以省略,建议保留,但是default
不能省略
(4)公共的静态的方法
:其中public
可以省略,建议保留,但是static
不能省略
③在JDK9.0 时,接口又增加了:私有方法
(5)私有方法
2、不可以声明:
构造器
、代码块
等【引用数据类型
:数组,类,枚举,接口,注解,记录】因为接口中没有成员变量需要动态初始化
七、main()方法
public static void main(String args[]){}
- 理解1:看做是一个
普通的静态方法
理解2:看做是程序的入口
,格式是`固定 的。- 与控制台交互:如何从键盘获取数据?
方式1:使用Scanner
。(可以传各种Scanner
支持的类型)
方式2:使用main()的形参进行传值
。(因为声明为String,只能传String类型)
四、关键字
package、import、return、this、super、this VS super、static、final、abstract
关键字 | ||||
package | 语法格式:package 顶层包名.子包名 ; | |||
import | 语法格式:import 包名.类名; | |||
return | 作用 | 结束一个方法 | ||
结束一个方法的同时,可以返回数据给方法的调用者 (方法声明中如果有返回值类型,则方法内需要搭配return使用) | ||||
注意点 | return后面不能声明执行语句。 如: return; system.out.println("输出语句"); //编译会报错 return已经结束当前方法 | |||
this | 简述 | Ⅰ、它在方法(准确的说是实例方法或非static的方法)内部使用,表示调用该方法的对象 Ⅱ、它在构造器内部使用,表示该构造器正在初始化的对象。 | ||
使用 | 可以调用的结构: | 成员变量、方法、构造器 | ||
this调用属性和方法 | ||||
1、针对于方法内的使用情况:(准确的说是非static修饰的方法) 一般情况: 我们通过对象a调用方法,可以在方法内调用当前对象a的属性或其他方法。此时,我们可以在属性和其他方法前使用"this.",表示当前属性或方法所属的对象a。但是,一般情况下,我们都选择省略此"this."结构。 特殊情况: 如果方法的形参与对象的属性同名了,我们必须使用"this."进行区分。 使用this.修饰的变量即为 属性(或成员变量); 没有使用this.修饰的变量,即为 局部变量。 | ||||
2、针对于构造器内的使用情况: 一般情况: 我们通过构造器创建对象时,可以在构造器内调用当前正在创建的对象的属性或方法。此时,我们可以在属性和方法前使用"this.",表示当前属性或方法所属的对象。但是,一般情况下,我们都选择省略此"this."结构。 特殊情况: 如果构造器的形参与正在创建的对象的属性同名了,我们必须使用"this."进行区分。使用this.修饰的变量即为属性(或成员变量),没有使用this.修饰的变量,即为局部变量。 | ||||
this调用构造器 | ||||
语法格式 this():调用本类的无参构造器 this(实参列表):调用本类的有参构造器 | ||||
要求: "this(形参列表)" 必须声明在当前构造器的首行 结论: "this(形参列表)" 在构造器中最多声明一个 如果一个类中声明了n个构造器,则最多有n-1个构造器可以声明有"this(形参列表)"的结构 | ||||
super | 理解 | super可以调用的结构:属性、方法、构造器 即: Ⅰ、super 可用于访问父类中定义的属性 Ⅱ、super 可用于调用父类中定义的成员方法 Ⅱ、super 可用于在子类构造器中调用父类的构造器 注意:子父类出现同名成员时,可以用 super 表明调用的是父类中的成员 | ||
使用 | super调用属性、方法 | |||
子类继承父类以后,我们就可以在子类的方法或构造器中,调用父类中声明的属性或方法。(满足封装性的前提下)使用"super."的结构,表示调用父类的属性或方法。 一般情况下,我们可以考虑省略"super."的结构。 但是如果出现子类重写了父类的方法或子父类中出现了同名的属性时,则必须使用"super."的声明,显式的调用父类被重写的方法或父类中声明的同名的属性。 | ||||
super 调用构造器 | ||||
① 子类继承父类时,不会继承父类的构造器。只能通过“super(形参列表)”的方式调用父类指定的构造器。 | ||||
② 规定:“super(形参列表)”,必须声明在构造器的首行。 | ||||
③ 我们前面讲过,在构造器的首行可以使用"this(形参列表)",调用本类中重载的构造器,结合②得出结论:在构造器的首行,"this(形参列表)" 和 "super(形参列表)"只能二选一。 | ||||
④ 如果在子类构造器的首行既没有显示调用"this(形参列表)",也没有显式调用"super(形参列表)", 则子类此构造器默认调用"super()",即调用父类中空参的构造器。。 | ||||
⑤ 由③和④得到结论:子类的任何一个构造器中,要么会调用本类中重载的构造器,要么会调用父类的构造器。只能是这两种情况之一。 | ||||
⑥ 由⑤得到:一个类中声明有n个构造器,最多有n-1个构造器中使用了"this(形参列表)", 则剩下的那个 一定使用"super(形参列表)" 我们在通过子类的构造器创建对象时,一定在调用子类构造器的过程中,直接或间接的调用到父类的构造器。 也正因为 调用过父类的构造器,我们才会将父类中声明的属性或方法加载到内存中,供子类对象使用。 | ||||
子类对象实例化全过程代码示例 | 1. 从结果的角度来看:体现为类的继承性。 当我们创建子类对象后,子类对象就获取了其父类中声明的所有的属性和方法,在权限允许的情况下,可以直接调用。 | |||
2. 从过程的角度来看: 当我们通过子类的构造器创建对象时,子类的构造器一定会直接或间接的调用到其父类的构造器,而其父类的构造器同样会直接或间接的调用到其父类的父类的构造器,....,直到调用了Object类中的构造器为止。 正因为我们调用过子类所有的父类的构造器,所以我们就会将父类中声明的属性、方法加载到内存中,供子类的对象使用。 问题:在创建子类对象的过程中,一定会调用父类中的构造器吗? yes! | ||||
3.问题:创建子类的对象时,内存中到底有几个对象? 就只有一个对象!即为当前new后面构造器对应的类的对象。 | ||||
this VS super | ||||
意义 | this:当前对象 在构造器和非静态代码块中,表示正在new的对象; 在实例方法中,表示调用当前方法的对象 | super:引用父类声明的成员 | ||
使用 | this.成员变量: 表示当前对象的某个成员变量,而不是局部变量 | super.成员变量: 表示当前对象的某个成员变量,该成员变量在父类中声明的 | ||
this.成员方法: 表示当前对象的某个成员方法,完全可以省略this. | super.成员方法: 表示当前对象的某个成员方法,该成员方法在父类中声明的 | |||
this()或this(实参列表): 调用另一个构造器协助当前对象的实例化,只能在构造器首行,只会找本类的构造器,找不到就报错 | super()或super(实参列表): 调用父类的构造器协助当前对象的实例化,只能在构造器首行,只会找直接父类的对应构造器,找不到就报错 | |||
static | 可修饰的结构 | 属性、方法 ; 代码块、内部类; | ||
变量分类 | ||||
修饰属性 | ① 个数 静态变量: 在内存空间中只有一份,被类的多个对象所共享。 实例变量:类的每一个实例(或对象)都保存着一份实例变量。 | |||
② 内存位置 静态变量: jdk6及之前:存放在方法区; jdk7及之后:存放在堆空间。 实例变量:存放在堆空间的对象实体中。 | ||||
③ 加载时机 静态变量: 随着类的加载而加载,由于类只会加载一次,所以静态变量也只有一份。 实例变量:随着对象的创建而加载。每个对象拥有一份实例变量。 | ||||
④ 调用者 静态变量: 可以被类直接调用,也可以使用对象调用。 实例变量:只能使用对象进行调用。 | ||||
⑤ 判断是否可以调用 ---> 从生命周期的角度解释 类变量 实例变量 类 yes no 对象 yes yes 静态变量: 实例变量: | ||||
⑥ 消亡时机 静态变量:随着类的卸载而消亡。 实例变量:随着对象的消亡而消亡。 | ||||
使用场景: 1、判断当前类的多个实例是否能共享此成员变量,且此成员变量的值是相同的。 2、开发中,常将一些常量声明是静态的。比如:Math类中的PI。 | ||||
修饰方法 | 1、随着类的加载而加载 | |||
2、可以通过“类.静态方法”的方式,直接调用静态方法 | ||||
3、静态方法内可以调用静态的属性或静态的方法。 (属性和方法的前缀使用的是当前类,可以省略) 不可以调用非静态的结构。(比如:属性、方法) 类方法 实例方法 类 yes no 对象 yes yes 补充:在类的非静态方法中,可以调用当前类中的静态结构(属性、方法)或非静态结构(属性、方法) | ||||
4、static修饰的方法内,不能使用this和super | ||||
使用场景: 1、方法内操作的变量如果都是静态变量(而非实例变量)的话,则此方法建议声明为静态方法。 2、开发中,常常将工具类中的方法,声明为静态方法。比如:Arrays类、Math类。 | ||||
经典代码示例 | ||||
final | 可修饰的结构 | 类、方法、变量 | ||
① 修饰类 | 表示此类不能被继承。 比如:String、StringBuffer、StringBuilder类 | |||
② 修饰方法 | 表示此方法不能被重写 比如:Object类中的getClass() | |||
③ 修饰变量 | 既可以修饰成员变量,也可以修饰局部变量。 此时的"变量"其实就变成了"常量",意味着 一旦赋值,就不可更改。 | |||
final与static搭配:修饰成员变量时,此成员变量称为:全局常量。 比如:Math的PI | ||||
final修饰 成员变量 VS 局部变量 赋值 | ||||
final修饰成员变量: 有哪些位置可以给成员变量赋值? 1、显式赋值 2、代码块中赋值 3、构造器中赋值 | final修饰局部变量:一旦赋值就不能修改 方法内声明的局部变量:在调用局部变量前,一定需要赋值。而且一旦赋值,就不可更改 方法的形参:在调用此方法时,给形参进行赋值。而且一旦赋值,就不可更改 | |||
abstract | abstract可以用来修饰: 类、方法 | |||
修饰 类 | 此类称为 抽象类 | |||
抽象类 不能实例化 | ||||
抽象类中是包含 构造器的, 因为子类对象实例化时,需要直接或间接的调用到父类的构造器。 | ||||
抽象类中 可以没有抽象方法。 反之,抽象方法所在的类,一定是抽象类。 | ||||
修饰 方法 | 此方法即为 抽象方法 | |||
抽象方法 只有方法的声明,没有方法体。 | ||||
抽象方法其功能是确定的 (通过方法的声明即可确定), 只是不知道如何具体实现(体现为没有方法体) | ||||
子类必须重写父类中的所有的抽象方法之后,方可实例化。 否则,此子类仍然是一个抽象类。 | ||||
abstract 不能用来修饰: 属性、 构造器、 代码块等 | ||||
不能用abstract修饰 私有方法、静态方法、final的方法、final的类 | ||||
私有方法 不能重写 | ||||
避免 静态方法 使用类进行调用,因为静态方法可以直接用类名调用, abstract修饰的方法 没有方法体 ,所属类类必定是一个 抽象类,不能实例化 | ||||
final 的方法 不能被重写 | ||||
final 修饰的类 不能有子类 | ||||
1、抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象 | 理解: ① 假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。 ② 抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。 | |||
2、抽象类中,也有 构造方法,是供子类创建对象时,初始化父类成员变量使用的。 | 理解: 子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法。 | |||
3、抽象类中,不一定包含 抽象方法,但是有抽象方法的类必定是抽象类。 | 理解: 未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。 | |||
4、抽象类的子类,必须 重写 抽象父类中 所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。 | 理解: 假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。 |
代码举例:
class Creature{ //生物类
//声明属性、方法、构造器
}
class Animal extends Creature{ //动物类
}
class Dog extends Animal{ //狗类
}
class DogTest{
public static void main(String[] args){
Dog dog = new Dog();
dog.xxx();
dog.yyy = ...;
}
}
public class Test02 {
static int x, y, z;
static {
int x = 5;
x--;
}
static {
x--;
}
public static void method() {
y = z++ + ++z;
}
public static void main(String[] args) {
System.out.println("x=" + x);
z--;
method();//-1 x的值 成员变量的x的作用域都可见, 两个static中的代码块中的x只在代码块中可见
System.out.println("result:" + (z + y + ++z));//3
}
}