Java学习笔记第三章:面向对象(动力节点)

第三章 面向对象

自用学习笔记,用来记录和复习


1.面向对象概述

面向过程(PO:Procedure Oriented)
面向对象(OO:Object Oriented)

  • OOA:Object Oriented Analysis面向对象分析
  • OOD:Object Oriented Design面向对象设计
  • OOP:Object Oriented Programming面向对象编程

面向对象三大特征

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

1.1 类和对象

  • 将事物与事物之间具有的共同特征,共同的状态和行为提取出来,形成了一个模版,称为类
  • 状态在程序中对应属性,属性通常用变量来表示
  • 行为在程序中对应方法,用方法来描述行为动作
  • 类 = 属性 + 方法

对象

  • 对象为实际存在的个体,又称为实例(instance)
  • 通过类可以实例化多个对象

2.对象的创建和使用

定义类的语法格式

[修饰符列表] class 类名{
    类体 = 属性 + 方法

    // 属性(实例变量),描述的是状态

    // 方法,描述的是行为动作
}

为什么要定义类:

因为要通过类实例化对象.有了对象,让对象和对象之间协作起来形成系统.

实例变量属于成员变量,成员变量如果没有手动赋值,系统会赋默认值

    数据类型        默认值
    ----------------------
    byte            0
    short           0
    int             0
    long            0L
    float           0.0F
    double          0.0
    boolean         false
    char            \u0000
    引用数据类型      null

2.1 JVM内存分析

局部变量存储在虚拟机的栈帧中
实例变量存储在JVM堆内存的java对象内部,实例对象必须先new创建,才能通过引用来访问
在java中,程序员不能直接操作堆内存(没有指针).需要借助引用来访问堆内存
在这里插入图片描述

Student s1 = new Student();

s1是局部变量,保存的是堆内存中Student对象的内存地址
s1有一个特殊的称呼:引用
引用的本质是一个变量,这个变量中保存了java对象的内存地址.
引用和对象要区分开,对象在JVM堆中,引用是保存对象地址的变量
这种行为动作是需要对象才能触发的

2.2 实例变量和实例方法

通常描述一个对象的行为动作时,不加static
没有添加static的方法,称为实例方法(对象方法)
实例方法需要使用"引用."来调用.不能使用"类名."去调用

2.3 空指针异常

引用可以赋给null,表示引用不再指向对象.但接着空指针引用访问属性,编译可以通过,在运行时会出现空指针异常:NullPointerException
如果没有任何引用指向一个对象,那么该对象会最终被当成垃圾,被GC回收

2.4 方法调用时传递

方法调用时参数是如何传递的:不管是基本数据类型还是引用数据类型,在传的时候,永远都是把变量里面保存的那个值复制一份传过去.

2.5 this关键字

属性只有在实例对象创建之后,才可以访问.而在class中定义的方法需要使用这个属性,就需要用"this.属性"来替代.
this本质上是一个引用,this中保存的也是对象的内存地址,this中保存的是当前对象的内存地址.
每个实体方法都会把this保存在局部变量表的0号槽,哪个实例谁调用方法,this就会存谁的值
this一般可以省略,用来区分局部变量和实例变量的时候不能省略
java有就近原则,以下代码如果没有this,所有的age都为形参,和类里的属性age没有关系

public void setAge(int age) {
    if (age < 0 || age > 100) {
        System.out.println("年龄不合法");
        return;
    }
    this.age = age; // this在这里用来区分
}

在这里插入图片描述


3.封装

封装是一种将数据和方法加以包装,使之成为一个独立的实体,并且把它与外部对象隔离开来的机制。具体来说,封装是将一个对象的所有“状态(属性)”以及“行为(方法)”统一封装到一个类中,从而隐藏了对象内部的具体实现细节,向外界提供了有限的访问接口,以实现对对象的保护和隔离。
封装通过限制外部对对象内部的直接访问和修改,保证了数据的安全性,并提高了代码的可维护性和可复用性。
只对外部提供部分接口,不允许外部随意修改内部任何属性.

//不使用封装机制,User类型对象的age属性非常不安全,在外部程序中可以对其随意访问与修改
public class User {
    int age;
}

public class UserTest {
    User user = new User();
    user.age = -100;
}

实现封装的步骤:

  • 1.属性私有化 使用private进行修饰 禁止外部程序对该属性进行随意访问 私有的只能在本类访问
  • 2.为了保证外部的程序仍然可以访问age属性,因此要对外提供公开的访问入口.访问分为两种:
    • 读:读取属性的值 getter
    • 改:修改属性的值 setter

3.1 实例方法中调用实例方法

调用实例方法时必须有一个对象,注意this的位置

public void shopping() {
    System.out.println(name + "正在疯狂购物!!!");
    this.pay();
}

public void pay() {
    System.out.println(this.name + "正在付款...");
}

shopping调用pay时,shopping把0号槽存的this,传给pay的0号槽.


4.构造方法

constructor:构造方法
构造方法的作用:

  1. 对象的创建(通过调用构造方法可以完成对象的创建)
  2. 对象的初始化(给对象的所有属性赋值)

类似C++中的构造函数
构造方法名必须和类名一致 使用new来调用
构造方法不需要提供返回值类型,如果加上返回值类型就会变为普通方法.但实际上会返回新创建的对象的内存地址
如果一个类没有显式的定义出来,系统会默认提供一个无参数的构造方法.(缺省构造器)
一个类中如果显式的定义了构造方法,系统则不再提供缺省构造器.所以为了对象创建更加方便,建议把无参数构造方法手动的写出来

[修饰符列表] 构造方法名(形参列表) {
    构造方法体;
}

在java中,一个类可以定义多个构造方法,而且这些方法自动构成了方法的重载

构造方法执行原理:两个阶段不能颠倒,也不可分割

  • 第一阶段:对象的创建 new的时候就直接在堆内存中开辟空间,然后给所有属性赋默认值 (在构造方法体执行之前就完成了)
  • 第二阶段:对象的初始化 构造方法体开始执行,标志着开始进行对象初始化.构造方法体执行完毕,表示对象初始化完毕
public Student() {
    System.out.println("Student类的无参数构造方法执行了");
}

public Student(String name, int age, boolean sex, String address) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.address = address;
}

构造代码块:{} 每一次在new的时候都会先执行一次构造代码块.构造代码块是在构造方法执行之前进行的
构造代码块的作用:如果所有的构造方法在最开始的时候有相同的一部分代码,不妨将这个公共的代码放到构造代码块中.

关于构造代码块。对象的创建和初始化过程梳理:

new的时候在堆内存中开辟空间,给所有属性赋默认值
执行构造代码块进行初始化
执行构造方法体进行初始化
构造方法执行结束,对象初始化完毕。


5.this关键字

this出现在实例方法中,代表当前对象
this本质是一个引用,该引用保存当前对象的内存地址
通过this可以访问实例变量,可以调用实例方法
this存储在栈帧的局部变量表的第0个号槽位上
this大部分情况下可以省略,用于区分局部变量和实例变量时不能省略
this不能出现在静态方法中 static方法中没有当前对象 没有static的叫实例方法

this(实参):

  1. 通过这种语法可以在构造方法中调用本类中其他的构造方法。
  2. 作用:代码复用。
  3. this(实参); 只能出现在构造方法第一行。
public Date() {
    /*
    this.year = 1970;
    this.month = 1;
    this.day = 1;
        */

    // 不要这么写,这样会导致再创建一个新的对象。
    //new Date(1970,1,1);

    // 不会创建新对象。只是通过一个构造方法去调用另一个构造方法。
    this(1970,1,1);

    System.out.println("日期创建成功!~");
}

public Date(int year, int month, int day) {
    this.year = year;
    this.month = month;
    this.day = day;
}

6.static关键字

6.1 初识static关键字

静态方法无法访问实例变量和调用实例方法
java中有三种变量:实例变量,静态变量,局部变量

  1. static修饰的变量:静态变量
  2. static修饰的方法:静态方法
  3. 所有static修饰的,访问的时候直接采用"类名."不需要new对象

什么情况下把成员变量定义为静态成员变量:

  1. 当一个属性是对象级别的,这个属性通常定义为实例变量(实例变量一个对象一份自己的,100个对象应该有100个空间)
  2. 当一个属性是类级别的(所有属性都有这个属性,并且这个属性的值是一样的)建议将其定义为静态变量,在内存空间上只有一份,节省内存开销
  3. 这种类级别的属性,不需要new对象,直接通过类名访问

静态变量存储在哪里?静态变量在什么时候初始化?(什么时候开辟空间)

JDK8之后:静态变量存储在堆内存中.
静态变量在类加载时初始化

静态变量可以采用"引用."来访问.可以但不建议,会给程序员造成困惑,会使程序员认为它是一个实例变量.比如给new出来的类赋null,这时通过静态变量仍然能访问,出现空指针异常
静态方法中不能使用this关键字.因此无法直接访问实例变量和调用实例方法

6.2 静态代码块

static关键字还可以定义静态代码块:static{ }
静态代码块在类加载时执行,并且只执行一次
静态代码块可以编写多个,遵循自上而下的顺序依次执行
静态代码块什么时候使用:本质上, 静态代码块就是为程序员预留的一个特殊的时间点:类加载时刻.如果你需要在类加载时执行一些代码,那么这段代码就可以写到静态代码块中.比如在类加载的时候输出日志

String name = "zhangsan";
static int i = 100;

static {
    // 会报错,因为在静态上下文中无法直接访问实例相关的数据
    System.out.println(name);
    // 这个i可以访问,因为i变量是静态变量,正好也是在类加载时初始化
    System.out.println(i);
    System.out.println("静态代码块执行了");
}

6.3 static小总结

static修饰变量:静态变量

  • 静态变量不属于任何对象,而是属于类本身.所有对象实例共享一份静态变量,当其中一个对象修改静态变量时,其他对象也能立即看到变化.
  • 静态变量的生命周期起始于类加载时,终于类被卸载时,不依赖任何对象.
  • 可以通过类名访问静态变量,无需创建对象实例.

static修饰方法:静态方法

  • 静态方法属于类,不与类的任何实例有关.调用静态方法时,无需创建对象,直接通过类名调用即可.
  • 不能访问非静态成员,因为非静态成员有对象的参与.

静态块

  • 静态代码块:在类加载时执行,只执行一次

7.JVM体系结构

7.1 Java虚拟机规范

JVM是一套规范,可以有多种不同的实现,有各个厂家针对不同业务的具体实现

Run-Time Data Area:运行时数据区:

  1. The pc Register(程序计数器):是一块较小的内存空间,此计数器记录的是正在执行的虚拟机字节码指令的地址;
  2. Java Virtual Machine Stacks(Java虚拟机栈):Java虚拟机栈用于存储栈帧。栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
  3. Heap(堆):是Java虚拟机所管理的最大的一块内存。堆内存用于存放Java对象实例以及数组。堆是垃圾收集器收集垃圾的主要区域。
  4. Method Area(方法区):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  5. Run-Time Constant Pool(运行时常量池):是方法区的一部分,用于存放编译期生成的各种字面量与符号引用。
  6. Native Method Stacks(本地方法栈):在本地方法的执行过程中,会使用到本地方法栈。和 Java 虚拟机栈十分相似。
    在这里插入图片描述

7.2 JDK6的HotSpot

年轻代:刚new出来的对象存放在这里
老年代:经过垃圾回收之后仍然存活的对象
符号引用:类全名,字段全名,方法全名等
这个时期的永久代和堆是相邻的,使用连续的物理内存,但内存空间是隔离的
永久代的垃圾收集和老年代捆绑在一起的,因此无论谁堆满了,都会触发永久代和老年代的垃圾收集
在这里插入图片描述

7.3 JDK7的HotSpot

这是一个过渡的版本,相对于JDK6来说,变化如下:

  1. 类的静态变量转移到堆中
  2. 字符串常量池转移到堆中
  3. 运行时常量池中的符号引用转移到本地内存
    在这里插入图片描述

7.4 JDK8+的HotSpot

以下是JDK8及更高版本的HotSpot,相对于JDK7来说发生了如下变化:

  1. 彻底删除永久代(为了避免OOM错误的发生)
  2. 将方法区的实现转移到本地内存
  3. 将符号引用重新放回运行时常量池

在这里插入图片描述


8.单例模式

8.1 设计模式概述

设计模式是可以重复利用的解决方案 是在软件工程角度的
设计模式有:

  1. GoF设计模式:《Design Patterns: Elements of Reusable Object-Oriented Software》
  2. 架构设计模式(Architectural Pattern)
  3. 企业级设计模式(Enterprise Pattern)
  4. 领域驱动设计模式(Domain Driven Design Pattern)
  5. 并发设计模式(Concurrency Pattern)
  6. 数据访问模式(Data Access Pattern)

GoF设计模式模式的分类:

  1. 创建型:主要解决对象的创建问题
  2. 结构型:通过设计和构建对象之间的关系,以达到更好的重用性,扩展性和灵活性
  3. 行为型:主要用于处理对象之间的算法和责任分配

8.2 饿汉式单例模式

单例模式实现:

  1. 构造方法私有化
  2. 对外提供一个公开的静态方法get,用这个方法获取单个实例
  3. 定义一个静态变量,在类加载的时候,初始化静态变量(只初始化一次)

饿汉式单例模式:类加载时对象就创建好,不管这个对象是否会被使用

public class Singleton {
    private static Singleton s = new Singleton(); // 静态变量在类加载的时候执行
    // 私有的,外界无法访问
    private Singleton() {
    }

    // 实例方法
    public static Singleton get() { // 只允许使用get获取 
    // 这里必须static,因为实例方法必须先new对象才能调用
        return s;
    }
}

饿汉式单例模式直接在类加载的时候就创建,后面调用时只能用get不用new,构造方法设为私有,这样能实现单例模式

8.3 懒汉式单例模式

懒汉式单例模式:用到这个对象的时候再创建对象,不在类加载时候自动创建对象.
实现:

  1. 构造方法私有化
  2. 对外提供一个公开的静态方法,通过这个方法可以获取到Singleton对象
  3. 提供一个静态变量,但是这个对象值为null
public class Singleton {
    private static Singleton s; // 类加载时为空
    private Singleton() {}

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

在这里插入图片描述


9.继承

9.1 继承的基础语法

继承的作用:

  1. 基本作用:代码复用
  2. 重要作用:有了继承,才有了方法覆盖和多态

语法:

[修饰符列表] class 类名 extends 父类名 {}

9.2 单继承与多重继承

java只支持单继承,一个类只能直接继承一个类
java不支持多继承,但支持多层继承
子类继承父类后,除私有的不支持继承,构造方法不支持继承,其他的全部继承
一个类没有显示继承任何类时,默认继承java.lang.Object类


10.方法覆盖

10.1 方法覆盖的基础语法

回顾方法重载 overload:

  1. 什么时候考虑使用方法重载:
    • 在一个类中,如果功能相似,可以考虑使用方法重载
    • 这样做的目的是:代码美观,方便编程
  2. 当满足什么条件的时候构成方法重载
    • 条件1:在同一个类中
    • 条件2:相同的方法名
    • 条件3:不同的参数列表:类型,个数,顺序
  3. 方法重载机制属于编译阶段的功能.(方法重载机制是给编译器看的)

方法覆盖 override /方法重写 overwrite

  1. 什么时候考虑使用方法重写
    • 当从父类中继承过来的方法,无法满足子类的业务需求时
  2. 当满足什么条件的时候 构成方法重写
    • 条件1:方法覆盖发生在具有继承关系的父子类之间
    • 条件2:具有相同的方法名,形参列表,返回值类型

如果有不相同,会变成方法重载而不是覆盖

10.2 方法覆盖的小细节

关于方法覆盖的细节

  1. 当子类将父类方法覆盖之后,将来子类对象调用方法的时候,一定会执行重写之后的方法
  2. 在java语言中,有一个注解@Override,这个注解可以在编译阶段检查这个方法是否是重写了父类的方法.
  3. @Override注解只在编译阶段有用,和运行期无关
  4. 如果返回值类型是引用数据类型,那么这个返回值类型可以是原类型的子类型
  5. 访问权限不能变低,可以变高.
  6. 抛出异常不能变多,可以变少.(异常介绍)
  7. 私有方法和构造方法不能继承,因此它们不存在方法覆盖
  8. 方法覆盖针对的是实例方法,和静态方法无关.(多态介绍)
  9. 方法覆盖针对的是实例方法,和实例变量无关.(多态介绍)

11.多态

多态是整个java技术体系的核心

11.1 向上转型和向下转型

关于基本数据类型之间的类型转换:

  • 第一种:小容量转换成大容量,叫做自动类型转换
  • 第二种:大容量转换成小容量,不能自动转换,必须添加强制类型转换符才可以,叫做强制类型转换

除了基本数据类型之间的类型转换之外,对于引用数据类型也可以进行类型转换.叫做向上转型和向下转型.

关于Java中的向上转型和向下转型:

  • 向上转型(upcasting):子—>父 (类比自动类型转换)
  • 向下转型():父—>子 (类比强制类型转换)
  • 大前提是两种类型必须要有继承关系,编译器才能编译通过
    在这里插入图片描述

向上转型:

  1. 子—>父
  2. 可以等同看做自动类型转换
  3. 前提:两种类型之间有继承关系
  4. 父类型引用指向子类对象,这个就是多态机制最核心的语法
Animal a2 = new Cat();
a2.move(); // 会调用Cat里override的move()方法

// 报错,编译器只知道a2是Animal类型.在Animal中找不到catchMouse()方法
a2.catchMouse(); 

// 假如让a2抓老鼠怎么办 向下转型 downcasting
Cat c2 = (Cat)a2;
c2.catchMouse();

什么时候会考虑使用向下转型:

  • 当调用的方法是子类中特有的方法

java程序包括两个重要的阶段:

  • 第一阶段:编译阶段
    • 在编译的时候,编译器只知道a2的类型是Animal类型.因此会在编译的时候就会去Animal类中找move()方法.找到之后绑定上去,此处发生静态绑定.若能绑定成功,表示编译通过.
  • 第二阶段:运行阶段
    • 在运行的时候,堆内存中真实的java对象是cat类型,所以move()的行为一定是cat对象发生的.因此运行的时候就会自动调用cat对象的move()方法.这种绑定称为运行期绑定/动态绑定.

因为编译阶段是一种形态,运行的时候是另一种形态,因此得名:多态

Animal x = new Cat();
Bird y = (Bird)x; // 编译通过,但运行报错

为什么编译的时候可以通过:

  • 因为x是Animal类型,Animal和Bird之间存在继承关系,语法没有问题,所以编译通过.

为什么运行时会出现ClassCastException:

  • 因为运行时堆中真实对象是Cat类型,Cat和Bird没有继承关系,所以无法转换,所以出现类型转换异常

instanceof

  • instanceof运算符的出现,可以解决ClassCastException异常
  • instanceof运算符的结果一定是:true/false
  • 语法格式: (引用 instanceof 类型)
  • eg:(a instanceof Cat)
    • true:a引用指向的对象是Cat类型
    • false:a引用指向的对象不是Cat类型

做向下转型之前,为了避免ClassCastException的发生,一般建议使用instanceof进行判断

if(x instanceof Bird){
    System.out.println("x是Bird类型");
    Bird y = (Bird)x;
}

11.2 多态的基础语法

java允许具有继承关系的父子类型之间的类型转换.
向上转型(upcasting):子—>父

  • 子类型的对象可以赋值给一个父类型的引用

向下转型(downcasting):父—>子

  • 父类型的引用可以转换为子类型的引用,但需要加强制转换符

无论是向上还是向下转型,前提条件都是两个类型之间必须存在继承关系.

多态:多种形态,编译阶段是一种形态,运行阶段是另一种形态,因此叫做多态

11.3 软件开发的七大原则

  1. 开闭原则 (Open-Closed Principle,OCP):一个软件实体应该对扩展开放,对修改关闭。即在不修改原有代码的基础上,通过添加新的代码来扩展功能。(最基本的原则,其它原则都是为这个原则服务的。
  2. 单一职责原则:一个类只负责单一的职责,也就是一个类只有一个引起它变化的原因。
  3. 里氏替换原则:子类对象可以替换其基类对象出现的任何地方,并且保证原有程序的正确性。
  4. 接口隔离原则:客户端不应该依赖它不需要的接口。
  5. 依赖倒置原则:高层模块不应该依赖底层模块,它们都应该依赖于抽象接口。换言之,面向接口编程。
  6. 迪米特法则:一个对象应该对其它对象保持最少的了解。即一个类应该对自己需要耦合或调用的类知道得最少。
  7. 合成复用原则:尽量使用对象组合和聚合,而不是继承来达到复用的目的。组合和聚合可以在获取外部对象的方法中被调用,是一种运行时关联,而继承则是一种编译时关联。

11.4 多态在开发中的作用

能用多态尽量使用多态,尽量面向抽象编程,不要面向具体编程
面向抽象编程的好处:降低耦合度,提高扩展力

方法覆盖针对的是实例方法,和静态方法无关.(方法的覆盖和多态机制联合起来才有意义)
静态方法被调用时和对象没有关系,和多态无关,多态和底层对象有关

public class Test {
    public static void main(String[] args) {
        // 方法覆盖和多态联合起来才有意义。
        // 多态:父类型引用指向子类型对象。
        // 静态方法本身和多态就是没有关系。因为多态机制需要对象的参与。
        // 静态方法既然和多态没有关系,那么静态方法也就和方法覆盖没有关系了。
        Animal.test();
        Cat.test();

        Animal a = new Cat();
        a.test(); // 静态的方法和对象没有关系

        /**
         * 输出结果:
         *      Animal's test method invoke
         *      Cat's test method invoke    
         *      Animal's test method invoke
        */ 
    }
}

public class Cat extends Animal{
    // 尝试去重写父类的静态方法
    public static void test(){
        System.out.println("Cat's test method invoke");
    }
}

public class Animal {
    public static void test(){
        System.out.println("Animal's test method invoke");
    }
}

方法覆盖针对的是实例方法,和实例变量无关
对于变量,编译时绑定是谁的,运行它的结果就是谁的,实例变量没有多态

public class Test2 {
    public static void main(String[] args) {
        // 多态
        A a = new B();
        // 实例变量不存在覆盖这一说。
        // a.name编译阶段绑定的是A类的name属性,运行的时候也会输出A类的name属性值。
        System.out.println(a.name); // 张三

        // 没有用多态
        B b = new B();
        System.out.println(b.name); // 李四
    }
}

class A {
    // 实例变量
    String name = "张三";
}

class B extends A {
    // 实例变量
    String name = "李四";
}

12.super关键字

this代表的是当前对象,super代表的是当前对象中的父类型特征
this和super都不能使用在静态上下文中,因为都是继承或调用过来的
在这里插入图片描述
“super.”大部分情况下是可以省略的。什么时候不能省略?

  • 当父类和子类中定义了相同的属性(实例变量)或者相同方法(实例方法)时,如果需要在子类中访问父类的属性或方法时,super.不能省略。

子类在构建的时候,会先自动调用父类的构造方法(先有父亲才有儿子)

若要求在父类方法执行的基础上额外再添加一些内容,需要使用"super."方法

// Person.java:父类
public void doSome(){
    System.out.println("人类正在做一些事情");
}

// Teacher.java:子类,继承Person
@Override
public void doSome(){
    System.out.println("doSome方法开始执行");
    super.doSome();
    System.out.println("doSome方法结束执行");
}

this本身是一个引用,所以可以直接输出
super本身不是一个引用,super只是代表了当前父类型特征那部分(不能再往前找父类的父类),不能单独的输出.假设super是一个引用,他也无法输出,因为会存在多重继承,有一连串的父类,无法确定super应该是哪一个父类型.this则很明确是当前本身

作用:

  • 代码复用
  • "模拟"现实世界中先有父亲才能有儿子
  • 通过子类构造方法调用父类构造方法,是为了给继承过来的父类型特征初始化

super(实参); 通过子类的构造方法调用父类的构造方法,目的是为了完成父类型特征的初始化。

当一个构造方法第一行没有显示的调用“super(实参);”,也没有显示的调用“this(实参)”,系统会自动调用super()。因此一个类中的无参数构造方法建议显示的定义出来。

this和super不能共存,因为他们都必须出现在第一行(this(实参)可以在构造方法中调用本类中其他的构造方法.)

在Java语言中只要new对象,Object的无参数构造方法一定会执行。(老祖宗)


13.final关键字

final修饰的类无法被继承
String类就被final修饰,无法继承
final修饰的方法无法被覆盖

final修饰的变量一旦被赋值,就不能再被修改
final修饰的实例变量,必须在构造方法执行完之前手动赋值(实例变量在创建时就会初始化默认值)(没人用,不如直接static)
final修饰的实例变量一般和static联合使用,这就是常量
final修饰的引用,一旦指向某个对象后,不能再指向其他对象,但指向的对象内部的数据是可以修改的(常指针)


14.抽象类

在java中,只要一个方法带着大括号,不管大括号里面有什么有没有,只要有大括号就表示一种实现。
一个类中必有得公共属性,但每个继承的子类实现都不一样,这时就可以定义为抽象方法。(像python中的抽象模板)
定义:修饰符列表添加abstract,不能有方法体,以;结束

public abstract void greet();

当一个类中有抽象方法,java要求该类必须也为抽象类.
一个非抽象的类继承了一个抽象的类之后,必须将抽象类中的所有抽象方法全部重写
抽象类中不一定要有抽象方法,但抽象方法一定要有抽象类.
抽象类有构造方法,但无法实例化.抽象类的构造方法给子类使用
abstract关键字不能和private,final,static关键字共存(抽象类是一定要被实现的).private不能被继承,不能被覆盖.final不能被继承,不能被覆盖.static没有方法覆盖


15.接口

15.1 接口的基础语法

接口(interface)在java中表示一种规范或契约,它定义了一组抽象方法和常量,用来描述实现这个接口的类应该具有哪些行为和特征.接口和类一样,也是一种引用数据类型.
语法:

[修饰符列表] interface 接口名{}
  • 抽象类是半抽象的,接口是完全抽象的.接口没有构造方法,也无法实例化.
  • (JDK8之前的语法规则)接口中只能定义:常量+抽象方法.接口中的常量的static final可以省略.接口中的抽象方法的abstract可以省略.接口中所有的方法和变量都是public的.
  • 接口和接口之间是可以多继承的
interface A{}
interface B{}

interface C extends A,B{}
  • 类和接口的关系叫做实现(类和类之间为继承),使用implements关键字进行接口的实现.
  • 一个非抽象的类实现接口,必须将接口中所有的抽象方法全部实现
  • 使用了接口之后,为了降低程序的耦合度,一定要让接口和多态联合起来使用.
  • 一个类可以实现多个接口,
    • class 类 implements 接口A,接口B {}
  • java8之后,接口中允许出现默认方法和静态方法(JDK8新特性)
    • 默认方法:引入默认方法是为了解决接口演变问题,接口可以定义抽象方法,但不能实现这些方法.所有实现接口的类都必须实现这些抽象方法.这会导致接口的升级问题:当我们向接口添加或删除一个抽象方法时(更新),这会破坏该接口的所有实现,并且所有接口的用户都必须修改其代码才能适应更改.这就是所谓的接口演变问题.
    • 静态方法: JDK8之后,接口可以定义静态方法.但是这个静态方法只能通过"该接口名"去调用.别的方法都无法调用.在JDK8之后引入接口可以定义静态方法,实际上表达一个意思:接口也可以作为工具来使用
  • JDK9之后允许定义私有的实例方法(给实例方法服务)和静态方法(给静态方法服务).
  • 所有接口隐式的继承Object,因此接口也可以调用Object类的相关方法.

15.2 接口的作用

接口的调用者和实现者通过接口达到了解耦合,也就是说调用者不需要关心具体的实现者,实现者也不需要关心具体的调用者,双方都遵循规范,面向接口进行开发.
面向抽象编程,可以降低程序的耦合度,提高程序的扩展力.
eg:电脑并不是直接连接各种设备的,而是通过USB接口.如果没有usb接口,电脑需要为每一个设备都进行单独的连接设计,而设计一个USB公共接口,只要双方都遵循usb设计协议,可以直接连接而不用考虑链接细节.

// 定义一个接口
public interface Usb {
    void read();
    void write();
}

public class Computer {
    public void conn(Usb usb){
        System.out.println("设备连接成功");
        usb.read();
        usb.write();
    }
}

public static void main(String[] args) {
        // HardDrive和Printer类实现了接口
        HardDrive hardDrive = new HardDrive();
        Printer printer = new Printer();
        // 多态
        Computer computer = new Computer();
        computer.conn(hardDrive);
        computer.conn(printer);
    }

15.3 接口和抽象类的选择

抽象类和接口虽然在代码角度都能达到同样的效果,但适用场景不同:

  • 抽象类主要适用于公共代码的提取,当多个类中有共同的属性和方法时,为了达到代码的复用,建议为这几个类提取出一个父类,在该父类中编写公共的代码.如果有一些方法无法在该类中实现,可以延迟到子类实现.这样的类应该使用抽象类.
  • 接口主要用于功能的扩展.例如有很多类,一些类需要这个方法,另外一些类不需要这个方法时,可以将该方法定义到接口中.需要这个方法的类就去实现这个接口,不需要这个方法的就可以不实现这个接口,接口主要规定的是行为.
  • 抽象类用于公共代码,接口用于定义行为

java中,一个类只能单继承另一个类,但可以同时实现多个接口.extends在前,implements在后

向下转型,类也可以转换成接口
类转换成接口的时候,类和接口之间不需要有继承关系,编译器也不会报错.

if(dog instanceof Flyable){
    // Dog类和Flyable接口之间在代码上没有任何继承关系.
    // 但是编译器也让通过了,说明类向下转型为某种接口类型时,不需要有继承关系,编译器也能通过.
    // 但直接 Flyable f = (Flyable)dog;会在运行时报错
    Flyable f = (Flyable)dog;
    f.fly();
}
if(dog instanceof Speakable){
    Speakable s = (Speakable)dog;
    s.speak();
}

16.类之间的关系

16.1 UML

UML(Unified Modeling Language,统一建模语言)是一种用于面向对象软件开发的图形化的建模语言.UML是一种图形化的语言,类似于现实生活中建筑工程师画的建筑图纸,图纸上有特定的符号代表特殊的含义
UML图包括:

  • 类图(Class Diagram):描述软件系统中的类,接口,关系和其属性等;
  • 用例图(Use Case Diagram):描述系统的功能需求和用户与系统之间的关系;
  • 序列图(Sequence Diagram):描述对象之间的交互,消息传递和时序约束等;
  • 状态图(Statechart Digram):描述类或对象的生命周期以及状态之间的转换;
  • 对象图(Object Diagram):表示特定时间的系统状态,并显示其包含的对象及其属性;
  • 协作图(Collaboration Diagram):描述对象之间的协作,表示对象之间相互合作来完成任务的关系;
  • 活动图(Activity Diagram):描述系统的动态行为和流程,包括控制流和对象流;
  • 部署图(Deployment Diagram):描述软件或系统在不同物理设备上部署的情况,包括计算机,网络,中间件,应用程序等.

16.2 类之间的关系

  • 泛化关系(继承关系)(is a)
  • 实现关系(is like a)
  • 并联关系(has a)
  • 聚合关系:一种弱的关联关系,整体和部分的关系.整体和部分分别具有自己的生命周期,空心菱形指向整体
  • 组合关系:整体和部分的关系,但是整体的生命周期决定了部分的生命周期
  • 依赖关系:通常体现在方法参数或者局部变量上

在这里插入图片描述
在这里插入图片描述


17.访问控制权限

在这里插入图片描述

  • private:私有的,只能在本类中访问
  • 缺省:默认的,同一个包下可以访问
  • protected:受保护的,子类中可以访问(受保护的通常就是给子孙用的)
  • public:共同的,在任何位置都可以访问

类中的属性和方法访问权限共有四种:private,缺省,protected,public
类的访问权限只有两种:public和缺省
访问权限控制符不能修饰局部变量(生命周期短,没有也不是缺省)


18.Object类

java.lang.Object是所有类的超类,java中所有类都实现了这个类中的方法.

18.1 toString()方法

  • toString():将java对象转换成字符串的表示形式
public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

默认实现是:完整类名 + @ + 16进制的hashCode,可以等同看做一个java对象的内存地址
当pirntln()输出的是一个引用的时候,会自动调用"引用.toString()"
可以不用继承Object直接重写,因为它是老祖宗

18.2 equals()方法

equals():判断两个对象是否相等

public boolean equals(Object obj) {
        return (this == obj);
    }

java中"=="比较两个变量中保存的值是否相等

int a = 10;
int b = 10;
System.out.println(a == b); // true

Object o1 = new Object();
Object o2 = new Object();
System.out.println(o1 == o2); // false

equal为什么要重写:

因为Object类中的equals()方法比较的是两个对象的地址,new的不同对象地址一定不同,我们希望比较的是对象的内容,只要两个对象内容相同,就认为是相等的.

@Override
public boolean equals(Object obj) {
    // 判断对象是否为空
    if (obj == null) return false;
    // 通过地址判断是不是同一个对象
    if (this == obj) return true; 
    // 先判断类型对不对
    if (obj instanceof Date) {
        // 向下转型
        Date d = (Date) obj;
        return this.year == d.year && this.month == d.month && this.day == d.day;
    }
    return false;
}

Sting字符串的比较不能使用==,必须使用equals()方法进行比较,说明String类重写了equals()方法
equals方法重写需要彻底重写,关联的都要重写(如果包含引用类,引用类自己还要重写equals方法)

18.3 hashCode()方法

hashCode():返回对象的哈希值,通常作为在哈希表中查找该对象的键值.Object类的默认实现是根据对象的内存地址生成一个哈希码(即将对象的内存地址转换为整数作为哈希值).
hashCode()方法是为了HashMap,Hashtable,HashSet等集合类进行优化而设置的,以便更快地查找和存储对象

在Object类中的默认实现:

public native int hashCode();

这是一个本地方法,底层调用了C++写的动态链接库程序:xxx.dll

18.4 finalize()方法(deprecated)

从java9开始,这个方法被标记已过时,不建议使用,作为了解
finalize():当java对象被回收时,由GC自动调用被回收对象的finalize()方法,通常在该方法中完成销毁前的准备

18.5 clone()方法

clone():创建当前对象的一个副本,并返回该副本
默认实现:

protected native Object clone() throws CloneNotSupportedException;

受保护的方法,专门给子类使用的
解决clone()方法的调用问题:

在子类中重写clone()方法
为了保证clone()方法在任何位置都可以调用,建议将其修饰符修改为public

凡是参加克隆的对象,必须实现一个标志接口:Cloneable
java中接口分为两大类,一类起到标志的作用,标志型接口(打开源代码会发现是空代码,这是给JVM虚拟机看的标志).另一类是实现给程序员看的.

public class UserTest(){
    public static void main(String[] args) {
        User user = new User("张三", 18);

        // 报错原因:因为Object类中的clone()方法时protected修饰的
        // protected修饰的类只能在:本类,同包,子类中访问.
        // public class User里面可以clone,UserTest无法clone User里面的
        user.clone(); // 报错
    }
}

clone分为浅克隆和深克隆

// 浅克隆
@Override
public Object clone() throws CloneNotSupportedException {
    return super.clone();
}

// 深克隆
@Override
public Object clone() throws CloneNotSupportedException {
    // User要克隆,User对象关联的Address对象也要克隆一份.
    Address copyAddr = (Address)this.getAddress().clone();
    User copyUser = (User) super.clone();
    copyUser.setAddress(copyAddr);
    return copyUser;
}

19.内部类

内部类:定义在一个类中的类
什么时候使用内部类:

  • 一个类中用到了另一个类,而这两个类的联系比较密切,但如果把这两个类定义为独立的类,不但增加了类的数量,也不利于代码的阅读和维护.
  • 内部类可以访问外部类的私有成员,这样可以将相关的类和接口隐藏在外部类的内部,从而提高封装性.
  • 匿名内部类是指没有名字的内部类,通常用于定义一个只使用一次的类,比如在事件处理中.

19.1 静态内部类

静态内部类:可以把静态内部类当做静态变量来看.
在静态内部类当中,无法直接访问外部类的实例相关的数据

public class OuterClass {
    // 对于静态内部类来说,访问控制权限修饰符在这里都可以使用
    private static class InnerClass {

    }
}

19.2 实例内部类

可以看作实例变量
实例内部类中,可以直接访问外部类中实例成员和静态成员.

public class OuterClass {
    private class InnerClass {

    }
}

19.3 局部内部类

局部内部类:等同于局部变量
局部内部类能不能访问外部类的数据,取决于局部内部类所在的方法.如果这个方法是静态的,只能访问外部类中静态的.如果这个方法是实例的,都可以访问
局部内部类不能使用访问权限修饰符修饰
局部内部类在访问外部的局部变量时,这个局部变量必须是final的.只不过从JDK8开始,这个 final关键字不需要提供了,系统自动提供

public class OuterClass {
    class InnerClass {

    }
}

19.4 匿名内部类

匿名内部类:没有名字的类,只能使用一次

public class Test {
    public static void main(String[] args) {
        Computer computer = new Computer(); 
        // computer.conn(new Printer());

        // 以下conn方法参数上的代码做了两件事:
        // 第一:完成了匿名内部类的定义
        // 第二:同时实例化了一个匿名内部类的对象
        computer.conn(new Usb() {
            // 接口的实现
            @Override
            public void read() {
                System.out.println("读取数据");
            }

            @Override
            public void write() {
                System.out.println("写入数据");
            }
        });
    }
}
  • 31
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值