javase 面向对象
三、面向对象
面向对象概述
面向过程
关注点在实现功能的步骤上
面向对象
关注点在实现功能需要哪些对象的参与,可以把问题拆分成几个对象,对象协作起来解决问题。
面向对象开发方式耦合度低,扩展能力强。
面向对象的三大特征
- 封装
- 继承
- 多态
对象的创建
1 | [修饰符列表] class 类名{ |
JVM内存分析
元空间 metaspace
元空间中存储的是类的元信息,字节码等。
元空间是java8之后引入的。是JVM规范中方法区的实现。
方法区:JVM规范 的叫法,各个厂商根据这个规范去实现具体的java虚拟机。
总结:方法区是规范,元空间是实现。java8之前使用永久代实现的。
堆内存
所有new的对象,都存储在堆内存中。
栈
方法被调用时会给该方法分配空间,在VM Stack中压栈。
JVM有自动垃圾回收机制,主要针对堆区。
实例变量和实例方法
通常描述一个对象的行为动作时,不加static——称为实例方法
实例方法不能通过类访问,必须通过对象访问。
方法调用传递引用数据类型
传的是地址!!!
!this关键字
- this本质是一个引用
- this中保存的是当前对象的内存地址
this.
大部分情况可以省略,默认是访问当前的类的实例变量。当需要区分局部变量和实例变量时,不能省略。- this存储在栈帧的局部变量表的第0个槽位上。
- this不能出现在静态方法中
this 实参
- 通过这种语法,可以在构造方法中调用本类的其他构造方法
- 作用:代码复用
- this实参只能出现在构造方法的第一行!!!
封装
通过限制外部对对象内部的直接访问和修改,保证数据的安全性,并提高了代码的可维护性和可复用性。
- 属性私有化:使用private修饰
- 对外提供接口
*快速创建getter,setter方法
- alt + insert
- 选择getter and setter
- 选择要创建的内容
构造器(构造方法)
- 对象的创建
- 对象的初始化(默认有
super();
,先对父类的变量进行初始化)
这两个阶段不能颠倒,也不能分割。
构造方法名需与类名一致。
不需要写return,不需要写返回值类型。
如果没有显式定义构造方法,系统会提供一个无参数的构造方法,并且会给属性赋默认值。
定义有参数的构造方法后,可以手动再写一个无参数的构造方法。【方法重载】
如何调用构造方法
new 构造方法名(实参)
写了构造方法,为什么还要单独写set方法?
构造方法是对象第一次创建时用于初始化的。set方法可以在后期修改属性值。
构造代码块
语法格式
1 | { } //直接写在class体里面 |
作用
如果所有的构造方法在最开始的时候有相同的一部分代码,可以将公共的代码放在构造代码块中,达到复用的效果。
流程
- new的时候在堆内存中开辟空间,给所有属性赋默认值
- 执行构造代码块进行初始化
- 执行构造方法体进行初始化
- 构造方法执行结束,对象初始化完毕
static 关键字
- static修饰静态变量,当所有对象的某个属性的值是相同的,建议将该属性定义为静态变量,来节省内存。
- JDK8后,静态变量存储在堆内存中。在类加载时进行初始化。
- 静态变量可以通过“
引用.
”来访问,实际运行时和对象无关(不会出现空指针异常),但不建议。会让程序员造成误解。
静态代码块
- 语法格式: static{ }
- 在类加载的时候执行,并且只执行一次。
- 可以有多个静态代码块,自上而下依次执行。
- 作用:在类加载的时候运行一段代码,可能是进行一些准备工作。
java虚拟机规范
运行时数据区的六个内容
- PC Register, PC计数器:是一块较小的内存空间,用于存储下一条要执行的字节码指令地址。
- java Virtual Machine Stacks, java虚拟机栈:用于存储栈帧,栈帧存储局部变量表、操作数栈、动态链接、方法出口等信息。
- Heap, 堆:java虚拟机所管理的最大的一块内存,用于存储java对象实例以及数组。堆是垃圾回收器主要使用区域。
- Method Area, 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量(hotspot把这个内容存到堆里去了)、即时编译器编译后的代码等数据。
- Run-Time Constant Pool, 常量池:方法区的一部分,用于存放编译期生成的各种字面量与符号引用(类名、方法名、属性名)。
- method stacks, 本地方法栈:在本地方法的执行过程中,会使用本地方法栈。
GoF设计模式
什么是设计模式
可以重复利用的一套方案
GoF设计模式的分类
- 创建型:主要解决对象的创建问题
- 结构型:通过设计和构建对象之间的关系,以达到更好的重用性、扩展性和灵活性
- 行为型:主要用于处理对象之间的算法和责任分配
单例模式
属于创造型设计模式,确保一个类只有一个实例,并提供一个全局访问点来访问该实例。
饿汉式单例模式
类加载时对象就创建好了,不管这个对象用还是不用
- 构造方法私有化
- 定义一个静态变量,在类加载的时候初始化静态变量(只初始化一次)
- 对外提供一个公开的静态方法,用这个方法获取单个实例
懒汉式单例模式
用到这个对象的时候再创建对象,别在类加载的时候创建对象
- 构造方法私有化
- 提供一个静态变量,但这个变量的值为NULL
- 对外提供一个静态方法,通过这个方法可以获取对象
继承
作用
- 代码复用
- 有了继承,才有了方法覆盖和多态机制
实现
1 | [修饰符列表] class 类名 extends 父类名 { |
特性
- 只支持单继承,一个类只能继承一个类
- 不支持多继承,但支持多重继承(多层继承)
- 子类继承父类的除私有的、构造方法以外的所有内容
- 一个类没有显式继承任何类时,默认继承java.lang.Object类
方法覆盖(重写) overwrite
什么时候使用?
当从父类继承过来的方法,无法满足子类的业务需求时。
特性
- 当子类将父类方法覆盖之后,将来子类对象调用方法的时候,一定会执行重写之后的方法。
- 注解:@override,在方法前写这个注解,在编译阶段会检查这个方法是否重写了父类的方法。
- 如果返回值类型是引用数据类型,那么这个返回值类型可以是原类型的子类型。
- 访问权限不能变低,可以变高。public最高
- 抛出异常不能变多,可以变少。
- 方法覆盖针对的是实例方法,和静态方法无关。
多态
向上转型和向下转型的基本概念
引用数据类型进行类型转换。
向上转型:子–>父 (可以等同看做自动类型转换)
父类型引用指向子类型对象,这是多态机制最核心的语法。
如果父类中没有某个方法,而子类中有,那么就需要向下转型。
向下转型:父–>子(可以等同看做强制类型转换)
当调用的方法是子类特有的方法,需要向下转型,进行强制转换。
如果两个子类不是同一类,会出现ClassCastException异常
如何避免ClassCastException异常?
使用运算符 instanceof
语法格式:
引用 instanceof 类型
在进行向下转型之前,用if语句判断一下是否是要向下转型的类型,不是就不要转换了。
静态方法和多态没有关系,因此静态方法和方法覆盖无关系。
软件开发七大原则
多态在开发中的作用
- 降低程序耦合度,提高程序的扩展力
- 尽量使用多态,面向抽象编程,不要面向具体编程。
实例变量无法覆盖,根据声明的类型进行赋值
抽象类和抽象方法
1 | public abstract class Name{ //父类:所有子类的公共属性+公共方法的集合体 |
存在的意义:强制子类重写抽象方法,编译器会报错。如果类中有一些方法无法实现或者没有意义,就可以将方法定义为抽象方法。
- abstract 关键字不能和private、final、static关键字共存
super 关键字
- 当子类和父类有名称相同的属性/方法,此时调用父类中继承而来的属性/方法需要使用
super.属性/方法
- super不能在静态方法中使用
- this可以单独输出(本质是引用,内容是地址),super不能单独输出(本质不是引用,只是代表了对象父类型特征的那部分)
如何在子类中在使用父类方法的基础上进行方法覆盖?
按正常方法覆盖,但是方法体中先写一个super.方法名()
调用一下父类的方法,再写需要添加的内容。
在子类中调用父类的构造方法
在子类的构造方法中使用super(参数);
通过此方法可以给继承过来的父类特征进行初始化,达到代码复用。
final 关键字
- final修饰的类不可以被继承
- final修饰的方法无法被覆盖
- final修饰的变量一旦赋值,不能重新赋值
- final修饰的实例变量必须在构造方法执行完之前手动赋值。这种变量一般和static联用,得到常量(单词全部大写,每个单词用_连接)
- final修饰的引用一旦指向某个对象,不能再指向其他对象。但指向的对象内部的数据可以修改。
接口
要想解耦合,就是多态+接口
接口在Java中表示一种规范或契约,它定义了一组抽象方法和常量,用来描述一些实现这个接口的类应该具有哪些行为和属性。接口和类一样,也是一种引用数据类型。
分类
- 普通接口
- 起标志的作用
如何定义
[修饰符列表] interface 接口名{}
接口是完全抽象的
抽象类是半抽象的(可以定义抽象的方法,也可以定义非抽象的方法)
接口是完全抽象的,没有构造方法,也无法实例化。
JDK8之前的语法规则
接口中只能定义:常量+抽象方法
接口中的常量的static final可以省略,抽象方法的abstract可以省略。
所有方法和变量都是public的
接口与接口之间可以多继承
类和接口的关系——实现
这里的实现可以等同看做继承。(接口是父,类是子) 这个说法仅供理解
使用implements关键字进行接口的实现。
一个非抽象的类实现接口必须将接口中所有抽象方法全部实现(否则编译器报错)
一个类可以实现多个接口
class 类名 implements 接口A,接口B{ }
使用了接口之后,为了降低程序的耦合度,一定要让接口和多态联合起来使用
父类型的引用指向子类型的对象。
JDK8后,接口中允许出现默认方法和静态方法
默认方法
引入默认方法是为了演变接口演变问题。
接口可以定义抽象方法,但不能实现这些方法。所有实现接口的类都必须实现这些抽象方法,这会导致接口升级问题——当我们向接口添加或删除一个抽象方法时,这会破坏该接口的所有实现,所有与它有关的类都需要修改代码。
覆盖会使用静态方法
只能通过接口名去调用
通常将接口作为工具使用时,会使用静态方法
JDK9之后允许定义私有实例方法(为默认方法服务)和私有静态方法(为静态方法服务)
私有静态方法便于将静态方法拆分为多个方法,免得一个方法中有几千行。便于代码复用
所有接口隐式继承object,因此接口也可以调用object类的相关方法
接口的作用
- 调用者和实现者通过接口达到了解耦合。调用者不需要关心具体的实现者,实现者也不许要关心具体的调用者,双方只要遵循规范,面向接口进行开发。
- 面向抽象编程,面向接口编程,可以降低程序的耦合度,提高程序的扩展力。
接口和抽象类的选择
- 抽象类主要用于公共代码的提取。多个类有共同的属性和方法时,提取出一个父类。
- 接口主要用于功能的扩展。有一些类需要实现某个方法,另一些类不需要,那就将这个方法定义到接口中,需要这个方法的就去实现这个接口。
一个类单继承父类,多实现接口
extends在前,implements在后
UML 统一建模语言
类之间的六种关系
聚合关系:整体和部分各自有自己的生命周期
组合关系:整体和部分有相同的生命周期。eg.人死了,四肢也没了。
三个比较重要的关系
l
其他的关系
聚合关系
组合关系
依赖关系
访问控制权限
- 类的访问权限只有两种:public和缺省
- 访问权限控制符不能修饰局部变量
Object类
toString :将java对象转换成字符串型
但默认的方法输出的是地址,因此需要自己覆盖方法。
调用print()
打印类时,会自动调用类的toString()
(和自己调用toString方法有区别,因为它会先判断是否是NULL,再使用toString)
equal :判断两个对象是否相等
默认方法是判断地址是否相等,也需要自己重写方法。
hashCode :返回一个对象的哈希值
通常用来在哈希表中查找该对象的键值。
默认实现是根据对象的内存地址生成一个哈希码(将对象的内存地址转换为整数作为哈希值)。
该方法是为了HashMap、Hashtable、HashSet等集合类进行优化而设置的,便于更快地查找和存储对象。
clone
- 实现对象拷贝。通常在开发中需要保护原对象数据结构,于是克隆出一份新对象,对新对象进行操作。
- 默认实现:是protected类型,专门给子类使用的。(本地方法,调用C++程序实现的)【浅克隆】
- 怎么解决克隆方法调用问题?—— 在子类中重写clone方法,并且为了保证clone方法在任何位置都可以调用,建议将其修饰符修改为public
- 凡是参加克隆的对象,必须实现一个标志接口:
java.lang.Cloneable
需要重写变成【深克隆】
就是先完成浅克隆,再单独克隆其中包含的类,然后赋值给克隆出来的东西。
eg.
内部类
什么是内部类
定义在一个类中的类
什么时候使用内部类
四种内部类
匿名内部类: