JAVA入门笔记9 —— 类和对象深入

  • 参考书:《Java语言程序设计与数据结构(基础篇)》—— 梁勇
  • 参考视频教程:java教程
  • 基于之前的文章,我们已经对java中的类和对象有了初步认识:JAVA入门笔记6 —— 类和对象初步
  • 这篇文章将进一步探讨java中面向对象的三大特征

一、封装性

1. 封装的思想

  • 封装:指对外部不可见,外部只能通过对象提供的接口来访问。这就好像是一个计算器,我们使用时不需要管它内部是怎样实现计算的,只要关注于使用它的接口(按键)即可

2. 封装的好处

  • 封装的好处是
    1. 良好的封装能够隐藏实现细节,减少耦合
    2. 封装后可以修改类的内部实现,而无需修改使用了该类的客户代码
    3. 可以对成员进行更精确的控制
  • 这三点里,第一点其实在面向过程中也能由函数体现出来,第二点和第三点则是比较重要的,下面重点分析一下

(1)通过修改类实现,减少用户代码的改动

  • 若不进行封装,类外的用户代码会直接访问类成员变量并与之进行交互(就好像C中的结构那样),一旦对类成员变量进行修改,就很可能要修改大量用户代码
  • 示例:
    1. 我们现在有一个学生类,没有使用封装

      public class Student {
              
              
          String name;
          int age;
      }
      

      在使用它的时候,可以写成这样

      Student stu = new Student();
      stu.age = 10;
      
    2. 现在对类成员变量进行修改

      public class Student {
              
              
          String name;
          String age;
      }
      

      这时我们不得不修改所有使用age成员变量的地方

      Student stu = new Student();
      stu.age =10;
      

      如果这个类被用户代码大量使用,将面临很多修改工作

    3. 现在以封装的思想重写这个类

      public class Student {
              
              
          private String name;
          private int age;
      
          public void setName(String name) {
              
               this.name = name; }
          public void setAge(int age) {
              
               this.age = age; }
          public String getName() {
              
               return name; }
          public int getAge() {
              
               return age; }
      }
      
    4. 这时若要修改类成员变量,可以通过修改gettersetter方法对用户代码屏蔽这个变化,从而避免修改大量用户代码

      public class Student {
              
              
          private String name;
          private String age;
      
          public void setName(String name) {
              
               this.name = name; }
          public void setAge(int age) {
              
               this.age = Integer.toString(age); }
          public String getName() {
              
               return name; }
          public int getAge() {
              
               return Integer.parseInt(age); }
      }
      

(2)对成员进行更精确的控制

  • 进行封装后,我们可以在setter方法中对输入进行检查
    public class Student{
          
          
        private int age;  
    	
    	// 在setter中,可以对数据进行检查
        public void setAge(int age) {
          
          
            if(num<100 && num>=0)		
                this.age = num;
            else
                System.out.println("数据不合理");
        }
    
        public int getAge() {
          
          
            return age;
        }
    }
    
  • 如果不给某个私有成员设置setter方法,可以把这个成员变量变成只读的

3. 在java中使用封装性

  • 简单说,就是通过访问权限关键字设置类和类成员的访问权限,禁止类外部直接对私有成员(private权限)进行操作,只允许通过公共权限的接口(public权限的getter/setter方法)操作类私有的成员
  • 以下主要讨论对类成员变量的封装,当然也可以对成员方法进行封装(比如通过私有化构造函数可以实现单例化编程),不过本文不展开讨论

(1)类访问权限

  • java类有两种种访问权限:默认/public
    1. 默认class前不加任何修饰即为默认权限。该类只能被自身所在的包中的类使用。
    2. public:在class前添加public修饰符。该类能被所有包中的类使用。
  • 注意:如果在一个源程序文件(.java文件)中声明了多个类,只能有一个类的权限关键字是public,并且这个类的名字应该和程序文件同名,main方法也应该在这个类中。

(2)成员变量访问权限

  • java类成员变量有四种访问权限:默认/public/protect/private,其访问特性如下

    1. 默认:成员变量前不加任何访问修饰符。该模式下,只允许在同一个包中进行访问

      扫描二维码关注公众号,回复: 12423705 查看本文章
    2. public:Java语言中访问限制最宽的修饰符。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包(package)访问。虽然java中没有全局变量的概念,但通过public static修饰,可以近似地实现全局变量

      // public赋予成员变量全局访问权限,static允许不创建实例地使用它们
      public class globalVars {
              
              
          public static String string;
          public static int a;
      }
      
      // 使用 "全局变量"
      String tmpString = globalVars.string;
      int tmpInt = globalVars.a;
      
    3. private: Java语言中对访问权限限制的最窄的修饰符。被其修饰的类、属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问

    4. protect: 介于publicprivate 之间的一种访问修饰符。被其修饰的类、属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问

  • 小结
    在这里插入图片描述

(3)权限设置原则

  • 类常常是public
  • 成员变量常常是private
  • 构造方法一般是public
  • 方法gettersetterpublic
  • 其它方法需要根据实际的需要而定。

二、继承性

1. 继承的思想

  • 继承主要解决的问题:共性抽取

  • 举个例子:现在要编写一个企业管理程序,要设置若干职务类,如讲师助教保安等,它们中有一些相同的的成员变量和方法姓名工号…也有自己特有的成员变量和方法

    • 若不使用继承,我们在每个职务类的实现中都要复制粘贴一遍这些相同的类成员,代码复用性差
    • 利用继承性,我们可以定义一个员工类作为父类提供共性的类成员,各个职务类作为子类,通过继承父类来获取这些成员,这样实现职务类时就可以专注于编写其特有的类成员,避免了重复的代码,提高了代码复用性。
      在这里插入图片描述
  • 简单来说,继承就是使子类的对象拥有父类的全部属性和行为,同时可以增添自己的所特有的属性和行为。这样可以节省写共同具有的属性和方法代码的时间,有利于代码的复用,这就是继承的基本思想

2. 在java中使用继承性

(1)java中继承的特点

  1. java语言是单继承的,每个子类有且只能有一个父类。子类的实例都是父类的实例,但不能说父类的实例是子类的实例。
  2. java语言可以多级继承。继承树中靠近根部的类都是靠近叶的类的父类,但每个类的直接父类只有一个
  3. 一个父类可以有多个子类

(2)定义和使用子类

  • 定义父类:java中定义父类不需要特殊写法,直接定义的类都可以用作父类

  • 定义子类:通过extends关键字指定父类

    修饰符 class 子类名 extends 父类名{
          
          
    	类体
    }
    
  • 示例:

    1. 定义父类Employee

      // Employee.java
      public class Employee {
              
              
          private String name;
          private int age;
      
          public void setName(String name) {
              
               this.name = name; }
          public void setAge(int age) {
              
               this.age = age; }
          public String getName() {
              
               return name; }
          public int getAge() {
              
               return age; }
      
          public void show(){
              
              
              System.out.println(name+" "+age);
          }
      }
      
    2. 定义两个子类TeacherAssistant

      // Assistant.java
      public class Assistant extends Employee {
              
              
      }
      
      // Teacher.java
      public class Teacher extends Employee {
              
              
      }
      
    3. 直接使用子类继承到的成员

      public class Main {
              
              
          public static void main(String[] args) {
              
              
              Teacher teacher = new Teacher();
              teacher.setAge(30);
              teacher.setName("教师");
              teacher.show();     // 教师 30
      
              Assistant assistant = new Assistant();
              assistant.setAge(22);
              assistant.setName("助教");
              assistant.show();   // 助教 22
          }
      }
      

(3)继承中成员的访问特性

1. 成员变量的访问特性

  1. 子类中有成员变量和父类重名,访问重名成员变量时

    1. 访问特性
      1. 直接通过子类实例.成员变量访问:看定义对象时等号左边是哪个类,就优先在哪个类中找。即先在子类中找,找不到向上去父类找,还找不到报错
      2. 使用子类实例.成员方法时,成员方法中访问了重名成员变量:看方法属于哪个类,就优先在哪个类找。若方法是子类的特有方法(非继承)或继承自父类并在在子类覆写了,则优先在子类找,找不到则向上去父类找,还找不到报错;若方法是继承自父类且没有在子类覆写,优先在父类找,找不到向上找,还找不到报错
    2. 示例
      class Father {
              
              
          int num = 100;
      
          public void showNum_F() {
              
              
              System.out.println(num);
          }
      }
      
      class Son extends Father {
              
              
          int num = 200;
      
          public void showNum_S(){
              
              
              System.out.println(num);
          }
      }
      
      public class Main {
              
              
          public static void main(String[] args) {
              
              
              Father f = new Father();
              Son s = new Son();
      
              System.out.println(f.num);  // 100
              System.out.println(s.num);  // 200
              f.showNum_F();              // 100
              s.showNum_S();              // 200
              s.showNum_F();              // 100
          }
      }
      
  2. 子类成员变量、父类成员变量、方法局部变量三者重名

    1. 访问特性
      1. 访问局部变量:直接写变量名
      2. 访问子类成员变量:this.变量名
      3. 访问父类成员变量:super.变量名
    2. 示例
      class Father {
              
              
          int num = 100;
      }
      
      class Son extends Father {
              
              
          int num = 200;
      
          public void show(){
              
              
              int num = 300;
              System.out.println(num);        // 300
              System.out.println(this.num);   // 200
              System.out.println(super.num);  // 100
          }
      }
      
      public class Main {
              
              
          public static void main(String[] args) {
              
              
              Son s = new Son();
              s.show();
          }
      }
      

2. 成员方法的访问特性

  1. 子类和父类方法重名时

    1. 通过子类实例.成员方法调用时,看定义对象时等号右边是哪个类(new的是哪个类),就优先在哪个类中找。即优先在子类找,找不到向上去父类找,一直找不到报错
    2. 一定要访问父类的同名方法,使用super关键字,类似上面关于同名变量的说明
  2. 示例

    class Father {
          
          
        public void method(){
          
          
            System.out.println("父类方法执行");
        }
    
        public void method_father(){
          
          
            System.out.println("父类特有方法执行");
        }
    }
    
    class Son extends Father {
          
          
        public void method(){
          
          
            System.out.println("子类方法执行");
            super.method();     // 调用父类的同名方法
        }
    }
    
    public class Main {
          
          
        public static void main(String[] args) {
          
          
            Son s = new Son();
            s.method();         // 子类方法执行  
                                // 父类方法执行
            s.method_father();  // 父类特有方法执行
        }
    }
    

(4)继承中成员方法的覆写

  • 概念:在继承的类中重写父类的成员方法,由于继承中子类成员方法的访问特性(“就近原则”),通过子类和父类对象实例调用时会调用到不同的方法
  • 思想:当需求改变时,对于已经投入使用的类,尽量不要改动,而是继承定义一个子类,改动和添加新的内容
  • 注意
    1. 必须保证父子类方法名相同,参数列表也相同
    2. 子类方法的返回值必须小于等于父类方法的返回值范围。这里的小于等于是继承关系上的,比如父类返回object变量,子类可以返回String变量,反之不行
    3. 子类方法的权限修饰符必须大于等于父类方法public > protected > (default) > private
  • 正确性检测:可选在子类覆写的方法前写一行注解@override,用来检测是否是有效的正确覆盖重写

(5)继承中构造方法的访问特点

  1. 子类构造时,编译器会在子类构造方法最前面隐式地加上super();,调用父类的无参构造方法
    class Father {
          
          
        public Father(){
          
          
            System.out.println("父类构造");
        }
    }
    
    class Son extends Father {
          
          
        public Son(){
          
          
            System.out.println("子类构造");
        }
    }
    
    public class Main {
          
          
        public static void main(String[] args) {
          
          
            Son s = new Son();
        }
    }
    
    /*
    父类构造
    子类构造
    */
    
    若父类中显式定义了有参构造,编译器将不生成隐式无参构造方法,这会导致子类构造方法报错。这时要么在父类显式定义无参构造,要么在子类构造方法最前使用super(参数)显式调用父类已有的重载父类构造方法
  • 注意:
    1. 子类中必须使用super(参数列表)调用父类的重载构造方法(显式或隐式)
    2. 子类中的super()调用必须是子类构造方法的第一个语句(无论显式还是隐式)
    3. 只有子类构造方法中才能使用super()调用父类构造方法
    4. 一个子类构造方法中不能进行多次super()调用

(6)this和super的小结

  • super的三种用法

    1. 子类成员方法中访问父类成员变量
    2. 子类成员方法中访问父类成员方法
    3. 子类构造方法中访问父类构造方法
  • this的三种用法

    1. 本类成员方法中访问本类成员变量
    2. 本类成员方法中访问本类另一个成员方法
    3. 本类构造方法中访问本类另一个构造变量
  • 注意:

    1. 在构造方法中使用superthis,必须放在第一句
    2. 构造方法中superthis不能同时使用
  • 内存存储示意

    1. 示例代码
      class Son extends Father {
              
              
          int num = 20;
      
          public void method(){
              
              
              super.method();
              System.out.println("子类方法执行");
          }
      
          public void show() {
              
              
              int num = 30;
              System.out.println(num);        // 30
              System.out.println(this.num);   // 20
              System.out.println(super.num);  // 10
          }
      }
      
      public class Demo {
              
              
          public static void main(String[] args) {
              
              
              Son s = new Son();
              s.show();
              s.method();
          }
      }
      
    2. 内存示例
      在这里插入图片描述

三、多态性

1. 多态的思想

  • 多态就是对同一个对象,在不同时刻表现出来的不同形态
  • 多态允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。比如小明是一个学生,汤姆猫是只猫,但他们都是生物,都会吃饭睡觉。我们可以定义一个吃饭方法,接受一个生物类型的参数,当传入小明时,吃的是披萨;当传入汤姆猫时,吃的是猫粮。这个参数就具有多态性。

2. 在java中使用多态性

(1)多态的定义和使用

  • 代码中体现多态性,关键就是:父类引用指向子类对象
  • java中父类和子类、接口和实现类之间都可以使用多态性
  • 格式:
    父类名称 对象名 = new 子类名称();
    接口名称 对象名 = new 实现类名称();
    
  • 这样通过对象名.方法名()进行调用时,先在子类/实现类中找,找不到则去父类/接口中找

(2)多态中成员变量的访问特点

  • 多态中成员变量的访问特点类似继承中的成员变量访问

    1. 直接通过对象名称访问看定义对象时等号左边是哪个类,就优先在哪个类中找。即先在父类中找,找不到再向上找
    2. 使用对象名.成员方法()调用时,成员方法中访问了重名成员变量:看方法属于哪个类,就优先在哪个类找,找不到再向上找。这里和继承有一点区别,就是对象名.成员方法()这个调用访问不到子类的特殊方法,只能访问到父类特有或子类继承并覆写的方法。如果是在父类特有方法中,优先在父类找;若是在子类覆写的同名方法中,优先在子类找
  • 示例

    class Father{
          
          
        int num = 10;
    
        public void showNum_Father(){
          
          
            System.out.println(num);
        }
    
        public void showNum(){
          
          
            System.out.println(num);
        }
    }
    
    class Son extends Father{
          
          
        int num = 20;
    
        public void showNum_Son(){
          
          
            System.out.println(num);
        }
    
        public void showNum(){
          
          
            System.out.println(num);
        }
    }
    
    public class Main {
          
          
        public static void main(String[] args) {
          
          
            // 多态写法:父类引用指向子类对象
            Father obj = new Son();
            System.out.println(obj.num);    // 同名成员变量优先在等号左边找(父类),打印10
    
            obj.showNum_Father();           // 父类特有方法,优先在父类找,打印10
            // obj.showNum_Son();           // 不能通过多态方式访问子类特有方法,报错
            obj.showNum();                  // 子类覆写的同名方法,优先在子类找,打印20
        }
    }
    

(3)多态中成员方法的访问特点

  • 多态中成员变量的访问特点类似继承中的成员方法访问
    1. 通过对象名.成员方法()调用时,看定义对象时等号右边是哪个类(new的是哪个类),就优先在哪个类中找。即优先在子类找,找不到向上去父类找,一直找不到报错
    2. 注意和继承的区别,访问不到子类特有方法,只能访问到子类覆写的同名方法和父类特有方法
  • 示例
    class Father{
          
          
        public void method(){
          
          
            System.out.println("父类同名方法");
        }
    
        public void methodFather(){
          
          
            System.out.println("父类特有方法");
        }
    }
    
    class Son extends Father{
          
          
        public void method(){
          
          
            System.out.println("子类同名方法");
        }
    
        public void methodSon(){
          
          
            System.out.println("子类特有方法");
        }
    }
    
    public class Main {
          
          
        public static void main(String[] args) {
          
          
            // 多态写法:父类引用指向子类对象
            Father obj = new Son();
            obj.method();           // 同名方法先在等号右边找(子类)
            obj.methodFather();     // 父类特有方法去父类找
            // obj.methodSon();     // 访问不到子类特有方法,报错
        }
    }
    
    /*
    子类同名方法
    父类特有方法
    */
    

(4)多态的好处

  • 现在我们有Teacher类和Assisant类,都是继承自Employee类/实现了Employee接口。Employee中有抽象方法work(),分别被两个子类覆写。如下
    在这里插入图片描述
  • 若不用多态,就必须定义不同类型的引用变量,分别调用两个work方法,如下
    Teacher teacher = new Teacher();
    Assisant assisant = new Assisant();
    teacher.work();
    assiant.work();
    
  • 使用多态,两个引用变量可以统一为Employee类的引用变量,如下
    Employee teacher = new Teacher();
    Employee assisant = new Assisant();
    teacher.work();
    assiant.work();
    
  • 从而我们可以提取出一个job(Employee employee)方法,其中调用employee.work()。这个形参employee在传入参数不同时表现出不同对象的特性(传入的是实现子类对象,传入时自动向上转型),并在调用work()时执行了不同的动作。这正是多态的精髓

(5)对象的转型

1. 对象的向上转型

  • 含义:创建一个子类对象,把它当作父类来看待和使用

  • 格式:对象的向上转型,其实就是多态写法

    父类名 对象名 = new 子类名();
    
  • 缺点:依照多态中成员方法的访问特性,对象一旦向上转型为父类,那么就无法使用子类的特有方法

  • 注意:

    1. 向上类型转换一定是安全的。它是从小范围转向大范围。有点类似于基本类型的自动类型转换,如double num = 100;
    2. 向上转型之后,可以通过向下转型还原回去

2. 对象的向下转型

  • 含义:对象向上转型的还原动作,将父类对象还原为原来的子类对象
  • 格式:类似强制类型转换
    子类名称 对象名 = (子类名称)父类对象;
    
  • 注意:对象向下转型时必须转为原来向上转型前的类型(即它创建时的类型),否则报错ClassCastException
    在这里插入图片描述
    Animal animal = new Cat();	// 向上转型
    Cat cat = (Cat)animal;		// 向下转型还原
    // Dog dog = (Dat)animal;   // 向下转型还原,不同类,报错
    
    这就类似int num = (int)10.5;报错,有精度损失

3. 类型判断

  • 上面提到向下转型时必须要转为原来的类型,如何获取原来的类型呢?

  • 可以instanceof关键字判断一个父类引用的对象本来是什么子类

    对象名 instanceof 类名称
    

    这会返回一个boolean值,即判断当前对象能不能当作后面类型的实例

  • 可以使用instanceof结合向下转型使用,避免转型错误

    Animal animal = new Cat();		// 向上转型
    
    if(animal instanceof Cat){
          
          
    	Cat cat = (Cat)animal;		// 向下转型还原
    	// 调用Cat类特殊方法
    }	
    if(animal instanceof Dog){
          
          
    	Dog dog = (Dog)animal;		// 向下转型还原
    	// 调用Dog类特殊方法
    }	
    
    
  • 特别是当我们提取出一个方法,使用父类引用形参接受各个子类对象时,instanceof关键字非常有用

3. 多态和接口综合示例

  • 请先看 “抽象和接口” 相关内容:JAVA入门笔记8 —— 抽象和接口

  • 要求:笔记本、键盘、鼠标通过USB接口实现交互

    1. USB接口:打开设备、关闭设备
    2. 笔记本类方法:开机、关机、使用USB设备
    3. 键盘类:实现USB接口,并有按键方法type
    4. 鼠标类:实现USB接口,并有点击方法click

    示意图如下
    在这里插入图片描述

  • 代码

    1. USB接口

      public interface USB {
              
              
          public abstract void open();
          public abstract void close();
      }
      
    2. 键盘类

      public class Keyboard implements USB{
              
              
      
          @Override
          public void open() {
              
              
              System.out.println("打开键盘");
          }
      
          @Override
          public void close() {
              
              
              System.out.println("关闭键盘");
          }
      
          public void type(){
              
              
              System.out.println("按键");
          }
      }
      
    3. 鼠标类

      public class Mouse implements USB {
              
              
      
          @Override
          public void open() {
              
              
              System.out.println("打开鼠标");
          }
      
          @Override
          public void close() {
              
              
              System.out.println("关闭鼠标");
          }
      
          public void click(){
              
              
              System.out.println("点击");
          }
      }
      
      
    4. 电脑类

      public class Computer {
              
              
          public void powerOn(){
              
              
              System.out.println("电脑开机");
          }
      
          public void powerOff(){
              
              
              System.out.println("电脑关机");
          }
      
          public void useDevice(USB device){
              
              
              device.open();
      
              if(device instanceof Mouse)
                  ((Mouse) device).click();
              else if(device instanceof Keyboard)
                  ((Keyboard) device).type();
      
              device.close();
          }
      }
      
    5. Main类

      public class Main {
              
              
          public static void main(String[] args) {
              
              
              Computer computer = new Computer();
              Mouse mouse = new Mouse();
              Keyboard keyboard = new Keyboard();
      
              computer.powerOn();
              computer.useDevice(mouse);
              computer.useDevice(keyboard);
              computer.powerOff();
          }
      }
      
      /*
      电脑开机
      打开鼠标
      点击
      关闭鼠标
      打开键盘
      按键
      关闭键盘
      电脑关机
      */
      

猜你喜欢

转载自blog.csdn.net/wxc971231/article/details/108989528