一、访问权限修饰符
在Java语言中,一切事都具有(显示定义或隐式定义的)访问权限,而这种语言层面的访问权限控制,是由访问权限修饰符实现的。
1.访问权限修饰符的访问控制,分为2个层面:
- 修饰类中成员(field & method)
控制类中的成员(成员变量 & 成员方法),对其他类可见性(其他类是否可以直接使用到); - 修饰类
通常用来限定类库中的类(自定义数据类型),对于外部使用者的可见性(是否能使用该类型)。
2.对类中成员的访问控制
对于类中成员的访问,可以使用的访问权限修饰符有4种:
- public:公共访问权限
任意类均可访问,实际就是没有限制访问权限 - protected:受保护的访问权限
同包中的其他类,和不同包的(可见)子类均可见
a. 在定义成员变量的类体中可以访问到
b. 同包中的其他类的类体中可以访问到
c. 不同包的中,在有一部分类中,无法访问到具有protected权限的成员,还有一部分类(子类)可以访问到。子类可以访问到父类中的protected成员,但只能通过创建子类对象去访问,创建父类去访问是访问不了的。 - default(默认权限,隐式定义)
同包中的其他类可见
a. 定义成员变量的类体中可以访问到
b. 同包中的其他类可见可以访问到
c. 不非同包的其他类不可以访问到 - private(最小的访问权限)
仅对同类中的其他成员可见
a. 在定义该成员的类体中可以访问
b. 同包中的其他类访问不到
c. 不同包的其他类中也访问不到
对于私有成员变量的访问可以在定义该私有成员的类体中,创建本类的对象并访问。
成员变量和成员方法在访问权限上是相同的。
3.对类的访问控制
能够修饰类的访问权限修饰符只有2种:
- public 对其他任意类可见
- default:对同包中的其他类可见
protected,private修饰符不能用来修饰一般的类(并非所有类,可以修饰内部类)。
为什么要使用访问控制修饰符(作用)?private
使用户不要触碰他们“不该”触碰的代码(private);
类库设计者可以安全的修改实现细节;
二、面向对象3大特征之封装
封装是一种信息隐藏技术。
- 是指将数据和基于数据的操作封装在一起;
- 数据被保护在内部;
- 系统的其他部分只有通过在数据外面的被授权的操作才能够进行交互;
- 目的在于将类使用者class user和类设计者class creator分开;
在面向对象的编程中,用类来封装相关的数据和方法,保证了数据的安全和系统的严密性。
我们可以通过修饰符private修饰成员变量使其成为私有成员变量,但如果要在并不是定义该成员的类,即另一个类体中访问私有成员变量,我们要怎么办?
针对private成员变量,专门定义public的方法,让外部能够访问私有成员变量的值:
get方法:让外部读取到私有成员变量值;
set方法:让外部通过该方法,修改私有成员变量的值;
同时,为了清楚地表明get和set分别获取和改变的是哪个成员变量的值,有如下常规命名规则:
如果成员变量的名字是xxx,则
public 返回值类型 getXxx()
public void setXxx(修改目标值参数)
提供get,set方法的好处:
a. 可以满足,某些场景下需要访问私有成员变量值的需求;
b. 通过定义get和set方法,实现了对私有成员变量的读写分离;
c. 一旦一个成员变量被private修饰,别人就无法直接通过对象名.的形式访问,只能通过set(),此时我们在set方法中,就可以自己通过代码控制别人的修改,比如如果修改的值不合法,就不进行修改。
三、面向对象3大特征之继承
1.继承概述
Java中的继承和我们现实生活中的“继承”的含义基本类似,但是含义更广。
- 简单来说都可以表示“不劳而获”(继承是一种代码复用机制)
- 类型之间“is a”的关系
a. 一种类型(子类) 继承 另外一种类型(父类)。
b. 从数据类型的角度,可以把子类类型,当做父类类型。数据类型是一个数据集合和基于这个数据集合的一组操作,当子类继承父类,子类拥有了父类中定义的成员(父类中的数据集和基于数据集的操作)。
c. 一个学生对象 是 一个人。
被继承的类称之为父类(基类或超类),继承其他类的类称之为子类(派生类)。
子类可以通过继承机制,不写任何额外代码就可以拥有父类的“所有”成员。
继承的语法:
class 子类名 extends 父类名 {}
我们不能把与A类毫无关系的B类的对象看做是A类的对象,但是一旦不同数据类型有了继承关系,可以把子类类型,看做是父类类型。
子类继承父类,即父类有的子类也都有,所以可以把子类看作是父类,父类引用可以接受子类引用的值,但子类不能接受父类,因为子类有的父类可能没有。
Java继承的特点:在Java中只能实现单重继承。
单重继承:简单来说Java的extends关键字后只能跟一个类名
class SubDemo extends Demo{} //ok
class SubDemo extends Demo1,Demo2{} //error
那是不是一个类只能继承另一个类中定义的成员呢? 并不是
一个类,继承其他类的时候,不仅仅可以继承其他类中定义的成员,其他类的父类中定义的成员,我们也可以间接继承。
2.继承的优缺点
-
优点:
a. 代码复用(类),复用已有的类定义代码(成员变量定义和成员方法定义);
b. 提高了代码的可维护性(这是一把双刃剑),父类中的代码可以被多个子类复用,被复用代码只定义在父类中,只有一份;
c. 弱化Java中的类型约束,可以让父类引用指向子类对象(多态的前提条件之一)。 -
缺点:
父类的修改可能会出现在所有子类中(我们无法选择这些修改可以反映在,哪些子类中,不可以反映在哪些子类中)。继承的注意事项: a.子类不能继承父类的构造方法; b.子类只能访问父类所有 非私有 的成员(成员方法和成员变量);
3.子类对象的初始化
一旦子类继承了父类,那么在创建子类对象时,初始化子类对象的过程和之前相比也会有较大的不同。
结合面向对象相关知识,子类继承父类,于是子类拥有了父类的成员,同时,子类本身又可以定义属于自己的成员,这样一来,一个子类对象中包含的数据,其实由两部分组成:父类包含的可继承的成员(成员变量和成员方法);
子类中自己定义的成员;
子类的内存映像
在知道了子类对象在内存中的组成结构之后,我们就能理解子类对象的初始化之所以和普通类对象的初始化不同,根本原因在于,子类对象初始化时要初始化父类成员和子类成员两部分数据。这两部分数据,在给他们赋初值的时候,谁先赋初值谁后赋初值?(谁先初始化值,谁后初始化值)
这种优先关系可以从2个角度去理解:
直觉:儿子能继承父亲的前提是,父亲先存在;
Jvm:子类成员变量的初始化可能依赖于父类成员;
java语言的结论是,一定是先初始化子类对象中继承的父类变量的值,后初始化子类对象中子类自己定义的成员变量值,即先父后子。
如何实现在子类对象中先父后子的成员变量值的初始化顺序呢?
核心思路:
首先,构造方法本身的功能就是初始化对象成员变量值的,所以,我们只需要保证父类的构造方法先于子类的构造方法运行。如何让父类构造方法,先于子类构造方法执行(在子类的构造方法的,第一条语句的位置,先去调用父类)。
子类的初始化方式:
a. 子类对象的隐式初始化方式(jvm来保证父类构造方法先执行)
b. 子类对象的显示初始化方式(coder通过写代码的方式保证父类构造方法先执行)
a.隐式初始化
当父类提供了默认的构造函数,且子类的构造函数中没有显式调用父类的其它构造函数,则在执行子类的构造函数之前会自动执行父类的构造函数,直到执行完Object的构造函数后才执行子类的构造函数。
super关键字:
this 代表当前这个对象,代表对象的内存空间标识(当前类定义的内容)。
super 代表父类对象的引用,代表对象的内存空间的标识(父类定义的内容),注意super关键字只能在子类中使用,同时在静态方法中既不能使用this也不能使用super。
用法(this和super均可如下使用)
- 访问成员变量
this.成员变量 super.成员变量 - 访问构造方法
this(…) super(…) - 访问成员方法
this.成员方法() super.成员方法()
b.显示初始化:
可以在子类构造函数的第一条语句通过super()或super(…)调用父类的默认构造函数或特定方法签名的构造函数;
当父类没有提供默认构造函数时(不是无参构造方法),必须在子类构造函数第一条语句通过super(…)完成父类对象成员的初始化工作。
4.父类域的隐藏
(1)在子类中是否可以定义和父类同名的成员变量?
可以。
(2)如果可以,那么通过子类对象调用子类定义的方法访问这个同名变量,究竟访问到的是父类对象中的值,还是子类对象中的值?
访问到的是子类中定义的同名成员变量值。
(3)如果通过子类对象调用父类定义的方法访问这个同名变量,究竟访问到的是父类对象中的值,还是子类对象中的值?
访问到的是父类中定义的同名成员变量值。
(4)是否可以在子类对象中,同时访问到子类对象和父类对象中的同名成员变量的值?
可以,在子类定义的方法中使用super.同名成员变量,访问父类中定义的父类同名成员变量值。
在父类和子类中,如果定义了同名成员变量,通过子类对象调用,记住以下结论:
a. 如果调用的是子类的方法,通过变量名访问同名成员变量,访问到的就是子类中定义的同名成员变量。
b. 如果调用的是父类的方法,通过变量名访问同名成员变量,访问到的就是父类中定义的同名成员变量。
对于子类中的方法,在子类对象上访问成员变量的时候,有一个查找规则:当我们在子类的方法体中访问子类对象的成员变量的时候,首先在子类对象中查找子类自己定义的成员变量中有没有目标成员变量,如果找到,就直接访问,如果在子类对象上,在子类自己定义的成员变量中没有目标成员变量,则会自动在父类对象上,查找目标成员变量,并访问。
上面问题的答案综合起来,其实说的就是域的隐藏。
5.方法的覆盖或重写(override)
(1)子类中能否定义和父类一模一样的方法?
可以。
(2)如果可以,那么在子类对象中访问这个一模一样的方法,究竟访问到的是父类定义的方法,还是子类定义的方法?
是子类中定义的方法。
(3)如果在父类对象中访问呢?
是子类中定义的方法。(方法覆盖)
说明的一点是:因为一旦在子类类体中,定义了和父类类体中一模一样的方法,在父类类体中,就无法访问到父类中自己定义的那个成员方法了,就好像父类的方法被子类中定义的那个一模一样的方法给覆盖掉了。
(4)是否可以在子类对象中,同时访问到子类和父类中定义的一模一样的方法?
可以,普通的方法调用到的是子类中定义的成员方法,通过super关键字在子类类体中可以访问父类定义的成员方法。
(5)当我们在子类对象上调用方法,该方法在运行时怎么确定运行的是哪个方法?
a. 当方法执行的时候(如果子类方法调用了其他方法),优先在子类中找目标方法,如果在子类中找到了目标方法,执行子类中定义的目标方法.
b. 如果说,在子类中没有找到目标方法,接着到父类中找目标方法,如果找到,就执行父类中定义的方法。
所谓的方法覆盖,子类中的方法并没有真正的物理覆盖父类的方法。
使用场景:方法覆盖经常使用在在子类中修改父类方法的方法实现(并非真正修改了父类中的方法)。
对于方法覆盖而言,发生的条件,必须是子类和父类中方法的定义一模一样吗?
对于方法覆盖的实现而言, 探究一下方法覆盖发生的条件: “一模一样”
主要看的是子类和父类的方法声明部分: 访问权限 返回值 方法签名(方法名+参数列表)
-
方法声明的访问权限的条件
并非子类和父类方法的访问权限要相同,只要子类方法的访问权限,不小于父类方法的访问权限 -
方法返回值
1) 基本数据类型的方法返回值:子类必须和父类的方法返回值返回值类型相同
2) 引用数据类型
a. 子类父类返回值类型相同
b. 子类方法返回值类型 是 父类方法返回值类型的 子类类型 (因为,可以把子类类型看做是父类类型) -
方法签名
子类方法的方法签名必须和父类一样(数据类型的角度)注意事项: 父类中不是所有的方法都能被子类覆盖 a.父类中私有方法不能被重写 b.静态方法不能被重写!!
6.final关键字
final是最终的意思,可以修饰类,变量,成员方法。
- 修饰类,类不能被继承;
- 修饰方法,方法不能被重写(override);
- 修饰变量,变量就变成了常量,只能被赋值一次;
final修饰变量时分为2种情况:
修饰局部变量:必须在使用局部变量之前,初始化一次局部变量的值,且仅一次;
修饰成员变量:(不包括jvm默认赋值的那次赋值),保证在对象创建完毕之前,我们coder自己给被final修饰的成员变量赋值,且只能赋值一次;
final int a = 10;
被final修饰的变量,我们也称之为自定义常量,
常量:
a.字面值常量: 1 2 “zhangsan”
b.自定义常量 final int A;
注意:final修饰引用变量时,仅仅只是引用变量,但final引用变量指向的对象的值可以多次赋值修改。
四、面向对象3大特征之多态
Java语言中的多态指什么呢? “同一个对象”的行为,在不同的时刻或条件下,表现出不同的效果。(这里的同一个对象之所以打引号,其实指的是同一个引用变量)。
1.多态实现的前提条件:
- 继承
- 方法覆盖
- 父类引用指向子类对象
2.成员访问特征:
- 成员变量
编译看左边,运行看左边 - 成员方法
编译看左边,运行看右边
解释:
(1)编译看左边: 父类引用指向子类对象,此时编译看左边是在说,通过引用变量可以访问到的子类成员的范围是由引用类型来决定的。
将引用变量比作遥控器,将引用变量指向的对象比作电视,即使电视有很多功能,但遥控器只提供有限的功能 ,我们也只能使用到这些有限的功能。
所以,回到java程序,访问对象的时候,引用变量就是我们用来操作对象的"遥控器",引用变量的类型决定了可以访问到的成员的范围。
(2)对于成员变量,运行看左边
一个对象有属性(成员变量) 和行为,一个对象的属性(成员变量)表示了一个对象的外貌。在多态中,此时对于子类对象而言,把子类对象赋值给父类类型的引用,就相当于给子类对象披上了一个父类类型马甲, 因此,该子类对象看起来就是一个父类对象(外貌特征,
表现出的就应该是父类的外貌特征)。
(3)对于成员方法的,运行看右边
就是说对于成员方法而言,通过引用变量,实际访问到的行为(方法), 是由引用实际指向的对象来决定的。
3.多态的好处
提高了程序的维护性(由继承保证)
提高了程序的扩展性(由多态保证)
4.多态的弊端
无法通过父类类型的引用,去访问子类特有方法。
如果能把引用变量的类型,改成子类类型的引用变量,问题似乎就可以解决了。
子类类型的引用
a. 子类类型的引用变量 -> 父类类型的引用变量(向上转型) 编译器天然允许的
b. 父类类型的引用变量 -> 子类类型的引用变量(向下转型) 默认编译器不允许, 因为一旦赋值不恰当,可能引起错误
对于引用变量,我们也可以做强制类型转化
子类类型 引用变量 = (子类类型)父类型的引用变量
instanceof 关键字(运算符):判断目标对象是否是指定类型的对象,运算类型:boolean
对象名(对象的引用变量) instanceof 实例类型(目标类的类名)
引用变量:不管什么类型的引用变量,都可以被赋值null,如果
null instanceof 任意一个类
结果固定始终是false。
null不是任何一个类型的对象。
#个人学习记录,如发现有错误之处,欢迎与我交流