面向对象编程(基础)
目录
1.类与对象的关系
2.对象在内存中的存在形式
2.2 注意事项(1)
2.3 注意事项(2)
3.对象的创建方式
4.变量
4.1 成员变量
4.1.1 语法格式
4.1.2 说明
4.2 局部变量
4.2.1 语法格式
4.2.2 说明
4.3 实例变量与局部变量的对比
5.成员方法
5.1 成员方法的语法格式
5.2 快速入门
5.3 成员方法的使用细节
5.4 成员方法的调用机制(非常重要)
5.5 成员方法的传参机制(非常重要)
5.5.1 形参为基本数据类型
5.5.2 形参为引用数据类型
5.5.2.1 形参为数组
5.5.2.2 形参为类
5.5.2.3 形参为类的特殊情况(重要)
5.6 可变个数的形参
5.6.1 基本语法
5.6.2 快速入门
5.6.3 使用细节
5.7 方法重载(OverLoad)
6.构造器(Constructor)
6.1 构造器的作用
6.2 基本语法
6.3 快速入门
6.4 使用说明(非常重要)
7.this关键字
7.1 引出this
7.2 this关键字的作用(非常重要)
7.3 使用细节
7.3.1 访问成员方法、成员变量
7.3.2 访问构造器
8.对象创建的流程分析(非常重要)
8.1 Java内存结构解析
8.2 流程分析
1.类与对象的关系
类与对象的关系
(1)类是抽象的,代表一类事物,比如人类,猫类,类是一种引用数据类型
(2)对象是具体的,代表一个具体事物,即实例
(3)类是对象的模板,对象是类的一个个体,对应一个实例
2.对象在内存中的存在形式
对象在内存中的存在形式
2.2 注意事项(1)
创建类的多个对象时,每个对象在堆中有一个对象实体。每个对象实体中保存着类的属性。如果修改某一个对象的某属性值时,不会影响其他对象此属性的值
2.3 注意事项(2)
在两个变量指向堆空间中的同一个对象实体前提下,如果通过其中某一个对象变量修改对象的属性时,会影响另一个对象变量此属性的值
3.对象的创建方式
- 先声明再创建
public class demo {public static void main(String[] args){Person person;//先声明对象person,这时的person还未指向任何一处空间person = new Person();//给该对象在堆创建一个空间,并将空间地址赋值给person}
}
- 直接创建
public class demo {public static void main(String[] args){Person person = new Person();}
}
注:person 仅仅只是指向 new Person() 的对象空间,person 也叫作对象引用或者对象名
4.变量
变量分类 = 成员变量 + 局部变量
变量分类
注: static 可以将成员变量分为两大类,静态变量和非静态变量。其中静态变量又称为类变量,非静态变量又称为实例变量或者属性。下面的成员变量部分主要是实例变量,类变量等后续再作补充
4.1 成员变量
成员变量 = 属性 = 字段,是类的一个组成部分,可以是基本类型,也可以是引用类型(对象,数组)
4.1.1 语法格式
[修饰符1] class 类名{ [修饰符2] 数据类型 成员变量名;//使用默认值[修饰符2] 数据类型 成员变量名 = ?;//直接赋值
}
4.1.2 说明
-
位置要求:必须在类中,方法之外
-
修饰符2:
-
常用的修饰符有:默认(缺省),public,protected,private
-
其他修饰符:static,final
-
-
数据类型:基本数据类型、引用数据类型均可
-
成员变量名:属于标识符,符合命名规则和规范即可
-
默认值:成员变量都有默认值,声明变量之后未赋值仍然可以直接使用
4.2 局部变量
4.2.1 语法格式
基本格式:数据类型 变量名 = ?
(1)方法一
String name;
name = "马";
(2)方法二
String name = "马"
4.2.2 说明
-
位置要求:位于方法体内部、 代码块、形参、构造器中定义的变量
-
修饰符:不能有修饰符
-
默认值:无默认值,必须给局部变量赋值之后再使用,否则会报错
public class demo {public static void main(String[] args) {String name;System.out.println(name);//X,报错,局部变量必须先赋值才能使用Person person = new Person();System.out.println(person.age);//√,成员变量有默认值,可以直接使用}
}
class Person {int age;
}
4.3 实例变量与局部变量的对比
- 声明位置的对比
- 实例变量:类中方法外
- 局部变量:方法体{}中或方法的形参列表、代码块、构造器中
- 内存中存储位置的对比
- 实例变量:栈
- 局部变量:堆
- 作用域的对比
- 实例变量:通过对象就可以使用,本类中直接调用即可,其他类中需要“对象.实例变量”
- 局部变量:出了方法体就不能使用
class Person {int age;public void test(){//本类使用实例变量System.out.println(age);//跨类使用实例变量Cat cat = new Cat();System.out.println(cat.name);}
}
class Cat{String name;
}
- 生命周期的对比
- 实例变量:生命周期较长。和对象的生命周期一样,随着对象的创建而存在,随着对象被GC回收而消亡, 而且每一个对象的实例变量是独立的
- 局部变量:生命周期较短。伴随着它的代码块的执行而创建,伴随着代码的结束而销毁,即一次方法的调用过程,而且每一次方法调用都是独立的
- 有无默认值对比
- 实例变量:有默认值,即使声明变量之后未赋值仍然可以直接使用
- 局部变量:无默认值,必须给局部变量赋值之后再使用,否则会报错
- 重名问题
- 实例变量和局部变量可以重名,访问的时候遵循就近原则
public class demo {public static void main(String[] args) {Person person = new Person();person.test();}
}
class Person {int age = 10;public void test(){int age = 20;System.out.println(age);}
}
/*输出结果
20*/
- 修饰符的对比
- 实例变量可以加修饰符,局部变量不可以加修饰符
5.成员方法
- 成员方法也叫方法,方法是类或对象行为特征的抽象,用来完成某个功能操作
- 将功能封装成方法的目的:实现代码复用,减少冗余
- Java里的方法不能独立存在,所有的方法必须定义在类里
5.1 成员方法的语法格式
访问修饰符 返回数据类型 方法名(形参列表..) {//方法体return 返回值;
}
5.2 快速入门
public class demo {public static void main(String[] args) {Person person = new Person();person.sayHello();}
}
class Person{public void sayHello(){System.out.println("hello");}
}
5.3 成员方法的使用细节
(1)参数类型可以为任意类型,包含基本类型和引用类型
(2)方法的返回值类型必须和 return 的值类型一致或兼容
兼容:就是返回值为低精度时,可以返回高精度,但是反过来则不行
public class demo {public static void main(String[] args) {Person p = new Person();System.out.println(p.test());}
}class Person {public double test(){return 10;//✔,int->double,自动转换为浮点类型}public int test1(){return 5.1;//X,报错,double -> int}
}
(3)如果方法返回值是 void ,则方法体不能有 "return 返回值;" 语句,但可以有 " return; " 语句
(4)方法定义时的参数称为形式参数,即形参;方法调用时传入的参数称为实际参数,即实参,实参和形参的类型要一致或者兼容,而个数必须一致
public class demo {public static void main(String[] args) {Person p = new Person();System.out.println(1.1);//实参为double类型,而形参为int类型,可以兼容}
}class Person {public double test(int num){return num;//✔,int->double,自动转换为浮点类型}
}
(5)
- 同一个类中的方法调用:直接调用即可
- 跨类中的方法::比如 A 类调用 B 类方法,需要先创建 B 类对象,然后用对象名调用
- 跨类的方法调用和方法的访问修饰符相关
public class demo {public static void main(String[] args) {Person p = new Person();p.f2();}
}
class Person {public void f1(){System.out.println("Person类的f1方法");}public void f2(){System.out.println("Person类的f2方法");f1();//同类方法直接调用//跨类方法调用Cat cat = new Cat();cat.f1();}
}
class Cat{public void f1(){System.out.println("Cat类的f1方法");}
}
/*输出结果
Person类的f2方法
Person类的f1方法
Cat类的f1方法
*/
5.4 成员方法的调用机制(非常重要)
调用机制内存图
调用机制
(1)方法没有被调用时,都在方法区中的字节码 .class 文件中存储
(2)当程序执行到方法时,就会在栈中开辟一个独立的空间(栈空间),即给当前方法开辟一块独立的内存区域,用于存放当前方法的局部变量的值
(3)当方法执行结束,或者执行到 return 语句时,开辟的栈空间销毁,返回到调用方法的那行代码
(4)返回后,继续执行后面的代码
(5)当 main 方法(栈)执行完毕时,整个程序退出
5.5 成员方法的传参机制(非常重要)
5.5.1 形参为基本数据类型
基本数据类型传递的是值,形参影响不到实参
示例代码
public class demo {public static void main(String[] args) {A obj = new A();int a = 10;int b = 20;obj.swap(a,b);System.out.println("a = "+a+" b = "+b);}
}class A {public void swap(int a,int b){int tmp = a;a = b;b = tmp;}
}
示例代码内存图
5.5.2 形参为引用数据类型
引用类型传递的是地址,可以通过形参影响实参
5.5.2.1 形参为数组
示例代码
public class demo {public static void main(String[] args) {int[] arr = {1,2,3};A obj = new A();obj.change(arr);for (int i = 0; i < arr.length; i++) {System.out.println(arr[i]);}}
}
class A {public void change(int[] arr) {arr[0]=100;}
}
示例代码内存图
5.5.2.2 形参为类
示例代码
public class demo {public static void main(String[] args) {A obj = new A();Person p = new Person();p.name = "jack";p.age = 20;obj.test(p);System.out.println(p.age);}
}
class Person {String name;int age;
}
class A {public void test(Person p) {p.age = 50;}
}
示例代码内存图
5.5.2.3 形参为类的特殊情况(非常重要)
这里看一下另一种情况 ,在 5.5.2.3 的代码作一下修改,在 test 方法中将形参为 p 置为 null ,即 p = null
示例代码
public class demo {public static void main(String[] args) {A obj = new A();Person p = new Person();p.name = "jack";p.age = 20;obj.test(p);System.out.println(p.age);}
}
class Person {String name;int age;
}
class A {public void test(Person p) {p=null;}
}
示例代码内存图
内存图
主方法中输出的 p.age 还是为 20 。是因为两个栈空间是独立的,在 test 方法的栈空间里将 p 置为 null ,只是将其指向 p 的对象实体的那条线"切断了",跟 main 方法的栈空间里的 p 没有任何关系,所以输出 20
5.6 可变个数的形参
在 Java 提供了 Varargs(variable number of arguments) 机制。即当定义一个方法时,形参的类型可以确定,但是形参的个数不确定,那么可以考虑使用可变个数的形参
5.6.1 基本语法
访问修饰符 返回类型 方法名(数据类型...形参名) {}
5.6.2 快速入门
public class demo {public static void main(String[] args) {Method m = new Method();System.out.println(m.getSum(1,2,3));}
}
class Method {public int getSum(int...args) {int total = 0;for (int i = 0; i < args.length; i++) {total += args[i];}return total;}
}
/*输出结果
6
*/
(1)int ... args 表示接受的是可变参数,类型是 int ,可以接收0-多个int
(2)使用可变参数 args 时,可以当作数组来使用,其本质就是数组
5.6.3 使用细节
使用细节
(1)一个形参列表中只能出现一个可变参数
public int getSum(int... a,int... b){};//X,只能出现一个可变参数
(2)可变参数的实参可以为0个或任意多个
(3)可变参数的本质就是数组,其实参也可以为数组
public class demo {public static void main(String[] args) {Method m = new Method();int[] arr = {1,2,3};System.out.println(m.getSum(arr));}
}
class Method {public int getSum(int...args) {int total = 0;for (int i = 0; i < args.length; i++) {total += args[i];}return total;}
}
/*输出结果
6*/
(4) 可变参数和普通类型的参数可以一起放在形参列表中,但必须保证可变参数在最后
public int getSum(int... a,String str){};//X,可变参数在前面
public int getSum(String str,int... a){};//√,可变参数在后面
public int getSum(int a,int... b){};//√,可变参数在后面
5.7 方法重载(OverLoad)
方法重载: Java 允许同一个类中有多个同名方法的存在
方法重载的要求:
- 方法名:必须一致
- 形参类型或个数或顺序,至少有一样不同,参数名无要求
- 返回类型:无要求
有关重载的练习题
判断下方函数是否与void show(int a,char b,double c){}构成重载
void show(int x,char y,double z){}//不是
int show(int a,double c,char b){}//是,参数顺序不同
void show(int a,double c,char b){}//是,参数顺序不同boolean show(int c,char b){}//是,参数个数不同
void show(){}//是,参数个数不同
double show(int x,char y,double z){}//不是,虽然返回类型不同,但重载对返回类型无要求。参数个数、类型、顺序全都没有改变,不是重载
6.构造器(Constructor)
Constructor = 构造器 = 构造方法,是类的一种特殊的方法,它的主要作用是完成对象的初始化(不是创建对象噢)
6.1 构造器的作用
new 完对象时,所有成员变量都是默认值,如果需要赋别的值,需要依次为它们再赋值,这样太麻烦了。所以 Java 中提供了构造器,使得在 new 对象的同时,直接为当前对象的某个或所有成员变量直接赋值
6.2 基本语法
[修饰符] class 类名{[修饰符] 构造器名(){// 实例初始化代码}[修饰符] 构造器名(参数列表){// 实例初始化代码}
}
说明:
(1)构造器名称必须与它所在的类名相同
(2)不需要返回值类型,也不需要 void
(3)构造器的修饰符只能是权限修饰符,即 [默认(缺省),protected,public,private] ,不能被其他任何修饰。比如,不能被 static、final、synchronized、abstract、native 修饰
6.3 快速入门
public class demo {public static void main(String[] args) {Student student = new Student("马", 20);System.out.println(student.name);System.out.println(student.age);}
}
class Student{String name;int age;//无参构造public Student() {}//有参构造public Student(String studentName, int studentAge) {name = studentName;age = studentAge;}
}
6.4 使用说明(非常重要)
构造器使用说明
(4)如果程序没有定义任何一个构造器,系统会自动给该类生成一个无参构造器(系统生成的无参构造器的修饰符看类的修饰符跟类是一致的)。这也是为什么没有给类定义构造器的时候仍然可以 Dog dog = new Dog() 的原因
验证
- 第1步:创建 Person 类,不创建任何构造器
public class Person {String name;int age;
}
- 第2步:在终端命令行输入 “javac Person.java”,对 Person.java 进行编译生成对应的字节码文件 Person.class
- 第3步: 在终端命令行输入 "javap Person.class" 对字节码文件进行反编译,反编译成程序员可以看得懂的 Person.java
验证结束,可以看到确实自动生成了无参构造器,并且其修饰符与类的修饰符一致都是 public
(5) 一旦定义了自己的构造器,系统便不会再自动生成无参构造器,就不能再使用默认的无参构造器了,除非自己显式的定义一下
验证
7.this关键字
7.1 引出this
先来看一段代码
public class demo{public static void main(String[] args){Person p = new Person("小马",20);System.out.println(p.name);//输出默认值nullSystem.out.println(p.age);//输出默认值0}
}
class Person{String name;int age;/*这样定义构造器的话虽然可以成功给属性赋值,但形参的name1和age1会很奇怪public Person(String name1,int age1){name = name1;age = age1;}*/public Person(String name,int age){name = name;//由于就近原则,这里的两个name都是形参中的nameage = age;}
}
/*输出结果
null
0
*/
按第一种方式定义构造器,虽然可以成功给属性 name 和 age 赋值,但是形参的 name1 和 age1 会很突兀
按第二种方式定义构造器,由于就近原则,不能成功给属性 name 和 age 赋值
引出 this 关键字
public class demo{public static void main(String[] args){Person p = new Person("小马",20);System.out.println(p.name);//输出小马System.out.println(p.age);//输出20}
}
class Person{String name;int age;public Person(String name,int age){this.name = name;//this.name表示当前对象的属性name,此例中this指的就是pthis.age = age;//this.age表示当前对象的属性age}
}
/*输出结果
小马
20
*/
7.2 this关键字的作用(非常重要)
(1) this 可以帮助区分成员变量和局部变量,使得代码的可读性更强。所以一般访问成员变量时加个 this 会更好
(2) Java 虚拟机会给每个对象分配 this,代表当前对象,可以简单理解为在对象实体中有一个隐藏的 this
(3) 哪个对象调用,this 就代表哪个对象
验证
这里可以使用 hashcode 方法作一下验证,先看一下 hashcode 方法的作用
hascode方法的作用
public class demo{public static void main(String[] args){Person person = new Person();System.out.println(person.hashCode());person.test();}
}
class Person{String name;int age;public void test(){System.out.println(this.hashCode());}
}
/*
输出结果是一致的,验证成功
*/
7.3 使用细节
this 关键字可以用来访问本类的属性、方法、构造器,但不能在类定义的外部使用,只能在类定义的成员方法、非static方法、构造器中使用
7.3.1 访问成员方法、成员变量
this.方法名(参数列表)
this.成员变量名
另外,使用 this 访问成员变量和方法时,如果在本类中未找到,会从父类中查找
7.3.2 访问构造器
this(参数列表)
- this():调用本类的无参构造
- this(参数列表):调用本类的有参构造
- 只能在构造器中访问另外一个构造器, 且必须放在第一条语句,否则会报错
- 在类的一个构造器中,最多只能声明一个 "this(参数列表)"
- 在类的一个构造器中,this 和 super 两个必须选一个存在
- 不能出现递归调用。比如,调用自身构造器
- 与访问成员方法、成员变量不同,利用 this 访问构造器时如果在本类中未找到,不会去父类中查找
8.对象创建的流程分析(非常重要)
8.1 Java内存结构解析
这里列出三个主要的,其他的还不是很清楚
堆(Heap):此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。另外还有数组的空间也再堆上分配。在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配
栈(Stack):栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(即引用类型,它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放
方法区(Method Area):用于存储已被虚拟机加载的类信息(类模板)、常量(字符常量池)、静态变量等
8.2 流程分析
分析 Person person = new Person(20,“小马”)
(1)在方法区加载 Person 类信息(属性和方法信息,只会加载一次)
(2)在堆中分配空间(地址)
(3)完成对象的初始化,一共 3 个步骤
- 默认初始化 -> [ age = 0,name = null ]
- 显示初始化 -> [ age = 90,name = null ]
- 构造器初始化 -> [ age = 20 , name = "小马"]
(4)把对象实体在堆中的地址返回给 person