面向对象
- 概述:面向对象时Java语言使用的而一种编程思路,编程思想
- 面向过程和面向对象
- 面向过程:比较注重解决问题的步骤
- 例:碰到一个问题,需要考虑解决这个问题的每一步应该怎么做
- 面向对象:比较注重解决问题的主题;
- 例:比如碰到一个问题,需要考虑一个主题来完成解决问题
- 面向过程:比较注重解决问题的步骤
- 面向对象与面向过程的关系:
- 面向对象基于面向过程的;面向过程是面向对象的基础
- 面向对象的特征:
- 封装
- 继承
- 多态
类和对象
- 类:类型。对一些相同的或者相似的事物的一个统称。【概念】
- 对象:指一些具体的事物,或者一些实实在在的事物
类的具体的定义
-
类:类型:对一些事物的统称
一般想要定义一个类型,从两点进行定义
-
属性:对该类型的信息的描述
在Java语言中,一般通过变量来定义类型的属性,来类中方法外定义该变量
-
行为:对该类型的功能的定义
在Java语言中,一般通过方法来定义类型的行为
-
-
举例:
人类{
属性:姓名、身高、体重、年龄……
功能:吃饭、睡觉、打豆豆
}
-
说明:
带着main方法的类型:测试类【程序的入口】
自己定义的方法的类型:自定义类【用来定义属性和方法】
无论时测试类还是自定义类,使用前都需要先编译为.class文件
对象的创建和使用
-
创建对象的类型:
类型 对象名 =new 类名();
-
说明:
类型:要创建哪一个类的对象,就写哪一个类的类名即可
对象名:给该对象起一个名字 和普通变量起名规则相同
= :赋值符号
new : 关键字,用于开辟空间
类名:和前面的名字一致
( ):构造方法
-
访问属性:对象名.属性名
-
给属性赋值: 对象名.属性名 = 值
-
访问方法: 对象名.方法名
类和对象在内存中的执行流程
一个对象在内存中的存储方式
/
- 总结:
- 使用一个类型之前,需要先加载该类型,类型在方法区中加载
- 创建一个对象名,就是在方法中,定义一个变量,分配一段空间,用来存储地址
- 创建对象是在堆内存中创建的,对象中存储的是属性值
- 使用对象访问的方法,是栈内存中运行的
- 使用对象名访问属性,通过变量中存储的地址找到堆内存中的对象,以此来访问 对象中的属性值。
两个对象在内存中的存储方式
- 总结:
- 类型加载过一次,下次使用不用重复加载
- 不同的对象中有相同的属性,但是属性的值不同
两个引用指向同一个对象
- 总结:当两个变量中保存的是同一个对象的地址时,其中一个变量修改了对象的属性值, 另外一个在访问时,访问的是修改后的结果。
成员变量和局部变量
-
局部变量:方法中定义的变量
-
成员变量:类中方法外定义的变量
-
区别:
- 定义位置不同
- 存储区域不同
- 成员变量:在堆内存中存储
- 局部变量:在栈内存中存储
- 生命周期不同
- 成员变量:随着对象的创建而存在,随着对象的消失而消失【当方法中没有引用访问该对象时,该对象就会默认被回收】
- 局部变量:随着方法的调用而存在,随着方法的调用结束而消失
- 默认值不同
- 成员变量:有默认值
- 局部变量:没有默认值
匿名对象
-
概述:在创建一个对象之后,而没有给该对象起名,(没有使用一个变量接收该对象地址)
-
格式:
new 类型();
-
使用场景:
- 使用某个方法,该方法需要一个对象,那么可以使用匿名对象当作实际参数来传递该数据
- 当需要调用类型中的某个方法,可以使用匿名对象来调用该方法
-
好处:
- 节约内存空间
图示对比:
代码:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接
上传(img-TUOflLNv-1645862681558)(file:///C:\Users\12994\AppData\Local\Temp\ksohtml\wpsCB90.tmp.jpg)]
封装
-
概念:
在生活中,每个人应该都有自己的信息,这些信息都应该只有自己有权访问或者修改,如果每个人的信息没有一种安全性,就造成一些错误的情况。在类中定义的属性和方法,如果没有一些保护的手段,呢么该方法或者属性很容易被其他类随意访问,不能保证安全性,所以需要封装保证安全性。
-
实现封装的关键字:private
-
如果需要让类中的属性和方法不能被其它类访问,就可以使用private修饰
-
私有之后的问题:一旦属性和方法被私有之后,那么属性和方法就不能再其他的类被访问,那定义的该属性和方法也就没有意义
-
解决问题:给私有之后的属性和方法提供一个公共的访问方式:
- 定义的属性一般给两种操作:一种是赋值、一种是取值
- 所以就给私有的属性提供一个公共的赋值和取值操作
- 公共的赋值操作:set属性名
- 公共的取值操作:get属性名
-
封装的原则:
- 将属性和方法通过private私有化
- 给私有化的内容提供公共的访问方式
代码:
封装的优化
-
优化方式:在定义类的方法或者方法的参数是,要做到见名知意;
-
实现方式:
- 在定义公共的访问方法时,方法的名字:get属性名或者set属性名
- 在定义方法的参数时,给哪一个属性赋值,参数名就定义为对应的属性名
-
Java语言中变量的访问原则:就近原则
- 在Java语言中要访问某一个变量,先看当前方法中有没有该变量的定义,如果有,直接使用,如果方法没有定义过该变量,去类中看一下有没有定义该变量,如果有,直接用,如果类中也没有,就报错
-
问题:当实现见名知意后,发现再给属性赋值的时候无法赋值成功
-
解决:
- 为了区分属性和参数,使用this关键字来区分
- 使用this关键字修饰的变量,指的是当前对象中定义的属性;没使用this的变量访问根据就近原则;
this:哪一个对象访问this所在的方法,this就指向哪一个对象(表示哪一个对象)
-
图示
代码:
构造方法
- 构造方法的作用:用来创建对象的给对象中的属性初始化
- 别名:构造函数、构造器
- 构造方法的定义的格式
修饰符 方法名 (){
方法体;
}
-
说明
-
修饰符:public
-
方法名:和当前所在的类名一摸一样
-
():构造方法后续如果需要传递一些数据,就在括号中定义对应的形参即可
-
方法体:可以有也可以没有,根据情况而定
-
-
特点:
- 构造方法是在创建对象时被调用
- 如果类中没有定义过构造方法,类型中会默认提供一个空参构造用于创建对象
- 如果在创建对象时,不需要给属性赋值,可以提供空参构造
- 如果在创建对象时,需要给属性赋值,可以提供有参构造
- 构造方法不能使用对象名调用
- 构造方法和set方法不冲突,构造是创建对象时可以给属性赋值;set方法在创建对象之后,给属性改值
代码:
静态
- 没有使用静态
- 如果某个类中所有的属性值都是相同的,那么就需要在各自的对象中为这个相同属性值分配空间。
- 缺点1:浪费内存空间
- 缺点2:数据维护麻烦
- 使用静态
-
如果某个类中所有对象中有一个相同属性,值相同。可以将该属性定义为静态修饰。
-
优点:
- 节约内存(因为不需要在每个对象中都为这个静态属性分配空间,只需要在类中初始化一次即可)
- 数据维护简单
-
静态变量的特点
-
静态变量使用关键字:static
-
静态变量属于类,随着类的加载就初始化【随着类的加载就存在】
-
静态变量在对象之前就存在,在类中存储所有对象所共用
-
在类中定义的静态变量,可以给该类所有的对象共用
-
静态变量可以使用类名直接调用,也可以使用对象名调用
静态方法的特点:
-
静态方法关键字:static
-
静态方法可以类名调用,也可以使用对象名调用
-
静态方法时随着类的加载就可以使用,所以静态方法的使用时机在对象之前;
-
注意事项:
-
静态方法中不能访问非静态变量,可以访问静态变量
静态方法可以使用的时候,对象可能还没有创建,对象没创建非静态属性就没有初始化,那么就不能使用
-
静态方法不能访问非静态方法
因为非静态方法可以访问非静态属性的,如果静态方法可以访问非静态方法,表示静态方法可以间接访问非静态属性,和第一点冲突。
-
静态方法中不能存在this关键字
-
-
总结:静态不能访问非静态
代码:
静态变量和非静态变量的区别
-
相同点:
都在类中方法外定义。
-
不同点:
- 调用方式不同:
- 静态变量可以使用类名调用,也可以使用对象名调用
- 非静态变量只能使用对象调用
- 存储空间不同:
- 静态变量属于类,在方法区中存储
- 非静态变量属于对象,在堆内存中存储
- 生命周期不同:
- 非静态变量随着对象的创建而存在,随着对象的回收而消失
- 静态变量随着类的加载就存在,随着类字节码对象的消失而消失
- 调用方式不同:
继承
- 概述:两个类之间实现了子父类之间的关系
- 继承的关键字:extends
- 子类:用于继承的的类型 派生类
- 父类:被继承的类型 基类 超类
代码:
继承的特点
-
特点:Java语言中,类之间的继承:支持单继承,支持多层继承、不支持多继承
-
单继承:一个类继承一个父类
-
多层继承:类型C 继承 类型B ,类型B继承类型A
-
多继承:一个类型同时继承多个父类(不支持)
如果一个类型同时继承了多个父类,多个父类中有一些相同的方法时,子类如果都继承,属于方法的重复定义;如果子类能继承,那么以后在调用该方法的时候,不知道执行哪一个父类中的该方法。
继承的注意事项:
-
父类中定义的私有方法,子类不能继承
-
父类中定义的私有属性,子类可以继承,但是不能直接访问
如果需要访问继承而来的私有属性,可以使用父类中继承的公共访问方式来间接访问
-
父类中的构造方法,子类不能继承
- 如果子类可以继承父类的构造方法,那么子类的构造方法名就无法和本类的类名保持一致,这一点和构造的定义格式矛盾
- 如果子类中有一些特殊的属性,那么从父类中继承而来的构造方法就无法给这个特殊的属性进行初始化,所以这一点和构造方法的作用矛盾
-
父类中的构造方法子类不能继承、因为子类要自己定义构造方法
如果在父类中有一些私有属性,子类是不能直接访问的,所以在子类的构造方法中,可以调用父类的有参构造间接给其赋值【super(参数)】
-
类和类之间不能随意继承,要遵循两个原则:
- 一:子类必须是父类的一种
- 二:父类中定义的内容,必须是每个子类的共同内容
子父类中属性的继承关系
- 父类中定义的属性,子类可以继承,子类对象也可以访问
- 子类中定义的属性,父类对象也可以访问
- 在子类中访问某一个变量,根据就近原则
- 在子类中访问某一个变量,先看方法中有没有定义,方法中有定义的就直接使用;
- 如果方法中没有定义,就看本类中有没有定义该变量,如果本类中定义过,也可以使用;
- 如果本类中也没有定义过,可以去父类中寻找有没有该变量的定义,如果父类中定义过,可以使用,如果父类中没有定义到,表示不能使用。
- 如果访问变量,没有使用任何关键字修饰,那么根据就近原则
- 如果访问变量,使用this修饰,直接访问本类对象中的属性
- 如果访问变量,使用super修饰,直接访问子类父类中的属性
- 图示:
补充:
- 在加载子类之前,需要加载父类
- 在子类的对象中先初始化父类的属性,在初始化本类的属性
子父类继承中构造方法的关系
- 如果在子类的构造方法中,没有调用任何构造方法,那么在子类构造方法的第一行,系统会默认提供一个super()表示默认调用父类的空参构造。
- 如果在子类的构造方法中,调用父类的构造方法,那么系统就不再默认提供任何构造
- 如果在子类的构造方法中,调用了本类的构造方法,那么系统就不再提供任何构造
- 总结:
- 通过以上三点发现,不管怎么创建子类的对象,都是先指定父类的构造方法
- 目的:为了在初始化子类的数据前,先初始化父类的数据
- 注意事项:
- 构造方法之间,不能自己掉用自己
- 调用父类的构造方法和调用本类的构造方法都需要在第一行定义。【super、this】
子父类中成员方法的关系
-
子类对象可以访问父类中定义的任何方法(私有除外)
-
父类对象不能使用子类中的方法
方法的重写
在子父类的关系中,两个方法的方法名相同,参数列表相同,返回值类型相同,方法的实现内容可以不同。
为了验证以及表示该方法时重写的方法,一般使用一个注解来修饰
方法重写的原因:
-
当子类从父类中继承一个方法之后,子类不想要使用父类对该方法的实现方式的时候,子类就可以将该方法的实现方式进行修改。
@override
使用该注解修饰的方法如果没有编译报错,表示是重写的方法,如果编译报错,表示该方法不是重写的方法。
-
注意事项:
- 私有方法不能被子类重写(根本不能继承)
- 方法再重写的时候,权限不能越来越小【因为子类是父类的扩展,父类不能改变父类】
-
静态方法是可以被子类继承的,但是不能被子类重写
如果在子类中定义了(声明)一个和父类一模一样的静态方法,那么父类中继承而来的静态方法就会默认被子类隐藏,只能使用自己定义的静态方法。
多态
概述,用来描述事物的多种状态
-
对象的多态性:一个对象可以有不同的类型来表示;一个对象可以通过不同的类型引用来接收。
-
类型的多态性:一个类型可以有不同的子类;一个类型引用可以接收不同的子类对象
-
多态实现的前提:
- 两个类之间有继承关系 或者 接口和实现类的实现关系
- 使用父类的类型接收子类的对象 或者 接口引用变量接收实现类对象
多态访问属性的特点
-
特点
编译看左边,运行看左边
-
解释:
- 编译看左边
- 如果访问某个属性的时候,先看 = 左边的类型中有没有该变量的定义,有的话编译成功,没有编译失败
- 运行看左边:
- 在运行的时候,看 = 左边的类型如何给该属性赋值,它赋什么值,访问结果 就是哪一个值
- 编译看左边
代码:
多态访问方法的特点
-
特点
编译看左边,运行看右边
-
解释:
- 编译看左边:
- 访问某个方法时,编译时候看 = 左边的类型有没有定义该方法,如果i当以了就能使用,如果没有定义,就不能使用
- 运行看右边:
- 在运行期间,看= 右边的类型对该方法如何实现,右边的类型如何实现该方法,就如何执行
- 编译看左边:
代码:
多态访问静态方法的特点
-
特点:
编译看左边,运行看左边
-
解释:
静态方法被哪个类的引用变量调用,就执行哪一个类中的实现内容
不会随着子类的不同而不同
向上向下转型
-
向上转型【多态】:
-
使用父类的引用变量,接收子类的对象
-
转型之后权限:
一旦向上转型之后,该变量访问权限变小,只能先看父类有没有定义过,父类中定义过才能使用,父类中没有定义过就不能使用
-
-
向下转型:
-
将指向子类的父类引用,恢复为子类的引用
-
格式:
SuperMan sm = (SuperMan) man;
转型后的权限:
就恢复为和子类对象一样的访问权限,既可以访问父类,也可以访问本类
-
图示:
instanceof关键字
-
使用格式:
引用变量 instanceof 类型
-
特点:
判断左边变量接受的对象是否属于右边的类型,如果属于右边的类型,返回值为true,否则为false
-
作用:
当需要向下转型时,不确定该变量接收的对象属于哪一个类型,可以使用该关键字先判断,避免出现类型转换异常
代码:
多态的好处
将来定义某个方法的参数时,可以将高参数类型定义为父类的类型,定义之后,可以传递任何子类的对象,传递的是哪一个子类,执行的内容会执行该子类的实现方式
代码:
总结
- 如果使用父类的引用变量接收子类对象,那么该变量的访问权限就和super关键字相同,直接指向父类的对象
- 如果使用本类的引用变量接收本类的对象,那么该变量访问权限和this一样
- 多态访问方法,根据动态绑定机制访问
- 静态绑定是指在编译期间JVM就确定了变量的值或者具体的方法的机制。
- 动态绑定是在运行时根据具体在堆中创建的对象JVM进行确认的。
- 所有的成员变量,静态方法,final方法以及private修饰的方法(其实private默认带final)都采用的是静态绑定机制,而成员方法采用的是动态绑定。
具体情况就是编译阶段进行类的加载时,会将各种静态放入方法区,各种常量以及方法名称和final方法放入常量池,然后会构造一个方法表,每个类都有一个方法表,是在编译的时候就确认的,放置着方法和其在方法区位置的对应关系,这个就是动态绑定机制的实现关键,顺序根据从父到子的顺序放置,如果出现重写方法(overwrite)则直接改变其地址。所以在编译时,如果father没有该方法,则father类的方法表中无此记录,调用则会发生编译错误(编译并未创建对象根据数据类型判断),所以编译看左边,而运行时,f为son的引用,调用方法时JVM根据f引用找到位于堆区的son对象,然后找到son类的方法表,调用方法,所以运行看右边。