Java - 面向对象

72

面向对象专业术语:

OO (Oriented Object) : 面向对象

OOP (Oriented Object Programming) : 面向对象的编程

OOD (Oriented Object Design) : 面向对象的设计

OOA (Oriented Object Analysis) : 面向对象的分析

OOT(Oriented Object Test) : 面向对象的测试

什么是面向过程:

面向过程也是解决问题的一种思想, 当我们在解决问题时, 会按照预先设定的想法和步骤, 一步一步去实现, 而具体的每一步都需要我们去实现和操作. 这些步骤相互调用和协作, 完成我们的需求

上述描述的每一个具体步骤我们都是参与者, 并且需要面对具体的每一个步骤和过程, 这就是面向 过程最直接的体现

通过上面简单的描述发现, 面向过程, 其实就是面向着具体的每一个步骤和过程, 就是面对具体的 每一个功能函数. 这些功能函数相互调用, 完成需求

提出问题, 分解问题, 一一解决

实质上是函数的调用

什么是面向对象:

OOP (Oriented Object Programming):

当不再面对具体的每一个方法时, 发现操作也变的简单了很多. 而封装具体功能的这类, 是我们需 要面对的. 而基于这个封装了具体功能的类, 那怎么使用呢?

当面向封装了具体功能类, 若要使用这个类, 一般情况下, 在 Java 中需要通过创建这个类的实体来 使用. 这个实体称之为对象. 在开发中, 我们是在不断的找封装不同功能的类. 基于这些类, 创建其对 象, 使用这些对象完成相应的操作

通过上面的讲解和分析得出:面向对象是基于面向过程, 对象是将功能进行了封装. 只要找到了具 体的类, 创建出对象, 就可以调用其中的具体功能. 面向对象也是用来解决问题的一种思维模式

在以后开发中, 先找对象, 调用对象中的具体功能. 如果真的没有能够完成需求的对象, 这时就自 己创建对象, 并将所需的功能定义到对象中, 方便以后使用

面向对象的思想也就是人认识世界所采用的方式: 分门别类的思想:

世界上的万物人都进行了分类, 按照相似性把数不清的事物分归类到有限的类别中, 比如不管是百灵鸟还是麻雀都是鸟类, 每个人有姓名都是不一样的个体但都叫人类

类 (class): 类别

在 java 中, 使用 class 关键字定义类

属性:

类的固有特征

行为和动作:

函数 function (方法 method)

对象 (object): 一个类别中的具体案例

如何定义类:

类的成员:

  1. 属性
  2. 方法
  3. 构造方法
  4. 静态属性
  5. 静态方法 // 静态方法就是类方法
  6. 构造代码块
  7. 静态代码块

访问修饰符 类型 属性名称 = 属性值;

public class Test {
    public static void main(String[] args) {

    }
}

// 定义了一个狗类
class Dog {
    // 固有属性, 类的固有特征
    // 访问修饰符 类型 属性名称 = 属性值;
    public String name;
    public int age = 3;

    // 狗会跑
    public void run() {

    }

    // 狗会吃
    public void eat() {

    }

    // 构造方法
    public Dog() {

    }

}

局部变量与成员变量的区别:

定义位置不同:

  • 成员变量定义在类中
  • 局部变量定义在方法中或者语句里面

在内存中的位置不同:

  • 成员变量存储在堆内存的对象中
  • 局部变量存储在栈内存的方法中

声明周期不同:

  • 成员变量随着对象的出现而出现在堆中, 随着对象的消失而从堆中消失
  • 局部变量随着方法的运行而出现在栈中, 随着方法的弹栈而消失

初始化不同:

  • 成员变量因为在堆内存中, 所有都是默认的初始化值
  • 局部变量没有默认的初始值, 必须手动给其赋值才可以使用

如何创建对象:

定义类, 在根据类来创建对象

可以通过类的构造方法创建对象, 使用 new 关键字:

类型 对象名称 = new 构造函数();

Scanner scan = new Scanner(System.in);

Random rand = new Random();

public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Dog dog_2 = new Dog();
    }
}

如何访问对象:

对象.属性

class Test {

    public static void main(String[] args) {
        Dog dog = new Dog();
        System.out.println(dog.name);
        Dog dog_2 = new Dog();
        dog2.name = "screl";
        System.out.println(dog2.name);
    }
}

成员的初始化默认值:

int, long, short, byte: 0

double: 0

char: '\u0000' 0

对象 (Object) 的默认值是 null

this 指针:

在java中, 存在 this 关键字, 这个关键字默认会指向当前对象

每一个对象都带有一个 this 指针

非静态的属性和方法不能在静态方法中使用, this 只能用在非静态方法中, 不能用在静态方法中(类方法)

非静态的方法可以使用静态属性和方法

构造方法 (Constructor):

也叫做构造函数

作用: 创建对象的时候初始化值, 对对象进行针对性初始化

  • 初始化过程: 默认初始化 -> 显示初始化 -> 针对性初始化

与类名相同, 没有返回值, 访问修饰符为 public, 非 static

如果声明了返回值jvm会将其解释为普通方法

在构造对象的时候自动调用构造方法

调用其他构造函数的语句必须定义在构造函数的第一行, 原因是初始化动作要最先执行

构造函数之间禁止出现相互调用的情况

注意:

  1. 在 java 类中如果没有自己定义构造函数, jvm 会自动生成一个无参的构造函数, 构造出来的成员的值为默认值, 当定义了一个构造函数之后, jvm 就不会生成
  2. 定义构造函数时, 一定要定义一个无参构造
  3. 构造方法如果是 private 不能从类外部实例化

构造方法的细节:

  • 一个类中可以有多个构造函数, 多个构造函数是以重载的形式存在的
  • 构造方法中也是有 return 语句的, 用于结束初始化动作(可省略)
  • 构造方法是可以被 private 修饰的, 作用:其他程序无法创建该类的对象
class Dog {
    public String name;
    public String color;


    // 自定义构造方法
    public Dog(String name, String color) {
        this.name = name;
        this.color = color;
    }

    // 一定要定义一个无参的构造方法
    public Dog() {

    }
}

this 调用构造方法

在之前学习方法之间调用时, 可以通过方法名进行调用. 可是针对构造方法, 无法通过构造方法名来相互调用.

构造方法之间的调用可以通过 this 关键字来完成

构造方法调用格式:

this(args);

调用其他构造方法的语句必须定义在构造函数的第一行, 原因是初始化动作要最先执行.

构造方法之间禁止出现相互调用的情况.

class Dog {
    String name;
    int age;
    String master;

    public Dog() {

    }

    public Dog(String name, int age, String master) {
        this(master);    // 调用另一个构造方法
        this.name = name;
        this.age = age;
    }

    public Dog(String master) {
        this.master = master;
    }
}

构造代码块:

在类中可以用 { } 表示构造代码块

class Dog {
    {
        System.out.ptintln("这是一个构造代码块");
    }
}

对象的创建流程及内存结构一:

  1. 在堆内存中开辟一块对象的内存空间, 并为其分配物理内存地址
  2. 对成员变量进行 默认初始化 (零值)
  3. 相应构造函数进栈, 先对成员变量进行 显式初始化
  4. 其次执行构造函数的内容, 对成员变量进行 针对性初始化
  5. 构造函数执行完成弹栈, 将对象的内存空间地址赋予相应的引用变量

static 关键字:

static 关键字可以修饰(属性, 方法, 类, 语法块, 导包)

static 还可以修饰内部类

static 修饰的只能执行一次

static 修饰的代码(方法或者属性), 会提前加载到内存中, 并且只有一份属于该类, 因此:

  1. 静态方法中, 无法直接调用非静态方法(或者属性)
  2. static 修饰的属性和方法, 不属于对象, 属于该类, 直接可以使用类名称.属性(或者方法)来调用
  3. 非静态的方法可以使用静态属性和方法
  4. 在静态方法中调用非静态方法和非静态属性, 需要先实例化

静态内部类:

class Test {

    // 静态内部类:
    static class Test_2 {

    }

}

静态方法:

class Test {
    public static void function() {

    }
}

静态属性:

class Test {
    static int num;
}

静态代码块:

static { } 表示静态代码块, 只会在类加载时执行一次

class Dog {
    static {
        System.out.println("这是一个静态代码块");
    }
}

对象的创建流程及内存结构(二)

  1. javac 命令编译Java源代码生成字节码文件
  2. java 命令将字节码文件加载进虚拟机
  3. 字节码文件具体加载进方法区(非静态区和静态区)
  4. JVM从静态区中寻找主函数 main(), 并将其加载进栈开始执行程序
  5. 后续和(一)一样

静态变量与成员变量的区别

  1. 变量所属不同:

    • 静态变量所属与类, 也称为类变量
    • 成员变量所属于对象, 也称为实例变量(实例变量)
  2. 内存中的位置

    • 静态变量存储于方法区中的静态区中
    • 成员变量存储于堆内存中
  3. 在内存中出现的时间

    • 静态变量随着类的加载而加载, 随着类的消失而消失
    • 成员变量随着对象的创建而在堆内存中出现, 随着对象的消失而消失
  4. 调用方式

    • 静态变量可以用类直接调用, 也可对象调用
    • 成员变量只能被对象调用

成员的加载顺序:

静态属性最先运行, 然后是非静态属性, 然后是静态代码块, 然后是构造代码块, 再然后是构造方法, 再是静态方法, 最后是普通方法

重写函数:

在打印对象的时候自动调用 toString() 方法

打印一个继承 Object 的类, 不重写则默认调用的是 ObjecttoString() 方法, 打印出来的是地址的哈希值

@override 标记重写

class Dog {
    public String name;
    public String color;

    // 构造函数
    public Dog(String name, String color) {
        this.name = name;
        this.color = color;
    }

    // 重写
    @override
    public String toString() {
        return this.name + this.color;
    }
}

Java (权限) 访问修饰符:

注意: 只有 缺省 和 public 修饰符能修饰类

public: 任何地方都可以正常访问

protected: 只能在同包, 同类, 同子类中访问 // 主要用于修饰方法, 为继承设计

缺省(默认): 只能在同包和同类中访问 // 不能被继承, 子类不能访问

private: 只允许当前类中访问 // 不能被继承

public 修饰类时:

public 表示全局类, 该类可以 import 到任何类内. 不加 public 默认为保留类, 只能被同一个包内的其他类引用

私有化构造函数:

  1. 工具类(全都是静态方法或者静态属性)
  2. 单例模式

单例模式:

某类创建对象只能且最多只有一个对象

将构造函数用 private 修饰, 让外界无法创建对象

在特殊情况下, 不允许外界创建对象, 减少堆内存占用

步骤:

  1. 私有化构造函数, 杜绝外界创建对象
  2. 内部创建该类对象(静态)
  3. 提供公共接口向外提供该类的对象

饿汉式:

提前提供一个对象

缺点: 即便没有使用对象, 也从始至终常驻内存, 浪费内存

public class Singleton {
    private static Singleton singleton = new Singleton();

    private Singleton() {

    }

    public static Singleton getInstance() {
        return singleton;
    }
}

饱汉式:

访问的才提供对象

缺点: 线程不安全

public class Singleton {
    private static Singleton singleton;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

内部类:

普通内部类

内部类可以直接访问外部类中的成员, 但外部类不能直接访问内部类, 若要访问, 必须创建内部类对象才能访问

内部类本身就是静态的, 用 static 修饰后类似于一个外部类了

创建一个内部类和对象:

class Test {
    class Inner {

    }
}

class Main {
    Inner inner = new Test().new Inner();
}

静态内部类

如果用 static 修饰内部类, 则该内部类属于该外部类, 但不属于外部类的对象

静态内部类可包括静态成员也可包括非静态成员. 根据静态成员不能访问非静态成员的规定, 所以静态内部类不能访问外部类实例成员, 只能访问外部类的静态成员.

class Test2 {
    static class Inner2 {

    }
}

class Main2 {
    Test2.Inner2 inner = new Test2.Inner2();
}

局部内部类:

在方法中创建局部内部类, 创建内部接口或者内部枚举也是一样:

class Outer {
    public void test() {
        class Inner {
            int a = 1;
        }
    }
}

方法中的内部类不能有访问修饰符

匿名局部内部类:

class Outer {
    int s = 12;

    public void test() {
        class Inner {
            int a = 1;
        }

        new Inner() {
            {
                System.out.println(a);
                System.out.println(s);
                execute();
            }

            public void execute() {
                System.out.println(s + 10);
            }
        };

    }

    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.test();

    }
}

非静态内部类细节

  1. 注意非静态内部类中不能定义静态成员变量和静态成员函数. 但可以定义静态成员常量. 原因常量 在生成字节码文件时直接就替换成对应的数字.
  2. 当内部类在外部类成员位置上被私有修饰, 在外部类以外的其他地方是无法访问的. 只能在外部类 中访问
  3. 有一个指针指向外部类
  4. 在外部创建内部类的实例时, 需要 Outer.Inner inner = new Outer().new Inner

以下怎么拿到num:

public class Test {
    public static void main(String[] args) {
        Outer.Inner in = new Outer().new Inner();
        in.show();
    }
}

class Outer {
    int num = 5;// 外部类的成员变量 

    class Inner {
        int num = 6;// 内部类的成员变量 

        void show() {
            int num = 7; // 内部类局部变量 
            System.out.println("内部类局部num=" + num);
            System.out.println("内部类成员num=" + this.num);
            System.out.println("外部类成员num=" + Outer.this.num);
        }
    }
}

注意如果外部类的成员为 static, 则内部类在访问外部类成员和方法时用外部类名.来访问

内部类本质上是外部类的成员, 所以外部类的 private 修饰符对内部类无效

为什么内部类可以直接访问外部类的成员, 那时因为内部类持有外部类的引用(外部类.this). 对 于静态内部类不持有 外部类.this 而是直接使用 外部类名.

面向对象三大特征:

  1. 封装
  2. 继承
  3. 多态

有人认为抽象也是一种特征, 但并不被公认

一. 封装 (Encapsulation):

为了安全性, 将成员私有化, 不让外界访问, 同时提供特定的访问方式, 相当于是一个外部类

class Cat {
    // 首先私有化属性
    private int id;
    private String name;
    private int age;

    // 提供对应的公开访问方法
    // get方法:
    public int getId() {
        return this.id;
    }

    // set 方法
    public void setId(int id) {
        this.id = id;
    }
}

POJO对象(Plain Ordinary Java Object)

  1. 根据封装来写
  2. 私有化属性
  3. 提供公开的 settergetter 方法
  4. 至少两个或者以上的构造方法

Java Bean对象:

  1. 所有的成员变量都要使用 private 关键字进行修饰

  2. 为每一个成员变量编写 settergetter 方法

  3. 创建一个无参数的构造方法

  4. 创建一个有参数的构造方法

二. 继承 (Inheritance):

在面向对象中, 类与类之间可以存在继承关系

Java是一门典型的单继承编程语言

Java 中如何实现继承:

关键字 extends

父类(超类, 基类), 子类

private 修饰的属性和方法都是无法被子类继承的

protected 修饰的方法就是用于子类继承的

class RichMan() {
    public int money = 100000000;
    private String wife = "wife";

    private void play() {
        System.out.println("炒股");
    }

    protected void controlCompany() {
        System.out.println("掌控公司");
    }
}

class Son extends RichMan {
    public static void main(String[] args) {
        Son son = new SOn();        // son中的成员只有money, 方法只有controlCompany
        System.out.println(son.money);
        son.contralCompany();
    }
}

继承的功能:

减少代码的重复, 提高代码的复用度

方法重写(覆盖):

重写 (OverWrite)

覆盖 (OverRide)

发生在继承中, 指的是, 子类继承了父类方法后不满足使用, 就重写该方法以满足子类使用

重写是访问修饰符权限可以放大但不能缩小, 除了访问修饰符, 返回值和参数还有函数名都保持一致

注解:

注解作业
@Override标记覆盖(重写)
@Deprecated标记过时
@SuppressWarnings压制警告
@SuppressWarnings("all")忽略全部类型的警告
@SuppressWarnings("unchecked")忽略未检查的转化, 例如集合没有指定类型的警告
@SuppressWarnings("unused")忽略未使用的变量的警告
@SuppressWarnings("resource")忽略与使用 Closeable 类型资源相关的警告
@SuppressWarnings("path")忽略在类路径, 原文件路径中有不存在的路径的警告
@SuppressWarnings("deprecation")忽略使用了某些不赞成使用的类和方法的警告
@SuppressWarnings("fallthrough")忽略 switch 语句执行到底没有break关键字的警告
@SuppressWarnings("serial")忽略某类实现Serializable, 但是没有定义 serialVersionUID 的警告
@SuppressWarnings("rawtypes")忽略没有传递带有泛型的参数的警告
class RichMan {
    public int money = 100000000;
    private String wife = "wife";

    public void play() {
        System.out.println("炒股");
    }
}

class Son extends RichMan {

    @Override
    public void play() {
        System.out.println("健身");
    }

    public static void main(String[] args) {
        Son son = new SOn();
        son.play();
    }
}

super 关键字:

super 在java中, 是一个指针, 类似于 this 关键字

this 关键字指向创建的每一个对象

super 会自动指向父类

super(); // 调用父类的无参构造, 默认在构造函数的第一行, 早于this(); 可以不写, 但是写就必须写在构造函数的第一行, 只能写其一

final 关键字:

final : 最后

  1. 被他修饰的变量就是常量

    static final int a = 1

  2. final 修饰的方法不能被重写

  3. ** final 关键字修饰类则该类不能被继承**

为什么类用 final 修饰:

基于 jvm 的安全性考虑, 防止被继承之后底层代码被篡改从而引起各种安全问题

对象的创建流程及内存结构三:

  1. javac 命令将源码 (.java) 进行编译, 生成字节码文件 (.class)
  2. java 命令执行字节码文件
  3. 将字节码文件加载进虚拟机, 具体方法进入方法区(非静态区和静态区)
  4. JVM从静态区中寻找main函数, 并将其加载进栈开始执行程序
  5. main 函数开始执行, 创建对象代码, 如 Son son = new Son();
  6. 在堆内存中开辟对对象的内存空间, 并分配地址
  7. 静态代码块执行
  8. 创建成员变量开始初始化
  9. 代码块执行
  10. 子类构造函数从非静态方法区加载进栈开始执行
  11. 第一句先执行父类的构造函数
  12. 父类构造函数执行, 为子类继承到的成员变量进行初始化(对象内存空间里的父类空间)
  13. 父类构造函数弹栈执行完成
  14. 子类构造函数继续执行
  15. 再执行子类构造函数的内容, 进行针对性初始化
  16. 执行完成, 子类构造函数弹栈, 将对象的内存空间地址赋予相应的引用变量

instanceof 关键字 判断对象是否为对应类, 配合继承判断

三. 多态 (Polymorphism):

在继承的基础上才有多态

父类应用指向子类示例

Animal cat = new Cat( );

访问不到子类自身的方法, 但可以重写父类方法来满足子类使用

多态的好处

提高了程序的扩展性.

多态的弊端

通过父类引用操作子类对象时, 只能使用父类中已有的方法, 不能操作子类特有的方法.

多态的前提

  1. 必须有关系:继承, 实现

  2. 通常都有重写操作

当父类的引用指向子类对象时, 就发生了向上转型, 即把子类类型对象转成了父类类型. 向上转型的好处是隐藏了子类类型, 提高了代码的扩展性.

public class Test {
    public static void main(String[] args) {
        Animal cat = new Cat("小黄", "猫");
        Animal dog = new Dog("小红", "狗");
        PetStore store = new PetStore();
        store.wash(cat);
        store.wash(dog);
    }
}

@Setter
@Getter
class Animal {
    String name;
    String type;

    public Animal() {

    }

    public Animal(String name, String type) {
        this.name = name;
        this.type = type;
    }

    public void beWash() {
        // 这段函数体没意义只用来被重写, 可以用抽象方法从而省去写这个函数
    }

}

class Dog extends Animal {

    public Dog() {

    }

    public Dog(String name, String type) {
        super(name, type);
    }

    @Override
    public void beWash() {
        System.out.printf("%s正在被洗, 是一只%s\n", this.name, this.type);
    }
}

class Cat extends Animal {

    public Cat() {

    }

    public Cat(String name, String type) {
        super(name, type);
    }

    @Override
    public void beWash() {
        System.out.printf("%s正在被洗, 是一只%s\n", this.name, this.type);
    }
}

class PetStore {

    public void wash(Animal pet) {
        pet.beWash();
    }
}