4.面向对象
- 属性+方法变成一个类
Java面向对象学习三条主线:
1.Java类及类的成员:属性,方法,构造器;代码块,内部类
2.面向对象的三大特征:封装性,继承性,多态性,(抽象性)
3.其他关键字:this,super,static,final,abstract,interface,package,import
- 面向对象编程思想?
(类,对象; 面向对象的三大特征;...) - 谈谈你对面向对象中类和对象的理解,并指出二者关系?
image.png类: 是对一类事物的描述, 抽象的, 概念上的内容
对象: 实实在在存在的一个个体. new出来的东西,在内存当中存在的, 在内存中真正的创建了一个对象,占据了内存一定空间
对象: 是由类派生(new)出来的
4.1.什么是面向对象
-
面向对象编程(Objected-Oriented-Programming,OOP),Java的核心思想
-
面向对象编程的本质是: 以类的方式组织代码,以对象的形式组织(封装)数据.
-
抽象 把相似的抽取出来
-
三大特性:
- 封装 把一个东西打包封装,留一个口方便把东西拿出
- 继承 子类可以继承父类的所有东西,多个子类继承的东西是一样的
- 多态 同一个事物会有多种形态
-
从认识论角度考虑是先有对象后有类. 对象,是具体的事物. 类,是抽象的, 是对对象的抽象
-
从代码运行角度考虑是先有类后有对象. 类是对象的模板.
-
值传递
// 值传递
public static void main(String[] args){
int i = 1;
System.out.println(i);
Demo04.change(i);
System.out.println(i);
i = Demo04.change1(i);
System.out.println(i);
}
// 返回值为空
public static void change(int a){
a = 10;
}
// 返回值不为空
public static int change1(int a){
a = 10;
return a;
}
- 引用传递
public class Demo05 {
// 一个类可以有多个public class 但是可以有多个class
// 引用传递: 对象,本质还是值传递
public static void main(String[] args) {
// Person是一个引用,指向的是堆里面的对象
Person person = new Person(); // 实例化Person对象
System.out.println(person.name); // null
// Demo05.change(person);
change(person); // 引用传递传递的是对象的地址
System.out.println(person.name);// dijia
// 变量名对应的内存地址不一样
// 值传递,传递后的值被改了不影响原变量
// 因此只是将方法中的变量指向了字符串,并未改变main方法原变量的引用
}
public static void change(Person person) {
// person是一个对象:指向Person这个类
// 或是 Person person = new Person();这是个具体的人,可以改变属性!
person.name = "dijia";
}
}
// 定义一个Person类,有一个属性: name
class Person {
String name;
}
4.2.创建与初始化对象
- 面向对象的两个要素:
- 类:对一类事物的描述,是抽象的,概念上的定义
- 对象:是实际存在的该类事物的每个个体,因而也称为实例(instance)
- 类和对象的创建
面向对象思想的体现一 ,类和对象的创建和执行操作(面向对象思想落地地实现):
1.创建类,设计类的成员
2.创建类的对象
3.通过"对象.属性"或"对象.方法()"调用对象的结构
二,如果创建了一个类的多个对象,每个对象都独立的拥有一套类的属性(无static关键字的)
意味着,如果我们修改一个对象的属性a,不影响另一个属性a的值.(static为可共享属性)
// 测试类
public class PersonTest {
// main方法作为程序入口
public static void main(String[] args) {
// 创建Person类的对象=类的实例化=实例化类
Person p1 = new Person(); // 类是对象的类型,对象是类的实例,Person是引用的变量类型
// 调用对象的(功能和行为)结构: 属性和方法
// 调用属性: "对象.属性;"
p1.name = "tom";
p1.isMale = true;
System.out.println(p1.name);
// 调用方法: "对象.方法();"
p1.eat();
p1.sleep();
p1.talk("Chinese");
System.out.println("================");
Person p2 = new Person();
System.out.println(p2.name); // null
System.out.println(p2.isMale); // false
System.out.println("===================");
//
Person p3 = p1; // p1对象的地址值赋给了p3,导致p1和p3指向了堆空间中的同一个对象实体
System.out.println(p3.name); // tom
p3.age = 10;
System.out.println(p1);
System.out.println(p3);
System.out.println(p1.age); // 10
}
}
// 创建类,设计类的成员:包括成员方法,成员变量
class Person{
// 属性= 成员变量= 域,字段
String name;
int age = 1;
boolean isMale;
// 设计方法:行为
public void eat(){
System.out.println("人可以吃饭");
}
public void sleep(){
System.out.println("人可以睡觉");
}
public void talk(String language){
System.out.println("人可以说话使用:" + language);
}
- 对象的内存解析:
- 栈的特点:先进后出
1.new Person(); 先造了一个对象,new的结构都在堆里,意味着在堆空间中要造一个对象的实体,该对象会有个地址值称作首地址值; 在栈空间中声明了一个变量p1,此时的p1实际是定义在main方法中的,方法中定义的变量都是局部变量,此时,new结构的数据赋值给p1,其实赋过来的是个地址,通过赋值地址值后,栈空间中的p1就指向了堆空间中造的对象实体,类在设计的时候声明过三个属性,这些属性不同于局部变量,属性存在于堆空间中,在造好的对象里面,在赋值过程中会给定默认的初始化值和先前赋好的值
2.通过p1找到new的结构调用name把null改成tom,tom等赋值后的数据实际在方法区中的字符串常量池里
3.重新在堆空间中造一个对象实体, 该实体有一个首地址值,将这个地址值赋值给栈空间中现在加载的变量p2,栈空间中的p2对象就可以指向堆空间的这个对象实体,对象实体也独立生成一份默认初始化值和先有的值
4.又在main方法(栈空间)中声明了一个p3局部变量,p3拿p1赋的值,p1存的是地址值,所以传给p3也是相同地址值,通过p1传过来的地址值,p3也顺着地址指向p1的在堆空间中的结构,所以p3不能叫新创建的一个对象,只能算新声明的一个变量,此时p1和p3指向了堆空间中的同一个对象实体
4.3.属性与局部变量的对比
类中属性的使用
属性(成员变量) vs 局部变量
- 1.相同点:
- 1.1.定义变量的格式: 数据类型 变量名 = 变量值
- 1.2.先声明,后使用
- 1.3.变量都有其对应的作用域
- 2.不同点:
- 2.1.在类中声明的位置不同
属性:直接定义在类的一对{}里
局部变量:声明在方法内,方法形参,代码块内,构造器形参,构造器内部的变量 - 2.2.关于权限修饰符的不同
属性:可以在声明属性时,指明其权限,使用权限修饰符.
常用的权限修饰符: private,public,缺省(相当于默认,default),protected --->类的封装性
目前,声明属性时,使用缺省就行了
局部变量:不可以使用权限修饰符,可理解为其权限被方法的权限代替了 - 2.3.默认初始化值的情况:
属性: 类的属性,根据其类型,都有默认初始化值.
整型(byte,short,int,long): 0
浮点型(float,double): 0.0
字符型(char), 0 (或'\u0000')
布尔型(boolean): false
引用数据类型:(类(特殊的String),数组,接口): null
局部变量: 没有初始化值,意味着调用局部变量之前,一定要显示赋值
特别地: 形参在调用时,赋值即可. - 2.4.二者在内存中加载的位置:
(非static)属性:加载到堆空间中(static属性都放在方法区中)
局部变量:加载到栈空间中
- 2.1.在类中声明的位置不同
public static void main(String[] args) {
User u1 = new User();
System.out.println(u1.name);
System.out.println(u1.age);
System.out.println(u1.isMale);
u1.talk("中文");
u1.eat();
}
}
class User{
// 属性(成员变量)
String name; // private权限小,出了定义的这个类就不能调了
public int age; // public权限大,在外的就能调
boolean isMale;
public void talk(String language){ // language:形参(局部变量)
System.out.println("我们使用" + language + "交流");
}
public void eat(){
String food = "烙饼"; // (声明)定义在方法内的变量叫局部变量
System.out.println("北方人吃" + food);
}
4.4.空指针异常(NullPointerException)
关于垃圾回收器: GC
在Java语言中,垃圾回收器主要针对的是堆内存.
当一个Java对象没有任何引用指向该对象的时候,
GC会考虑将该垃圾数据释放回收掉.
- 出现空指针异常的前提条件是?
- "空引用"访问实例(对象相关,例如id)相关的数据时,都会出现呢空指针异常.
/*
空指针异常
*/
public class NullPointerTest {
public static void main(String[] args) {
// 创建客户对象
Customer1 c1 = new Customer1();
// 访问这个客户对象
System.out.println(c1.id);
// 重新给id赋值
c1.id = 242;
System.out.println(c1.id);
c1 = null;
// NullPointerException 空指针异常
// 编译器没问题,因为编译器只检查语法,编译器发现c是Customer类型,
// Customer类型中由id属性,所以可以: 调用c.id,语法过了,
// 但是运行的时候需要对象的存在,但是对象丢失,就只能出现异常
System.out.println(c1.id);
}
}
// 客户类
class Customer1{
// 客户id属性
int id; // 成员变量中的实例变量,应该先创建对象,再通过"引用."的方式访问
}
image.png
4.5.对象数组
public class StudentTest1 {
public static void main(String[] args) {
// 声明自定义类Student类型的对象数组
Student1[] stus = new Student1[20]; // 类似于String[] arr = new String[];
for (int i = 0;i < stus.length;i++){
// 给数组元素赋值
stus[i] = new Student1();
// 给Student对象的属性赋值
stus[i].number = (i + 1);
// 年级: [1,6]
// 随机数a~b公式: int value = (int)(Math.random() * (b - a + 1) - a)
stus[i].state = (int)(Math.random() * (6 - 1 + 1) + 1);
// 分数: [0~100]
stus[i].score = (int)(Math.random() * (100 - 0 + 1));
}
// 想在main方法里调用其他方法,要在main方法公共类里造一个当前类的对象,
StudentTest1 test = new StudentTest1();
// 遍历学生数组
test.print(stus);
/*for (int i = 0;i < stus.length;i++){
// stus[i].info();
System.out.println(stus[i].info2());
}*/
System.out.println("======================");
// 打印三年级(state值为3)的学生信息
test.searchState(stus,3);
/*for (int i = 0;i < stus.length;i++){
if (stus[i].state == 3){
stus[i].info();
}
}*/
System.out.println("========================");
// 使用冒泡排序按学生成绩排序,并遍历所有学生信息
/* for (int i = 0;i < stus.length - 1;i++){
for (int j = 0;j < stus.length - 1 - i;j++){
if (stus[j].score > stus[j+1].score){
// 这里要交换数组元素的对象!意思是把整个人交换
// 如果只交换成绩,学号和年级都没换,相当于"作弊"
Student1 temp = stus[j];
stus[j] = stus[j+1];
stus[j+1] = temp;
}
}
}*/
/*for (int i = 0;i < stus.length;i++){
System.out.println("学号:" + stus[i].number + "年级:" + stus[i].state + "成绩:"+ stus[i].score);
}*/
}
// 遍历Student1[]类型数组的操作
/**
* @Description 遍历Student1[]类型数组的操作
* @author tiga
* @date time
* @param stus
*/
public void print(Student1[] stus){ // 传入Student1[]类型数组的形参
for (int i = 0;i < stus.length;i++){
stus[i].info();
}
}
// 查找某年级的学生信息
/**
*
* @Description 查找Student类型数组中指定年级的学生信息
* @author tiga
* @date time
* @param stus 要查找的数组
* @param state 要查找的年级
*/
public void searchState(Student1[] stus,int state){ // 传入要找的那个数组和要找的年级为形参
for (int i = 0;i < stus.length;i++){
if (stus[i].state == state){
System.out.println(stus[i].info2());
}
}
}
// 冒泡排序学生成绩,遍历学生信息
public void sort(Student[] stus){
for (int i = 0;i < stus.length - 1;i++){
for (int j = 0;j < stus.length - 1 - i;j++){
if (stus[j].score > stus[j+1].score){
Student temp = stus[j];
stus[j] = stus[j+1];
stus[j+1] = temp;
}
}
}
}
}
class Student1{
int number; // 学号
int state; // 年级
int score; // 成绩
public void info(){
System.out.println("学号:" + number + "年级:" + state + "成绩:" + score);
}
public String info2(){
return "学号:" + number + "年级:" + state + "成绩:" + score;
}
}
- 对象数组的内存解析
image.png引用类型变量只能存储null或对象的地址值(含变量的类型).
4.6.匿名对象
一.理解"万事万物皆对象"
1.在Java语言范畴中,都将功能,结构等封装到类中,通过类的实例化,来调用具体的功能结构
Scanner,String
文件:File
网络资源: URL
2.涉及到Java语言与前端HTML,后端的数据库交互时,前后端的结构在Java层面交互时,都体现为类,对象
二.内存解析的说明:
1.引用类型的变量,只可能存储两类值:null 或 地址值(含变量的类型)
三.匿名对象的使用
1.理解:创建的对象,没有显式的赋给一个变量名.即为匿名对象
2.特征: 匿名对象只能调用一次
3.使用: 可以将匿名对象放入方法里多次调用
- 内存简单分析
在调用方法里new完Phone这个类的对象,在堆里产生的空间也会有一个地址值,调用时new的对象其实是赋值给了定义类里的方法里作为形参,形参又是个局部变量,且该变量会存入栈中,相当于把new Phone这个类出来的对象的地址值赋给了形参,只要是赋给了变量,对象就可以多次使用,从而形参就指向了堆中new的新对象, 就能通过新对象的地址调用其他方法, 这时调用的是同一个匿名对象,这相当于把匿名的对象赋给了有名的对象(形参).
public class InstanceTest {
public static void main(String[] args) {
Phone p = new Phone(); // p就是Phone对象的变量名
// p = null;
System.out.println(p);
p.sentEmail();
p.playGame();
// 匿名对象
// 两者调用的不是同一对象
new Phone().sentEmail();
new Phone().playGame();
new Phone().price = 2000;
new Phone().showPrice(); // 0.0
System.out.println("======================");
// 匿名对象的使用
PhoneMall mall = new PhoneMall();
mall.show(new Phone()); // 理论上是匿名
ShowAddress add = new ShowAddress();
add.printAdd(new PhoneMall());
}
}
class ShowAddress{
public void printAdd(PhoneMall phone){
System.out.println(phone);
}
}
class PhoneMall{
public void show(Phone phone){ // 实际上形参就是匿名函数的名字
phone.sentEmail();
phone.playGame();
}
}
class Phone{
double price; // 价格
public void showPrice(){
System.out.println("手机价格是:" + price);
}
public void sentEmail(){
System.out.println("发送邮件");
}
public void playGame(){
System.out.println("玩游戏");
}
}
- 理解变量的赋值
- 当在方法中定义了基本数据类型的局部变量,其变量存放在栈中,给变量赋值的数字就是变量在栈内存中存放的东西, 变量之间的赋值传递, 也是传递数字.
int m = 10;
int n = m; // n == 10
- 当在方法中定义了引用类型的局部变量,其变量也是存放在栈中,但是变量赋给的值的地址值, 所以当同类型变量之间赋值传递, 是传递地址,而不是数字.
- 值传递机制: 针对基本数据类型
一.方法的形参的传递机制: 值传递
1.形参: 方法定义时,声明的小括号内的参数
2.实参,方法调用时,实际传递给形参的数据,可以是具体数也可以是变量
二.值传递机制: 如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值.
- 内存分析.
1.m和n没有换成的原因: 在栈中,先从main方法进来,main方法中定义的m, n分别赋值10, 20;
2.通过对象v调swap方法,v就不往里面写了,当调用swap方法时候,将上面实参m, n表示数据的值,赋给了下面swap方法的形参m, n(新定义的),这两个形参还要在栈中提供,形参m, n分别是实参赋值过来的,也是10和20,此时的m, n 是swap方法中的;
3.接着v调用swap方法就进入方法执行,一进去又声明新变量temp,temp拿了形参m赋的值,接着形参n的值给了m,接着又把temp给了n,最swap方法实现效果是把swap方法里的m和n交换,如果在方法里面输出,m和n就会交换,但是如果方法内不输出,swap方法执行完以后就销毁了(出栈), 销毁之后接着在main方法下面再输出,输出的m和n的值是main方法赋的值,因为swap方法出栈以后,输出的m和n保存的值是main里面定义的m和n两变量的值, main里面定义的变量在main的整个大括号区间里都是有效的,所以main里面输出的还是main里定义的值,所以两数没换成. 数组元素的值交换也是同理
public static void main(String[] args) {
int m = 10;
int n = 20;
System.out.println(m + "," + n);
ValueTransferTest1 test = new ValueTransferTest1();
test.swap(m,n); // 实参
/*int temp = m;
m = n;
n = temp;*/
System.out.println(m + "," + n); // 10, 20
}
public void swap(int m,int n){ // 新变量实参传入的形参
int temp = m;
m = n;
n = temp;
// System.out.println(m + "," + n);
}
image.png
-
值传递机制: 针对引用数据类型
静态方法调用非静态方法要先new一个对象
静态方法调用静态方法直接调用
非静态方法调用非静态方法直接调用 -
内存分析
image.png1.从main方法进来,在栈中new了一个data变量,也在堆空间中new了一个对象,该对象会有个地址值,并把地址值赋值给栈空间的data变量,通过地址值,栈空间中的data变量指向堆空间中的对象实体
2.接着下一步,data(对象)变量调用Data类中定义的变量m, n,一开始m, n的初始值都为0,通过对象调属性改了m, n的值,此时输出就是10, 20.
3.接着新建的对象去调swap方法,顺便把引用类型data传入作为形参,传入的引用类型data是上面的实参,所以形参是引用类型就存的是地址值,形参在栈中加载,相当于把main方法中data的地址值复制了一份,形参有了地址值后,他就指向堆空间中同一对象实体
4.进入swap方法体,通过data(形参)变量调用m的值,赋给一个swap方法内部声明的局部变量temp,temp也加载在栈中,data的n的值赋给data的m,temp的值又赋给了data的n,至此方法执行结束,结束后,swap方法定义的temp和data变量就出栈了,出栈后形参data的指针就没了,但是堆中对象还在,还有实参data的指向,这样判断堆中对象就不是垃圾,不能回收了.
5.回到main方法接着执行输出 data.m和data.n, 这两data变量是实参data, m和n的值就交换了
- 练习: 方法的参数传递
4.7.面向对象的特征一: 封装和隐藏
image.png面向对象的特征一: 封装和隐藏
一.问题的引入:
当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值,这里,赋值操作要瘦到属性的数据类型和存储范围的制约
除此之外,没有其他制约条件,但是在实际问题中,我们往往需要给属性赋值加入额外的限制调价.这个特条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加.(比如: setLegs())
同时,我们需要避免用户再使用"对象.属性"的方式对属性进行赋值.则需要将属性声明为私有的(private)
-->此时,针对于属性就体现了封装性
二.封装性的体现之一(不等同于封装性):
将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
拓展: 封装性的体现: 1.如上 2.不对外暴露的私有的方法 3.单例模式: 把构造器私有化
三.封装性的体现,需要权限修饰符来配合
1.Java规定的4种权限(从小到大排列): private,缺省,protected,public
2.4种权限可以用来修饰类及类的内部结构: 属性,方法,构造器,内部类,(代码块不行)
3.具体的,4中权限都可以用来修饰类的内部结构:属性,方法,构造器,内部类
修饰类的话,只能用: 缺省,public,不能用private
总结封装性: Java提供了4中权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小
体现一: 将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
体现二: 不对外暴露的私有的方法
体现三:单例模式(将构造器私有化,外边不能随便调构造器, 里面自己只造一个对象,大家都只拿一个对象用)
体现四: 如果不希望类在包外被调用,可以将类设置为缺省的.
public class AnimalTest {
public static void main(String[] args) {
Animal a = new Animal();
a.name = "大黄";
// a.age = 1; // 将属性私有化后不能直接调用,要通过public方法调用
// a.legs = 4; // 作用域不可见 The field Animal.legs is not visible
a.show(); // 谁调的show方法,方法里显示的属性值就是谁的
a.setLegs(-6);
// a.legs = -4; // The field Animal.legs is not visible
a.show();
}
}
class Animal{
String name;
private int age;
// private好处:setLegs提供方法让legs赋值并加入判断条件,还能把直接调属性给禁掉
// 此时就能达到对 legs属性的封装,相当于这个属性没有对外暴露
private int legs; // 腿的数量 限制为私有权先,可见但类外面不能调用
public void setLegs (int l){
if (l >= 0 && l % 2 == 0){
legs = l;
}else{
legs = 0;
// 另一种写法,抛出一个异常(暂时没讲)
}
}
public int getLges(){
return legs;
}
public void eat(){
System.out.println("动物进食");
}
public void show (){
System.out.println("name = " + name + ",age = " + age + ",legs = " + legs);
}
// 提供关于属性age的get和set方法
public int getAge(){
return age;
}
public void setAge(int a){
age = a;
}
}
- 练习: 封装性的基本使用
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
// p1.age = 1; // age私有化后不能直接调用,编译不通过
p1.setAge(12);
System.out.println("年龄为:" + p1.getAge());
}
}
public class Person {
private int age;
public void setAge(int a){
if (a < 0 || a > 130){
// throw new RuntimeException("传入数据非法"); // [[抛异常
System.out.println("传入数据非法");
return; // 结束方法
}
age = a;
}
public int getAge(){
return age;
}
// 绝对不要把设置和获取的方法合起来写! 这些写方法没意义
/*public int doAge(int a){
age = a;
return a;
}*/
}
4.8.类的成员之三: 构造器(或构造方法)
- 类的结构之三,构造器(或构造函数(c++)或构造方法但又不同于方法,constructor)的使用
construct: 建设,建造. construction constructor: 建设者- 一.构造器的作用:
1.创建对象
2.初始化对象的属性- 二.说明:
1.如果没有显式的定义类的构造器的话,则系统默认提供默认无参构造器
2.定义构造器的格式(不同于方法): 权限修饰符 类名(形参列表){}
3.一个类中定义多个构造器,彼此构成重载
4.一旦我们显式的定义了类的构造器之后,系统就不在提供默认的无(空)参构造器(可用国家贫困补贴举例)
5.一个类中,至少会有一个构造器.(相当于给我们造对象的能力,有构造器才能造对象)(可能是默认的也可是自己写的)
public class PersonTest {
public static void main(String[] args) {
// 创建类的对象(构造器的使用): new + 构造器 (构造器和类同名)
Person p = new Person(); // 自己定义了显式的构造器
p.eat();
Person p1 = new Person("tom");
System.out.println(p1.name);
}
}
class Person{
int age;
String name;
// 构造器
public Person(){
// 可以用于初始化 比如实例化这个类时就需要传值,而不是在调用方法才传值
// 构造器里的代码在new对象的时候就执行了
System.out.println("Person().....");
}
// 构造器的重载
public Person(String n){ // 形参的作用 初始化造的对象当前的属性
name = n; // name: 当前正在创建的对象的属性
}
public Person(String n, int a){
name = n;
age = a;
}
public void eat(){
System.out.println("人吃饭");
}
public void study(){
System.out.println("人睡觉");
}
}
- 练习: 构造器的基本使用
1.创建程序,在其中定义两个类:Person和PersonTest类.定义如下:
用setAge()设置热的合法年龄(0~130),用getAge()返回人的年龄
2.1.在前面定义的Person类中添加构造器,利用构造器设置所有人的age属性初始值都为18
2.2.修改上题中类和构造器,增加name属性,使得每次创建Person对象的同时初始化对象的age属性值和name属性值
3.如果类中提供了至少一个构造器,但是没有提供空参构造器,那么构造对象时如果不提供参数就是不合法的.也就是创建没有形参的对象会报错
/*
* 在PersonTest类中实例化Person类的对象b
* 调用setAge()和getAge()方法,体会Java的封装性
*/
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
// p1.age = 1; // age私有化后不能直接调用,编译不通过
p1.setAge(12);
// p1.getAge(); // age == 18
System.out.println("年龄为:" + p1.getAge());
Person p2 = new Person("tom", 21);
// 属性私有化后只能通过方法来调用
System.out.println("name: " + p2.getName() + ", age: " + p2.getAge());
}
}
public class Person {
// 代码顺序: 属性-->构造器-->方法
private int age;
private String name;
public Person(){
age = 18;
}
// 体现了有参构造器
public Person(String n, int a){
name = n;
age = a;
}
public void setAge(int a){
if (a < 0 || a > 130){
// throw new RuntimeException("传入数据非法"); // 抛异常
System.out.println("传入数据非法");
return; // 结束方法
}
age = a;
}
public int getAge(){
return age;
}
// 体现了封装性
public void setName(String n){
name = n;
}
public String getName(){
return name;
}
// (错误写法)绝对不要把设置和获取的方法合起来写! 这些写方法没意义
/*public int doAge(int a){
age = a;
return a;
}*/
}
- 构造器练习: 三角形
/*
* 编写两个类,TriAngle和TriAngleTest,其中TriAngle类中声明私有的底边长base和高height,同时声明公共方法访问私有变量.
* 此处,提供类必要的构造器.另一个类中使用这些公共方法,计算三角形的面积
*/
public class TriAngleTest {
public static void main(String[] args) {
TriAngle t1 = new TriAngle();
t1.setBase(3.2); // 相当于传入实参
t1.setHeight(4.66);
System.out.println("base: " + t1.getBase() + ", height: " + t1.getHeight());
// 构造器一方面可创建对象,同时可给相应对象的属性赋值
TriAngle t2 = new TriAngle(5.1, 4.6);
System.out.println("base: " + t2.getBase() + ", height: " + t2.getHeight());
}
}
public class TriAngle {
private double base;
private double height;
public TriAngle(){
}
public TriAngle(double b,double h){
base = b;
height = h;
}
public void setBase(double b){
base = b;
}
public double getBase(){
return base;
}
public void setHeight(double h){
height = h;
}
public double getHeight(){
return height;
}
}
- 总结: 属性赋值的先后顺序
1.默认初始化(类属性默认值)
2.显式初始化(给类中属性赋值)
3.构造器中初始化(赋值)
=======上面三个操作只能执行一次,下面的可以反复执行=========
4.未封装通过"对象.属性"的方式,赋值或封装后通过"对象.方法名()",赋值
以上操作的先后顺序: 1 - 2 - 3 - 4,从后往前覆盖,前面的都作为过程出现
public class UserTest {
public static void main(String[] args) {
User u = new User();
System.out.println(u.age);
User u1 = new User(2);
u1.setAge(3);
System.out.println(u1.age);
}
}
class User{
String name;
int age = 1;
public User(){
}
public User(int a){
age = a;
}
public void setAge(int a){
age = a;
}
}
4.9.关键字: this的使用---this调用属性和方法
this关键字的使用:
- 1.this可用来修饰: 属性,方法,构造器
- 2.this修饰属性和方法:
this理解为: 当前对象或当前正在创建的对象
- 2.1.在类的方法中,我们课使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法.
但是,通常情况下,我们都选择省略"this. ".特殊情况下,如果方法的形参和类的属性同名时,必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参.- 2.2.在类的构造器中,我们可使用"this.属性"或"this.方法"的方式,调用当前正在创建的对象属性或方法,但是,通常情况下,都选择省略"this.",特殊情况下,如果构造器的形参和类的属性同名时,必须显式的 使用"this.变量"的方式,表明此变量是属性,而非形参.
public class PersonTest {
public static void main(String[] args){
Person p1 = new Person(); // 使用默认的空参构造器
p1.setAge(1);
System.out.println(p1.getAge());
p1.eat();
}
}
class Person{
private String name; // 类的成员变量(属性)
private int age;
public Person(){
this.eat(); // 调当前正在创建的对象的eat方法
}
// 构造器中的this: 当前正在创建的对象
public Person(String name){
this.name = name; // 当前正在创建的对象的属性
}
public Person(int age){
this.age = age;
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
public void setName(String name){ // 属性和形参(局部变量)同名不报错因为地位不同,一个在类中一个在方法中
this.name = name; // 就近原则,优先考虑近的变量名
}
public String getName(){
return this.name;
}
public void setAge(int age){ // age = 1
// age = age; // 1 = 1,跟属性age无关了
// this: 可理解为当前对象,相当于p1
this.age = age; // 当前对象的属性/方法 通过对象调方法或属性都用点
}
public int getAge(){
return this.age;
}
public void eat(){
System.out.println("人吃饭");
this.study();
}
public void study(){
System.out.println("人学习");
}
}
-
this调用构造器
this关键字的使用:
1.this可用来修饰: 属性,方法,构造器
2.this修饰属性和方法:
this理解为: 当前对象或当前正在创建的对象
2.1.在类的方法中,我们课使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法.
但是,通常情况下,我们都选择省略"this. ".特殊情况下,如果方法的形参和类的属性同名时,
必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参.
2.2.在类的构造器中,我们可 使用"this.属性"或"this.方法"的方式,调用当前正在创建的对象属性或方法,
但是,通常情况下,都选择省略"this.",特殊情况下,如果构造器的形参和类的属性同名时,必须显式的
使用"this.变量"的方式,表明此变量是属性,而非形参.
3.this调用构造器
(1)在类的构造器中,可显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器
(2)构造器中不能通过"this(形参列表)"方式调用自己
(3)如果一个类中由n个构造器,则最多有n-1个构造器中使用了"this(形参列表)"
(4)规定: "this(形参列表)"必须声明当前构造器的首行
(5)构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器
public class PersonTest {
public static void main(String[] args){
Person p1 = new Person(); // 使用默认的空参构造器
p1.setAge(1);
System.out.println(p1.getAge());
p1.eat();
System.out.println();
// 构造器多重调用也只是创建了一个对象,只是调用其他构造器时逻辑借用了其他构造器的逻辑
// 不管通过什么方式调用构造器,最终也只是造了一个对象
Person p2 = new Person("Harry", 20);
System.out.println(p2.getAge());
System.out.println(p2.getName());
}
}
class Person{
private String name; // 类的成员变量(属性)
private int age;
public Person(){
// this.eat(); // 调当前正在创建的对象的eat方法
// this(); // 空参构造器不能调自己,会死循环,两个构造器也不能来回调
String info = "Person初始化时,需要考虑如下的1,2,3,4...(共40行代码)";
System.out.println(info);
}
// 构造器中的this: 当前正在创建的对象
public Person(String name){
this(); // 调用构造器的方法,参数是什么,就是调哪个构造器
this.name = name; // 当前正在创建的对象的属性
}
public Person(int age){
this();
this.age = age;
}
public Person(String name, int age){
this(age);
this.name = name; // 再初始化独立的name
// this.age = age; 调用上面的构造器后可省略
}
public void setName(String name){ // 属性和形参(局部变量)同名不报错因为地位不同,一个在类中一个在方法中
this.name = name; // 就近原则,优先考虑近的变量名
}
public String getName(){
return this.name;
}
public void setAge(int age){ // age = 1
// age = age; // 1 = 1,跟属性age无关了
// this: 可理解为当前对象,相当于p1
this.age = age; // 当前对象的属性/方法 通过对象调方法或属性都用点
}
public int getAge(){
return this.age;
}
public void eat(){
System.out.println("人吃饭");
this.study();
}
public void study(){
System.out.println("人学习");
}
}
- 综合练习
public class CustomerTest {
public static void main(String[] args) {
Customer cust = new Customer("Jane", "Smith");
Account acct = new Account(1000, 2000, 0.0123); // 声明构造器
cust.setAccount(acct); // 银行办卡后得到了卡号
cust.getAccount().deposit(100); // 类变量的运用
cust.getAccount().withdraw(960);
cust.getAccount().withdraw(2000);
System.out.println("Customer [" + cust.getLastName() + ","
+ cust.getFirstName() + "] has a account: id is "
+ cust.getAccount().getId() + ", annualInterrestRate is"
+ cust.getAccount().getAnnualInterestRate() * 100
+ "%, balance is " + cust.getAccount().getBalance());
}
}
public class Account {
private int id; // 账号
private double balance; // 余额
private double annualInterestRate; // 年利率
// 构造器
public Account(int id,double balance,double annualInterestRate){
this.id = id;
this.balance = balance;
this.annualInterestRate = annualInterestRate;
}
public void setId(int id){
this.id = id;
}
public int getId(){
return this.id;
}
public void setBalance(double balance){
this.balance = balance;
}
public double getBalance(){
return this.balance;
}
public void setAnnualInterestRate(double annualInterestRate){
this.annualInterestRate = annualInterestRate;
}
public double getAnnualInterestRate(){
return this.annualInterestRate;
}
public void withdraw(double amount){ // 取钱
if (balance < amount){
System.out.println("余额不足,取钱失败");
return; // 和else作用相同
}
balance -= amount;
System.out.println("成功取出" + amount);
}
public void deposit(double amount){ // 存钱
if (amount > 0){
balance += amount;
System.out.println("成功存入" + amount);
}
}
}
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 void setAccount(Account account){
this.account = account;
}
public Account getAccount(){
return this.account;
}
}
- 综合练习2: 对象数组
public class BankTest {
public static void main(String[] args) {
Bank bank = new Bank();
bank.addCustomer("Jane","Smith");
// 连续操作 可能会空指针,方法要有返回值才能连续操作
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).getLastName() + "的账户余额为: " + balance);
bank.addCustomer("万里","杨");
System.out.println("银行客户个数: " + bank.getNumberOfCustomers());
}
}
public class Customer {
private String firstName;
private String lastName;
private Account account;
// 构造器
public Customer(String f,String l){
this.firstName = f;
this.lastName = l;
}
// get,set方法
public String getFirstName(){
return this.firstName;
}
public String getLastName(){
return this.lastName;
}
public void setAccount(Account acct){
this.account = acct;
}
public Account getAccount(){
return this.account;
}
}
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 = balance + amt;
System.out.println("存款成功");
}
}
// 取钱操作
public void withdraw(double amt){
if (balance > amt){
balance = balance - amt;
System.out.println("取钱成功");
}else{
System.out.println("余额不足");
}
}
}
public class Bank {
// 定义了变量,未初始化,但是属性是已经默认初始化了的,此时默认值为null
private Customer[] customers; // 存放多个用户的数组
private int numberOfCustomers; // 记录客户的个数
// 构造器
public Bank(){
customers = new Customer[10]; // 先要给数组初始化空间 因为权限是private所以不能再测试类main里初始化
}
// 添加客户方法
public void addCustomer(String f,String l){
// cust是customer类型变量,上面定义的数组也是customer类型的,所以可以赋值
Customer cust = new Customer(f, l);
// customers[numberOfCustomers] = cust; // 可以理解为 customers[numberOfCustomers] = new Customer(f,l); 数组元素在构造器中静态赋值
// numberOfCustomers++;
// customers = new Customer[10]; // 数组空间初始化不能写在这,因为,本来是是造构造器中造好一个数组后掉一个方法存一个人再调方法再存一个人,写在方法里变成,先调方法造好一个数组后再存一个人,再调一次方法又重新造一个数组再重新存一个人
customers[numberOfCustomers++] = cust;
}
// 获取客户个数的方法
public int getNumberOfCustomers(){
return numberOfCustomers;
}
// 获取指定位置上的客户的方法
public Customer getCustomer(int index){
// return customers[index]; // 不能这样写,可能报异常1.数组未初始化时可能空指针,2.数组初始化后可能会数组下标越界,3.index也许不会超出数组长度,但是大于实际存入的数组元素个数也不行.
if (index >= 0 && index < numberOfCustomers){ // 索引值小于客户数,因为第一个客户放在了第0位
return customers[index];
}
return null;
}
}
4.10.1.package关键字的使用
- 1.为了更好地实现项目中类的管理,提供包的概念
- 2.使用package声明类或接口所属的包,声明在源文件的首行
- 3.包,属于标识符,遵循标识符的命名规则,规范(xxx.yyy.zzz),"见名知意"
- 4.每"."一次,就代表一层文件目录.
补充:同一个包下不能命名同名的接口,类
不同的包下,可以命名同名的接口,类
4.10.2.import关键字的使用
image.png5.1.面向对象特征之二: 继承性的理解
1.为什么要有类的继承性?(继承性的好处)
image.png1.减少了代码的冗余,提高了代码的复用性
2.便于功能的扩展: 把子类都想扩展的功能直接声明在父类当中,子类又因为继承了,直接就可以拿到
3.为之后多态性的使用,提供了前提
2.继承性的格式:
class A extends B{}
A: 子类,派生类,subclass
B: 父类,超类,基类,superclass
3.子类继承父类以后有哪些不同?
2.1.体现: 一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有:属性,方法 (用来调用)
特别地,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构
只有因为封装性的影响,使得子类不能直接调用父类的结构而已
2.2.子类继承父类以后,还可以声明自己特有的属性或方法,实现自己特有的功能的拓展
子类和父类的关系,不同于子集和集合的关系(父类有的子类一定有,子类有的父类不一定有)
4.Java中继承的说明
image.png1.一个父类可以被多个子类继承.
2.Java中类的单继承性:一个类只能有一个父类;(接口可以多继承)
3.子父类是相对的概念
4.子类直接继承的父类,称为: 直接父类;简介继承的父类称为:间接父类
5.子类继承父类后,就获取了直接父类以及所有间接父类中声明的属性和方法
public class ExtendsTest {
public static void main(String[] args) {
Person p1 = new Person();
// p1.age = 1;
p1.eat();
Student s1 = new Student();
// s1.age = 1;
s1.eat(); // 私有化的方法也被继承,
// s1.sleep();
s1.setAge(10);
System.out.println(s1.getAge());
s1.breath();
Creature c = new Creature();
System.out.println(c.toString());
}
}
public class Creature {
public void breath(){
System.out.println("呼吸");
}
}
public class Person extends Creature {
private int age;
String name;
// 定义构造器
public Person(){
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
// 定义方法
public void eat(){
System.out.println("吃饭");
sleep();
}
private void sleep(){
System.out.println("睡觉");
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return this.age;
}
}
// Student继承Person类的属性,功能...
public class Student extends Person{
// Person中定义过的可以省略
/*String name;
int age;*/
String major; // 专业 对继承的不满意可以自己创建新的
// 定义构造器 和类重名不能删
public Student(){
}
public Student(String name,int age, String major){
this.name = name;
setAge(age);
this.major = major;
}
// 定义方法
/*public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}*/
// 独有的方法也不能删
public void study(){
System.out.println("学习");
}
public void show(){
System.out.println("name: " + name + ", age = " + getAge());
}
}
- 继承性练习:
public class CylinderTest {
public static void main(String[] args) {
Cylinder cy = new Cylinder();
cy.setRadius(1.2);
cy.setLength(3.4);
double volume = cy.findVolume();
System.out.println("圆柱的体积为: " + volume);
// 没有重写findArea方法时
/*double area = cy.findArea();
System.out.println("底面圆的面积: " + area);*/
// 重写findArea方法后
double area = cy.findArea();
System.out.println("圆柱的表面积: " + area);
System.out.println("========================");
// 体现子类默认调用父类空参构造器:super()
Cylinder cy1 = new Cylinder(); // 输出: 3.1415926...
double volume1 = cy1.findVolume();
System.out.println("圆柱的体积为: " + volume1);
}
}
public class Circle {
private double radius; // 半径
public Circle(){
radius = 1.0;
}
public Circle(double radius){
this.radius = radius;
}
public void setRadius(double radius){
this.radius = radius;
}
public double getRadius(){
return this.radius;
}
// 计算面积
public double findArea(){
return Math.PI * radius * radius;
}
}
/*
* 如果把父类空参构造器注释掉子类空参构造器报错是因为子类构造器先执行父类的空参构造器super();而父类没有空参构造器
* 如果再把子类的空参构造器注释掉,子类类名又会报错,因为,子类有系统默认提供的空参构造器,构造器还是要调用父类的空参构造器super();而父类没有空参构造器
*/
public class Cylinder extends Circle{
private double length;
public Cylinder(){
// 实际默认有个super(); // 通过子类构造器造对象,他一上来就奔到父类空参构造器初始化半径
length = 1.0;
}
public void setLength(double length){
this.length = length;
}
public double getLength(){
return this.length;
}
// 返回圆柱体积
public double findVolume(){
// return Math.PI * getRadius() * getRadius() * length;
// return findArea() * getLength(); // 子类把父类方法重写覆盖了,现在调用的是子类的findArea方法,所以不能用这个方法算
return super.findArea() * getLength(); // 调用父类的findArea方法
}
// 返回圆柱表面积
public double findArea(){
return Math.PI * getRadius() * getRadius() * 2 + Math.PI * getRadius() * 2 * getLength();
}
}
5.java.lang.Object类的理解
特别声明是java.lang包下的Object类因为可能自己会创建同名的Object类
1.如果我们没有先是的声明一个类中的父类的话,则此类继承于java.lang.Object类
2.所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
3.所有java类具有java.lang.Object类声明的功能
5.2.方法重写的理解(override/overwrite)
方法重写(override/ overwrite): 方法名形参相同,只改变方法体.权限范围可相同或更大(除private外),返回值类型相同或更大
1.重写: 子类继承父类后,可以对父类中同名同参数的方法,进行覆盖操作,执行的就是自己的
2.应用: 重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法(相当于子类自己定义的)
3.应用: 重写规定:
方法的声明: 权限修饰符 返回值类型 方法名(形参列表){
方法体
}
约定俗称: 子类中的叫重写的方法,父类中的叫被重写的方法
(1)子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
(2)子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
特殊情况: 子类不能重写父类中声明为private权限的方法
(3)返回值类型:
父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
父类被重写的方法的返回值类型是基本数据类型(如:double),则子类重写的方法的返回值类型是必须相同的基本数据类型(必须也是double)(自动类型提升不等同于子父类关系)
(4)子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型,(具体到异常处理时候讲)
========================================================================
子类和父类中的同名同参数的方法要么都声明为非static的(才考虑重写),要么都声明为static的(不是重写),因为静态方法不能被覆盖,是随着类的加载而加载的
面试题: 区分方法的重载和重写
public class PersonTest {
public static void main(String[] args) {
Student s = new Student("计算机科学与技术");
s.eat();
s.walk(10); // 显然这里walk方法里调的是父类的show方法,如果是重写,子类的show方法会把父类的覆盖
System.out.println("============");
s.study();
Person p1 = new Person();
p1.eat(); // 父类的对象调用父类的方法,显示的还是父类的,自己定义就调自己
}
}
public class Person {
String name;
int age;
public Person(){
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("吃饭");
}
public void walk(int distance){
System.out.println("走路的距离是 " + distance + " 公里");
show();
eat();
}
// 父类中的声明为private权限的方法不能被子类重写
private void show(){
System.out.println("我是人");
}
// 父类被重写的方法的返回值类型要大于等于子类重写的方法
public Object info(){
return null;
}
public double info1(){
return 1.0;
}
}
public class Student extends Person{
String major;
public Student(){
}
public Student(String major){
this.major = major;
}
public void study(){
System.out.println("学习专业是: " + major);
}
// 对父类中eat()进行了重写
public void eat(){
System.out.println("学生应都吃有营养的事物");
}
public void show(){
System.out.println("我是学生");
}
public String info(){
return null;
}
// 子类重写方法的返回的基本数据类型必须和父类被重写的相同
/*public int info1(){
return 1;
}*/
}
5.3.四种权限访问修饰符
public class OrderTest {
public static void main(String[] args) {
Order order = new Order();
order.orderDefault = 1;
order.orderProtected = 2;
order.orderPublic = 3;
order.methodDefault();
order.methodProtected();
order.methodPublic();
// 同一个包中的其他类,不可以调用Order类中私有的属性,方法
/*order.orderPrivate = 4;
order.methodPrivate();*/
}
}
public class Order {
private int orderPrivate;
int orderDefault;
protected int orderProtected;
public int orderPublic;
private void methodPrivate(){
orderPrivate = 1;
orderDefault = 2;
orderProtected = 3;
orderPublic = 4;
}
void methodDefault(){
orderPrivate = 1;
orderDefault = 2;
orderProtected = 3;
orderPublic = 4;
}
protected void methodProtected(){
orderPrivate = 1;
orderDefault = 2;
orderProtected = 3;
orderPublic = 4;
}
public void methodPublic(){
orderPrivate = 1;
orderDefault = 2;
orderProtected = 3;
orderPublic = 4;
}
}
==============================================
import com.oop.demo02.PermissionModifier.Order;
public class OrderTest {
public static void main(String[] args) {
// 通过造对象体现
Order order = new Order();
order.orderPublic = 1;
order.methodPublic();
// 不同包下的普通类(非子类)要使用Order类,不可以调用声明为private,缺省,protected权限的属性,方法
/*order.orderPrivate = 2;
order.orderDefault = 3;
order.orderProtected = 1;
order.methodPrivate();
order.methodDefault();
order.methodProtected();*/
}
// 通过不造对象体现,但是调用(形参)实际还是要造对象
public void show(Order order){
order.orderPublic = 1;
order.methodPublic();
// 不同包下的普通类(非子类)要使用Order类,不可以调用声明为private,缺省,protected权限的属性,方法
/*order.orderPrivate = 2;
order.orderDefault = 3;
order.orderProtected = 1;
order.methodPrivate();
order.methodDefault();
order.methodProtected();*/
}
}
import com.oop.demo02.PermissionModifier.Order;
public class SubOrder extends Order {
public void method(){
// 因为已经继承了父类,相当于调用自己的属性和方法,所以不用new对象也能调用
// 继承后直接可以赋值,相当于继承帮你声明了
orderProtected = 1;
orderPublic = 2;
methodProtected();
methodPublic();
// 在不同包的子类中,不能调用父类Order类中声明private和缺省权限的属性,方法
/*orderDefault = 3;
orderPrivate = 2;
methodDefault();
methodPrivate();*/
}
}
5.4.super关键字的使用
1.super理解为:父类的
2.super可以用来调用(修饰):属性,方法,构造器,调的父类(就近原则),this也一样
3.super的使用
3.1.可以在子类类的方法或构造器中.通过使用"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法.但是,通常情况下,习惯省略"super.",(在父类有,子类没有属性或方法的情况下)
3.2.特殊情况下,当子类和父类中定义了同名的属性时,要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性.
3.3.特殊情况,当子类重写了父类中的方法后,想在子类的方法中调用父类中被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法.
4.super调用构造器
4.1.可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
4.2."super(形参列表)"的使用,必须声明在子类构造器的首行!
4.3.在类的构造器中,针对"this(形参列表)"(表示调用本类中重载的其他构造器或"super(形参列表)"(表示父类中指定的构造器),只能二选一,不能同时出现
4.4.在构造器的首行,没有显式的声明"this(形参列表)"或:super(形参列表)",则默认调用的是父类中空参构造器:super()
4.5.一个类的构造器可能有多个,构造器的首行不是this(形参列表),就是super(形参列表),只能二选一,不可能有其他情况
一个类可能有n个构造器,至少有一个用super方式调用了父类的构造器super();最多有n-1个使用了this(形参列表),剩下一个没用的只能是用了super();
public class SuperTest {
public static void main(String[] args) {
Student s = new Student();
s.show();
s.study();
Student s1 = new Student("tom", 21, "IT");
s1.show();
System.out.println("=================");
Student s2 = new Student();
}
}
public class Creature {
int age = 1;
}
public class Person extends Creature {
String name;
int age;
int id = 1001; // 身份证号
public Person(){
System.out.println("我无处不在!");
}
public Person(String name){
this.name = name;
}
public Person(String name,int age){
this(name);
this.age = age;
}
public void eat(){
System.out.println("人: 吃饭");
}
public void walk(){
System.out.println("人: 睡觉");
}
}
public class Student extends Person{
String major;
int id = 1002; // 学号 属性此时在内存中有两个id,属性不像方法一样会覆盖掉父类的
public Student(){
super(); // 编译器先执行了父类的构造方法才执行子类的操作
}
public Student(String major){
super(); // 子类中每个构造器都默认隐式的调用了父类的空参构造器,
this.major = major;
}
// super使用构造器
public Student(String name, int age, String major){
// (有继承权无使用权)继承是子类继承父类的属性和方法,但调用了private修饰的属性和方法,子类不能直接访问
super(name,age); // 调用父类指定参数的构造器
this.major = major;
}
@Override
public void eat() {
System.out.println("学生多吃有营养的东西");
}
public void study(){
System.out.println("学生学习知识");
eat(); // 默认都是this.eat(),调用重写以后的方法,
super.eat(); // 调用父类的方法
walk(); // 调用子类中未重写父类中的方法,方法前写this,super都可以通常省略掉了,区别在this先在子类中找,找没才去父类中找,super直接去父类(包括直接父类和间接父类)中找,直到找到为止
}
public void show(){
// this: 先在当前的Student类当中找看看有无该属性的结构,如果在自己类里没找到,他再接着去父类中找
// super: 不在(当前)子类找,直接去父类找
System.out.println("name = " + this.name + ", age = " + super.age);
System.out.println("id = " + id); // id前省略的是: this.
System.out.println("id = " + super.id);
}
}
5.5.子类对象实例化过程
子类对象实例化的全过程
如果没有明确地指出父类,Object类就是该类的父类,是默认的,而且不用显式地写出来
1.从结果上来看:(继承性)
子类继承父类后,就获取了父类声明的属性或方法
创建子类的对象,在堆空间中,就会加载所有父类(直接父类,间接父类)中声明的属性
2.从过程上来看:
当通过子类的构造器创建子类对象时,一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器...
直到调用了java.lang.Object类中空参的构造器为止.正因为家在过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用
明确: 虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象,因为堆中值分配了一个地址
- 练习: 继承&super
public class AccountTest {
public static void main(String[] args) {
Account acct = new Account(1122, 20000, 0.045);
acct.withdraw(30000);
System.out.println("账户余额: " + acct.getBalance());
acct.withdraw(2500);
System.out.println("账户余额: " + acct.getBalance());
acct.deposit(3000);
System.out.println("账户余额: " + acct.getBalance());
System.out.println("月利率: " + (acct.getMonthInterest() * 100) + "%");
}
}
public class Account {
private int id; // 账号
protected double balance; // 余额
private double annualInterestRate; // 年利率
// 子类构造器/类名报错解决办法: 1.给父类造空参构造器 2.给子类造指定构造器
public Account(int id, double balance, double annualInterestRate){
super();
this.id = id;
this.balance = balance;
this.annualInterestRate = annualInterestRate;
}
public void setId(int id){
this.id = id;
}
public int getId(){
return this.id;
}
public void setBalance(double balance){
this.balance = balance;
}
public double getBalance(){
return this.balance;
}
public void setAnnualInterestRate(double annualInterestRate){
this.annualInterestRate = annualInterestRate;
}
public double getAnnualInterestRate(){
return this.annualInterestRate;
}
// 返回月利率
public double getMonthInterest(){
return this.annualInterestRate / 12;
}
// 取钱
public void withdraw(double amount){
if (balance >= amount){
balance -= amount;
return;
}
System.out.println("余额不足");
}
// 存钱
public void deposit(double amount){
if (amount > 0){
balance += amount;
}
}
}
public class CheckAccount extends Account{
private double overdraft; // 可透支限额
public CheckAccount(int id, double balance, double annualInterestRate, double overdraft){
super(id,balance,annualInterestRate);
this.overdraft = overdraft;
}
public void setOverdraft(double overdraft){
this.overdraft = overdraft;
}
public double getOverdraft(){
return this.overdraft;
}
@Override
public void withdraw(double amount) {
// 余额足够消费
if (this.getBalance() >= amount){
// 方式一:
// setBalance(getBalance() - amount);
// 方式二:
super.withdraw(amount); // super.属性/方法可以调用父类的属性和方法
// 方式三: 改balance的权限为protected
// balance = balance - amount;
// 透支+余额足够消费
}else if (overdraft >= amount - getBalance()){
overdraft = overdraft - (amount - getBalance()); // 先从可透支余额取出
// setBalance(0); // 再把余额全清空
// 或
super.withdraw(getBalance());
}else{
System.out.println("超过可透支限额");
}
}
}
public class CheckAccountTest {
public static void main(String[] args) {
CheckAccount acct = new CheckAccount(1122, 20000, 0.045, 5000);
acct.withdraw(5000);
System.out.println("账户余额: " + acct.getBalance() + ", 可透支余额: " + acct.getOverdraft());
acct.withdraw(18000);
System.out.println("账户余额: " + acct.getBalance() + ", 可透支余额: " + acct.getOverdraft());
acct.withdraw(3000);
System.out.println("账户余额: " + acct.getBalance() + ", 可透支余额: " + acct.getOverdraft());
}
}
5.6.多态性的使用
面向对象特征之三: 多态性
1.理解多态性: 可以理解为一个事物的多种形态
2.何为多态性: 父类的引用指向子类的对象(或子类的对象赋给父类的引用)
3.多态的使用: 虚拟方法使用
有了对象的多态性后,在编译期,只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法
如果子类中没有重写父类的方法,就执行父类的方法(这样使用多态性没有意义)
总结:编译看等号左边的类型,运行看右边的对象(编译时是父类,执行时是子类)
4.多态性的使用前提(只说方法的事没有属性的事,多态性方面跟属性没关系): (1)要有类的继承关系 (2)方法的重写
5.对象的多态性: 只适用于重写的方法,不适用于属性(属性的编译和运行都看左边).
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.eat();
Man man = new Man();
man.eat();
man.age = 25;
man.earnMoney();
System.out.println("*********************");
// 对象的多态性: 父类的引用(p2)指向子类的对象(new Man())
Person p2 = new Man(); // 多态的形式
Person p3 = new Woman();
// 多态的使用: 当调用子类同名同参数的方法时,实际执行的是子类重写父类的方法---虚拟方法调用
// 有了多态的形式后,通过引用调用子父类都声明过的方法,真正执行的是子类重写父类的方法
p2.eat(); // 编译看左,运行看右
p2.walk();
// p2.earnMoney(); // 多态性的使用不能调父类没有的方法
System.out.println(p2.id);
}
}
public class Person {
String name;
int age;
int id = 1001;
public void eat(){
System.out.println("人吃饭");
}
public void walk(){
System.out.println("人走路");
}
}
public class Man extends Person{
boolean isSmoking;
int id = 1002;
public void earnMoney(){
System.out.println("男人负责挣钱");
}
/*@Override
public void eat() {
System.out.println("男人多吃肉,长肌肉");
}*/
@Override
public void walk() {
System.out.println("男人霸气的走路");
}
}
public class Woman extends Person{
boolean isBeauty;
public void goShopping(){
System.out.println("女人喜欢购物");
}
@Override
public void eat() {
System.out.println("女人少吃为了减肥");
}
@Override
public void walk() {
System.out.println("女人窈窕的走路");
}
}
- 多态性的使用举例:
import java.sql.Connection;
// 多态性的使用举例一:
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Dog());
test.func(new Cat());
}
// 使用多态性: 凡是new子类的对象
// 每种动物对一件事表现出来的形态都不一样 就是多态
public void func(Animal animal){ // Animal animal = new Dog(); 对象多态性的形式
// 因为形参是Animal类型的,所以只能调Animal类里的方法,但实际new了一个Dog类的对象,真正运行的时候,是Dog重写父类方法的执行
animal.eat();
animal.shout();
}
// 不用多态性的写法: 声明什么类型只能new这个类型的对象
public void func(Dog dog){
dog.eat();
dog.shout();
}
public void func(Cat cat){
cat.eat();
cat.shout();
}
}
// 父类
class Animal{
public void eat(){
System.out.println("动物进食");
}
public void shout(){
System.out.println("动物叫");
}
}
// 子类
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
@Override
public void shout() {
System.out.println("汪汪汪");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void shout() {
System.out.println("喵喵喵");
}
}
// 举例二:
class Order{
public void method(Object obj){
}
}
// 举例三:
class Driver{
// 形参里传哪个对象就建立跟哪个数据库的连接,因为子类方法都重写过了,自然就能实现对那个数据库中表的操作
public void doData(Connection conn){ // conn = new MySQLConnection();/ conn = new OracleConnection();/...
// 规范步骤去操作数据 都是子类重写过的方法
/*conn.method1();
conn.method2();
conn.method3();*/
}
}
网友评论