手机版
您的位置: 首页 > 生活常识 >

面向对象的理解(一文读懂面向对象(OOP))

100次浏览     发布时间:2024-08-08 13:46:11    


面向过程和面向对象

概述

面向过程与面向对象都是解决问题的思维方式

面向过程:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用的时候一一调用则可。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发。

面向对象:是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事物在解决整个问题的过程中所发生的行为

面向对象有封装、继承、多态的特性,所以易维护、易复用、易扩展。可以设计出低耦合的系统。但是性能上来说,比面向过程要低。

开车与造车

面向过程思想思考问题时,我们首先思考“怎么按步骤实现?”并将步骤对应成方法,一步一步,最终完成。 这个适合简单任务,不需要过多协作的情况下。比如,如何开车?我们很容易就列出实现步骤:

而如何造车,我们不能通过列举简单的12345个步骤去完成,因为造车太复杂,需要很多协作才能完成。此时面向对象思想就应运而生了;

面向对象的思想就是我们怎么设计这个事物,以帮助我们从宏观上把握、从整体上分析整个系统。

比如思考造车,我们就会先思考“车怎么设计?”,而不是“怎么按步骤造车的问题”。这就是思维方式的转变。然后我们就会想到车由哪些部分组成,比如 发动机、轮胎、底盘等等部分,每个部分我们可以理解为各个对象,每个对象可以由不同厂商或者部门生产,最终进行组装,大大提高了效率。但是,具体到轮胎厂的一个流水线操作,仍然是有步骤的,还是离不开执行者、离不开面向过程思维!

所以我们千万不要把面向过程和面向对象对立起来。他们是相辅相成的。面向对象离不开面向过程

类与对象

概述

在我们的世界里,其实就是面向对象的。比如让大家认识下什么是外星人,相信大家都没有见过,但是如果我现在大家面前放几个模型,让大家看,看完以后,大家下次见了后就认识了吧。

因为看完后我们会总结外星人有哪些特征:

  • 有翅膀
  • 会魔法
  • 面向丑陋
  • . . . . . .

总结认知抽象,便于认识未知的事物, 这个总结过程就是抽象过程 , 我们进行抽象,抽象出了外星人的特征,我们也可以归纳一个外星人类。 通过这个过程,类就是对象的抽象

类可以看做是一个模版,或者图纸,系统根据类的定义来造出对象。我们要造一个汽车,怎么样造?类就是这个图纸,规定了汽车的详细信息,然后根据图纸将汽车造出来。

类:我们叫做class。 对象:我们叫做Object,instance(实例)。以后我们说某个类的对象,某个类的实例。是一样的意思。

LOL中的类和对象

英雄就是类,盖伦和提莫就是对象


总结

  • 类可以看成一类对象的模板,对象可以看成该类的具体实例;
  • 类是用于描述同一类型对象的一个抽象概念,类定义了这一类型对象共同属性和方法

变量

概述

类中定义的变量是成员变量,类变量是static声明的成员变量而方法中定义的变量,包括方法的参数,代码块中定义的变量被称为局部变量。三个的区别主要表现在以下几方面


类变量

成员变量

局部变量

代码中位置不同

类中定义的变量,通过static修饰

类中定义的变量

方法中定义的变量,包括方法的参数,代码块中定义的变量

内存中位置不同

静态变量数据存储在方法区(共享数据区)的静态区,所以也叫对象的共享数据

堆内存

栈内存

代码作用范围不同

静态变量可以被对象调用,还可以被类名调用

当前类的方法,当前类都能访问

当前的一个代码块或者方法

作用时间不同

随着类的加载而存在,随着类的销毁而销毁

当前对象从创建到销毁

定义变量到所属方法或者代码块执行完毕

是否有默认初始值

成员变量初始值

  • 基本数据类型默认值都是0 boolean默认是false;
  • 引用数据类型默认值为null,null表示空 ,什么都没有(占位)。

this关键字

概述

  • this 关键字有两个含义,一是指向当前对象(隐式参数)的引用,二是调用该类的其他构造器。
  • this可以调用本类对象所有的方法与属性,用来区分成员变量与局部变量的重名问题。
  • this();指向本类的构造方法,只能写在构造方法中并且必须是第一句。
  • this不能用于static方法中。

代码演示

package com.xl.oop.thisdemo;
import lombok.Data;
/**
 * @author 公众号:编程识堂
 * @date 2023/4/14 0014 9:55
 * @description
 */
@Data
public class ThisDemoTest {

    private String name;

    private String phone;

    private Integer age;

    private String remark;

    /**
     * 构造方法
     *
     * @param name
     * @param phone
     * @param age
     * @param remark
     */
    public ThisDemoTest(String name, String phone, Integer age, String remark) {
        //this可以调用本类对象所有的方法与属性
        this.name = name;
        this.phone = phone;
        this.age = age;
        this.remark = remark;
    }

    @Override
    public String toString() {
        return "ThisDemoTest{" +
                "name='" + this.name + '\'' +
                ", phone='" + this.phone + '\'' +
                ", age=" + this.age +
                ", remark='" + this.remark + '\'' +
                '}';
    }

    /**
     * 构造方法也可以重载
     *
     * @param name
     * @param phone
     * @param age
     */
    public ThisDemoTest(String name, String phone, Integer age) {
        //调用该类的其他构造器
        this(name, phone, age, "");
        // this(name, phone, age, "");//all to 'this()' must be first statement in constructor body  只能写在构造方法中并且必须是第一句
    }

    public void test1(ThisDemoTest thisDemo) {
        String name = "疯狂的小堂";
        // 用来区分成员变量与局部变量的重名问题
        thisDemo.setName(this.name);//小堂
        thisDemo.setName(name);//疯狂的小堂
        System.out.println(thisDemo.toString());

        String nickName = "编程识堂";
        //this不能作用于局部变量
        // System.out.println(this.nickName);Cannot resolve symbol 'nickName'
    }

    public static void main(String[] args) {
        ThisDemoTest thisDemo = new ThisDemoTest("小堂", "17000000001", 18);
        thisDemo.test1(thisDemo);
    }

}

static关键字

概述

static字面意思为静态的,可用于方法、属性、代码块, 一般放在修饰符的后面。

静态内容在内存中是保留一份儿,并且各个对象之间共享。

特点

  • static可以修饰变量、方法、代码块
  • 被static修饰的属性称为静态变量(类变量);被static修饰的方法称为静态方法(类方法)。
  • 静态属性声明必须在方法、构造方法和语句块之外
  • 静态变量可以在声明时指定,构造方法中指定,静态语句块中初始化。
  • 静态变量在程序开始时创建,在程序结束时销毁。
  • 静态变量属于这个类所有,不属于对象,由这个类创建的所有对象共用同一个static变量
  • 静态变量和静态方法的访问可以使用:

类名.属性

类名.方法()

  • 静态方法中只能访问静态变量和静态方法
  • 非静态方法中可以访问静态和非静态变量、静态和非静态方法。
  • 类中初始化执行顺序:静态变量、静态语句块->变量、语句块->构造方法

代码演示

静态变量属于这个类所有

package com.xl.oop.staticdemo;

import lombok.Data;
import lombok.ToString;

/**
 * @author 公众号:编程识堂
 * @date 2023/4/14 0014 13:59
 * @description
 */
@Data
public class Person {

    private String name;

    private String remark;

    /**
     * 类变量,各个对象共享
     */
    private static String  country;

    public Person(String name, String remark) {
        this.name = name;
        this.remark = remark;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", remark='" + remark + '\'' +
                ", country='" + country + '\'' +
                '}';
    }

    public static void main(String[] args) {
        Person p1 = new Person("小堂","编程识堂博主");
        Person p2 = new Person("花花","爱打扮的小女孩儿");
        Person.country = "中国";
        System.out.println(p1.toString());
        System.out.println(p2.toString());
    }

}

类中初始化执行顺序

package com.xl.oop.staticdemo;

/**
 * 类中初始化执行顺序演示
 * @author 公众号:编程识堂
 * @date 2023/4/14 0014 14:07
 * @description
 */
public class ExecRank {

    private String name;

    private static  String country="中国";

    private void test(){
        System.out.println("普通方法开始执行··,country="+country);
        this.name = "小堂";
        country = "日本";
    }

    static {
        System.out.println("静态语句块开始执行·····");
    }

    {
        System.out.println("普通语句块开始执行·····country="+country);
    }

    public ExecRank() {
        System.out.println("构造方法开始执行。。。。。。。");
    }

    private static void  test2(){
        System.out.println("静态方法开始执行。。。。。country="+country);
    }

    public static void main(String[] args) {
        ExecRank execRank = new ExecRank();
        execRank.test();
        ExecRank.test2();
    }
}

继承

概述

继承可以解决代码的复用性问题,让编程更解决我们人类的思维逻辑,多个类出现相同的属性/方法时,可以将这些属性/方法抽象出来,放在一个父类中来定义,所有的子类都不需要再定义这些属性/方法,只需要继承(extends)父类即可。

  • 父类也被称为超类、基类;子类也被称为派生类、孩子类
  • 子类会自动拥有父类定义的属性和方法一般情况下子类比父类拥有的功能会更多,如果在子类中定义了与父类签名相同的方法,那么这个方法就会覆盖父类中拥有相同签名的方法。
  • 子类方法不能低于父类方法的访问权限
  • Object类:所有类的父类,Java中每个类都扩展了Object类,包括基本数据类型,因为基本数据类型都有一个与之对应的类(并且除了Boolean其它六个类还有一个公共的父类Number)称为包装类,自然Object类型的变量可以引用所有类的对象 ,当然,Object类引用子类对象只是作为一个泛型容器,要想对子类对象进行相应的操作还是要用强制类型转换来进行

代码演示

package com.xl.oop.extendsdemo;

import lombok.Data;
import lombok.ToString;

/**
 * 父类
 *
 * @author 公众号:编程识堂
 * @date 2023/4/14 0014 15:55
 * @description
 */
@Data
@ToString
public class Animal {

    public String name;

    private Integer age;

    void eat() {
        System.out.println("开始吃东西了。。。");
    }

    public Animal() {
        System.out.println("Animal 构造器执行。。。");
    }
}
package com.xl.oop.extendsdemo;

import lombok.Data;
import lombok.ToString;

/**
 * 子类 猫
 *
 * @author 公众号:编程识堂
 * @date 2023/4/14 0014 15:56
 * @description
 */
@Data
@ToString
public class Cat extends Animal {

    private Boolean mews;

    /**
     * 抓老鼠
     */
    private void catchMouse() {
        System.out.println("猫抓老鼠。。。。。");
    }

    /**
     * 类方法不能低于父类方法的访问权限
     */
    @Override
    public void eat() {
        super.eat();
    }

    public Cat() {
        System.out.println("Cat 构造器执行。。。。");
        this.mews = true;//子类私有属性
        super.name = "小花猫";////访问从父类中继承过来的属性
        // super.age = 10;//编译报错,age是父类中的私有属性,子类中不能访问

    }
}
package com.xl.oop.extendsdemo;

import lombok.Data;
import lombok.ToString;

/**
 * 子类 鸟
 *
 * @author 公众号:编程识堂
 * @date 2023/4/14 0014 16:01
 * @description
 */
@Data
@ToString
public class Birder extends Animal {

    //颜色
    private String colour;

    private void flight() {
        System.out.println("鸟会飞。。。。");
    }

    @Override
    void eat() {
        super.eat();
    }

    public Birder() {
        System.out.println("Birder 构造器执行。。。");
    }
}
package com.xl.oop.extendsdemo;

/**
 * @author 公众号:编程识堂
 * @date 2023/4/14 0014 16:08
 * @description
 */
public class ExtendsDemoTest {

    public static void main(String[] args) {
        Animal a1 = new Cat();
        /**
         * 子类的对象可以作为父类的对象(引用时是对父类方法的引用,但是传入的对象是子类的对象,
         * 即用子类的对象来对父类进行实例化),但是反过来不行
         */
        //Cat a2 = (Cat) new Animal();// com.xl.oop.extendsdemo.Animal cannot be cast to com.xl.oop.extendsdemo.Cat
        //只能强转父类引用,不能强转父类对象
        Cat a2 = (Cat) a1;
        System.out.println(a1.toString());
        System.out.println(a2.toString());

    }
}

注:子类继承父类,会继承父类的属性和方法,那么就需要先调用父类的构造器对父类中的属性进行初始化,初始化后再给子类使用。

优点

  1. 代码复用性提高了。
  2. 代码的扩展性和维护性提高了。

继承使用细节与注意事项


  1. 子类继承了父类所有的属性和方法,只是私有的属性不能直接访问,需要通过公共方法进行访问。(封装的体现)
  2. 子类没有继承父类的构造器,但在子类的构造器中必须调用父类的构造器,完成父类的初始化。(例:肯定是先有爷爷再有爸爸最后有儿子)
  3. 当创建子类时,不管你使用子类的哪个构造方法,默认情况下总会去调用父类的无参构造函数(super(),如果父类没有提供无参构造函数,则必须在子类的构造函数中用 super 去指定使用父类的哪个构造函数完成对父类的初始化工作,否则,编译不会通过。
  4. 如果希望指定调用父类的某个构造方法,需要使用super关键字显式调用。
    1. 无参构造器:super();
    2. 有一个参数:super(参数);
    3. 要注意super在使用时,需要放在方法体的第一句位置。
  1. super() 和 this() 都只能放在构造方法句首,因此这两个方法不能共存在一个方法中
  2. java中所有的类都是Object类的子类
  3. 子类最多只能有一个直接父类,也就是只能继承一个父类。(若需要A类继承B类和C类,则A继承B,B继承C)。
  4. 父类构造器的调用不限于直接父类!将一直往上追溯直到Object类。同样的,若子类调用父类提供的方法,也不限于直接父类。

super关键字

概述

super代表父类的引用,用于访问父类的属性、方法、构造器。

  • 访问父类的属性 , 不能访问父类的private属性 ( super.属性名);
  • 访问父类的方法,不能访问父类的private方法 super.方法名(参数列表));
  • 访问父类的构造器(只能访问非私有的父类构造器):
  • super(参数列表); 构造器的调用只能放在构造器中,且一定在第一行
  • 调用父类的构造器 (分工明确, 父类属性由父类初始化,子类的属性由子类初始化)
  • 当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super、this、直接访问是一样的效果!
  • super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;如果多个基类中都有同名的成员,使用super访问遵循就近原则。A->B->C

super与this区别

访问权限

概述

指的是本类及本类内部的成员(成员变量、成员方法、内部类)对其他类的可见性,即这些内容是否允许其他类访问。Java 总共有四种访问权限控制,其权限大小为:public > protected > default(包访问权限) > private

  • public:Java 访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且可以跨包访问
  • protected:介于 public 和 private 之间的一种访问修饰符,一般称之为“保护访问权限”。被其修饰的属性以及方法只能被类本身及其子类访问,即使子类在不同的包中。外包的非子类不可以访问
  • default:“默认访问权限“或“包访问权限”,即不加任何访问修饰符只允许在同包访问,外包的所有类都不能访问。接口例外
  • private:Java 访问限制最窄的修饰符,一般称之为“私有的”。被其修饰的属性以及方法只能被该类的对象访问,其子类不能访问,更不允许跨包访问

访问权限控制的五种使用场景

外部类的访问控制

外部类(外部接口)是相对于内部类(也称为嵌套类)、内部接口而言的。外部类的访问控制只能是这两种:public、default

public 访问权限的外部类,所有类都可以使用此类

public class Person{}

default 访问权限的外部接口,所有类、接口均可以使用此接口

interface IPersonInterface{}

类成员的访问控制

概述

类成员分为三类:成员变量、成员方法、成员内部类(内部接口)。类成员的访问控制可以是四种,也就是可以使用所有的访问控制权限

代码演示

package com.xl.oop.access;

/**
 * 类成员的访问控制
 * @author 公众号:编程识堂
 * @date 2023/4/14 0014 14:07
 * @description
 */
public class AccessRights {
    /**
     * 可以被所有的类访问
     */
    public String publicStr = "public";
    /**
     * 可以被本包的类及所有子类使用
     */
    protected String protectedStr = "protected";
    /**
     * 只允许同包访问
     */
    String defaultStr = "default";
    /**
     * 只允许同类访问
     */
    private String privateStr = "private";
    
    public void publicMethod() {
        System.out.println("publicMethod");
    }
    protected void protectedMethod() {
        System.out.println("protectedMethod");
    }

    /**
     * default 访问权限,在本包范围内使用
     */
    void method() {
        System.out.println("defaultMethod");
    }

    /**
     * private权限的方法,只能在本类使用
     */
    private void privateMethod() {
        System.out.println("privateMethod");
    }

    /**
     * private权限的内部类,即这是私有的内部类,只能在本类使用
     */
    private class privateMethod {
    }
}

注:此处的类成员是指类的全局成员,并没有包括局部的成员(局部变量、局部内部类,没有局部内部接口)。或者说,局部成员是没有访问权限控制的,因为局部成员只在其所在的作用域内起作用,不可能被其他类访问到。

/**
 * 局部成员
 * @author 公众号:编程识堂
 */
public void test(){
        //局部成员变量
        public int age;//编译无法通过,不能用public修饰
        int money;//编译通过
        //局部嵌套接口
        class User{//编译通过
        }
}

抽象方法的访问权限

普通方法可以使用四种访问权限,但抽象方法不能用 private 来修饰,即抽象方法不能是私有的。否则,子类就无法继承实现抽象方法

接口成员的访问权限

接口由于其自身特殊性,接口中的方法默认是public abstract 类型的,它必须由子类实现,所以在用的时候,并不一定需要完整写出所有的修饰符,编译器会帮忙完成。也就是,可以少写修饰符,但不能写错修饰符。

构造器的访问权限,可以是以上四种权限中的任意一种

  • 采用 public:对于内外包的所有类都是可访问的。
  • 采用 protected:就是为了能让所有子类继承这个类,但是外包的非子类不能访问这个类。
  • 采用包访问控制:比较少用,此类对象只能在本包中使用,但是如果此类有static成员,那么该类还是可以在外包使用(也许可以用于该类的外包单例模式)。

注意:外包的类不能继承该类

  • 采用 private:一般是不允许直接构造此类的对象,再结合工厂方法(static方法),实现单例模式。注意:所有子类都不能继承它。


注意:构造方法有点特殊。因为子类的构造器初始化时,都要调用父类的构造器,所以一旦父类构造器不能被访问,那么子类的构造器调用失败,意味着子类继承父类失败

子类异常、访问权限与父类的关系

子类的对象可以作为父类的对象(引用时是对父类方法的引用,但是传入的对象是子类的对象,即用子类的对象来对父类进行实例化),但是反过来不行。所以:

  • 子类的访问权限一定要比父类大或相等。【子访问权限>父访问权限】

例:

父类A拥有的方法public void setXXX(){}可以被其他任意对象调用。该方法被子类B重写后为void setXXX(){},即默认的访问权限只能被本包及其子类所访问。假设其它包中的对象C调用方法为:get(A a=new B()){a.setXXX();}。而此时传入的对象为B类对象b,此时b将转型为a,但是b中的setXXX()调用权限已经被缩小了这将造成错误。所以子类的方法的访问权限不能小于父类。

以上只是一个例子还有其他出于易维护、易代码结构设计的设计思想原因。

  • 子类重写父类的方法时,抛出的异常大小不能比父类的异常大。【子异常<父异常】

方法的重写、重载

重写

发生在父子类中

子类继承父类,继承了父类中的方法,但是父类中的方法并不一定能满足子类中的功能需要,所以子类中需要把方法进行重写。

父类中哪些方法不能被重写

  • 父类中的静态方法不能被子类重写
  • 父类中的私有方法不能被子类重写
  • 被final修饰的方法不能被重写

也就是说,只有在子类中,可以直接访问到的,父类的方法,并且是非静态的方法,才能被子类重写

重载

发生在同一个类中

类可能有多个同名的方法,包括构造方法,它们有相同的方法名,但是却有不同的参数,这时就会根据函数参数传递的参数类型进行匹配,选择适合的相同参数类型的方法。因此要完整的描述一个方法就需要展示其相同方法名以及不同的参数类型,这就叫做方法的重载。

对于构造方法,仅当当前类中没有其它有参构造函数时才会得到一个默认的无参构造器,但是只要有一个有参构造函数,但是却没自定义一个无参构造器的话就必须给定有参的初始值

区别

override(重写)

   1、方法名、参数、返回值相同。

   2、子类方法不能缩小父类方法的访问权限。

   3、子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。

   4、存在于父类和子类之间。

   5、方法被定义为final不能被重写。

 overload(重载)

  1、参数类型、个数、顺序至少有一个不相同。

  2、不能重载只有返回值不同的方法名。

  3、存在于父类和子类、同类中。

多态

概述

多态(polymorphism)指为不同数据类型的实体提供统一的接口。多态允许将子类的对象当作父类的对象使用,父类的引用指向其子类的对象,调用的方法是该子类的方法。多态是建立在继承和封装之上的。

多态的作用

1.不必编写每一子类的功能调用,可以直接把不同子类当父类看,屏蔽子类间的差异,提高代码复用性
2.父类引用可以调用不同子类的功能,提高了代码的扩充性和可维护性

多态的体现

1.方法重载是一个类中的多态性表现,方法重写是子类与父类的一种多态性表现。
2.实例化对象的过程中也有多态性的体现

我们可以通过父类实例化子类!这样我们就实现了通过一个父类实例化的实例来调用他下属子类的方法,而不再是一一地去实例化子类来调用方法或者传入形参列表,这不极大地提高了代码的复用性!

多态使用细节

1.向上转型

本质:父类的引用指向了子类的对象

父类 引用名 = new 子类类型();

①它可以调用父类中的所有成员(遵守访问权限的前提下)

②不能调用子类中特有的成员

③最终运行效果看子类的具体实现(有继承的性质,调用方法是从子类开始查找)

2.向下转型

本质:用子类的类型来接收父类的实例对象

子类类型 引用名 = (子类类型) 父类引用;

①只能强转父类引用,不能强转父类对象

②要求父类的引用必须指向的是当前目标类型的对象

③当向下转型后,可以调用子类中的所有成员

Final关键字

概述

修饰类

如果该类不需要有子类,不需要被扩展,类中的方法不允许被重写,就使用final修饰。

  • 被final修饰过的类不能被继承
  • 类中所有方法默认都是final修饰

修饰变量

表示变量一旦被赋值就不可以更改它的值。

修饰类变量

  • 如果final修饰的是类变量,只能在静态初始化代码块中指定初始值或者声明该类变量时指定初始值

修饰成员变量

  • 如果final修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值。

修饰局部变量

系统不会为局部变量进行初始化,局部变量必须由程序显示初始化。因此使用final修饰局部变量时,即可以在定义时指定默认值(后面的代码不能对该变量进行修改),

也可以不指定默认值,而在后面的代码中对final变量赋初始值(仅一次)。

package com.xl.oop.finaldemo;

/**
 * final修饰变量测试
 * @author 公众号:编程识堂
 * @date 2023/4/17 0017 15:44
 * @description
 */
public class FinalDemoTest {
    /**
     * final修饰的是类变量,只能在静态初始化代码块中指定初始值或者声明该类变量时指定初始值
     */
    final static Integer b =11;
    final static Integer a;
    static {
        a= 10;
    }

    /**
     * final修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值
     */
    final Integer c;
    final Integer d = 80;
    {
        c=20;
    }

    public static void main(String[] args) {
        /**
         * final修饰的是局部变量
         * 可以在定义时指定默认值(后面的代码不能对该变量进行修改),
         * 也可以不指定默认值,而在后面的代码中对final变量赋初始值(仅一次)
         */
        final Integer f;
        f= 90;
       // f=88; 非法 Variable 'f' might already have been assigned to
    }
}

修饰基本类型和引用类型数据

  • 如果修饰基本类型变量,则其数值一旦被初始化便不可以再被修改。
  • 如果修饰引用类型变量,则在对其初始化之后便不能再让其指向另外一个对象,但引用的值是可以变的
package com.xl.oop.finaldemo;

import lombok.Data;

/**
 * final修饰用类型测试
 * @author 公众号:编程识堂
 * @date 2023/4/17 0017 15:53
 * @description
 */
public class FinalDemo2Test {

    @Data
     class Person{
        private String name;
        private Integer age;
    }

   void test(){
        final  Person p1 = new  Person();
        //p1= new  Person();//非法Cannot assign a value to final variable 'p1'
       p1.setAge(10);//合法 引用的值是可以变的
       p1.setName("张三");
       System.out.println(p1);
   }
    public static void main(String[] args) {
        FinalDemo2Test finalDemo2Test = new  FinalDemo2Test();
        finalDemo2Test.test();
    }
}

为什么局部内部类和匿名内部类只能访问局部final变量

首先我们需要知道一点是:内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就销毁

这里就会产生问题:当外部类的方法结束时,局部变量就会销毁了,但内部类对象可能还存在(只有没有其他地方在引用它时,才会死亡)。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以访问它,实际访问的是局部变量的"copy"。这样就好像延长了局部变量的生命周期。

将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变,怎么解决呢?

将局部变量设置为final,对它初始化后,我就不让你再去修改这个变量,就保证了内部类的成员变量和方法的局部变量的一致性。这实际上也是一种妥协。使得局部变量与内部类简历的拷贝保持一致。

package com.xl.oop.finaldemo;

/**
 * 局部内部类和匿名内部类只能访问局部final变量测试
 *
 * @author 公众号:编程识堂
 * @date 2023/4/17 0017 16:05
 * @description
 */
public class FinalDemo3Test {

    public static void main(String[] args) {
        FinalDemo3Test finalDemo3Test = new FinalDemo3Test();
        finalDemo3Test.test2();
    }

    void test1() {
        //  String a="张三"; 非法
        /**
         * final保证了匿名内部类的成员变量和方法的局部变量的一致性
         */
        final String a = "张三";
        //匿名内部类
        new Thread() {
            @Override
            public void run() {
                System.out.println(a);
            }
        }.start();
    }

    void test2() {
        InClass inClass = new InClass();
        inClass.m1();
    }

    /**
     * final保证了内部类的成员变量和方法的局部变量的一致性
     */
    final Integer b = 12;

    //内部类
    class InClass {
        public void m1() {
            System.out.println(b);
        }
    }
}

抽象

概述

当父类的某一些方法并不知道具体实现内容,但需要继承给子类让其在子类中实现时,就可以将这些方法声明为抽象方法,而有抽象方法的类就叫做抽象类。使用abstract来声明。

  • 抽象类不可以被实例化
  • 抽象类不一定必须包含abstract方法,即抽象类不一定有抽象方法
  • 一旦包含了抽象方法,则这个类必须声明为abstract抽象类
  • abstract只能修饰类与方法,不可以修饰其他内容
  • 抽象类可以有任意成员(因为抽象类还是类),比如:非抽象方法、构造器、静态属性等等
  • 抽象方法不能有主体,即不能实现
  • 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。
  • 抽象方法不能使用private、final 和 static来修饰,因为这些关键字都是和重写相违背的。

代码演示

/**
 * 抽象类
 *
 * @author 公众号:编程识堂
 * @date 2023/4/17 0017 16:05
 * @description
 */
public class Abstract_Test {

    public static void main(String[] args) {
        //细节1:对于抽象类而言,不可以声明实例
//      A a = new A(); //报错:Cannot instantiate the type A
         
        //细节5.但当抽象类的子类,创建了实例之后,仍然可以使用A类中满足访问权限的所有东西。(继承的特点)
        B b = new B();
        b.setName("小堂");
        b.getName();
    }
}


abstract class A{   //细节3:由于A类中的hi()方法是被abstract修饰的(即抽象方法),所以A类也必须声明为抽象的
     
    private String name;    //细节5:抽象类中也可以有非抽象的东西,抽象类本质也是一个类
     
    public String getName() {  
        return name;
    }
    public void setName(String name) { 
        this.name = name;
    }

    public abstract void hi();  //细节6:对于抽象类而言,不可以有{},即方法体
     
    public abstract void hello();
}

class B extends A{
    //细节7:只要继承抽象类,则必须实现抽象类中所有的抽象方法。否则编译器报错:
    //The type B must implement the inherited abstract method A.hi()....
     
    @Override
    public void hi() {
        System.out.println("B----hi");
    }
     
    @Override
    public void hello() {
        System.out.println("B-----hello");
    }
}

abstract class C extends A{
    /*  细节7:
     * 若继承了抽象类,但子类本身也是一个抽象类,那么可以不用实现抽象类中的抽象方法
     * 因为对于抽象子类C而言,是允许抽象方法存在的。
     * 一旦有非抽象子类D,继承了子类C,那么D同样需要实现抽象子类C中的所有抽象方法
     */

//  private abstract int age; //abstract 只能修饰方法与类,不可以修饰属性和其他的
     
    /*  细节8:
     * 对于抽象方法而言,不可以使用 private 或 final 修饰
     * 因为,抽象的本质就规范化父类的东西,让子类去实现父类已经写好的抽象方法
     * 而用private与final修饰的方法都不可以被子类继承,这就与抽象的作用冲突了
     */
//  private abstract void m1();
     
    /*  细节8:
     * 对于抽象方法而言,也不可以使用 static 修饰
     * 因为,static修饰之后,方法不再是成员方法了,而是类方法。
     * 而子类是无法继承类方法的,所以static与abstract的作用也是产生冲突了。
     */
//  public static abstract void m2();
}

class D extends C{
    @Override
    public void hi() {
        System.out.println("D-----hi");
    }

    @Override
    public void hello() {
        System.out.println("D-----hello");
    }
}

接口

概述

  • 接口实际上是一种特殊的抽象类;
  • 接口中所有的方法都是抽象方法,接口中的方法默认是public abstract 类型的,它必须由子类实现。
  • 类只能单继承,接口可以支持多实现
  • 接口同样具有多态性
  • 接口可以把很多不相关的内容进行整合
  • 接口中所有的变量都是全局静态变量

代码演示

package com.xl.oop.interfacedemo;

/**
 * 接口是多继承
 * @author 公众号:编程识堂
 * @date 2023/4/17 0017 16:48
 * @description
 */
public interface ICompanyServiceInterface extends IUserServiceInterface,SystemServiceInterface {

    void com();
}
package com.xl.oop.interfacedemo;

import org.springframework.stereotype.Service;

/**
 * @author 公众号:编程识堂
 * @date 2023/4/17 0017 16:48
 * @description
 */
@Service
public class CompanyServiceImpl implements ICompanyServiceInterface {
    /**
    必须实现抽象方法
    **/
    @Override
    public void com() {

    }

    @Override
    public void addUser() {

    }

    @Override
    public void sys() {

    }
}

接口和抽象类的区别

  • 定义的关键字不同。
  • 子类继承或实现关键字不同。
  • 类型扩展不同:抽象类是单继承,而接口是多继承
  • 方法访问控制符:抽象类无限制,只是抽象类中的抽象方法不能被 private 修饰;而接口有限制,接口默认的是 public 控制符
  • 属性方法控制符:抽象类无限制,而接口有限制,接口默认的是 public 控制符。
  • 方法实现不同:抽象类中的普通方法必须有实现,抽象方法必须没有实现;而接口中普通方法不能有实现,但在 JDK 8 中的 static 和 defualt 方法必须有实现。
  • 静态代码块的使用不同:抽象类可以有静态代码块,而接口不能有

Object

是所有引用类型的顶级父类,系统都会默认使用引用类型extends Object;

此类中提供了常用的方法:

1、toString():在Object中返回的是类全名@HashCode值,即对象的内存堆中的位置信息;

此方法会在输出变量时,或引用变量进行拼接时默认调用,而查看地址信息,通常没有必要,我们通常要查看的是对象的成员变量信息,

因此我们需要重写toString()方法,用于查看对象的详情。

2、equals(Object obj)

Object类型中的此方法中的逻辑是比较调用者this与形参obj的地址信息是否相等

简单说成:比较this与obj是不是同一个对象

在定义类型时,继承过来的equals()方法我们要重写

重写规则:

(1)查看传进来的obj是不是null

(2)查看传进来的obj是不是this

(3)查看传进来的obj是不是本类型

网站内容来自网络,如有侵权请联系我们,立即删除!
Copyright © 笨百科 鲁ICP备2024053388号-2