1.类的继承
- 我们生活中包含所属关系的事物,在代码层面来说就是一种继承关系
- 类的继承格式:public class 子类(派生类) extends 父类(超类/基类){ }
- 每一个子类它的父类都是唯一的
- 接口支持多继承,而类只能单继承
Java中继承的特点:
- 单继承(一对多):每个类的父类是唯一的,一个父类可以有多个子类,多个子类只能有一个父类
- 多层次继承:父类可以有父类,子类可以有子类
- 继承的作用:子类会继承父类中所有的属性和方法,但是私有属性和方法不能直接使用(可以通过set,get方法来使用)
2.方法重写/覆盖(前提:在不同类中,且有继承关系)
- 方法重写/覆盖的目的:父类里面设计好了几个方法,在子类里面也要用,但是从父类继承过来的方法,它的功能在子类中已经不适用了/不能满足子类的需求(父类功能不能满足子类需求),但是整个方法的定义还是相同,只是去修改它的功能。
方法重写/覆盖的两个条件:
- 有继承关系的两个类之间(因为重写是重写父类里面的方法)
- (访问修饰符大于等于父类),方法名,返回值类型,参数类型(个数,顺序)完全一样
重写方法有两种情况:
- 完全覆盖/替换掉了父类的功能
- 在原有父类的功能的基础上去增加新的功能(保留父类的功能):
a.保留父类原有的功能:调用父类的方法
super(超类):表示当前类的父类对象 this:表示当前类的对象
b. 重写方法后新添加的功能
方法重载(前提:在同一类中):可以让同一个方法实现不同的功能
1.方法名相同/重名
2.形参列表不同(方法的参数类型,个数,顺序不一样)
构造方法重载的特点:利用不同的构造方法可以创建出不同需求的对象。
package com.com;
public class Student {
// 类的结构:类里面可以定义属性和方法
// 属性: 格式: 访问修饰符 数据类型 变量名/属性名;
// 4种可见修饰符:public protected friendly(default) private
// 加了private修饰符,只能在当前类访问
private String name;
private int score;
public String getName() {
return name;
}
public void setName(String name) {
= name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public void study(){
System.out.println(name + "正在学习!学分是:" + score);
}
}
package com.com;
/**
* 定义大学生类
*/
// 继承的格式: public class 子类 extends 父类
public class UniversityStudent extends Student{
public void printName(){
System.out.println("Name = " + getName());
}
// 方法重写
public void study(){
// 保留父类原有的功能
super.study(); // super:表示当前类的父类对象
// 重写方法后添加新的功能
System.out.println("子类重写父类的方法!");
}
}
package com.com;
public class Manage {
// 主函数
public static void main(String[] args) {
// 创建对象
UniversityStudent us = new UniversityStudent();
us.setName("小李");
us.setScore(80);
us.printName(); // Name = 小李
us.study(); // 小李正在学习!学分是:80
// 子类重写父类的方法!
System.out.println("学生的学分是:" + us.getScore()); // 学生的学分是:80
}
}
3.继承的自动转型
1.向上转型(自动转型):把子类对象转成父类类型
- 前提:B extends A ===> 才能A a = new B( );
- 如果没有这种继承关系,就不存在什么自动转型,这种创建对象的过程我们把它叫做向上转型/自动转型。
- 父类引用指向子类对象,可以把子类对象转成父类类型。
- 当我们把一个子类对象转型成父类类型之后,拿着这个对象去调用的每一个方法,它都是从父类中开始检测有没有这个方法,没有肯定是不能执行的,如果有,看子类有没有重写,如果子类重写优先执行子类重写的方法,因为这个对象是从子类转型过来的。
- 自动转型后,该对象只能调用子类中重写的方法。
- 它可以在程序运行过程中,给你的对象向上转型。
2.向下转型(强制转型):把父类对象转成子类类型
- 前提:强制转型的前提是该对象已经自动转型。
- B b = (B)a;把父类对象转成子类类型。
- 在对象进行向下转型时,必须首先发生对象向上转型,否则运行时将会出现对象转换异常/强制类型转换异常(ClassCastException)。
- 当我们把一个父类对象转成子类类型之后,拿着这个对象去调用每一个方法,它都是从子类中开始找有没有定义过这个方法,如果子类没有,看从父类里面有没有继承过。
package com.extendsoop;
public class Student {
// 属性
private String name;
// 方法
public void study(){
System.out.println(name + "正在学习!");
}
public void setName(String name){
= name;
}
public String getName(){
return name;
}
}
package com.extendsoop;
// 定义大学生类: public class 子类(派生类) extends 父类(超类/基类){}
// 接口支持多继承,类只能单继承
// 子类会继承父类中所有的属性和方法,但是私有属性和方法不能直接使用
public class UniversityStudent extends Student{
// 方法
public void printName(){
System.out.println("Name = " + getName());
}
// 方法重写(Override) 从父类继承过来的方法已经不能满足子类的需求
public void study(){
// 1.保留父类原有的功能:调用父类的方法
// this:表示本类对象/当前类对象 super:表示当前类的父类对象 每一个子类它的父类都是唯一的
super.study();
// 2.重写方法后添加新的功能
System.out.println("子类重写父类的学习方法!");
}
}
package com.extendsoop;
public class Manage {
// 主函数
public static void main(String[] args) {
// UniversityStudent extends Student
// 向上转型/自动转型(前提:继承):把子类对象转成父类类型
// 父类引用指向子类对象
Student st = new UniversityStudent();
st.study(); // 调用的是子类重写的方法
st.setName("Rocket");
// st.printName(); 直接报错,因为在父类Student中没有这个方法
// 强制转型/向下转型(前提:该对象已经发生自动转型/向上转型):把父类对象转成子类类型
UniversityStudent us = (UniversityStudent)st;
us.printName();
us.study(); // 调用的是子类重写的方法
}
}
4.继承的自动转型的应用场景(多态的应用)
- 方法需要什么类型的参数就创建什么类型的对象给它。
- 每个类都会默认继承Object类,Object是所有类的父类。
- 我们调用一个方法,它的参数类型除了可以给它本身这种类型之外,实际上只要是它的子类都可以。
- 自动转型/向上转型的好处:使方法的参数类型变得不唯一了。
package com.extendsoop;
public class Student {
// 属性
private String name;
// 方法
public void study(){
System.out.println(name + "正在学习!");
}
public void setName(String name){
= name;
}
public String getName(){
return name;
}
}
package com.extendsoop;
// 定义大学生类: public class 子类(派生类) extends 父类(超类/基类){}
// 接口支持多继承,类只能单继承
// 子类会继承父类中所有的属性和方法,但是私有属性和方法不能直接使用
public class UniversityStudent extends Student{
// 方法
public void printName(){
System.out.println("Name = " + getName());
}
// 方法重写(Override) 从父类继承过来的方法已经不能满足子类的需求
public void study(){
// 1.保留父类原有的功能:调用父类的方法
super.study();
// 2.重写方法后添加新的功能
System.out.println("子类重写父类的学习方法!");
}
}
package com.extendsoop;
// 定义老师类
// 每个类都会默认继承Object类,Object类是所有类的父类
public class Teacher extends Object {
public void teach(Student s){
s.study();
}
}
package com.extendsoop;
public class Manage {
// 主函数
public static void main(String[] args) {
Teacher t = new Teacher(); // t.teach(Student s);
// Student st = new Student();
// t.teach(st); // 需要什么类型的参数就创建什么样的对象给它
// 创建子类对象
UniversityStudent us = new UniversityStudent();
// 调用的是子类重写study的方法
t.teach(us); // 在程序运行的过程中,发生了自动转型/向上转型
}
}
5.类的继承之构造方法
- 子类不会继承父类的构造方法,父类的构造方法是不能被子类继承的,因为构造方法名要跟当前的类名保持一致。
- 父类中如果定义了有参构造方法,那么就要在子类的构造方法中,一定要调用一次父类的构造方法,也可以在父类中重新定义一个无参构造方法,否则会报错!
- 父类中的无参构造方法会被子类默认调用。
- 调用父类的构造方法:super(参数1,参数2...);
2. 父类中如果定义了有参构造方法,那么一定要在子类的构造方法中调用一次父类的构造方法!
package com.extend.createmethod;
public class Student {
// 属性
private String name;
// 构造方法
public Student(String name){
= name;
}
}
package com.extend.createmethod;
public class UniversityStudent extends Student{
// 在子类的构造方法中,一定要调用一次父类的构造方法,否则会报错
// 父类中的无参构造方法会被默认调用
// 调用父类的构造方法:super(参数1,参数2,,,);
public UniversityStudent(){
super("");
}
}
3.如何避免在子类中手动调用父类的构造方法?
- 在父类中再定义一个无参构造方法,因为父类的无参构造方法会被子类默认调用!
package com.extend.createmethod;
public class Student {
// 属性
private String name;
// 构造方法
public Student(String name){
= name;
}
// 无参构造方法
public Student(){
}
}
package com.extend.createmethod;
public class UniversityStudent extends Student{
// 在子类的构造方法中,一定要调用一次父类的构造方法,否则会报错
// 父类中的无参构造方法会被默认调用
// 调用父类的构造方法:super(参数1,参数2,,,);
// public UniversityStudent(){
// super("");
// }
}
4.创建子类对象(调用构造方法),先调用的是父类的构造方法,然后才调用子类的构造方法,其实先创建的是父类对象,父类中的无参构造方法会被子类默认调用
package com.extend.createmethod;
public class Student {
// 属性
private String name;
// 构造方法
public Student(String name){
= name;
}
// 无参构造方法
public Student(){
System.out.println("父类的无参构造方法被调用!");
}
}
package com.extend.createmethod;
public class UniversityStudent extends Student{
// 在子类的构造方法中,一定要调用一次父类的构造方法,否则会报错
// 父类中的无参构造方法会被默认调用
// 调用父类的构造方法:super(参数1,参数2,,,);
public UniversityStudent(){
// super(""); 在子类的构造方法中会默认调用父类的无参构造方法
System.out.println("子类的无参构造方法被调用!");
}
}
package com.extend.createmethod;
public class Manage {
// 主函数
public static void main(String[] args) {
// 创建子类对象 调用的是子类的构造方法
// 用子类创建对象(调用构造方法),先调用的是父类的构造方法,然后才调用子类的构造方法,
// 其实先创建的是父类对象,父类中的无参构造方法会被子类默认调用
UniversityStudent us = new UniversityStudent();
}
}
6.面向对象三大特征之三:多态
a.多态的概述
1、什么是多态?
- 同类型的对象,执行同一个行为,会表现出不同的行为特征。
2、多态的常见形式:
- 父类类型 对象名称 = new 子类构造器;
- 接口 对象名称 = new 实现类(子类)构造器;
3、多态中成员访问特点
- 方法调用:编译看左边,运行看右边(这样才能看到不同对象执行不同行为)。
- 变量调用:编译看左边,运行也看左边,因为变量没有多态的概念(多态侧重行为多态)。
4、多态的前提:
- 有继承/实现关系;有父类引用指向子类对象;有方法重写。
package com.gch.d1_polymorphic;
/**
父类
*/
public class Animal {
public String name = "动物名称";
public void run(){
System.out.println("动物可以跑~~~");
}
}
package com.gch.d1_polymorphic;
public class Dog extends Animal {
public String name = "狗名称";
@Override
public void run() {
System.out.println("狗跑的贼溜~~~");
}
}
package com.gch.d1_polymorphic;
/**
* 定义乌龟类
*/
public class Tortoise extends Animal{
public String name = "乌龟名称";
@Override
public void run() {
System.out.println("乌龟跑的非常慢!!");
}
}
package com.gch.d1_polymorphic;
public class Test {
public static void main(String[] args) {
// 目标:先认识多态的形式
// 父类 对象名称 = new 子类构造器;
// 1.狗这个类型本身也是动物类型
// 2.小范围类型的变量可以赋给大范围类型的变量
Animal a = new Dog();
a.run(); // 方法调用:编译看左边,运行看右边
System.out.println(); // 变量调用:编译看左,运行也看左
System.out.println();
Animal a1 = new Tortoise();
a1.run(); // 方法调用:编译看左,运行看右
System.out.println(); // 变量调用:编译看左,运行也看左
}
}
b.多态的优势与劣势
1.多态的优势
- 在多态形式下,右边对象可以实现解耦合(右边对象想换就换),便于扩展和维护。
- Aniaml a = new Dog() / new Tortoise();
- a.run(); // 后续业务行为随对象而变,后续代码无需修改
- 定义方法的时候,使用父类类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。
2.多态的劣势
- 劣势下不能使用子类的独有功能,因为多态下方法的调用:编译看左边。
- 在多态劣势下不能调用子类的独有行为。
package com.gch.d2_polymorphic_advantage;
/**
定义父类:动物类
*/
public class Animal {
public String name = "动物名称";
public void run(){
System.out.println("动物可以跑~~~");
}
}
package com.gch.d2_polymorphic_advantage;
/**
定义狗类
*/
public class Dog extends Animal {
public String name = "狗名称";
@Override
public void run() {
System.out.println("狗跑的贼溜~~~");
}
/**
独有功能
*/
public void lookDoor(){
System.out.println("狗在看门~~~");
}
}
package com.gch.d2_polymorphic_advantage;
/**
* 定义乌龟类
*/
public class Tortoise extends Animal {
public String name = "乌龟名称";
@Override
public void run() {
System.out.println("乌龟跑的非常慢!!");
}
}
package com.gch.d2_polymorphic_advantage;
public class Test {
public static void main(String[] args) {
Animal d = new Dog();
go(d);
// d.lookDoor(); // 直接报错,因为父类Animal没有这个方法
System.out.println();
Animal t = new Tortoise();
go(t);
}
/**
* 希望这个方法可以接收一切子类动物对象
* 这样方法的扩展性更强,也更便利
* @param a:父类动物类型的对象
*/
public static void go(Animal a){
System.out.println("预备~~~");
a.run(); // 对象回调:我把对象送给你,最终你还是调我对象的方法
System.out.println("结束~~~");
}
}
c.多态下引用数据类型的类型转换
1.多态形式下引用数据类型的类型转换
- 自动类型转换(向上转型):从子到父
- 强制类型转换(向下转型):从父到子,从父到子必须进行强制类型转换,否则报错:子类 对象名称 = (子类)父类类型的变量
2.强制类型转换的作用:强制类型转换可以转换成真正的子类类型,可以解决多态下的劣势,从而实现调用子类独有的功能。
3.注意:有继承/实现关系的2个类型就可以在编译阶段进行强制类型转换,编译无问题;但是,如果转型后的类型和对象真实对象的类型不是同一种类型,那么在运行代码时,就会出现ClassCastException(类型转换异常)。
- Animal c = new Cat();
- Dog d = (Dog)c; // 出现异常 ClassCastException
4.Java建议强制转换前使用instanceof判断当前对象的真实类型,再进行强制转换。
- 对象变量名 instanceof 真是类型
- 判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者是其子类类型,是则返回true,反之false。
package com.gch.d3_polymorphic_convert;
/**
定义父类:动物类
*/
public class Animal {
public String name = "动物名称";
public void run(){
System.out.println("动物可以跑~~~");
}
}
package com.gch.d3_polymorphic_convert;
/**
定义狗类
*/
public class Dog extends Animal {
public String name = "狗名称";
@Override
public void run() {
System.out.println("狗跑的贼溜~~~");
}
/**
独有功能
*/
public void lookDoor(){
System.out.println("狗在看门~~~");
}
}
package com.gch.d3_polymorphic_convert;
/**
定义乌龟类
*/
public class Tortoise extends Animal {
public String name = "乌龟名称";
@Override
public void run() {
System.out.println("乌龟跑的非常慢!!");
}
/**
独有功能
*/
public void layEggs(){
System.out.println("乌龟在下蛋~~~");
}
}
package com.gch.d3_polymorphic_convert;
public class Test {
public static void main(String[] args) {
// 目标:学习多态形式下的类型转换机制
// 自动类型转换(向上转型)
Animal a = new Dog();
a.run();
// a.lookDoor(); 直接报错,多态下无法调用子类的独有功能
// 强制类型转换(向下转型):可以实现调用子类的独有功能
Dog d = (Dog) a;
d.lookDoor();
// 注意:多态下直接进行强制类型转换,可能出现类型转换异常
// 规定:有继承或者实现关系的2个类型就可以强制类型转换,运行时可能出现问题
// 动物类型的a变量和乌龟类型的t变量有继承关系
// Tortoise t = (Tortoise) a; // 编译不报错,运行报错:java.lang.ClassCastException(类型转换异常)
// 建议强制转换前,使用instanceof关键字先先判断变量指向对象的真实类型,再强制类型转换。
if(a instanceof Tortoise){
Tortoise t = (Tortoise) a;
t.layEggs();
}else if(a instanceof Dog){
Dog d1 = (Dog) a;
d1.lookDoor();
}
System.out.println("------------------");
Animal a1 = new Tortoise();
go(a1);
}
public static void go(Animal a){
System.out.println("预备~~~");
// 独有功能
if(a instanceof Tortoise){
Tortoise t = (Tortoise) a;
t.layEggs();
}else if(a instanceof Dog){
Dog d1 = (Dog) a;
d1.lookDoor();
}
System.out.println("结束~~~");
}
}
d.多态的综合案例
package com.gch.d4_polymorphic_test;
/**
定义USB接口
*/
public interface USB {
void insert(); // 插入
void pullOut(); // 拔出
}
package com.gch.d4_polymorphic_test;
/**
鼠标实现类(子类)
*/
public class Mouse implements USB{
private String name;
public Mouse(String name) {
= name;
}
@Override
public void insert() {
System.out.println(name + "成功的接入设备~~~");
}
@Override
public void pullOut() {
System.out.println(name + "成功的拔出设备~~~");
}
/**
独有功能
*/
public void click(){
System.out.println(name + "双击点亮小红心~~~");
}
public String getName() {
return name;
}
public void setName(String name) {
= name;
}
}
package com.gch.d4_polymorphic_test;
/**
键盘实现类(子类)
*/
public class KeyBoard implements USB{
private String name;
public KeyBoard(String name) {
= name;
}
@Override
public void insert() {
System.out.println(name + "成功的接入设备~~~");
}
@Override
public void pullOut() {
System.out.println(name + "成功拔出设备~~~");
}
/**
独有功能
*/
public void control(){
System.out.println(name + "正在控制电脑~~~");
}
public String getName() {
return name;
}
public void setName(String name) {
= name;
}
}
package com.gch.d4_polymorphic_test;
/**
定义电脑类
*/
public class Computer {
/**
提供一个安装的入口:行为。
*/
public void installUSB(USB u){
u.insert();
// 独有功能
if(u instanceof Mouse){
Mouse m = (Mouse) u;
m.click();
}else if(u instanceof KeyBoard){
KeyBoard k = (KeyBoard) u;
k.control();
}
u.pullOut();
}
}
package com.gch.d4_polymorphic_test;
/**
目标:USB设备模拟
1.定义USB接口:接入 拔出
2.定义两个USB的实现类分别代表:鼠标、键盘
3.创建一个电脑对象,再创建USB设备对象,安装启动
*/
public class Test {
public static void main(String[] args) {
// a.创建电脑对象
Computer c = new Computer();
// b.创建USB设备对象
USB m = new Mouse("ROG鼠标");
c.installUSB(m);
System.out.println();
USB k = new KeyBoard("罗技键盘");
c.installUSB(k);
}
}