java面向对象(OOP)

java面向对象(OOP)

面向过程&面向对象

  • 面向过程
    • 第一步做什么, 第二步做什么
    • 微观操作
  • 面向对象
    • 分类的思维
    • 适合处理复杂的, 多人协作的问题

对于描述复杂的事物, 为了从宏观上把握, 从整体上合理分析, 需要面向对象分析整个系统

但是, 具体到微观操作, 仍然需要面向过程

面向对象

  • Object-Oriented Programming, OOP

  • 本质: 以类的方式组织代码, 以对象的方式封装数据

  • 特性

    • 封装

    • 继承

    • 多态

封装

  • 高内聚: 隐藏对象的属性和实现细节,仅对外公开接口
  • 属性私有, 通过get和set来获取
  • 在get和set方法内部, 可以进行一些合法性检查(比如, 年龄不能是999岁), 规避掉直接修改的风险

继承

修饰符

  1. extends

    1. 在Java中,没有明确写extends的类,编译器会自动加上extends Object
    2. Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,它没有父类
  2. protected

    1. 子类无法访问父类的private字段或者private方法
    2. protected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问
    3. 四个优先级: public, protected, default, private
  3. final

    1. 一般情况下,只要某个class没有final修饰符,那么任何类都可以从该class继承。

构造方法

  1. 传入的参数, 直接初始化

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
  2. super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName

    class Person {
    	//属性
        private String name;
        private int age;
    	//初始化
        public Person(String name, int age) {
        	super();//调用父类构造方法
            this.name = name;
            this.age = age;
        }
        //方法
        public String getName() {
            return this.name;
        }
    
        public int getAge() {
            return this.age;
        }
    }
    
  3. 构造顺序

    public class Person{
        public Person{
            System.out.println("父类构造");
        }
    }
    
    public class Student extends Person{
        public Student{
            System.out.println("子类构造");
        }
    }
    
    public class Test{
        public static void main(String[] args){
        	Student student = new Student();
    	}
    }
    
    /*输出
    	父类构造
    	子类构造
    */
    

    父类先构造完毕, 然后构造子类

向上转型与向下

  1. 向上转型

    1. 父类的引用指向子类

      引用类型是把数据和功能组织在一起的结构,也称对象定义,它描述了自己的对象应有的属性和方法。

      萌新的简单理解: 栈里面存的是Person类型的引用, 但其指向的堆里面存的是Student类型

      Person p = new Student();
      
    2. 方法被重写*(或者说方法被覆盖)*

      class A {
          public void print() {
              System.out.println("A:print");
          }
      }
      
      class B extends A {
          public void print() {
              System.out.println("B:print");
          }
      }
      
      public class Hello {
          public static void main(String args[])
          {
              A a = new B();
              a.print();
          }
          
          //B:print
      

      向上转型丢失子类的方法, 但是子类override父类方法之后, 子类方法是有效的

      本来子类的方法应该丢失, 但由于父类也有这个方法, 所以这个子类的方法覆盖了父类的方法

      父类有的, 就会被覆盖.

      父类没有的, 就会丢失.

      静态方法不会被重写, 因此不会被覆盖.

      class A {
          public static void print() {
              System.out.println("A");
          }
      }
      
      class B extends A {
          public static void print() {
              System.out.println("B");
          }
      }
      
      public class Test {
          public static void main(String args[])
          {
              A a = new B();
              a.print();
          }
          
          //A
      

      方法可重写,属性不可重写。父类的方法被子类覆盖,父类的属性不被子类覆盖。

      public class Son extends Father {
      	public String name = "子类属性";
       
      	public void show() {
      		System.out.println("子类方法");
      	}
      	
      	public static void main(String[] args) {
      		Father son = new Son();
      		son.show();
      		System.out.println(son.name);
      	}
      }
       
      class Father {
      	public String name = "父类属性";
       
      	public void show() {
      		System.out.println("父类方法");
      	}
      }
          
          /*输出:
          	子类方法
          	父类属性
          */
      
    3. 作用

      减少重复代码,传入参数

      public class Test {
          public static void main(String[] args) {
              A(new Daughter());//可视为Father daughter = new Daughter();
              A(new Son());//可视为Father son = new Son();
          }
          public static void A(Father children){
              children.eat();
          }
      }
      
  2. 向下转型

    1. 父类强制转换为子类, 从而调用子类的独有的方法(在工程中很少用)

    2. instanceof: 判断某对象是否是某类的实例

      A a = new B();                 //向上转型 (B类是A的子类)
      
      a instanceof A;                //返回true.
      a instanceof B;                //返回true
      a instanceof C;                //返回false
      
    3. 向上转型传入参数, instanceof分辨是哪个子类

      class A {
               public void print() {
                        System.out.println("A:print");
               }
      }
      
      class B extends A {
               public void print() {        
                        System.out.println("B:print");
               }
               public void funcB(){
                        System.out.println("funcB");
               }
      }
      
      class C extends A {
               public void print() {        
                        System.out.println("C:print");
               }
               public void funcC(){
                        System.out.println("funcC");
               }
      }
      
      public class Test{
               public static void func(A a)
               {
                        a.print();
                        if(a instanceof B)
                        {
                                B b = (B)a;   //向下转型,通过父类实例化子类
                                b.funcB();    //调用B类独有的方法
                        }
                        else if(a instanceof C)
                        {
                                C c = (C)a;  //向下转型,通过父类实例化子类
                                c.funcC();   //调用C类独有的方法
                        }
               }
      
               public static void main(String args[])
               {
                        func(new A());   
                        func(new B());
                        func(new C());
               }
      }
      
  3. 重写override和重载overload

    重写是子类父类才有的: 子类重写父类的方法(针对于方法, 属性不能被重写. 因此父类的属性不会被覆盖)

    1. 区别: 参数列表必须相同, 不然变成重载了

    2. 修饰符: 范围可能扩大, 但不能缩小 public > protected > default > private

    3. 抛出的异常: 范围可以缩小, 但不能变大 ClassNotFoundException --> Exception(大)

    4. **重写override是覆盖的意思, 即覆盖之前的方法. 之前的方法不能使用了, 被覆盖掉了. **

      而重载overload是方法的复用. 重载的方法, 和之前的方法都能使用.

多态

  • 多态基于override

  • 允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。

  • 功能拓展基于重写override

    不知道实际传入的参数类型是Person, 还是Person的某个子类

    public void runTwice(Person p) {
        p.run();
    }
    
  • 父类利用多态派生子类, 覆写相应的方法

    多态使用例子, 详见下文抽象类.

其他

  1. static

    1. 静态的属性和方法, 可以通过类调用.

      类加载过程中, 将静态变量, 静态方法, 常量存入方法区

    2. 匿名代码块和静态代码块

      public class Person{
          {
              System.out.println("匿名代码块");
          }
          static{
              System.out.println("静态代码块");
          }
          public Person(){
              System.out.println("构造方法");
          }
          public static void main(String[] args){
              Person person1 = new Person();
              System.out.println("=============");
              Person person2 = new Person();
          }
      }
      /*输出
      	静态代码块
      	匿名代码块
      	构造方法
      	=============
      	匿名代码块
      	构造方法
      */
      

      静态代码块只执行一次.

      匿名代码块每次执行: 用于赋初始值

  2. import

    1. 为了让同名的方法, 变量可以区分, 把它放入不同的package中

      使用这些方法, 变量的时候, 需要输入全名: 包名.方法名

      但是, import导入包里面的类, 可以不用输入包名, 直接输入对应的方法名即可. 这样做, 不用再写很长的名称, 很方便.

    2. 两种导入方式

      导入子包内某个类: import java.util.ArrayList;

      导入子包内所有类: import java.util.*;(按需导入声明)

      一般大型项目不用按需导入

      因为: 编译慢, 命名冲突, 可读性差, 无名包问题

    3. import static 静态导入

      import导入类和接口, 但是对于类里面的变量和方法只能静态导入. (因为其必须挂靠于类或接口)

      //直接导入具体的静态变量、常量、方法方法,注意导入方法直接写方法名不需要括号。
      import static java.lang.Math.random;//方法
      import static java.lang.Math.PI;//属性
      import static java.lang.Math.*;//按需导入
      

抽象类与接口

抽象类

首先我们需要了解什么是抽象.

抽象, abstract. 英文有简化的意思. 即抽象, 就是把很多信息压缩简化.

例如, 一个学生类, 有如下属性: 学号, 姓名, 性别, 出生日期.

但是, 除了这些, 必定会有高矮胖瘦美丑等外貌的属性, 父母健在或双亡等家庭的属性.

因为我们不需要这些信息, 所以我们没有写入学生类.

我们把很多信息去掉, 简化, abstract的过程, 就是抽象.

什么是抽象类?

抽象类即类的抽象.

比如狗有很多种, 比如牧羊犬, 柴犬, 拉布拉多, 博美, 等.

牧羊犬等这些狗的细分种类, 都会抽象为"狗"类. 这些类都会继承自"狗"类. 即他们的固有的共性就是"狗"类.

这个"狗"类, 即为抽象类.

  • 特点

    • abstract修饰

    • 含有抽象方法, 但也可以含有普通的成员方法, 成员变量.

      abstract void fun();
      

      抽象方法: 只有声明, 没有具体的实现

      抽象方法必须为public或protected. 缺省时, 默认为public

    • 抽象类不能被实例化。

    • 抽象类里面的抽象方法必须全部被子类实现,如果子类不能全部实现,那么子类必须也是抽象类。

  • 使用

    [public] abstract class ClassName {
        abstract void fun();
    }
    
    • 抽象类主要用于继承

      被继承之后, 子类会重写(override)抽象类中的抽象方法, 从而可根据子类的不同需求而进行实现.

      比如:

      抽象类 飞行物类 有抽象方法abstract void fly();. 飞机类鸟类 可以继承飞行物类.

      飞机类鸟类 有不同的飞行方式, 即可重写fly(), 按照各自的飞行方式分别进行实现.

接口

什么是接口?

抽象类由于继承而存在, 把所有东西传承给子类.

而接口为了附加而存在, 为了把某东西附加给某个类.

一个是全盘接受, 一个是附加.

例如门都会开和关. 这是门本身固有的属性. (抽象类)

但是有些门可以报警, 有些门可以密码解锁, (接口)

我们可以给某些门加上这些接口.

但不是所有的门都一定要有报警系统, 密码解锁. 只是一种附加行为.

接口是规范,是一个大家都遵守的规则。

如同法律一样,所有人都遵守,就能在一起富强*文明和谐。

  • 特点

    • interface修饰

    • 接口中可以含有 变量和方法。(但一般不在接口中定义变量)

    • 接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误)

      而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误)

    • 接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。

      接口里面的方法也必须全部被子类实现,如果子类不能实现那么子类必须是抽象类。

    • 一个类可以实现多个接口

  • 使用

    [public] interface InterfaceName {
    	
    }
    
    • 实现一个接口需要用到关键字 implements,它表示:“我这个类遵从了接口的协议,如果你想使用我,看接口就行了,具体实现不用关心。”

      class ClassName implements Interface1,Interface2,[....]{
          
      }
      
    • 和抽象类的向上转型一样, 实现接口的类也可以向上转型为接口.

      interface A {
      	void func();
      }
      
      class B implements A {
      	@Override
      	public void func() {
      		System.out.println("func");
      	}
      }
      
      public class Test {
      	public static void func(A a) {
      		a.func();
      	}
      	
      	public static void main(String[] args) {
      		B b = new B();
      		func(b);
      	}
      }
      
    • 接口的适配器模式

      • 适配器模式——针对调用者的需求对原有的接口进行转接。

        如果类直接实现该接口的话,就需要对两个方法进行实现。

        如果我们只需要对其中一个方法进行实现的话,就可以使用一个抽象类作为中间件,即适配器(AdapterCoach)。

        用这个抽象类实现接口,并对抽象类中的方法置空(方法体只有一对花括号,因为该方法为非抽象方法)。

        这时候,新类就可以绕过接口,继承抽象类,我们就可以只对需要的方法进行覆盖,而不是接口中的所有方法。

区别

  • 禁止多继承

    继承多个父类时, 这些父类有同名的不同方法, 会造成二义性. (还有其他原因, 此处简单理解即可)

    java禁止多继承, 但引入了interface(接口). 而接口可以实现多个,弥补了不能多继承的缺陷

  • 固有与附加 (先天与后天)

    抽象类是子类的整体抽象, 一种模板式设计. 是先天的, 本身固有的属性.

    接口是延伸的附加行为, 是后天的, 附加的属性.

    接口用途广泛. 如报警装置, 可以使用在门, 室内, 手机, 车…

  • 修改

    抽象类可以直接修改该类, 不需要改动子类.

    接口更新的话, 需要对每个实现该接口的类进行改动.

  • 设计

    接口的设计目的,是对类的行为进行约束(更准确的说是一种“有”约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。对“接口为何是约束”的理解,我觉得配合泛型食用效果更佳。

    而抽象类的设计目的,是代码复用。当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。在这个抽象类中实现了B,避免让所有的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。正是因为A-B在这里没有实现,所以抽象类不允许实例化出来(否则当调用到A-B时,无法执行)。

  • 语法层面

    1. 抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;

    2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;

    3. 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;

    4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

参考资料:

java向上转型, 向下转型

什么是泛型

import

深入理解Java的接口和抽象类

java抽象类和接口, 看这一篇就够了

接口和抽象类有什么区别

抽象类和接口的区别

上一篇:Equality in ADT and OOP


下一篇:ADT and OOP 复习总结(一)