面向对象-封装继承多态

发布时间:2024年01月01日

内容大部分来源于学习笔记,随手记录笔记内容以及个人笔记

面向对象

  • 面向过程:根据解决问题的过程,直接设计系统。如C语言

    举个”栗子“

    饭店给顾客做一道鱼香肉丝

    1. 顾客进入饭店

    2. 服务员招待,并询问吃什么

    3. 服务员记录相关信息,给后厨传递订单

    4. 后厨接收信息,并准备材料

    5. 厨师做出鱼香肉丝,并告知服务员

    6. 服务员取餐并给客户

    7. 客户吃饭

    8. 客户离开

    9. 服务员打扫“战场”

  • 面向对象:将问题分解成多个对象,设计模块化、低耦合的系统。如java语言

    • 特性:封装、继承、多台

    • 优点:开发的系统更灵活,易维护、复用、扩展

      还是上面的“栗子”

      1. 分析对象有哪些,有厨师、服务员、顾客、菜品饭店等

      2. 厨师有哪些功能,如接收服务员的订单信息,做饭,通知服务员餐好了

      3. 服务员的功能,接待顾客,传递订单,送饭

      4. 顾客的功能,进入饭店,点餐,吃饭,结账

      5. 按照系统进行不同的对象的行为的组合

封装

可以这样理解,我有一个宝宝,他会拉屎。每次拉屎的时候,我都需要给他拖裤子,等他拉完,给他擦屁股。

我觉得这个过程可以优化,我给他的裤子进行了优化,做成了开裆裤。这样我就不用宝宝每次拉屎的时候都脱裤子穿裤子了。

而对穿裤子脱裤子这个过程就是封装,封装好的裤子还可以给其他宝宝用,体现了复用性。

专业点就是,对一段代码把重复使用的代码进行提取优化,封装成一个方法,在用的时候进行调用。

普通内部类

定义在类中的类,可以使用外部类所有属性和方法。普通内部类属于具体对象,因此不能声明 static 成员变量和方法。

成员内部类依附外部类而存在。也就是说,如果要创建普通内部类的对象,就必须首先存在外部类的对象。

public class Test {
 ? ?public static void main(String[] args)  {
 ? ? ? ?// 创建内部类
 ? ? ? ?Outter outter = new Outter();
 ? ? ? ?Outter.Inner inner = outter.new Inner(); ?
 ? ? ? ?inner.output();
 ?  }
}
?
// 外部类 
class Outter {
 ? ?private int num = "10";
 ? ?// 内部类 ?
 ? ?class Inner {
 ? ? ? ?void output(){
 ? ? ? ? ? ?System.out.println(num);
 ? ? ?  }
 ?  }
}

局部内部类

定义在一个方法或者一个作用域里的内部类。对局部内部类的访问仅限于方法内或者该作用域内,且局部内部类不能被访问权限所修饰。

public class Test {
 ? ?public static void main(String[] args)  {
 ? ? ? ?// 创建内部类
 ? ? ? ?Factory f = new Factory();
 ? ? ? ?Gun myrifle = f.getRifle(); ?
 ?  }
}
?
class Factory {
 ? ?// 局部内部类
 ? ?public Gun getRifle(){
 ? ? ? ?class Rifle extends Gun { ? 
 ? ? ? ? ? ?int len = 60;
 ? ? ?  }
 ? ? ? ?return new Rifle();
 ?  }
}

匿名内部类

没有名字的内部类,就是省了命名的步骤,好处是节省命名空间,坏处是只使用一次,范围有限。

匿名内部类不用定义名称,但必须继承一个父类或实现一个接口。由于没有类名,匿名内部类不能定义构造器。在创建匿名内部类的时候会立即创建它的实例。因此匿名内部类只能使用一次,通常用来简化代码编写。

最常用的情况就是在多线程的实现上,创建线程类传入参数需要继承 Thread 类或实现 Runnable 接口。

// 父类或接口
interface Person {
 ? ?public void eat();
}
?
public class Demo {
 ? ?public static void main(String[] args) {
 ? ? ? ?Person p = new Person() { 
 ? ? ? ? ? ?// 定义匿名内部类
 ? ? ? ? ? ?public void eat() {
 ? ? ? ? ? ? ? ?System.out.println("eat apple");
 ? ? ? ? ?  }
 ? ? ?  };
 ? ? ? ?//调用
 ? ? ? ?p.eat();
 ?  }
}

JDK 1.8 中引入了 Lambda 表达式,你甚至连方法名都不需要写。

public class Demo {
 ? ?public static void main(String[] args) {
 ? ? ? ?Person p = new Person(() -> {
 ? ? ? ? ? ?System.out.println("eat apple");
 ? ? ?  });
 ? ? ? ?p.eat();
 ?  }
}

这个Lambda表达式感觉用到的比较多,后续还要多了解点

局部内部类和匿名内部类都定义在方法中(共同点),如果调用方法中的其他局部变量,只能调用外部类的局部 final 变量。因为在多线程中,外部类方法中定义的变量 A 在方法执行完毕后生命周期就结束了,而此时 Thread 对象的生命周期很可能还没有结束。内部类方法中访问的变量 A 实际上是拷贝。这就必须限定变量为 final,否则改动将导致数据不一致

public class Test {
 ? ?public void test(final int b) {
 ? ? ? ?final int a = 10;
 ? ? ? ?new Thread(){
 ? ? ? ? ? ?public void run() {
 ? ? ? ? ? ? ? ?System.out.println(a);
 ? ? ? ? ? ? ? ?System.out.println(b);
 ? ? ? ? ?  };
 ? ? ?  }.start();
 ?  }
}

可以这样理解,你有一个类A,需要调用类A中的name属性,那么A的生命周期就从new类A的对象开始,最后坑定是要结束的。

为什么会要有生命周期这个概念呢,是因为电脑的性能是有限的,而程序原则上来说就是类调类,方法调方法这种形式,而你调用就会创建对象,就会占用电脑资源,次数少的,体量小的程序无所谓,但正常的程序其实都不小。

第一次调用类A,占用了资源,占用了一个类名(大部分是系统起的),不去结束这个类A,那你下次使用类A的时候就又要占用一次资源,占用一个类名。第一次的类A它的任务完成了,还占用着资源。现在的电脑执行效率很快,可能一下子类A就被调用了上千次,慢慢的电脑就崩了,所以会有生命周期的概念,来释放资源

静态内部类

静态内部类是不需要依赖于外部类,可以在不创建外部类对象的情况下创建内部类的对象。静态内部类不能使用外部类的非 static 成员变量或者方法。

简单说还是static的特性,可以通过类名调用。

如下:要使用Inner,就调Outter类,通过这个类调Inner

普通的内部类还是通过对象来,如先声明一个Outter类的对象,用这个对象再声明内部类Inner(假设没有static修饰)的对象

public class Test {
 ? ?public static void main(String[] args)  {
 ? ? ? ?// 无需外部对象,直接创建内部类
 ? ? ? ?Outter.Inner inner = new Outter.Inner();
 ?  }
}
?
class Outter {
 ? ?static class Inner {
 ? ? ? ?int data = 0;
 ?  }
}

继承

类的继承

java中的继承是单继承,就是只能有一个父类(你只能有一个亲爹),子类会拥有父类所有的属性和方法(不包含private修饰的私有的),也可以对继承的方法进行重写(这就成多态了)

  • 父类的属性值不会被子类继承,但子类可以通过父类提供的方法得到父类的属性值。

  • 父类的 static 方法不会被子类继承,子类的 static 方法会隐藏父类的同名 static 方法。

  • 父类的构造方法不会被子类继承,子类必须在构造方法首行调用父类构造方法(先构造父类,再构造子类)

final public class Trunk extends Car{ 
 ? ?// 重定义属性(未公开无法继承)
 ? ?String brand;
 ? ?String description = "this is a trunk";
 ? ?// 扩展属性
 ? ?int goods;
 ? ?// 扩展方法 ? ? ? ? ? ? ?
 ? ?public void load(int num){
 ? ? ? ?this.goods += num;
 ?  }
 ? ?// 子类构造方法
 ? ?public Trunk(String brand){ ? ?
 ? ? ? ?super(brand); ? ? ? ?
 ? ? ? ?this.goods = 0; ? ? ? ? ? ? ? ? ? ? ? ? ?
 ?  }
 ? ?// 重写方法
 ? ?@Override ? ? ? ? 
 ? ?public void go(String loc){
 ? ? ? ?super.go(loc); ? ? ? ? ? ? ? ? ? ? ? ? ? ?
 ? ? ? ?System.out.print(" with" + goods + "goods"); 
 ? ? ? ?this.goods = 0;
 ?  }
}

Object 类是一切 java 类的父类。对于普通的 java 类,即便不声明也默认继承了 Object 类。

接口继承

和类继承相似,但可以多实现,接口继承接口

interface Charge extends Move, Fight{ ?
 ? ?public abstract void kill(int num);
}

多态

重载

定义多种同名的方法,调用时根据传入参数判定调用哪种方法

static class A{
    public void aa(String a){}
    public void aa(int a){}
    public void aa(String a,int b){}
}

-----------------------------------
    A.aa("a");//调用的第一种
	A.aa(1);//调用的第二种
	A.aa("aa",1);//调用的第三种
  • 被重载的方法必须改变参数列表(参数个数或类型不一样);

  • 被重载的方法可以改变返回类型;

  • 被重载的方法可以改变访问修饰符;

  • 被重载的方法可以声明新的或更广的检查异常;

  • 方法能够在同一个类中或者在一个子类中被重载。

  • 无法以返回值类型作为重载函数的区分标准。

每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。

最常用的地方就是构造器的重载。

重写

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!

可以理解为吧父类的能复制的都复制到自己身上了

重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。(就是访问权限修饰符不能比父类的大或者说是广泛)

重写是多态的前提,其允许父类引用指向子类对象(引用类型为父类,指向的实际对象类型为子类)。

class Animal{
   public void move(){
      System.out.println("动物可以移动");
   }
}
 
class Dog extends Animal{
   public void move(){
      System.out.println("狗可以跑和走");
   }
}
 
public class TestDog{
   public static void main(String args[]){
      Animal a = new Animal(); // Animal 对象
      Animal b = new Dog(); // Dog 对象
 
      a.move();// 执行 Animal 类的方法
 
      b.move();//执行 Dog 类的方法
   }
}

重写规则
  • 参数列表与被重写方法的参数列表必须完全相同。

  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。

  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。

  • 父类的成员方法只能被它的子类重写。

  • 声明为 final 的方法不能被重写。

  • 声明为 static 的方法不能被重写,但是能够被再次声明。

  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。

  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。

  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。

  • 构造方法不能被重写。

  • 如果不能继承一个类,则不能重写该类的方法。

Super 关键字的使用

当需要在子类中调用父类的被重写方法时,要使用 super 关键字。

class Animal{
   public void move(){
      System.out.println("动物可以移动");
   }
}
 
class Dog extends Animal{
   public void move(){
      super.move(); // 应用super类的方法(可以理解为父类.move)
      System.out.println("狗可以跑和走");
   }
}
 
public class TestDog{
   public static void main(String args[]){
 
      Animal b = new Dog(); // Dog 对象
      b.move(); //执行 Dog类的方法
 
   }
}

文章来源:https://blog.csdn.net/missmoonlight/article/details/135314677
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。