面向对象编程
一、类和对象
(1)什么是对象?
万物皆对象,客观存在的事物都是对象。
例如:一部手机,一条狗,一只猫,一张火车票,一个订单等等。
(2)什么是类?
类是对具有相同特征和行为的事物的抽象。
例如:手机、狗、猫、火车票、订单、帅哥、美女等。
类其实就是类型。类的存在是为了弥补数据类型不足的问题。
(3)什么是对象的属性?
**属性是对象具有的各种特征。**对象可以有很多个属性,每个属性都有特定的值。
例如:一部手机有品牌、价格、内存、颜色等属性,不同的手机属性值不同,下面是一部小米手机的属性。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZSswtIjp-1615184582733)(11_面向对象编程/对象的属性.png)]
(4)什么是对象的行为?
行为就是对象能做什么事情,即对象能执行的操作。
例如:手机可以打电话,发短信等。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k1Pynf72-1615184582751)(11_面向对象编程/对象的行为.png)]
(5)类和对象的关系
类是对象的抽象,用于描述对象,是对象的数据类型。
对象是类的具体体现,是类的实例。
例如: 手机是个类,用于描述什么是手机。手机应该具有品牌、价格、颜色等属性,具有打电话、发短信等功能。小米手机是个对象,华为手机是个对象。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ERFe8iN9-1615184582756)(11_面向对象编程/类和对象的关系.png)]
再例如:人是一个类,张三是一个具体的人,是对象。电脑是个类,我的电脑是个对象。
下面哪些是类,哪些是对象?
汽车、宝马、手机、iPhone X、我的iPhone X、狗、藏獒、小红家的藏獒。
注意:类可以细分,细分后的类也是类。细分的类可以认为是子类。
类是Java程序的基本组成单位,任何代码都要依托于类存在。想要做好Java开发必须要掌握类的定义以及对象的使用。
二、类的定义
类是对对象的抽象,是对对象的描述。它来定义对象都有哪些属性,有哪些行为。
因为对象包含属性和行为,由于类是对对象的描述,所以类也包含属性和行为。
对象的属性,在定义类的时候称为实例变量或成员变量或属性。
对象的行为,在定义类的时候称为实例方法或成员方法。
类本质上就是一种自定义的数据类型,定义出来类型以后,可以用这个类型去创建变量(创建的变量称为对象)。
(1)类的定义格式
使用class关键字来定义类。
public class 类名{
//实例变量(或成员变量)
数据类型 变量1;
数据类型 变量2;
....
//实例方法(或成员方法)
方法1;//此方法要去掉static关键字
方法2;//此方法要去掉static关键字
...
}
注意事项:
- 类名使用大驼峰法命名,即每个单词首字母大写。
- 实例方法不能用static修饰。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gsEV0ETn-1615184582761)(11_面向对象编程/类的定义格式.png)]
(2)类的定义示例
-
需求:定义上述的手机类。手机包含品牌、价格,能打电话、发短信。
-
代码:
public class Phone { //实例变量(成员变量) String brand; //品牌 int price; //价格 //实例方法(成员方法) public void call() { System.out.println("打电话"); } public void sendMessage() { System.out.println("发短信"); } }
一个文件中能定义多个类。----只要用class去定义就可以。
但只能有一个public修饰的类。被public修饰的类必须和文件名相同。
之所以只能由一个public修饰的类,是因为一个文件只有一个文件名。
大多数情况下一个文件中只定义一个类。
属性的默认值与数组元素的默认值相同:引用类型的默认值是null,基本数据类型默认值是: 整数是0,小数是0.0,布尔值是false,字符值是0。
三、对象的使用
对象的使用指的是:使用对象的属性 和 使用对象的方法。
要想使用对象,必须先创建对象。
(1)对象的创建
对象创建的格式:
类名 对象名 = new 类名();
对象创建的示例:
Phone phone = new Phone();
如果在一个类中要使用别的类,分2种情况。
- 别的类和当前类在同一个包中。不需要导包。
- 别的类和当前类在不同的包中。需要导包。快捷键 Ctrl+Shift+O
(2)对象的使用
1. 使用对象的属性
使用对象的属性包括:给属性赋值、使用属性的值。
给对象的属性赋值格式:
对象名.属性名 = 值;
示例:
phone.brand = "iPhone";
phone.price = 6888;
使用对象的属性值格式:
数据类型 变量名 = 对象名.属性名;
示例:
String yourPhoneBrand = phone.brand;
int yourPhonePrice = phone.price;
2. 使用对象的方法
使用对象的方法指的就是调用对象的方法。
格式:
对象名.方法名();//如果方法有参数,在小括号内写上实参。如果方法有返回值,可以用变量接收返回值。
示例:
phone.call();//调用打电话方法
phone.sendMessage();//调用发短信方法
(3)测试类和对象
-
新建一个类(ClassTest)用于测试Phone对象的使用,在main方法中创建1部手机,测试其属性和方法。
-
代码:
public class ClassTest { public static void main(String[] args) { //创建phone对象 Phone phone = new Phone(); //给brand、price赋值 phone.brand = "iPhone"; phone.price = 6888; //打印手机的brand和price System.out.println(phone.brand); System.out.println(phone.price); //打电话 phone.call(); //发信息 phone.sendMessage(); } }
四、对象在内存中的表示
(1)单个对象在内存中的表示
public static void main(String[] args) {
//创建phone对象
Phone phone = new Phone();
System.out.println(phone);
//打印手机的brand和price
System.out.println(phone.brand);
System.out.println(phone.price);
//给brand、price赋值
phone.brand = "iPhone";
phone.price = 6888;
//打印手机的brand和price
System.out.println(phone.brand);
System.out.println(phone.price);
//打电话
phone.call();
//发信息
phone.sendMessage();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-liqYsg9B-1615184582766)(11_面向对象编程/单个对象内存图1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zJa6eiFX-1615184582767)(11_面向对象编程/单个对象内存图2.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QcLtbtxm-1615184582780)(11_面向对象编程/单个对象内存图3.png)]
(2)多个对象在内存中的表示
public static void main(String[] args) {
// 创建phone对象
Phone phone = new Phone();
System.out.println(phone);
// 打印手机的brand和price
System.out.println(phone.brand);
System.out.println(phone.price);
// 给brand、price赋值
phone.brand = "iPhone";
phone.price = 6888;
// 打印手机的brand和price
System.out.println(phone.brand);
System.out.println(phone.price);
// 打电话
phone.call();
// 发信息
phone.sendMessage();
System.out.println("--------------");
// 创建phone对象
Phone phone2 = new Phone();
System.out.println(phone2);
// 打印手机的brand和price
System.out.println(phone2.brand);
System.out.println(phone2.price);
// 给brand、price赋值
phone2.brand = "华为";
phone2.price = 2999;
// 打印手机的brand和price
System.out.println(phone2.brand);
System.out.println(phone2.price);
// 打电话
phone2.call();
// 发信息
phone2.sendMessage();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CbD6jkDV-1615184582788)(11_面向对象编程/单个对象内存图1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SkDlvKfY-1615184582790)(11_面向对象编程/单个对象内存图2.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FNREEASx-1615184582792)(11_面向对象编程/单个对象内存图3.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XxctG46C-1615184582793)(11_面向对象编程/多个对象内存图1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QZT5wJsh-1615184582796)(11_面向对象编程/多个对象内存图2.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dm2dwQ2W-1615184582801)(11_面向对象编程/多个对象内存图3.png)]
(3)多个对象指向相同堆区
public static void main(String[] args) {
// 创建phone对象
Phone phone = new Phone();
System.out.println(phone);
// 打印手机的brand和price
System.out.println(phone.brand);
System.out.println(phone.price);
// 给brand、price赋值
phone.brand = "iPhone";
phone.price = 6888;
// 打印手机的brand和price
System.out.println(phone.brand);
System.out.println(phone.price);
// 打电话
phone.call();
// 发信息
phone.sendMessage();
System.out.println("--------------");
Phone phone2 = phone;
System.out.println(phone2);
// 打印手机的brand和price
System.out.println(phone2.brand);
System.out.println(phone2.price);
// 给brand、price赋值
phone2.brand = "华为";
phone2.price = 2999;
// 打印手机的brand和price
System.out.println(phone2.brand);
System.out.println(phone2.price);
// 打电话
phone2.call();
// 发信息
phone2.sendMessage();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qEgkMRQX-1615184582804)(11_面向对象编程/单个对象内存图1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F5JwT5Pu-1615184582805)(11_面向对象编程/单个对象内存图2.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GUy12N48-1615184582807)(11_面向对象编程/单个对象内存图3.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s0DgkGVe-1615184582809)(11_面向对象编程/指向相同对象1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j7v0Wz7K-1615184582810)(11_面向对象编程/指向相同对象2.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1dO2mW7e-1615184582812)(11_面向对象编程/多个对象内存图3.png)]
new出来的内存区域才是真正的对象。
phone和phone2只是保存了对象的地址,相当于phone和phone2代表了对象。我们习惯把这种保存了对象地址的变量叫做对象。
如果多个变量指向同一个对象,那么任何一个变量都可以修改对象的内容。
五、类和对象练习
(1)学生类
-
需求:创建一个代表学生的类,并创建学生对象测试效果。属性只写出姓名、年龄即可,方法只写出学习、做作业即可。
-
代码:
学生类代码:
public class Student { //属性 String name; //姓名 int age; //年龄 //方法 public void study() { System.out.println(name + "正字努力学习Java面向对象"); } public void doHomework() { System.out.println("十万代码成就十万年薪。" + name + "正在努力做练习,离10万年薪越来越近了"); } }
测试代码:
public static void main(String[] args) { Student stu = new Student(); stu.name = "马化腾"; stu.age = 22; stu.study(); stu.doHomework(); Student stu2 = new Student(); stu2.name = "李彦宏"; stu2.age = 19; stu2.study(); stu2.doHomework(); }
实例变量(成员变量)和局部变量:
实例变量:定义在类中方法外的变量。
局部变量:在方法中定义的变量(包括方法的形参)。
区别 实例变量(成员变量) 局部变量 类中定义的位置不同 类中方法外 方法内(含形参) 内存中位置不同 堆内存 栈内存 生命周期不同 随着对象的存在而存在,对着对象的消失而消失 随着方法的调用而存在,随着方法的调用完毕而消失 初始值不同 有默认的初始值 没有默认的初始值,必须先定义,定义时要给初始值。 作用域 1.类内;2.对象使用时,随着对象使用。 只能在方法内使用,出了方法将不能使用。
(2)圆
-
设计一个代表圆的类。要求能计算圆的周长和面积。
-
代码:
圆类代码:
public class Circle { //属性 double r; //半径 //方法 //计算周长 public double primeter() { return 2 * 3.1415926 * r; } //计算面积 public double area() { return 3.1415926 * r * r; } }
测试代码:
public static void main(String[] args) { Circle c1 = new Circle(); c1.r = 10; System.out.println(c1.primeter()); System.out.println(c1.area()); Circle c2 = new Circle(); c2.r = 4; System.out.println(c2.primeter()); System.out.println(c2.area()); }
(3)教师数组
-
定义一个教师类,教师包含工号(num)和工资(salary)两个属性。定义一个数组,保存10名教师的信息。教师的编号从100开始,教师的工资是[8k, 15k]之间的一个数。打印出薪资是12k的教师的信息;对教师按薪资从小到大进行排序。
-
代码:
教师类代码:
public class Teacher { int num; //工号 int salary; //工资 public void showInfo() { System.out.println("工号:" + num + ",工资:" + salary); } }
测试代码:
public static void main(String[] args) { // 创建一个数组,保存10个教师 Teacher[] teachers = new Teacher[10]; // 创建Random对象,用于随机工资。 Random random = new Random(); // 通过循环创建10个教师,并放入数组中。 for (int i = 0; i < teachers.length; i++) { Teacher t = new Teacher(); t.num = i + 100; t.salary = (random.nextInt(15 - 8 + 1) + 8) * 1000; teachers[i] = t; } // 遍历数组 for (int i = 0; i < teachers.length; i++) { teachers[i].showInfo(); } System.out.println("-----------"); // 查找工资为12k的教师 for (int i = 0; i < teachers.length; i++) { if (teachers[i].salary == 12000) { teachers[i].showInfo(); } } System.out.println("-----------"); // 按薪资从小到大对教师进行排序。冒泡法 for (int i = 0; i < teachers.length - 1; i++) { for (int j = 0; j < teachers.length - 1 - i; j++) { if (teachers[j].salary > teachers[j + 1].salary) { Teacher temp = teachers[j]; teachers[j] = teachers[j + 1]; teachers[j + 1] = temp; } } } // 遍历数组 for (int i = 0; i < teachers.length; i++) { teachers[i].showInfo(); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-84JWDA6P-1615184582814)(11_面向对象编程/教师数组.png)]
六、面向对象编程、面向过程编程
面向对象编程(Object Oriented Programing)是相对于面向过程编程(Procedure Oriented Programing)而言的。它们是2种不同的编程思想。
(1)面向过程编程(POP)
面向过程编程是一种以功能为中心来进行思考和组织的编程方法,它关注的是功能如何实现,就是分析出解决问题所需要的的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个调用就可以了。----关注的是功能的实现
(2)面向对象编程(OOP)
面向对象编程是一种以事物(对象)为中心的编程思想,它关注的是完成某件事情需要哪些对象参与,这些对象应该具有什么样的属性和方法。通过对象的协作完成某件事情。----关注的是参与者(类)怎么设计
(3)面向对象编程和面向过程编程的区别
需求:把大象关进冰箱里
面向过程编程
-
编写函数实现打开冰箱门的功能
编写函数实现把大象装进去的功能
编写函数实现把冰箱门关闭的功能
-
在main函数中依次调用函数:打开冰箱门、把大象装进去、把冰箱门关上。
面向对象编程
-
分析把大象关进冰箱需要什么对象参与:大象、冰箱、人。
-
设计各个类
public class 冰箱{ public void 开门(){ ... } public void 关门(){ ... } } public class 大象{ public void 进入(冰箱 icebox){ ... } } public class 人{ public void 打开(冰箱 icebox){ icebox.开门(); } public void 抬起(大象 elephant){ ... elephant.进入(icebox); ... } public void 关闭(冰箱 icebox){ icebox.关门(); } }
-
在main方法中:
创建人(张三),创建冰箱(海尔冰箱),创建大象(艾米丽)
张三.打开(海尔冰箱);
张三.抬起(艾米丽);
张三.关闭(海尔冰箱);
无论面向过程,还是面向对象都能实现把大象装进冰箱这么一件事。从上述的过程中,我们不难发现,面向过程编程的核心是完成某件事;而面向对象的核心是设计参与者,最后才去完成具体的事。
项目种类越大,面向对象越有优势,而面向过程这越难以实现。例如:拍一个几分钟的小品,可以使用面向过程的思想,从头到尾设计每个细节。但拍一部电影,或者拍摄一部电视剧就得用面向对象的思想了,对每个参与者进行分工,各自干好自己的事情,镜头也未必从前往后拍摄,今天下雨可以先拍下雨的戏份,今天到场的演员多,可以拍摄演员多的戏份,最后进行剪辑拼接制作成电影和电视剧。
七、封装
面向对象语言有三大特性:封装、继承和多态。
在讲解**封装(encapsulation)**的概念之前,我们先学习一下private关键字。
(1)private
先看下面的代码:
有一个Student类
public class Student {
//实例变量
String name; //姓名
int age; //年龄
//实例方法
public void study() {
System.out.println(name + "正在努力学习Java面向对象的知识。");
}
public void doHomework() {
System.out.println(name + "在努力的写作业。十万行代码成就你十万年薪");
}
public void showInfo() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
Student类创建了2个对象。
public static void main(String[] args) {
Student stu1 = new Student();
stu1.name = "马化腾";
stu1.age = 22;
stu1.showInfo();
Student stu2 = new Student();
stu2.name = "李彦宏";
stu2.age = -20;
stu2.showInfo();
}
输出结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XfFC7aCx-1615184582816)(11_面向对象编程/输出结果.png)]
上述的输出结果是没有错的,程序也正常执行的。但是人的年龄是-20是有点违背逻辑的。
之所以出现这种问题,是因为外界可以直接操作属性。想要避免这种问题,最好的办法是不让外界直接操作属性,把属性隐藏起来,给外界提供访问属性的方法。
1. 通过private把属性隐藏起来
private单词的含义是私有的。private可以修饰类中的属性和方法,修饰属性的时候,表示属性私有,外界无法直接访问(所谓的直接访问是通过对象名.属性名访问),但是在本类中可以访问。
public class Student {
//实例变量
private String name; //姓名
private int age; //年龄
//实例方法
public void study() {
System.out.println(name + "正在努力学习Java面向对象的知识。");
}
public void doHomework() {
System.out.println(name + "在努力的写作业。十万行代码成就你十万年薪");
}
public void showInfo() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
上述代码实现了属性的私有,一旦是有,外界将不能使用对象名.属性名去访问对象的属性。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LINNQysO-1615184582818)(11_面向对象编程/不能访问私有属性.png)]
2. 通过setter、getter方法访问属性
setter方法:为属性(实例变量)赋值的方法。
getter方法:获取属性(实例变量)值的方法。
由于getter、setter方法是我们特意为外界定义的方法,方便外界能访问属性,因此要有public修饰。
public String getName() {
return name;
}
public void setName(String n) {
name = n;
}
public int getAge() {
return age;
}
public void setAge(int a) {
age = a;
}
setter方法既然是为属性赋值,就必须要有参数,而且参数的类型和属性的类型相同。setter方法的方法名是set+首字母大写的属性名。例如:属性名是name,setter方法的方法名是setName,属性名是age,setter方法的方法名是setAge。
getter方法既然是要获取属性的值,就必须有返回值,而且返回值的类型和属性的类型相同。getter方法的方法名是get+首字母大写的属性名。例如:属性名是name,getter方法的方法名是getName,属性名是age,getter方法的方法名是getAge。
提供了方法以后,外界就可以通过方法去访问属性。
public static void main(String[] args) {
Student stu1 = new Student();
stu1.setName("马化腾");
stu1.setAge(22);
stu1.showInfo();
Student stu2 = new Student();
stu2.setName("李彦宏");
stu2.setAge(-20);
stu2.showInfo();
}
输出结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6J61eZuA-1615184582819)(C:\Users\lx\Desktop\java\输出结果.png)]
输出结果还是-20,并没有解决任何问题呀!稍作改进,改进代码如下:
public void setAge(int a) {
if(a < 0) {
System.out.println("您输入的年龄有误。");
}else {
age = a;
}
}
输出结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KArzP095-1615184582821)(11_面向对象编程/输出结果2.png)]
3. setter/getter和直接访问的异同
相同点:都是在访问属性,包括赋值和取值。
不同点:1.直接访问属性优势是简单快捷,一步到位;劣势是有可能会出现数据错误。2.通过setter/getter访问属性优势是数据没有直接赋值给属性,在赋值之前可以做一些操作和处理;劣势是代码量略大。
在开发中属性一般情况下都定义private,对外提供pulic的getter和setter方法。
(2)this
讲解this之前,先看一下刚才写的setter、getter方法
public String getName() {
return name;
}
public void setName(String n) {
name = n;
}
public int getAge() {
return age;
}
public void setAge(int a) {
if(a < 0) {
System.out.println("您输入的年龄有误。");
}else {
age = a;
}
}
上面方法中的参数并没有做到见名知意。如果做到见名知意,应改为:
public String getName() {
return name;
}
public void setName(String name) {
name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age < 0) {
System.out.println("您输入的年龄有误。");
}else {
age = age;
}
}
改成这样以后,确实见名知意了,但是程序的结果却不对了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tefwva7I-1615184582822)(11_面向对象编程/输出结果3.png)]
这是因为:在一个类中,如果局部变量和实例变量名称相同,在方法中使用变量的时候,使用的是局部变量,而不是实例变量。想要在方法中使用实例变量的话,需要使用this.实例变量名。
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age < 0) {
System.out.println("您输入的年龄有误。");
}else {
this.age = age;
}
}
this的英文含义是这个,在代码中this是一个特殊的对象,它始终指的是调用方法的对象,即谁调用方法,this就是谁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o2P2BnYk-1615184582824)(11_面向对象编程/this是谁.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tZMZzvLr-1615184582825)(11_面向对象编程/this是谁2.png)]
可以通过打印内存地址的方式,来检验this究竟是谁。
this关键字
- this修饰的变量用于指代实例变量。方法的形参如果与实例变量同名,不带this的变量是形参,而不是实例变量。方法的形参如果和实例变量不同名,不需要用this修饰。
- this代表的就是调用方法的那个对象。
(3)构造方法
构造方法是一种特殊的方法,它是创建对象时调用的方法,用于在创建对象的时候对属性进行初始化。
Student stu1 = new Student();//Student()就是一个构造方法。
1. 构造方法的特点
- 构造方法只能用在对象创建的时候。
- 构造方法的作用是对属性进行初始化。
- 构造方法的方法名必须和类名相同。
- 构造方法没有返回值,连void都不能写。
- 如果自己没有提供构造方法,系统会默认生成一个无参数的构造方法。
- 如果自己提供了任何一个构造方法,系统将不再生成无参数的构造方法。
- 构造方法可以重载。
2. 构造方法的书写格式
public 类名(参数列表){
}
3. 构造方法示例
public Student() {
}
public Student(String name) {
this.name = name;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
上面的三个方法都是构造方法。
4. 构造方法的使用
public static void main(String[] args) {
Student stu1 = new Student("马化腾",22);
stu1.showInfo();
Student stu2 = new Student("李彦宏");
stu2.setAge(20);
stu2.showInfo();
Student stu3 = new Student();
stu3.setName("马云");
stu3.setAge(25);
stu3.showInfo();
}
在开发过程中,我们通过会至少给2个构造方法。其中一个是无参构造方法,另外一个是全参的构造方法。除此以外根据需求添加别的构造方法。
(4)标准的Java类
1. 标准Java类的定义原则
- 属性用private修饰
- 提供属性对应的setter、getter方法
- 提供1个或多个构造方法
- 提供正常的功能性方法
2. 标准Java类示例
public class Circle {
//属性
private double r; //半径
public double getR() {
return r;
}
public void setR(double r) {
this.r = r;
}
public Circle() {
}
public Circle(double r) {
this.r = r;
}
//实例方法
public double perimeter() {
return 2 * Math.PI * r;
}
public double area() {
return Math.PI * r * r;
}
}
使用圆的时候:
public static void main(String[] args) {
// 使用无参构造创建对象
Circle c1 = new Circle();
c1.setR(10);
c1.perimeter();
c1.area();
// 使用有参构造创建对象
Circle c2 = new Circle(4);
c2.perimeter();
c2.area();
}
(5)类的练习(Fraction类)
-
需求:定义一个分数(Fraction)类,包含分子(numerator)和分母(denominator)2个属性。实现分数的加、减、乘、除、约分以及打印分数功能。
-
代码:
public class Fraction { private int numerator; // 分子 private int denominator; // 分母 public int getNumerator() { return numerator; } public void setNumerator(int numerator) { this.numerator = numerator; } public int getDenominator() { return denominator; } public void setDenominator(int denominator) { this.denominator = denominator; } // 无参构造 public Fraction() { } // 全参构造 public Fraction(int numerator, int denominator) { this.numerator = numerator; this.denominator = denominator; } // 打印分数 public void showInfo() { // 是否是负数 boolean isNegative = numerator * denominator < 0 ? true : false; // 定义一个表示分数的字符串。 String fractionString = ""; if (isNegative == true) {// 如果是负数,加上负号 fractionString += "-"; } fractionString += Math.abs(numerator);// 拼上分子的绝对值 fractionString += "/";// 拼上 / fractionString += Math.abs(denominator);// 拼上分母的绝对值 System.out.println(fractionString); } // 约分 public void reduce() { // 拿到分子分母的绝对值 int num1 = Math.abs(numerator); int num2 = Math.abs(denominator); // 定义一个变量接收最大公约数 int gcd = greatestCommonDivisor(num1, num2); // 更新分子和分母 numerator /= gcd; denominator /= gcd; } // 最大公约数 ----不想对外公开的方法,用private修饰。最大公约数只是约分的一个中间过程,不应该对外公开 private int greatestCommonDivisor(int x, int y) { // 辗转相除法找最大公约数。 while (x % y != 0) { int temp = x % y; x = y; y = temp; } return y; } // 求和 public Fraction add(Fraction fraction) { // 求和后的分子 int num1 = this.numerator * fraction.denominator + this.denominator * fraction.numerator; // 求和后的分母 int num2 = this.denominator * fraction.denominator; Fraction result = new Fraction(num1, num2); result.reduce(); return result; } // 求差 public Fraction minus(Fraction fraction) { // 求差后的分子 int num1 = this.numerator * fraction.denominator - this.denominator * fraction.numerator; // 求差后的分母 int num2 = this.denominator * fraction.denominator; Fraction result = new Fraction(num1, num2); result.reduce(); return result; } // 求积 public Fraction multiply(Fraction fraction) { // 求积后的分子 int num1 = this.numerator * fraction.numerator; // 求积后的分母 int num2 = this.denominator * fraction.denominator; Fraction result = new Fraction(num1, num2); result.reduce(); return result; } // 求商 public Fraction divide(Fraction fraction) { // 求商后的分子 int num1 = this.numerator * fraction.denominator; // 求商后的分母 int num2 = this.denominator * fraction.numerator; Fraction result = new Fraction(num1, num2); result.reduce(); return result; } }
private关键字,不但可以修饰属性,还可以修饰方法。被private的修饰的方法,不能被外界访问,但是在本类中是可以访问的。
一般完整的功能用public修饰,不完整的功能(只是某功能的一个步骤)通常用private修饰。这样可以避免外界误用了不完整的功能,导致逻辑错误。
测试代码:
public static void main(String[] args) { // 定义一个分数 3/5 Fraction f1 = new Fraction(3, 5); f1.showInfo(); // 定义一个分数-2/4 Fraction f2 = new Fraction(2, -4); f2.reduce();// 约分 f2.showInfo(); // 计算 3/5 + (-2/4)的和 Fraction sum = f1.add(f2); sum.showInfo(); // 计算 3/5 - (-2/4)的差 Fraction minus = f1.minus(f2); minus.showInfo(); // 计算 (3/5) * (-2/4)的乘积 Fraction multiply = f1.multiply(f2); multiply.showInfo(); // 计算 (3/5) / (-2/4)的商 Fraction divide = f1.divide(f2); divide.showInfo(); }
(6)封装
封装:隐藏对象的内部细节,对外提供接口(访问方式)。
**封装的原则:**将类的某些信息隐藏在类内部,不允许外界直接访问,而是给外界提供接口(方法)。外界通过接口访问内部的数据以及类的功能。
-
getter、setter封装了实例变量。
-
方法封装了功能的实现细节。
-
类封装了属性和方法。
封装的好处:
-
通过方法来控制实例变量的操作,提高了代码的安全性。
-
把代码用方法进行封装,提高了代码的复用性。
八、继承
**继承(inherit)**也是面向对象三大特性之一。
生活中的继承:某某继承了父亲百亿资产;某某继承了家族产业等等。
简单来看,继承就是获得了原本不属于自己的东西。在Java中亦是如此。
(1)教师、辅导员、学生
某公司正在开发一款高校教学、教务管理系统。其中涉及到教师(Teacher)、辅导员(Assistant)、学生(Student)的信息管理。抛开教师、辅导员、学生的增删改查,如何用Java类表示教师、辅导员、学生?
通过分析:我们需要3个类Teacher、Assistant、Student。
Teacher类有姓名、性别、年龄、职称,能授课、能管理学生。
Assistant类有姓名、性别、年龄,能管理学生。
Student类有姓名、性别、年龄,能学习。
Teacher类:
public class Teacher {
//属性
private String name; //姓名
private String sex; //性别
private int age; //年龄
private String title; //职称
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
//构造方法
public Teacher() {
}
public Teacher(String name, String sex, int age, String title) {
this.name = name;
this.sex = sex;
this.age = age;
this.title = title;
}
//方法
public void teach() {
System.out.println(name + "老师正在认真的讲课");
}
public void manageStudents() {
System.out.println("管理学生");
}
public void showInfo() {
System.out.println(name + "," + sex + "," + age + "," + title);
}
}
Assistant类:
public class Assistant {
//属性
private String name; //姓名
private String sex; //性别
private int age; //年龄
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//构造方法
public Assistant() {
}
public Assistant(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
//方法
public void manageStudents() {
System.out.println("管理学生");
}
public void showInfo() {
System.out.println(name + "," + sex + "," + age);
}
}
Student类:
public class Student {
//属性
private String name; //姓名
private String sex; //性别
private int age; //年龄
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//构造方法
public Student() {
}
public Student(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
//方法
public void study() {
System.out.println("Good good study, day day up");
}
public void showInfo() {
System.out.println(name + "," + sex + "," + age);
}
}
测试代码:
public static void main(String[] args) {
Teacher t = new Teacher("张三", "男", 32, "教授");
t.showInfo();
t.teach();
t.manageStudents();
Assistant a = new Assistant("李思思", "女", 27);
a.showInfo();
a.manageStudents();
Student stu1 = new Student("张磊", "男", 21);
stu1.showInfo();
stu1.study();
Student stu2 = new Student("李静", "女", 20);
stu2.showInfo();
stu2.study();
}
通过观察,你会发现这3个类有部分特征和行为是相同的。都有姓名、性别、年龄属性,都有对应的setter、getter方法。如果要完整的表示这3个类,会更多的属性,更多的setter、getter方法。
如果一个项目比较大,有几百个类,要写多少代码?能不能优化?
(2)教师、辅导员、学生使用继承
Java提供了继承(inherit)功能。继承可以解决我们上述的问题。
父类:在继承关系里,被继承的类叫做父类,也叫基类或超类。
子类:在继承关系里,继承别的类的类叫子类,也叫派生类。
父类和子类是相对而言的。
1. 继承的语法
public class 类名 extends 父类名{
//属性
//方法
}
我们可以将上述案例中相同的特征和行为进行抽取,放到一个单独的类中,把这个类作为父类,再让现有的类继承于这个单独的类,这样就可以继承父类中的特征和行为。
公共父类Person
public class Person {
//属性
private String name; //姓名
private String sex; //性别
private int age; //年龄
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Teacher类
public class Teacher extends Person{
//属性
private String title; //职称
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
//构造方法
public Teacher() {
}
public Teacher(String name, String sex, int age, String title) {
this.setName(name);
this.setSex(sex);
this.setAge(age);
this.title = title;
}
//方法
public void teach() {
System.out.println(this.getName() + "老师正在认真的讲课");
}
public void manageStudents() {
System.out.println("管理学生");
}
public void showInfo() {
System.out.println(this.getName() + "," + this.getSex() + "," + this.getAge() + "," + title);
}
}
Assistant类:
public class Assistant extends Person{
//属性
//构造方法
public Assistant() {
}
public Assistant(String name, String sex, int age) {
this.setName(name);
this.setSex(sex);
this.setAge(age);
}
//方法
public void manageStudents() {
System.out.println("管理学生");
}
public void showInfo() {
System.out.println(this.getName() + "," + this.getSex() + "," + this.getAge());
}
}
Student类:
public class Student extends Person{
//属性
//构造方法
public Student() {
}
public Student(String name, String sex, int age) {
this.setName(name);
this.setSex(sex);
this.setAge(age);
}
//方法
public void study() {
System.out.println("Good good study, day day up");
}
public void showInfo() {
System.out.println(this.getName() + "," + this.getSex() + "," + this.getAge());
}
}
测试代码:
public static void main(String[] args) {
Teacher t = new Teacher("张三", "男", 32, "教授");
t.showInfo();
t.teach();
t.manageStudents();
Assistant a = new Assistant("李思思", "女", 27);
a.showInfo();
a.manageStudents();
Student stu1 = new Student("张磊", "男", 21);
stu1.showInfo();
stu1.study();
Student stu2 = new Student("李静", "女", 20);
stu2.showInfo();
stu2.study();
}
通过继承,代码明显少了很多,而且能实现继承前全部的功能。但美中不足的地方是,构造方法赋值比较麻烦,能不能优化的?
2. 父类、子类构造方法
子类可以从父类中继承属性和方法,无法继承构造方法,但可以调用父类中的构造方法为我们继承过来的属性赋值。
Person类中添加构造方法:
public class Person {
//属性
private String name; //姓名
private String sex; //性别
private int age; //年龄
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//父类构造方法
public Person() {
}
public Person(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
}
Teacher类修改构造方法:
public class Teacher extends Person{
//属性
private String title; //职称
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
//构造方法
public Teacher() {
}
public Teacher(String name, String sex, int age, String title) {
super(name, sex, age);
this.title = title;
}
//方法
public void teach() {
System.out.println(this.getName() + "老师正在认真的讲课");
}
public void manageStudents() {
System.out.println("管理学生");
}
public void showInfo() {
System.out.println(this.getName() + "," + this.getSex() + "," + this.getAge() + "," + title);
}
}
Assistant类修改构造方法:
public class Assistant extends Person{
//属性
//构造方法
public Assistant() {
}
public Assistant(String name, String sex, int age) {
super(name, sex, age);
}
//方法
public void manageStudents() {
System.out.println("管理学生");
}
public void showInfo() {
System.out.println(this.getName() + "," + this.getSex() + "," + this.getAge());
}
}
Student类修改构造方法:
public class Student extends Person{
//属性
//构造方法
public Student() {
}
public Student(String name, String sex, int age) {
super(name, sex, age);
}
//方法
public void study() {
System.out.println("Good good study, day day up");
}
public void showInfo() {
System.out.println(this.getName() + "," + this.getSex() + "," + this.getAge());
}
}
修改之后,代码仍然能正确的运行。
3. super关键字
super是一个特殊的关键字,这个关键字用于访问父类中的方法和属性(super相当于父类类名)。
注:private修饰的属性和方法无法直接访问(super.属性名或者super.方法名())。因为private修饰的属性和方法只能在本类中访问。
虽然不能访问,但肯定是继承了。
通过下面的案例学习一下super关键字的用法。
定义2个类,Son类和Father类,Son继承于Father。
Father类代码:
public class Father {
//属性
int a;
private int b;
//getter、setter
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
//构造方法
public Father() {
}
public Father(int a, int b) {
this.a = a;
this.b = b;
}
//打印对象信息
public void showInfo() {
System.out.println("a = " + a + ",b = " + b);
}
//私有方法--私有方法只能在类内部供类中别的方法调用,例如在publicMethod方法中访问
private void privateMethod() {
System.out.println("这是私有方法");
}
//公开方法,在这个方法中调用私有方法。
public void publicMethod() {
privateMethod();//等价于this.privateMethod();
}
}
Son类代码:
public class Son extends Father {
//属性
private int a;
private int c;
//setter、getter
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public int getC() {
return c;
}
public void setC(int c) {
this.c = c;
}
//构造方法
public Son() {
super();
}
public Son(int a, int b) {
super(a, b);
}
public Son(int a, int b, int c) {
super(a, b);
this.c = c;
}
//打印自身信息
public void showInfo() {
System.out.println("a = " + a + ",b = " + this.getB() + ",c = " + c);
}
//测试super调用父类方法
public void testSuperMethod() {
super.showInfo();
}
//测试super调用父类属性
public void testSuperProperty() {
System.out.println(a);
System.out.println(this.a);
System.out.println(super.a);
//私有属性不能直接访问,因为private修饰的属性,只能在本类中直接访问。我们可以通过方法间接访问。
//System.out.println(super.b);//报错
System.out.println(super.getB());
}
//测试调用父类中的 私有方法
public void testSuperPrivateMthod() {
//私有方法不能直接访问,因为private修饰的方法,只能在本类中直接方法。我们可以通过方法间接访问。
//super.privateMethod();//报错
super.publicMethod();
}
}
public static void main(String[] args) {
Son son = new Son(10,20,30);
son.showInfo();//如果子类和父类有同名方法,调用的是自身的方法,不是父类继承过来的方法。
son.testSuperMethod();//测试super调用父类方法
son.testSuperPrivateMthod();//测试super调用父类私有方法
son.testSuperProperty();//测试super调用父类属性
son.publicMethod();//调用继承过来的方法
son.setB(100);//调用继承过来的setter方法
son.showInfo();
}
- 通过super访问父类构造方法。语法:super(参数列表); 主要作用:给继承过来的属性赋初始值。通过super调用父类构造方法时,代码必须写在第一行。
- 通过super访问父类中的其他方法(非私有)。语法:super.方法名(参数列表);
- 通过super访问父类中的属性(非私有)。语法:super.属性名;
4. this关键字
前面的课程,我们讲了this关键是一个特殊的对象,代指当前对象,即谁调用方法,this就是谁。this可以用于区分实例变量和局部变量。
this除了用于区分实例变量和局部变量之外,还有以下2个功能:
- this调用本类的其他构造方法。语法:this(参数列表); 通过this调用本类构造方法时,代码必须写在第一行。
- this调用本类中其他的方法。(this可省略)
5. this和super的区别
比较事项 | this | super |
---|---|---|
访问实例变量 | this.实例变量 访问本类中的实例变量 | super.实例变量 访问父类中的实例变量(非私有) |
访问实例方法 | this.实例方法(…) 访问本类中的实例方法 | super.实例方法(…) 访问父类中的实例方法(非私有) |
访问构造方法 | this(…) 调用本类中的构造方法,写在构造方法的第一行 | super(…) 调用父类中的构造方法,写在构造方法的第一行 |
是否是对象 | 是对象,代表当前对象 | 不是对象,只是关键字 |
继承中构造方法的访问特点:如果子类构造方法中没有明确写出调用哪个父类构造方法,会默认调用父类的无参构造方法,即super(); -----即使你没有写super(),系统也会默认在第一行代码中调用super()。如果父类不提供无参构造方法,提供有参数的构造方法,子类会报错。
继承中实例方法的访问特点:1.首先在子类中查找要调用的方法,如果有直接调用;2.如果没有,在父类中查找要调用的方法,如果有就执行;3.如果没有,继续向上找父类,如果更上一级父类中有要调用的方法,就执行,如果没有继续向上找,直到根类Object,如果Object中也没有,就报错。
在继承中,子类对象的堆区内存里,既会有子类自身实例变量,也会有父类继承来的实例变量。如果子类和父类有同名的变量,堆内存会有2个变量。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rbCicdq4-1615184582827)(11_面向对象编程/子类的堆区内存表示.png)]
6. 方法重写
方法重写:在继承关系里,如果子类中的某个方法和父类中的方法相同(方法名和参数也要相同),称为子类重写了父类的方法。子类重写父类方法往往是因为父类的方法满足不了子类的需求,所以子类才需要自己实现这个方法。
方法重写、方法重载的区别:
方法重载:在同一个类中,如果多个方法具有相同的方法名,但是参数个数或者参数类型不同(或都不同),这称为方法的重载。
方法重写:在继承关系里,父类的实现满足不了子类的需求,子类可以重新实现父类中定义的方法,这是方法重写。
如果子类重写了父类的方法,我们习惯上在重写的方法上面添加@Override注解。—注解是我们后面要学习的内容。@Override注解的作用是检测方法是否和父类中的方法相同。
(3)继承
继承是Java提供一种语法,这种语法允许子类得到父类的属性和方法。
继承能在保证程序功能不变的情况下,大大简化代码量,因此继承能帮我们更好的去设计类。
在Java中使用extends关键字实现继承。被继承的类叫做父类,或超类,或基类。
1. Java中继承的特点
- 一个类只能有一个父类。----不允许多继承
- 子类会继承父类全部的属性和方法(私有的也能继承,只是不能直接访问),无法继承构造方法。
- 如果要访问父类中的属性、方法、构造方法使用super关键字。
- 子类可以重写父类的方法。调用时默认调用子类的方法。
- 可以多层继承。A extends B,B extends C,这样A将拥有B和C中的内容
- 所有类的根类是Object。如果一个类没有继承任何类,默认继承Object类。
- 通常在子类的构造方法中调用父类的构造方法。
根类:没有父类的类。已经是最顶层的类了。
2. 继承的好处和弊端
继承的好处:
- 提高了代码的复用性(多个类相同的内容可以放到同一个类中)。
- 提高了代码的维护性(如果方法的代码需要修改,修改一处即可)。
继承弊端:
- 继承让类与类之间产生了关联,类的耦合性增强了,当父类发生变化时,子类也不得不跟着变化,削弱了子类的独立性。
什么时候使用继承?
- 继承体现的关系是: is a (是一个)
- 如果两个类A和B,如果他们在逻辑上满足A是B的一种,或者B是A的一种,就说明他们是继承关系,这个时候可以使用继承来体现,否则就是滥用继承。
- 例如:苹果和水果,苹果属于水果,可以让苹果类继承于水果类。再例如:苹果手机和手机,苹果手机是手机的一种,可以让苹果手机类继承于手机类。再例如:猫和狗,不能让猫继承于狗,也不能让狗继承于猫,而是定义一个动物类,他们都继承于动物。
(4)继承示例
-
需求:通过继承设计猫(Cat)和狗(Dog)类,并编写代码测试。猫有姓名和年龄属性,有抓老鼠的方法(catchMouse)。狗有姓名和年龄,有看门的方法(lookDoor)。
-
代码:
父类Animal:
public class Animal { //实例变量 private String name; //姓名 private int age; //年龄 //setter、getter public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //构造方法 public Animal() { super(); } public Animal(String name, int age) { super(); this.name = name; this.age = age; } }
Cat类:
public class Cat extends Animal { public Cat() { super(); } public Cat(String name, int age) { super(name, age); } public void catchMouse() { System.out.println("抓老鼠"); } }
Dog类:
public class Dog extends Animal { public Dog() { super(); } public Dog(String name, int age) { super(name, age); } public void lookDoor() { System.out.println("看门"); } }
测试代码:
public static void main(String[] args) { Cat c1 = new Cat("花花", 2); c1.catchMouse(); System.out.println(c1.getName() + "," + c1.getAge()); Dog d1 = new Dog("旺财", 5); d1.lookDoor(); System.out.println(d1.getName() + "," + d1.getAge()); }
-
需求:通过继承设计枪(Gun)和匕首(Dagger)类。枪和匕首都有名称、攻击力、价格。枪可以发射子弹,能更换弹夹;匕首可以攻击。
-
代码:
公共父类Weapon:
public class Weapon { private String name; //武器的名字 private int ap; //攻击力 private int price; //价钱 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAp() { return ap; } public void setAp(int ap) { this.ap = ap; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } public Weapon() { super(); } public Weapon(String name, int ap, int price) { super(); this.name = name; this.ap = ap; this.price = price; } public void showInfo() { System.out.println("武器名称:" + name + ",攻击力:" + ap + ",价格:" + price); } }
Gun类:
public class Gun extends Weapon { public Gun() { super(); } public Gun(String name, int ap, int price) { super(name, ap, price); } //发射子弹 public void fire() { System.out.println("biubiu~biu"); } //更换弹夹 public void reload() { System.out.println("更换弹夹"); } }
Dagger类:
public class Dagger extends Weapon { public Dagger() { super(); } public Dagger(String name, int ap, int price) { super(name, ap, price); } //攻击 public void attack() { System.out.println("砍敌人,刺敌人"); } }
测试代码:
public static void main(String[] args) { Gun gun = new Gun("AWM", 1000, 5000); gun.showInfo();//调用继承的方法 gun.fire(); gun.reload(); Dagger dagger = new Dagger("猛虎刀", 500, 2000); dagger.showInfo();//调用继承的方法 dagger.attack(); }
九、多态
**多态(Polymorphism)**是面向对象三大特征的最后一个特征。
(1)什么是多态
多态:指的是对象的多态性,同一对象在不同时刻表现出来的不同形态。
例如:猫
我们可以说猫是猫:猫 cat = new 猫();
也可以说猫是动物:动物 animal = new 猫();
这里猫在不同的时刻表现出来了不同的形态,就是多态。
(2)程序中的多态
多态的前提:
- 有继承或者实现关系
- 有方法重写
- 父类引用指向子类对象(或子类的对象赋值给父类的引用)
实现关系:后面会学习接口(interface),类和接口就是实现关系,类实现接口。
实现关系也可以看成是继承关系
(3)多态示例
父类Animal:
public class Animal {
public void eat() {
System.out.println("吃东西");
}
}
子类Cat:
public class Cat extends Animal{
@Override
public void eat() {//子类重写父类方法
System.out.println("猫吃鱼");
}
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}
测试类ClassTest:
public class ClassTest {
public static void main(String[] args) {
//正常使用:
Animal animal = new Animal();
animal.eat();
Cat cat1 = new Cat();
cat1.eat();
cat1.catchMouse();
System.out.println("----------")
//多态:
Animal animal2 = new Cat();//父类引用指向子类对象(或子类的对象赋值给父类的引用)
animal2.eat();//执行Animal类的eat方法,还是执行Cat类的eat方法
animal2.catchMouse();//此处会报错
}
}
上面的Animal animal2 = new Cat();就是多态的体现,猫可以被当做动物来看待。
animal2.eat();这就是多态的使用。虽然猫可以当做动物来看待,但真正执行方法的时候,还是执行子类的方法。
animal2.catchMouse();会报错,当把猫当动物看的时候,编译器会查看动物类是否有catchMouse方法,发现没有,就会报错。
(4) 多态中成员访问的特点
父类Animal:
public class Animal {
int a = 20;
public void eat() {
System.out.println("吃东西");
}
}
子类Cat:
public class Cat extends Animal{
int a = 100;
int b = 200;
@Override
public void eat() {//重写父类的方法
System.out.println("猫吃鱼");
}
public void catchMouse() {//子类独有的方法
System.out.println("猫抓老鼠");
}
}
测试类ClassTest:
public class ClassTest {
public static void main(String[] args) {
//正常使用:
Animal animal = new Animal();
animal.eat();
Cat cat1 = new Cat();
System.out.println(cat1.a);
System.out.println(cat1.b);
cat1.eat();
cat1.catchMouse();
System.out.println("--------------");
//多态:
Animal animal2 = new Cat();//父类引用指向子类对象(或子类的对象赋值给父类的引用)
System.out.println(animal2.a);
// System.out.println(animal2.b);//会报错
animal2.eat();//执行Cat类的eat
// animal2.catchMouse();//此处会报错。
}
}
回顾:Java代码运行的流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YY3WPrkA-1615184582829)(11_面向对象编程/Java代码执行流程.png)]
编译期:Java源代码(.java)编译成字节码文件(.class)的时期。
运行期:运行字节码的时期。
成员 | 编译期 | 运行期 |
---|---|---|
实例变量 | 看等号左边(即父类) | 看等号左边(即父类) |
实例方法 | 看等号左边(即父类) | 看等号右边(即子类) |
为什么实例变量和实例方法的访问不一样呢?
因为实例方法有重写,实例变量没有。
(5)多态的应用-参数多态
-
需求:定义一个Person类,完成饲养猫,饲养狗的功能。设计猫和狗类的时候,使用继承完成。
-
代码:
父类Animal:
public class Animal { public void eat() { System.out.println("动物进食"); } }
猫类Cat:
public class Cat extends Animal{ @Override public void eat() { System.out.println("猫吃鱼"); } //抓老鼠 public void catchMouse() { System.out.println("猫抓老鼠"); } }
狗类Dog:
public class Dog extends Animal { @Override public void eat() { System.out.println("狗吃骨头"); } //看门 public void lookDoor() { System.out.println("狗看门"); } }
人类Person:
public class Person { //养猫 public void raiseCat(Cat cat) { cat.eat(); } //养狗 public void raiseDog(Dog dog) { dog.eat(); } //养任何动物------方法的参数多态 public void raiseAnimal(Animal animal) { animal.eat(); } }
测试类ClassTest:
public class ClassTest { public static void main(String[] args) { //创建猫和狗 Cat c = new Cat(); Dog d = new Dog(); //创建人 Person p = new Person(); //饲养猫,饲养狗----不用多态 p.raiseCat(c); p.raiseDog(d); System.out.println("---------"); //饲养猫,饲养狗----使用多态 p.raiseAnimal(c);//猫可以看做动物。raiseAnimal要求的参数是Animal(即父类型),我们把子类型赋值给了父类型,这就是多态。 p.raiseAnimal(d);//狗可以看做动物。raiseAnimal要求的参数是Animal(即父类型),我们把子类型赋值给了父类型,这就是多态。 } }
(6) 多态的应用-数组元素多态
-
需求:创建一个数组,存放猫和狗。注意数组只能存放相同数据类型的数据。
-
代码:
猫类、狗类、动物类使用之前定义好的Cat、Dog以及Animal。
测试代码:
public class ClassTest { public static void main(String[] args) { //创建猫和狗 Cat c1 = new Cat(); Cat c2 = new Cat(); Cat c3 = new Cat(); Dog d1 = new Dog(); Dog d2 = new Dog(); Dog d3 = new Dog(); Cat[] arr = new Cat[6]; arr[0] = c1; arr[1] = c2; arr[2] = c3; // arr[3] = d1;//报错 Cat数组只能存放Cat对象,不能存放Dog对象 // arr[4] = d2;//报错 Cat数组只能存放Cat对象,不能存放Dog对象 // arr[5] = d3;//报错 Cat数组只能存放Cat对象,不能存放Dog对象 Dog[] arr2 = new Dog[6]; // arr2[0] = c1;//报错 Dog数组只能存放Dog对象,不能存放Cat对象 // arr2[1] = c2;//报错 Dog数组只能存放Dog对象,不能存放Cat对象 // arr2[2] = c3;//报错 Dog数组只能存放Dog对象,不能存放Cat对象 arr2[3] = d1; arr2[4] = d2; arr2[5] = d3; //创建Animal数组存放猫和狗。---多态 Animal[] animals = new Animal[6]; animals[0] = c1;//把Cat看成Animal存入数组 animals[1] = c2;//把Cat看成Animal存入数组 animals[2] = c3;//把Cat看成Animal存入数组 animals[3] = d1;//把Dog看成Animal存入数组 animals[4] = d2;//把Dog看成Animal存入数组 animals[5] = d3;//把Dog看成Animal存入数组 for(int i = 0; i < animals.length; i++) { Animal a = animals[i]; a.eat();//多态的对象在运行期,执行子类的方法。 } } }
(7)多态的应用-数组元素或方法返回值多态
-
需求:用Animal数组存放Cat和Dog。遍历数组,如果元素是猫,执行抓老鼠的方法,如果是狗执行看门方法。
-
代码:
猫类、狗类、动物类使用之前定义好的Cat、Dog以及Animal。
测试代码ClassTest:
public class ClassTest { public static void main(String[] args) { //创建猫和狗 Cat c1 = new Cat(); Cat c2 = new Cat(); Cat c3 = new Cat(); Dog d1 = new Dog(); Dog d2 = new Dog(); Dog d3 = new Dog(); //创建Animal数组存放猫和狗。---多态 Animal[] animals = new Animal[6]; animals[0] = c1;//把Cat看成Animal存入数组 animals[1] = c2;//把Cat看成Animal存入数组 animals[2] = c3;//把Cat看成Animal存入数组 animals[3] = d1;//把Dog看成Animal存入数组 animals[4] = d2;//把Dog看成Animal存入数组 animals[5] = d3;//把Dog看成Animal存入数组 for(int i = 0; i < animals.length; i++) { Animal a = animals[i]; if(a instanceof Cat) { Cat c = (Cat)a;//强制类型转换 c.catchMouse(); }else if(a instanceof Dog) { Dog d = (Dog)a; d.lookDoor(); } } } }
返回值多态:
动物管理员AnimalManager:
public class AnimalManager { public Animal getAnimalByIndex(int index) { Cat c1 = new Cat(); Cat c2 = new Cat(); Cat c3 = new Cat(); Dog d1 = new Dog(); Dog d2 = new Dog(); Dog d3 = new Dog(); Animal[] animals = new Animal[6]; animals[0] = c1; animals[1] = c2; animals[2] = c3; animals[3] = d1; animals[4] = d2; animals[5] = d3; return animals[index]; } }
测试代码:
public class ClassTest { public static void main(String[] args) { AnimalManager am = new AnimalManager(); Cat a = (Cat)am.getAnimalByIndex(0); a.eat(); a.catchMouse(); Dog b = (Dog)am.getAnimalByIndex(3); b.eat(); b.lookDoor; } }
intanceof关键字的作用是:判断对象是不是某个类的实例。
例如:a instanceof Cat是判断对象a是否是Cat类的实例,如果是就返回true,否则就是false
如果a是Cat子类的实例,Cat继承于Animal,那么a instanceof Animal也是true。
向上转型:子类类型转换为父类类型。即把子类对象赋值给父类引用。系统自动完成。
向下转型:父类类型转换为子类类型。需要强制类型转换。
方法的返回值类型也可以使用多态(即父类型),实际返回的是子类对象。
例如:在Java桌面编程里,可以把控件添加到页面上,也可以从页面上获取组件。添加组件和获取组件用的就是多态。添加组件的方法可以把父类组件作为方法的参数,调用的时候,添加子类(按钮、输入框、复选框等),获取组件的方法返回值可以设置为组件类型,方法返回的是实际的组件(按钮,输入框,复选框等),通过强制类型转换,转成自己需要的类型。
(8)多态的好处和弊端
好处:提高了程序的扩展性,容易设计出通用的代码。
弊端:屏蔽了子类独有的功能。(不过,可以强转类型)
十、其他相关内容
(1)包(package)
1. 什么是包?
包本质上就是文件夹,作用是对类进行分类管理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fR2DT9bn-1615184582831)(11_面向对象编程/包的定义格式.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pvhglZdC-1615184582832)(11_面向对象编程/类的存放位置.png)]
开发中,经常会出现同名的类,如果没有包对他们进行区分,他们是没有办法共存的。有了包,我们可以把同名的类放在不同的包里,这样就不会出现冲突。
2. 包的定义
包的定义格式:
package 包名; //多级包用.分隔
示例:
package com.lanou.oop;
由于包是用于分类管理类的,因此类要定义在包中。即在类定义代码的上面,定义所在的包。
例如:
package com.lanou.weapon;//此处定义了Weapon位于哪个包中
public class Weapon {
//属性
private String name; //名称
private int ap; //攻击力
private int price; //价钱
//getter和setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAp() {
return ap;
}
public void setAp(int ap) {
this.ap = ap;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
//构造方法
public Weapon() {
super();
}
public Weapon(String name, int ap, int price) {
super();
this.name = name;
this.ap = ap;
this.price = price;
}
public void showInfo() {
System.out.println("名称:" + name + ",攻击力:" + ap + ",价格:" + price);
}
}
3. 导包
先看一个案例,通过案例了解什么是导包。
-
需求:Student类所属的包是com.lanou.bean,ClassTest类所属的包是com.lanou.main,即Student类和ClassTest类不在同一个包中,在ClassTest的main方法中创建Student对象,并测试Student对象的功能。
-
代码:
Student类:
package com.lanou.bean; public class Student { public void study() { System.out.println("Good good study, day day up!"); } }
ClassTest类:
package com.lanou.main; public class ClassTest { public static void main(String[] args) { Student stu = new Student();//此处代码会报错!原因是不认识Student类 stu.study(); } }
修改后的ClassTest类:
package com.lanou.main; public class ClassTest { public static void main(String[] args) { com.lanou.bean.Student stu = new com.lanou.bean.Student();//给出类的完整路径后,错误就消失了,而且程序可以正常运行。 stu.study(); } }
简化方式,提前引入Student类。
package com.lanou.main; import com.lanou.bean.Student;//引入Student类,这就是导包。或者import com.lanou.bean.*; public class ClassTest { public static void main(String[] args) { Student stu = new Student(); stu.study(); } }
导包的语法格式:
import 包名.类名;
或者
import 包名.*;
不推荐import 包名.*;
因为他是把包内所有的类都导入进来了。
导包的快捷键:Ctrl + Shift + O
这个快捷键能帮你管理包,添加缺少的包,删除多导入的包。
java.lang包下的类不用导包。
同一个包内的类不用导包
示例:
import com.lanou.bean.Student;
(2)访问修饰符
Java中提供了4种权限访问修饰符(从小到大):private、缺省、protected、public
4种修饰符可以修饰类以及类的成员(属性、方法、构造方法)。
-
如果修饰类:只能使用public和缺省。
-
如果修饰类的成员:private、缺省、protected、public都可以。
1. 访问修饰符修饰类
public class A {
}
class B{
}
public修饰的类可以在任何地方使用(所属的包,以及其他包)。
缺省权限的类只能在本包中使用。
2. 访问修饰符修饰类的成员
-
在类内部,4种访问权限修饰的成员都可以访问。(指的是可以通过对象.属性名或者对象.方法名()这种形式访问属性和方法)
package com.lanou.p1; public class A { private int a; int b; protected int c; public int d; private void privateMethod() { } void method() { } protected void protectdMethod() { } public void publicMethod() { } private A() { } A(int x){ } protected A(int x, int y) { } public A(int x, double y) { } public void test() { this.a = 100; this.b = 200; this.c = 300; this.d = 400; A a1 = new A(); A a2 = new A(100); A a3 = new A(100, 200); A a4 = new A(100, 3.5); this.privateMethod(); this.method(); this.protectdMethod(); this.publicMethod(); } }
-
在同包的其他类中,缺省、protected、public修饰的成员可以访问。(指的是可以通过对象.属性名或者对象.方法名()这种形式访问属性和方法)
public class TestA { public static void main(String[] args) { //A a1 = new A();//private修饰的构造方法在同包其他类中不能用! A a2 = new A(100); A a3 = new A(100, 200); A a4 = new A(100, 3.5); //a4.a = 100;//private修饰的属性在同包的其他类中不能使用。 a4.b = 200; a4.c = 300; a4.d = 400; //a4.privateMethod();//private修饰的方法在同包其他类中不能使用 a4.method(); a4.protectdMethod(); a4.publicMethod(); } }
package com.lanou.p1; public class SubA extends A { public SubA() { super(100); } public void test() { //this.a = 100;//在同包子类中,不能访问private修饰的属性 this.b = 200; this.c = 300; this.d = 400; //A a1 = new A();//在同包子类中,不能访问private修饰的构造方法 A a2 = new A(100); A a3 = new A(100, 200); A a4 = new A(100, 3.5); //this.privateMethod();//在同包子类中,不能访问private修饰的方法 this.method(); this.protectdMethod(); this.publicMethod(); } }
-
不同包的子类中,protected、public修饰的成员可以访问。(指的是可以通过对象.属性名或者对象.方法名()这种形式访问属性和方法)
package com.lanou.p2; import com.lanou.p1.A; public class SubA extends A { public SubA() { super(100, 200); } public void test() { //this.a = 100; //this.b = 200; this.c = 300; this.d = 400; //A a1 = new A();//在不同包的子类中,private修饰的构造方法不能使用 //A a2 = new A(100);//在不同包的子类中,缺省的构造方法不能使用 A a3 = new A(100, 200); A a4 = new A(100, 3.5); //this.privateMethod();//在不同包的子类中,private修饰的方法不能使用; //this.method();//在不同包的子类中,缺省的方法不能使用; this.protectdMethod(); this.publicMethod(); // a4.privateMethod(); // a4.method(); //a4.protectedMethod(); a4.publicMethod(); } }
-
不同包的无关类,public修饰的成员可以访问。(指的是可以通过对象.属性名或者对象.方法名()这种形式访问属性和方法)
package com.lanou.p2; import com.lanou.p1.A; public class ClassTest { public static void main(String[] args) { // A a1 = new A();//private修饰的构造方法在不同包其他类中不能用! //A a2 = new A(100);//缺省的构造方法在不同包其他类中不能用! //A a3 = new A(100, 200); A a4 = new A(100, 3.5); // a4.a = 100;//private修饰的属性在不同包的其他类中不能使用。 //a4.b = 200;//缺省的属性在不同包的其他类中不能使用。 //a4.c = 300;//protected修饰的属性在不同包的其他类中不能使用 a4.d = 400; //a4.privateMethod();//private修饰的方法在不同包其他类中不能使用 //a4.method();//缺省的方法在不同包其他类中不能使用 //a4.protectdMethod();//protected修饰的方法在不同包其他类中不能使用 a4.publicMethod(); } }
访问修饰符修饰类的成员总结:
修饰符 | 类内部 | 同包类 | 不同包子类 | 不同包其他类 |
---|---|---|---|---|
private | √ | |||
缺省 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
在开发中,只会用到public和private,想私有就用private修饰,不想私有就用public。另外2种几乎不用。
一般类都用public修饰。属性用private修饰,getter、setter用public修饰,构造方法用public修饰,方法一般是public,个别不想暴露的方法用private。
(3)final关键字
final单词的含义是最终的,在Java中final可以修饰类,方法,变量(实例变量,局部变量)。
1. final修饰类
final修饰类:说明类是最终的类,即类不能有子类。换句话说就是类不能被继承。
package com.lanou.testfinal;
public final class A {
}
package com.lanou.testfinal;
public class B extends A{//会报错
}
2. final修饰方法
final修饰方法:说明方法是最终的方法,即方法不能被子类重写。
package com.lanou.testfinal;
public class AA {
public final void test() {
System.out.println("这是final修饰的方法,不允许被重写");
}
}
package com.lanou.testfinal;
public class BB extends AA {
public void test() {//此处会报错!
System.out.println("这是子类实现");
}
}
3. final修饰变量(实例变量、局部变量)
final修饰变量:说明变量是最终的,即变量的值不能发生变化。(如果变量没赋值,有一次赋值机会)。
package com.lanou.testfinal;
public class AAA {
private final int A = 100;
public void method() {
A = 200;//此处会报错!
}
}
package com.lanou.testfinal;
public class TestFinal {
public static void main(String[] args) {
final int A;
A = 10;
A = 20;//此处会报错
}
}
package com.lanou.testfinal;
public class TestFinal {
public static void main(String[] args) {
final int A = 10;
A = 20;//此处会报错
}
}
因为final修饰的变量,值不能发生变化,所以我们通常把final修饰的变量称为常量,变量名一般用纯大写命名。
(4)static关键字
static单词的含义是静态的。在Java中static可以修饰属性、方法、代码块、内部类。—代码块、内部类以及static修饰他们,后面讲解。
1. static修饰属性
如果属性用static修饰,那么这个属性不再是实例变量,而是类变量。
实例变量,即实例的变量,有多少个实例就有多少个变量
类变量,即类的变量,这个类所有的实例共用这个变量。
package com.lanou.teststatic;
public class Person {
private String name; //姓名
private int age; //年龄
public static String nationality; //国籍
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Person() {
super();
}
public void showInfo() {
System.out.println(name + "," + age + "," + nationality);
}
}
package com.lanou.teststatic;
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person("张三", 21);
p1.nationality = "中国";//p1设置国籍以后,p2也会是这个国籍
p1.showInfo();
Person p2 = new Person("李四", 20);
p2.showInfo();
p2.nationality = "美国";//p2改变国籍,p1也会改变国籍
p2.showInfo();
p1.showInfo();
}
}
被static修饰的属性,既可用对象访问,也可以用类访问。类属性应该使用类去访问,不要使用对象去访问(尽管可以)。
package com.lanou.teststatic;
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person("张三", 21);
p1.nationality = "中国";//p1设置国籍以后,p2也会是这个国籍
p1.showInfo();
Person p2 = new Person("李四", 20);
p2.showInfo();
p2.nationality = "美国";//p2改变国籍,p1也会改变国籍
p2.showInfo();
p1.showInfo();
Person.nationality = "朝鲜";
p2.showInfo();
p1.showInfo();
}
}
2. static修饰方法
如果方法用static修饰,那么这个方法不再是实例方法,而是类方法。
实例方法:实例的方法,即对象的方法。由对象来调用方法。
类方法:类的方法,这个类所有的对象共用这个方法。
package com.lanou.teststatic;
public class Person {
private String name; //姓名
private int age; //年龄
public static String nationality; //国籍
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Person() {
super();
}
public void showInfo() {
System.out.println(name + "," + age + "," + nationality);
}
//静态方法(类方法)
public static void staticMethod() {
System.out.println("这是静态方法。");
}
}
package com.lanou.teststatic;
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person("张三", 21);
p1.staticMethod();//p1可以调用静态方法
Person p2 = new Person("李四", 20);
p2.staticMethod();//p2也可以调用静态方法
}
}
被static修饰的方法,既可以被对象访问,也可以被类访问**。类方法应该使用类去访问,不要使用对象去访问(尽管可以)。**
package com.lanou.teststatic;
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person("张三", 21);
p1.staticMethod();//p1可以调用静态方法
Person p2 = new Person("李四", 20);
p2.staticMethod();//p2也可以调用静态方法
Person.staticMethod();
}
}
3. 静态方法中不能访问实例变量
package com.lanou.teststatic;
public class Person {
private String name; //姓名
private int age; //年龄
public static String nationality; //国籍
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Person() {
super();
}
public void showInfo() {
System.out.println(name + "," + age + "," + nationality);
}
//静态方法(类方法)
public static void staticMethod() {
System.out.println("这是静态方法。");
System.out.println(name);//此处会报错!
name = "王五";//此处会报错!
showInfo();//此处会报错!
}
}
静态方法中,访问实例变量会报错。之所以报错是因为,静态方法是类方法,不是实例方法,因此访问实例变量的时候,不知道是访问的是哪个对象的实例变量。
静态方法中,访问实例方法会报错。之所以报错是因为,静态方法中访问实例方法,不知道访问的是哪个对象的实例方法。
静态方法中,不能使用this。this是一个代表当前方法调用者的对象。因为类通常是用类名调用的,不是具体的对象,所以不能用this。
4. static总结
- static修饰的属性和方法被所有对象共享,即可以被对象访问。
- static修饰的属性和方法可以被类访问。推荐使用类去访问静态属性和静态方法。
- 静态方法中只能访问静态属性和静态方法。
(5)代码块、静态代码块
用{}括起来的代码叫做代码块。
在Java中一共3种代码块:
- 局部代码块
- 初始化代码块
- 静态代码块。
1. 局部代码块
定义在方法体内的代码块叫局部代码块。
public class BlockTest {
public static void main(String[] args) {
int a = 10;
{
int b = 20;
System.out.println(a);
System.out.println(b);
}
//System.out.println(b);//此处会报错。
}
}
上面的代码,在main方法中定义了一个局部代码块。代码块外定义的变量可以在代码块中使用,但代码块内定义的变量不能在代码块外使用,即代码块内的变量作用域仅限于代码块内。
**局部代码块平时很少使用。**switch…case中具体的case可以使用,可以避免多个case定义相同的变量出现重名的问题。
2. 初始化代码块
初始化代码块是定义在类中的代码块。它和属性、方法、构造方法属于是一个层级的东西。由于通常用它来做初始化,所以叫做初始化代码块。
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person() {
super();
System.out.println("无参构造方法");
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
System.out.println("全参构造方法");
}
{
name = "zhangsan";
age = 20;
System.out.println("这是一个初始化块!我 比 构造方法执行 先执行");
}
public void showInfo() {
System.out.println("姓名: " + name + ",年龄:" + age);
}
}
测试代码:
public class BlockTest {
public static void main(String[] args) {
Person p = new Person();
p.showInfo();
Person p2 = new Person("lisi", 22);
p2.showInfo();
}
}
初始化代码块作用:
- 可以为属性赋值----等价于构造方法。
- 可以做公共初始化。----如果多个构造方法,他们有共同的代码,共同的代码可以提到初始化代码块中。
初始化代码块的特点:
- 初始化代码块定义格式和方法类似,只不过只有{},没有参数,没有返回值。
- 初始化代码块不能主动调用,创建对象的时候自动执行,先于构造方法执行。
- 一个类中可以有多个初始化代码块,写在上面的初始化代码块先执行。尽管可以定义多个,一般最多定义一个。
3. 静态代码块
用static修饰的代码块称为静态代码块。
静态代码块是类的代码块,随着类的加载而调用,因为类只会加载一次,所以静态代码块也只执行一次。
public class StaticBlock {
public static int a = 100;
private int b;
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
//静态代码块
static {
System.out.println("静态代码块");
}
//初始化代码块
{
System.out.println("初始化代码块");
}
//构造方法
public StaticBlock(int b) {
super();
this.b = b;
System.out.println("有参构造方法");
}
public StaticBlock() {
super();
System.out.println("无参构造方法");
}
}
测试代码:
public class BlockTest {
public static void main(String[] args) {
int num = StaticBlock.a;
System.out.println(num);
StaticBlock sb1 = new StaticBlock();
StaticBlock sb2 = new StaticBlock(20);
}
}
静态代码块的特点:
- 静态代码块的书写格式和初始化代码块的格式类似,在前面加一个static关键字即可。
- 静态代码块不能主动调用,类加载的时候自动调用。因为类只加载一次,所以只调用一次。
- 静态代码块可以有多个,写在上面的先执行。一般最多只写一个。
- 静态代码块中不可以使用实例变量,不可以调用实例方法。
- 静态代码块比初始化代码块先执行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hWisAu7l-1615184582835)(11_面向对象编程/静态代码块用于数据库连接池.png)]
4. 继承关系里,代码块执行顺序
父类Father:
public class Father {
static {
System.out.println("父类--静态代码块");
}
{
System.out.println("父类----初始化代码块");
}
public Father() {
super();
System.out.println("父类------构造方法");
}
}
子类Son:
public class Son extends Father{
static {
System.out.println("zi类--静态代码块");
}
{
System.out.println("zi类----初始化代码块");
}
public Son() {
super();
System.out.println("zi类------构造方法");
}
}
测试类:
public class BlockTest {
public static void main(String[] args) {
Son s = new Son();
System.out.println("==========");
Son s2 = new Son();
}
}
在继承关系里,创建子类对象时,会先加载类,再创建对象。
加载类时,先加载父类,再加载子类。— 类只有首次用的时候才加载,加载之后就一直在内存中。
创建对象时,先执行父类的初始化代码块和构造方法,再执行子类的初始化代码块和构造方法。
(6)abstract关键字
abstract单词的含义是抽象。它可以用来修饰方法,也可以用来修饰类。被abstract修饰的类称为抽象类,被abstract修饰的方法称作抽象方法。
1. 抽象类
抽象类最大的特点就是不能实例化对象,即不能创建对象。
public abstract class Animal {
}
测试代码:
public class ClassTest {
public static void main(String[] args) {
Animal a = new Animal();//会报错
}
}
2. 抽象类有什么用?
有些时候,父类只是为了被子类继承,在用的时候,我们希望别人用子类,而不是用父类,这个时候,我们就可以把父类定义成抽象类。
例如:Animal只是表示动物,即使创建了对象也没有什么意义,我们更希望创建的是猫、狗等更具体的对象。为了防止别人误用,防止创建Aninal对象,我们可以通过abstract把Animal定义为抽象类,这样别人就无法创建Animal对象了。
3. 抽象类中可以定义属性、方法吗?
能!一个普通类能包含的内容,抽象类都能包含。
抽象类和普通类的唯一区别就是不能创建对象。—我不能创建对象,但是我东西可以被子类继承呀!
public abstract class Animal {
//属性
private String name;
private int age;
//常量
public static final int TEST = 100;
//静态属性
public static int test;
//setter、getter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//构造方法
public Animal() {
super();
}
public Animal(String name, int age) {
super();
this.name = name;
this.age = age;
}
//静态代码块
static {
System.out.println("静态代码块");
}
//初始化代码块
{
System.out.println("初始化代码块");
}
//实例方法
public void eat() {
System.out.println("吃东西");
}
//静态方法
public static void staticMethod() {
System.out.println("静态方法");
}
}
4. 抽象方法
抽象方法:被abstract修饰的方法。
抽象方法必须定义在抽象类中!
抽象方法不能有方法实现!
public abstract class Animal {
public abstract void eat();
}
5. 抽象方法有什么用?
**抽象方法是专门为多态设计的!**是专门设计用来被子类重写的!
Animal做为动物类,它比较抽象,即使实现了eat方法,也会被子类重写,不如不实现。只定义出来有这个一个方法,但不实现,由具体的子类来实现。
如果一个类继承于抽象类,那么必须实现抽象类中所有的抽象方法!
或者自己也定义成抽象类,由自己的子类来实现抽象方法!
Cat类实现Animal类的抽象方法:
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
Cat类不实现Animal类的抽象方法:
public abstract class Cat extends Animal {
}
子类BosiCat实现抽象方法:
public class BosiCat extends Cat {
@Override
public void eat() {
System.out.println("波斯猫吃鱼");
}
}
6. 抽象类不能创建对象怎么办?
可以借助多态,用子类创建对象,抽象类的引用指向子类创建的对象。
public class ClassTest {
public static void main(String[] args) {
Animal a = new BosiCat();
a.eat();
}
}
7. 饲养宠物
-
需求:定义一个Person类,完成饲养猫,饲养狗的功能。设计猫和狗类的时候,使用抽象和继承完成。
-
代码:
父类Animal:
public abstract class Animal { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Animal() { super(); } public Animal(String name, int age) { super(); this.name = name; this.age = age; } //声明抽象方法 eat public abstract void eat(); public void showInfo() { System.out.println(name + "," + age + "岁"); } }
Cat类:
public class Cat extends Animal { @Override public void eat() { System.out.println("猫吃鱼"); } public Cat() { super(); } public Cat(String name, int age) { super(name, age); } }
Dog类:
public class Dog extends Animal { @Override public void eat() { System.out.println("狗吃骨头"); } public Dog() { super(); } public Dog(String name, int age) { super(name, age); } }
Person类:
public class Person { public void raiseAnimal(Animal animal) { animal.eat(); } }
测试代码:
public class ClassTest { public static void main(String[] args) { Animal a1 = new Cat("花花", 2); Animal a2 = new Dog("旺财", 5); a1.eat(); a2.eat(); System.out.println("----------"); Person p = new Person(); p.raiseAnimal(a1); p.raiseAnimal(a2); } }
总的来说:抽象类属于类的设计层面上的问题。它是为了设计类而存在,不是为了创建对象而存在。它最佳的使用场景就是多态。
抽象类声明子类必须要实现的方法(即自身的抽象方法),子类根据自己特征实现父类中声明的抽象方法。在创建和使用对象的时候,借助多态,用父类引用指向子类对象。这样可以充分发挥多态的优势。