07 -- 08. Java学习 -- 继承、抽象类、final关键字、接口与多态

07 – 08. Java学习 – 继承、抽象类、final关键字、接口与多态



前言

继承:子类使用父类的方法
多态:父类使用子类的方法


一、 继承

所谓继承,就是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。

1. 继承的定义格式

**继承的定义格式:**
public class 父类{
    ... ...
}

public class 子类 extends 父类 {
    ... ...
}

继承关系下,父类公有的属性和方法都会被子类共享。

/*例子:*/
// 父类
public class Animal { 
    public String name;
    public int age;

    public void showInfo(){
        System.out.println("姓名:" + this.name + "  年龄:" + this.age);
    }
}
// 子类  extends继承的关键字
public class Cat extends Animal{ 

}
// 测试类
public class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.name = "橘猫";
        cat.age = 2;
        cat.type = "田园猫";
        cat.showInfo();	// 姓名:橘猫  年龄:2
    }
}

2. 方法的重写

// 例子:
// 父类
public class Animal {

    public String name;
    public int age;
    public String type;

    public void show(){
        System.out.println("animal");
    }
}
// 子类
public class Cat extends Animal{

    @Override // 方法重写的标识 本身这个注解没有任何的实际作用
    public void show() {
        System.out.println("cat");
    }
}

方法重写要注意:

    1. 子类重写的方法,要求方法的访问修饰符必须大于或等于父类
    1. 在方法重写时,要求子类方法的返回值类型必须要小于或等于父类
    1. 子类方法抛出的异常类型必须小于或等于父类方法抛出的异常类型
2.1. 方法重写和方法重载的区别
  1. 定义不同:重载是定义相同的方法名,但参数列表不同;重写是子类重写父类方法
  2. 范围不同:重载是在一个类中;重写是在继承关系的前提下
  3. 参数不同:重载的参数个数、类型、顺序可以不同;重写必须严格相同
  4. 修饰不同:重载对访问修饰符范围没有要求;重写要求访问修饰符范围大于或等于被重写方法

3. 使用继承的注意事项

3.1. private关键字在继承中的使用

priavte关键字修饰的资源是不能被继承的。
如果我们想要在子类中访问父类的私有成员变量,只能在父类中提供公有的set、get方法。

3.2. super关键字在继承中的使用

super关键字只能在继承的环境下面使用
定义类时,如果子类和父类中出现了同名的变量,在子类使用这个变量的时候,java编译器会按照就近原则,使用子类自己内部的变量。如果我们就是想要访问父类中的同名属性,就可以使用super关键字。

**使用格式:**
super.变量名  // 指定访问父类的成员变量

如果子类和父类中有同名的方法,super也可以调用父类的方法。

super.方法名  // 指定访问父类的方法

super关键字还可以调用父类的构造函数(注意:父类的构造函数是不能被子类继承的,只能被子类调用)

// 父类
public abstract class Poultry {
    private String name;
    private String symptom;
    private int age;
    private String illness;

    public Poultry(String name, String symptom, int age, String illness) {
        this.name = name;
        this.symptom = symptom;
        this.age = age;
        this.illness = illness;
    }
}
// 子类
public class Duck extends Poultry {
    public Duck(String name, String symptom, int age, String illness) {
        super(name,symptom,age,illness);
    }
}

super和this关键字的区别

  • this.变量名称
    • 在一个类的里面,如果成员变量和局部变量同名,this.变量名,指定使用成员变量
    • 在继承关系下,在子类中使用this.变量名,先找子类的成员变量,如果找不到再找父类的成员变量
  • this.方法名称
    • 在一个类里面,this.方法名称,先从子类找这个方法,如果找不到再找父类中的方法
    • 在一个类里面,构造函数的重载,在一个构造函数的内部,可以使用this去指代另一个构造函数
  • super
    • 不管是调用变量还是调用方法,都是访问父类的方法
    • 如果父类定义构造函数,那么在子类的内部必须通过super关键字调用父类的构造函数
    • 多个继承关系,super关键字只能找到子类的直接父类

二、抽象类

在描述一类事物的时候,发现该事物确实存在着某种行为,但是目前该行为是不具体的,那么这时候我们应该抽取该方法的声明,而不去实现该方法。

1. 抽象类的定义格式

public abstract class 类名{
   public abstract 返回值类型  方法名(参数列表);
}
// 例子:
// 类名必须使用abstract关键字修饰
public abstract class Shape { 
    // 抽象方法也必须使用abstract关键字修饰
    public abstract double getArea(); 
}

要注意,抽象类是不能直接使用的,也就是说我们不能创建抽象类的实例化对象。抽象类定义之后,必须要被其子类继承,里面的方法必须由其子类重写之后才能使用。

2. 使用抽象类的注意事项

  1. 如果具体的子类继承了一个抽象类,在当前子类里面必须重写父类的所有的抽象方法
  2. 如果一个抽象的子类继承一个抽象的父类,在抽象的子类里面可以重写,也可以不重写父类的抽象方法
  3. 在一个抽象类里面可以存在非抽象的方法
  4. 抽象类不能使用new关键字实例化对象
  5. 抽象类可以有成员变量和构造函数,因为提供了构造函数可以被其子类调用

三、final关键字

final意味着不可改变,final可以用来修饰类、方法和变量

  • final修饰类意味着该类不可被继承
  • final修饰方法意味着该方法不可被重写
  • final修饰变量意味着该变量不可被修改,是常量

1. final关键字的使用

1.1. final关键字修饰类

final修饰类意味着该类不可被继承

** 使用格式:**
访问修饰符 final class 类名{}
// 例子:
public final class FinalTest{
	String name;
	int age;

	public void showInfo(){
		System.out.println("这是一个final类");
	}
}

Java中也定义了很多final修饰的类,例如:String、Math、Scanner…

1.2. final关键字修饰方法

final修饰方法意味着该方法不可被子类重写

** 使用格式:**
访问修饰符 final 返回值 函数名(){}
// 例子:
public class FinalTest{
	String name;
	int age;

	public final void showInfo(){
		System.out.println("这是一个final方法");
	}
}
1.3. final关键字修饰变量

final修饰变量意味着该变量不可被修改,是常量

**使用格式:**
final 数据类型 变量名 = 赋值;
// 例子:final修饰局部变量
public class FinalTest{
	String name;
	int age;

	public final void showInfo(){
		final int a = 20;
		System.out.println(a + "是一个final变量");
	}
}
// 例子:final修饰成员变量
public class FinalTest{
	final String name;
	int age;

	public final void showInfo(){
		System.out.println(name + "是一个final变量");
	}
}

四、接口

1. 接口的定义格式

**使用格式:**
public interface 接口名称{ // 抽象方法 }

2. 接口的使用

类实现接口的关键字是implements

/*例子:*/
// 接口1
interface InterfaceTest1 {
    default void show(){
        System.out.println("接口1被default修饰的show方法");
    }
}

// 实现类
public class Main implements InterfaceTest2{
    public static void main(String[] args) {
        
    }
}

3. 定义接口的注意事项

3.1. 接口中可以定义抽象方法,也可以定义非抽象方法

jdk8后,Java允许接口中定义非抽象方法。在jdk8之前,接口中只能定义抽象方法。

// 例子:
public interface Shape {
    double getArea();

    public abstract double getLength();

    //定义非抽象的方法 非抽象方法在接口中定义,访问修饰符以default开头
    default void defaultMethod(){
        System.out.println("hello");
    }
}
3.1.1. 接口中关于default关键字的使用

default 关键字在jdk8后被引入,属于修饰符关键字,default关键字大部分都用于修饰接口。
default 关键字在修饰方法时只能在接口类中使用,在接口中被 default 标记的方法可以直接写方法体,而无需修改所有实现了此接口的类。

  • 1. default关键字是全局的,可以在不同接口中定义一个相同的方法
/*例子:*/
// 接口1
interface InterfaceTest1 {
    default void show(){
        System.out.println("接口1的default修饰的show方法");
    }
}
// 接口2
interface InterfaceTest2 {
    default void show(){
        System.out.println("接口2的default修饰的show方法");
    }
}

public class Main {
    public static void main(String[] args) {

    }
}
  • 2. 可以在实现类中单独实现任意一个接口的同名方法
/*例子:*/
// 接口1
interface InterfaceTest1 {
    default void show(){
        System.out.println("接口1被default修饰的show方法");
    }
}
// 接口2
interface InterfaceTest2 {
    default void show(){
        System.out.println("接口2被default修饰的show方法");
    }
}
// 单独实现接口1的show方法
/*public class Main implements InterfaceTest1{
    public static void main(String[] args) {
        new Main().show();     // 接口1被default修饰的show方法
    }
}*/
// 单独实现接口2的show方法
public class Main implements InterfaceTest2{
    public static void main(String[] args) {
        new Main().show();     // 接口2被default修饰的show方法
    }
}
  • 3. 在实现类中同时实现2个接口
    • 在实现类中重写同名方法
    • 用一个 @Override 告诉编译器要运行的是哪个接口中的方法
/* 例子:在实现类中重写同名方法 */ 
// 接口1
interface InterfaceTest1 {
    default void show() {
        System.out.println("接口1被default修饰的show方法");
    }
}

// 接口2
interface InterfaceTest2 {
    default void show() {
        System.out.println("接口2被default修饰的show方法");
    }
}

// 实现类
public class Main implements InterfaceTest1, InterfaceTest2 {
    @Override
    public void show() {
        System.out.println("实现类中重写的show方法");
    }

    public static void main(String[] args) {
        new Main().show();     // 实现类中重写的show方法
    }
}
/*例子:指定实现接口*/
interface InterfaceTest1 {
    default void show() {
        System.out.println("接口1被default修饰的show方法");
    }
}

// 接口2
interface InterfaceTest2 {
    default void show() {
        System.out.println("接口2被default修饰的show方法");
    }
}

// 实现类
public class Main implements InterfaceTest1, InterfaceTest2 {
    @Override
    public void show() {
        InterfaceTest1.super.show();
        InterfaceTest2.super.show();
    }

    public static void main(String[] args) {
        new Main().show();     // 接口1被default修饰的show方法
                               // 接口2被default修饰的show方法
    }
}
  • 4. 子类继承父类,父类中有show方法,该子类同时实现的接口中也有show方法(被default修饰)
    • 在实现类中重写同名方法
/*例子:在实现类中重写同名方法*/
// 接口1
interface InterfaceTest1 {
    default void show() {
        System.out.println("接口1被default修饰的show方法");
    }
}

// 父类
class Father{
    void show(){
        System.out.println("这是父类的show方法");
    }
}

// 实现类
public class Main extends Father implements InterfaceTest1 {
    @Override
    public void show() {
        System.out.println("被实现类重写后的show方法");
    }

    public static void main(String[] args) {
        new Main().show();     // 被实现类重写后的show方法
    }
}
3.2. 接口中不能定义成员变量,如果要定义,必须定义成常量
// 例子:
interface InterfaceTest1 {
    /* String name;  编译器报错 */
    // 定义一个常量,编译器在编译时会自动加上public static final 变为 public static final int age = 22
    int age = 22;

    default void show() {
        System.out.println("接口1被default修饰的show方法");
    }
}
3.2.1. 接口中关于final关键字的使用
  • 1. 接口不可以被final修饰

关于这一点,我在学习的时候看到有人说:接口可以被final修饰。
但是,我自己尝试的时候报错,编译器说不允许用final修饰。再查资料,又看到有人说接口不能被final修饰,就先按我自己的尝试结果,当他不能吧。

  • 2. 接口的成员变量必须被final修饰,变成常量
  • 3. 接口的方法不能用final修饰
3.3. 类与类之间只能是单继承,但是接口与接口之间是可以多继承的
/*例子*/
interface InterfaceTest1 {
    default void show() {
        System.out.println("接口1被default修饰的show方法");
    }
}

// 接口2
interface InterfaceTest2 {
    default void show() {
        System.out.println("接口2被default修饰的show方法");
    }
}

// 实现类
public class Main implements InterfaceTest1, InterfaceTest2 {
    @Override
    public void show() {
        InterfaceTest1.super.show();
        InterfaceTest2.super.show();
    }

    public static void main(String[] args) {
        new Main().show();     // 接口1被default修饰的show方法
                               // 接口2被default修饰的show方法
    }
}

总结:

  1. 类与类之间只能是单继承
  2. 接口与接口之间可以有多继承
  3. 类与接口之间是实现关系

五、多态

多态按字面意思就是“多种状态”,让继承自同一父类的子类们,在执行相同方法时有不同的表现(状态)。多态所解决的问题是让同一个对象有唯一的行为特征。

1.多态的定义格式

这里可以扩展一个知识点:里氏替换原则
里氏替换原则是面向对象七大原则中最重要的原则,其概述为任何父类出现的地方,子类都可以替代,作用是方便进行对象的储存和管理
有概念说:里氏代换原则是对“开-闭”原则的补充,实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范,其要求子类从抽象继承而不是从具体继承。若是从抽象中继承,那么子类必然要重写父类方法,因此里氏转换原则和多态相辅相成

里氏替换原则的语法表现为:
父类容器装载子类对象,因为子类对象包含了父类的所有内容

1.1. 继承下的多态
**定义格式:**
父类的数据类型 变量名称 = new 子类的数据类型();
// 例子:
// 定义一个父类
public class Person {
    public void show(){
        System.out.println("父类Person");
    }
}
// 定义一个子类
public class Student extends Person{
    @Override
    public void show() {
        System.out.println("子类Student");
    }
}
// 测试类
public class TestStudent {
    public static void main(String[] args) {
        Person student = new Student(); // 多态
        student.show();		 // 学生边看书边吃饭.....
    }
}
1.2. 接口下的多态
**定义格式:**
接口的数据类型 变量名称 = new 实现类的数据类型();
// 例子:
// 接口
public interface UserDao {
    public void add();
}
// 实现类
public class UserDaoImpl implements UserDao{
    @Override
    public void add() {
        System.out.println("实现类");
    }
}
// 测试类
public class TestUserDao {
    public static void main(String[] args) {
        UserDao userDao = new UserDaoImpl(); // 多态
        userDao.add();
    }
}

2. 多态的使用

2.1. 将父类作为方法的形式参数

将父类作为方法的形式参数,可以接收更多的数据类型

例子:封装一个计算图形面积的方法

// 父类
public abstract class Shape {
    public String name;

    public Shape(String name) {
        this.name = name;
    }
    
    //计算图形面积的方法
    public abstract double getArea();
}
// 子类
public class Square extends Shape{ 
    int width;

    public Square(String name) {
        super(name);
    }

    public Square(String name, int width) {
        super(name);
        this.width = width;
    }

    @Override
    public double getArea() {
      double area = width * width;
      return area;
    }
}
public class Circle extends Shape{ 
    int r;

    public Circle(String name, int r) {
        super(name);
        this.r = r;
    }

    @Override
    public double getArea() {
        return 3.14*this.r*this.r;
    }
}
// 测试类
public class TestShape {
    public static void main(String[] args) {
        Square square = new Square("正方形",4);
        System.out.println(getAreaFromShape(square));
    }

    public static double getAreaFromShape(Shape shape){
            return shape.getArea();
    }
}
2.2. 将父类作为方法的返回值

将父类作为方法的返回值,可以返回更多的数据结果。

例子:根据用户输入的数字,返回图形

// 父类同上
// 测试类
public class TestShape {
    public static void main(String[] args) {
        Shape shape = getShape(2);
        System.out.println("图形名称是:" + shape.name);
    }
    
    //将父类作为方法的返回值可以返回更多类型的数据
    public static Shape getShape(int num){
        Shape shape = null;
        if(num == 1){ 
        	// 如果用户输入1返回正方形
            shape = new Square("正方形",10);
        }
        if(num == 2){
        	// 如果用户输入2返回圆形
            shape = new Circle("圆形",3);
        }
        return shape;
    }
}

3. 使用多态的注意事项

1. 多态场景下面对成员变量的使用
* 如果子类和父类存在同名的成员变量,在多态场景下面,访问的是父类的成员变量的值
例子:

// 父类
public class Father { 
    int num =10;

    public void show(){
        System.out.println("父类");
    }
}
// 子类
public class Son extends Father{
    int num = 100;
    int a = 10;

    public void show(){
        System.out.println("子类");
    }
}
// 测试类
class TestSon{
    public static void main(String[] args) {
        /**
         * 在多态的前提下,如果父类和子类都存在同名的成员变量,访问的父类的成员变量的值。
         */
        Father f = new Son();
        System.out.println(f.num); // 访问的是父类的num值
    }
}

2. 多态场景下面对成员方法的调用
* 如果子类和父类存在同名的成员方法,在多态的场景下面,访问的是子类的成员方法

// 测试类
class TestSon{
    public static void main(String[] args) {
        Father f = new Son();
        /**
         * 在多态的前提下,如果父类和子类都存在同名的成员方法,访问的是子类的成员方法
         */
        f.show();
    }
}

3. 如果父类是抽象类,也存在多态的场景

// 父类
public abstract class Shape {
    public abstract void getArea();
}
// 子类
public class Square extends Shape{
    @Override
    public void getArea() {
        System.out.println("计算正方形的面积");
    }
}
// 测试类
class TestShape{
    public static void main(String[] args) {
        Shape shape = new Square(); // 多态
        shape.getArea();
    }
}

4. 在多态场景下面,子类特有的方法是不能被直接访问

// 父类
public class Father { 
    int num =10;

    public void show(){
        System.out.println("父类");
    }
}
// 子类
public class Son extends Father{
    int num = 100;
    int a = 10;

    public void show(){
        System.out.println("子类");
    }

	// 子类中特有的方法
    public void work(){
        System.out.println("子类特有的方法");
    }
}
class TestSon{
    public static void main(String[] args) {
        Father f = new Son();
        f.show();
        // f.work(); // 调用子类特有的方法,此时编译报错
        // 对象 instanceof 类 --> 用来判断某个对象是否属于某个类型
        if(f instanceof Son){
          ((Son) f).work(); //就可以调用子类中特有的方法
        }
    }
}

补充

一点开发的“潜规则”:

  • 操作数据库相关:
    • 接口命名一般为:XxDao
    • 实现接口的类命名一般为:XxDaoImpl
  • 业务层相关:
    • 接口命名一般为:XxService
    • 实现接口的类命名一般为:XxServiceImpl

IDEA快捷方式:

  • 快速生成需要重写的方法:Ctrl+O
  • 快速创建对象:.var
    • new Random.var --> Random random = new Random();
  • 快速将代码用输出语句包裹:.sout
    • myList.show().sout --> System.out.println(myList.show());
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值