目录
封装
什么是封装
封装:隐藏对象的属性和实现细节,仅仅对外提供公开接口来和对象进行交互。
在类和对象一节,我们当时谈到了面向对象和面向过程的区别,还举了一个洗衣服的例子。那现在我们回到这个例子
例子:洗衣服
面向过程:
- 收拾脏衣服
- 将脏衣服放到盆子里
- 放水
- 放洗衣液
- 搓衣服
- 洗去泡沫
- 换水
- 搓衣服
- 拧干
- 晾晒
面向对象:
在洗衣服事件中涉及到的对象有:
- 洗衣机
- 洗衣服的人
- 洗衣粉
- 衣服
在面向对象时,我们将洗衣服的具体实现(3456789)隐藏了起来,让洗衣机对象去实现这些步骤,我们不用关心。洗衣机只需要对外提供按钮来和洗衣服的人进行交互,那么我们就可以将这个理解为封装。
封装的实现
Java提供了访问限定符来对类的成员变量和方法的访问权限进行控制,同时也引入了包机制对类的访问进行控制。
继承
什么是继承
“世界上没有两片完全相同的树叶”,同时,世界上也没有两片完全不同的树叶(沃兹基硕德)。任何两片树叶,他们之间一定存在着相同的地方,比如:都是树叶。同时,也存在着不同的地方,比如形状颜色等。
那么让我们把格局打开,跳出树叶这个例子,就会发现世界上的事物其实都存在着关联,他们会有相同的地方,也会有不同的地方。那么,我们在用编程语言去描绘这个世界的实体的时候,就需要将这些关联考虑进去,因此,在面向对象的语言中,存在着继承的特性,我们用继承来描述事物与事物之间的共性和特性。
例子:在一个游戏开发项目中,我们定义了这样的三个类,用户类,成年人类,未成年人类。这三个用户的关系是这样的,在游戏用户之中,有成年人,未成年人,成年人和未成年人之间存在着共性(比如都有自己的id),也有着自己的特性(登录游戏的时间),因此,我们可以将共性的部分抽取出来,放在我们在父类之中,我们的子类在继承父类的基础上(共性),扩展自己的功能和属性(特性)。
继承:用来抽取共性,实现代码复用。
父类:又称超类,基类。用来保存子类的共性的类,可以直接被继承。
子类:又称派生类,子类可以继承父类的所有成员,同时也可以定义属于自己的成员。
继承的语法
修饰符 class 子类名 extends 父类 {
成员属性
成员方法
}
//用户类
class User {
public String name;
public String ID;
public int loginTime;
//登录功能
public void login() {
System.out.println("登录功能");
}
}
//成年用户类
class AdultUser extends User {
//充值功能
public void recharge() {
System.out.println("氪金功能");
}
}
//未成年用户类
class MinorUser extends User {
//强制下线功能
public void downLine() {
System.out.println("强制下线");
}
}
public class Test {
public static void main(String[] args) {
MinorUser minorUser = new MinorUser();
minorUser.name = "那小子真帅"; //继承父类得来的
minorUser.ID = "16888"; //继承父类得来的
minorUser.login(); //继承父类得来的
minorUser.downLine(); //子类自己定义的
AdultUser adultUser = new AdultUser();
adultUser.name = "一剑霜寒十四州"; //继承父类得来的
adultUser.ID = "52588"; //继承父类得来的
adultUser.login(); //继承父类得来的
adultUser.recharge(); //子类自己定义的
//在成年用户和未成年用户之中,我们并没有定义名字和id属性以及登录功能,
//所以这两个属性和登录功能一定是从父类用户类继承来的
//除了可以访问从父类继承来的功能和属性,还可以访问子类自己定义的属性和方法。
}
}
总结:子类会继承父类的成员方法和属性,在访问范围允许的情况下,子类可以直接使用父类的成员和属性。
成员变量访问
子类和父类成员不同名情况:在访问成员变量时,优先在子类当中查找,如果子类当中查无这个成员,就去父类当中找。
子类和父类成员同名情况:和不同名情况类似,优先在子类当中查找,如果子类有该成员,则优先访问子类自己的成员。
public class Tset {
public static void main(String[] args) {
Base base = new Base(); //创建一个父类对象
Derived derived = new Derived(); //创建一个子类对象
System.out.println(derived.d); //访问子类成员 d -->打印666
System.out.println(derived.c); //访问子类成员 c -->打印999
//父类和子类的同名变量,优先访问子类的
}
}
class Base {
public int a;
public int b;
public int c = 99;
}
class Derived extends Base {
public int c = 999;
public int d = 666;
}
注意:子类和父类成员名相同,但是类型不同时,依旧是优先访问子类的成员。
成员方法访问
成员方法名不同:和成员变量的访问相同,成员方法名不同时,依旧是优先在子类当中查找,子类有就访问子类的成员方法,子类没有就到父类当中查找,父类有就访问父类的成员方法,父类也没有就报错。
成员方法名相同:方法名相同时存在两种情况。
- 构成重载:参数列表不同时,根据调用方法时传递的参数选择合适的方法。
- 不构成重载:优先访问子类的成员方法。
总结:在存在继承关系的子类中,在进行访问成员变量和成员方法时,始终坚持“就近原则”。即优先访问自己的成员。
super关键字
在成员访问时,Java语法坚持了就近原则,优先访问子类成员,那么,为了访问到父类成员,java引入了super关键字。值得说清楚的是,和this是个引用变量不相同,super只是一个关键字,仅仅说明此刻我们访问的是父类成员而已,并非是引用变量。在使用super关键字时,并没有创建一个父类对象,super也没有指向一个父类对象。
super关键字的作用:在子类方法当中访问父类成员。
super关键字访问父类成员
public class Tset {
public static void main(String[] args) {
Base base = new Base();
Derived derived = new Derived();
derived.func2();
}
}
class Base {
int a = 99;
public void func() {
System.out.println("base");
}
}
class Derived extends Base{
int a = 88;
public void func() {
System.out.println("derived");
}
public void func2() {
System.out.println(a); //访问子类的成员变量a
System.out.println(super.a); //访问父类的成员变量a
func(); //访问子类的func()方法
super.func(); //访问父类的func()方法
}
}
super调用父类构造方法
在实例化一个子类对象时,需要调用构造方法对子类成员进行初始化。但是,我们要怎么初始化父类的成员呢?这里就涉及到了super调用父类构造方法了。
在子类对象构造时,需要先调用父类的构造方法,对父类成员进行初始化,再调用子类的构造方法,对子类成员进行初始化。可以简单认为,将父类的构造方法放到了子类构造方法的最前面,只有将父类的构造方法的代码执行完毕,才能执行子类的构造方法代码。
class Base {
int a = 99;
public Base () {
}
}
class Derived extends Base{
int b = 88;
public Derived() {
super(); //如果用户自己不调用,编译器会自动帮助我们隐式调用
}
}
子类构造方法的第一行代码一定是调用父类的构造方法,如果用户不调用,编译器自动帮助我们调用父类没有参数的构造方法。
总结
- 可以通过super在子类方法当中访问父类成员和方法。格式:super.data
- 在子类构造方法第一行,通过super调用父类构造方法。在构造方法当中,super构造和this构造不能同时出现。(因为这两个都要求出现在第一行,存在冲突)
- 如果显式定义父类的有参构造方法,则需要用户显示定义子类构造方法,并且在构造方法第一行通过super调用父类的有参构造。
- super关键字必须在非静态方法当中使用。
辨析:super和this的异同
多态
什么是多态
多态,可以简单理解为对同一件事物,不同对象会做出不同的响应。比如动物都会叫,但是鸡是咯咯叫,狗是汪汪叫,猫是喵喵叫。
多态的实现
在了解多态的实现之前,还需要先了解几个知识点。
重写
重写:重写又称覆盖,是指子类对继承来的父类方法的方法体部分做出需要的修改,其他部分保持不变的操作。重写的优点是可以在保留父类原有功能的基础上,对父类方法进行扩展或者是做出需要的修改。
重写的条件
- 存在于继承体系当中,子类继承父类的方法
- 方法的参数名,参数列表,返回值不变
- 静态方法,构造方法,private修饰的方法,final修饰的方法不能重写
- 子类重写方法的修饰权限必须不小于父类被重写方法的修饰权限。如父类被protected修饰,子类可以为public或者protected,但是不能为默认权限或者private。
重写方法实例
注意:
- 如果重写方法的返回值是一个类,则子类和父类的返回值可以不相同,但是要求父类和子类的返回值存在继承关系。
- @override 是编译器提供的注解符,可以加也可以不加,如果加上了,编译器会自动帮助用户检测重写的方法是否合法,如果不合法则会报错, 如果不加上,就算方法不合法,编译器也不会检测到错误,所以,推荐最好加上。
重写和重载的区别
向上转型
向上转型:父类引用接收子类对象。将一个子类对象当成父类对象来使用。
向上转型的应用场景:
- 直接赋值
- 方法返回
- 方法参数
向上转型的优缺点
优点:可以用一个父类引用接收不同子类的对象,让代码实现更灵活,不需要重复定义。
缺点:父类引用不能调用子类的特有方法。
向下转型
向下转型:子类引用接收父类引用,需要强制转换。向下转型需要将子类对象先向上转型给父类引用,再将父类引用强制类型转换为子类对象。要求父类引用实际上指向的是子类,否则运行时会报错。
向下转型的两种常见错误:
向下转型的缺点:不安全,容易出现以上两个错误。
instanceof关键字
为了保证向下转型的安全型,java语法引入了instanceof关键字,该关键字的作用是判断父类引用是否真的指向需要转型的子类对象。
动态绑定
动态绑定:接收子类对象的父类引用调用子类父类重写的重名的方法。在程序运行时,编译器调用子类重写的方法。动态绑定运行的结果就是多态思想的提现,同时,动态绑定是多态的前提。
动态绑定的条件:
- 父类引用接收子类对象
- 子类重写父类方法
- 父类引用调用子类父类重写的同名方法
- 到这里,我们就可以得出多态的实现条件就是存在动态绑定。多态的体现则是调用不同类对象时,会得到不一样的响应,这个响应不同是由于调用了不同类的重写方法。
- 编译器在编译时,并不清楚应该调用哪个子类对象的方法,只有等到程序开始运行,父类引用的指向的对象确定后,才能知道调用哪个子类的方法
多态的优点缺点
优点:利用多态思想,可以让代码的实现更加灵活,只需要一个接口就可以接收不同类型的对象。同时,代码的可扩展能力增强,在新增一个子类对象时,同样可以用该接口去接收,如果没有多态则需要对代码做出较大的变动。
缺点:无法调用子类的专属方法。
避免在构造方法方法当中调用重写方法
构造 Derived 对象的时, 会先会调用 父类Base 的构造方法,Base 的构造方法中调用了 func 方法, 此时会触发动态绑定, 调用到 Derived 中的 func。此时 Derived的构造方法还没被调用,所以,此时num还没有初始化,所以打印的结果0。(此处涉及到代码块的执行顺序问题,有兴趣的小伙伴可以自己调试一下)