java基础-面向对象
黑马2022资料阶段一:java基础-第三章、第七章
3-1 面向对象基础上
07-两个对象内存图
需要注意的是,第二次创建对象时,类的字节码文件不需要再次加载到方法区。调用成员变量方法的时候,在堆内存中保存的是同一个成员方法的地址(方法区),只是堆内存地址(指向方法区的指针)不一样。且每次执行都会压栈,执行完会弹栈。
08-两个引用指向同一个对象内存图
定义学生类:
在main方法创建一个对象stu1,再用这个类的引用stu2指向stu1,改变stu2的name属性:
stu1的name属性会变化吗为什么?
会变化,打印出来是:阿珍...阿珍。
因为stu1和stu2都指向了堆内存的同一块地址。
3-2面向对象基础下
12-this关键字
当方法局部变量和类成员变量重名时,会采用就近原则,导致set方法失效:
想要将成员变量的值改为set方法传参的值,可以使用this关键字:
this的作用:调用本类的成员(变量和方法),解决局部变量和成员变量的重名问题。
this代表所在类的对象引用,方法被哪个对象调用,this就代表哪个对象。
13-this的内存原理
15~17-构造方法格式、调用时机和作用、注意事项
是什么:构建创造对象的时候所调用的方法。
格式:方法名与类名相同,大小写也一致。没有返回值类型,连void都没有。没有具体的返回值。
执行时机:创建对象的时候调用,每创建一次对象,就会执行一次构造方法。不能手动调用构造方法。
作用:给对象的属性做初始化,在new对象时传参数,在构造函数内执行初始化逻辑。
注意事项:如果没有定义构造方法,会默认给出一个默认构造方法。一旦定义了构造方法,系统不会提供默认构造方法(无参的),构造方法是可以重载的,如果自定义了有参构造,还要使用无参构造的话需要自己再定义一个无参构造。
7-1面向对象进阶-分类和static
01-分类思想概述
分工协作、专人干专事。
02-分类思想之黑马信息管理系统
- Student类 标准学生类,封装键盘录入的学生信息(id , name , age , birthday)
- StudentDao类 Dao : (Data Access Object 缩写) 用于访问存储数据的数组或集合
- StudentService类 用来进行业务逻辑的处理(例如: 判断录入的id是否存在)
- StudentController类 和用户打交道(接收用户需求,采集用户信息,打印数据到控制台)
03-分包思想
如果将所有的类文件都放在同一个包下,不利于管理和后期维护,所以,对于不同功能的类文件,可以放在不同的包下进行管理
包
本质上就是文件夹
创建包
多级包之间使用 " . " 进行分割 多级包的定义规范:公司的网站地址翻转(去掉www) 比如:我的网站址为www.iocaop.com 后期我们所定义的包的结构就是:com.iocaop.其他的包名
包的命名规则
字母都是小写
04-包的注意事项、类之间的访问
包的注意事项:
- package语句必须是程序的第一条可执行的代码
- package语句在一个java文件中只能有一个
- 如果没有package,默认表示无包名 (如果类在某个包下,必须有package,否则报错,反之亦然,不在包下,不能有package)
类之间的访问:
同一个包下的访问
不需要导包,直接使用即可
不同包下的访问
1.import 导包后访问
2.通过全类名(包名 + 类名)访问
注意:import 、package 、class 三个关键字的摆放位置存在顺序关系
package 必须是程序的第一条可执行的代码
import 需要写在 package 下面
class 需要在 import 下面
05-07学生信息管理系统
需求说明:
添加学生: 键盘录入学生信息(id,name,age,birthday)
使用数组存储学生信息,要求学生的id不能重复
删除学生: 键盘录入要删除学生的id值,将该学生从数组中移除,如果录入的id在数组中不存在,需要重新录入
修改学生: 键盘录入要修改学生的id值和修改后的学生信息
将数组中该学生的信息修改,如果录入的id在数组中不存在,需要重新录入
查询学生: 将数组中存储的所有学生的信息输出到控制台
分包:这里只是简单的项目,不是Spring三层架构项目,主要目的是学习分类、分包思想。
包 | 存储的类 | 作用 |
---|---|---|
com.lzc.edu.info.manager.domain | Student.java | 封装学生信息 |
com.lzc.edu.info.manager.dao | StudentDao.java | 访问存储数据的数组,进行赠删改查(库管) |
com.lzc.edu.info.manager.service | StudentService.java | 业务的逻辑处理(业务员) |
com.lzc.edu.info.manager.controller | StudentController.java | 和用户打交道(客服接待) |
com.lzc.edu.info.manager.entry | InfoManagerEntry.java | 程序的入口类,提供一个main方法 |
启动菜单的打印,循环可以用标号跳出:
11-12问题分析、static的引出
两次添加学生, 调用了两次addStudent()方法,在方法中new了StudentService,也就是有两个studentService对象,这两个studentService中有各自的成员变量studentDao(有两个),自然就有了两个Student数组,所以无法判断学号是否重复。这里可以使用static
关键字解决这个问题。
这里只是简单的类,在Spring中,Bean默认是单例模式,而且也不会这样使用成员变量,而是自动装配的,都是同一个。
12-static关键字的特点
被类的所有对象共享
是我们判断是否使用静态关键字的条件
随着类的加载而加载,优先于对象存在
对象需要类被加载后,才能创建
可以通过类名调用
也可以通过对象名调用(编译器优化,最终class代码会替换成类名调用)
13-static注意事项
静态方法只能访问静态的成员
静态方法为什么不能非静态成员?没有this指针,不知道是哪个对象的静态成员。
非静态方法可以访问静态的成员,也可以访问非静态的成员
静态方法中是没有this关键字 (类比对象先加载,静态方法是在方法区,实例化对象在堆区,方法区的数据先赋值,而对象还没创建,不知道this应该指向堆区哪个位置。即方法区this(假设有this)->堆区(???未知),就算满足顺序也不合理,因为静态方法是所有对象共享的,this不知道应该指向哪个对象)
说到底还是加载顺序的问题。
7-3~7-4继承
01-继承入门
根据多个类中共性的内容,向上抽取出来。让类之间产生父子关系,子类可以直接使用父类的非私有成员(方法和变量)。
02-继承的好处和坏处
好处:
- 提高代码复用性(抽取共性写在父类,子类继承父类,子类编写特性即可,共性只需要编写一次)
- 提高了代码的维护性(当需要对共性进行修改时,只需要修改父类即可,不需要去子类多次修改)。
- 让类之间产生了关系,是多态的前提。
坏处:
- 继承是侵入性的
- 降低了代码的灵活性(继承导致子类必须有父类的非私有方法和属性,约束了子类。)
- 增强了代码的耦合性(代码和代码之间存在关联,耦合,耦合性太强,导致关系紧密,维护麻烦。)
什么时候使用继承?
类与类之间存在共性内容,并且产生了is a的关系,就可以考虑使用继承。
03-继承的特点
- Java只支持单继承(子类只能继承一个父类),不支持多继承(子类不能同时继承多个父类),但支持多层继承(A继承B,B继承C)。
java为什么不支持多继承?
04-继承中成员变量的访问特点
子类和父类中出现重名的成员变量,会使用子类的成员变量,如果子类的成员方法还有重名,就会使用方法中定义的变量,就近原则,可以使用this和super进行调用子类或父类的成员变量。
05-this和super访问成员的格式
06-继承中成员方法的访问特点
通过子类对象访问方法,会先到子类成员范围中找,如果没有再到父类成员方法范围中找。也是可以用this和super进行区分调用。
07-方法重写概述和应用场景
重写:在继承体系中,子类出现了和父类一模一样(参数类型、个数,返回值,方法名)的方法声明。
使用场景:子类需要父类的功能,而功能主体子类有自己特有的内容,可以重写父类方法,保留父类功能,又定义了子类特有的内容(可以在子类重写的方法中使用super调用父类的方法,再接着写新增的内容)。
重载:在同一个类中,方法名相同,参数列表不同,与返回值无关。
08-方法重写注意事项
- 父类中私有方法不能被重写
- 父类的静态方法,子类必须通过静态方法"重写"(现象看起来是重写),非静态方法也必须通过非静态方法重写。
注意:静态方法不能被重写,如果子类也存在和父类方法声明一模一样的方法,可以理解为子类将父类中同名的方法隐藏了起来,并非重写。
- 子类重写父类方法时,访问权限必须大于父类
09-权限修饰符
用来修饰成员变量、成员方法、构造方法。
权限修饰符:(无关类:没有继承关系的类)
10-继承中构造方法的访问特点
每次创建子类对象,都会执行父类的构造方法,并且是优先于子类的构造方法。
为什么?子类初始化之前,一定要完成父类的初始化,假设父类A有成员变量a=10,子类B没有该变量,在子类要使用a,肯定要父类先初始化完成才能用。数据的初始化是在构造方法执行时进行的。
系统在每一个构造方法中,都默认隐藏了一句代码super(),调用父类的无参构造方法。就算不写,也会自动带上。如果没有手动继承哪个类,也会有这个spuer(),因为java中所有类都继承了Object类。
11-继承中构造方法的访问特点-父类中没有无参构造方法
如果一个类被public修饰,那类名必须与文件名保持一致。所以一个java文件中只能有一个public修饰的类。
如果父类没有无参构造,在new子类对象时,会出现错误,那么我们需要在子类构造中手动加上super(有参),手动调用父类的有参构造。
以上代码中,子类无参构造,系统不会自动帮我们加上spuer(),因为this(...)和super(...)都必须在构造方法的第一行,会冲突。
12-继承中成员变量的内存图解
父类成员变量name和age是私有的,为什么会在子类对象中?所谓的私有指的是在子类没法直接使用,但是会被继承过来,可以通过get(),set()方法使用。
14-抽象类
抽象方法:将共性行为(方法)抽取到父类后,发现该方法的实现逻辑无法在父类中给出具体明确,非法·该方法就可以定义为抽象方法
抽象类:如果一个类中存在抽象方法,该类就必须声明为抽象类。
抽象方法怎么写?
只有定义,没有实现,加上abstract关键字,然后在类上加上abstract关键字。
子类继承抽象类后,必须重写抽象方法。
//抽象类的定义
public abstract class 类名 {}
//抽象方法的定义
public abstract void eat();
15-抽象类的注意事项
- 抽象类不能创建对象,即不能实例化。为什么?因为抽象类没有重写抽象方法,实例化后通过对象可以调用抽象方法,没有实现的方法调用是没有意义的。
- 抽象类中有构造方法(为了让子类调用),抽象类的子类构造方法肯定会有
spuer()
,因为系统会默认加上,说明抽象类有构造方法。 - 抽象类的子类,必须重写父类中的所有抽象方法,或者该子类也是一个抽象类。
- 抽象类中可以没有抽象方法,但有抽象方法的类一定是抽象类。
16-模板设计模式
什么是设计模式?
一套反复被使用,多数人知晓、经过分类编目的、代码设计经验的总结。作用是代码重用、便于他人理解、保证代码可靠性、程序重用性。
一句话:一套良好的编码风格。
模板设计模式:把抽象类整体看成一个模板,模板中不能决定的东西定义为抽象方法,让使用模板的类(抽象类的子类)去重写抽象方法实现需求。
17-final关键字
16中的write方法,是模板方法,不允许被更改的。假设子类去重写了这个方法,就会打乱我们原有的逻辑。怎么办?使用final
关键字。
final关键字可以修饰方法、变量、类
特点:
修饰方法:表示是最终方法,不能被重写。
修饰变量:表示该变量是常量(所有字母大写,下划线分割),不能被再次赋值。修饰基本数据类型,变量只能被赋值一次。修饰引用类型变量,不允许修改变量地址值,可以修改变量中的属性值。即不能修改指针指向,但指针指向的堆内存中的内容可以被修改。
修饰类:表示最终类,不能被继承。该类所有方法都是核心方法,不希望任何一个方法被重写,直接修饰类,即没有子类就不会被重写了。
修饰成员变量时,需要注意成员变量的初始化时机。要么在创建时赋值,要么在构造方法结束之前完成赋值。
19-代码块
使用{}
括起来的代码被称为代码块
分类:
局部代码块
位置:方法内。局部代码块定义的变量执行完后,局部代码块中的变量的内存会被释放,可以限定局部变量的生命周期,及早释放,提高内存利用率。
构造代码块
位置:类中,方法外。构造代码块是每次创建对象都会执行的,且是在构造方法之前执行。作用是构造方法代码复用,在多个构造方法中的重复代码,可以提取到构造代码块中。
静态代码块
位置:类中,方法外,且使用
static
修饰。被static修饰的变量会随着类的加载而加载。代码块也是一样的,当类被加载到内存中,静态代码块也会随着执行,且由于类只会加载一次,静态代码块也只会执行一次。作用是在类加载的时候做一些数据初始化的操作。
7-5~7-6 接口、多态、内部类
03-接口的介绍
当一个类中所有方法都是抽象方法,说明这个类是用来指定规则的。制定规则时,一般是用接口来做的。
接口也是一种引用数据类型,比抽象类还要抽象(接口是特殊的抽象类)。
接口存在的意义:
- 规则的定义
- 程序的扩展性
04-接口的定义和特点
interface定义
接口不能实例化
接口和类之间是实现关系,implements关键字表示
接口的实现类要么重写接口中的所有方法,要么就是抽象类。
一个类可以实现多个接口,但不能继承多个父类。
为什么不能多继承而可以多实现?因为多继承情况下如果父类存在方法名重复,无法确定使用哪一个。但接口多实现,就算实现的接口中方法名重复也没关系,因为抽象方法本来就没有具体实现,实现是在实现类编写的。
菱形继承问题:点击跳转
05-接口中成员的特点
变量:
接口中的变量只能是常量,就算不加final,系统也会自动加上。并且是静态的、公共的。
接口中的成员变量系统默认会加上
public static final
。为什么?点击跳转
方法:
- 接口中没有构造方法,实现类的构造方法
super()
访问的是Object
中的构造方法。 - 成员方法系统默认会加上
public abstract
关键字。方法只能是抽象方法(jdk8之前)
06~07 JDK8接口中成员的特点
默认方法
jd8版本以后只对成员方法做了改进。
假设有个场景:
在版本1.0时接口中有2个方法,并且该接口有多个实现类。假设要在版本2.0时在接口中增加10个方法,那么原来该接口的所有实现类都需要重写这10个新增的方法。很麻烦。
jdk8后允许在接口中定义非抽象法方法,但是需要加上关键字default
,表示默认方法。
作用:解决接口升级问题。
格式:
public default void test(){}
实现类实现接口中的默认方法时不需要再加
default
关键字。接口中的默认方法都是public 不写也会自动加上
接口可以多实现,如果实现的多个接口中有方法重名,且都是默认方法,子类必须重写该方法,否则报错(不重写会有二义性)。
静态方法
jdk8以后接口中支持静态static
方法。默认public 不写会默认加上。
定义格式:
public static void test(){}
需要注意的是:接口中的静态方法只能通过接口名调用,不能通过实现类类名或者实现类对象名调用。
因为只能通过接口名调用接口中的静态方法,所以不会有接口多实现出现静态方法重名的问题。
08-jdk9版本中接口成员的特点
在jdk8的基础上支持私有成员方法,提供给接口中的默认方法使用。
当接口中默认方法中有共用逻辑需要抽取,就可以使用私有成员方法进行封装。
如下:
对于静态方法,也是可以私有的:
09-类和接口的关系
类和类的关系:继承关系,只能单继承,但是可以多层继承
类和接口的关系:实现关系,可以单实现也可以多实现,还可以在继承一个类的情况下实现多个接口。
如果直接父类和接口中出现了相同的方法声明,但是逻辑不一样,优先使用父类的方法逻辑。
接口和接口的关系:基础关系,可以单继承也可以多继承。多继承时,如果父类接口重名但逻辑不一样,需要在子类接口重写该方法,且定义为默认方法。
12-多态的前提条件
同一个对象在不同时刻变现出来的不同形态。
多态的前提和体现
- 有继承或实现关系
- 有方法重写
- 有父类引用指向子类对象
13-多态中成员访问的特点
- 构造方法:同继承一样,子类会通过
spuer()
访问父类构造方法 - 成员变量:编译看左边,执行看左边。
- 成员方法:编译看左边,执行看右边。
成员变量:使用父类的,如果父类没有,那么在编译时就会报错。
成员方法,使用子类的,如果父类没有,也会在编译时错误。
为什么成员变量和成员方法的访问不一样?
因为成员方法有重写的概念,而成员变量没有。
14-多态的好处和弊端
好处:提高方法扩展性
- 父类是Animal,两个子类Dog和Cat,重写eat方法。静态方法调用:
当函数执行时,压栈,就会有父类引用指向子类对象。满足多态的三个条件。
这样,这个静态方法可以接收任意Animal子类对象。方法的扩展性提高了。
弊端
当父类引用调用子类特有的方法时,编译会报错。
15-多态中的转型
向上转型
从子到父
父类引用指向子类对象
要解决多态父类引用调用子类特有方法,可以直接创建子类对象,或者向下转型,一般多态只会调用共用方法,不会调用特有方法。
向下转型
从父到子
父类引用转为子类对象
16-多态中转型存在的风险和解决方案
如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换时会出现ClassCastException
如何解决:
使用instanceof关键字,在强转之前进行判断。
18-内部类-成员内部类
在类中定义一个类,在A类内部定义一个B类,B类就被称为内部类。
内部类可以直接使用外部类的成员变量(包括私有)。
内部类的访问特点:
内部类可以直接访问外部类的成员,包括私有。
外部类要访问内部类的成员,必须创建对象。
成员内部类外键如何创建对象使用?
注意格式:
外部类名.内部类名 对象名 = new 外部类对象().new 内部类对象()
19-私有成员内部类-静态成员内部类
成员内部类也属于成员,可以被修饰符所修饰,如private
、static
私有成员内部类:
被private
修饰后,直接访问会报错:
所以只能在类中通过成员方法来访问:
静态成员内部类
对象的创建格式发生了变化:
外部类名.内部类名 对象名 = new 外部类名.内部类名()
静态成员内部类中的静态方法可以直接通过类名进行调用:
20-局部内部类
在方法中定义的类,外界无法直接使用,需要在方法内部创建并使用,该类可以直接访问外部类的成员,也可以访问方法内的局部变量。
21-匿名内部类
是一种特殊的局部内部类(在方法的内部)。
正常来说,使用接口中的某个方法, 需要完成以下四步:
- 创建实现类,通过implements关键字去实现接口
- 重写方法
- 创建实现类对象
- 实现类对象调用重写后的方法
如果使用匿名内部类,就可以简化成一步。
匿名内部类的理解:将继承或实现、方法重写、创建对象三步放在了一步进行。
现在有Inter接口:
interface Inter{
void show();
}
如下代码就是创建了匿名内部类作为实现类,并创建了该实现类对象:
new Inter(){}
这时候编译器会提示需要重写方法:
new Inter(){
@Override
public void show(){
// 重写的内容
}
};
上面创建了一个匿名内部类对象,该内部类实现了接口中的方法。
既然是一个对象,就可以直接调用:
new Inter(){
@Override
public void show(){
// 重写的内容
}
}.show();
如果匿名内部类中重写了多个方法,可以使用接口的引用进行接收(父类引用/接口类型引用),再按需调用。
22-匿名内部类的使用场景
23-Lambda初体验和函数式编程思想
函数式编程思想和面向对象思想的区别:
面向对象思想,更多关注应该怎么用,怎么满足传参要求,而函数式编程更多关注的是重写函数后,里面的内容。
数学中,函数就是有输入量、输出量的一套计算方案,也就是
拿数据做操作
面向对象思想强调的是必须通过对象的形式来做事情
函数式思想则尽量忽略面向对象的复杂语法,强调做什么,而不是以什么形式去做
24-Lambda表达式的格式说明和前提条件
lambda表达式的使用前提
- 有一个接口
- 接口中有且仅有一个抽象方法
25- lambda练习-带参无返回值
定义个接口,有且仅有一个方法,再定义一个静态方法,参数是接口类型, 分别使用匿名内部类和lambda进行实现:
public class Test1 {
public static void main(String[] args) {
// 匿名内部类
invokeSayWords(new Inter() {
@Override
public void sayWords(String msg) {
System.out.println("匿名内部类:"+msg);
}
});
// 函数式编程lambda
invokeSayWords((msg)->{
System.out.println("lambda:"+msg);
});
}
public static void invokeSayWords(Inter inter){
inter.sayWords("哈哈");
}
}
interface Inter{
/**
* 说话
*
* @param msg 消息
*/
void sayWords(String msg);
}
26- lambda练习-无参带返回值
编写一个接口,无参,带int返回值,产生随机数,分别使用匿名内部类和函数式编程进行实现:
public class Test2 {
public static void main(String[] args) {
// 匿名内部类
invokeRandomNumHandler(new RandomNumHandler() {
@Override
public int getNumber() {
Random random = new Random();
int i = random.nextInt(10)+1;
return i;
// 注意如果接口中的方法需要返回值,匿名内部类的方法体也需要有返回值,否则报错
}
});
// lambda表达式
invokeRandomNumHandler(()->{
Random random = new Random();
int i = random.nextInt(10)+1;
return i;
// 注意如果接口中的方法需要返回值,lambda的方法体也需要有返回值,否则报错
});
}
public static void invokeRandomNumHandler(RandomNumHandler randomNumHandler){
int number = randomNumHandler.getNumber();
System.out.println(number);
}
}
interface RandomNumHandler{
/**
* 获取随机数
*
* @return int
*/
int getNumber();
}
27- lambda练习-带参带返回值
定义一个接口,有且仅有一个方法,用于计算两个int相加,分别使用匿名内部类和lambda实现:
public class Test3 {
public static void main(String[] args) {
// 匿名内部类
invokeCalc(new Calc() {
@Override
public int calc(int a, int b) {
return a+b;
}
});
// lambda
invokeCalc((a,b)->{
return a+b;
});
}
public static void invokeCalc(Calc calc){
int res = calc.calc(10, 20);
System.out.println(res);
}
}
interface Calc{
int calc(int a,int b);
}
28-lambda的省略模式
省略规则:
- 参数类型可以省略,但是有多个参数的情况下,不能只省略一个
- 如果参数有且仅有一个,小括号可以省略
- 如果代码块只有一条语句,可以省略大括号和分号,甚至return
29-lambda表达式和匿名内部类的区别
所属类型不同
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
- Lambda表达式:只能是接口
使用限制不同:
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中多于一个抽象方法,只能使用匿名内部类,不能使用Lambda表达式
实现原理不同:
- 匿名内部类:编译之后不能使用Lambda表达式一个单独的
class
字节码文件 - Lambda表达式:编译以后没有单独的
class
字节码文件,对应的字节码会在运行的时候动态生成
Lambda不会单独产生字节码文件:
匿名内部类会产生单独的字节码文件: