学不会面向对象的最大问题,你猜是什么?

面向对象

权限修饰符

在这里插入图片描述

  1. public: 全局可见
  2. protected:继承权限(同一个包和不同包的子类,没有关系的类是不可见的),出了子类就不能用.
  3. default:包访问权限(同一个包下可见)
  4. private:同一个类中可见

面向对象编程的三大属性

- 封装

什么是封装?

<<代码大全>> 开篇就在讨论一个问题: 软件开发的本质就是对程序复杂程度的管理. 如果一个软件代码复杂程 度太高, 那么就无法继续维护.
如何管理复杂程度? 封装就是最基本的方法. 在我们写代码的时候经常会涉及两种角色: 类的实现者和类的调用者.
封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的, 只要知道如何使用类就行了. 这样就降低了类使用者的学习和使用成本, 从而降低了复杂程度.

简单来说 封装就是使用private将属性封装,当前属性只在当前类内部可见,对外部隐藏

private实现封装

public 和 Private 是访问控制权限符

  • 被 public 修饰的成员变量或者成员方法, 可以直接被类的调用者使用.
  • 被 private 修饰的成员变量或者成员方法, 不能被类的调用者使用

简单的说:通过封装,类的调用者不需要,也不关心,类里面有哪些private成员变量,从而让调用者以更小的成本来使用类.

比如,将一辆汽车启动,汽车的内部要经过多种步骤共同配合,才能启动,但是经过封装之后,我们只需要按动开关就能将汽车启动.这就是封装的好处.

来看一段代码:

public class Student {
	//成员变量
    public String name;
    public int age;
}
public class Test {
    public static void main(String[] args) {
        Student s = new Student();
        System.out.println(s.name);
        System.out.println(s.age);
    }
}

这个时候,我们可以直接访问成员变量,但是这样做有两个问题:不安全,不方便.如果是银行卡余额也用public,那用户可以随便更改动.

所以我们就使用private修饰成员变量.这样用户就不能直接访问.
在这里插入图片描述
那用户如何访问呢?

我们可以定义相应的方法来供用户使用,去访问成员变量.

public class Student {
    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 class Test {
    public static void main(String[] args) {
        Student s = new Student();
        s.setName("王祖贤");
        s.setAge(30);
        System.out.println(s.getName());
        System.out.println(s.getAge());
    }
}

运行结果:

王祖贤
30

对于需要用户设置访问的,我们给他set()方法,get()方法,去设置,对于只要访问的,我们只给他get()方法,这样就提高了数据的安全性.

- 继承

优点: 继承可以复用代码,减少代码冗余.

继承的规则:

  • 在使用继承时,类和类之间必须满足某种关系
  • 一个子类只能继承一个父类(单继承)
  • 子类继承父类,就继承了父类的所有方法和属性,继承又分为显式继承(public属性和方法可以直接使用)和隐式继承(private属性和方法,子类其实也继承了,但是无法直接使用).
public class TaiDi extends Dog extends Animal{}
public class TaiDi extends Dog Animal{}

程序这样写,就会报错,
在这里插入图片描述
但是我们可以TaiDi继承Dog,Dog再继承Animal,TaiDi也是Animal的子类.

在继承类中是如何产生对象?

当两个类之间是继承关系时,子类new对象时会优先先产生父类的对象,然后再产生子类的对象.我们可以看下下面这道题就明白了.


/**
 * @author hh
 * date 2022/04/28 12:16
 */
public class B {
    public B(){
        System.out.println("1.B的无参构造方法");
    }
    {
        System.out.println("2.B的构造块");
    }
    static {
        System.out.println("3.B的静态块");
    }
}

// --------------------------------
public 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.mian方法开始");
        new D();
        new D();
        System.out.println("8.main方法结束");
    }
}

运行结果:
在这里插入图片描述

可以看下这到题,你就能理解它是如何运行的.

多态

一个引用可以表现出多种行为=>多态性
另一种解释:同一对象的同一引用区调用同一种方法,会表现出不同的行为.
向上转型:最大的意义是:参数统一化,降低使用者的使用难度.

Animal animal = new Dog()//父类引用指向子类

//向上转型是天然发生的,比如dog肯定是一个animal
//向上转型类和类之间必须是继承关系

//也不一定是子类,也可以是孙子类
//比如Tidi继承了dog
Animal animal =new Tidi();
Dog dog = new Tidi();//这个写法就没有意义了,因为多态最重要的是参数统一,来降低使用难度.

假如现在没有向上转型,一种行为,我们要为每个动物都要创建一个方法,成天上万的动物就要成千上万的方法,非常麻烦.

 public static void fun(Bird bird){
        bird.eat();
    }
    public static void fun(Cat cat){
        cat.eat();
    }
    public static void fun(Dog dog){
        dog.eat();
    }

    public static void main(String[] args) {
        fun(new Dog());
        fun(new Cat());
        fun(new Bird());
    }

运行结果:
在这里插入图片描述

现在有了向上转型之后,我们只需要每个动物去继承Animal类,顶端的父类引用就 可以指代所有的子类对象,向上转型就这样产生了.这样使程序非常容易扩展.

 public static void fun(Animal animal) {
        animal.eat();
    }

    public static void main(String[] args) {
        fun(new Dog());
        fun(new Cat());
        fun(new Bird());
    }

在这里插入图片描述

方法重载(overload)

什么是方法重载?

在同一个类中,定义多个方法名称相同,参数列表不同,与返回值无关的一组方法,这样的一组方法称为方法重载.

方法重写(override)

什么是方法重写?

发生在有继承关系之间的类,子类定义了和父类除了权限不同之外,名称,参数,返回值(向上转型也可以)完全相同的方法.

package myDuotai;

/**
 * @author hh
 * date 2022/04/30 15:12
 */
public class Dog extends Aiimal{
    @Override
    public void eat() {
        System.out.println("Dog中的eat方法");
    }
}
package myDuotai;

/**
 * @author hh
 * date 2022/04/30 15:12
 */
public class Aiimal {
    public void eat(){
        System.out.println("Animal中的eat方法");
    }
}

package myDuotai;

/**
 * @author hh
 * date 2022/04/30 15:16
 */
public class Test {
    public static void main(String[] args) {
        Aiimal dog =new Dog();
        dog.eat();
    }
}

重写eat方法运行结果:

Dog中的eat方法

上面的代码中Aimal中有eat方法,dog继承了Animal,重写了Animal中的方法.所以运行时就表现的是重写后的方法.

如果没有方法重写,那么运行时就是Animal中的eat方法

Animal中的eat方法

了解了继承,了解了方法重写,我们可以看下这到题:

public class B {
    public B() {
        fun();
    }

    public void fun() {
        System.out.println("B.fun");
    }
}
//--------------------------------------------
public class D extends B {
    private int num = 10;

    @Override
    public void fun() {
        System.out.println("D.fun" + "\t"+num);
    }

    public static void main(String[] args) {
        D d =new D();
    }
}
//这道题的输出是什么?
//相信很多人都会觉得是输出是
//B.fun()
//D.fun 10

答案是:

D.fun	0
new D 的对象,因为D继承了B,所以要先产生B 的对象,所以就会调用B的构造方法,
B的构造方法调用了fun()方法,但是fun() 方法在D里面重写过了,
所以执行的应该是D里面的fun()方法
但是为什么num是0而不是10? 这是因为还没有执行D的构造方法,所以D里面的num是默认值0.

多态中方法的调用及执行问题?

上面我们说了,如果子类继承了父类,并且重写了父类中的方法,那么在执行时表现的是子类重写后的方法.
那如果父类中没有这个方法,但是子类中有这个方法,结果有什么呢?

public class Dog extends Aiimal{
    @Override
    public void eat() {
        System.out.println("Dog中的eat方法");
    }

    public void play(){
        System.out.println("Dog中的play方法");
    }
}
public class Aiimal {
    public void eat(){
        System.out.println("Animal中的eat方法");
    }
}

我们在子类中添加了play方法,但是父类Animal中并没有这个方法,我们来测试一下.

public class Test {
    public static void main(String[] args) {
       Aiimal dog =new Dog();
        dog.eat();
        dog.play();//编译会报错
    }
}

执行结果:
在这里插入图片描述
这个时候我们得出了结论:

编译看左边,执行看右边

什么意思呢?

Animal dog = new Dog()

当我们使用dog去调用方法时,只能调用Animal中已经有的方法,如果没有编译就会报错,比如上面的我们用dog.play();编译就会报错.就是编译看左边.

而执行的时候,执行的是子类中重写后的方法,这就是执行看右边,

感觉这样就好记多了.

也可以这样理解:dog这个引用其实就是就是一只披着Animal皮的狗,它只能调用Animal中有的方法,虽然它是狗,但是它还是Animal类的.

但是如果我们要调用Dog类的方法,应该怎么办呢?

向下转型

如何向下转型呢?
可以类比基础类型:大类型(int)转小类型(Byte)需要强转,向下转型同样也需要强转.

  Animal animal = new Animal();
  Dog dog =(Dog) animal; //向下转型
  
  Animal dog = new Dog();
  Dog dog1 = (Dog) dog;//向下转型

向上转型

向上转型是天然发生的

  Dog dog =new Dog();
  Animal dog1 =dog;

instanceof关键字

向下转型时会有风险,比如两个毫不相关的类就不能转型
引用名称instanceof类=>返回布尔值,可以搭配分支语句来使用,判断两个类是否相关.

  Animal dog = new Dog();
  Animal cat = new Cat();
  System.out.println(dog instanceof Dog);//true
  System.out.println(cat instanceof Dog);//false

运行结果

true
false

重写方法时的注意点:

重写方法时,子类权限必须大于等于父类权限,才能重写,但是父类权限是private除外

public class Aiimal {
     protected void eat(){
        System.out.println("Animal中的eat方法");
    }
}
public class Aiimal {
     void eat(){
        System.out.println("Animal中的eat方法");
    }
}
public class Test {
    public static void main(String[] args) {
       Aiimal dog =new Dog();
        dog.eat();
    }
}

运行结果:
在这里插入图片描述
因为父类的权限为protected,而子类的权限为default,子类权限小于父类权限,执行会出错.

刚才说为什么private除外呢?

因为private是类权限,只能在这个类中可见,如果父类中的方法为private权限,那么这个方法,只能在父类中可见,子类都不知道有这个方法,那还怎么重写呢(举个例子,比如你爸的私房钱能告诉你吗,虽然你继承了你爸,但是私房钱是你爸私有的.)

能否重写静态方法

多态的本质就是:同一引用,调用了不同的子类对象,所属类的覆写后的方法,才能表现出不同的行为.
而static和对象无关.

public class Aiimal {
    public static void eat() {
        System.out.println("Animal中的eat方法");
    }
}

在这里插入图片描述

重写时方法的返回值问题

  • 如果返回的是普通类型,那必须相同
  • 如果是引用类型,向上转型可以,不能向下转型
public class Animal {
    public Dog eat() {
        Animal animal = new Animal();
        return new Dog();
    }
}

向下转型会报错,因为Animal不一定是dog.
在这里插入图片描述

向上转型的应用场景

  1. 引用赋值

  2. 方法传参

  3. 返回值类型

public class Test {
   public static void main(String[] args) {
       Animal dog = new Dog();//引用赋值
       Animal cat = new Cat();//引用赋值
   }

   //方法传参
   public void fun(Animal animal) {
       animal.eat();
   }

   //方法返回值
   public static Animal test() {
       Dog dog = new Dog();
       return dog;
   }
}

super关键字

super修饰属性

super修饰属性,表示从直接父类中寻找同名属性.如果找不到再继续向上寻找.

测试:

public class Animal   {
  public String name="Animal中的name";
}

public class Dog extends Animal {
  public String name="Dog类中的name";

  public void show(){
      System.out.println(super.name);
  }

   public static void main(String[] args) {
       Dog dog =new Dog();
       dog.show();
   }
}

打印结果:

Animal中的name

如果我们将上面的super改为this,因为this指代当前对象的引用,所以会优先在当前类(dog)中寻找同名属性,如果找不到,会向上去父类中寻找同名属性.

public class Animal   {
  public String name="Animal中的name";
}

public class Dog extends Animal {
  public String name="Dog类中的name";

  public void show(){
      System.out.println(this.name);
  }

   public static void main(String[] args) {
       Dog dog =new Dog();
       dog.show();
   }
}

打印结果:

Dog类中的name

super修饰方法

super修饰普通方法

super修饰普通方法,和修饰属性一样,从直接父类中寻找同名方法,若找不到,继续向祖类寻找.
在这里插入图片描述

super修饰构造方法
  1. 当产生对象时,会默认调用父类的无参构造方法.若父类还有父类,则再向上调用祖类.
public class Animal  {
  public Animal(){
      System.out.println("animal的无参构造");
  }
}
public class Dog extends Animal {

   public Dog(){
       super();//系统默认给出的去调用父类的无参构造方法,可以不写
       System.out.println("dog的无参构造");
   }
}
public class Tidi extends Dog{
   public Tidi(){
       super();//系统默认给出的去调用父类的无参构造方法,可以不写
       System.out.println("Tidi的无参构造方法");
   }

   public static void main(String[] args) {
       Tidi tidi = new Tidi();
   }
}

上面三段代码运行结果:
在这里插入图片描述

  1. 若父类里面创建了有参构造方法,这时候系统就默认不再给出无参构造方法,这个时候子类构造方法的首行就必须super(有参构造)来调用父类的有参构造方法.
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  2. 注意:this调用构造方法也必须放在构造方法的首行,这时候就要进行选择.

在这里插入图片描述

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

在这里插入图片描述

final关键字

修饰属性

修饰属性表示常量,值不能改变,常量

修饰类

修饰类表示类不能被继承

抽象类Abstract

之前我们讲过,多态非常依赖方法重写,但是如果子类没有重写父类方法,那么子类运行后就执行的是父类中的方法.普通父类没法要求子类重写,这个时候就需要用到抽象类.

抽象类是普通类的"超集",只是比普通方法多了一些抽象方法而已.

抽象方法所在的类必须是抽象类,普通类继承了抽象类,必须覆写抽象类中的方法.

java中使用Abstract来定义抽象类.

抽象方法

抽象方法使用Abstract来描述,只有函数声明,没有函数实现的方法称为抽象方法.

public abstract class Animal {
	//只有方法声明,没有具体的实现,具体的实现延迟到子类中实现.
	//抽象方法是没有方法体的
    public abstract void eat();
}

问题:没有方法体的方法就是抽象方法 (错误)

解释:本地方法也没有方法体,但它不是抽象方法.

在这里插入图片描述

抽象方法的注意事项

1.抽象类不能被实例化,要想使用只能创建抽象类的子类,让子类重写抽象类中的方法.
在这里插入图片描述
2.抽象方法必须在抽象类中,但是抽象类中也可以存在非抽象方法和成员变量.这个非抽象方法和普通方法一样,可以被子类调用和重写.

public abstract class Animal {
    private String name;
    private int age;

    public abstract void eat();

    public void show() {
        System.out.println("抽象类Animal中的show方法");
    }
}

public class Dog extends Animal {

    @Override
    public void eat() {
        System.out.println("狗吃狗粮");
    }

    @Override
    public void show() {
        System.out.println("Dog中的show方法");
    }
}

抽象类的作用

抽象类存在的最大意义就是为了被继承.

抽象类强制要求子类重写父类中的方法,大大降低了程序的出错率.

接口

  • 接口表示具备某种行为或者能力,子类在实现接口不像继承那样,而是实现关系.

比如接口为游泳,如果我们将游泳这种行为写在父类中,那么子类在继承父类时也继承了这个方法,显然不是所有的子类都会游泳,这种方法就不科学.
但是如果我们将游泳这种行为,定义成接口,那么子类如果具备游泳这种能力,那就让它实现这个接口即可.

  • 接口表示一种规范.

如USB接口,5G标准.

接口是抽象类的更进一步,抽象类中还可以存在非抽象方法和字段,而接口中只能存在抽象方法和静态常量.

我们来实现一个USB接口:

public interface USB {
    //接口使用intreface声明,只有全局常量(1%接口)和构造方法

    //USB接口插入
    //public , abstract 都是可以不写的
  	 public abstract void plugin();

    //USB接口工作
     void work();
     
-------------------
public class KeyBoard implements USB{
	//子类实现了usb接口,必须重写接口中的方法
    @Override
    public void plugin() {
        System.out.println("插入键盘");
    }

    @Override
    public void work() {
        System.out.println("键盘驱动安装中");
    }
}
}
//鼠标类未给出,可自行补全.
--------------------
public class Computer {
    public static void main(String[] args) {
    	//实例化对象
        Computer computer = new Computer();
        Mouse mouse = new Mouse();
        KeyBoard keyBoard = new KeyBoard();
        //实现usb接口,传入具有usb接口的对象
        computer.show(mouse);
        computer.show(keyBoard);
    }

	//如果写成这样,那么这个接口将只能插入鼠标,键盘都不可以,因为他们是两个不想关的类
//	public void show(Mouse mouse){
//	}

//一个接口可以接受无数种设备,只要这个这个设备实现了usb接口,那么它就能被识别
    public void show(USB usb) {
        usb.plugin();
        usb.work();
    }
}

运行结果:

插入鼠标
鼠标驱动安装中
插入键盘
键盘驱动安装中
  1. 在java中,继承只允许单继承,但是接口可以支持多实现,从而达到类似继承的效果.
  2. 若实现了多个接口,子类普通类需要覆写所有的抽象方法.
  3. 因为接口中只有构造方法和全局常量,所以public abstract, public static 都可以省略不写.
//    public static String name="张三";
    String name = "张三";

//        public abstract  void plugin();
    void plugin();

接口的命名规范

为了区分接口和类,我们在定义一个接口时要遵从命名规范.
如果要定义一个接口,以I开头,比如:

public interface IRun {
}
public interface ISwim {
}

要实现一个接口,以接口名开头,以impl结尾,比如:

 public void USBImpl(USB usb) {
        usb.plugin();
        usb.work();
    }
//如果子类实现多个父接口,不需要使用此命名规范.

接口的继承

  • 接口之间也存在继承,如果子类继承了多个接口,就必须覆写所有接口中的方法.

在这里插入图片描述

  • 如果一个子类,既要继承一个类,又要实现多个接口,可以先继承类,再实现多个接口.

     								<完>
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值