面向对象中
六、 封装性
6.1 高内聚、低耦合
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉
- 低耦合:仅对外暴露少量的方法用于使用
6.2 目的
- 为了隐藏实现的细节,提高代码的可维护性
- 便于使用者正确使用系统,防止错误修改属性
- 有助于系统之间的松耦合,提高系统独立性
- 提高软件的可重用性
- 降低了构建大型系统的风险
6.3 权限修饰符(四种)(从大到小)
public
任意位置可见
protected
本包下可见,在不同包下的子类可见
缺省(default)
本包下可见
private
只能在本类中可见
权限修饰符可以修饰:
外部类:public和缺省;
成员变量、成员方法、构造器、成员内部类:public,protected,缺省,private。
6.4 属性封装
作用
- 1.隐藏类的实现细节
- 2.保证对象内信息的完整性
- 3.便于修改,提高代码的可维护性
步骤
- 1.属性私有化(private修饰)
- 2.提供set get 方法供外界设置属性值以及获取属性值
七、 构造器
作用:用于创建并初始化对象(快速给成员变量赋值)
语法格式
格式:
【修饰符】 构造器名(){
// 实例初始化代码
}
【修饰符】 构造器名(参数列表){
// 实例初始化代码
}
注意事项
- 1.构造器名必须与它所在的类名必须相同
- 2.它没有返回值,所以不需要返回值类型,甚至不需要void
- 3.如果你不提供构造器,系统会给出默认无参数构造器,并且该构造器的修饰符默认与类的修饰符相同
- 4.如果你提供了构造器,系统将不再提供无参数构造器,除非你自己定义
- 5.构造器是可以重载的,既可以定义参数,也可以不定义参数
- 6.构造器的修饰符只能是权限修饰符,不能被其他任何修饰
总结:只要创建对象必然会调用构造器
思考
一.通过构造器给属性赋值的特点是什么?
- 构造器只能对属性赋值一次
二.既然构造器可以给属性赋值还有必要提供方法进行赋值吗?
- 有必要,因为后面需要对对象的属性进行修改,在通过构造器进行修改是不可能的
三.什么场景下需要使用构造器?
- 1.创建对象
- 2.一个对象中只执行一次的代码可以放在构造器中
八、 继承性
8.1 概述
生活中:继承遗产、“子承父业”
Java中:有父类、子类的概念,父类中的一些属性和方法被子类继承下来使用,不用再次定义。
8.2 作用
- 提高代码的复用性
- 一个父类,多个子类,不需要重复定义变量和方法
- 提高代码的扩展性
- 父类更通用,子类更具体
- 类与类之间产生了关系,是学习多态的前提
8.3 格式
【修饰符】 class 父类 {
...
}
【修饰符】 class 子类 extends 父类 {
...
}
8.4 继承的实现:extends
8.5 注意事项
- 1.子类在使用资源时,先从本类寻找资源,如果本类中没有的话再从父类中找,一直找到object类为止
- 2.如果一个类没有显示的继承另一个类,那么此类默认继承object类
- 3.java中的继承是单继承,即一个类只能直接继承另外一个类,但是可以多层继承
- 4.父类中的私有属性子类是不能直接使用的
- 5.继承表示is- a的关系,即
- 6.一个父类可以有多个子类
- 7.父类的私有属性不能被继承
- 子类不能直接访问父类私有化的属性,但是可以通过继承的get/set方法访问私有属性
- 8.父子类成员变量重名(super关键字) (不建议变量重名)
8.6 方法重写
定义:子类中定义与父类中相同的方法,一般方法体不同,用于改造并覆盖父类的方法。
**当父类的方法不能满足子类的需求时,需要重写方法**
格式
class Phone {
public void sendMessage(){
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
public void showNum(){
System.out.println("来电显示号码");
}
}
//智能手机类
class NewPhone extends Phone {
//重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能
public void showNum(){
//调用父类已经存在的功能使用super
super.showNum();
//增加自己特有显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");
}
}
public class ExtendsDemo06 {
public static void main(String[] args) {
// 创建子类对象
NewPhone np = new NewPhone();
// 调用父类继承而来的方法
np.call();
// 调用子类重写的方法
np.showNum();
}
}
//这里重写时,用到super.父类成员方法,表示调用父类的成员方法。
要求
- 1.权限修饰符的使用中,不能比父类更加严格
- 2.返回值类型
- 使用基本数据类型时,子类必须和父类一致
- 使用引用数据类型时,那么可以是父类也可以是父类的子类
- 3.方法名必须与父类一致
- 4.形参列表必须与父类一致
注意事项
- 1.静态方法是可以被继承,但是不能被重写(若出现子类编写一个与父类同名同参方法,两者是没有任何关系)
- 2.私有等在子类中不可见的方法是不能被重写的
方法重写和方法重载的区别(没有任何联系)
- 概念上区别
- 权限修饰符的区别
- 使用范围
九、 this和super
9.1 this
this代表当前对象的引用,即当前被创建的对象
使用位置
- this在实例初始化相关的代码块和构造器中:表示正在创建的那个实例对象,即正在new谁,this就代表谁
- this在非静态实例方法中:表示调用该方法的对象,即谁在调用,this就代表谁。
- this不能出现在静态代码块和静态方法中
- this可以调用本类中的构造器
- 调用构造器必须位于首行,一般是多参调少参,且一般出现与源码
使用格式
- this.成员变量名
- this.成员方法名
- this()或this(实参列表):调用构造器
- 注意:this是不能调用静态的属性或方法的
必须记住
- this调用遵循就近原则
- 子类可以用this调用父类的资源,父类不会用this调用子类的资源
- 属性就近原则;
方法要追根溯源
this可以区分成员变量和局部变量
super
super是用于在当前类中访问父类的一个特殊关键字,不是对象的引用。(super是从父类继承的资源)
作用
super使用的前提
- 通过super引用父类的资源,都是在子类中仍然可见的
- 不能在静态代码快和静态方法中使用super
调用
- 属性super.成员变量
- 方法super.成员方法
- 静态方法内不能使用super
- 构造器super()或super(实参列表)
- 一个构造器不能同时调用this和super
常见的问题
- Implicit super constructor SuperClass() is undefined. Must explicitly invoke another constructor
- 解决的方式
- 方式1:父类提供一个空参的构造器
- 方式2:子类构造器中显示去调用父类中的有参构造器
- 解决的方式
十、 就近原则和追根溯源
10.1 找变量
没有super和this
- 在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的局部变量
- 如果不是局部变量,先从当前执行代码的本类去找成员变量
- 如果从当前执行代码的本类中没有找到,会往上找父类的(非private,跨包还不能是缺省的)
有this无super
- 通过this找成员变量时,先从当前执行代码的本类中找,没有的会往上找父类的(非private,跨包还不能是缺省的)
有super(父类)无this
- 通过super找成员变量,直接从当前执行代码所在类的父类找
- super()或super(实参列表)只能从直接父类找
- 通过super只能访问父类在子类中可见的(非private,跨包还不能是缺省的)
10.2 找方法
没有super和this
- 先从当前对象(调用方法的对象)的本类找,如果没有,再从直接父类找,再没有,继续往上追溯
有this无super
- 先从当前对象(调用方法的对象)的本类找,如果没有,再从父类继承的可见的方法列表中查找
有super无this
- 直接从当前对象(调用方法的对象)的父类继承的可见的方法列表中查找
注意:super和this都不能出现在静态方法和静态代码块中,因为super和this都是存在与对象中的
十一、 初始化(面试)
11.1 类的初始化
单个类初始化
- 1.类中静态成员变量显示赋值语句
- 2.静态代码块内容
- 会将这两个内容合并到<clinit>
触发类的初始化的操作
- 1.在本类中执行main()方法
- 2.使用了本类中的静态资源
注意
- 1.类的初始化顺序是先声明谁就先合并谁
- 2.类的初始化只会执行一次
- 3.类的初始化滞后,子类使用了从父类继承的静态资源,只会导致父类初始化,不会导致子类静态初始化
11.2 实例初始化
<init>执行顺序(从上到下)
- 1.默认赋值
- 2.普通成员变量显示赋值
- 3.普通代码块
- 4.构造器内容
- 先有默认值,再进行父类的实例初始化,然后在进行子类的实例初始化
new
11.3 类的执行顺序(必须学会)
父类比子类先执行,先执行完静态后,再继续顺序执行父类,若与方法重写,则需看谁调用
多做题,需要理解,后面会放上一些题目
十二、 多态性
12.1 形成多态的前提
- 有继承关系
- 有方法重写
12.2 多态的好处
- 提高程序的扩展性
- 降低类与类之间的耦合度
12.3 向上转型
形式:
父类的引用指向了子类的对象
格式:
父类 变量名 = new 子类名()
记住:编译看左边,运行看右边。等号的左右
- 编译时:指写完代码后
- 看等号右边类里有什么资源
- 运行时:程序跑起来
- 当前资源重写父类(等号左边)的方法以及从父类继承得到的方法
注意事项
- 1.向上转型时不能使用子类新增的资源
- 2.只和方法有关,与属性无关,若需使用子类的属性则需要向下转型
12.4 向下转型
形式:
父类的引用指向子类的实例
格式:
小的数据类型 变量名 = (小的数据类型)大的数据类型的值
注意
- 1.在进行向下转型是,可能会出现的问题(ClassCastException)
- 2.使用instanceof进行判断可以解决
向上转型弊端:无法使用子类自己新增的方法,而向下转型可以使用子类新增的方法
12.5 多态引用时关于成员变量与成员方法引用的原则
对于成员变量
只看编译时类型(等号左边的类型)
非虚方法
对于非虚方法(在编译时就可以明确要调用的方法):不可以重写的方法
三种非虚方法
- 1.由invokestatic指令调用的static方法,这种方法在编译时确定在运行时不会改变。
- 2.由invokespecial指令调用的方法,这些方法包括私有方法,实例构造方法,这些方法也是在编译时已经确定,在运行时不会再改变的方法
- 3.由final关键字修饰的方法。虽然final方法是由invokevirtual指令进行调用的,但是final修饰的方法不能够进行在子类中进行覆盖,所有final修饰的方法是不能够在运行期进行动态改变的。在java语言规范中明确规定final方法就是非虚方法。
虚方法
可以重写的方法
定义:在Java中虚方法是指在编译阶段和类加载阶段都不能确定方法的调用入口地址,在运行阶段才能确定的方法,即可能被重写的方法
静态分派
- 先看这个对象的编译时类型,在这个对象的编译时类型中找到最匹配的方法, 最匹配的是指,实参的编译时类型与形参的类型最匹配
动态绑定
- 再看这个对象的运行时类型,如果这个对象的运行时类重写了刚刚找到的那个最匹配的方法,那么执行重写的,否则仍然执行刚才编译时类型中的那个方法
十三、 native(了解)
被native修饰的方法叫本地方法
本地方法是没有方法体的。方法是可以调用,却不能看到底层的实现
用的其它语言实现,因为Java不能与硬件实现联系
本地方法被调用时,会在本地方法栈内开辟空间
十四、 final
形式: final class 类名
注意
- 被final修饰的类不能被继承,不能有子类
- final修饰的方法不能被重写
- final修饰的变量变为了常量
- 被final修饰的对象不能被改变,修饰的是对象的地址值
- final修饰的成员变量可以显示赋值,也可以通过构造器赋值
十五、 Object类
Object概述
- Object是所有类的父类
- 任何对象(包括数组)都可以使用Object类中的方法
- 若是没有特别指定父类,则默认会继承Object类
常见的API (方法)
toString方法
- 默认的toString方法返回的是:对象运行时类型+"@"+ 对象的哈希值的十六进制值
- 如果单独输出对象的名字是会默认调用该对象的toString方法
- 直接使用System.out.println(对象),默认会自动调用这个对象的toString()
- 因为Java的引用数据类型的变量中存储的实际上时对象的内存地址,但是Java对程序员隐藏内存地址信息,所以不能直接将内存地址显示出来,所以当你打印对象时,JVM帮你调用了对象的toString()。
重写toString方法的意义:要快速展示属性的信息
equals方法
默认作用:默认的equals方法的实现等价于"==",比较的是两个对象的地址值
重写作用:比较两个对象的内容是否相同
重写要求
- 如果重写equals方法,那么一定也要重写hashCode方法
- 遵循规定
- 自反性
- 传递性
- 一致性
- 对称性
- 非空对象与null的equals一定是false
hashCode方法
作用:获取当前对象的哈希码
如果两个对象的属性值完全一样,那么认为是同一个对象
注意点
- 哈希码不同对象肯定不同
- 哈希码相同对象也不一定相同
实现方式:重写方法
使用目的:
- 为了提高存储和查询性能用的,主要用于后面集合。
到集合的hashMap还会补充
getClass方法
作用:获取对象的运行时的类型
格式:public final Class<?> getClass()
为什么使用
- 因为Java有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致,因此如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法
到反射时还会补充