面向对象
# 面向对象
# 1. 类和对象
# 1.1 类和对象的理解
客观存在的事物皆为对象 ,所以我们也常常说万物皆对象。
- 类
- 类的理解
- 类是对现实生活中一类具有共同属性和行为的事物的抽象
- 类是对象的数据类型,类是具有相同属性和行为的一组对象的集合
- 简单理解:类就是对现实事物的一种描述
- 类的组成
- 属性:指事物的特征,例如:手机事物(品牌,价格,尺寸)
- 行为:指事物能执行的操作,例如:手机事物(打电话,发短信)
- 类的理解
- 类和对象的关系
- 类:类是对现实生活中一类具有共同属性和行为的事物的抽象
- 对象:是能够看得到摸的着的真实存在的实体
- 简单理解:类是对事物的一种描述,对象则为具体存在的事物
# 1.2 类的定义
类的组成是由属性和行为两部分组成
- 属性:在类中通过成员变量来体现(类中方法外的变量)
- 行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)
类的定义步骤:
①定义类
②编写类的成员变量
③编写类的成员方法
public class 类名 {
// 成员变量
变量1的数据类型 变量1;
变量2的数据类型 变量2;
…
// 成员方法
方法1;
方法2;
}
2
3
4
5
6
7
8
9
示例代码:
/*
手机类:
类名:
手机(Phone)
成员变量:
品牌(brand)
价格(price)
成员方法:
打电话(call)
发短信(sendMessage)
*/
public class Phone {
//成员变量
String brand;
int price;
//成员方法
public void call() {
System.out.println("打电话");
}
public void sendMessage() {
System.out.println("发短信");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 1.3 对象的使用
- 创建对象的格式:
- 类名 对象名 = new 类名();
- 调用成员的格式:
- 对象名.成员变量
- 对象名.成员方法();
- 示例代码
/*
创建对象
格式:类名 对象名 = new 类名();
范例:Phone p = new Phone();
使用对象
1:使用成员变量
格式:对象名.变量名
范例:p.brand
2:使用成员方法
格式:对象名.方法名()
范例:p.call()
*/
public class PhoneDemo {
public static void main(String[] args) {
//创建对象
Phone p = new Phone();
//使用成员变量
System.out.println(p.brand);
System.out.println(p.price);
p.brand = "小米";
p.price = 2999;
System.out.println(p.brand);
System.out.println(p.price);
//使用成员方法
p.call();
p.sendMessage();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 1.4 学生对象-练习
- 需求:首先定义一个学生类,然后定义一个学生测试类,在学生测试类中通过对象完成成员变量和成员方法的使用
- 分析:
- 成员变量:姓名,年龄…
- 成员方法:学习,做作业…
- 示例代码:
class Student {
//成员变量
String name;
int age;
//成员方法
public void study() {
System.out.println("好好学习,天天向上");
}
public void doHomework() {
System.out.println("键盘敲烂,月薪过万");
}
}
/*
学生测试类
*/
public class StudentDemo {
public static void main(String[] args) {
//创建对象
Student s = new Student();
//使用对象
System.out.println(s.name + "," + s.age);
s.name = "林青霞";
s.age = 30;
System.out.println(s.name + "," + s.age);
s.study();
s.doHomework();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 2. 对象内存图
# 2.1 单个对象内存图
- 成员变量使用过程
- 成员方法调用过程
# 2.2 多个对象内存图
- 成员变量使用过程
- 成员方法调用过程
总结:
多个对象在堆内存中,都有不同的内存划分,成员变量存储在各自的内存区域中,成员方法多个对象共用的一份
# 2.3 多个对象指向相同内存图
总结
当多个对象的引用指向同一个内存空间(变量所记录的地址值是一样的)
只要有任何一个对象修改了内存中的数据,随后,无论使用哪一个对象进行数据获取,都是修改后的数据。
# 3. 成员变量和局部变量
# 3.1 成员变量和局部变量的区别
- 类中位置不同:成员变量(类中方法外)局部变量(方法内部或方法声明上)
- 内存中位置不同:成员变量(堆内存)局部变量(栈内存)
- 生命周期不同:成员变量(随着对象的存在而存在,随着对象的消失而消失)局部变量(随着方法的调用而存在,醉着方法的调用完毕而消失)
- 初始化值不同:成员变量(有默认初始化值)局部变量(没有默认初始化值,必须先定义,赋值才能使用)
# 4. 封装
# 4.1 private关键字
private是一个修饰符,可以用来修饰成员(成员变量,成员方法)
被private修饰的成员,只能在本类进行访问,针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作
- 提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
- 提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰
示例代码:
/* 学生类 */ class Student { //成员变量 String name; private int age; //提供get/set方法 public void setAge(int a) { if(a<0 || a>120) { System.out.println("你给的年龄有误"); } else { age = a; } } public int getAge() { return age; } //成员方法 public void show() { System.out.println(name + "," + age); } } /* 学生测试类 */ public class StudentDemo { public static void main(String[] args) { //创建对象 Student s = new Student(); //给成员变量赋值 s.name = "林青霞"; s.setAge(30); //调用show方法 s.show(); } }
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 4.2 private的使用
需求:定义标准的学生类,要求name和age使用private修饰,并提供set和get方法以及便于显示数据的show方法,测试类中创建对象并使用,最终控制台输出 林青霞,30
示例代码:
/* 学生类 */ class Student { //成员变量 private String name; private int age; //get/set方法 public void setName(String n) { name = n; } public String getName() { return name; } public void setAge(int a) { age = a; } public int getAge() { return age; } public void show() { System.out.println(name + "," + age); } } /* 学生测试类 */ public class StudentDemo { public static void main(String[] args) { //创建对象 Student s = new Student(); //使用set方法给成员变量赋值 s.setName("林青霞"); s.setAge(30); s.show(); //使用get方法获取成员变量的值 System.out.println(s.getName() + "---" + s.getAge()); System.out.println(s.getName() + "," + s.getAge()); } }
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 4.3 this关键字
- this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)
- 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
- 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
public class Student {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void show() {
System.out.println(name + "," + age);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 4.4 this内存原理
this代表当前调用方法的引用,哪个对象调用的方法,this就代表哪一个对象
示例代码:
public class StudentDemo { public static void main(String[] args) { Student s1 = new Student(); s1.setName("林青霞"); Student s2 = new Student(); s2.setName("张曼玉"); } }
1
2
3
4
5
6
7
8图解:
# 4.5 封装思想
- 封装概述 是面向对象三大特征之一(封装,继承,多态) 是面向对象编程语言对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界是无法直接操作的
- 封装原则 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问 成员变量private,提供对应的getXxx()/setXxx()方法
- 封装好处 通过方法来控制成员变量的操作,提高了代码的安全性 把代码用方法进行封装,提高了代码的复用性
# 5. 对象的创建
在Java中,万事万物都是对象。这句话相信你一定不陌生,尽管一切都看作是对象,但是你操纵的却 是一个对象的引用(reference)
。在这里有一个很形象的比喻:你可以把车钥匙和车看作是一组对象 引用和对象的组合。当你想要开车的时候,你首先需要拿出车钥匙点击开锁的选项,停车时,你需要点 击加锁来锁车。车钥匙相当于就是引用,车就是对象,由车钥匙来驱动车的加锁和开锁。并且,即使没 有车的存在,车钥匙也是一个独立存在的实体,也就是说,你有一个对象引用,但你不一定需要一个对 象与之关联,也就是
Car carKey
这里创建的只是引用,而并非对象,但是如果你想要使用S这个引用时,会返回一个异常,告诉你需要 一个对象来和这个引用进行关联。一种安全的做法是,在创建对象引用时同时把一个对象赋给它。
Car carKey = new Car();
在Java中,一旦创建了一个引用,就希望它能与一个新的对象进行关联,通常使用new操作符来实 现这一目的。new的意思是,给我一个新对象,如果你不想相亲,自己new 一个对象就好了。祝你 下辈子幸福。
# 6. 属性和方法
类一个最基本的要素就是有属性
和方法
。
- 属性
属性也被称为字段,它是类的重要组成部分,属性可以是任意类型的对象,也可以是基本数据类型。例 如下
class A{
int a;
Apple apple;
}
2
3
4
- 方法
类中还应该包括方法,方法表示的是做某些事情的方式。方法其实就是函数,只不过Java习惯把函数 称为方法。这种叫法也体现了面向对象的概念。
**方法的基本组成包括方法名称
、参数
、返回值
和方法体
,**下面是它的示例
public int getResult(){
// ...
return 1;
}
2
3
4
其中,getResult就是方法名称、()里面表示方法接收的参数、return表示方法的返回值,
注意:方法的返回值必须和方法的参数类型保持一致。有一种特殊的参数类型void表示方法无返 回值。{}包含的代码段被称为方法体。
# 6.1 构造方法
在Java中,有一种特殊的方法被称为构造方法
,也被称为构造函数
、构造器等。在Java中,通过 提供这个构造器,来确保每个对象都被初始化。构造方法只能在对象的创建时期调用一次,保证了对象 初始化的进行。构造方法比较特殊,它没有参数类型和返回值,它的名称要和类名保持一致,并且构造 方法可以有多个,下面是一个构造方法的示例
class Apple {
int sum;
String color;
public Apple(){}
public Apple(int sum){}
public Apple(String color){}
public Apple(int sum,String color){}
}
2
3
4
5
6
7
8
9
10
11
有参构造、无参构造函数
上面定义了一个Apple类,你会发现这个Apple类没有参数类型和返回值,并且有多个以Apple同名 的方法,而且各个Apple的参数列表都不一样,这其实是一种多态的体现,我们后面会说。在定义完成 构造方法后,我们就能够创建Apple对象了。
class createApple {
public static void main(String[] args) {
Apple apple1 = new Apple();
Apple apple2 = new Apple(1);
Apple apple3 = new Apple("red");
Apple apple4 = new Apple(2,"color");
}
}
2
3
4
5
6
7
8
# 默认的构造方法
如上面所示,我们定义了四个Apple对象,并调用了 Apple的四种不同的构造方法,其中,不加任何参数的构造方法被称为默认的构造方法,也就是
Apple apple1 = new Apple();
- 如果类中没有定义任何构造方法,那么JVM会为你自动生成一个构造方法,如下
class Apple {
int sum;
String color;
}
class createApple {
public static void main(String[] args) {
Apple apple1 = new Apple();
}
}
2
3
4
5
6
7
8
9
上面代码不会发生编译错误,因为Apple对象包含了一个默认的构造方法。
默认的构造方法也被称为默认构造器或者无参构造器。
这里需要注意一点的是,即使JVM会为你默认添加一个无参的构造器,但是如果你手动定义了任何一 个构造方法,JVM就不再为你提供默认的构造器,你必须手动指定,否则会出现编译错误。
显示的错误是,必须提供Apple带有int参数的构造函数,而默认的无参构造函数没有被允许使用。
# 6.2 方法重载
在Java中一个很重要的概念是方法的重载,它是类名的不同表现形式。我们上面说到了构造函数,其实构造函数也是重载的一种。另外一种就是方法的重载
public class Apple {
int sum;
String color;
public Apple(){}
public Apple(int sum){}
public int getApple(int num){
return 1;
}
public String getApple(String color){
return "color";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
如上面所示,就有两种重载的方式,一种是Apple构造函数的重载,一种是getApple方法的重载。
但是这样就涉及到一个问题,要是有几个相同的名字,Java如何知道你调用的是哪个方法呢?
这里记住一点即可,每个重载的方法都有独一无二的参数列表。其中包括参数的类型、顺序、参数数量等,满足一种一个因素就构成了重载的必要条件。
# 重载的条件🚗
方法名称必须相同。
参数列表必须不同(个数不同、或类型不同、参数类型排列顺序不同等)。
方法的返回类型可以相同也可以不相同。
仅仅返回类型不同不足以成为方法的重载。
重载是发生在编译时的,因为编译器可以根据参数的类型来选择使用哪个方法。
# 6.3 方法的重写
方法的重写与重载虽然名字很相似,但却完全是不同的东西。方法重写的描述是对子类和父类之间 的。而重载指的是同一类中的。例如如下代码
class Fruit {
public void eat(){
System.out.printl('eat fruit');
}
}
// 重写
class Apple extends Fruit{
@Override
public void eat(){
System.out.printl('eat apple');
}
}
2
3
4
5
6
7
8
9
10
11
12
13
上面这段代码描述的就是重写的代码,你可以看到,子类Apple中的方法和父类Fruit中的方法同名。
# 重写的原则🚗
重写的方法必须要和父类保持一致,包括返回值类型,方法名,参数列表也都一样。
重写的方法可以使用
@Override
注解来标识子类中重写方法的访问权限不能低于父类中方法的访问权限。
# 7. 初始化
# 7.1 类的初始化
上面我们创建出来了一个Car这个对象,其实在使用new关键字创建一个对象的时候,其实是调用了 这个对象无参数的构造方法进行的初始化,也就是如下这段代码
class Car{
public Car(){}
}
2
3
这个无参数的构造函数可以隐藏,由JVM自动添加。也就是说,构造函数能够确保类的初始化。
# 7.2 成员初始化
Java会尽量保证每个变量在使用前都会获得初始化,初始化涉及两种初始化。
- 一种是编译器默认指定的字段初始化,基本数据类型的初始化
类型 | 初始值 |
---|---|
boolean | false |
char | /u0000 |
byte | (byte)0 |
short | (short)0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d |
一种是其他对象类型的初始化,String也是一种对象,对象的初始值都为null,其中也包括基本类型的包装类。
- 一种是指定数值的初始化,例如
int a = 11
也就是说,指定a的初始化值不是0,而是11。其他基本类型和对象类型也是一样的。
# 7.3 构造器初始化
可以利用构造器来对某些方法和某些动作进行初始化,确定初始值,例如
public class Counter{
int i;
public Counter(){
i = 11;
}
}
2
3
4
5
6
7
8
利用构造函数,能够把i的值初始化为11。
# 7.4 初始化顺序
首先先来看一下有哪些需要探讨的初始化顺序
静态属性:static开头定义的属性
静态方法块:static {}包起来的代码块
普通属性:非static定义的属性
普通方法块:{}包起来的代码块
构造函数:类名相同的方法
方法:普通方法
public class LifeCycle {
// 静态属性
private static String staticField = getStaticField();
// 静态方法块
static {
System.out.println(staticField);
System.out.println("静态方法块");
}
// 普通属性
private String field = getField();
// 普通方法块
{
System.out.println(field);
}
// 构造函数
public LifeCycle() {
System.out.println("构造函数");
}
public static String getStaticField() {
String statiFiled = "Static Field Initial";
return statiFiled;
}
public static String getField() {
String filed = "Field Initial";
return filed;
}
// 主函数
public static void main(String[] argc) {
new LifeCycle();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
这段代码的执行结果就反应了它的初始化顺序。
静态属性初始化静态方法块初始化普通属性初始化普通方法块初始化构造函数初始化。
# 7.5 数组初始化
数组是相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。数组是通过方 括号下标操作符[]
来定义使用。
一般数组是这么定义的
int[] a1;
//或者
int a1[];
2
3
两种格式的含义是一样的。
直接给每个元素赋值:int array[4] = {1,2,3,4};
给一部分赋值,后面的都为0 : int array[4] = {1,2};
由赋值参数个数决定数组的个数:int array[] = {1,2};
可变参数列表
Java中一种数组冷门的用法就是可变参数,可变参数的定义如下
public int add(int... numbers){
int sum = 0;
for(int num : numbers){
sum += num;
}
return sum;
}
2
3
4
5
6
7
然后,你可以使用下面这几种方式进行可变参数的调用
add(); // 不传参数
add(1); // 传递一个参数
add(2,1); // 传递多个参数
add(new Integer[] {1, 3, 2}); // 传递数组
2
3
4
# 8. 对象的销毁
虽然Java语言是基于C++的,但是它和C/C++ 一个重要的特征就是不需要手动管理对象的销毁工 作。在著名的一书《深入理解Java虚拟机》中提到一个观点:Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人 想进去,墙里面的人却想出来
。
在Java中,我们不再需要手动管理对象的销毁,它是由Java虚拟机
进行管理和销毁的。虽然我们 不需要手动管理对象,但是你需要知道对象作用域
这个概念。
# 对象作用域
J多数语言都有作用域(scope)
这个概念。作用域决定了其内部定义的变量名的可见性和生命周期。在 C、C++和Java中,作用域通常由{}的位置来决定,例如
{
int a = 11;
{
int b = 12;
}
}
2
3
4
5
6
a变量会在两个{}作用域内有效,而b变量的值只能在它自己的{}内有效。
虽然存在作用域,但是不允许这样写
{
int x = 11;
{
int x = 12;
}
}
2
3
4
5
6
这种写法在C/C++中是可以的,但是在Java中不允许这样写,因为Java设计者认为这样写会导致程序混乱。
# 9. this和super
this和super都是Java中的关键字
this表示的当前对象,this可以调用方法、调用属性和指向对象本身。this在Java中的使用一般有三 种:指向当前对象
public class Apple {
int i = 0;
Apple eatApple(){
i++;
return this;
}
public static void main(String[] args) {
Apple apple = new Apple();
apple.eatApple().eatApple();
}
}
2
3
4
5
6
7
8
9
10
11
这段代码比较精妙,精妙在哪呢,我一个eatApple。方法竟然可以调用多次,你在后面还可以继续调 用,这就很神奇了,为啥呢?其实就是this在作祟了,我在eatApple方法中加了一个return this的返回值,也就是说哪个对象调用eatApple方法都能返回对象的自身。
this还可以修饰属性,最常见的就是在构造方法中使用this,如下所示
public class Apple {
private int num;
public Apple(int num){
this.num = num;
}
public static void main(String[] args) {
new Apple(10);
}
}
2
3
4
5
6
7
8
9
10
main方法中传递了一个int值为10的参数,它表示的就是苹果的数量,并把这个数量赋给了 num全 局变量。所以num的值现在就是10。
this还可以和构造函数一起使用,充当一个全局关键字的效果
public class Apple {
private int num;
private String color;
public Apple(int num){
this(num,"红色");
}
public Apple(String color){
this(1,color);
}
public Apple(int num, String color) {
this.num = num;
this.color = color;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
你会发现上面这段代码使用的不是this,而是this(参数)。它相当于调用了其他构造方法,然后传递 参数进去。这里注意一点:this。必须放在构造方法的第一行,否则编译不通过。
如果你把this理解为指向自身的一个引用,那么super就是指向父类的一个引用。super关键字和this —样, 你可以使用super对象来引用父类成员,如下:
public class Fruit {
int num;
String color;
public void eat(){
System.out.println("eat Fruit");
}
}
public class Apple extends Fruit{
@Override
public void eat() {
super.num = 10;
System.out.println("eat " + num + " Apple");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
你也可以使用super(参数)来调用父类的构造函数。
下面为你汇总了 this关键字和super关键字的比较。
关键字 | this | super |
---|---|---|
调用方式 | 调用本类中的属性、构造函数、方法 | 调用父类中属性、构造函数、方法 |
调用位置 | 构造函数第一行,其他可自行指定 | 构造函数第一行,其他可自行指定 |
调用次数 | —个构造函数只能调用一次 | 一个构造函数只能调用一次 |