面向对象编程
什么是面向对象
- 面向对象编程 (OOP, Object-Oriented Programming)
- 面向对象的本质就是以类的方式组织代码,以对象的方式组织(封装)数据
- 抽象
- 三大特性
- 封装
- 继承
- 多态
类与对象的关系
类是一种抽象的数据类型,它是对某一类事物的整体描述/定义,但是并不能代表某一个具体的事物
对象是抽象概念的具体实例
从认识论的角度是先有对象后有类。对象是具体的事物,类是对对象的抽象。
从代码运行角度是先有类后有对象。类是对象的模板。
创建与初始化对象
使用new关键字创建对象
创建对象除了会分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用。
构造器详解
类中的构造器也称为构造方法,创建对象的时候必定会被调用。
使用new关键字,本质是在调用构造器;
一个类中即使什么内容都不写,它也会存在一个构造方法;
构造器的两个特点:
- 必须和类的名字相同
- 必须没有返回类型,也不能写void
一旦定义了有参数的构造方法,无参构造方法必须显式定义才会有效
创建对象的内存分析
1 | package com.hunter.oop; |
1 | package com.hunter.oop; |
## 封装
程序设计要追求高内聚、低耦合
- 高内聚:尽可能类的每个方法只完成一件事
- 低耦合:尽可能减少类的中的方法调用其他方法
从类的角度看:减少类内部对其他类的调用
从功能模块角度看:减少模块之间的交互复杂度
封装
封装就是对方法的实现细节进行隐藏。它是一种防止外界调用端去访问对象内部实现细节的手段。
属性私有,提供公开接入的方法(getters/setters)
代码更容易理解与维护,同时加强了代码的安全性。
继承
继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。
子类是父类的扩展(extends)
- 子类继承了父类,就会拥有父类的全部方法和属性(private除外)
- 被
final
修饰的类无法被继承
继承是类和类之间的一种关系,此外还有依赖、组合、聚合等
Object类
在Java中,所有的类都默认直接或间接继承Object类。
Java类只有单继承,没有多继承
一个子类只能有一个父类
super
super();
调用父类的构造器,必须要在子类构造器的第一行。
如果父类定义了有参数的构造方法,且没有显式定义无参构造方法,那么子类就不能定义无参构造。因此定义有参构造方法之前,最好先显式定义无参构造。
super只能出现在子类的方法或构造方法中
不能同时用
super
和this
调用构造方法因为两者都要求自己在第一行,会冲突。
super和this的比较
this表示本身调用者这个对象
super表示父类对象,只能在继承后使用
构造方法
this()
:本类的构造方法,可以简化代码的编写super()
:父类的构造方法
重写 Override
重写需要有继承关系,是子类对父类非静态的方法进行重写,与属性无关。
方法名必须相同
参数列表必须相同
修饰符:范围可以扩大,但不能缩小
public > protected > default > private
抛出的异常:范围可以缩小,但不能扩大
Exception > ClassNotFoundException
静态方法和非静态方法区别很大
静态方法:方法的调用只和左边定义的数据类型有关(不会被重写)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public class A {
public static void test() {
System.out.println("A.test()")
}
}
public class B extends A {
public static void test() {
System.out.println("B.test()");
}
}
public class Application {
public static void main(String[] args) {
B b = new B();
b.test(); // 打印 B.test()
A a = new B();
a.test(); // 打印 A.test()
}
}非静态方法:重写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public class A {
public void test() {
System.out.println("A.test()")
}
}
public class B extends A {
public void test() {
System.out.println("B.test()");
}
}
public class Application {
public static void main(String[] args) {
B b = new B();
b.test(); // 打印 B.test()
A a = new B();
a.test(); // 打印 B.test()
}
}
为什么需要重写
父类的功能子类不一定需要或不一定满足
多态
多态就是同一方法可以根据发送对象的不同而采用多种不同的行为方式
一个对象的实际类型是确定的,但指向的引用类型可以不确定。
1
2
3
4
5
6
7// Student extends Person
Student s1 = new Student();
Person s2 = new Student(); // 父类可以指向子类,但不能调用子类独有的方法
Object s3 = new Student();
s2.run(); // 如果子类重写了父类的方法,会执行子类的方法
((Student) s2).eat(); // 如果是子类有的,而父类没有的方法,必须进行强制类型转换一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多
多态存在的条件
- 有继承关系
- 子类重写了父类方法
- 父类引用指向子类对象
Father f = new Son();
多态是方法的多态,属性没有多态
- 静态方法(static)的调用只和左边定义的数据类型有关
- 常量(final)的调用也只和左边定义的数据类型有关
- private方法的调用也只和左边定义的数据类型有关
关键词 instanceof
关键词instanceof
用于判断一个对象是什么类型
1 | // Object > String |
System.out.println(X instanceof Y);
语句能否编译通过要看X与Y之间是否有继承关系。
引用类型的类型转换
1 | Person obj = new Student(); |
- 引用类型的类型转换可能会丢失自己本来的一些方法:
- 子类转换为父类是向上转型
- 父类转换为子类为向下转型,属于强制转换
- 引用类型的类型转换是为了方便方法的调用,减少代码冗余
static关键字详解
代码块
1 | publica class ClassName { |
静态代码块
加载类时就执行(只执行一次)
匿名代码块
- 创建对象时自动创建,且在构造器之前创建
- 运用场景:赋一些初始值
静态导入包
为了方便,想不加类名来使用工具类的静态方法,需要通过静态(static)导入包来实现:
1 | import static java.lang.Math.random; |
抽象类
abstract
修饰符可以修饰方法,也可以修饰类。如果修饰方法,方法就是抽象方法;如果修饰类,类就是抽象类。
抽象类中可以没有抽象方法,但有抽象方法的类一定要声明为抽象类
抽象类中可以有普通的方法
抽象类不能用
new
关键字来创建对象,它是用来让子类继承的抽象类不能被创建对象,但有构造器。那么构造方法的作用是什么?
抽象类的构造方法可以用来初始化抽象类内部声明的通用变量,并被各种实现使用。
抽象方法
只有方法的声明,没有方法的实现,实现交由子类来实现。
子类继承抽象类,就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类
接口
普通类:只有具体实现
抽象类:具体实现和**规范(抽象方法)**都有
接口:只有规范,自己无法写方法。使约束和实现分离,面向接口编程。
声明接口的关键字是
interface
接口就是规范,定义的是一组规则,让不同的人实现。
OO(Object-Oriented)的精髓就是对对象的抽象,最能体现这一点的就是接口。
设计模式所研究的,实际上就是如何合理地去抽象。
接口中定义属性和方法
属性是常量
public static final
,但一般不会在接口中定义属性;方法是抽象的public abstract
。因此接口中定义的属性和方法的修饰符可以省略。public interface UserService { // 接口中的所有定义的方法其实都是抽象的 public abstract,因此修饰符可省略 void run(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- **类只能单继承,但接口可以多继承(可以实现多个接口)**
- 类通过关键字`implements`**实现接口**。**实现了接口的类,必须重写接口中的方法**。
- **实现接口的类的命名**通常以`Impl`结尾。
```java
// 类可以通过implements实现多个接口
public class UserServiceImpl implements UserService, TimeService {
@Override
public void add(String name) {
}
@Override
public void timer() {
}
}接口中没有构造方法(抽象类有构造方法)
内部类
- 内部类就是在一个类的内部再定义一个类
成员内部类
1 | public class Outer { |
静态内部类
1 | public class Outer { |
局部内部类
局部内部类位于方法中
1 | public class Outer { |
匿名内部类
没有类的名称,必须借助接口或者父类。
1 | public class Test { |
一个Java类中可以有多个class类,但只能有一个
public class
1
2
3
4
5
6
7
8
9
10
11public class Outer {
}
// 可以作为测试类,对public class类进行测试
class A {
public static void main(String[] args) {
}
}
常用API
Object 类
概述
java.lang.Object
类是Java语言中所有类的父类,其中描述的所有方法,子类都可以使用。如果一个类没有特别指定父类,则默认继承自Object类。
toString 方法
直接打印对象的变量名,其实就是调用toString方法(对象在堆内存中的地址值)。但是Object默认的toString方法获取对象的地址值其实没有多少意义和可读性。
自定义的类,一般会重写toString方法,获取对象的属性。
equals 方法
默认地址比较
Object默认的equals方法为:使用==
运算符比较对象地址,只要不是同一个对象,结果必然为false。
对象内容比较
如果希望进行对象的内容比较,则可以重写equals方法。equals方法隐含着一个多态(传入的是Object类的对象):
1 | public boolean equals(Object obj) {...} |
- 可选:
- 添加一个判断,传递的参数obj如果是this本身,直接返回true,提高程序效率
- 添加一个判断,传递的参数obj如果是null,直接返回false,提高程序效率
- 添加一个判断,防止类型转换异常ClassCastException
- 使用强制类型转换,把obj类转换成需要的类型
- 再比较两个对象的属性
1 | //idea自动生成的equals方法的重写 |
clone 方法
clone方法可以实现对象的复制。clone
方法要求子类实现java.lang.Clonable
接口,从而在子类中实现对象的复制。
浅克隆:对象在复制时仅复制基本类型的属性值到新对象中,引用的变量不会被复制。
深度克隆:不仅复制基本类型的属性值到新对象中,引用的变量本身也会被复制。
实现深度克隆
要实现深度克隆,需要重写clone方法。
Objects 类
JDK 1.7添加了一个java.util.Objects
工具类,它提供了一些方法来操作对象,这些方法是null-save(空指针安全)的,用于计算对象的hashcode、返回对象的字符串表示形式、比较两个对象。
在比较两个对象的时候,Object的equals方法容易抛出空指针异常,而Objects类中的equals方法就优化了这个问题:
1 | public static boolean equals(Object a, Object b) { |
日期时间类
Date 类
java.util.Date
类表示特定的瞬间,精确到毫秒。
- 构造方法:
public Date()
public Date(long date)
:分配Date对象并初始化此对象,以表示自从标准基准时间(1970年1月1日00:00:00 GMT)以来的指定毫秒数。
简单来说,使用无参构造,可以自动设置当前系统时间的毫秒时刻,指定long类型可以自定义毫秒时刻;输出Date类时,会自动转换为日期格式。
成员方法:
long getTime()
:把当对象表示的时间转换为毫秒值
DateFormat类
java.text.DateFormat
是日期/时间格式化子类的抽象类,可以通过这个类完成日期和文本之间的转换,即在Date对象和String对象之间来回转换。
格式化
按照指定的格式,从Date对象转换为String对象;
String format(Date date)
;解析
按照指定的格式,从String对象转换为Date对象;
Date parse(String source)
构造方法
由于DateFormat为抽象类,因此需要常用的子类java.text.SimpleDateFormat
。这个类需要指定格式化或解析的格式。
构造方法为:public SimpleDateFormat(String pattern)
。
参数pattern是一个字符串,代表日期时间的自定义格式,常用的格式规则如下:
标识字母(区分大小写) | 含义 |
---|---|
y | 年 |
M | 月 |
d | 日 |
H | 时 |
m | 分 |
s | 秒 |
格式中的字母不能更改,但连接的符号可以改变。
1 | // 1. 创建SimpleDateFormat对象,构造方法中传递指定的格式 |
格式化
调用SimpleDateFormat对象中的format方法,按照构造方法中指定的模式,把Date日期格式化为符合模式的字符串
1
2
3Date date = new Date();
String d = sdf.format(date);
System.out.println(d);解析
调用SimpleDateFormat对象中的parse方法,把符合构造方法中的格式的字符串解析为Date对象。
注意:parse方法声明了一个ParseExceptoion,如果字符串和构造方法的格式不一样,就会抛出异常。
1
2Date date = sdf.parse("2021/07/31 22:26:33");
System.out.println(date);
Calendar 类
java.util.Calendar
在Date类之后出现,是一个抽象类,替换掉了很多Date的方法。该类将所有可能用到的时间信息封装为静态成员变量,方便获取。
字段值 | 含义 |
---|---|
YEAR | 年 |
MONTH | 月(0-11) |
WEEK_OF_YEAR | 一年中的第几周 |
WEEK_OF_MONTH | 一个月中的第几周 |
DATE DAY_OF_MONTH |
日 |
DAY_OF_WEEK | 一周中的第几天 |
DAY_OF_YEAR | 一年中的第几天 |
HOUR | 时 |
MINUTE | 分 |
SECOND | 秒 |
MILLISECOND | 毫秒 |
获取方法
Calendar类无法直接创建对象使用,它有一个静态方法getInstance()
,该方法使用默认时区和语言环境返回Calendar类的子类对象。
常用方法
public int get(int field)
:返回给定日历字段的值(参数使用Calendar类的静态成员变量)public void set(int field, int value)
:将给定的日历字段设置为给定值public void set(int year, int month, int day)
:同时设置年月日public abstract void add(int field, int amount)
:根据日历的规则,为给定的日历字段添加或减去指定的时间量public Date getTime()
:返回一个表示此Calendar时间值(从历元到现在的毫秒偏移量)的Date对象
1 | Calendar c = new Calendar.getInstance(); |
Math 类
BigInteger 类
BigDecimal 类
System 类
java.lang.system
类中提供了大量的静态方法,可以获取与系统相关的信息或系统级操作,在System类的API文档中,常用的方法有:
public static long currentTimeMills()
:返回以毫秒为单位的当前时间(和DATE类能达到相同的效果),可以用来测试程序的效率public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
:将数组中指定的数据拷贝到另一个数组中- src 源数组
- srcPos 起始位置
- dest 目标数组
- destPos 目标数组中的起始位置
- length 要复制的数组长度
currentTimeMills 方法
1 | long s = System.currentTimeMills(); |
arraycopy 方法
1 | int[] src = {1, 2, 3, 4, 5}; |
StringBuilder 类
又称为字符串缓冲区,可以提高字符串的操作效率(看作一个长度可变的字符串)。底层是一个初始长度为16的数组,但是没有被final修饰,因此长度可变。
构造方法
public StringBuilder()
:构造一个空的StringBuilder容器public StringBuilder(String str)
:构造一个StringBuilder容器,并将字符串添加进去
常用方法
public StringBuilder append(...)
:添加任意类型数据,会将其内容转换为字符串形式,并返回当前对象自身,因此无需接收返回值public String toString()
:将当前StringBuilder对象转换为String对象