目录
一 封装
1.1 概述
1.2 为什么要进行封装?
1.3 Java中的封装
1.4 四种访问权限修饰符
1.5 练习
二 继承
2.1 继承的由来
2.2 继承的好处
2.3 语法格式
2.4 继承的特点之一:成员变量
2.4.1 父类成员变量私有化
2.4.2 父类和子类成员变量重名
2.4.3 成员变量的访问特点
2.5 继承的特点之二:成员方法
2.5.1成员方法的访问特点
2.5.2方法重写( override )
2.5.2 方法重载( overload )
2.6 继承的特点之三:构造方法
2.7 继承的特点之四:单继承
三 多态
3.1 概述
3.2多态的应用场景
3.3 多态中调用成员的特点
3.3.1示例
3.3.2多态调用成员的内存图解
3.4多态的优势和弊端
3.4.1 多态的优势
3.4.2多态的弊端
一 封装
1.1 概述
对象代表什么,就得封装对应的数据,并提供数据对呀的行为
- 程序设计追求
高内聚、低耦合
:
-
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉。
- 低耦合:仅对外暴露少量的方法用于使用。
- 封装:隐藏对象内部的复杂性,只对外部公开简单的接口,便于外界调用,从而提高系统的可扩展性、可维护性。换言之,
将该隐藏的隐藏起来,该暴露的暴露出来
。
1.2 为什么要进行封装?
- 使用者对类内部定义的属性(对象的成员变量)的直接操作会导致数据的错误、混乱或安全性问题。
- 示例:
package com.encapsulation.demo1; /** * 动物 * * @author 逆水行舟 * @version 1.0 * @since 2022-12-01 */ public class Animal { /** * 腿 */ public int legs; /** * 吃饭的方法 */ public void eat() { System.out.println("吃饭"); } /** * 移动的方法 */ public void move() { System.out.println("移动"); } }
package com.encapsulation.demo1; /** * @author 逆水行舟 * @version 1.0 * @since 2022-12-01 */ public class AnimalTest { public static void main(String[] args) { Animal animal = new Animal(); // 如果这边赋值的是-100?,不合法啊 // 应该将legs属性保护起来,防止乱用 animal.legs = 4; System.out.println(animal.legs); // 4 animal.eat(); animal.move(); } }
1.3 Java中的封装
- Java 中通过将数据声明为私有的(
private
),再提供公共的(public
)方法:setXxx()
和getXxx()
来实现对该属性的操作,以实现以下的目的: - ① 隐藏一个类中不需要对外提供的实现细节。
- ② 使用者只能通过事先定制好的
方法来访问数据
,可以方便的加入控制逻辑,限制对属性的不合理操作。 - ③ 便于修改,增强代码的维护性。
- 示例:
package com.encapsulation.demo2; /** * 动物 * * @author 逆水行舟 * @version 1.0 * @since 2022-12-01 */ public class Animal { /** * 腿 将属性进行私有化 */ private int legs; public int getLegs() { return this.legs; } public void setLegs(int legs) { if (legs != 0 && legs != 2 && legs != 3) { System.out.println("动物的腿一般为0、2、4"); return; } this.legs = legs; } /** * 吃饭的方法 */ public void eat() { System.out.println("吃饭"); } /** * 移动的方法 */ public void move() { System.out.println("移动"); } }
package com.encapsulation.demo2; /** * @author 逆水行舟 * @version 1.0 * @since 2022-12-01 */ public class AnimalTest { public static void main(String[] args) { Animal animal = new Animal(); // 非法 动物的腿一般为0、2、4 animal.setLegs(-100); System.out.println(animal.getLegs()); animal.eat(); animal.move(); } }
1.4 四种访问权限修饰符
- Java 权限修饰符 public、protected 、缺省 、private 置于
类的成员
定义前,用来限定对象对该类成员的访问权限。
修饰符 | 类内部 | 同一个包中 | 不同包的子类 | 同一个工程 |
private | √ | |||
缺省 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
- 对于 class 的权限修饰符只可以用 public 和 缺省 :
-
- public 类可以在任意地方被访问。
- 缺省类 只可以被同一个包内部的类访问。
1.5 练习
- 创建程序,在其中定义两个类:Person 和 PersonTest 。定义如下:用 setAge() 设置人的合法年龄( 0~130 ),用 getAge() 返回人的年龄。用PersonTest 类中实例化 Person 类的对象 b ,调用 setAge() 和 getAge() 方法,体会 Java 的封装性。
- 示例:
package com.encapsulation.demo3; /** * @author 逆水行舟 * @version 1.0 * @since 2022-12-01 */ public class Person { /** * 年龄 */ private int age; /** * 获取年龄 * * @return int */ public int getAge() { return this.age; } /** * 设定年龄 * * @param age * 年龄 */ public void setAge(int age) { if (age < 0 || age > 130) { System.out.println("输入不合法"); return; } this.age = age; } @Override public String toString() { return "Person{" + "age=" + this.age + '}'; } }
package com.encapsulation.demo3; /** * @author 逆水行舟 * @version 1.0 * @since 2022-12-01 */ public class PersonTest { public static void main(String[] args) { Person person = new Person(); person.setAge(50); System.out.println(person.getAge()); System.out.println(person); } }
二 继承
2.1 继承的由来
- 多个类中存在相同属性和行为的时候,将这些内容抽取到单独的一个类中,那么多个类中无需再定义这些属性和行为,只需要继承那个类即可。
- 此处的多个类称为
子类(派生类)
,单独的这个类称为父类(基类或超类)
。 - 继承描述的是事物之间的所属关系,这种关系是:
is a
的关系。例如:图中的猫属于动物,狗也属于动物。由此可见,父类更通用,子类更具体。通过继承,可以使得多种事物之间形成一种关系体系。
2.2 继承的好处
- ① 继承的出现,减少了代码的冗余(重复),提供了代码的复用性。
- ② 继承的出现,有利于功能的扩展。
- ③ 继承的出现,让类和类之间产生了关系,提供了多态的前提。
注意:不要为了获取其它类中的某个功能而去继承。
2.3 语法格式
- 在 Java 中,继承是通过
extends
关键字,声明一个子类继承一个父类。 - 语法:
修饰符 class 父类{ ... } 修饰符 class 子类 extends 父类 { ... }
- 示例
public class Animal { String name; int age; public void eat(){ System.out.println("动物:吃"); } public void drink(){ System.out.println("动物:喝"); } private void name(){ System.out.println("名字"); } }
public class Cat extends Animal { public void catchMouse(){ System.out.println("捕捉"); } } public class Dog extends Animal{ public void look(){ System.out.println("看护"); } }
public class Tes { public static void main(String[] args) { Cat cat=new Cat(); cat.eat(); cat.drink(); //子类只能访问父类中非私有的成员 } }
2.4 继承的特点之一:成员变量
2.4.1 父类成员变量私有化
●父类的成员,无论是公有( public )还是私有( private ),均为被子类所继承。
●子类虽然会继承父类私有的( private )的成员,但是子类不能对继承的私有成员直接进行访问,却可以通过继承的 setter 和 getter 方法进行范访问。
- 非私有成员变量继承的内存图
●示例:
public class Animal { private String name; int age; public void eat(){ System.out.println("动物:吃"); } public void drink(){ System.out.println("动物:喝"); } private void name(){ System.out.println("名字"); } public String getName() { return name; } public void setName(String name) { this.name = name; } }
public class Cat extends Animal { public void catchMouse(){ System.out.println("捕捉"); } }
public class Tes { public static void main(String[] args) { Cat cat=new Cat(); // cat.name = "Jerry"; 编译报错 cat.setName("aaa"); } }
2.4.2 父类和子类成员变量重名
- 子类会继承父类所有的成员变量,那么如果子类出现和父类同名的成员变量会怎么样?
- 父类代码:
public class Animal { /** * 年龄 */ int age = 88; /** * 姓名 */ private String name; public String getName() { return this.name; } public void setName(String name) { this.name = name; } }
- 子类代码:
public class Cat extends Animal { int age = 5; public void showAge() { System.out.println("Cat的年龄是:" + this.age + ",Animal的年龄是:" + super.age); } }
- 测试代码:
public class Test { public static void main(String[] args) { Cat cat = new Cat(); cat.setName("Jerry"); cat.showAge(); } }
- 总结:
-
- ① 当父类的成员变量私有化的时候,在子类中是无法直接访问的,所以是否重名没有任何影响;但是,如果想访问父类的私有成员变量,只能通过父类提供的 setter 和 getter 访问。
- ② 当父类的成员变量非私有化的时候,在子类中是可以直接访问的,所以如果有重名,就需要加上
super.父类成员变量名
来进行区分。
注意:在实际开发中,虽然我们可以通过 super 关键字来实现区分父子类重名成员变量,但是不建议这么干。
2.4.3 成员变量的访问特点
继承中:成员变量的访问特点————就近原则
先在局部位置找,本类成员位置找,父类成员位置找,逐级往上
name:从局部位置开始往上找
this.name:从本类成员位置开始往上找
supper.name:从父类成员位置开始往上找
- 示例
public class Fu {
String name="fu";
}
class zi extends Fu{
String name="zi";
public void ziShow(){
String name="ziShow";
System.out.println(name); //就近原则:谁离我近,我就用谁
System.out.println(this.name);//name="zi"
System.out.println(super.name);//name="fu"
}
}
2.5 继承的特点之二:成员方法
成员方法是否可以被继承?
只有父类中的虚方法才能被子类继承
虚方法:非private,非static,非final
- 继承内存图
2.5.1成员方法的访问特点
在没有supper的情况下:也遵循就近原则,this调用时逐级往上寻找
- 示例:
class Person{ public void eat(){ System.out.println("吃米饭,吃菜"); } public void drink(){ System.out.println("喝豆浆"); } } class Student extends Person { public void lunch(){ //先在本类查看eat和drink方法,就会调用子类的,如果没有,就会调用父类中继承下来的eat和drink方法 this.eat(); this.drink(); //直接调用父类中的方法 super.eat(); super.drink(); } }
2.5.2方法重写( override )
- 定义:在子类中可以根据需要对从父类中继承而来的方法进行改造,也称为方法的
重置
、覆盖
。在程序执行的时候,子类的方法将覆盖父类的方法。 - 要求:
-
- ① 子类重写的方法
必须
和父类被重写的方法具有相同的方法名称
、参数列表
。 - ② 子类重写的方法的返回值类型
不能大于
父类被重写的方法的返回值类型(这对于返回值类型为引用数据类型来说的,如果返回值类型是基本数据类型和 void 类型必须相同,换言之,只有继承,才会有父类和子类)。 - ③ 子类重写的方法使用的访问权限
不能小于
父类被重写的方法的访问权限(注意:子类不能重写父类中声明为privat权限的方法或final修饰的方法
)。 - ④ 子类方法抛出的异常不能大于父类被重写方法的异常。
- ① 子类重写的方法
注意: 子类和父类中同名同参数的方法必须同时声明为非 static的(重写),或者同时声明为 static 的(不是重写,因为 static 方法是属于类的,子类无法覆盖父类的方法。
- 示例:
public class Phone { public void sendMessage() { System.out.println("发送短信"); } public void call() { System.out.println("打电话"); } public void showNum() { System.out.println("显示来电号码"); } }
public class SmartPhone extends Phone { /** * 重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能 */ @Override public void showNum() { // super.父类成员方法,表示调用父类的成员方法。 super.showNum(); // 增加自己的显示姓名和图片功能 System.out.println("显示来电姓名"); System.out.println("显示头像"); } }
public class SmartPhoneTest { public static void main(String[] args) { SmartPhone smartPhone = new SmartPhone(); // 调用父类继承而来的方法 smartPhone.call(); // 调用子类重写的方法 smartPhone.showNum(); } }
打电话
显示来电号码
显示来电姓名
显示头像
2.5.2 方法重载( overload )
- 在同一类中:
public class Overload { public int max(int a, int b) { return a > b ? a : b; } public double max(double a, double b) { return a > b ? a : b; } public int max(int a, int b, int c) { return max(this.max(a, b), c); } }
- 在父子类中:
public class Father { public int max(int a, int b) { return a > b ? a : b; } }
public class Son extends Father { public double max(double a, double b) { return a > b ? a : b; } }
参考 05 面向对象-上 第九章方法重载
2.6 继承的特点之三:构造方法
- 构造方法的定义:
-
- ① 构造方法的名称和类名是一致的。
- ② 构造器不声明返回值类型(和声明 void 不同)。
- ③ 构造器不能被 static 、final 、synchronized 、abstract 、native 修饰,不能有 return 语句返回值。
- 所以,子类是无法继承 父类的构造方法的。
- 构造方法的作用:初始化实例变量的。但是,子类又会从父类那边继承所有成员变量,所以子类在初始化的过程中,必须先执行父类的初始化动作(子类的构造方法中默认有一个
super()
,表示调用父类的实例初始化方法,父类成员变量初始化后,才可以给子类使用),才能执行自己的初始化动作。
继承中构造方法访问特点是什么?
- 子类不能继承父类的构造方法,但是可以通过supper()调用
- 子类构造方法的第一行,有默认的supper()
- 默认先访问父类的无参构造方法,再执行自己
- 如果想要方法的父类有参构造,必须手动书写
2.7 继承的特点之四:单继承
- ① Java 只支持单继承,不支持多继承。
// 一个类只能有一个父类 class C extends A{}
- ② Java 支持多层继承。
class A{}
class B extends A{}
class C extends B{}
注意:顶层父类是 Object 类,所有类默认继承 Object 类作为父类。
- ③ 子类和父类是一种相对概念。比如:B 类相对于 A 类来说是子类,但是相对于 C 类来说是父类。
- ④ 一个父类可以同时有多个子类。
三 多态
3.1 概述
- 什么是多态?
同类型的对象,表现出不同形态;对象的多种形态
2.表现形式:
父类类型 变量名 = new 子类类名();
温馨提示:多态也可以应用在抽象类和接口上。
3.多态的前提条件:
-
- ① 有继承关系,或实现关系。
- ② 有方法重写。
- ③ 父类引用指向子类对象。:Fu f=new Zi();
- 示例(不同用户注册)
- 父类Person
/** * @BelongsProject: StudyList * @BelongsPackage: com * @Author: GXY * @CreateTime: 2022-12-23 15:58 * @Description: TODO * @Version: 1.0 */ package com; public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void show(){ System.out.println("name:"+name+",age:"+age); } }
子类1:管理员
package com; public class Admin extends Person{ @Override public void show() { System.out.println("管理员的信息为:"+getName()+","+getAge()); } }
子类2:老师
public class Teacher extends Person{ @Override public void show() { System.out.println("老师的信息为:"+getName()+","+getAge()); } }
子类3:学生
public class Student extends Person{ @Override public void show() { System.out.println("学生的信息为:"+getName()+","+getAge()); } }
测试类:
public class Test { public static void main(String[] args) { Student student=new Student(); student.setName("张三"); student.setAge(18); Teacher teacher=new Teacher(); teacher.setName("苍老师"); teacher.setAge(25); Admin admin=new Admin(); admin.setName("admin"); admin.setAge(25); register(student); register(teacher); register(admin); } //这个方法既能接收学生,又能接收老师,又能接收管理员 //只能写成这三个的父类 public static void register(Person person){ person.show(); } }
打印结果:
学生的信息为:张三,18
老师的信息为:苍老师,25
管理员的信息为:admin,25
4.多态的好处:
使用父类类型作为参数,可以接收所有子类类型,体现多态的扩展性和便利性。
3.2多态的应用场景
3.3 多态中调用成员的特点
- 成员变量:编译看左边,运行也看左边
- 成员方法:编译看左边,运行看右边
3.3.1示例
-
- 成员变量:在子类的对象中,会把父类的成员变量也继承下的。
- 成员方法:如果子类对方法进行了重写,那么在虚方法表中是会把父类的方法进行覆盖的。
public class Test2 { public static void main(String[] args) { Animal animal=new Dog(); System.out.println(animal.name);//动物 1 animal.show(); //Dog--------show } } class Animal{ String name="动物"; public void show(){ System.out.println("Admin--------show"); } } class Dog extends Animal{ String name="狗"; @Override public void show(){ System.out.println("Dog--------show"); } } class Cat extends Animal{ String name="猫"; @Override public void show(){ System.out.println("Cat--------show"); } }
3.3.2多态调用成员的内存图解
3.4多态的优势和弊端
3.4.1 多态的优势
- 在多态形势下,右边对象可以实现解耦合,便于扩展和维护。
Person p= new Student();
p.work();//业务逻辑发生改变时,后续代码无需修改
- 定义方法的时候,使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性和便利性。
StringBuilder sb=new StringBuilder();
3.4.2多态的弊端
- 不能调用子类的特有功能
public class Test2 { public static void main(String[] args) { Animal animal=new Dog(); System.out.println(animal.name);//动物 1 animal.show(); //Dog--------show //那么在编译的时候会先检查左边的父类中有没有这个方法,若果没有直接报错 animal.lookHome(); 解决方案: } } class Animal{ String name="动物"; public void show(){ System.out.println("Admin--------show");! } } class Dog extends Animal{ String name="狗"; @Override public void show(){ System.out.println("Dog--------show"); } public void lookHome(){ System.out.println("Dog--------看家"); } } class Cat extends Animal{ String name="猫"; @Override public void show(){ System.out.println("Cat--------show"); } public void catchMouse(){ System.out.println("Cat--------捉老鼠"); } }
- 解决方案:
//变回子类类型就可以了
//细节:转换的时候不能瞎转,如果转成其他类的类型,就会报错
//Cat c = (Cat) a;
//c.catchMouse();
- 转换的时候不能瞎转,如果转成其他类的类型,就会报错
//先判断:转换类型与真实类型是否一致 instanceof if(a instanceof Dog){ Dog d= (Dog) a; d.1ookHome(); }else if(a instanceof Cat){ CatC=(Cat) a; c.catchMouse(); }else{ System.out.printin(“没有这个类型,无法转换"); }
- jdk14新特性:先判断就转换
if(a instanceof Dog d){ d.1ookHome(); }