美文网首页
面向对象

面向对象

作者: c_gentle | 来源:发表于2021-01-11 21:22 被阅读0次

    一、软件开发方式

    1、面向过程

    一种较早的编程思想,顾名思义该思想是站在过程的角度思考问题,强调的是我该怎么去做。即功能的执行过程,即先干啥,后干啥。
    ​ 面向过程思想中函数是一等公民,每个函数负责完成某一个功能,用以接受输入数据,函数对输入数据进行处理,然后输出结果数据。
    而每一个功能我们都使用函数(类似于方法)把这些步骤一步一步实现,使用的时候依次调用函数就可以了。
    面向过程的设计思想,系统软件适应性差,可拓展性差,维护性低。

    二、面向对象

    一种基于面向过程的新的编程思想,顾名思义该思想是站在对象的角度思考问题,我们把多个功能合理的放到不同对象里,强调的是我该让谁来做。
    面向对象最小的程序单元是类,必须先存在类的定义,再有对象,而具备某种功能的实体,称为对象。
    举个例子,小明完成买菜,做菜,吃饭,洗完,写代码功能。
    大家一起来看看有对象和没对象的区别:


    wps4.jpg

    左图是没有对象的,右图是有对象的。区分面向过程的我该怎么做和面向对象的我该让谁来做的思想。
    面向过程和面向对象各有千秋,面向对象更符合我们常规的思维方式,稳定性好,可重用性强,易于开发大型软件产品,有良好的可维护性,它拥有三大特征:

    • 封装(Encapsulation)
    • 继承(Inheritance)
    • 多态(Polymorphism)

    三、成员变量和局部变量

    3.1、变量的分类

    ​根据变量定义位置的不同,分成两大类:

    • 成员变量:直接定义在类中,方法外面。又称之为字段(Field),不要称之为属性(错误),后讲
    • 局部变量:除了成员变量,其他都是局部变量,具体存在于三个地方
      • 方法内
      • 方法的形
      • 代码块中(一对花括号)

    3.2、变量的初始化

    变量的初始化表示在内存中开辟存储空间,只有初始化之后,才能使用。

    • 成员变量:默认是有初始值的,不同类型的初始值,如下图
    • 局部变量:没有初始值,所以必须手动先初始化才能使用

    3.3变量的作用域

    变量根据定义的位置不同,也决定了各自的作用域是不同的,关键看变量所在的那对花括号。

    • 成员变量:在所定义的类中都有效
    • 局部变量:从开始定义的位置开始,只能在自己所在的花括号内有效

    3.4变量的生命周期

    变量的生命周期,表示变量在内存在能存活多久。

    • 成员变量:存储在堆内存中,随着对象的销毁而销毁
    • 局部变量:存储在栈内存中,随着所定义方法的调用结束而销毁
      • 局部变量存储在方法中,每次调用方法都会在栈空间开辟一块内存空间——栈帧,方法调用结束,栈帧就被销毁了,内存中的存储的变量数据也销毁了

    3.5类和对象的关系

    面向对象思想中有两个非常重要的概念,类和对象,其中:

    • 类(class),是对某一类事务的抽象描述(状态和行为),如下图的抽象女人图。
    • 对象(object),表示现实生活中该类事物的个体,也称之为实例,如下图的每一个具体的女人。
    • 类可以看作是对象的数据类型,就好比无论你是谁,只要是女人,那么类型就是女人。
      wps5.jpg
      上图,从左往右看是抽象的过程,从右往左看是实例化的过程,所谓实例化就表示根据类这个模板制造出每一个对象。
      任何事物存在就有一定的功能,在面向对象中,任何事物都可以看成对象,也就是万物皆对象的概念。
      注意,在开发中,必须先有类,再使用new关键字根据类来创建出对象。

    四、类的定义

    类是拥有相同特性(状态)和行为(功能)的多个对象的抽象。而一般的,描述类或者说定义类,就从状态和行为上分析,那么怎么来表示状态和行为呢?

    • 使用成员变量来表示状态
    • 使用成员方法来表示行为
      定义语法格式:
    public class 类名{
        //可编写0到N个成员变量
        [修饰符] 数据类型  变量名1;
        [修饰符] 数据类型  变量名2;
    
        //可编写0到N个成员方法
        [修饰符] 返回值类型  方法名称(参数){
            //方法体
        }
    }
    

    注意:

    • 成员变量和方法都 能使用static修饰,修饰符是可选用的,都先不使用任何修饰符
    • 在面向对象设计中,描述对象的类和测试类分开来编写
    • 在描述对象的类中,不需要定义main方法,专门在测试类中提供main方法。

    五、访问修饰符

    车库有一个车位,旁边写着”公共车位”,那么该车位就是公共的,谁都可以访问它。如果我在车位旁边写上“私家车位”,那么该车位就只能是我自己来访问。外界(除我之外)都访问不了,像“公共”、“私有”这种限制外界访问的标记符号,就称之为访问修饰符。
    ​ 访问修饰符,决定了有没有权限访问某个资源。
    ​ 封装其实就是要让有些类看不到另外一些类中定义的字段和方法。Java提供了不同的访问权限修饰符来限定类中的成员让谁可以访问到。


    wps7.jpg
    • private:表示当前类私有的,类访问权限,只能在本类中操作,离开本类之后就不能直接访问
    • 不写(缺省):表示当前包私有,包访问权限,定义和调用只能在同一个包中,才能访问
    • protected:表示子类访问权限,同包中的可以访问,即使不同包但是有继承关系也可以访问
    • public:表示公共的,可以在当前项目中任何地方访问

    六、面向对象

    1、this关键字

    局部变量和成员变量同名,此时在方法中调用变量时根据就近原则,优先使用局部变量,示意图如下。


    image-20200605174604319.png

    可以看出setName方法中两次使用的name,都是直接寻找距离自己最近的形参name,就相当于把参数name的值设置给参数name,根本就没有把参数值设置给成员变量。
    ​ 该问题,更专业的叫法是局部变量和成员变量存在二义性,也就是变量名有歧义。为了解决该问题——有请this关键字。
    ​ 使用 this.变量名的语法,此时访问的就是成员变量,


    image-20200605174658036.png
    使用构造器还是setter方法
    构造器和setter方法都可以给对象设置数据:
    • 构造器,在创建对象的时候设置初始数据,只能初始化一次。
    • setter方法,创建对象后再设置初始数据,可以设置多

    2、继承

    子类继承父类之后,可以拥有到父类的某一些成员(字段和方法),根据访问修饰符来判断:

    • 如果父类中的成员使用public和protected修饰,子类都能继承.
    • 如果父类和子类在同一个包中,使用缺省访问修饰的成员,此时子类可以继承到
    • 如果父类中的成员使用private修饰,子类继承不到。private只能在本类中访问
    • 父类的构造器,子类也不能继承,因为构造器必须和当前的类名相同

    3、方法覆盖

    当子类存在一个和父类一模一样的方法时,我们就称之为子类覆盖了父类的方法,也称之为重写。那么我们就可以在子类方法体中,重写编写逻辑代码。
    方法的调用顺序:
    通过对象调用方法时,先在子类中查找有没有对应的方法,若存在就执行子类的,若子类不存在就执行父类的,如果父类也没有,报错。
    方法覆盖的细节:
    private修饰的方法不能被子类所继承,也就不存在覆盖的概念。

    1. 实例方法签名必须相同 (方法签名= 方法名 + 方法的参数列表)
    2. 子类方法的返回值类型是和父类方法的返回类型相同或者是其子类
    3. 子类方法中声明抛出的异常小于或等于父类方法声明抛出异常类型
    4. 子类方法的访问权限比父类方法访问权限更大或相等

    4、super关键字

    问题,在子类中的某一个方法中需要去调用父类中被覆盖的方法,此时得使用super关键字。

    public class Ostrich extends Bird{
        public void fly() {
            System.out.println("扑扑翅膀,快速奔跑...");
        }
        
        public void say() {
            super.fly();//调用父类被覆盖的方法
            fly();//调用本类中的方法
        }
    }
    

    如果调用被覆盖的方法不使用super关键字,此时调用的是本类中的方法。
    super关键字表示父类对象的意思,更多的操作,后面再讲。
    super.fly()可以翻译成调用父类对象的fly方法。

    5、抽象类和抽象方法

    使用abstract修饰的方法,称为抽象方法。

    public abstract 返回类型 方法名(参数);
    

    抽象方法的特点:

    • 使用abstract修饰,没有方法体,留给子类去覆盖
    • 抽象方法必须定义在抽象类或接口中
    public abstract class 类名{
    }
    

    一般的,抽象类以Abstract作为类名前缀,如AbstractGraph,一看就能看出是抽象类。
    抽象类的特点:

    • 抽象类不能创建对象,调用没有方法体的抽象方法没有任何意义
    • 抽象类中可以同时拥有抽象方法和普通方法
    • 抽象类要有子类才有意义,子类必须覆盖父类的抽象方法,否则子类也得作为抽象类
      父类代码:
    public abstract class AbstractGraph {
        public abstract double getArea();   //没有方法体
    }
    

    子类代码:

    public class Circle extends AbstractGraph {
        private int r;// 半径
        public void setR(int r) {
            this.r = r;
        }
        public double getArea() {   //覆盖父类抽象方法
            return 3.14 * r * r;    //编写方法体
        }
    }
    

    == 符号到底比较的是什么:

    • 比较基本数据类型:比较两个值是否相等
    • 比较对象数据类型:比较两个对象是否是同一块内存空间
      每一次使用new关键字,都表示在堆中创建一块新的内存空间。

    七、多态思想

    1、接口

    接口是一种约定规范,是多个抽象方法的集合。仅仅只是定义了应该有哪些功能,本身不实现功能,至于每个功能具体怎么实现,就交给实现类完成。
    接口中的方法是抽象方法,并不提供功能实现,体现了规范和实现相分离的思想,也体现了组件之间低耦合的思想。
    ​ 所谓耦合度,表示组件之间的依赖关系。依赖关系越多,耦合性越强,同时表明组件的独立性越差,在开发中往往提倡降低耦合性,可提高其组件独立性,举一个低耦合的例子。
    电脑的显卡分为集成显卡和独立显卡:

    • 集成显卡:显卡和主板焊死在一起,显卡坏了,只能换主板
    • 独立显卡:显卡和主板相分离,显卡插到主板上即可,显卡坏了,只换显卡,不用换主板
      ​ 接口也体现的是这种低耦合思想,接口仅仅提供方法的定义,却不提供方法的代码实现。那么得专门提供类并去实现接口,再覆盖接口中的方法,最后实现方法的功能,在多态案例中再说明。
      接口定义和多继承性
      接口可以认为是一种特殊的类,但是定义类的时候使用class关键字,定义接口使用interface关键字。
    public  interface  接口名{
        //抽象方法1();
        //抽象方法2();
        //抽象方法2();
    }
    

    接口表示具有某些功能的事物,接口名使用名词,有人也习惯以I打头如IWalkable.java。
    接口定义代码:

    public interface IWalkable {
        void walk();
    }
    

    接口中的方法都是公共的抽象方法,等价于:

    public interface IWalkable {
        public abstract void walk();
    }
    

    拓展: 从Java8开始, Java支持在接口中定义有实现的方法, 如:

    public interface IWalkable {
        public abstract void walk();//抽象方法
        
        default void defaultMethod(){
            System.out.println("有默认实现的方法, 属于对象");
        }
        static void defaultMethod(){
            System.out.println("有默认实现的方法, 属于类");
        }
    }
    

    类可以继承类,但是只能单继承的,接口也可以继承接口,但是却可以继承多个接口,也就是说一个接口可以同时继承多个接口,如两栖动物可以行走也可以拥有。

    2、多态时方法调用问题

    把子类对象赋给父类变量,此时调用方法:

    Animal a = new Cat();
    a.shout();
    

    那么a对象调用的shout方法,是来自于Animal中还是Cat中?判断规则如下:


    image.png

    文字解释,先判断shout方法是否在父类Animal类中:

    • 找不到:编译报错
    • 找 到:再看shout方法是否在子类Cat类中:
    • 找不到:运行父类方法
    • 找 到:运行子类方法(这个才是真正的多态方法调用)
      多态小结
    //Dog d = new Dog();
    Cat c = new Cat();
    Animal a = new Cat();
    a.xxx();
    a.ooo();
    IEmployeeService service = new EmployeeServiceImpl2();
    service.save();
    service.delete();
    

    八、this关键字

    什么是this:表示当前对象
    this主要存在于两个位置:

    • 在构造器中:表示当前被创建的对象
    • 在 方法中:哪一个对象调用this所在的方法,此时this就表示哪一个对象
    public class Cat {
        private String name;
        private int age;
    
        public Cat() {
            System.out.println("构造器中:" + this); //当前创建的对象
        }
        public void say() {
            System.out.println("say方法中:" + this);   //当前调用say方法的对象
            System.out.println("名字=" + this.name + ",年龄=" + this.age);
        }
        public void setName(String name) {
            this.name = name;
        }
        public void setAge(int age) {
            this.age = age;
        }
    }
    

    测试代码:

    public class ThisDemo {
        public static void main(String[] args) {
            Cat c1 = new Cat(); //此时构造中的this就是当前创建的c1对象
            c1.setName("加菲");
            c1.setAge(5);
            c1.say();   //此时say方法中的this就是c1对象
            
            Cat c2 = new Cat();//此时构造中的this就是当前创建的c2对象
            c2.setName("Tom");
            c2.setAge(3);
            c2.say();   //此时say方法中的this就是c2对象
        }
    }
    

    运行测试:

    构造器中            :cn.wolfcode._01_this.Cat@15db9742
    say方法中           :cn.wolfcode._01_this.Cat@15db9742
    名字=加菲,年龄=5
    构造器中            :cn.wolfcode._01_this.Cat@6d06d69c
    say方法中          :cn.wolfcode._01_this.Cat@6d06d69c
    名字=Tom,年龄=3
    

    什么时候需要使用this:

    • 解决局部变量和成员变量之间的二义性,此时必须使用
    • 同一个类中非static方法间互调(此时可以省略this,但是不建议省略)
    • 构造器重载的互调
    public class Dog {
        private String name;
        private int age;
    
        public Dog() {
        }
    
        public Dog(String name) {
            this(name, 0);// 调用两个参数的构造器,必须放在构造器第一行
            // TODO其他操作
        }
    
        public Dog(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public void say() {
            String name = "局部变量";
            System.out.println(name);       // 访问局部变量
            System.out.println(this.name);  // 访问成员变量
            this.other();// 调用当前类中非static方法
        }
    
        public void other() {
            System.out.println(this.age);//此时的this是谁
        }
    }
    

    九、super关键字

    什么是super:
    this :表示当前对象,谁调用this所在的方法,this就是哪一个对象
    super :当前对象的父类对象
    在创建子类对象时,在子类构造器的第一行会先调用父类的构造器。
    什么时候使用super:

    • 在子类方法中,调用父类被覆盖的方法,此时必须使用super
    • 在子类构造器中,调用父类构造器,此时必须使用super语句
      父类代码:
    public class Person {
        private String name;
        private int age;
    
        public Person() {
        }
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public void doWork() {
            System.out.println("Person...doWork...");
        }
    }
    

    子类代码:

    public class Student extends Person {
        private String sn;
    
        public Student(String sn) {
            super();// 隐式调用父类无参数构造器,必须作为构造第一行
            this.sn = sn;
        }
        public Student(String name, int age, String sn) {
            super(name, age);// 显示去调用父类无参数构造器,必须作为构造第一行
            this.sn = sn;
        }
        public void doWork() {
            super.doWork(); //  ?此时调用谁的方法
            this.doWork();  //  ?此时调用谁的方法
            System.out.println("Student...doWork...");
        }
    }
    

    十、static修饰符

    static修饰的字段和方法直接属于类,不属于该类的对象。记住:字段和方法属于谁,就让谁来调用。

    • 使用static修饰的成员: 属于类 直接使用类名调用即可
    • 没有使用static修饰的成员: 属于对象 必须先创建对象,再调用
      注意:static方法不能使用super和this:
    因为static是类级别的,super和this是对象级别的,存在类的时候不一定存在对象,也就说使用类名调用static方法时,此时可能是没有对象的。
    

    测试代码:

    public class Dog {
        public static int totalNumber = 100;
        public int age;
    
        public void m1() {
            System.out.println("实例方法");
        }
    
        public static void m2() {
            System.out.println("静态方法");
        }
    }
    
    public class StaticDemo {
        public static void main(String[] args) {
            Dog d1 = new Dog();
            d1.age = 5;
    
            Dog d2 = new Dog();
            d2.age = 15;
    
             //调用代码再后面
        }
    }
    

    内存分析:
    static修饰的成员变量(字段),随着所在类被加载进JVM,也同时存储在方法区中,被所有对象共享。


    图片 1_7.png

    实例成员和类成员的访问规则:

    d1.m1();//  实例方法
    d2.m1();//  实例方法
    // Dog.m1(); 语法报错
    
    d1.m2();//静态方法   底层使用类名访问
    d2.m2();//静态方法   底层使用类名访问
    d1.m2();//静态方法   底层使用类名访问
    Dog.m2();
    
    System.out.println(d1.age);// 5
    System.out.println(d2.age);// 15
    // System.out.println(Dog.age); 语法报错
    
    System.out.println(d1.totalNumber);//100     底层使用类名访问
    System.out.println(d2.totalNumber);//100     底层使用类名访问
    System.out.println(Dog.totalNumber);//100
    

    使用对象访问static方法或成员变量,底层依然使用类名访问的。
    一般的,static方法访问的成员变量必须使用static修饰。
    最后记住结论:

    • 类 成 员:使用static修饰的字段和方法: 属于类 直接使用类名调用即可
    • 实例成员:没有使用static修饰的字段和方法: 属于对象 必须先创建对象,再调用

    十一、final修饰符

    ​ 继承关系最大弊端是破坏封装,子类可以继承父类的实现细节,也可以通过方法覆盖的形式修改功能实现细节。那么怎么来限制某个类不能有子类,不能覆盖方法?——final修饰符。
    final的含义是最终的,不可改变的,可以修饰类、方法、变量。

    • final修饰的类:表示最终的类, 该类不能再有子类
     final public class Super {}public class Sub  extends Super{    //此行语法报错}
    

    final修饰的方法:最终的方法,该方法不能被子类覆盖

    public class Super {
        final public void doWork() {
        }
    }
    
    public class Sub  extends Super{
        public void doWork() {      //此行语法报错
        }
    }
    

    final修饰的变量:表示常量,该变量只能赋值一次,不能再重新赋值。

    • 基本数据类型:表示的值不能改变
    • 引用数据类型:所引用的地址值不能改变
    final int age = 17;
    age = 100;  //此行语法报错
    
    final Dog d = new Dog();
    d.setAge(5);    //d的字段值是可以改变的   
    d = new Dog();  //此行语法报错
    

    十二、代码块

    什么是代码块:直接使用{}括起来的一段代码区域。
    代码块里变量属于局部变量,只在自己所在区域{}内有效。
    存在三种形式:
    局部代码块: 直接定义在方法内部的代码块,一般的,不会直接使用局部代码块的,结合if、while、for等关键字使用,表示一块代码区域。

    public class CodeBlockDemo {
        public static void main(String[] args) {
            System.out.println("begin...");
            {
                //直接使用代码块,一般不用
                int age = 17;
            }
            System.out.println(age);    //此行报错,超出age作用范围,就不能访问到了
            if (100 > 5) {
                System.out.println("100 > 5");
            }
            System.out.println("end...");
        }
    }
    
    • 初始化代码块(构造代码块):直接定义在类中。每次创建对象的时候都会执行初始化代码块,开发中不使用初始化代码块,即使要做初始化操作,可以直接在构造器中完成即可。
    • 静态代码块:使用static修饰的初始化代码块,当该代码块的类的字节码被加载进JVM,就执行static代码块代码。在开发中,用来做加载资源、加载配置文件等操作。
      分析下面代码执行顺序:
    public class Fish {
        {
            System.out.println("初始化代码块...");
        }
        static {
            System.out.println("静态代码块...");
        }
        public Fish() {
            System.out.println("构造器");
        }
        
        public static void main(String[] args) {
            System.out.println("主方法...");
            new Fish();
            new Fish();
        }
    

    运行结果:

    静态代码块...
    主方法...
    初始化代码块...
    构造器
    初始化代码块...
    构造器```
    ##十三、内部类和匿名内部类
    什么是内部类,把一个类定义在另一个类的内部,把里面的类称之为内部类,把外面的类称之为外部类。
    ![图片 2_7.png](https://img.haomeiwen.com/i15616626/a8e424a607a5ddc5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    内部类可以看作和字段、方法一样,是外部类的成员,而成员可以有static修饰。
    - 静态内部类:使用static修饰的内部类,那么访问内部类直接使用外部类名来访问
    - 实例内部类:没有使用static修饰的内部类,访问内部类使用外部类的对象来访问
    - 局部内部类:定义在方法中的内部类,一般不用
    - 匿名内部类:特殊的局部内部类,适合于仅使用一次使用的类
    对于每个内部类来说,Java编译器会生成独立.class文件。
    - 静态和实例内部类:外部类名$内部类名字-
    - 局部内部类:外部类名$数字内部类名称
    - 匿名内部类:外部类名$数字
    . 匿名内部类
    ​   如果这一个Print类只需要使用一次的话,就完全没有必要单独定义一个Java文件,直接使用匿名内部类来完成。
    ​   匿名内部类,可以使用父类构造器和接口名来完成。
    针对类,定义匿名内部类来继承父类(使用较少):
    

    new 父类构造器([实参列表]) {
    //匿名内部类的类体部分
    //等价于一个类的类体部分
    }

    针对接口,定义匿名内部类来实现接口(使用较多):
    

    new 接口名称(){
    //匿名内部类的类体部分
    //定义成员变量
    //定义成员方法

    //覆盖接口中的所有抽象方法
    

    }

    注意:这里不是根据接口创建对象,而是一种语法而已。
    

    board.plugin(new IUSB() {
    public void swapData() {
    System.out.println("打印...打印...");
    }
    });

    其实匿名内部类,底层依然还是创建了一份字节码文件USBDemo$1,其反编译代码为:
    ![图片 3_6.png](https://img.haomeiwen.com/i15616626/668f077277d3e36c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    

    相关文章

      网友评论

          本文标题:面向对象

          本文链接:https://www.haomeiwen.com/subject/zhkjaktx.html