2021大数据学习之旅 JavaSE·面向对象(final、接口、多态)


前言

系列文章比较适合非科班的同学, 由浅及深, 循序渐进, 从基础开始走上大数据之路

让我们一起努力, 达成目标, 实现理想

最后恳请留下你的小❤, 点赞给我一个小小的鼓励吧


一、final关键字

1.1 概述

学习了继承后,我们知道,子类可以在父类的基础上改写父类内容,比如,方法重写。那么我们能不能随意的继承API中提供的类,改写其内容呢?显然这是不合适的。为了避免这种随意改写的情况,Java提供了final 关键字,用于修饰不可改变内容。

final: 最终的,不可改变的。可以用于修饰类、方法和变量.

  • 类:被修饰的类,不能被继承。
  • 方法:被修饰的方法,不能被重写。
  • 变量:被修饰的变量,不能被重新赋值。

1.2 final详细解释

1.被修饰的类,不能被继承

格式:

final class 类名{}

查询API发现像 public final class Stringpublic final class Mathpublic final class Scanner 等,很多我们学习过的类,都是被final修饰的,目的就是供我们直接使用,而不让我们随意修改其内容(API帮助文档之后会详细展开介绍具体的类的)。


2.被修饰的方法,不能被重写

格式:

修饰符列表  final  返回值类型  方法名(参数列表){
		方法体
}

重写final修饰的方法,编译时就会报错。


3.被修饰的变量,不能被重新赋值

  • <1>成员变量

final修饰成员变量必须在创建对象之前赋值,有两种方式初始化方式:

// 显示初始化
public class User {
    final String NAME = "张三"; //直接在定义的时候给定一个默认值
    private int age;
}
//构造方法初始化
public class User {
    final String NAME ;
    private int age;

	public User(){
		this.NAME = "张三";
	}
    public User(String name, int age) {
        this.NAME = name;
        this.age = age;
    }
}

被final修饰的常量名称,有特殊的命名规范,所有字母都要大写


  • <2>局部变量 —— 基本数据类型

基本类型的局部变量,被final修饰后,只能赋值一次,不能再做更改。

举例:

public class Test01 {
    public static void main(String[] args) {
        final int a;
        a = 10;
//        a = 20; //在编译的过程中就会报错
        System.out.println(a);
    }
}

思考:

以下两种写法有问题吗?

public static void main(String[] args) {
        final int a;
        for (int i = 0; i < 10; i++) {
            a = i;
            System.out.println(a);
        }
    }
public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            final int a = i;
            System.out.println(a);
        }
    }

根据 final 的定义,上面的写法报错!第二种写法为什么通过编译呢?因为每次循环,都是一次新的不能改变的变量a。


  • <3>局部变量 —— 引用数据类型

引用类型的局部变量,被final修饰后,只能指向一个对象,地址不能再更改。但是不影响对象内部的成员变量值的修改。

举例:

public static void main(String[] args) {
	final User u = new User();
    u = new User(); //不能赋新的地址值
    u.setName("张三"); //可以修改
}


二、接口

2.1 概述

接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法,接口中所有方法都是抽象方法(JDK 7及以前,在JDK8中新出了默认方法和静态方法)。

接口只描述所应该具备的方法,并没有具体实现,是没有方法体的。具体的实现由接口的实现类(相当于接口的子类)来完成。这样将功能的定义与实现分离,优化了程序设计,降低了程序的 耦合性,实现了解耦合。


2.2 接口的定义

接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。

格式:

public interface 接口名{
  常量;
  抽象方法
}

使用interface代替了原来的class,其他步骤与定义类相同:

  • 接口中的方法均为公共访问的抽象方法
  • 接口中无法定义普通的成员变量

注意:

  • 一般的public class 类名.java在编译之后的class文件名类似Test.class
  • 接口public interface 接口名.java在编译之后的class文件名类似Test$1.class

2.3 类实现接口

实现的概述

类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字。

实现格式:

public class 类名 implements 接口名{
  //重写接口中的所有抽象方法(强制)
}

在类实现接口后,该类就会将接口中的抽象方法继承过来,此时该类需要重写该抽象方法,完成具体的逻辑。

  • 接口中定义功能,当需要具有该功能时,可以让类实现该接口,只声明了应该具备该方法,是功能的声明。
  • 在具体实现类中重写方法,实现功能,是方法的具体实现。

举例:

接口

public interface Test02 {
//    public static final String NAME = "张三";
//    public abstract void move();
//    public abstract String name();

//    由于接口中只存在常量和抽象方法,所以可以简写为:
    String NAME = "张三";
    void move();
    String name(String name);
}

实现类

public class Test03 implements Test02 {
    @Override
    public void move() {
        System.out.println("在移动");
    }

    @Override
    public String name(String name) {
        return name;
    }
	//测试
    public static void main(String[] args) {
        Test04 a = new Test04();
        System.out.println(a.name(NAME));
        a.move();
    }
}

2.4 接口中成员的特点

接口中,无法定义成员变量,但是可以定义常量,其值不可以改变,默认使用public static final修饰。

public interface Test04 {
   int NUM0 ; // 错误,必须赋值  
   int NUM1 =10; // 正确 , 省去了默认修饰符 public static final
   public static final int NUM2= 100; // 正确 , 完整写法
}

解释一下:为什么成员变量一定要用static final修饰?

  • static:因为接口是允许多实现的。如果一个类实现了两个接口,且两个接口都具有相同名字的变量,此时这个变量可以被实现类使用,那么如果不是static的,这个变量来自哪一个接口就会产生歧义,所以实现类使用接口中的变量必须通过接口名指定,也就只能定为static的。
  • final:既然必须是static修饰的,那么所有子类共享。而接口是一种抽象, 所以一个子类修改了值会影响到其他所有子类,因此就不应该允许子类修改这个值,所以也必须定义为final。

接口中可以定义方法,方法也有固定的修饰符,public abstract

public interface Test05 {
  
  void method(); //正确 没写public abstract java自动补全
  public abstract void method2(); //正确 完成写法
  public void method3(); //正确 没写abstract  java自动补齐
  private void method4(); //错误 
  public void method5(){} //错误 接口中都是抽象方法
}

接口没有构造方法,不能直接创建对象

public interface Test05{
  	public LiveAble(){} //错误 接口没有构造方法
}

2.5 接口的特点(注意事项)

  • 类与类是继承关系,类与接口是实现关系,一个类可以在继承一个类的同时实现多个接口
public interface JieKou1
{
	void show1();
    void show2();
}
public interface JieKou2
{
	void show3();
    void show4();
}
public abstract class Fu{
    public abstract void showFu1();
    public abstract void showFu2();
}

//有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次
public class Zi extends Fu implements JieKou1, JieKou2 // 多实现,同时实现多个接口。
{
	public void show1(){}
	public void show2(){}
	public void show3(){}
	public void show4(){}
	public abstract void showFu1(){}
    public abstract void showFu2(){}
}
注意:一个类继承一个父类的同时实现一个或者多个接口,要重写所有的抽象方法
	如果两个接口中有重名的抽象方法,那么我们就重写一个
  • 接口与接口的关系继承关系,可以单继承,也可以多继承

一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承使用 extends 关键字,子接口继承父接口的方法

public interface A {
    void show1();
}
public interface B {
   void show2();
}
public interface C extends A,B{
   void show3();
}
public class D implements A,B,C{
   void show1(){}
   void show2(){}
   void show3(){}
}

2.6 接口的好处

  • 接口解决了java单继承的局限性,提高了程序的扩展性

我们都知道java中类是单继承的,不可以多继承,为什么呢?因为多继承可能造成安全隐患,多继承时,当多个父类中有相同功能时,子类调用会产生不确定性。其实核心原因就是在于多继承父类中功能有主体,而导致调用运行时,不确定运行哪个主体内容。如:

public class A{
  public void show(){
      System.out.println("A");
  }  
}
public class B{
  public void show(){
    System.out.println("B");
  }
}
//假设可以多继承的话
public class C extends A,B{}
//当创建C的对象调用show方法时根本不能确定调用的A的show还是B的show方法

但是接口就可以多实现,因为接口中的方法都没有方法体,方法体的具体内容是由子类来实现的。所以为了避免出现这种不安全的隐患java是单继承的,但是单继承是有局限性的,比如一个类已经有父类,还想为这个类添加一些功能时,是没有办法再继承一个的类的,但是这时可以通过实现接口的方式来添加。


  • 接口的出现降低了耦合性,即设备与设备之间实现了解耦。

耦合代表着事物与事物之间的的紧密程度,解耦合就是降低事物之间的联系.打个比方你买了一台电脑,假如鼠标是焊死在电脑上的我们就说耦合性比较高,耦合度太高就会有一些问题,比如鼠标坏了是没办法更换的。但是我们现在买的笔记本上面都有USB接口,这个USB接口就帮我们降低了电脑和鼠标之间的耦合性,如果鼠标坏了,拔下来换一个,并且也提高了程序的扩展性,这个USB接口不止可以插鼠标还可以插键盘,插任何符合USB接口规则的东西。

java中类与类继承的耦合性就是挺高的。如果父类中定义了很多方法并且都给出了实现,子类继承后,可以直接使用这些功能,但是如果子类不继承这个父类了,子类中什么功能都没有了。但是接口就不会,实现类必须实现接口的功能,接口给出方法声明,实现类进行具体实现,即使实现类不实现接口了,也不会造成太的的影响。

public class Fu {
    public void show1() {}
    public void show2() {}
}

//子类继承父类 可以直接使用父类中的方法 ,如果子类不继承父类了 子类中什么方法都没有了
public class Zi extends Fu {
}

public interface A {
    void show1();
    void show2();
}

//B实现A 必须实现所有方法 如果B不实现A了 也没有太大的影响 还可以正常使用
public class B implements A {
    void show1() {}
    void show2() {}
}

2.7 抽象类和接口的区别

抽象类:在这里插入图片描述
接口:
在这里插入图片描述
由图可以看出两者一些相同点

  • 都位于继承的顶端,用于被其他类实现或继承;
  • 都不能直接实例化对象;
  • 都包含抽象方法,其子类都必须覆写这些抽象方法;

区别

  • 抽象类为部分方法提供实现,避免子类重复实现这些方法,提高代码重用性;接口只能包含抽象方法;
  • 一个类只能继承一个直接父类(可能是抽象类),却可以实现多个接口;(接口弥补了Java的单继承)
  • 抽象类为继承体系中的共性内容,接口为继承体系中的扩展功能

2.8 总结抽象类

成员区别

  • 抽象类:变量,常量;有构造方法;有抽象方法,也有非抽象方法
  • 接口:常量;抽象方法

关系区别

  • 类与类:继承,单继承
  • 类与接口:实现,可以单实现,也可以多实现
  • 接口与接口:继承,单继承,多继承

设计理念区别

  • 抽象类:对类抽象,包括属性、行为
  • 接口:对行为抽象,主要是‘功能’


三、多态

3.1 概述

多态是继封装、继承之后,面向对象的第三大特性。

现实事物经常会体现出多种形态,如学生,学生是人的一种,则一个具体的同学张三既是学生也是人,即出现两种形态。

Java作为面向对象的语言,同样可以描述一个事物的多种形态。如Student类继承了Person类,一个Student的对象便既是Student,又是Person。

Java中多态的代码体现在一个子类对象(实现类对象)既可以给这个子类(实现类对象)引用变量赋值,又可以给这个子类(实现类对象)的父类(接口)变量赋值。

如Student类可以为Person类的子类。那么一个Student对象既可以赋值给一个Student类型的引用,也可以赋值给一个Person类型的引用。

class Student extens Person{}
Student s = new Student(); //创建一个学生对象,把学生对象赋值给学生类型s
Person p = new Student(); //创建一个学生对象,把学生对象赋值给人类型p 

最终多态体现为父类引用变量可以指向子类对象

前提条件【重点】

  1. 继承或者实现【二选一】
  2. 方法的重写【意义体现:不重写,无意义】
  3. 父类引用指向子类对象【格式体现】

3.2 多态的定义与使用格式

多态的定义格式:就是父类的引用变量指向子类对象

父类类型 变量名 = new 子类对象;

父类类型:指子类对象继承的父类类型,或者实现的父接口类型。

普通类多态定义的格式

//父类 变量名 = new 子类();
class Fu {}
class Zi extends Fu {}
//类的多态使用
Fu f = new Zi();

抽象类多态定义的格式

//抽象类 变量名 = new 抽象类子类();
abstract class Fu {
        public abstract void method();
 }
class Zi extends Fu {
	public void method(){
	      System.out.println(“重写父类抽象方法”);
	}
}
//类的多态使用
Fu fu= new Zi();

接口多态定义的格式

//接口 变量名 = new 接口实现类();
interface Fu {
  	public abstract void method();
}
class Zi implements Fu {
    public void method(){
     	System.out.println(“重写接口抽象方法”);
	}
}
//接口的多态使用
Fu fu = new Zi();

需要注意的是,同一个父类的方法会被不同的子类重写。在调用方法时,调用的为各个子类重写后的方法。如:

Person p1 = new Student();
Person p2 = new Teacher();
p1.work(); //p1会调用Student类中重写的work方法
p2.work(); //p2会调用Teacher类中重写的work方法

当变量名指向不同的子类对象时,由于每个子类重写父类方法的内容不同,所以会调用不同的方法。


3.3 多态-成员的特点

多态调用变量时:

编译时期:到父类中找有没有这个变量,如果有编译成功,如果没有编译失败.
运行时期:访问的是父类成员变量的值
简单记:编译看=号左边,运行看=左边

public class Fu {
	int num = 4;
}
public class Zi extends Fu {
	int num = 5;
    int a = 10;
}
public class Demo {
	public static void main(String[] args) 	{
		Fu fz = new Zi();
      	System.out.println(fz.num);//4
        //System.out.println(fz.a);// 父类中没有这个变量 编译失败
	}
}

多态调用方法时:

编译时期:到父类中去找这个方法,如果有编译成功,如果没有编译失败
运行时期:运行的是子类重写后的方法.
简而言之:编译看左边,运行看右边。

public class Fu{
  public void show1(){
    System.out.println("fu");
  }  
}
public class Zi{
  //子类重写父类方法
  public void show1(){
    System.out.println("Zi");
  }
  //子类特有方法
  public void show2(){
    System.out.println("子类特有方法");
  }
}
public class Demo {
	public static void main(String[] args) 	{
		 Fu fz = new Zi();
      	 fz.show1();//打印 Zi  
         //fz.show2();//父类中没有这个方法编译失败
	}
}

3.4 多态的好处

实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。

举例:

定义父类:

public abstract class Animal {  
    public abstract void eat();  
}  

定义子类:

class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
}  

class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
}

定义测试类:

public class Test {
    public static void main(String[] args) {
        // 多态形式,创建对象
        Cat c = new Cat();  
        Dog d = new Dog(); 

        // 调用showCatEat 
        showCatEat(c);
        // 调用showDogEat 
        showDogEat(d); 

        /*
        以上两个方法, 均可以被showAnimalEat(Animal a)方法所替代
        而执行效果一致
        */
        showAnimalEat(c);
        showAnimalEat(d); 
    }

    public static void showCatEat (Cat c){
        c.eat(); 
    }

    public static void showDogEat (Dog d){
        d.eat();
    }

    public static void showAnimalEat (Animal a){
        a.eat();
    }
}

由于多态特性的支持,showAnimalEat方法的Animal类型,是Cat和Dog的父类类型,父类类型接收子类对象,当然可以把Cat对象和Dog对象,传递给方法。

当eat方法执行时,多态规定,执行的是子类重写的方法,那么效果自然与showCatEat、showDogEat方法一致,所以showAnimalEat完全可以替代以上两方法。

不仅仅是替代,在扩展性方面,无论之后再多的子类出现,我们都不需要编写showXxxEat方法了,直接使用showAnimalEat都可以完成。

所以,多态的好处体现在可以使程序编写的更简单,并有良好的扩展。


3.5 多态的转型

多态的转型分为向上转型与向下转型两种:

  • 向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的,称作自动类型转换。

当父类引用指向一个子类对象时,便是向上转型。

父类类型 变量名 = new 子类类型();
如:Animal a = new Cat();


  • 向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的,称作强制类型转换。

一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。

子类类型 变量名 = (子类类型) 父类变量名;
如:Cat c = (Cat) a;


为什么要转型?

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,无法调用子类独有,父类中没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。

转型演示,代码如下:

abstract class Animal {  
    abstract void eat();  
}  

class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
    public void catchMouse() {  
        System.out.println("抓老鼠");  
    }  
}  

class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
    public void watchHouse() {  
        System.out.println("看家");  
    }  
}

测试类:

public class Test {
    public static void main(String[] args) {
        // 向上转型  
        Animal a = new Cat();  
        a.eat(); 				// 调用的是 Cat 的 eat

        // 向下转型  
        Cat c = (Cat)a;       
        c.catchMouse(); 		// 调用的是 Cat 的 catchMouse
    }  
}

转型的异常

转型的过程中,一不小心就会遇到这样的问题,请看如下代码:

public class Test {
    public static void main(String[] args) {
        // 向上转型  
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat

        // 向下转型  
        Dog d = (Dog)a;       
        d.watchHouse();        // 调用的是 Dog 的 watchHouse 【运行报错】
    }  
}

这段代码可以通过编译,但是运行时,却报出了 ClassCastException ,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。

为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下:

变量名 instanceof 数据类型
如果变量属于该数据类型,返回true。
如果变量不属于该数据类型,返回false。

所以,转换前,我们最好先做一个判断,举例:

public class Test {
    public static void main(String[] args) {
        // 向上转型  
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat

        // 向下转型  
        if (a instanceof Cat){
            Cat c = (Cat)a;       
            c.catchMouse();        // 调用的是 Cat 的 catchMouse
        } else if (a instanceof Dog){
            Dog d = (Dog)a;       
            d.watchHouse();       // 调用的是 Dog 的 watchHouse
        }
    }  
}

学习到这里,面向对象的三大特征结束!

总结下封装、继承、多态的作用:

  • 封装:把对象的属性与方法的实现细节隐藏,仅对外提供一些公共的访问方式
  • 继承:子类会自动拥有父类所有可继承的属性和方法。
  • 多态:配合继承与方法重写提高了代码的复用性与扩展性;如果没有方法重写,则多态同样没有意义。


四、综合案例

定义笔记本类,具备开机,关机和使用USB设备的功能。具体是什么USB设备,笔记本并不关心,只要符合USB规格的设备都可以。鼠标和键盘要想能在电脑上使用,那么鼠标和键盘也必须遵守USB规范,不然鼠标和键盘的生产出来无法使用;

进行描述笔记本类,实现笔记本使用USB鼠标、USB键盘

  • USB接口,包含开启功能、关闭功能

  • 笔记本类,包含运行功能、关机功能、使用USB设备功能

  • 鼠标类,要符合USB接口

  • 键盘类,要符合USB接口

参考

import java.util.ArrayList;

class Laptop{
    ArrayList<USB> usbs;
    
    public void turnOn(){
        System.out.println("笔记本开机");
    }

    public void turnOff(){
        System.out.println("笔记本关机");
    }

    public void load(ArrayList<USB> usbs){
        this.usbs = usbs;
    }

    public void check(){
        for (int i = 0; i < usbs.size(); i++) {
            usbs.get(i).open();
        }
    }
}

interface USB{
    public void open();
    public void close();

}

class Mouse implements USB{
    @Override
    public void open() {
        System.out.println("鼠标插上");
    }

    @Override
    public void close() {
        System.out.println("拔出鼠标");
    }
}

class KeyBoard implements USB{
    @Override
    public void open() {
        System.out.println("键盘插上");
    }

    @Override
    public void close() {
        System.out.println("拔出键盘");
    }
}

//测试类
public class Test06 {
	public static void main(String[] args) {
		Laptop lenovo = new Laptop();
        ArrayList<USB> arr = new ArrayList<>();
        arr.addAll(Arrays.asList(new Mouse(),new KeyBoard()));
        lenovo.turnOn();
        lenovo.load(arr);
        lenovo.check();
        lenovo.turnOff();
	}
}



总结


左上角主页里面有所有系列文章喔!

消除贫穷的最好办法就是和我一起学大数据,加油,奥利给!

看到这里点个赞吧!

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值