Java基础整理(六)

面向对象——封装继承多态

6. 封装

  • 封装就是对数据的隐藏,应禁止访问一个对象中数据的实际表示,而通过操作接口来访问。

  • 我们的程序追求就是——高内聚,低耦合

    • 高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;
    • 低耦合就是仅暴露少量的方法给外部使用。
  • 属性私有,使用get/set调用(实例方法,不带static)

  • 规范:

    public 返回值类型 get+属性名首字母大写(){
        return xxx;
    }
    
    public void set+属性名首字母大写(有一个参数){
        xxx=参数;
    }
    
  • 作用:

    • 提高程序的安全性,保护数据
    • 隐藏代码的实现细节
    • 统一接口
    • 增加系统的可维护性

示例代码:

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

        //new实例化一个对象
        Person person1=new Person();
        
        person1.setName("li");
        System.out.println(person1.getName());
        
        person1.setAge(20);
        System.out.println(person1.getAge());
    }
}


class Person {

    private String name; //私有数据
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void shout(){
        System.out.println("大喊大叫");
    }
}

7. 继承

  • 继承是类与类之间的一种关系。继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示。

  • 在Java中,所有的类都直接或间接继承Object

  • Java中只有==单继承==,没有多继承。(一个儿子只能有一个爸爸,但是一个爸爸可以有多个儿子)

  • 子类继承父类,可以继承父类的全部属性和方法(除了构造方法),但私有的部分在子类中不能被直接访问

  • 继承的作用:

    • 基本作用:子类继承父类,代码可以复用
    • 主要作用:有了继承关系,才有了后期的方法覆盖和多态机制
  • 什么时候使用继承?

    凡是采用 “ is a ” 能描述的,都可以继承

    猫 is a 动物

    狗 is a 动物

  • 继承示例:

    package com.bobo.base;
    
    public class Demo {
        public static void main(String[] args) {
    
            Student student=new Student();
            student.name="li";
            System.out.println(student.name); //li
            student.test();   //你好
    
        }
    }
    
    class Person {
    
        String name;
    
        public void test(){
            System.out.println("你好");
        }
    
    }
    
    class Student extends Person { //继承
    
    }
    

8. 重写

  • 为什么要重写?

    父类的功能,子类不一定需要或者不一定满足。方法覆盖需要和多态机制联合起来使用才有意义。

    因为没有多态机制,方法覆盖也可以没有,如果父类的方法无法满足子类业务需求的时候,子类完全可以定义一个全新的方法

  • 声明为static的方法不能被重写,但是可以被再次声明,且在main()方法调用时,只与调用的类有关。

    多态自然和对象有关,而静态方法的执行不需要对象。所以一般情况下,静态方法不讨论覆盖

    package com.bobo.base;
    
    public class Demo {
        public static void main(String[] args) {
    
            A a = new A();
            a.test(); //A
    
            //父类的调用指向了子类
            B b=new A();
            b.test(); //B
            B.test();  //B
    
        }
    }
    
    class A extends B{
    
        public static void test(){
            System.out.println("A");
        }
    
    }
    
    class B {
    
         public static void test(){
             System.out.println("B");
         }
    
    }
    
  • 非静态方法重写,与调用的对象有关

    package com.bobo.base;
    
    public class Demo {
        public static void main(String[] args) {
    
            A a = new A();
            a.test();
    
            B b=new A(); //子类重写了父类的方法
            b.test();
    
    
        }
    }
    
    class A extends B{
    
        @Override
        public void test() {
            System.out.println("A");
        }
    }
    
    class B {
    
         public  void test(){
             System.out.println("B");
         }
    
    }
    
  • 重要结论

    当子类对父类继承过来的方法进行”方法覆盖“后,子类对象调用该方法的时候,一定执行覆盖之后的方法

  • 重写特点

    • 需要有继承关系,子类重写父类的方法
    • 方法名必须相同,参数列表必须相同,方法体不同
    • 对于返回值类型是基本数据类型,则必须一致
    • 对于返回值类型是引用数据类型,重写之后返回值类型可以变得更小(意义不大)
    • 修饰符的范围可以扩大但不能缩小
    • 抛出异常的范围可以缩小但不能扩大
  • 注意事项

    • 重写都是对方法的重写,与属性无关
    • 私有方法无法被覆盖
    • 构造方法不能被继承,所以构造方法也不能被覆盖
    • 方法覆盖只针对于实例方法,静态方法覆盖没有意义
  • toString()方法

    • 每个类都是继承于Object方法,所以每个类中都有toString()方法,toString()方法为实例方法,需要通过对象才能调用

    • toString()方法存在的作用就是:将java对象转换成字符串形式

    • 输出格式:

    类名+"@"+哈希值,其中@符号后的值可以“等同”看作是对象在堆内存中的地址,实际上是内存地址通过“哈希算法”得出的十六进制结果

    public class Demo {
        public static void main(String[] args) {
    
            Demo d=new Demo();
            System.out.println(d.toString()); //Demo@50cbc42f
            System.out.println(d);           //Demo@50cbc42f
    
        }
    }
    

    当直接输出一个“引用“时,println()方法会自动调用”引用.toString()“,然后输出toString()方法的执行结果

    • 关于toString()方法的覆盖

      大多数的java类toString()方法都是需要覆盖的。因为Object类中提供的toString()方法输出的是一个java对象的内存地址。至于toString()方法如何覆盖,可以根据需求来定义

      public class Demo {
          public static void main(String[] args) {
             Student s =new Student("Tom",30);
              System.out.println(s);
          }
      }
      class Student{
          String name;
          int age;
      
          public Student(String name,int age){
              this.name=name;
              this.age=age;
          }
      
          public String toString(){  //toString()方法重写
              return "姓名: "+name+" 年龄:"+age;
          }
      }
      
  • System.out.println()解读

    System.out中,out后面没有小括号,说明是变量名。另外System是类名,直接使用System.out,说明out是一个静态变量。System.out返回一个对象,然后采用"对象."的方式访问println()方法


9. 多态

  • 即同一方法可以根据发送对象的不同而采取多种不同的行为方式

  • 多态是方法的多态,属性没有多态性

  • Java允许向上转型(子–>父),也允许向下转型(父—>子,需要加强制类型转换符),无论向上还是向下转型,两类型之间必须有继承关系

  • 一个对象的实际类型是确定的,但可以指向对象的引用的类型很多父类的引用指向子类但不能调用子类独有的方法,对象能执行哪些方法,主要看对象左边的类型,和右边关系不大

    public class Demo {
        public static void main(String[] args) {
    
            //一个对象的实际类型是确定的
            //new Student();
            //new Person();
    
            //可以指向的引用类型就不确定了
            Student s1 = new Student();
            Person s2 = new Student();  //向上转型
            Object s3 = new Student();
    
            s1.test(); //Student
            s2.test(); //Student 没重写之前 结果为 Person
                       //s2调用不了子类的方法 不能调用exam()
                        //s3调用不了test()和exam()方法
        }
    }
    
    class Person{
    
        public void test(){
            System.out.println("Person");
        }
    
    }
    
    class Student extends Person{
        @Override
        public void test() {
            System.out.println("Student");
        }
    
        public void exam(){
            System.out.println("exam");
        }
    }
    
    class Teacher extends Person{
        @Override
        public void test() {
            System.out.println("Teacher");
        }
    }
    
    • 分析s2.exam():

    ​ java程序分为编译阶段运行阶段

    先来分析编译阶段

    ​ 对于编译器来说,编译器只知道s2的类型是Person,所以编译器在检查语法时,会去Person.class字节码文件中找test()方法,找到了,绑定上test()方法,编译通过,静态绑定成功。(编译阶段属于静态绑定)

    再来分析运行阶段

    ​ 运行阶段的时候,实际上在堆内存中创建的java对象是Student对象,所以test的时候,真正参与test的对象是Student,所以在运行阶段会动态执行Student对象的test()方法。这个过程属于运行阶段绑定。(运行阶段绑定属于动态绑定)

    多态就是编译时的一种形态,运行时另一种形态

    • 分析s2.exam()

      s2.exam()会报错,因为编译器只知道s2的类型是Person,去Person.class文件中找exam()方法,结果没有找到,静态绑定失败,编译报错,无法运行。

    • 假设我非要调用exam()方法怎么办?

      可以使用向下转型

      Student x=(Student)s2; //向下转型
      x.exam();
      

      因为s2是Person类型,转为Student,Student和Person之间存在继承关系,所以没有报错

  • 什么时候必须使用“向下转型”?

    向下转型不能随便使用,存在一定的风险,当你需要访问的是子类对象中“特有”的方法,此时必须使用向下转型

    分析以下代码:

    Person p = new Teacher();
    Student s = (Student)p;
    s.exam();
    

    编译器检测到p这个引用是Person类型,而Person和Student之间存在继承关系,所以可以向下转型。

    运行阶段,堆内存实际创建的对象是Teacher对象,在实际运行过程中,拿Teacher对象转换成Student对象就不行了,因为Teacher和Student之间没有继承关系。运行时会出现异常java.lang.ClassCastException:类型转换异常

  • 如何避免类型转换异常的发生——instanceof运算符(运行阶段动态判断)

    • instanceof可以在运行阶段动态判断引用指向的对象的类型

    • 用法: (引用 instanceof 类型)

    • instanceof运算符的运算结果只能是:true/false

    • s是一个引用,s变量保存了内存地址指向堆中的对象

      假设(s instanceof Student)为true表示:s引用指向的堆内存中的java对象是一个Student

      假设(s instanceof Student)为false表示:s引用指向的堆内存中的java对象不是一个Student

  • 利用instanceof修改上部代码

    Person p = new Teacher();
    if(p instanceof Student){ //判断是不是Student
        Student s = (Student)p;
        s.exam();
    }
    

​ 程序员要养成一个好习惯:任何时候,任何地点,对类型进行向下转型时,一定要使用instanceof运算符进行判断,避免出现类型转换异常。

  • 多态存在的条件
    • 有继承关系
    • 子类重写父类的方法
    • 父类引用指向子类对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值