栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JAVA之面对对象编程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JAVA之面对对象编程

目录

前言

权限修饰符

封装思想

Private实现封装 

Default(包访问权限)

继承思想

 protected

super关键字 

 super修饰属性

super修饰方法 

super修饰构造方法

super修饰普通方法 

final的补充

多态思想

向上转型

多态的前提——方法重写(override)

向下转型 


前言

我们在前面已经介绍了什么叫面向对象编程,现在我们在这章博客详细的去介绍面对对象编程的几大特性封装,多态和继承

权限修饰符

指的是修饰的属性,方法,类,可见的范围有多大,一共有四大访问修饰符,可见范围从小到大

private

  • private 表示私有的,被private修饰的属性和方法,只在当前类内部可见,出来类的范围,对外部就完全隐蔽了,外部不知道其的存在
  • default 不写任何修饰权限的修饰符,表示的就是default,包访问权限,当前包的内部可见,不包括子包(同级目录可见)
  • protected 继承,不同包的有继承关系的类之间可见,也包括同一个包下没有继承关系的类
  • public 公开的,被public修饰的,当前整个程序(项目)都是可以使用的

封装思想

封装是为了实现程序的保护性和易用性,让使用者不必太知道实现者是如何实现的,只要能用就行,降低了使用者的学习量

例子

拿银行卡这个类来说,银行卡有卡号,余额,密码三个属性,如果这三个属性都直接暴露在外部肯定是不合理的(直接就在卡上贴着)——>不安全,不能让这些属性可以通过对象直接调用

对于汽车这个类来说,车真正发动起来,我们需要启动发动机,变速箱,等等属性,但是现实中,我们只需要一键启动就行,对于这些具体的属性是不可见的,也是我们不关注的——>易用性

Private实现封装 

 只有Bank内部知道这三个属性的存在,在这个类的外部,是不知道这三个属性的存在,不能直接调用

那如何在外部去实现这些私有属性呢?

需要使用这个类提供的getter(取值)和setter(修改值)的方法,而且哪些属性要提供setter,哪些属性只提供getter,或者都提供,需要由这些属性来决定

import java.util.Scanner;

public class PrivateTest {
    public static void main(String[] args) {
        Bank bank=new Bank();
        System.out.println(bank.getCardNum());
        System.out.println(bank.getSal());
        bank.setPassword();
    }
}
class Bank{
    private int cardNum=145678;
    private double sal=20000.6;
    private String password="12345678";

    public int getCardNum() {
        return cardNum;
    }

    public double getSal() {
        return sal;
    }

    public void setPassword() {
      int count=0;
      Scanner scanner=new Scanner(System.in);
      while (true){
          System.out.println("请输入原来的密码");
          String oldPassword=scanner.nextLine();
          count++;
          if (oldPassword.equals(password)){
              System.out.println("验证成功,请输入你的新密码");
              String newPassword=scanner.nextLine();
              password=newPassword;
              System.out.println("密码修改成功");
              break;
          }
          else {
              System.out.println("验证失败,请查证后重新输入");
              if (count==3){
                  System.out.println("尝试次数过多,银行卡已经锁定");
                  break;
              }
          }
      }
    }
}

实现了对银行卡这个类的属性包含,让别人不能直接修改卡号和余额,只能通过Bank这个类的类方法去提取卡号和余额的数据,能改动密码,但是必须按照Bank这个类的规定的方法去改,实现了安全性

public class PrivateTest {
    public static void main(String[] args) {
        Car car=new Car();
        car.start();
    }
}
class Car{
    private String engine;
    private String bianSuXiang;
    private String guLu;
    public void start(){
        engine="引擎启动";
        bianSuXiang="启动变速箱";
        guLu="轮胎开始转";
        System.out.println("汽车启动了");
    }
}

对于Car这个类,我们假设启动汽车,不需要知道其内部如何构成,只需要调用启动这个函数,就直接可以启动

阿里编码规范 JAVA类中所有的成员变量一律使用private封装,并根据自身属性实际情况对我提供getter和setter方法

Default(包访问权限)

default就是什么都不写的限权标识符,默认的就是包访问限权,表示的范围就是在一个包中,子包和包外都不行

什么是包呢?

  • 同一个包下的类可以调用default修饰权限的属性或者方法
  • 不同包下的类不能使用default修饰的属性或者非方法
  • 相同包下不同的子包也不能使用default修饰的属性和方法

(Package)的注意点

  • 在不同的包中可以定义相同名称的类
  • 包的命名使用全小写,多个单词下划线分割
  • 引用一个包的内容,需要import 包名.类名(类的全名就是包名.类名)不能直接导入一个包,只能导入一个包下面的某个类
  • JAVA中的包就是操作系统中的文件夹,声明一个包使用package关键字,若存在多个文集夹的嵌套,需要使用.来分割

如何导入包中的类

首先我们要明确一个类的全称是(包的路径+类的名称),而且在JAVA中我们只能导入某个具体的类,不能导入一个包

  •  在src下的test类导入animal这个类
  • 导入系统提供的包中的类 import 包的路径.类名
  • 导入一个包中所有的类  import 包的路径.*
  • 但是一般不推荐用*这种方式,因为会产生一些问题,我们可以使用包的完全类型,比如
    java.sql.Date date=new java.sql.Date();或导入明确的类
  • import static 导入一个类的静态方法和属性,可以直接访问这个类的静态方法和属性,不需要通过类名称.方法或者属性 这样写不推荐

常见的系统包

  • java.lang:JDK的基础类 System Stirng Object都在这个包下,JDK1.1之后的这个包所有的类自动导入
  • java.lang.reflect 反射开发包
  • java.util 工具包(集合类都在这个包内 Arrays,LinkList,HashMap)
  • java.io I/O开发包,文件读取和写入
  • java.net 网络编程开发包 Socket
  • java.sql 数据库开发用到的包 

继承思想

为什么引入继承?

 

我们可以发现我们定义的三个类,他们的代码是一样的,这样写代码会导致代码冗余,那我们怎么解决呢?

我们可以发现狗和猫都是属于动物的,可以描述成Cat is a Animal Dog is a Animal ,所以按道理说,动物应该有的行为和属性,狗和猫都应该有,就像名字和进食这种属性和行为,那我们就引入继承这种方法,让狗和猫的类去继承动物这个类

发生继承后的代码

 

 

 

  • JAVA中用extends来表示继承
  • 继承发生,像狗和猫这种类就是动物这个类的子类/派生类,动物就是狗的父类/基类
  • 一个子类中只能用extends继承一个父类(单继承),但是可以进行多层继承,就像一个人只能有一个生理意义上的父亲,但是一个人可以有父亲,也能有爷爷和祖宗

  • JAVA中不能为了省略代码就去使用继承,像要使用阶乘必须要满足is a关系,就像Dog is animal,没有这种逻辑关系千万不能使用继承
  • 对于静态的方法和属性也是一样,子类都会继承,使用则看这个属性或者方法的修饰权限,区别就是在一个通过类调用,一个通过对象调用
  • 子类继承了父类,那么父类中所有的属性和方法子类就是天然具备了(public修饰的属性和方法能直接使用)(private修饰的子类也会继承,但是是无法直接使用的,因为虽然继承,但是子类或者其他类都是没有限权去访问,只有本类可以去访问),对于隐式继承虽然不能直接调用,但是可以通过父类提供的方法去调用

 如何理解隐式继承?

就像我们继承了我们父亲,但是我们父亲的余额是对于我们父亲是一个私有属性,我们肯定不能随意的去花我们父亲的钱(不能直接调用),我们要是想花我们父亲的钱,必须要按照我们父亲的要求才能去花,得到他的允许才能花(通过父类提供的方法去调用)

 protected

它的访问权限是在不同包中的子类可见,和同一个包的下的类(不包括子包)

各个类所在的包的情况

Animal类的情况

  • 不同包的非子类不可见
  • 不同包的子类可见
  • 相同包下的类(没有继承关系)可以直接调用(不包括子包)
  • 加深理解

super关键字 

首先我们来看一个现象

 我们发现我们在new一个Taidi对象,会先产生一个父类对象Dog(调用了Dog的构造方法),我们可以类比一下现实生活,如果没有你爸怎么来的你,所以new一个对象肯定要先产生父类对象,如果父类对象也有父类对象,也会遵循这个规则(没有你爷,那来的你爸)

 一道经典的阿里面试题来帮助我们理解静态块+构造块+构造方法+继承

class B{
    public B(){
        System.out.println("1.B的构造方法——————————————");
    }
    {
        System.out.println("2.B的构造块————————————————");
    }
    static {
        System.out.println("3.B的静态块————————————————");
    }
}
 class D extends B {
    public D(){
        System.out.println("4.D的构造方法——————————————");
    }
    {
        System.out.println("5.D的构造块————————————————");
    }
    static {
        System.out.println("6.D的静态块————————————————");
    }
    public static void main(String[] args) {
        System.out.println("7.main开始————————————————");
        new D();
        new D();
        System.out.println("8.main结束————————————————");
    }
}

  • 主方法在D这个类中,所以程序想要从主方法进入运行起来,我们先去加载D这个类,因为B是D的父类,所以加载D之前要先加载B,所以3这个静态块最先加载,然后加载B,执行6D的静态块
  • 进入主方法后,我们们执行7这条语句
  • 我们进行new对象时,先会产生父类对象也就是B,然后产生D的对象,会执行构造方法,但是构造块比构造方法先执行 也就是先执行 2 1 5 4
  • 最后执行8,然后程序结束

 super修饰属性

表示从父类中寻找同名属性

父类People

  •  如果在一个方法里,局部变量和成员变量和父类的成员变量都是一个名字,那么就会遵循就近原则,局部>成员>父类(多层)
  • this在继承中的向上寻找
  • super只会去从直接父类去找同名属性,若不存在就在向上寻找

 当super和private碰撞到一起

 

 虽然我们可以在Person父类中找到,但是因为是private权限,所以不能在子类China中使用

super修饰方法 

super修饰构造方法
 class China extends Person{
//    public String name="china";
    public  China(){
        System.out.println("这是Chian的无参构造");
    }
    public void fun(){
        System.out.println(super.name);
    }

    public static void main(String[] args) {
        China china=new China();
    }
}
class Person extends Animal {
    public Person (){
        System.out.println("这是Person的无参构造");
    }
    public String name="person";

}
 class China extends Person{
//    public String name="china";
    public  China(){
        System.out.println("这是Chian的无参构造");
    }
    public void fun(){
        System.out.println(super.name);
    }

    public static void main(String[] args) {
        China china=new China();
    }
}

我们new一个China对象,会发生什么

 当产生子类对象时,默认先产生父类对象,若父类对象还有继承,继承向上先产生祖类对象

如果将Person类写一个有参构造

 我们再去new一个Chian对象,会怎么样

 为什么会发生这样?因为在有继承关系的类中,子类的构造方法的第一行会默认会有super(),表示调用父类的无参构造方法,如果父类没有无参构造,那么就需要我们在子类的构造方法自己写super(有参构造),表示调用父类的有参构造,不然就会报错

this()和super()的关系

因为this()表示调用构造方法,也必须放在第一行,super()调用父类构造方法,也必须放在第一行,所以它两不能同时显式同时出现,

super修饰普通方法 

直接从父类中去寻找同名方法,跟属性差不多

 class Person  {

    public Person (){
        System.out.println("这是Person的无参构造");
    }
    public String name="person";
    public void fun(){
        System.out.println("这是Person的fun函数");
    }
}
 class China extends Person{
//    public String name="china";
    public  China(){
        System.out.println("这是Chian的无参构造");
    }
    public void fun(){
        System.out.println(super.name);
    }
    public void test(){
        super.fun();
    }

    public static void main(String[] args) {
        China china=new China();
        china.test();
    }
}

test调用的式Person的fun方法

但是super不能指代当前父类的对象引用

final的补充
  1. 修饰属性表示属性的值和类型都不能改变
  2. 修饰类,表示这个类无法被继承

多态思想

多态:一个引用可以表现为多种行为/特性

首先我们来看编程中会存储的一个问题

 class Animal {
    public String name="Animal";
    public void eat(){
        System.out.println(name+"的eat方法");
    }
}
 class Bird extends Animal{
    public String name="Bird";
    public void eat(){
        System.out.println(name+"的eat方法");
    }
}
 class Duck extends Bird{
    public String name="Duck";
    public void eat(){
        System.out.println(name+"的eat方法");
    }
}

public class Test {
    public static void main(String[] args) {
        fun(new Animal());
        fun(new Bird());
        fun(new Duck());
    }
    public static void fun (Animal animal){
       animal.eat();
    }
    public static void fun (Bird bird){
       bird.eat();
    } 
    public static void fun (Duck duck){
       duck.eat();
    } 
}

假如我们调用一个动物类的输出自己名字的功能,我们如果像以前一样,那么我们需要重载所有动物的动物类的fun功能的方法,在自然界中,动物的子类有几百万种,那么就要将fun写上几百万次?这显然是不可能的,既然任何动物的子类都是动物,我们为什么不能让动物来指代这些子类,所以我们引入了向上转型 

向上转型
  • 语法 父类名称 父类引用=new 子类对象();
  • 前提 向上转型必须发生在有继承关系的类之间,可以不是之间子类,可以是子孙类,反正满足 is a的关系就行
  • 意义通过最顶层的父类引用,指代所有的子类对象

引入向上转型后的代码

 class Animal {
    public String name="Animal";
    public void eat(){
        System.out.println(name+"的eat方法");
    }
}
 class Bird extends Animal{
    public String name="Bird";
    public void eat(){
        System.out.println(name+"的eat方法");
    }
}
 class Duck extends Bird{
    public String name="Duck";
    public void eat(){
        System.out.println(name+"的eat方法");
    }
}


public class Test {
    public static void main(String[] args) {
        fun(new Animal());
        fun(new Bird());
        fun(new Duck());
    }
    public static void fun (Animal animal){
       animal.eat();
    }
}

 我们引入了向上转型之后,我们只需要写一个fun方法,用顶层父类去指代所有的子类,就可以让所有子类实现这个功能,就非常方便

向上转型的时机

  • 引用赋值的时候
  • 方法传参的时候
  • 方法返回值的时候

由向上转型引出多态

我们发现在fun这个方法中当我们传入不同的对象的时候,表现了出不同的eat方法(方法对应着行为) 我们将这种同一个引用,同一个方法名称,根据对象的不同表现出不同的行为称为多态

 

多态的前提——方法重写(override)

为什么在刚刚由相同的引用,相同的方法名,表现出了不同的行为,因为在子类中重写了eat方法。

概念 

发生在有继承关系的类之间,子类定义了和父类除了权限不同,其他全部相同的方法,这一组方法被称为方法重写

如果在子类中没有重写方法,会调用谁的方法呢(就近原则,先碰到谁,就直接调用)

如果在Duck类中没有重写eat方法,那么调用结果是

如果在Duck和Bird都没有去重写eat方法,那么调用结果是 如何分辨到底调用了谁的方法

向上转型时,我们在看调用的时候,不要看前半部分引用类型是什么,主要看new的是那个类,如果这个类重写了相关的方法,则调用的一定是重写后的方法,如果new的这个类中没有重写这个方法,就遵循就近原则去向上寻找

 方法重写与限权的关系 

 当发生重写的时候,子类权限必须要大于等于父类权限(不包括private)

  • 当父类权限是public 子类为protected时 protected 

     

  • 当父类权限为protected 子类也为protected时 protected==protected

  • 当父类权限为protetced 子类为public public>protected

  • 当父类为private 子类为public (这个规则类名不包括privtae) 

 为什么不包括private权限呢?

package polymorphism;
 class Bird extends Animal{
    public String name="Bird";
    public void eat(){
        System.out.println(name+"的eat方法");
    }
}
class Duck extends Bird{
     public String name="Duck";
    public void eat(){
        System.out.println(name+"的eat方法");
    }
}
public class Animal {
    public String name="Animal";
    private void eat(){
        System.out.println(name+"的eat方法");
    }

    public static void main(String[] args) {
        fun(new Animal());
        fun(new Bird());
        fun(new Duck());
    }
    public static void fun (Animal animal){
        animal.eat();
    }
}

 为什么我们在每个子类重写了eat方法,调用之后都是Animal的方法,因为Animal的方法是privtae,它的权限只能在本类中可用,你的子类继承也没什么用,因为只能在本类中调用(本类调用,也只是本类的方法),在子类中根本就不知道这个方法的存在,那么何来重写之说,那用private权限修饰,还重写就没有任何意义

重写和static的关系

我们重写的本质是为了多态,多态的本质是调用不同的对象,这些对象的类重写了父类的方法,从而表现出了不同的行为特性,只要涉及到对象就跟static无关,所以方法重写只发生在普通方法

重写方法的返回值问题

在一般情况下,重写的方法的返回值必须是一样的,不然就会报错,但是存在一种特殊情况,就是在继承关系中,可以至少是向上转型的类

 

 

 


 检查重写的手段@Override来检查

一道题加深重写和继承

重写(Override)和重载(Overload)的区别

         No               区别          重载                    重写
          1        概念方法名相同,参数的类型以及个数不同     方法名称,返回值类型(多态中有一种特殊情况),参数类型及个数完全相同
          2        范围          一个类        继承关系
          3        限制       没有权限要求被覆写的方法不能拥有比父类更严格的访问权限
          4        static        没有要求不能覆写static方法

向下转型 

为什么引入向下转型?

 我们可以看到Duck类中,我们只能调用Animal中有的方法,而不能去调用Duck特有的swim方法,我们引入多态调用方法的规则

 我们知道引入向上转型就是为了写代码方便,肯定是要用的,所以我们引入了向下转型

如何理解向下转型?

 

 从上知道,我们向下转型是有前提的,如果随意向下转,可能会报错,如何避免?

instanceof——>返回布尔值,表示引用指向的本质是不是该类的对象

 什么时候使用向上转型?什么时候使用向下转型?

  • 当一个方法接收一个类和当前类的子类,参数就为指定的父类引用,发生向上转型
  • 当某个特殊情况下,需要使用子类的拓展的方法,那么就把这个原本向上转型的类向下转型为原子类引用
 抽象类

向上转型的给我们带来的最大好处就是参数统一化,我们使用一个共同的父类引用,就能接收所有的子类对象

比如我们描述一个图像类,我们要输出它的形状,这是比较抽象的,无法具体化,所以图像类的每个子类都需要重写这个输出形状的方法

 class Sharp {
    public void print(){

    }
}
 class Circle extends Sharp{
    public void print(){
        System.out.println("●");
    }
}
 class Square extends Sharp{
    public void print(){
        System.out.println("▬");
    }
}

public class Test {
    public static void main(String[] args) {
        new Circle().print();
        new Square().print();
    }
    public static void fun(Sharp sharp){
        sharp.print();
    }
}


 我们发现Sharp的子类想要调用ptint方法,必须要重写print方法,那如何保证子类必须必须去重写父类的print方法呢——>抽象方法(强制让子类去重写父类的抽象方法)

​​​​​​

 abstract class Sharp {
    public abstract void print();
}
 class Circle extends Sharp{
    public void print(){
        System.out.println("●");
    }
}
 class Square extends Sharp{
    public void print(){
        System.out.println("▬");
    }
}

public class Test {
    public static void main(String[] args) {
        new Circle().print();
        new Square().print();
    }
    public static void fun(Sharp sharp){
        sharp.print();
    }
}


如果在子类中不重写父类的抽象方法,则会报错

 

抽象的概念

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/844648.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号