面对对象基础
1. 主线
- Java类及其成员:属性、方法、构造器;代码块、内部类
- 面对对象的三大特征
- 封装性
- 继承性
- 多态性
- (抽象性)
- 其他关键字:this、super、static、final、abstract、interface、package、import
2. 类和对象
类
类是对一类事物的描述,是抽象的、概念上的定义。
-
设计一个类
- 属性(成员变量、field、域)
- 方法 (成员函数、method、方法)
class Person{ //属性 String name; int age; boolean isMale; //方法 public void eat(){ System.out.println("吃饭"); } public void sleep(){ System.out.println("睡觉"); } public void talk(String language){ System.out.println("说" + langue + "语言"); } }
-
使用类及其对象
public class Test{ public static void main(String[] args){ //创建Person类的对象(类的实例化) Person p1 = new Person(); //调用对象的属性和方法 //调用属性:对象.属性名 p1.name = "Tom"; p1.age = 18; p1.isMale = true; System.out.println(p1.name); //调用方法:对象.方法名() p1.eat(); p1.sleep(); p1.talk("Chinese"); } }
- 创建类的实例时需要使用new关键字;
- 类是对象的抽象,对象是类的具体;
- 由对象可以反推出类(反射机制);
-
类中的属性
- 属性 vs 局部变量
- 相同点
- 定义的格式相同
- 先声明后使用
- 都有其对应的作用域
- 不同点:
- 在类中声明的位置不同:属性直接声明在类中;局部变量声明在方法内、方法形参、代码块内、构造器形参或构造器中
- 生命周期不同:属性随对象的创建而诞生,随对象的消亡而消失;局部变量自声明起至方法结束完
- 内存位置不同:属性分配堆中的空间或静态域内;局部变量分配栈中的空间
- 作用域不同:属性可以使用权限修饰符控制其作用域;局部变量作用域为一对大括号内
- 默认初始化的值不同:属性有默认初始化值(同数组);局部变量没有默认初始化值,声明后必须赋值才可以使用
- 相同点
- 属性 vs 局部变量
-
类中的方法
描述对象所具有的功能。
- 方法的声明:
权限修饰符 返回值类型 方法名(形参列表){方法体}
- 方法也可以使用权限修饰符
- static、final、abstract修饰符
- 方法名为标识符,须遵循命名规则
- 形参列表:可声明0个、1个或多个形参
- 类方法中可以调用当前类 的属性,也可以调用当前类的方法
- 类中的方法可以访问该类对象的私有属性
- 方法的声明:
对象
对象是实际存在的该类事物的每个个体,因而也称为实例(instance)。
public class Test{
public static void main(String[] args){
Person p1 = new Person();
p1.name = "Tom";
p1.age = 18;
p1.isMale = true;
System.out.println(p1.name);
p1.eat();
p1.sleep();
p1.talk("Chinese");
Person p2 = new Person();
System.out.println(p2.name);//null
p2.name = "Petter";
System.out.println(p2.name);
}
}
- 每个对象拥有自己独立的内存空间(static修饰的属性和方法除外);
- 类中未初始化的属性赋予其默认初始化值(参考数组中的默认初始化值);
- 对象的内存解析
- 堆(Heap),此内存区域的唯一目的就是存放对象实例(这里指的是广义上的对象:new出来的),几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
- 栈(Stack),是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char 、 short 、 int 、 float 、 long 、double)、对象引用(reference类型,它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。
- 方法区(Method Area),用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 实例解析:
public static void main(String[] args){
Person p1 = new Person();
p1.name = "Tom";
p1.age = 18;
p1.isMale = true;
Person p2 = new Person();
sysout(p2.name);//null
Person p3 = p1;
p3.age = 10;
}
-
对象数组
例题:
定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int)。 创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。
问题一:打印出3年级(state值为3)的学生信息。
问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息public class Exercise4 { public static void main(String[] args) { Student[] students = new Student[20]; Exercise4 demo = new Exercise4(); demo.initStudent(students, 20); demo.searchState(students, 3); demo.sortWithScore(students); demo.printInfo(students); } /** * * @Description 初始化对象数组 * @author zbs * @date Jul 29, 20212:36:24 PM * @param students */ public void initStudent(Student[] students, int num) { for (int i = 0; i < num; i++) { students[i] = new Student(); students[i].number = i + 1; students[i].score = (int)(Math.random() * 100) + 1; students[i].state = (int)(Math.random() * 6) + 1; } } /** * * @Description 根据年级进行查找 * @author zbs * @date Jul 29, 20212:37:15 PM * @param students * @param state */ public void searchState(Student[] students, int state) { System.out.println("三年级的学生有:"); System.out.println("学号\t成绩\t年级"); for (int i = 0; i < students.length; i++) { if (students[i].state == state) { students[i].showInfo(); } } } /** * * @Description 根据成绩进行排序 * @author zbs * @date Jul 29, 20212:37:43 PM * @param students */ public void sortWithScore(Student[] students) { boolean flag; for (int i = 0; i < students.length; i++) { flag = true; for (int j = 0; j < students.length - i - 1; j++) { if (students[j].score > students[j + 1].score) { Student temp = students[j]; students[j] = students[j + 1]; students[j + 1] = temp; flag = false; } } if (flag) { break; } } } /** * * @Description 输出对象数组的信息 * @author zbs * @date Jul 29, 20212:38:18 PM * @param students */ public void printInfo(Student[] students) { System.out.println("按成绩由小到大的排序为:"); System.out.println("学号\t成绩\t年级"); for (int i = 0; i < students.length; i++) { students[i].showInfo(); } } } class Student{ /** * 学号 */ int number; /** * 年级 */ int state; /** * 分数 */ int score; public void showInfo() { System.out.print(number + "\t"); System.out.print(score + "\t"); System.out.println(state + "\t"); } }
-
如何理解万物皆对象
- 在Java语言的范畴,我们将功能、结构封装到类中,通过类的实例化,来调用具体的功能结构;
- 自定义的类、对象
- 文件:File
- 网络资源、URL
- 涉及到Java语言与前端的Html、后端的数据库交互时,都体现为类、对象;
- 在Java语言的范畴,我们将功能、结构封装到类中,通过类的实例化,来调用具体的功能结构;
-
匿名对象
创建的对象没有被变量所接收;
匿名对象只可以调用一次;
常用作参数传递,如下:
class Test{ public static void main(){ PhoneMall mall = new PhoneMall(); mall.show(new Phone()); //参数传入匿名对象 } } class PhoneMall{ public void show(Phone phone){ phone.sendMaile(); phone.playGame(); } } class Phone{ double price; public void sendEmail(){ System.out.println("发送邮件"); } public void playGame(){ System.out.println("玩游戏"); } }
-
练习:自定义数组的工具类
public class ArrayUtil { /** *返回数组的最大值ֵ * @Description * @author zbs * @date Jul 29, 20214:25:45 PM * @param array * @return */ public static int getMax(int[] array) { int max = array[0]; for (int i = 1; i < array.length; i++) { if (array[i] > max) { max = array[i]; } } return max; } /** * 返回数组的最小值ֵ * @Description * @author zbs * @date Jul 29, 20214:29:13 PM * @param array * @return */ public static int getMin(int[] array) { int min = array[0]; for (int i = 1; i < array.length; i++) { if (array[i] < min) { min = array[i]; } } return min; } /** *返回数组的总和 * @Description * @author zbs * @date Jul 29, 20214:34:02 PM * @param array * @return */ public static int getSum(int[] array) { int sum = 0; for (int i = 0; i < array.length; i++) { sum += array[i]; } return sum; } /** * 返回数组的平均值ֵ * @Description * @author zbs * @date Jul 29, 20214:37:43 PM * @param array * @return */ public static double getAvarage(int[] array) { int sum = 0; for (int i = 0; i < array.length; i++) { sum += array[i]; } return sum/array.length; } /** * 反转数组 * @Description * @author zbs * @date Jul 29, 20214:41:50 PM * @param array */ public static void reverse(int[] array) { for (int i = 0; i < array.length / 2; i++) { int index = array.length - 1 - i; int temp = array[i]; array[i] = array[index]; array[index] = temp; } } /** *复制数组 * @Description * @author zbs * @date Jul 29, 20214:44:02 PM * @param array * @return */ public static int[] copy(int[] array) { int[] temp = new int[array.length]; for (int i = 0; i < array.length; i++) { temp[i] = array[i]; } return temp; } /** * 冒泡排序 * @Description * @author zbs * @date Jul 29, 20214:48:23 PM * @param array */ public static void bullotSort(int[] array) { boolean flag; for (int i = 0; i < array.length - 1; i++) { flag = true; for (int j = 0; j < array.length - 1 - i; j++) { if (array[j] > array[j + 1]) { int temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; flag = false; } } if (flag) { break; } } } /** * 输出数组内容 * @Description * @author zbs * @date Jul 29, 20214:50:24 PM * @param array */ public static void toString(int[] array) { System.out.print("["); for (int i = 0; i < array.length - 1; i++) { System.out.print(array[i] + ","); } System.out.print(array[array.length - 1] + "]"); } /** * 查找指定元素的位置 * @Description * @author zbs * @date Jul 29, 20214:52:35 PM * @param array * @param dest * @return */ public static int getElem(int[] array, int dest) { for (int i = 0; i < array.length; i++) { if (array[i] == dest) { return i; } } return -1; } } public class ArrayUtilTest { public static void main(String[] args) { int[] array = {1, 3, 5, 7, 9, 2, 4, 6, 8, 10}; ArrayUtil.bullotSort(array); ArrayUtil.toString(array); } }
3. 方法重载
重载
-
概念:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
-
只有下列三种情况属于重载:
- 参数类型不同
- 参数个数不同
- 参数顺序不同
- 重载只与参数列表中的参数个数及其类型有关,与参数名和返回值类型无关;
- 参数的顺序不同也是重载;
- 存在重载的方法时,从低级类型开始匹配;
-
如何确定对象调用的哪个方法:方法名 —> 参数列表
方法参数的值传递机制
-
什么是赋值操作
-
对于基本数据类型来说:
int m = 10; int n = m; System.out.println("m = " + m + ", n = " + n);//m = 10, n = 10 n = 20; System.out.println("m = " + m + ", n = " + n);//m = 10, n = 20
在基本数据类型赋值的过程中,是将值复制了一份再赋给了新的变量。也就是在内存中有两份相同的值。
-
对于引用数据类型来说:
class Order{ int orderId; } class Test{ public static void main(String[] args){ Order o1 = new Order(); o1.orderId = 1001; Order o2 = o1; System.out.println("o1.orderId = " + o1.order + ", o2.orderId = " + o2.orderId); //o1.orderId = 1001, o2.orderId = 1001 o2.orderId = 1002; System.out.println("o1.orderId = " + o1.order + ", o2.orderId = " + o2.orderId); //o1.orderId = 1002, o2.orderId = 1002 } }
引用数据类型的赋值操作是将地址赋给新的变量。也就是说两个变量指向同一个内存空间。
-
-
形参的传递机制
-
值传递
class Test { public static void main(String[] args){ int a = 10; int b = 20; System.out.println("a = " + a + ", b = " + b);//a = 10, b = 20 Test test = new Test(); test.swap(a, b); System.out.println("a = " + a + ", b = " + b);//a = 10, b = 20 } public void swap(int m, int n){ int temp = m; m = n; n = temp; } }
执行结果:两个数值交换失败。
执行过程:
-
首先在main方法中定义了a、b两个变量,在swap()函数中定义了m、n两个形参以及局部变量temp。
-
紧接着将a、b两个实参传给swap()函数中的形参,相当于执行了
m = a;
,n = b
的赋值操作,由于是基本数据类型,所以a、b与m、n的内存地址不是同一个地址。 -
然后在swap()函数中通过temp变量对m、n的数值进行交换,但是对a、b的值并没有影响。
-
函数执行完后释放掉形参和局部变量的内存空间,并没有起到交换a、b的效果。
内存图:
-
-
址传递
class Data{ int a; int b; } class Test { public static void main(String[] args){ Data data = new Data(); int data.a = 10; int data.b = 20; System.out.println("a = " + data.a + ", b = " + data.b);//a = 10, b = 20 Test test = new Test(); test.swap(data); System.out.println("a = " + a + ", b = " + b);//a = 20, b = 10 } public void swap(Data data){ int temp = data.a; data.a = data.b; data.b = temp; } }
执行结果:交换成功
执行过程:
- 将想要交换的两个数封装在Data类中,在main方法中创建该类的对象并进行初始化。
- 将data对象最为swap()函数的参数传入方法;
- 由于data属于引用数据类型,所以使用址传递,将data对象的地址传入方法,即swap()方法中data所指向的内存空间和main方法中data所指向的内存空间相同。
- 因此在swap()函数中交换数值相当于对main方法中的data交换数值。
内存图:
-
总结:
- 若参数为基本数据类型,函数内对于形参的改变不影响函数外实参的数值;
- 若参数为引用数据类型,函数内对于形参的改变影响函数外实参的数值。
-
可变个数的形参
允许直接定义能和多个实参相匹配的形参。
-
形参定义的格式:
数据类型 ... 变量名
-
使用方法:
class Test{ public static void main(String[] args){ Test test = new Test(); test.show(1); test.show("hello", "world"); //传入可变参数 } public void show (int num){ System.out.println("show(int)"); } public void show(String ... strs){ //定义可变参数 System.out.println("show(String ...)"); for (int i = 0; i < strs.length; i++){ System.out.println(strs[i]); } } }
-
Notes:
- 可变参数的形式可与其他同名的方法构成重载;
- 可变参数与数组参数同名的方法不可同时存在;
- 可变参数实际上就是数组;
- 可变形参应该生命在参数列表末尾且只能声明一个;
4. 封装性
程序设计的追求:高内聚、低耦合
- 高内聚:类内部的数据操作细节自己完成,不允许外部干涉;
- 低耦合:仅对外暴露少量的方法供使用;
封装性通俗的说就是把该隐藏的隐藏起来,把该暴露的暴露出来。
public class Test{
public static void main(String[] args){
Animal animal = new Animal();
animal.name = "蛋黄";
animal.age = -2;//错误的赋值
animal.setAnimal(-2);
animal.show();
System.out.println("age: " + animal.getAge());
}
}
class Animal{
String name;
private int age;//添加访问修饰符对age变量进行约束
//通过方法对age进行判断、赋值
public setAge(int age1){
if (age > 0 && age < 100){
age = age1;
} else {
age = 0;
}
}
//为不能访问的属性能够提供访问方法
public int getAge(){
return age;
}
public void show(){
System.out.println("name: " + name + ", age: " + age);
}
}
问题的提出:
当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没有其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如:setAge())。同时,我们需要避免用户再使用"对象.属性"的方式对属性进行赋值。则需要将属性声明为私有的(private)。
封装性的体现:
我们将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
拓展:封装性的体现:① 如上 ② 不对外暴露的私有的方法 ③ 单例模式 …访问控制修饰符
类内 本包 非本包子类 非本包非子类 public √ √ √ √ default √ √ √ × protected √ √ × × private √ × × × 可以用来修饰类及类的内部结构、属性、方法、构造器、内部类;
类只可以使用public或default修饰;
private限制的是类外不能对属性或方法进行访问,但是类内可以。
5. 构造器(constructor)
任何一个类都至少有一个构造器
- 构造器的作用:
-
创建对象
String str = new String(); //new + 构造器
-
初始化对象
String str = new String("lhp"); //初始化str = "lhp"
-
构造器的定义:
权限控制修饰符 类名(形参列表){方法体}
构造器不同于方法;
构造器的名字必须与类名同名;
构造器没有返回值;
在一个类中可定义多个构造器,符合方法重载的规则;
一旦显示的定义了类的构造器,系统不再提供默认的空参构造器;
默认构造器的权限与类的权限相同;
构造器中可调用方法和构造器;
6. this关键字
public class Test{
Person p1 = new Person();
p1.setAge(19);
System.out.println(p1.getAge());
/* 忽略构造器的情况下:
* case1:输出0
* case2:输出19
*/
}
class Person{
private String name;
private int age;
public Person(){
}
public Person(String name){
this.name = name;
}
public Person(int age){
this.age = age;
}
public Person(String name, int age){
//调用空参构造器
this();
//调用有参数的构造器
this(name);
this(name, age);
this.name = name;
this.age = age;
}
public void setName(String name){
//case1:
name = name;
//case2:
this.name = name;
}
public void setAge(int age){
//case1:
age = age;
//case2:
this.age = age;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
}
thsi修饰属性和方法:
- this可以理解为当前对象
- 在类的方法中,可以使用“this.属性”或“this.方法”的方式来调用当前对象的属性或方法。通常情况下可以省略this。特殊情况下,若方法的形参与属性或方法重名时,必须加上this关键字,表明是当前对象的属性,而不是形参。
- 在类的构造器中,我们可以使用"this.属性"或"this.方法"的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都选择省略"this."。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
this修饰构造器:
- 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器
- 构造器中不能通过"this(形参列表)"方式调用自己
- 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)"
- 规定:"this(形参列表)"必须声明在当前构造器的首行
- 构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器
练习:

public class Test{
public static void main(String[] args) {
Boy boy = new Boy("zbs", 20);
Girl girl = new Girl("lhp", 19);
boy.marry(girl);
girl.marry(boy);
}
}
class Boy{
private String name;
private int age;
public Boy(){
}
public Boy(String name){
this.name = name;
}
public Boy(int age){
this.age = age;
}
public Boy(String name, int age){
this.name = name;
this.age = age;
}
public void setName(String i){
name = i;
}
public String getName(){
return this.name;
}
public void setAge(int i){
age = i;
}
public int getAge(){
return this.age;
}
public void marry(Girl girl){
System.out.println("我想娶" + girl.getName());
}
public void shout(){
if (this.age >= 22){
System.out.println("可以去结婚了");
} else {
System.out.println("再等等吧");
}
}
}
class Girl{
private String name;
private int age;
public Girl(){
}
public Girl(String name){
this.name = name;
}
public Girl(int age){
this.age = age;
}
public Girl(String name, int age){
this.name = name;
this.age = age;
}
public void setName(String i){
name = i;
}
public String getName(){
return this.name;
}
public void marry(Boy boy){
System.out.println("我想嫁给" + boy.getName());
boy.marry(this);
}
/**
* @Description 比较两个对象的大小
* @author zbs
* @date Jul 29, 20212:38:18 PM
* @param girl
* @return 正数:当前对象大;负数:当前对象小;0:当前对象与形参对象相等
*/
public void compare(Girl girl){
return this.age - girl.age;
}
}
7. JavaBean
JavaBean是Java语言写成的可重用组件。
JavaBean指的是符合下列标准的Java类:
- 类是公共的;
- 有一个无参数的构造器;
- 有属性,且有对应的get、set方法;
public class Customer{
//属性
private int id;
private String name;
//空参数构造器
public Customer (){
}
//get、set方法
public void setId(int id){
this.id = id;
}
public void setName(String name){
this.name = name;
}
public int getId(){
return id;
}
public String getName(){
return name;
}
}
8. UML类图
9. package 关键字
-
使用package可以更好的实现项目的管理。
-
使用package声明类或接口所属的包,放在源文件的首行。
-
包名是标识符,遵循命名规则。
-
报名中的每一个点都代表一个文件目录。
同一个包下不能命名同名的接口、类;不同的包下可以命名同名的接口、类
10. import 关键字
在源文件中使用import
关键字导入指定包下的类、接口;
声明在包的声明和类的声明之间;
导入多个结构并列写出即可;
可以使用==.*==的方式可导入指定包下的所有结构;
java.lang包下的结构默认已经导入,其中放着Sting、System等常用类;
若使用的类或接口是本包下的,则可以省略import结构;
若在源文件中使用了不同包下的同类名的类,应使用全类名的方式;
使用import导入的包只可使用当前包下的类,而当前包下的子包中的类不可使用,还需要导入;
import static
可导入类或接口中的静态结构;
11. MVC设计模式
实验
按照如下的 UML 类图,创建相应的类,提供必要的结构
在提款方法 withdraw()中,需要判断用户余额是否能够满足提款数额的要求,如果不能,应给出提示。deposit()方法表示存款。
按照如下的 UML 类图,创建相应的类,提供必要的结构
按照如下的 UML 类图,创建相应的类,提供必要的结构
addCustomer 方法必须依照参数(姓,名)构造一个新的 Customer 对象,然后把它放到 customer 数组中。还必须把 numberOfCustomer 属性的值加 1。
getNumOfCustomers 方法返回 numberofCustomers 属性值。
getCustomer 方法返回与给出的 index 参数相关的客户。
创建 BankTest 类,进行测试。
public class Account{
private double balance;
public Account(double init_balance){
this.balance = init_balance;
}
public double getBalance(){
return this.balance;
}
public void deposit(double amt){
if (amt >= 0){
balance += amt;
sysout("存钱成功!");
} else {
sysout("存钱数不能为负数!!");
}
}
public void withdraw(double amt){
if (balance >= amt){
balance -= amt;
sysout("取钱成功!");
} else {
sysout("余额不足,取钱失败!");
}
}
}
public class Customer{
private String firstName;
private String lastName;
private Account account;
public Customer(String f, String l){
this.firstName = f;
this.lastName = l;
}
public String getFirstName(){
return this.firstName;
}
public String getLastName(){
return this.lastName;
}
public Account getAccount(){
return this.account;
}
public void setAccount(Account acct){
this.account = acct;
}
}
public class Bank{
private Customer[] customers;
private int numberOfCustomer;
public Bank(){
customers = new Customer[10];
}
public addCustomer(String f, String l){
customers[numberOfCustomer++] = new Custemou(f, l);
}
public int getNumOfCustomers(){
return this.numberOfCustomer;
}
public Customer getCustomer(int index){
if (index < numberOfCustomer && index >= 0){
return this.customers[index];
}
return null;
}
}
public class Test{
public static void main(String[] args){
Bank bank = new Bank();
bank.addCustomer("zhao", "binsheng");
bank.getCustomer(0).setAccount(new Account(2000));
bank.getCustomer(0).getAccount().withdraw(500);
double balance = bank.getCustomer(0).getAccount().getBalance();
System.out.println("客户:" + bank.getCustomer(0).getFirstName() + "的账户余额为:" + balance);
}
}