面向对象编程(基础部分)
★★★第七章 面向对象编程(基础部分)
★类与对象
类是引用数据类型,类中定义有属性和方法。
对象是类的一个具体实例。
类是抽象的、概念的,代表一类事物,例如狗类、猫类等等,类是一种数据类型;
对象是具体的、实际的,代表一个具体事物,即对象是类的实例。
类是对象的模板,对象是类的一个个体,对应一个实例。
★对象内存布局
对象内存布局,即对象在内存中的存在形式。

★属性/成员变量
属性是类的一个组成部分,一般是基本数据类型,也可以是引用类型(对象、数组)。
成员变量 == 属性 == 字段,叫法不同而已。
例如上图
cat.age
中的age
就是猫类的年龄属性。注意:
- 属性的定义语法同变量,**
访问修饰符 属性类型 属性名
**; - 属性的定义类型可以为任意类型,包括基本类型和引用类型;
- 属性如果不赋值,有默认值,规则和数组一致。
- int、short、byte、long ——> 默认值为 0
- float、double ——> 默认值为 0.0
- char ——> 默认值为 \u0000(空字符)
- boolean ——> 默认值为 false
- String ——> 默认值为 null
- 属性的定义语法同变量,**
创建对象与访问属性
创建对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class Test{
public static void main(String[] args){
/* 1.先声明再创建 */
Cat cat1;
cat1 = new Cat();
/* 2.直接创建 */
Cat cat2 = new Cat();
}
}
class Cat{
String name;
int age;
String color;
}访问属性:
基本语法:对象名.属性名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class Test {
public static void main(String[] args) {
Cat cat1 = new Cat();
cat1.name = "小白";
cat1.age = 3;
cat1.color = "白色";
/* 使用属性 */
System.out.println("cat1的具体信息为:" + cat1.name + " " + cat1.age + "岁 " + cat1.color);
}
}
class Cat{
String name;
int age;
String color;
}
★★★类与对象的内存分配机制
接下来分析下图中的代码:
首先在 JVM 的方法区中会加载
Person
类信息(包括属性信息和方法信息)(一个类的类信息只会加载一次);然后
new Person()
会在堆中分配一段空间,由于Person
类有两个属性,所以该空间中分配有两个存储属性内容的地方(初始时未赋值则这两个地方存储的是默认值,即p1.age = 0;p1.name = null;
,默认初始化);然后
Person p1
创建了变量p1
,p1
指向了上面的那段空间,即把这段空间的地址赋给p1
;
然后通过
p1.age = 10;
和p1.name = "小明";
给那段空间中的两个存储属性内容的地方赋值,且对于属性name
,在堆中存储的是地址,该地址指向常量池中的一个地址空间,那个空间中存储的才是 “小明” ;最后
Person p2 = p1;
创建了变量p2
,并让p2
指向和p1
相同的空间;
另:Java 内存的结构分析:
- 栈:一般存放基本数据类型(局部变量);
- 堆:存放对象(Cat cat,数组等);
- 方法区:常量池(常量,比如字符串),类加载信息。
克隆对象
编写一个方法
copyPerson
,可以复制一个Person
对象,返回复制的对象。克隆对象,注意要求得到的新对象和原来的对象是两个独立的对象,只是他们的属性相同。
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
32public class Test {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "小明";
p1.age = 10;
Person p2 = new MyTools().copyPerson(p1);
System.out.println("修改属性前 p1 的信息为:" + p1.name + " " + p1.age + "岁");
System.out.println("修改属性前 p2 的信息为:" + p2.name + " " + p2.age + "岁");
System.out.println(String.class.getName() + "@" + Integer.toHexString(System.identityHashCode(p1.name))); //p1.name 地址
System.out.println(String.class.getName() + "@" + Integer.toHexString(System.identityHashCode(p2.name))); //p2.name 地址
p2.name = "小王";
p2.age = 20;
System.out.println("修改属性后 p1 的信息为:" + p1.name + " " + p1.age + "岁");
System.out.println("修改属性后 p2 的信息为:" + p2.name + " " + p2.age + "岁");
System.out.println(String.class.getName() + "@" + Integer.toHexString(System.identityHashCode(p1.name))); //p1.name 地址
System.out.println(String.class.getName() + "@" + Integer.toHexString(System.identityHashCode(p2.name))); //p2.name 地址
}
}
class Person{
String name;
int age;
}
class MyTools{
public Person copyPerson(Person pOld){
Person pNew = new Person();
pNew.name = pOld.name;
pNew.age = pOld.age;
return pNew;
}
}- 注意:当执行完
Person p2 = new MyTools().copyPerson(p1);
后,p2.name
和p1.name
指向的是常量池中的同一个地方,只是在p2.name = "小王";
执行完毕后,会在常量池中分配有新的空间,p2.name
重新指向了这个新的空间,该处存储有 “小王” 字符串。
- 注意:当执行完
成员方法
方法的定义
1 | 访问修饰符 返回数据类型 方法名 (形参列表..) {//方法体 |
- 注意:
- 访问修饰符:用于控制方法的使用范围,有
public
、protected
、private
、默认; - 返回数据类型:表示方法输出,
void
表示没有返回值; - 形参列表:表示方法输入;
- 方法体:表示为了实现某一功能的代码块;
return
语句不是必须的(对于返回类型为void
的方法,可以不需要return
语句)。
- 访问修饰符:用于控制方法的使用范围,有
★方法的调用机制
如下图所示,是方法的调用机制:
首先由于在
main
方法中执行Person p1 = new Person();
,所以在栈中会有一个 **main栈
**,在该栈中执行这句话,就会在堆中分配一段空间,然后变量p1
指向这个空间;然后
p1
调用方法getSum(10,20)
,此时就会在栈中分配一段独立的空间,由于getSum
函数有两个形参和一个返回参数,所以在该段独立的空间中就有三个存储空间,分别存储num1
、num2
、res
;然后
return res;
,即在独立的空间(getSum栈
。这里是为了方便讲解而命名该空间为getSum栈
,实际上该空间没有这个名字)中执行return res;
,就会把结果res
返回给main栈
中的getSum(10,20)
;
在返回完结果
res
后,那个独立空间就会被释放,main栈
继续执行println
函数。
综上所述:
当程序执行方法时,就会分配一个独立的空间(栈空间);
当方法执行完毕或执行到
return
语句时,就会返回到调用该方法的地方,并且该独立的栈空间就会释放;(由此可知,当
main栈
执行完毕后,也会被释放,整个程序退出)
★方法使用细节
返回数据类型:
- 一个方法最多只有一个返回值(为了返回多个值,可以返回一个数组);
- 返回数据类型可以是任意类型,包括基本数据类型和引用数据类型(数组、对象等);
- 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为
return
值;而且要求返回值类型必须和 return 的值类型一致或兼容; - 如果方法是
void
,则方法体中可以没有return
语句,或者只写return
。
方法名:
多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写:xxxYyyZzz;—> (小)驼峰命名方式
即方法名使用小驼峰命名方式。
形参列表:
- 一个方法可以有 0 个参数,也可以有多个参数,中间用逗号隔开,例如
int getSum(int num1, int num2)
; - 形参类型可以是任意类型,包括基本数据类型和引用数据类型(数组、对象等),例如
printArr(int[][] map)
; - 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数;
- 方法定义时的参数称为形式参数,简称形参;方法调用时的参数称为实际参数,简称实参;实参和形参的类型要一致或兼容,个数、顺序必须一致。
- 一个方法可以有 0 个参数,也可以有多个参数,中间用逗号隔开,例如
方法体:
- 里面写完成功能的具体的语句,可以为输入、输出、变量、运算、分支、循环、方法调用,但里面不能再定义方法,即方法不能嵌套定义。
方法调用:
同一个类中的方法调用:直接调用即可;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class Test{
public static void main(String[] args) {
Person p = new Person();
p.name = "小明";
p.intro();
}
}
class Person {
String name;
int age;
public void speak() {
System.out.println("我是" + this.name);
}
public void intro(){
this.speak();
System.out.println("退出intro()");
}
}跨类的方法调用,A 类方法调用 B 类方法,需要通过 B 类的对象调用;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class Test{
public static void main(String[] args) {
A a = new A();
a.print();
}
}
class A {
public void print(){
System.out.println("这里是 A 的 print 函数");
new B().print();
}
}
class B {
public void print(){
System.out.println("这里是 B 的 print 函数");
}
}注意:如果在 B 类中,
print()
方法的访问修饰符为private
,则此时 A 类就不能调用 B 类的print()
方法了,该方法就只能在 B 类内部使用。即跨类的方法调用和方法的访问修饰符相关。
★方法传参机制
对于基本数据类型,传递的是值(值拷贝),形参的任何改变不影响实参。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class Test {
public static void main(String[] args) {
AA a = new AA();
int num1 = 1, num2 = 2;
System.out.println("num1 和 num2 执行函数前的值为:num1 = " + num1 + " num2 = " + num2);
//输出为:num1 和 num2 执行函数前的值为:num1 = 1 num2 = 2
a.swap(num1, num2);
System.out.println("num1 和 num2 执行函数后的值为:num1 = " + num1 + " num2 = " + num2);
//输出为:num1 和 num2 执行函数后的值为:num1 = 1 num2 = 2
}
}
class AA {
public void swap(int a, int b) {
System.out.println("a 和 b 交换前的值为:a = " + a + " b = " + b);
//输出为:a 和 b 交换前的值为:a = 1 b = 2
int temp = a;
a = b;
b = temp;
System.out.println("a 和 b 交换后的值为:a = " + a + " b = " + b);
//输出为:a 和 b 交换后的值为:a = 2 b = 1
}
}
对于引用数据类型,传递的是地址(传递的也是值,但是这个值是地址),可以通过形参影响实参。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Test {
public static void main(String[] args) {
AA a = new AA();
int[] array = {0,0,0};
System.out.println("执行函数前 array[0] = " + array[0]);
//输出为:执行函数前 array[0] = 0
a.change(array);
System.out.println("执行函数后 array[0] = " + array[0]);
//输出为:执行函数后 array[0] = 100
}
}
class AA {
public void change(int[] arr){
arr[0] = 100;
}
}