封装
封装是什么
封装(encapsulate)是指将数据及相关操作绑定在一起的一种编程机制,使其构成一个不可分割的独立实体。
对象代表什么,就得封装对应的数据,并提供数据对应的行为
封装的好处
- 使用者能够完全得到自己想要的功能,又不需要思考过多的细节
- 实现则会可以隐藏功能实现的细节,方便灵活修改而不影响使用者使用
- 可以对数据进行验证,保证安全合理
封装的实现步骤
从语法角度来说,Java中的封装是依赖于访问权限修饰符来实现
- 完全不需要被外界知道的属性,在类中可以私有化
- 提供公共的(public) set方法,用于对成员变量判断并赋值
- 提供一个公共的(public) get方法,用于获取成员变量的值
继承
语法定义
继承是干什么的
Java在定义一个类时,可以显式地,直接让它继承另一个类,让类和类之间产生父子的关系
可以把多个子类中重复的代码抽取到父类中,子类可以直接使用
减少代码的冗余,提高代码的复用性,这就是Java的继承机制
继承的本质是成员的复用
继承的语法:
[访问权限修饰符] class 类名(子类) extends 被继承的类(父类){}
子类特点
子类可以得到父类的属性和行为,子类可以直接使用
子类可以在父类的基础上增加新的功能,使其更加强大
继承关系
被继承的类称之为 父类,继承父类的叫做 子类
Java只支持单继承,不支持多继承,但支持多层继承
每一个类都直接或间接的继承于 [[2.6 Object]]
从属关系(relation)上来说,继承中的父子类具有 “is-a” 关系。即子类 “is-a” 父类。子类可以近似地看成是一个父类,子类可以当作父类来使用
子类可以继承哪些内容:
父类的内容 | 非私有修饰 | private修饰 |
---|---|---|
构造方法 | 不能 | 不能 |
成员变量 | 能 | 能 |
成员方法 | 虚方法表 能 | 否则 不能 |
[[虚方法表]] |
访问权限修饰符
-
private:只能够在同一类中能够访问,私有的,外面谁都不能用。
-
(缺省的)不写任何关键字:同一包中的子类或者其它类能够访问,同包中都可以使用。
-
protected:不同包的子类能够访问。
-
public:不同包的其他类能够访问,相当于没有做访问权限。
public | protected | (缺省) | private | |
---|---|---|---|---|
同类 | √ | √ | √ | √ |
同包其他类 | √ | √ | √ | |
不同包子类 | √ | √ | ||
不同包其他类 | √ |
子类对象的初始化
子类的类加载会触发父类的类加载,且类加载的顺序是“先父后子”的
流程:
- 父子类加载(先父后子)
- 创建子类对象
- 子类对象中会专门开辟一片独立的区域,用来存储父类的成员变量(父类成员区域, 近似看成一个父类对象, 被super关键字指向, 近似看做super指向当前子类对象的父类对象)
- 子类自身的成员仍会存储在自身对象当中(this指向当前子类对象)
- 父子类成员赋值(先父后子)
- 默认初始化
- 显式赋值
- 构造代码块赋值
- 构造器赋值
父类的构造器优于子类的构造器执行
- super关键字可以在子类构造器或成员方法中,用于调用父类构造器或父类成员
- 在一个类的构造器中,如果第一行没有用 this/super 去调用其他构造器,那么一定隐含一个 super(); 表示调用父类的无参构造器,这种通过隐含 super() 的方法叫做 “子类对象的隐式初始化”
- 无参构造器是不能给成员变量赋值的,在创建子类对象时,如果希望能够给父类成员赋值,就必须在子类构造器第一行写 super(实参列表); 直接指出调用父类哪个构造器,这种方式称为 “子类对象的显式初始化”
引用数据类型的类型转换
引用数据类型转换和前提 → 继承
转换是什么?
引用数据类型变量 = 引用 + 对象
对象只能改变状态,所以 只能是转换了引用的类型
// 父类引用指向子类对象
Father fs = new Son();
// 上述代码可以写成:
Son s = new Son();
Father fs2 = s;
// 改变的是引用的类型,原始对象并没有发生变化
类型转换分类
- 自动类型转换
把子类引用转换成父类引用,即 “向上转型” - 强制类型转换
把父类引用转换成子类引用,即 “向下转型”
继承中对象的访问特点
成员变量
就近原则:谁离得近就用谁
public class Fu{
String name ="Fu";
}
public class Zi extends Fu{
String name ="Zi";
public void ziShow(){
String name = "ziShow";
System.out.println(name); // ziShow
System.out.println(this.name); // Zi
System.out.println(super.name); // Fu
}
}
- 出现同名成员变量:
name
从局部位置开始往上找this.name
从本类成员位置开始往上找super.name
从父类成员位置开始往上找
成员方法
当父类的方法不能满足子类现在的需求时,需要进行 [[#方法的覆盖/重写|方法重写]]
构造方法
- 父类中的构造方法不会被子类继承,但可以通过 super关键字 调用
- 子类构造方法的第一行都有一个默认的 super();
- 子类中所有的构造方法默认先访问父类中的无参构造,再执行自己
- 为了调用父类方法前先完成父类数据空间的初始化
- 如果想调用父类的有参构造,需要在 super(); 中写入参数
super关键字
super关键字 代表父类存储空间,即指向当前类的 父类“对象”的一个引用
- this 代表当前类的当前对象
- super 代表当前类的父类对象
两者比较大的区别:this在当前类中不受访问权限控制,super访问父类成员受访问权限控制
关键字 | 访问成员变量 | 访问成员方法 | 访问构造方法 |
---|---|---|---|
this | this.成员变量 | this.成员方法 | this.(…) |
super | super.成员变量 | super.成员方法 | super.(…) |
继承中的方法重写
方法的覆盖,也叫方法的重写(override),指在子类中,拥有和父类同名的成员方法,并且能够改写这个父类成员方法的内容
方法重写的本质是:覆盖了 虚方法表 中的方法
格式:
// 父类方法
public void test(){
System.out.println("father");
}
// 子类方法
@Override
public void test(){
System.out.println("son")
}
@Override重写注解:
- @Override写在重写后的方法上,用于校验子类重写时语法是否正确
- 语法错误时会有红色波浪线
- 重写方法都要加@Override注解,代码安全
注意事项:
- 重写方法的名称、形参列表必须与父类中严格保持一致
- 子类重写父类方法时,访问权限必须大于等于父类
- 子类重写父类方法是,返回值类型必须和父类保持兼容
- 建议:重写的方法尽量和父类保持一致
- 只有被添加到 虚方法表 中的方法才能被重写
优缺点
- 优点:
- 继承的出现减少了代码冗余,提高了代码复用性
- 继承的出现有利于功能的扩展,提升了程序的可维护性,更好地应对需求变更
- 继承是[[#多态|多态]]的前提
- 缺点:
- 子类继承父类必须无条件的接受父类的所有成员
- 父类中队成员进行修改,会严格体现到每一个子类中
final关键字
final有最终的、最后的意思
他是一个修饰符,可以用来修饰:
- 方法:不能被重写
- 类:不能被继承
- 变量:常量
修饰方法
final修饰方法表示最后的方法,最终的方法,表示该方法无法被重写 (但是仍然可以被继承)
语法:
[访问权限修饰符] final 返回值类型 方法名(形参列表){
方法体
}
注意事项:
-
final修饰成员方法,表示该方法无法被重写,但是仍然是可以继承的。
-
什么样的成员方法应该设置为final呢?
- 方法的设计已经比较完善,不需要子类进行修改了,子类只需要乖乖继承,使用父类的实现即可。比如一旦修改就会导致问题、bug等时,就可以设置为final修饰。
- 写规则,即便是父类的方法不是很完善,但只要是希望子类不要改写方法都可以这么做。(要么你就用,不爽你就自己实现一个)
-
日常开发,程序员还是比较 少见 有需要把方法设置成final修饰的。同样比较多见于JDK源码中,比较常见的有:像Object类中的getClass()方法.
修饰类
final修饰类时,表示最后的类,最终的类。
即表示这个类不能被继承。
语法:
[访问权限修饰符] final class 类名{
//类体
}
注意事项:
-
什么样的类需要设置成final?
- 不需要或不想要被子类继承的类,才需要设置为final修饰。
- 当你认为当一个类的设计已经足够完善,功能足够强大了,不需要再让子类去扩展它了。
- 这时出于安全的考虑,就可以将一个类设置为final。这样类中成员,既不会被继承,更不会被修改。
-
正常情况下,我们很少会主动把一个类设置成final,因为没有太多的必要性。实际开发中,也要 慎重 将一个类设置final。
-
常见的final修饰的类,都在JDK的源码当中。比如四类八种基本数据类型的包装类、Void、String、System、Scanner等等。
修饰变量
局部变量
- 方法体
- 用final修饰后表示该变量一旦声明并初始化赋值,就不可再修改它的取值了
- 方法的形参列表
- 用final修饰形参后表示实参一旦传入就无法在方法体中修改了
成员变量
final修饰成员变量表示该成员变量变成了一个 常量
它在内存中的位置,生命周期,使用方式等都不会改变。
语法:
[访问权限修饰符] final 数据类型 变量名;
成员常量的赋值,有且必须有一次
有些固定的变量,比如 PI 的值,可以用final修饰
常量:
- 在实际开发中,常量一般作为系统的配置信息,方便维护
- 命名规范:全部大写,单词之间用下划线隔开
静态成员变量
静态成员常量: 静态属于类。 在类中只有一份。
final修饰静态成员变量表示一个静态的"常量",在 类的全局仅有一份,所以final修饰静态成员变量,也称之为 “全局常量” 。
它是一个真正意义上的常量,不会因为创建对象而更改,实际开发中的常量多使用它。
语法:
[访问权限修饰符] static final 数据类型 变量名;
引用数据类型
final修改基本数据类型:记录的值不能发生改变
final修饰引用数据类型:记录的地址值不能发生改变,内部的属性值还是可以改变的
语法:
final 类名 对象名 = new 类名(参数);
多态
概念
同类型的对象,表现出的不同形态
即:
- 同一种事物
- 不同的情况
- 不同的状态
发生条件:
- 必须存在 继承,多态一定发生在父子类之间
- 必须存在方法重写
- 必须存在父类引用指向子类对象
调用成员的特点
对象的创建:
- 创建父类对象,用父类引用接收,用对象名. 访问
- 创建子类对象,用子类引用接收,用对象名. 访问
- 创建子类对象,用父类引用接收,用对象名. 访问
访问范围:
访问范围是由 引用的数据类型 决定的
- 如果引用是父类类型,那么访问范围只有父类及以上
- 如果引用是子类类型,那么访问范围是子类+父类
访问结果:
访问结果是由 对象的实际类型 决定的
- 如果对象是一个子类类型,方法调用要体现出子类的行为
- 如果对象是一个父类类型,方法调用的结果就是父类行为
对于成员变量: 编译看左边,运行看左边
对于成员方法: 编译看左边,运行看右边.
优缺点
优点:
- 要实现多态必须要继承,而继承提高了代码复用率,提升了程序的可维护性
- 使用多态后,用父类引用指向不同的子类对象,之后只需要调用同名方法,就能自动根据不同的子类对象得出不同的行为,大幅度简化代码
- 在多态形式下,右边的对象可以实现解耦合,即可以随时更改对象名
缺点:
- 父类引用限制了子类对象的功能,这意味着子类独有的成员是无法使用父类引用访问的
- 而如果这时候需要访问子类独有成员的话,就需要 强转引用数据类型,且是“向下转型”,稍有不慎就会产生异常
所以强转能够成功的前提条件是:
引用所指向的真实对象,必须是强转后的引用能够指向的对象。所以可以是强转后的引用的类型的对象或者子类对象
父类的引用转换成子类的引用,在继承链中属于向下,属于"向下转型"。编译器默认不允许,需要显式地写代码完成类型转换。
语法:
子类类名 对象名 = (子类类名) 父类引用
instanceof关键字
向下转型 是一种强转,它成功的条件相对比较苛刻。在操作之前,要先慎重考虑。
强转失败会导致程序抛出异常:ClassCastException
,导致程序终止执行。
正是由于强转的条件苛刻,而且失败后果很严重,所以Java当中提供了检测手段,来保障强转的安全性。
需要使用关键字: instanceof
语法:
引用名 instanceof 类名
上述语法返回一个boolean类型值:
-
true表示该引用指向的对象,是后面类名的一个对象或者子类对象。
-
反之,false则表示不是。
-
当引用指向null时,使用该语法,结果会直接返回false。
总结:
- 强转类型可以转换成真正的子类类型,从而调用子类独用的功能
- 转换类型与真实对象类型不一致时会报错
- 转换时用instanceof关键字进行判断