面向对象
本章为大体内容, 一些细分内容请见下章
文章目录
1面向对象内容的三条主线:
- 类的成员:属性、方法、构造器;(熟悉)代码块、内部类
- 面向对象的特征:封装、继承、多态、抽象
- 关键字的使用:this、super、package、import、static、final、interface、abstract等
2面向过程、对象
1 区别
面向过程:
- 以函数
为组织单位。
- 是一种“执行者思维
”,适合解决简单问题。扩展能力差、后期维护难度较大。
面向对象:
- 以
类
为组织单位。每种事物都具备自己的属性
和行为/功能
。
种“设计者思维
”,适合解决复杂问题。代码扩展性强、可维护性高。
相辅相成的。面向对象离不开面向过程!
3.类、对象
类:具有相同特征的事物的抽象描述,是抽象的定义。
对象:实际存在的该类事物的每个个体
,是具体的。
4 类的声明与使用
1 设计类
class Person{
}
2 类的实例化1
格式:类类型 对象名 = 通过new创建的对象实体
Phone p1 = new Phone(); //1
Scanner scan = new Scanner(System.in);//2
String str = new String();//3
3 内部成员:
属性、成员变量、field
1变量的分类:
- 角度一:按照数据类型来分:基本数据类型(8种)、引用数据类型(数组、类、接口、枚举、注解、记录)
- 角度二:按照变量在类中声明的位置的不同:成员变量(或属性)、局部变量(方法内、方法形参、构造器内、构造器形参、代码块内等)
2属性的几个称谓:成员变量、属性、field(字段、域)
3成员变量\局部变量
3.1 相同点:
- 变量都有其有效的作用域。出了作用域,就失效了。
3.2 不同点:
① 类中声明的位置的不同:
属性:声明在类内,方法外
局部变量:声明方法、构造器内
② 在内存中分配的位置不同:
属性:随着对象的创建,存储在堆空间中。
局部变量:存储在栈空间中
③ 生命周期:
属性:随着对象的创建而创建,随着对象的消亡而消亡。
局部变量:随着方法对应的栈帧入栈,局部变量会在栈中分配;随着方法对应的栈帧出栈,局部变量消亡。
④是否可以有权限修饰符进行修饰:
都有哪些权限修饰符:public、protected、缺省、private。(用于表明所修饰的结构可调用的范围的大小)
属性,是可以使用权限修饰符进行修饰的。
而局部变量,不能使用任何权限修饰符进行修饰的。
⑤ 默认值:
属性:都有默认初始化值
局部变量:都没有默认初始化值。
意味着,在使用局部变量之前,必须要显式的赋值,否则报错。
方法、函数、method
运用中出现的错误
- 方法没放在类内
- 在类内引用时应该 类名.方法名(形参列表);
1 好处
用来完成某个功能。
2 格式
权限修饰符 [其它修饰符] 返回值类型 方法名(形参列表) { //方法头
//方法体
}
注:[]中的内部不是 必须的
3 举例
声明
public void eat()
public void sleep(int hour)
public String interests(String hobby)
public int getAge()
调用
- Math.random()的random()方法
- Math.sqrt(x)的sqrt(x)方法
- System.out.println(x)的println(x)方法
- new Scanner(System.in).nextInt()的nextInt()方法
- Arrays类中的binarySearch()方法、sort()方法、equals()方法
4 各名称的意义
权限修饰符 [其它修饰符] 返回值类型 方法名(形参列表)
4.1 权限修饰符
① 有四种:private \ 缺省 \ protected \ public
4.2 返回值类型:描述当调用完此方法时,是否需要返回一个结果。
分类:
无返回值类型:使用void表示即可。
有具体的返回值类型:需要指明返回的数据的类型。
需要"return + 返回的值"
4.3 方法名:需要满足标识符的规定和规范。“见名知意”
4.4 形参列表:形参,属于局部变量,且可以声明多个。
格式:(形参类型1 形参1,形参类型2 形参2,…)
分类:无形参列表 、 有形参列表
无形参列表:不能省略()。 比如:Math.random()、new Scanner(System.in).nextInt()
**有形参列表:**需要的不确定的变量的类型和个数,确定形参的类型和个数。 比如:Arrays类中的binarySearch()方法、sort()方法、equals()方法
4.5 方法体:
方法的功能。
5 注意点
方法不能独立存在,所有的方法必须定义在类里。
方法不调用,不执行。每调用一次,就执行一次。
方法内可以调用本类中的(其它)方法或属性
方法内不能定义方法。
6.关键字:return
6.1 return的作用
- 作用1:结束一个方法
- 作用2:可以返回数据给方法的调用者
6.2 使用注意点:
return后面不能声明执行语句。
ps:形参与实参
-
形参:方法在声明时,一对()内声明的一个或多个形式参数,简称为形参。
-
实参:方法在被调用时,实际传递给形参的变量或常量,就称为实际参数,简称实参。
ps2:
在修改类中的变量时 , 且下方方法有用到这个变量 , 则方法内的这变量也会修改 .
例
class twoAdd(){
int first=0;
public int add(){
return first+first;
}
}
public twoAdd_text(){
public static void main(System[] args){
twoAdd a=new twoAdd();
a.first()=2;
System.out.println(a.add());//则输出后是 2 ;
}
}
5 四种方法高级使用方法
1 方法重载
-
定义:在同一个类中,存在同名方法,只要参数列表不同返回值不同也行即可。
如此就可以让不同数据类型调用一种方法了
2可变个数形参
-
使用场景
在调用方法时,可能会出现方法形参的类型是确定的,但是参数的个数不确定。 -
格式:([前面也可以声明一个或多个形参],参数类型 … 参数名)
ps:参数类型本质上是一个数组
-
说明:
① 可以构成重载
② 特例:可变个数形参的方法与同一个类中方法名相同,且与可变个数形参的类型相同的数组参数不构成重载。
③ 必须声明在形参列表的最后
3 方法值传递机制
1 对于方法内声明的局部变量来说:如果出现赋值操作
如果是基本数据类型的变量,则将此变量保存的数据值传递出去。
如果是引用数据类型的变量,则将此变量保存的地址值传递出去。
2 方法的参数的传递机制:值传递机制
2.1 概念
形参:方法名后**()中声明的**变量
实参:方法名后面括号()中的使用的值/变量/表达式
易错点:
public void method1(int m)/*形参*/{
m++;
}
int m=10;//实参
test.method1(m);
System.out.println("m="+m);
//此时m的值是10 因为基本数据类型引用的值 形参改变实参不改变
//若要实现m++这个 可以把类中的方法改成如下如下
public void method1(int m)/*形参*/{
m++;
System.out.println("m="+m);
System.exit(0);//直接停止程序
}
引出规则 ↓↓↓
2.2 规则:实参给形参赋值的过程
如果形参是基本数据类型的变量,则将实参保存的数据值赋给形参。
如果形参是引用数据 类型的变量,则将实参保存的地址值赋给形参。
ps:面试题:
Java中的参数传递机制是什么?
值传递 基本数据类型传数值 , 引用数据类型传递地址 。(不是引用传递)
ps2: 数组内元素交换
要用数组加上下标的形式传参
swap(arr,i,i+1);
这样一来 , 数组是个地址 , i和i+1虽然是形参 , 但作用于 arr这数组的地址上 , 可以调换堆中的值
4 递归
1 方法自己调用自己
2 分类:直接递归2、间接递归3
3使用说明:
- 递归方法包含了一种
隐式的循环
。 - 递归 方法会
重复执行
某段代码 . - 递归一定要向已知方向递归(在前就用’ - ’ , 在后就用’ + '),否则死循环。最终发生栈内存溢出。(找不着已知的值 , 递归也会迷茫QAQ)
4 注意:
- 递归调用会占用大量的系统堆栈,内存耗用多,在递归调用层次多时速度要比循环慢的多,
所以在 使用递归时要慎重。 - 在要求高性能的情况下尽量避免使用递归,递归调用既花时间耗又内存。考虑使用循环迭代
一些经典案例
走台阶 \ 斐波那契 \ 快排
5.面向对象三步流程
步骤1:创建类,并设计类的内部成员4
步骤2:**new一个类的对象。**比如:Phone p1 = new Phone();
步骤3:通过对象,调用其内部声明的属性或方法
6 package(包)关键字的使用
1 说明
package用于指明该文件中定义的类、接口等结构所在的包
ps : package语句作为Java源文件的第一条语句出现。若缺省该语句,则指定为无名包。
2 包名
-
全部小写、见名知意
-
包通常使用所在公司域名的倒置:com.xxx(公司).xxx。
-
不要使用"
java.xx
"包 (容易报错) -
语句中用 “.” 来指明包(目录)的层次,每 . 一次就表示一层文件目录。
- 包的作用
- 包含类和子包,划分层次
- 将功能相近的类划分到同一个包中。
- 解决类命名冲突的问题
- 控制****访问权限
- JDK中主要的包
java.lang
----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread,提供常用功能
java.net
----包含执行与网络相关的操作的类和接口。
java.io
----包含能提供多种输入/输出功能的类。
java.util
----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。
java.text
----包含了一些java格式化相关的类
java.sql
----包含了java进行JDBC数据库编程的相关类/接口
java.awt
----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
7 import(导入)关键字的使用
用法及说明
1 import语句告诉编译器到哪里去寻找类。
2 用 a.* ,表示导入a包下的所有的结构。
例:可以使用java.util.*的方式,一次性导入util包下所有的类或接口。
3 类或接口是java.lang包下的,或当前包下的,则可以省略此import语句。
4 如果已导入java.a包,还需要使用a包的子包下的类的话,仍然需要导入。
5 如果在代码中使用不同包下的同名的类,那么就需要使用类的全类名。
例 java.sql.Date date1 = new java.sql.Date(121231231L);
8 面向对象特征
之一:封装性
1 封装性?
高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
(java程序通常以类的形态呈现 , 相关的功能封装到方法中)低耦合:仅暴露少量的方法给外部使用。
(给相关的类和方法设置权限 , 把该隐藏的隐藏起来 , 该暴露的暴露出去)
白话:把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
2 如何实现数据封装?
2.1 权限修饰符
private
缺省
protected
public
2.2 作用
用4种**权限修饰来修饰类及类的内部成员。**当这些成员被调用时,体现可见性的大小。
2.3 例:
给Animal的对象的legs属性赋值。在实际的常识中,legs不能赋值为负数的。但是如果
直接调用属性legs,是不能加入判断逻辑的。
将legs属性私有化(private),禁止在Animal类的外部直接调用此属性
提供给legs属性赋值的setLegs()方法,在此方法中加入legs赋值的判断逻辑if(legs >= 0 && legs % 2 ==0)
将此方法暴露出去,使得在Animal类的外部调用此方法,对legs属性赋值。
提供给legs属性获取的getLegs()方法,此方法对外暴露。使得在Animal类的外部还可以调用此属性的值。
//包
public class Person {
private int age;
//设置age属性
public void setAge(int a){
if(a >= 0 && a <= 130){
age = a;
}else{
System.out.println("你输入的数据非法");
}
}
//获取age属性
public int getAge(){
return age;
}
//错误的
// public int doAge(int a){
// if(a >= 0 && a <= 130){
// age = a;
// return age;
// }else{
// System.out.println("你输入的数据非法");
// return -1;
// }
// }
//
//这样耦合度太高了
//!!!每一个方法是为了组合成一个功能
-------------------------------------------------------------
//主
public class PersonTest {
public static void main(String[] args) {
//创建Person实例1
Person p1 = new Person();
// p1.age = 10; //编译不通过
// System.out.println(p1.age);//因为被封装了
p1.setAge(20);
System.out.println(p1.getAge());
}
}
2.4 4种权限具体使用
**类:**只能使用public、缺省修饰
**类的内部成员:**可以使用4种权限修饰进行修饰。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-92uoZpcr-1680571358890)(C:\Users\76344\AppData\Roaming\Typora\typora-user-images\1679659874255.png)]
2.5 使用频率:
比较高:public、private
比较低:缺省、protected
场景1:私有化(private)类的属性,提供公共(public)的get和set方法,对此属性进行获取或修改
场景2:将类中不需要对外暴露的方法,设置为private.
场景3:单例模式中构造器private的了,避免在类的外部创建实例。(放到static关键字后讲)
之二: 继承性
1 理解
生活上:
财产的继承、颜值的继承代码层面:
- 自上而下:定义了一个类A,在定义另一个类B时,发现类B的功能与类A相似,考虑类B继承于类A
- 自下而上:定义了类B,C,D等,发现B、C、D有类似的属性和方法,则可以考虑将相同的属性和方法进行抽取,封装到类A中,让类B、C、D继承于类A,同时,B、C、D中的相似的功能就可以删除了。
2 继承性的好处
- 减少了代码冗余,提高了代码的复用性。
- 更有利于功能的扩展。
- 让类与类之间产生了
is-a
的关系,为多态的使用提供了前提。- 继承描述事物之间的所属关系,父类更通用、更一般,子类更具体。
3 继承的格式:
class A{
//属性、方法
}
class B extends A{
//B独有的
}
基本概念:
类A: 父类、superClass、超类、基类
类B: 子类、subClass、派生类
4 使用继承性后:
-
子类就获取到了父类中声明的所有的属性和方法。
-
但是,由于封装性的影响,可能子类不能直接调用父类中带有**(private)权限修饰符声明**的属性或方法。
-
子类在继承父类以后,还可以扩展自己特有的功能(体现:增加特有的属性、方法)
extends:延展、扩展、延伸 -
不要为了继承而继承。在继承之前,判断一下是否有is a的关系。
5 默认的父类
Java中声明的类,如果没有显式的声明其父类时,则默认继承于java.lang.Object
↓↓↓
- 补充说明:
Java是支持多层继承。
子父类的概念是相对的。
Java中一个父类可以声明多个子类。反之,一个子类只能有一个父类(Java的单继承性)
方法的重写
理解:
方法重写 ,一般有继承关系 , 根据子类的特性 , 重写父类的方法; 而方法重写一般是在同一个类中同名方法有不同形参列表.
1 为何需要方法的重写?
子承父类后,父类中的方法可能不太适用于子类,子类需要对父类中继承过来的方法进行覆盖、覆写的操作。
举例(银行账户):
class Account{//账户
double balance;//余额
//取钱
public void withdraw(double amt){
//判断balance余额是否够amt取钱的额度
}
}
class CheckAccount extends Account{ //信用卡
double protectedBy;//透支额度
public void withdraw(double amt){
//判断balance余额是否够amt取钱的额度
//如果不够,还可以考虑从protectedBy额度里取
}
}
class AccountTest{
public static void main(String[] args){
CheckAccount acct = new CheckAccount();
acct.withdraw(); //执行的是子类重写父类的方法
}
}
2 方法的重写含义
子类对父类继承过来的方法进行的覆盖、覆写的操作,就称为方法的重写。
3 规则
方法声明的格式:权限修饰符 返回值类型 方法名(形参列表) [throws 异常类型] { //方法体 }
具体规则:
① 父类被重写的方法与子类重写的方法的方法名和形参列表必须相同。
② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
子类不能重写父类中声明为private权限修饰的方法。
③ 关于返回值类型:
父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型必须是void
父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须与被重写的方法的返回值类型相同。
父类被重写的方法的返回值类型是引用数据类型(比如类),则子类重写的方法的返回值类型可以与被重写的方法的返回值
类型相同 或 是被重写的方法的返回值类型的子类
④ (超纲)子类重写的方法抛出的异常类型可以与父类被重写的方法抛出的异常类型相同,或是父类被重写的方法抛出的异常类型的子类。
补充说明:方法体:没有要求。但是子类重写的方法的方法体必然与父类被重写的方法的不同。
- 面试题:区分方法的重载(overload)与重写(override / overwrite)
重载:“两同一不同”
重写:继承以后,子类覆盖父类中同名同参数的方法
[类比]相同类型的面试题:
throws / throw
final / finally / finalize
Collection / Collections
String / StringBuffer / StringBuilder
ArrayList / LinkedList
HashMap / LinkedHashMap / Hashtable
…
sleep() / wait()
== / equals()
同步 / 异步
Object类的概述
1 Object类的说明
明确:java.lang.Object
任何一个Java类(除Object类)都直接或间接的继承于Object类
Object类称为java类的根父类
Object类中声明的结构(属性、方法等)就具有通用性。
Object类中没有声明属性
Object类提供了一个空参的构造器
重点关注:Object类中声明的方法
2 常用方法
重点方法:equals() \ toString()
了解方法:clone() \ finalize()
后面的方法:getClass() \ hashCode() \ notify() \ notifyAll() \ wait() \ wait(xx) \ wait(xx,yy)
3 重要方法
equals()的使用
1 适用性:
任何引用数据类型都可以使用。
2 java.lang.Object类中equals()的定义:
public boolean equals(Object obj) {
return (this == obj);
}
3 子类使用说明:
自定义的类在没有重写Object中equals()方法的情况下,调用的就是Object类中声明的equals(),比较两个
对象的引用地址是否相同。则必是false
对于像String、File、Date和包装类等,它们都重写了Object类中的equals()方法,用于比较两个对象的
实体内容是否相等。
4 开发中使用说明:
实际开发中,针对于自定义的类,常常会判断两个对象是否equals(),而此时主要是判断两个对象的属性值是否相等。
所以:要重写Object类的equals()方法。
手动自己实现
@Override public boolean equals(Object obj) { if (this == obj) { return true; } if(obj instanceof User){ User user = (User)obj; //方式1: // if(this.age == user.age && this.name.equals(user.name)){ // return true; // }else{ // return false; // } //方式2: return this.age == user.age && this.name.equals(user.name); } return false; }
调用IDEA自动实现(推荐)
ALT+Insert
5 高频面试题: 区分 == 和 equals()
==:运算符
①使用范围:基本数据类型、引用数据类型
- 基本数据类型:判断数据值是否相等
int i1 = 10;
int i2 = 10;
sout(i1 == i2);//true
char c1 = ‘A’;
int i3 = 65;
sout(c1 == i3);//true
float f1 = 12.0F;
int i4 = 12;
sout(f1 == i4);//true
- 引用数据类型变量:比较两个引用变量的地址值是否相等。(或比较两个引用是否指向同一个对象实体)
equals():方法
使用范围:只能使用在引用数据类型上。
具体使用:对于类来说,重写equals()和不重写equals()的区别。
toString()的使用
4.1 Object类中toString()的定义:
public String toString() {
return getClass().getName() + “@” + Integer.toHexString(hashCode());
}
4.2 开发中的使用场景
平时我们在调用System.out.println()打印对象引用变量时,其实就调用了对象的toString()
4.3 子类使用说明:
自定义的类,在没有重写Object类的toString()的情况下,默认返回的是当前对象的地址值。
像String、File、Date或包装类等Object的子类,它们都重写了Object类的toString(),在调用toString()时,
返回当前对象的实体内容。
4.4 开发中使用说明:
习惯上,开发中对于自定义的类在调用toString()时,也希望显示其对象的实体内容,而非地址值。这时候,就需要重写Object
类中的toString().
之三:多态性
1 理解:
理解为一个事物的多种形态。
生活举例:
女朋友:我想养一个宠物。
孩子:我想要一个玩具。
2 多态性的体现:
子类的对象赋给父类的引用
**比如:**Person p2 = new Man();
3 多态性的应用:
多态性的应用:虚拟方法调用
在多态的场景下,调用方法时。
编译时,认为方法是左边声明的父类的类型的方法(即被重写的方法)
执行时,实际执行的是子类重写父类的方法。
简称为:编译看左边,运行看右边。
4 前提:
① 要有类的继承关系 ② 要有方法的重写
5 多态的适用性:
适用于方法,不适用于属性。
6 多态的好处与弊端
-
弊端:
在多态的场景下,我们创建了子类的对象,也加载了子类特有的属性和方法。但是由于声明为父类的引用,
导致我们没有办法直接调用子类特有的属性和方法。 -
好处:
极大的减少了代码的冗余,不需要定义多个重载的方法。
举例:(一般用于不确定的,如下不知道卡是什么类型)
class Account{
public void withdraw(){} //取钱
}
class CheckAccount extends Account{ //信用卡
//存在方法的重写
public void withdraw(){} //取钱
}
class SavingAccount extends Account{ //储蓄卡
//存在方法的重写
}
class Customer{
Account account;
public void setAccount(Account account){
this.account = account;
}
public Account getAccount(){
return accout;
}
}
class CustomerTest{
main(){
Customer cust = new Customer();
cust.setAccount(new CheckAccount());
cust.getAccount().withdraw();
}
}
向下转型5
此时就可使用子类特有的属性
1 instanceof的使用
-
-
建议在向下转型之前,使用instanceof进行判断,避免出现类型转换异常**(保证代码的健壮性)**
Person p2 = new Woman(); Man m2 = (Man)p2; m2.earnMoney(); //会报错,因为p2不是Woman实例
if(p2 instanceof Man){//p2是否是Man的实例 Man m2 = (Man)p2; m2.earnMoney(); }
-
-
- 格式: a instanceOf A : 判断对象a是否是类A的实例。
-
- 如果a instanceOf A 返回true,则:
a instanceOf superA 返回也是true。(其中,A 是superA的子类。)
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.adopt(new Dog());
test.adopt(new Cat());
}
public void adopt(Animal animal){ //Animal animal = new Dog()
animal.eat();
animal.jump();
// animal.watchDoor();
}
// public void adopt(Dog dog){
// dog.eat();
// dog.jump();
//
// }
//
// public void adopt(Cat cat){
// cat.eat();
// cat.jump();
//
// }
}
class Animal{
public void eat(){
System.out.println("动物进食");
}
public void jump(){
System.out.println("动物跳");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗吃骨头");
}
public void jump(){
System.out.println("狗急跳墙");
}
public void watchDoor(){
System.out.println("狗能看家");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
public void jump(){
System.out.println("猫跳~~");
}
public void catchMouse(){
System.out.println("猫抓老鼠");
}
}
**9 类的成员之三:构造器 **6
1 什么是构造器
凡是类都有构造器 , ne w后面与类名相同的就是构造器
Person a = new Person();
2 构造器的作用
作用1:搭配new关键字,创建类的 对象
**作用2:**在创建对象的同时,给对象的相关属性赋值
3 构造器的使用说明
- 构造器声明的格式:
权限修饰符 类名(形参列表){} - 创建类以后,在没有显示提供任何构造器的情况下,系统会默认提供一个空参的构造器,且构造器的权限与类声明的权限相同。
- 一旦类中显示声明了构造器,则系统不再提供默认的空参的构造器。
- 一个类中可以声明多个构造器,彼此之间构成重载。
- 例
public class Student {
String name;
int age;
String school;
String major;//专业
public Student(String n,int a){
name = n;
age = a;
}
public Student(String n, int a, String s){
name = n;
age = a;
school = s;
}
Student(String n, int a, String s, String m){
name = n;
age = a;
school = s;
major = m;
}
----------------
public class StudentTest {
public static void main(String[] args) {
Student s1 = new Student("刘强东",48,"中国人民大学","社会学");
System.out.println(s1.getInfo());
Student s2 = new Student("奶茶妹妹",28,"清华大学");
System.out.println(s2.getInfo());
}
}
**10 ** 关键字
this关键字的使用
1 不用this时会出现的问题?
方法中如果形参名和属性名同名,需要区分两个变量
解决方案: 使用this修饰的变量,表示的是属性。没有用this修饰的,表示的是形参。
2 this可以调用的结构:
成员变量、方法、构造器
理解:当前对象(在方法中调用时) 或 当前正在创建的对象(在构造器中调用时)
1 this调用属性和方法
【针对于方法内的使用情况:】
特殊情况:如果方法的形参与对象的属性同名了,我们必须使用"this."进行区分。使用this.修饰的变量即为属性,
没有使用this.修饰的变量,即为局部变量。
【针对于构造器内的使用情况:】
一般情况:我们通过构造器创建对象时,可以在构造器内调用当前正在创建的对象的属性或方法。此时,我们可以在属性和方法前
使用"this.",表示当前属性或方法所属的对象。但是,一般情况下,我们都选择省略此"this."结构。
特殊情况:**如果构造器的形参与正在创建的对象的属性同名了,我们必须使用"this."进行区分。**使用this.修饰的变量即为属性(或成员变量),没有使用this.修饰的变量,即为局部变量。
例
class person(){
int age;
String name;
String emil;
//方法内
class setAge(int age){
this.age = age;//谁调方法this属性就是谁的
}
//构造器内
public Person(int age,String name,String emil){
this.age=age;
this.name=name;
this.emli=emil;
}
}
2 this调用构造器
格式:“this(形参列表)”
我们可以在类的构造器中,调用当前类中指定的其它构造器
要求:"this(形参列表)"必须声明在当前构造器的首行
结论:"this(形参列表)"在构造器中最多声明一个
如果一个类中声明了n个构造器,则最多有n-1个构造器可以声明有"this(形参列表)"的结构
例
public User(){
//模拟对象创建时,需要初始化50行代码。
}
public User(String name){
this(); //此时空参构造器就发挥了作用
this.name = name;
}
public User(String name,int age){
this(name); // 类似递归的调用
// this.name = name;
this.age = age;
}
super关键字的使用
1 为什么需要super?
- 子承父类后,对父类的方法进行了重写,那么在子类中,可以对父类中被重写的方法进行调用
- 子承父类后,发现子类和父类中定义了同名的属性,可以在子类中区分两个同名的属性
2 使用super关键字。
- 理解:父类的
- super可以调用的结构:属性、方法、构造器
1 调用属性、方法
子承父类以后,可以在子类的方法或构造器中,调用父类中声明的属性或方法。
格式:“super.(属性或方法)”
一般情况下,我们可以考虑省略"super."的结构。但是,如果出现子类重写了父类的方法或子父类中出现了同名的属性时,
则必须使用"super."的声明,显式的调用父类被重写的方法或父类中声明的同名的属性。
2 调用构造器
① 子类继承父类时,不会继承父类的构造器。只能通过“super(形参列表)”的方式调用父类指定的构造器。
② 规定:“super(形参列表)”,必须声明在构造器的首行。
③ 我们前面讲过,在构造器的首行可以使用"this(形参列表)“,调用本类中重载的构造器,
结合②,结论:在构造器的首行,“this(形参列表)” 和 “super(形参列表)“只能二选一。
④ 如果在子类构造器的首行既没有显示调用"this(形参列表)”,也没有显式调用"super(形参列表)”,
则子类此构造器默认调用"super()”,即调用父类中空参的构造器。
⑤ 由③和④得到结论:子类的任何一个构造器中,要么会调用本类中重载的构造器,要么会调用父类的构造器。
只能是这两种情况之一。
⑥ 由⑤得到:一个类中声明有n个构造器,最多有n-1个构造器中使用了"this(形参列表)“,
则剩下的那个一定使用"super(形参列表)”。
–> 我们在通过子类的构造器创建对象时,一定在调用子类构造器的过程中,直接或间接的调用到父类的构造器。
也正因为调用过父类的构造器,我们才会将父类中声明的属性或方法加载到内存中,供子类对象使用。
二、子类对象实例化全过程
代码举例:
class Creature{ //生物类
//声明属性、方法、构造器
}
class Animal extends Creature{ //动物类
}
class Dog extends Animal{ //狗类
}
class DogTest{
public static void main(String[] args){
Dog dog = new Dog();
dog.xxx();
dog.yyy = …;
}
}
- 从结果的角度来看:体现为类的继承性
当我们创建子类对象后,子类对象就获取了其父类中声明的所有的属性和方法,在权限允许的情况下,可以直接调用。
- 从过程的角度来看:
当我们通过子类的构造器创建对象时,子类的构造器一定会直接或间接的调用到其父类的构造器,而其父类的构造器
同样会直接或间接的调用到其父类的父类的构造器,…,直到调用了Object类中的构造器为止。
正因为我们调用过子类所有的父类的构造器,所以我们就会将父类中声明的属性、方法加载到内存中,供子类的对象使用。
问题:在创建子类对象的过程中,一定会调用父类中的构造器吗? yes!
- 问题:创建子类的对象时,内存中到底有几个对象?
就只有一个对象!即为当前new后面构造器对应的类的对象。
super this例子
public class Interview02{
public static void main(String[] args) {
Father f = new Father();
Son s = new Son();
System.out.println(f.getInfo());//atguigu
System.out.println(s.getInfo()); //atguigu
s.test();//atguigu atguigu
System.out.println("-----------------");
s.setInfo("大硅谷");
System.out.println(f.getInfo());//atguigu
System.out.println(s.getInfo());//大硅谷
s.test(); //大硅谷 大硅谷
}
}
class Father{
private String info = "atguigu";
public void setInfo(String info){
this.info = info;
}
public String getInfo(){
return info;
}
}
class Son extends Father{
private String info = "硅谷";
public void test(){
System.out.println(this.getInfo());
System.out.println(super.getInfo());
}
// public String getInfo(){
// return info;
// }
}
11 对象数组
1 何为对象数组
数组元素是引用类型中的类时,我们称为对象数组。
2 举例:
String[],Person[],Student[],Customer[]等
- 案例:
public class Student {
//属性
int number;//学号
int state;//年级
int score;//成绩
//声明一个方法,显示学生的属性信息
public String show(){
return "number : " + number + ",state : " +
state + ", score : " + score;
}
}
————————————————————————————
/**
* ClassName: StudentTest
* Description:
* 创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。
* 问题一:打印出3年级(state值为3)的学生信息。
* 问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
* @Author 硅谷
* @Create 14:36
* @Version 1.0
*/
public class StudentTest {
public static void main(String[] args) {
//创建Student[]
Student[] students = new Student[20]; //String[] strs = new String[20];
//使用循环,给数组的元素赋值
for (int i = 0; i < students.length; i++) {
students[i] = new Student();
//给每一个学生对象的number、state、score属性赋值
students[i].number = i + 1;
students[i].state = (int)(Math.random() * 6 + 1);
students[i].score = (int)(Math.random() * 101);
}
//需求1:打印出3年级(state值为3)的学生信息
for (int i = 0; i < students.length; i++) {
if(3 == students[i].state){
Student stu = students[i];
// System.out.println("number : " + stu.number + ",state : " +
// stu.state + ", score : " + stu.score);
System.out.println(stu.show());
}
}
//需求2:使用冒泡排序按学生成绩排序,并遍历所有学生信息
//排序前遍历
for (int i = 0; i < students.length; i++) {
System.out.println(students[i].show());
}
System.out.println("********************");
for (int i = 0; i < students.length - 1; i++) {
for (int j = 0; j < students.length - 1 - i; j++) {
if(students[j].score > students[j + 1].score){
//错误的,不满足实际需求!
// int temp = students[j].score;
// students[j].score = students[j + 1].score;
// students[j + 1].score = temp;
//正确的
Student temp = students[j];
students[j] = students[j + 1];
students[j + 1] = temp;
}
}
}
//排序后遍历
for (int i = 0; i < students.length; i++) {
System.out.println(students[i].show());
}
}
}
1)定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int)。
2)创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。
问题一:打印出3年级(state值为3)的学生信息。
问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
提示:
- 生成随机数:Math.random(),返回值类型double;
- 四舍五入取整:Math.round(double d),返回值类型long。
年级[1,6] : (int)(Math.random() * 6 + 1)
分数[0,100] : (int)(Math.random() * 101)
12 其他一些细微知识点
一、类中属性(当前仅考虑实例变量)赋值过程:
-
可以给属性赋值的位置
① 默认初始化;
② 显式初始化;
③ 构造器中初始化;
④ 通过"对象.方法"的方式赋值;
⑤ 通过"对象.属性"的方式赋值; -
执行的先后顺序?
① - ② - ③ - ④/⑤ -
可以执行的次数如何?
只能执行一次:①、②、③
可以多次执行:④、⑤
二、JavaBean的理解
JavaBean,是指符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
三、读懂UML类图
- UML(Unified Modeling Language,统一建模语言),用来描述
软件模型
和架构
的图形化语言。 - 常用的UML工具软件有
PowerDesinger
、Rose
和Enterprise Architect
。 - UML工具软件不仅可以绘制软件开发中所需的各种图表,还可以生成对应的源代码。
- 使用
UML类图
可以更加直观地描述类内部结构(类的属性和操作)以及类之间的关系(如关联、依赖、聚合等)。- +表示 public 类型, - 表示 private 类型,#表示protected类型
- 方法的写法:
方法的类型(+、-) 方法名(参数名: 参数类型):返回值类型 - 斜体表示抽象方法或类。
四、匿名对象(套娃)
customer.steAccount(new Account(number,money));
//此时 Account new的对象并没有名字但仍然在使用
但使用一次就无法再使用了 .
一般只用在不需要二次调用的对象 .
[外链图片转存中…(img-NfIdZvqI-1680571358891)]
四、匿名对象(套娃)
customer.steAccount(new Account(number,money));
//此时 Account new的对象并没有名字但仍然在使用
但使用一次就无法再使用了 .
一般只用在不需要二次调用的对象 .
内存的解析
- 对象在内存中的分配涉及到的内存结构
- 栈(stack): 方法内定义的变量,存储在栈中, 以栈针为基本单位 , 有入栈和出栈的操作 , 每个栈帧对应一个方法。
- 堆(heap) : new 出来的结构(比如:数组、对象)。
- 方法区(method area) : 存放类的模板。
- 类中对象的内存解析
2.1 创建类的一个对象
[外链图片转存中…(img-6QR4VQzd-1680571358891)]
2.2 创建类的多个对象
[外链图片转存中…(img-vRyMWPZY-1680571358891)]
[外链图片转存中…(img-LQkLzdZA-1680571358892)]
强调1:创建了Person类的两个对象
Person p1 = new Person();
Person p2 = new Person();
说明:创建类的多个对象时,每个对象在堆空间中有一个对象实体。每个对象实体中保存着一份类的属性。
如果修改某一个对象的某属性值时,不会影响其他对象此属性的值。
p1.age = 10;
p2.age = 20;
p1.age = 30;
System.out.println(p2.age);//20
强调2:声明类的两个变量
Person p1 = new Person();
Person p3 = p1;
说明:此时的p1,p3 两个变量指向了堆空间中的同一个对象实体。(或p1,p3保存的地址值相同)
如果通过其中某一个对象变量修改对象的属性时,会影响另一个对象变量此属性的值。
p1.age = 10;
p3.age = 20;
System.out.println(p1.age);//20
2.3 对象调用方法的过程