- 含义:类是一种对象的描述
- 定义:类的组成是由成员变量和成员方法两部分组成
类的组成是由属性和行为两部分组成
- 属性:在类中通过成员变量来体现(类中方法外的变量)
- 行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)
10.2方法
笔记小结:
- 含义:方法(method)是程序中最小的执行单元
- 方法的定义和调用
- 无参数方法
public static void 方法名 ( ) { // 方法体; } // 注意,方法必须先定义,后调用,否则程序将报错
- 带参数方法
public static void 方法名 (参数1) { 方法体; } // 注意,方法调用时,参数的数量与类型必须与方法定义中的设置相匹配,否则程序将报错
- 带返回值方法
public static 数据类型 方法名 ( 参数 ) { return 数据 ; } // 注意,方法定义时return后面的返回值与方法定义上的数据类型要匹配
- 方法的注意事项
- 方法不能嵌套定义
- 定义方法时,需要明确方法返回值类型、明确方法参数个数
- 方法重载
- 含义:一个类中多个方法,它们具有相同的方法名但参数列表不同(个数不同、类型不同或顺序不同)
- 格式:
public class MethodDemo { public static void fn(int a) { //方法体 } public static int fn(double a) { //方法体 } }
- 构造方法
- 含义:它是一种特殊的方法,用于创建并初始化对象
- 格式:
class Student { private String name; private int age; //构造方法 public Student() { System.out.println("无参构造方法"); } }
- 标准类
① 类名需要见名知意
② 成员变量使用private修饰
③ 提供至少两个构造方法
④ get和set方法
⑤ 如果还有其他行为,也需要写上
10.2.1概述
方法(method)是程序中最小的执行单元
注意:
- 方法必须先创建才可以使用,该过程成为方法定义
- 方法创建后并不是直接可以运行的,需要手动使用后,才执行,该过程成为方法调用
10.2.3定义和调用
10.2.3.1无参数方法定义和调用
基本用例
/\*格式:
public static void 方法名 ( ) {
// 方法体;
} \*/
public static void method ( ) {
// 方法体;
}
// 调用
/\*格式:
方法名(); \*/
method();
注意:
方法必须先定义,后调用,否则程序将报错
10.2.3.2带参数方法定义和调用
基本用例
/\* 格式:
public static void 方法名 (参数1) {
方法体;
}
public static void 方法名 (参数1, 参数2, 参数3...) {
方法体;
} \*/
public static void isEvenNumber(int number){
...
}
public static void getMax(int num1, int num2){
...
}
// 调用
/\* 格式:
方法名(参数);
方法名(参数1,参数2); \*/
isEvenNumber(10);
getMax(10,20);
说明:
参数是由数据类型和变量名组成 - 数据类型 变量名 例如:int a
注意:
- 方法定义时,参数中的数据类型与变量名都不能缺少,缺少任意一个程序将报错
- 方法定义时,多个参数之间使用逗号( ,)分隔
- 方法调用时,参数的数量与类型必须与方法定义中的设置相匹配,否则程序将报错
形参和实参
- 形参:方法定义中的参数
等同于变量定义格式,例如:int number
- 实参:方法调用中的参数
等同于使用变量或常量,例如: 10 number
10.2.3.3带返回值方法的定义和调用
基本用例
/\*格式:public static 数据类型 方法名 ( 参数 ) {
return 数据 ;
} \*/
public static boolean isEvenNumber( int number ) {
return true ;
}
public static int getMax( int a, int b ) {
return 100 ;
}
// 调用
/\* 方法名 ( 参数 ) ;
数据类型 变量名 = 方法名 ( 参数 ) ; \*/
isEvenNumber ( 5 ) ;
boolean flag = isEvenNumber ( 5 );
注意:
- 方法定义时return后面的返回值与方法定义上的数据类型要匹配,否则程序将报错
- 方法的返回值通常会使用变量接收,否则该返回值将无意义
10.2.4方法的注意事项
10.2.4.1方法不能嵌套定义
- 示例代码:
public class MethodDemo {
public static void main(String[] args) {
}
public static void methodOne() {
public static void methodTwo() {
// 这里会引发编译错误!!!
}
}
}
10.2.4.2void表示无返回值
可以省略return,也可以单独的书写return,后面不加数据
- 示例代码:
public class MethodDemo {
public static void main(String[] args) {
}
public static void methodTwo() {
//return 100; 编译错误,因为没有具体返回值类型
return;
//System.out.println(100); return语句后面不能跟数据或代码
}
}
10.2.4.3方法的通用格式
格式:
public static 返回值类型 方法名(参数) {
方法体;
return 数据 ;
}
说明:
public static 修饰符,目前先记住这个格式
返回值类型
- 方法操作完毕之后返回的数据的数据类型
- 如果方法操作完毕,没有数据返回,这里写void,而且方法体中一般不写return
方法名 调用方法时候使用的标识
参数 由数据类型和变量名组成,多个参数之间用逗号隔开
方法体 完成功能的代码块
return 如果方法操作完毕,有数据返回,用于把数据返回给调用者
-
定义方法时,要做到两个明确
- 明确返回值类型:主要是明确方法操作完毕之后是否有数据返回,如果没有,写void;如果有,写对应的数据类型
- 明确参数:主要是明确参数的类型和数量
-
调用方法时的注意:
- void类型的方法,直接调用即可
- 非void类型的方法,推荐用变量接收调用
10.2.5方法重载
10.2.5.1概述
方法重载指同一个类中定义的多个方法之间的关系
满足下列条件的多个方法相互构成重载
- 多个方法在同一个类中
- 多个方法具有相同的方法名
- 多个方法的参数不相同,类型不同或者数量不同
注意:
- 重载仅对应方法的定义,与方法的调用无关,调用方式参照标准格式
- 重载仅针对同一个类中方法的名称与参数进行识别,与返回值无关,换句话说不能通过返回值来判定两个方法是否相互构成重载
10.2.5.2基本用例
- 正确范例:
public class MethodDemo {
public static void fn(int a) {
//方法体
}
public static int fn(double a) {
//方法体
}
}
public class MethodDemo {
public static float fn(int a) {
//方法体
}
public static int fn(int a , int b) {
//方法体
}
}
- 错误范例:
public class MethodDemo {
public static void fn(int a) {
//方法体
}
public static int fn(int a) { /\*错误原因:重载与返回值无关\*/
//方法体
}
}
public class MethodDemo01 {
public static void fn(int a) {
//方法体
}
}
public class MethodDemo02 {
public static int fn(double a) { /\*错误原因:这是两个类的两个fn方法\*/
//方法体
}
}
10.2.6构造方法
10.2.6.1概述
构造方法是一种特殊的方法
- 作用:创建对象 Student stu = new Student();
- 功能:主要是完成对象数据的初始化
10.2.6.2基本用例
/\* 格式:
public class 类名{
修饰符 类名( 参数 ) {
}
} \*/
class Student {
private String name;
private int age;
//构造方法
public Student() {
System.out.println("无参构造方法");
}
public void show() {
System.out.println(name + "," + age);
}
}
/\*
测试类
\*/
public class StudentDemo {
public static void main(String[] args) {
//创建对象
Student s = new Student();
s.show();
}
}
10.2.6.3注意事项
- 构造方法的创建
如果没有定义构造方法,系统将给出一个默认的无参数构造方法
如果定义了构造方法,系统将不再提供默认的构造方法
- 构造方法的重载
如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法
- 推荐的使用方式
无论是否使用,都手工书写无参数构造方法
- 重要功能
可以使用带参构造,为成员变量进行初始化
- 示例代码
/\*
学生类
\*/
class Student {
private String name;
private int age;
public Student() {}
public Student(String name) {
this.name = name;
}
public Student(int age) {
this.age = age;
}
public Student(String name,int age) {
this.name = name;
this.age = age;
}
public void show() {
System.out.println(name + "," + age);
}
}
/\*
测试类
\*/
public class StudentDemo {
public static void main(String[] args) {
//创建对象
Student s1 = new Student();
s1.show();
//public Student(String name)
Student s2 = new Student("林青霞");
s2.show();
//public Student(int age)
Student s3 = new Student(30);
s3.show();
//public Student(String name,int age)
Student s4 = new Student("林青霞",30);
s4.show();
}
}
10.2.6.4标准类制作
① 类名需要见名知意
② 成员变量使用private修饰
③ 提供至少两个构造方法
- 无参构造方法
- 带全部参数的构造方法
④ get和set方法
提供每一个成员变量对应的setXxx()/getXxx()
⑤ 如果还有其他行为,也需要写上
10.3对象
笔记小结:
- 含义:对象是指一个具体的实例化的实体,就是一种客观存在的事物
- 使用:
格式:类名 对象名 = new 类名(); // 例如 Phone p = new Phone();
- 内存图:
- 单个对象内存图
- new出来的对象会存在堆内存中
- 执行的方法会存在栈内存中
- 多个对象内存图
- 多个对象在堆内存中,都有不同的内存划分。其中,成员变量存储在各自的内存区域中
- 成员方法中,多个对象共用的一份栈内存
10.3.1概述
对象就是一种客观存在的事物,客观存在的事物皆为对象 ,所以我们也常常说万物皆对象。
- 类
- 类的理解
- 类是对现实生活中一类具有共同属性和行为的事物的抽象
- 类是对象的数据类型,类是具有相同属性和行为的一组对象的集合
- 简单理解:类就是对现实事物的一种描述
- 类的组成
- 属性:指事物的特征,例如:手机事物(品牌,价格,尺寸)
- 行为:指事物能执行的操作,例如:手机事物(打电话,发短信)
- 类的理解
- 类和对象的关系
- 类:类是对现实生活中一类具有共同属性和行为的事物的抽象
- 对象:是能够看得到摸的着的真实存在的实体
- 简单理解:类是对事物的一种描述,对象则为具体存在的事物
10.3.2基本用例
/\*格式:
创建对象
类名 对象名 = new 类名();\*/
/\*格式:
使用成员变量
格式:对象名.变量名
使用成员方法
格式:对象名.方法名()
\*/
public class PhoneDemo {
public static void main(String[] args) {
//创建对象
Phone p = new Phone();
//使用成员变量
System.out.println(p.brand);
System.out.println(p.price);
p.brand = "小米";
p.price = 2999;
System.out.println(p.brand);
System.out.println(p.price);
//使用成员方法
p.call();
p.sendMessage();
}
}
10.3.3对象内存图(重点)
10.3.3.1单个对象内存图
- 成员变量使用过程
- 成员方法调用过程
10.3.3.2多个对象内存图
- 成员变量使用过程
- 成员方法调用过程
- 总结:
多个对象在堆内存中,都有不同的内存划分,成员变量存储在各自的内存区域中,成员方法多个对象共用的一份堆内存空间
10.4封装
笔记小结:
- 含义:指隐藏对象的内部实现细节,并将其暴露出来可以使用的公共接口
- 作用:
- 隐藏实现细节
- 简化编程
- 提高代码复用性
- 接口隔离
10.4.1概述
在 Java 中,封装是面向对象编程中的一种重要的概念,它指的是将类的属性和方法保护起来,以避免外部程序直接访问和修改它们,从而提高了类的安全性和可维护性。封装的实现可以通过访问控制修饰符(public、private、protected)来实现。
10.4.2基本用例
public class Person {
private String name;
private int age;
private double height;
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 double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
}
说明:
将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问
10.5继承
笔记小结:
- 概述:
含义:就是子类继承父类的属性和行为,使得子类对象可以直接具有与父类相同的属性、相同的行为
优点:
- 代码重用
- 可以实现多态性
- 代码可维护性高
- 代码可扩展性强
- 代码复用性增强
格式;
class 父类 { ... } class 子类 extends 父类 { ... } // 子类 extends 父类
- 注意事项:子类不能继承的内容
- 构造方法:非私有(不能),私有(不能)
- 成员变量:非私有(能),私有(能)
- 成员方法:虚方法表(能),否则(不能)
- 注意:子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量。
- 继承后的特点:
成员变量:
- 成员变量不重名:不影响
- 成员变量重名:子类会优先访问自己对象中的成员变量
- 要想在子类中访问父类成员变量可通过super关键字
// 例如 super.school
成员方法:
- 成员方法不重名:不影响
- 成员方法重名:子类会优先访问自己对象中的成员方法
- 方法重写:
- 含义:声明不变,重新实现
- @Override:注解,重写注解校验
- 注意:
- 必须是子类与父类的关系
- 权限大于等于父类权限
- 返回值类型、函数名和参数列表需要一样
构造方法:
- 特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法
- 子类构造方法的第一行都隐含了一个**super()**去调用父类无参数构造方法,**super()**可以省略不写
public Student() { //super(); // 调用父类无参,默认就存在,可以不写,必须再第一行 System.out.println("子类无参"); }
- 特点:
- Java只支持单继承,不支持多继承
- 一个类可以有多个子类
- 继承之间可以形成多层继承
10.5.1概述
10.5.1.1定义
继承就是子类继承父类的属性和行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。从而提高代码的复用性(减少代码冗余,相同代码重复利用)。使类与类之间产生了关系。
10.5.1.2子类不能继承的内容
说明:
- 子类中不能继承构造方法,不能继承非虚方法表里的方法
- 虚方法表就算非 static、final、private修饰的方法
说明:
Java的继承,并不是一层一层往上寻找父方法,而是先判断虚方法表中有无此方法,方便直接调用
注意
值得注意的是子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量
10.5.2基本用例
说明:
类继承用法,基础使用
通过 extends
关键字,可以声明一个子类继承另外一个父类,定义格式如下:
class 父类 {
...
}
class 子类 extends 父类 {
...
}
注意:
Java是单继承的,一个类只能继承一个直接父类
10.5.4继承后的特点—成员变量
10.5.4.1成员变量不重名
如果子类父类中出现不重名的成员变量,这时的访问是没有影响的。代码如下:
class Fu {
// Fu中的成员变量
int num = 5;
}
class Zi extends Fu {
// Zi中的成员变量
int num2 = 6;
// Zi中的成员方法
public void show() {
// 访问父类中的num
System.out.println("Fu num="+num); // 继承而来,所以直接访问。
// 访问子类中的num2
System.out.println("Zi num2="+num2);
}
}
class Demo04 {
public static void main(String[] args) {
// 创建子类对象
Zi z = new Zi();
// 调用子类中的show方法
z.show();
}
}
演示结果:
Fu num = 5
Zi num2 = 6
10.5.4.2成员变量重名
如果子类父类中出现重名的成员变量,这时的访问是有影响的。代码如下:
class Fu1 {
// Fu中的成员变量。
int num = 5;
}
class Zi1 extends Fu1 {
// Zi中的成员变量
int num = 6;
public void show() {
// 访问父类中的num
System.out.print9ln("Fu num=" + num);
// 访问子类中的num
System.out.println("Zi num=" + num);
}
}
class Demo04 {
public static void main(String[] args) {
// 创建子类对象
Zi1 z = new Zi1();
// 调用子类中的show方法
z1.show();
}
}
演示结果:
Fu num = 6
Zi num = 6
说明:
- 子父类中出现了同名的成员变量时,子类会优先访问自己对象中的成员变量。如果此时想访问父类成员变量如何解决呢?我们可以使用super关键字。
- 例如
class Fu { // Fu中的成员变量。 int num = 5; } class Zi extends Fu { // Zi中的成员变量 int num = 6; public void show() { int num = 1; // 访问方法中的num System.out.println("method num=" + num); //method num=1 // 访问子类中的num System.out.println("Zi num=" + this.num); // Zi num=6 // 访问父类中的num System.out.println("Fu num=" + super.num); // Fu num=5 } } class Demo04 { public static void main(String[] args) { // 创建子类对象 Zi1 z = new Zi1(); // 调用子类中的show方法 z1.show(); } }
需要注意的是:super代表的是父类对象的引用,this代表的是当前对象的引用。
10.5.5继承后的特点—成员方法
10.5.5.1成员方法不重名
如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。代码如下:
class Fu {
public void show() {
System.out.println("Fu类中的show方法执行");
}
}
class Zi extends Fu {
public void show2() {
System.out.println("Zi类中的show2方法执行");
}
}
public class Demo05 {
public static void main(String[] args) {
Zi z = new Zi();
//子类中没有show方法,但是可以找到父类方法去执行
z.show();
z.show2();
}
}
10.5.5.2成员方法重名
如果子类父类中出现重名的成员方法,则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法。
代码如下:
class Fu {
public void show() {
System.out.println("Fu show");
}
}
class Zi extends Fu {
//子类重写了父类的show方法
public void show() {
System.out.println("Zi show");
}
}
public class ExtendsDemo05{
public static void main(String[] args) {
Zi z = new Zi();
// 子类中有show方法,只执行重写后的show方法
z.show(); // Zi show
}
}
10.5.7方法重写
10.5.7.1概念
方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
10.5.7.2使用场景与案例
发生在子父类之间的关系。
子类继承了父类的方法,但是子类觉得父类的这方法不足以满足自己的需求,子类重新写了一个与父类同名的方法,以便覆盖父类的该方 法。
10.5.7.3@Override重写注解
- @Override:注解,重写注解校验!
- 这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。
- 建议重写都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错!
10.5.7.4注意事项
- 方法重写是发生在子父类之间的关系。
- 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
- 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
10.5.8继承后的特点—构造方法
笔记小结
概述:子类构造方法的第一行都隐含了一个**super()**去调用父类无参数构造方法,**super()**可以省略不写。
10.5.8.1概述
当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?
首先我们要回忆两个事情,构造方法的定义格式和作用。
- 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
- 构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个
super()
,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。(先有爸爸,才能有儿子) - 子类重写父类方法时,访问权限子类必须大于等于父类
- 子类重写父类方法时,返回值类型子类必须小于等于父类
- 建议:重写的方法尽量和父类保持一致。
继承后子类构方法器特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法
10.5.8.2案例-人际关系
按如下需求定义类:
- 人类
成员变量: 姓名,年龄
成员方法: 吃饭 - 学生类
成员变量: 姓名,年龄,成绩
成员方法: 吃饭
代码如下:
class Person {
private String name;
private int age;
public Person() {
System.out.println("父类无参");
}
// getter/setter省略
}
class Student extends Person {
private double score;
public Student() {
//super(); // 调用父类无参,默认就存在,可以不写,必须再第一行
System.out.println("子类无参");
}
public Student(double score) {
//super(); // 调用父类无参,默认就存在,可以不写,必须再第一行
this.score = score;
System.out.println("子类有参");
}
}
public class Demo07 {
public static void main(String[] args) {
Student s1 = new Student();
System.out.println("----------");
Student s2 = new Student(99.9);
}
}
输出结果:
父类无参
子类无参
----------
父类无参
子类有参
小结
- 子类构造方法执行的时候,都会在第一行默认先调用父类无参数构造方法一次。
- 子类构造方法的第一行都隐含了一个**super()**去调用父类无参数构造方法,**super()**可以省略不写。
10.5.9继承的特点
- Java只支持单继承,不支持多继承。
// 一个类只能有一个父类,不可以有多个父类。
class A {}
class B {}
class C1 extends A {} // ok
// class C2 extends A, B {} // error
- 一个类可以有多个子类。
// A可以有多个子类
class A {}
class C1 extends A {}
class C2 extends A {}
- 可以多层继承。
class A {}
class C1 extends A {}
class D extends C1 {}
说明:
顶层父类是Object类。所有的类默认继承Object,作为父类。
10.6多态
笔记小结:
- 概述:
- 定义:指同一个方法或者同一个类,在不同的情况下具有不同的表现形式
- 注意:满足多态的前提是有继承或者实现关系
- 格式:父new子
父类类型 变量名 = new 子类/实现类构造器; 变量名.方法名(); // 多态前提,有继承关系,子类对象是可以赋值给父类类型的变量.
- 使用场景
- 父new子
- 父new接口
- 特点
- 调用成员变量时:编译看左边,运行看左边
- 调用成员方法时:编译看左边,运行看右边
- 内存图
6. 多态优点:
- 在多态形式下,右边对象可以实现解耦合,便于扩展和维护
- 定义方法的时候,使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性与便利。
- 多态缺点:无法直接访问子类特有的成员,也就是多态的写法无法访问子类独有功能,也就是说,父类不能调用子类的特有方法
- 引用类型转换:
含义:将一个对象的引用从一个类型转换为另一个类型
向上转型:将一个子类对象的引用转换为其超类或接口类型的引用
- 例如
父类类型 变量名 = new 子类类型(); Animal a = new Cat();
向下转型:将一个超类或接口类型的引用转换为一个子类对象的引用
- 例如
子类类型 变量名 = (子类类型) 父类变量名; Aniaml a = new Cat(); Cat c =(Cat) a;
instanceof
关键字
- 含义:用于测试一个对象是否是一个类的实例,或者是该类的子类或接口的实例。简单来说就是类型校验
变量名 instanceof 数据类型 /\* 如果变量属于该数据类型或者其子类类型,返回true。 如果变量不属于该数据类型或者其子类类型,返回false。\*/
10.6.1概述
10.6.1.1定义
多态是指同一行为,具有多个不同表现形式。
多态是继封装、继承之后,面向对象的第三大特性。
多态是出现在继承或者实现关系中的。
10.6.1.2前提【重点】
- 有继承或者实现关系
- 方法的重写【意义体现:不重写,无意义】
- 父类引用指向子类对象【格式体现】
说明:
父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
10.6.1.3格式
父类类型 变量名 = new 子类/实现类构造器;
变量名.方法名();
10.6.1.4使用场景
如果没有多态,在下图中register方法只能传递学生对象,其他的Teacher和administrator对象是无法传递给register方法方法的,在这种情况下,只能定义三个不同的register方法分别接收学生,老师和管理员。
有了多态之后,方法的形参就可以定义为共同的父类Person。
注意:
- 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。
- 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象。
- 而且多态还可以根据传递的不同对象来调用不同类中的方法。
10.6.2基本用例
步骤一:创建父类
- 创建
Person
实体类
public class Person {
private String name;
private int age;
空参构造
带全部参数的构造
get和set方法
public void show(){
System.out.println(name + ", " + age);
}
}
步骤二:创建子类
1.创建Administrator
实体类,并继承Person
类
public class Administrator extends Person {
@Override
public void show() {
System.out.println("管理员的信息为:" + getName() + ", " + getAge());
}
}
2.创建Student
实体类,并继承Person
类
public class Student extends Person{
@Override
public void show() {
System.out.println("学生的信息为:" + getName() + ", " + getAge());
}
}
3.创建Teacher
实体类,并继承Person
类
public class Teacher extends Person{
@Override
public void show() {
System.out.println("老师的信息为:" + getName() + ", " + getAge());
}
}
步骤三:演示
- 创建
Test
用于演示register
方法
注意:
register
方法的参数需要为Person
父类,这样才能在参数中传入子类对象
public class Test {
public static void main(String[] args) {
//创建三个对象,并调用register方法
Student s = new Student();
s.setName("张三");
s.setAge(18);
Teacher t = new Teacher();
t.setName("王建国");
t.setAge(30);
Administrator admin = new Administrator();
admin.setName("管理员");
admin.setAge(35);
register(s);
register(t);
register(admin);
}
//这个方法既能接收老师,又能接收学生,还能接收管理员
//只能把参数写成这三个类型的父类
public static void register(Person p){
p.show();
}
}
10.6.3运行特点
- 调用成员变量时:编译看左边,运行看左边
- 调用成员方法时:编译看左边,运行看右边
调用成员变量时:编译看左边,运行看左边
说明:
- 编译看左边:javac编译代码的时候,会看左边的父类中有没有这个变量,如果有,编译成功,如果没有编译失败。
- 运行看左边:Java运行代码的时候,若父类与子类都存在相同名字的变量,那么则调用左边,也就是父类中的变量
调用成员方法时:编译看左边,运行看右边
说明:
- 编译看左边:javac编译代码的时候,会看左边的父类中有没有这个变量,如果有,编译成功,如果没有编译失败。
- 运行看右边:Java运行代码的时候,若父类与子类都存在相同名字的方法,那么则运行右边,也就是子类中的方法
代码示例:
Fu f = new Zi();
//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);
//编译看左边的父类中有没有show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();
说明:
- 成员变量:在子类的对象中,会把父类的成员变量也继承下的。父: name子: name
- 成员方法:如果子类对方法进行了重写,那么在虚方法表中是会把父类的方法进行覆盖的
10.6.4调用成员的内存图解(重点)
详细链接:https://www.bilibili.com/video/BV17F411T7Ao?p=130
结论:
- 调用成员变量的特点:编译看左边,运行也看左边
- 调用成员方法的特点:编译看左边,运行看右边
10.6.5多态的优势
- 在多态形式下,右边对象可以实现解耦合,便于扩展和维护。
Person p =new Student ();
p.work();//业务逻辑发生改变时,后续代码无需修改
- 定义方法的时候,使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性与便利。
ArrayList list = new ArrayList();
list.add(student);
list.add("ok");
System.out.println(list);
说明:
当不指定ArrayList的泛型时,添加的数据类型将不受到限制
10.6.6多态的弊端
我们已经知道多态编译阶段是看左边父类类型的,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了。
class Animal{
public void eat(){
System.out.println("动物吃东西!")
}
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
}
class Test{
public static void main(String[] args){
Animal a = new Cat();
a.eat();
a.catchMouse();//编译报错,编译看左边,Animal没有这个方法
}
}
10.6.7引用类型转换
10.6.7.1概述
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。
回顾基本数据类型转换
- 自动转换: 范围小的赋值给范围大的.自动完成:double d = 5;
- 强制转换: 范围大的赋值给范围小的,强制转换:int i = (int)3.14
多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种。
10.6.7.2向上转型(自动转换)
向上转型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。
当父类引用指向一个子类对象时,便是向上转型。
- 使用格式:
父类类型 变量名 = new 子类类型();
如:Animal a = new Cat();
**原因是:父类类型相对与子类来说是大范围的类型,Animal是动物类,是父类类型。Cat是猫类,是子类类型。Animal类型的范围当然很大,包含一切动物。**所以子类范围小可以直接自动转型给父类类型的变量。
10.6.7.3向下转型(强制转换)
向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。
一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
基本用例
/\* 格式:
子类类型 变量名 = (子类类型) 父类变量名; \*/
Aniaml a = new Cat();
Cat c =(Cat) a;
10.6.7.4案例-转型演示
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。
步骤一:定义父类
- 定义
Animal
作为父类
abstract class Animal {
abstract void eat();
}
步骤二:定义子类
1.创建Cat
实体类,并继承Animal
类
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
2.创建Dog
实体类,并继承Animal
类
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void watchHouse() {
System.out.println("看家");
}
}
步骤三:演示
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
}
}
10.6.7.5转型的异常
转型的过程中,一不小心就会遇到这样的问题,请看如下代码:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}
这段代码可以通过编译,但是运行时,却报出了 ClassCastException
,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。
10.6.7.6instanceof关键字(重点)
为了避免ClassCastException的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验,格式如下:
变量名 instanceof 数据类型
如果变量属于该数据类型或者其子类类型,返回true。
如果变量不属于该数据类型或者其子类类型,返回false。
所以,转换前,我们最好先做一个判断,代码如下:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
}
10.6.7.7instanceof新特性
JDK14的时候提出了新特性,把判断和强转合并成了一行
//新特性
//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
//如果不是,则不强转,结果直接是false
if(a instanceof Dog d){
d.lookHome();
}else if(a instanceof Cat c){
c.catchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}
11.抽象类
笔记小结:
- 概述:
- 定义:没有方法体的方法称为抽象方法,包含抽象方法的类就是抽象类
- 抽象方法:没有方法体的方法
// 抽象方法 public abstract void abstractMethod();
- 抽象类:包含抽象方法的类
public abstract class AbstractClass { // 抽象方法 public abstract void abstractMethod(); }
- 注意:抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类。
- abstract介绍
- 使用
// 被继承
特征:
- 抽象类得到了拥有抽象方法的能力,也就是说有了自己的一套规范
- 抽象类失去了创建对象的能力,也就是说不能创建对象
细节:
- 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象
- 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的
- 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
- 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错
- 抽象类存在的意义是为了被子类继承
意义:抽象类存在的意义是为了被子类继承,否则抽象类将毫无意义
抽象类和接口区别,主要在于构造函数、成员变量、继承关系(单继承,多实现)
11.1概述
11.1.1定义
Java抽象类(Abstract Class)是一种特殊的类,它不能被实例化,只能被继承。抽象类用于定义一些基本行为,而具体的行为由其子类来实现。
11.1.2抽象方法
没有方法体的方法,也就是没有"{ }"的方法
11.1.3抽象类
包含抽象方法的类
11.1abstract关键字
11.1.1abstract含义
abstract是抽象的意思,用于修饰方法方法和类,修饰的方法是抽象方法,修饰的类是抽象类。
11.1.2抽象方法
使用abstract
关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。
基本用例:
/\* 格式:
修饰符 abstract 返回值类型 方法名 (参数列表);\*/
public abstract void run();
11.1.3抽象类
如果一个类包含抽象方法,那么该类必须是抽象类。注意:抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类。
/\* 格式:
abstract class 类名字 {
} \*/
public abstract class Animal {
public abstract void run();
}
11.2基本用例
要求:继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。
// 父类,抽象类
abstract class Employee {
private String id;
private String name;
private double salary;
public Employee() {
}
public Employee(String id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
// 抽象方法
// 抽象方法必须要放在抽象类中
abstract public void work();
}
// 定义一个子类继承抽象类
class Manager extends Employee {
public Manager() {
}
public Manager(String id, String name, double salary) {
super(id, name, salary);
}
// 2.重写父类的抽象方法
@Override
public void work() {
System.out.println("管理其他人");
}
}
// 定义一个子类继承抽象类
class Cook extends Employee {
public Cook() {
}
public Cook(String id, String name, double salary) {
super(id, name, salary);
}
@Override
public void work() {
System.out.println("厨师炒菜多加点盐...");
}
}
// 测试类
public class Demo10 {
public static void main(String[] args) {
// 创建抽象类,抽象类不能创建对象
// 假设抽象类让我们创建对象,里面的抽象方法没有方法体,无法执行.所以不让我们创建对象
// Employee e = new Employee();
// e.work();
// 3.创建子类
Manager m = new Manager();
m.work();
Cook c = new Cook("ap002", "库克", 1);
c.work();
}
}
说明:
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法。
11.3特征
抽象类的特征总结起来可以说是 有得有失
有得:抽象类得到了拥有抽象方法的能力。
有失:抽象类失去了创建对象的能力。
其他成员(构造方法,实例方法,静态方法等)抽象类都是具备的。
11.4细节
不需要背,只要当idea报错之后,知道如何修改即可。
关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
- 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
- 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
- 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
- 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
- 抽象类存在的意义是为了被子类继承。
理解:抽象类中已经实现的是模板中确定的成员,抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。
11.5意义
抽象类存在的意义是为了被子类继承,否则抽象类将毫无意义。抽象类可以强制让子类,一定要按照规定的格式进行重写
11.6.抽象类和接口的区别
抽象类 | 接口 | |
---|---|---|
方法实现 | 可以有实现的方法和非抽象方法 | 只有方法签名,无方法实现 |
构造函数 | 可以有构造函数 | 无法定义构造函数 |
成员变量 | 可以有成员变量 | 只能定义常量,无成员变量 |
继承关系 | 子类只能继承一个抽象类 | 类可以实现多个接口 |
功能实现 | 提供对类的部分实现 | 定义契约和行为规范 |
12.接口
笔记小结:
- 概述
- 含义:接口是一种规范或契约,它只定义了方法签名、常量以及嵌套类型的声明,没有方法实现或属性
- 格式:
//接口的定义格式: interface 接口名称{ // 抽象方法 } // 接口的声明:interface // 接口名称:首字母大写,满足“驼峰模式”
特点:
- 抽象方法:会自动加上public abstract修饰
- 常量:会自动加上 public static final修饰
- 基本实现
- 实现方式:使用
implements
关键字- 格式:
/\*\*接口的实现: 在Java中接口是被实现的,实现接口的类称为实现类。 实现类的格式:\*/ class 类名 implements 接口1,接口2,接口3...{ }
- 要求:接口体现的是一种规范,接口对实现类是一种强制性的约束。需要强制重写或定义为抽象类
- 接口与接口的多继承
- 含义:一个接口可以继承另一个或多个接口,这被称为接口的多继承
- 格式:
public interface SportMan extends Law , Abc { void run(); }
补充:接口和类之间的关系
- 类和类是继承关系,只能单继承,不能多继承
- 类与接口是实现关系,可以单实现,还可以多实现
- 接口与接口是继承关系,可以单继承,还可以多继承
- JDK中接口新增:
- JDK7以前:接口中只能定义抽象方法。
- JDK8以后:新增默认方法(default method)
格式:public default 返回值类型 方法名(参数列表) { } // 例如 public default void show(){ System.out.println("InterA接口中的默认方法 ---- show"); }
特点:
- 默认方法不是抽象方法,所以不强制被重写
- 解决接口升级的问题
注意:
- 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
- public可以省略,static不能省略
JDK9以后:新增private修饰符
格式:
- 格式 : private返回值类型方法名(参数列表){} - 范例: private void show() { } - 格式: private static返回值类型方法名(参数列表){} - 范例: private static void method(){ }
- 接口的多态
- 含义:当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口多态
- 接口的细节
- 实现类可以同时继承A类也可以实现接口,不过需要实现所有方法
- 实现类可以同时继承抽象类也可以实现接口,不过需要重写所有方法
- 实现类实现了两个接口,且两个接口存在相同抽象方法,此时只需重写一次
- 当实现了接口,子类实现类中的方法跟父类方法同名是,看需求选择重写
- 做空重写:只需要重写实现类中的部分接口,可先创建类进行重写,在将此类继承,实现部分
12.1概述
12.1.1定义
我们已经学完了抽象类,抽象类中可以用抽象方法,也可以有普通方法,构造方法,成员变量等。那么什么是接口呢?接口是更加彻底的抽象,JDK7之前,包括JDK7,接口中全部是抽象方法。接口同样是不能创建对象的
12.1.2格式
//接口的定义格式:
interface 接口名称{
// 抽象方法
}
// 接口的声明:interface
// 接口名称:首字母大写,满足“驼峰模式”
12.1.3特点
在JDK7,包括JDK7之前,接口中的只有包含:抽象方法和常量
12.1.3.1抽象方法
注意:接口中的抽象方法默认会自动加上public abstract修饰,程序员无需自己手写!!
按照规范:以后接口中的抽象方法建议不要写上public abstract。因为没有必要啊,默认会加上。
12.1.3.2常量
在接口中定义的成员变量默认会加上: public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量。这里是使用public static final修饰后,变量值就不可被修改,并且是静态化的变量可以直接用接口名访问,所以也叫常量。常量必须要给初始值。常量命名规范建议字母全部大写,多个单词用下划线连接。
12.2基本用例
public interface InterF {
// 抽象方法!
// public abstract void run();
void run();
// public abstract String getName();
String getName();
// public abstract int add(int a , int b);
int add(int a , int b);
// 它的最终写法是:
// public static final int AGE = 12 ;
int AGE = 12; //常量
String SCHOOL\_NAME = "黑马程序员";
}
12.3基本的实现
12.3.1概述
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements
关键字。
12.3.2格式
/\*\*接口的实现:
在Java中接口是被实现的,实现接口的类称为实现类。
实现类的格式:\*/
class 类名 implements 接口1,接口2,接口3...{
}
12.3.3要求和意义
- 必须重写实现的全部接口中所有抽象方法。
- 如果一个类实现了接口,但是没有重写完全部接口的全部抽象方法,这个类也必须定义成抽象类。
- 意义:接口体现的是一种规范,接口对实现类是一种强制性的约束,要么全部完成接口申明的功能,要么自己也定义成抽象类。这正是一种强制性的规范。
12.3.4案例-基本实现
假如我们定义一个运动员的接口(规范),代码如下:
/\*\*
接口:接口体现的是规范。
\* \*/
public interface SportMan {
void run(); // 抽象方法,跑步。
void law(); // 抽象方法,遵守法律。
String compittion(String project); // 抽象方法,比赛。
}
接下来定义一个乒乓球运动员类,实现接口,实现接口的实现类代码如下:
package com.itheima._03接口的实现;
/\*\*
\* 接口的实现:
\* 在Java中接口是被实现的,实现接口的类称为实现类。
\* 实现类的格式:
\* class 类名 implements 接口1,接口2,接口3...{
\*
\*
\* }
\* \*/
public class PingPongMan implements SportMan {
@Override
public void run() {
System.out.println("乒乓球运动员稍微跑一下!!");
}
@Override
public void law() {
System.out.println("乒乓球运动员守法!");
}
@Override
public String compittion(String project) {
return "参加"+project+"得金牌!";
}
}
测试代码:
public class TestMain {
public static void main(String[] args) {
// 创建实现类对象。
PingPongMan zjk = new PingPongMan();
zjk.run();
zjk.law();
System.out.println(zjk.compittion("全球乒乓球比赛"));
}
}
12.3.5案例-多实现
类与接口之间的关系是多实现的,一个类可以同时实现多个接口。
首先我们先定义两个接口,代码如下:
/\*\* 法律规范:接口\*/
public interface Law {
void rule();
}
/\*\* 这一个运动员的规范:接口\*/
public interface SportMan {
void run();
}
然后定义一个实现类:
/\*\*
\* Java中接口是可以被多实现的:
\* 一个类可以实现多个接口: Law, SportMan
\*
\* \*/
public class JumpMan implements Law ,SportMan {
@Override
public void rule() {
System.out.println("尊长守法");
}
@Override
public void run() {
System.out.println("训练跑步!");
}
}
说明:
从上面可以看出类与接口之间是可以多实现的,我们可以理解成实现多个规范,这是合理的
12.3接口与接口的多继承
Java中,接口与接口之间是可以多继承的:也就是一个接口可以同时继承多个接口
注意:
- 类与接口是实现关系
- 接口与接口是继承关系
接口继承接口就是把其他接口的抽象方法与本接口进行了合并。
案例演示:
public interface Abc {
void go();
void test();
}
/\*\* 法律规范:接口\*/
public interface Law {
void rule();
void test();
}
\*
\* 总结:
\* 接口与类之间是多实现的。
\* 接口与接口之间是多继承的。
\* \*/
public interface SportMan extends Law , Abc {
void run();
}
12.4JDK8以后接口中新增的方法
- 允许在接口中定义默认方法,需要使用关键字default修饰
作用:解决接口升级的问题
-
接口中默认方法的定义格式:
- 格式: public default返回值类型方法名(参数列表){}
- 范例:public default void show(){ }
-
接口中默认方法的注意事项:
- 默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候去掉default关键字
- public可以省略,default不能省略
- 如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写
示例:
public interface InterA {
/\*接口中默认方法的定义格式:
格式:public default 返回值类型 方法名(参数列表) { }
接口中默认方法的注意事项:
1.默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候去掉default关键字
2.public可以省略,default不能省略
3.如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写\*/
public abstract void method();
public default void show(){
System.out.println("InterA接口中的默认方法 ---- show");
}
}
接口中,静态的关键字可以不能省略。
接口中,静态的关键字修饰可以又方法体
接口中,静态的关键字是为了解决接口升级的问题
- 允许在接口中定义定义静态方法,需要用static修饰
- 接口中静态方法的定义格式:
- 格式: public static返回值类型方法名(参数列表){}
- 范例:public static void show(){ }
- 接口中静态方法的注意事项:
- 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
- public可以省略,static不能省略
示例:
在接口中,被static修饰的方法,不能被重写,可以直接调用
重写(子类把从父类继承下来的虚方法表里面的方法进行覆盖了,这才叫重写。)
12.5JDK9新增的方法
基本用例
/\*格式:
private返回值类型方法名(参数列表){} \*/
private void show() { }
/\*格式:
private static返回值类型方法名(参数列表){} \*/
private static void method(){ }
12.6接口的应用
12.6.1接口的灵活使用
接口代表规则,是行为的抽象。想要让哪个类拥有一个行为,就让这个类实现对应的接口就可以了
若想让某种javaBean实现某种功能,则实现某种接口即可
12.6.2接口的多态
当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口多态。
说明:
如果一个方法中,当参数为接口时,那么在调用方法时就可传递这个接口的所有实现类对象
基本用例:
public class Main {
public static void main(String[] args) {
test(new IHomeServiceImpl());
}
private static void test(IHomeService IHomeService){
IHomeServiceImpl home1= (IHomeServiceImpl) IHomeService;
home1.test();
}
说明:
IHomeService为接口,那么当这个接口需要什么对象时,new相应对象即可拿到这个接口对应的实现类对象
12.7适配器设计模式
请查看接口的细节 第5个
12.8接口的细节
关于接口的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
- 当两个接口中存在相同抽象方法的时候,该怎么办?
只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的。
- 实现类能不能继承A类的时候,同时实现其他接口呢?
- 继承的父类,就好比是亲爸爸一样
- 实现的接口,就好比是干爹一样
- 可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。
- 实现类能不能继承一个抽象类的时候,同时实现其他接口呢?
实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写。
- 实现类Zi,实现了一个接口,还继承了一个Fu类。假设在接口中有一个方法,父类中也有一个相同的方法。子类如何操作呢?
- 处理办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。
- 处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。
- 如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?(重点)
- 可以在接口跟实现类中间,新建一个中间类(适配器类)
- 让这个适配器类去实现接口,对接口里面的所有的方法做空重写。
- 让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。
- 因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象
13.枚举
笔记小结:
- 含义:它是一种特殊的数据类型,用于定义一组固定的常量
- 普通枚举常量
enum Weekday { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
- 带有参数的枚举常量
enum DayOfWeek { MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6), SUNDAY(7); private int value; DayOfWeek(int value) { this.value = value; } public int getValue() { return this.value;
- 实现接口的枚举常量
public enum BasicOperation implements Operation { PLUS("+") { public int apply(int x, int y) { return x + y; } } private final String symbol; BasicOperation(String symbol) { this.symbol = symbol; } }
- 匿名内部类的方式
enum Operation { PLUS { public int apply(int x, int y) { return x + y; } } public abstract int apply(int x, int y); }
13.1概述
Java中枚举是一种特殊的数据类型,用于定义一组固定的常量。枚举类型定义了一个枚举集合,可以在其中定义枚举常量,并且可以通过名称来访问它们。枚举在Java中是一个独立的类,可以包含属性、方法和构造函数等元素
枚举常量通常用于表示一组有限的可能取值,例如一周中的星期几、一年中的季节、颜色等等。使用枚举类型可以提高代码的可读性、可维护性和可扩展性
13.2基本用例
步骤一:定义枚举
- 创建枚举
Weekday
类
enum Weekday {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
说明:
常量的定义用
,
作为分隔符进行分割
步骤二:演示
// 调用
Weekday today = Weekday.MONDAY;
说明:
调用的方式类跟静态常量的调用方法相同
13.3枚举常量
/\* 格式
enum 枚举名 {
枚举常量1,枚举常量2,……
} \*/
enum Weekday {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
// 使用
Weekday today = Weekday.MONDAY;
System.out.println(day); // 输出 MONDAY
13.4带有参数的枚举常量
/\* 格式
enum 枚举名 {
枚举常量1(值),枚举常量2(值),……
} \*/
enum DayOfWeek {
MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6), SUNDAY(7);
private int value;
DayOfWeek(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
// 使用
DayOfWeek day = DayOfWeek.MONDAY;
int value = day.getValue();
System.out.println("Today is " + day + ", value is " + value);// 输出Today is MONDAY, value is 1
13.5实现接口的枚举常量
/\* 格式
public interface Operation {
int apply(int x, int y);
} \*/
public enum BasicOperation implements Operation {
PLUS("+") {
public int apply(int x, int y) { return x + y; }
},
MINUS("-") {
public int apply(int x, int y) { return x - y; }
},
TIMES("\*") {
public int apply(int x, int y) { return x \* y; }
},
DIVIDE("/") {
public int apply(int x, int y) { return x / y; }
};
private final String symbol;
BasicOperation(String symbol) {
this.symbol = symbol;
}
@Override public String toString() {
return symbol;
}
}
// 使用
int result = BasicOperation.PLUS.apply(1, 2); // 3
13.6匿名内部类的方式
// 例如
enum Operation {
PLUS {
public int apply(int x, int y) {
return x + y;
}
},
MINUS {
public int apply(int x, int y) {
return x - y;
}
},
TIMES {
public int apply(int x, int y) {
return x \* y;
}
},
DIVIDE {
public int apply(int x, int y) {
return x / y;
}
};
public abstract int apply(int x, int y);
}
// 使用
int result = Operation.PLUS.apply(1, 2) // 返回值为3
14.常用类
笔记小结:
String
、StringBuilder
、StringJoiner
类,见各个小节- 字符串原理:请参见字符串原理小节内容。
- 注意:不断的使用
String s = “常量”
进行字符串拼接会消耗大量的堆内存空间,建议使用StringJoiner
类进行字符串的拼接
14.1String类
笔记小结:
- 概述:Java 程序中所有的双引号字符串,都是 String 类的对象。String对象不需要导包
- 特点:
- 不可变性:频繁修改字符串的操作会导致内存占用和性能问题,因为频繁创建String对象并进行重新赋值
- 线程安全性:多个线程可以同时访问同一个String对象,而不会出现线程安全问题
- 存储在常量池中:Java中有一个字符串常量池,用于存储字符串常量
- 构造方法
- String()
- String(char[] chs)
- String(byte[] bys)
- String s = “常量”
- 构造方法创建对象与直接赋值区别
- 创建对象:不存在复用,对象每new一次就会在内存中开辟一个新的空间
- 直接赋值:存在复用,若对象的值是常量池中已存在的,则JVM不会在堆内存中开辟新的空间
- 字符串比较
- == 的作用:基本数据类型比较值,引用数据类型比较地址值
- equals方法的作用:比较字符串内容是否相同
- equalsIgnoreCase方法的作用:比较字符串内容是否相同,不区分大小写
- 案例:
- 手机号屏蔽:
字符.substring()
- 敏感词替换:
字符串.replace()
14.1.1概述
String 类代表字符串,Java 程序中的所有字符串文字(例如“abc”)都被实现为此类的实例。也就是说,Java 程序中所有的双引号字符串,都是 String 类的对象。String 类在 java.lang 包下,所以使用的时候不需要导包!
14.1.2特点
- 不可变性:String对象一旦创建,其内容不可变。这意味着当对一个String对象进行修改时,实际上是创建了一个新的String对象,并将原对象的内容复制到新对象中。因此,频繁修改字符串的操作会导致内存占用和性能问题
- 线程安全性:由于String对象不可变,所以多个线程可以同时访问同一个String对象,而不会出现线程安全问题
- 存储在常量池中:Java中有一个字符串常量池,用于存储字符串常量。当创建一个字符串时,如果该字符串已经存在于常量池中,则返回常量池中的字符串对象,否则创建一个新的对象并添加到常量池中
- 支持操作符+和+=:可以通过+和+=操作符将两个字符串连接起来,形成一个新的字符串。但是需要注意的是,每次使用这些操作符都会创建一个新的String对象
- 支持Unicode编码:String类中的字符编码采用Unicode编码,这使得String可以支持多种语言和字符集
- 支持常用的字符串操作:String类提供了许多常用的字符串操作,例如字符串比较、查找、替换、切割、大小写转换等
14.1.3构造方法
- 常用的构造方法
方法名 | 说明 |
---|---|
public String() | 创建一个空白字符串对象,不含有任何内容 |
public String(char[] chs) | 根据字符数组的内容,来创建字符串对象 |
public String(byte[] bys) | 根据字节数组的内容,来创建字符串对象 |
String s = “abc”; | 直接赋值的方式创建字符串对象,内容就是abc |
- 示例代码
public class StringDemo01 {
public static void main(String[] args) {
//public String():创建一个空白字符串对象,不含有任何内容
String s1 = new String();
System.out.println("s1:" + s1);
//public String(char[] chs):根据字符数组的内容,来创建字符串对象
char[] chs = {'a', 'b', 'c'};
String s2 = new String(chs);
System.out.println("s2:" + s2);
//public String(byte[] bys):根据字节数组的内容,来创建字符串对象
byte[] bys = {97, 98, 99};
String s3 = new String(bys);
System.out.println("s3:" + s3);
//String s = “abc”: 直接赋值的方式创建字符串对象,内容就是abc
String s4 = "abc";
System.out.println("s4:" + s4);
}
}
说明:
- 字符数组常用在更换 某个字符,此时需要构造方法
- 字节数组常用在把字节信息进行转换,此时需要构造方法
14.1.4构造和直接赋值方式创建对象区别
- 通过构造方法创建
通过 new 创建的字符串对象,每一次 new 都会申请一个内存空间,虽然内容相同,但是地址值不同
说明:
不存在复用,因为每次new出来的对象会在内存中开辟一个新的空间
- 直接赋值方式创建
以“ ”方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一个 String 对象,并在字符串池中维护
说明:
当使用双引号直接赋值时,系统会检查该字符串在串池中是否存在。若不存在,则创建新的、若存在则复用
14.1.5字符串的比较
14.1.5.1==号的作用
- 比较基本数据类型:比较的是具体的值
- 比较引用数据类型:比较的是对象地址值
14.1.5.2equals方法的作用
- 方法介绍
public boolean equals(String s) 比较两个字符串内容是否相同、区分大小写
- 基本用例
public class StringDemo02 {
public static void main(String[] args) {
//构造方法的方式得到对象
char[] chs = {'a', 'b', 'c'};
String s1 = new String(chs);
String s2 = new String(chs);
//直接赋值的方式得到对象
String s3 = "abc";
String s4 = "abc";
//比较字符串对象地址是否相同
System.out.println(s1 == s2); // false
System.out.println(s1 == s3); // false
System.out.println(s3 == s4); // true
System.out.println("--------");
//比较字符串内容是否相同
System.out.println(s1.equals(s2)); // true
System.out.println(s1.equals(s3)); // true
System.out.println(s3.equals(s4)); // true
}
}
14.1.5.3equalsIgnoreCase方法的作用
equalsIgnoreCase 是 Java 中的一个字符串方法,用于比较两个字符串是否相等,但忽略它们的大小写。它与 equals
方法类似,但不考虑大小写的区别。
方法介绍:
public boolean equalsIgnoreCase(String anotherString)
14.1.5案例—手机号屏蔽
说明:
需求:以字符串的形式从键盘接受一个手机号,将中间四位号码屏蔽
代码实现:
public class Test8手机号屏蔽 {
public static void main(String[] args) {
/\*以字符串的形式从键盘接受一个手机号,将中间四位号码屏蔽
最终效果为:131\*\*\*\*9468\*/
//1.键盘录入一个手机号码
Scanner sc = new Scanner(System.in);
System.out.println("请输入手机号码");
String phoneNumber = sc.next();//13112349408
//2.截取手机号码中的前三位
String star = phoneNumber.substring(0, 3);
//3.截取手机号码中的最后四位
//此时我用substring方法,是用1个参数的,还是两个参数的?1个参数的会更好
//因为现在我要截取到最后,所以建议使用1个参数的。
String end = phoneNumber.substring(7);
//4.拼接
String result = star + "\*\*\*\*" + end;
System.out.println(result);
}
}
补充:在 Java 中,substring
是字符串类中的一个方法,用于截取一个字符串的子串
这个方法有两种不同的用法:
- 截取从指定位置开始到字符串末尾的子串:
public String substring(int beginIndex)
其中,
beginIndex
表示截取子串的起始位置。返回从beginIndex
开始到字符串末尾的子串。例如:
rustCopy codeString str = "Hello, world!"; String substr = str.substring(7); // 截取 "world!" System.out.println(substr);
输出结果为:
world!
- 截取从指定位置开始到指定位置结束的子串:
public String substring(int beginIndex, int endIndex)
其中,
beginIndex
和endIndex
分别表示截取子串的起始位置和结束位置。返回从beginIndex
开始到endIndex - 1
结束的子串。例如:
rustCopy codeString str = "Hello, world!"; String substr = str.substring(7, 12); // 截取 "world" System.out.println(substr);
输出结果为:
world
需要注意的是,
substring
方法返回的是一个新的字符串,而不是在原字符串上进行修改。如果beginIndex
或endIndex
超出了字符串的范围,会抛出IndexOutOfBoundsException
异常。
14.1.6案例—敏感词替换
需求1:键盘录入一个 字符串,如果字符串中包含(TMD),则使用***替换
public class Test9敏感词替换 {
public static void main(String[] args) {
//1.定义一个变量表示骂人的话
String talk = "后裔你玩什么啊,TMD";
//2.把这句话中的敏感词进行替换
String result = talk.replace("TMD", "\*\*\*");
//3.打印
System.out.println(talk);
System.out.println(result);
}
}
需求2:如果要替换的敏感词比较多怎么办?
public class Test10多个敏感词替换 {
public static void main(String[] args) {
//实际开发中,敏感词会有很多很多
//1.先键盘录入要说的话
Scanner sc = new Scanner(System.in);
System.out.println("请输入要说的话");
String talk = sc.next();//后裔你玩什么啊,TMD,GDX,ctmd,ZZ
//2.定义一个数组用来存多个敏感词
String[] arr = {"TMD","GDX","ctmd","ZZ","lj","FW","nt"};
//3.把说的话中所有的敏感词都替换为\*\*\*
for (int i = 0; i < arr.length; i++) {
//i 索引
//arr[i] 元素 --- 敏感词
talk = talk.replace(arr[i],"\*\*\*");
}
//4.打印结果
System.out.println(talk);//后裔你玩什么啊,\*\*\*,\*\*\*,\*\*\*,\*\*\*
}
}
补充:在 Java 中,replace
是字符串类中的一个方法,用于替换一个字符串中的某些字符或子串。****
这个方法有两种不同的用法:
- 用新的字符串替换掉所有的旧字符串:
public String replace(CharSequence target, CharSequence replacement)
其中,
target
表示要被替换的旧字符串,replacement
表示用于替换的新字符串。返回一个新的字符串,其中所有的target
都被替换成了replacement
。例如:
rustCopy codeString str = "Hello, world!"; String newStr = str.replace("o", "0"); // 替换所有的 "o" 为 "0" System.out.println(newStr);
输出结果为:
Hell0, w0rld!
- 用新的字符串替换掉某个位置开始的一段子串:
public String replace(int startIndex, int endIndex, String newStr)
其中,
startIndex
和endIndex
分别表示要被替换的子串的起始位置和结束位置(不包括endIndex
所在的字符),newStr
表示用于替换的新字符串。返回一个新的字符串,其中从startIndex
开始到endIndex - 1
结束的子串都被替换成了newStr
。例如:
rustCopy codeString str = "Hello, world!"; String newStr = str.replace(7, 12, "JAVA"); // 替换 "world" 为 "JAVA" System.out.println(newStr);
输出结果为:
Hello, JAVA!
需要注意的是,
replace
方法返回的是一个新的字符串,而不是在原字符串上进行修改。如果startIndex
或endIndex
超出了字符串的范围,会抛出IndexOutOfBoundsException
异常。
14.2StringBuilder类
笔记小结:
- 概述:可以看成是一个容器,创建之后里面的内容是可变的。
- 常用方法:append()、length()、reverse()、toString()
- 链式编程:
- 含义:对StringBuilder对象进行方法连续操作
- 格式:
StringBuilder sb = new StringBuilder(); sb.append("aaa").append("bbb").append("ccc").append("ddd");
14.2.1概述
StringBuilder 可以看成是一个容器,创建之后里面的内容是可变的。当我们在拼接字符串和反转字符串的时候会使用到
14.2.2成员方法
14.2.3基本用例-基本使用
public class StringBuilderDemo3 {
public static void main(String[] args) {
//1.创建对象
StringBuilder sb = new StringBuilder("abc");
//2.添加元素
/\*sb.append(1);
sb.append(2.3);
sb.append(true);\*/
//反转
sb.reverse();
//获取长度
int len = sb.length();
System.out.println(len);
//打印
//普及:
//因为StringBuilder是Java已经写好的类
//java在底层对他做了一些特殊处理。
//打印对象不是地址值而是属性值。
System.out.println(sb);
}
}
14.2.4链式编程
public class StringBuilderDemo4 {
public static void main(String[] args) {
//1.创建对象
StringBuilder sb = new StringBuilder();
//2.添加字符串
sb.append("aaa").append("bbb").append("ccc").append("ddd");
System.out.println(sb);//aaabbbcccddd
//3.再把StringBuilder变回字符串
String str = sb.toString();
System.out.println(str);//aaabbbcccddd
}
}
注意:
需要用
tostring
将它变为字符串,因为此时为StringBuilder容器而不是字符串
14.3StringJoiner类
笔记小结:
- 概述:可以看成是一个容器,创建之后里面的内容是可变的。
- 构造方法:
- StringJoiner(间隔符号)
- StringJoiner(间隔符号,开始符号,结束符号)
- 常用方法:
- add()
- length()
- toString()
14.3.1概述
- StringJoiner跟StringBuilder一样,也可以看成是一个容器,创建之后里面的内容是可变的。
- 作用:提高字符串的操作效率,而且代码编写特别简洁,但是目前市场上很少有人用。
- JDK8出现的
14.3.2构造方法
14.3.3成员方法
14.3.4基本用例-基本使用
//1.创建一个对象,并指定中间的间隔符号
StringJoiner sj = new StringJoiner("---");
//2.添加元素
sj.add("aaa").add("bbb").add("ccc");
//3.打印结果
System.out.println(sj);//aaa---bbb---ccc
//1.创建对象
StringJoiner sj = new StringJoiner(", ","[","]");
//2.添加元素
sj.add("aaa").add("bbb").add("ccc");
int len = sj.length();
System.out.println(len);//15
//3.打印
System.out.println(sj);//[aaa, bbb, ccc]
String str = sj.toString();
System.out.println(str);//[aaa, bbb, ccc]
14.4字符串原理(重点)
说明:
详细视频讲解:https://www.bilibili.com/video/BV17F411T7Ao/?p=107
14.4.1扩展底层原理1:字符串存储的内存原理
- 直接赋值会复用字符串常量池中的
- new出来不会复用,而是开辟一个新的空间
14.4.2扩展底层原理2:==号比较的到底是什么?
- 基本数据类型比较数据值
- 引用数据类型比较地址值
14.4.3扩展底层原理3:字符串拼接的底层原理
- 如果没有变量参与,都是字符串直接相加,编译之后就是拼接之后的结果,会复用串池中的字符串。
- 如果有变量参与,每一行拼接的代码,都会在内存中创建新的字符串,浪费内存。
常量拼接
变量拼接
说明:
会非常的消耗堆内存,一个加号会在堆中创建两个对象
说明:JDK8之后将字符串拼接进行了优化处理
- 在 JDK8 中,Java 对字符串拼接进行了优化处理。在之前的版本中,字符串拼接通常会使用
+
运算符,这会创建大量的临时字符串对象,导致内存的浪费和垃圾回收的频繁发生,降低程序的性能。- 为了解决这个问题,JDK8 中引入了新的字符串拼接方式,即使用
StringBuilder
类或StringJoiner
类进行拼接。这些类在内部使用可变长度的字符数组来存储字符串,可以避免创建大量的临时字符串对象,提高了程序的性能和效率。例如,以下代码使用
StringBuilder
类进行字符串拼接:String name = "John"; int age = 30; String city = "New York"; String message = new StringBuilder("My name is ") .append(name) .append(", I am ") .append(age) .append(" years old, and I live in ") .append(city) .toString(); System.out.println(message);
输出结果为:
My name is John, I am 30 years old, and I live in New York
需要注意的是,这种优化并不意味着在所有情况下都应该使用
StringBuilder
或StringJoiner
类进行字符串拼接。在一些简单的情况下,直接使用+
运算符可能更加方便和易读。开发者应该根据具体情况选择合适的方式进行字符串拼接。
14.4.4扩展底层原理4:StringBuilder提高效率原理图
- 所有要拼接的内容都会往StringBuilder中放,不会创建很多无用的空间,节约内存
说明:
StringBuilder是一个内容可变的容器
14.4.5扩展底层原理5:StringBuilder源码分析
- 默认创建一个长度为16的字节数组
- 添加的内容长度小于16,直接存
- 添加的内容大于16会扩容(原来的容量*2+2)
- 如果扩容之后还不够,以实际长度为准
说明:
StrinBuilder会创建默认为16的字节数组
15.内部类
笔记小结:
- 概述
- 定义:定义在另一个类中的类
- 作用:实现更好的封装性,内部的事务脱离外部的事务无法使用
- 分类:
- 成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
- 静态内部类,类定义在了成员位置 (类中方法外称为成员位置,有static修饰的内部类)
- 局部内部类,类定义在方法内
- 匿名内部类,没有名字的内部类,可以在方法中,也可以在类中方法外。
- 成员内部类:请见成员内部类小节
- 静态内部类:请见成员内部类小节
- 局部内部类:请见成员内部类小节
- 匿名内部类:请见成员内部类小节。只使用一次类时,可以使用匿名内部类
15.1概述
15.1.1定义
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。可以把内部类理解成寄生,外部类理解成宿主。
Java内部类是定义在另一个类中的类,它可以访问包含它的外部类的所有成员和方法,包括私有成员和方法。
15.1.2作用
一个事物内部还有一个独立的事物,内部的事物脱离外部的事物无法独立使用
- 人里面有一颗心脏。
- 汽车内部有一个发动机。
- 为了实现更好的封装性。
15.2分类
- 成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
- 静态内部类,类定义在了成员位置 (类中方法外称为成员位置,有static修饰的内部类)
- 局部内部类,类定义在方法内
- 匿名内部类,没有名字的内部类,可以在方法中,也可以在类中方法外。
15.3成员内部类
笔记小结:
- 特点:无static修饰的内部类,属于外部类对象
- 使用:
// 前提需要new对象 外部类.内部类。 // 访问内部类的类型都是用 外部类.内部类
- 获取内部类对象方式
- 外部直接创建成员内部类的对象
外部类.内部类 变量 = new 外部类().new 内部类();
- 在外部类中定义一个方法提供内部类的对象
public class Outer { String name; private class Inner{ static int a = 10; } public Inner getInstance(){ return new Inner(); } }
- 细节:
- 成员内部类可以被一些修饰符所修饰,例如: private,默认,protected等
- 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量
- 创建内部类对象时,对象中有一个隐含的Outer.this记录外部类对象的地址值
内存图:
方法区中的字节码文件,会加载外部内字节码文件和内部类的字节码文件,它是两个独立的字节码文件
2. 当new Inner()时会在堆内存中记录隐藏的this,而这个this是外部类的地址
3. 补充:a,就近原则、this.a,是内部类中的a、Outer.this.a,是外部类中的a
15.3.1特点
- 无static修饰的内部类,属于外部类对象的。
- 宿主:外部类对象。
15.3.2使用格式
// 前提是new对象
外部类.内部类。 // 访问内部类的类型都是用 外部类.内部类
15.3.3获取成员内部类对象
方式一:外部直接创建成员内部类的对象
/\* 格式:
外部类.内部类 变量 = new 外部类().new 内部类();\*/
public class Test {
public static void main(String[] args) {
// 宿主:外部类对象。
// Outer out = new Outer();
// 创建内部类对象。
Outer.Inner oi = new Outer().new Inner();
oi.method();
}
}
class Outer {
// 成员内部类,属于外部类对象的。
// 拓展:成员内部类不能定义静态成员。
public class Inner{
// 这里面的东西与类是完全一样的。
public void method(){
System.out.println("内部类中的方法被调用了");
}
}
}
方式二:在外部类中定义一个方法提供内部类的对象
public class Outer {
String name;
private class Inner{
static int a = 10;
}
public Inner getInstance(){
return new Inner();
}
}
public class Test {
public static void main(String[] args) {
Outer o = new Outer();
System.out.println(o.getInstance());
}
}
说明:
- 当成员内部类被private修饰时。在外部类编写方法,对外提供内部类对象
- 当成员内部类被非私有修饰时,直接创建对象。Outer.Inner oi = new Outer().new Inner();
15.3.4细节
编写成员内部类的注意点:
- 成员内部类可以被一些修饰符所修饰,比如: private,默认,protected,public,static等
- 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。
- 创建内部类对象时,对象中有一个隐含的Outer.this记录外部类对象的地址值。(请参见"成员内部类"的内存图)
详解:
- 内部类被private修饰,外界无法直接获取内部类的对象,只能通过“成员内部类”中的方式二获取内部类的对象
- 被其他权限修饰符修饰的内部类一般“成员内部类”中的方式一直接获取内部类的对象
- 内部类被static修饰是成员内部类中的特殊情况,叫做静态内部类下面单独学习。
- 内部类如果想要访问外部类的成员变量,外部类的变量必须用final修饰,JDK8以前必须手动写final,JDK8之后不需要手动写,JDK默认加上。
15.3.5内存图(重点)
说明:
- 方法区中的字节码文件,会加载外部内字节码文件和内部类的字节码文件,它是两个独立的字节码文件
- 当new Inner()时会在堆内存中记录隐藏的this,而这个this是外部类的地址
- 补充:a,就近原则、this.a,是内部类中的a、Outer.this.a,是外部类中的a
15.3.6案例–面试题
注意:
内部类访问外部类对象的格式是:外部类名.this
public class Test {
public static void main(String[] args) {
Outer.inner oi = new Outer().new inner();
oi.method();
}
}
class Outer { // 外部类
private int a = 30;
// 在成员位置定义一个类
class inner {
private int a = 20;
public void method() {
int a = 10;
System.out.println(???); // 10 答案:a
System.out.println(???); // 20 答案:this.a
System.out.println(???); // 30 答案:Outer.this.a
}
}
}
说明:
外部类成员变量和内部类成员变量重名时,在内部类如何访问?使用
System.out.println(outer.this.变量名);
15.4静态内部类
笔记小结:
- 特点:有static修饰,属于外部类本身的
- 使用:
外部类名.内部类名.方法名();
- 获取内部类对象方式
外部类.内部类 变量 = new 外部类.内部类构造器;
15.4.1特点
- 静态内部类是一种特殊的成员内部类。
- 有static修饰,属于外部类本身的。
总结:
静态内部类与其他类的用法完全一样。只是访问的时候需要加上外部类.内部类。
补充:
- 静态内部类可以直接访问外部类的静态成员。
- 静态内部类不可以直接访问外部类的非静态成员,如果要访问需要创建外部类的对象。
- 静态内部类中没有隐含的Outer.this。
15.4.2使用格式
外部类名.内部类名.方法名();
15.4.3获取成员内部类对象
/\* 格式:
外部类.内部类 变量 = new 外部类.内部类构造器; \*/
// 外部类:Outer01
class Outer01{
private static String sc_name = "黑马程序";
// 内部类: Inner01
public static class Inner01{
// 这里面的东西与类是完全一样的。
private String name;
public Inner01(String name) {
this.name = name;
}
public void showName(){
System.out.println(this.name);
// 拓展:静态内部类可以直接访问外部类的静态成员。
System.out.println(sc_name);
}
}
}
public class InnerClassDemo01 {
public static void main(String[] args) {
// 创建静态内部类对象。
// 外部类.内部类 变量 = new 外部类.内部类构造器;
Outer01.Inner01 in = new Outer01.Inner01("张三");
in.showName();
}
}
说明:
注意区分成员内部类的创建方式 Outer.inner oi = new Outer().new inner();
15.5局部内部类
笔记小结:
- 特点:定义在方法中的类
- 使用格式
class 外部类名 { 数据类型 变量名; 修饰符 返回值类型 方法名(参数列表) { // … class 内部类 { // 成员变量 // 成员方法 } } }
15.5.1特点
局部内部类 :定义在方法中的类
15.5.2使用格式
class 外部类名 {
数据类型 变量名;
修饰符 返回值类型 方法名(参数列表) {
// …
class 内部类 {
// 成员变量
// 成员方法
}
}
}
15.6匿名内部类(重点)
笔记小结:
- 特点:
- 定义一个没有名字的内部类
- 这个类实现了父类,或者父类接口
- 匿名内部类会创建这个没有名字的类的对象
- 使用格式:new 接口 或者 new 父类
new 父类名或者接口名(){ // 方法重写 @Override public void method() { // 执行语句 } };
- 使用方式
- new 父类或方法后 直接.调用
new Swim() { @Override public void swimming() { System.out.println("自由泳..."); } }.swimming();
- new 父类或方法后 直接赋值
// 接口 变量 = new 实现类(); // 多态,走子类的重写方法 Swim s2 = new Swim() { @Override public void swimming() { System.out.println("蛙泳..."); } }; s2.swimming();
- 意义:定义一个只要使用一次的类,就可考虑使用匿名内部类。匿名内部类的本质作用是为了简化代码
匿名内部类 :是内部类的简化写法。他是一个隐含了名字的内部类。开发中,最常用到的内部类就是匿名内部类了。
15.6.1特点
- 定义一个没有名字的内部类
- 这个类实现了父类,或者父类接口
- 匿名内部类会创建这个没有名字的类的对象
15.6.2使用格式
前提:
匿名内部类必须继承一个父类或者实现一个父接口
new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};
注意:
使用前提,要么继承父类,要么实现接口
15.6.3获取匿名内部类
概述:
1.直接new接口
new Swim() {
@Override
public void swimming() {
System.out.println("自由泳...");
}
}.swimming();
2.重写子类方法
// 接口 变量 = new 实现类(); // 多态,走子类的重写方法
Swim s2 = new Swim() {
@Override
public void swimming() {
System.out.println("蛙泳...");
}
};
基本用例:
interface Swim {
public abstract void swimming();
}
public class Demo07 {
public static void main(String[] args) {
// 使用匿名内部类
new Swim() {
@Override
public void swimming() {
System.out.println("自由泳...");
}
}.swimming();
// 接口 变量 = new 实现类(); // 多态,走子类的重写方法
Swim s2 = new Swim() {
@Override
public void swimming() {
System.out.println("蛙泳...");
}
};
s2.swimming();
s2.swimming();
}
}
15.6.4应用场景
之前我们使用接口时,似乎得做如下几步操作:
- 定义子类
- 重写接口中的方法
- 创建子类对象
- 调用重写后的方法
interface Swim {
public abstract void swimming();
}
// 1. 定义接口的实现类
class Student implements Swim {
// 2. 重写抽象方法
@Override
public void swimming() {
System.out.println("狗刨式...");
}
}
public class Test {
public static void main(String[] args) {
// 3. 创建实现类对象
Student s = new Student();
// 4. 调用方法
s.swimming();
}
}
现在:通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递
interface Swim {
public abstract void swimming();
}
public class Demo07 {
public static void main(String[] args) {
// 普通方式传入对象
// 创建实现类对象
Student s = new Student();
goSwimming(s);
// 匿名内部类使用场景:作为方法参数传递
Swim s3 = new Swim() {
@Override
public void swimming() {
System.out.println("蝶泳...");
}
};
// 传入匿名内部类
goSwimming(s3);
// 完美方案: 一步到位
goSwimming(new Swim() {
public void swimming() {
System.out.println("大学生, 蛙泳...");
}
});
goSwimming(new Swim() {
public void swimming() {
System.out.println("小学生, 自由泳...");
}
});
}
// 定义一个方法,模拟请一些人去游泳
public static void goSwimming(Swim s) {
s.swimming();
}
}
15.6.5意义
实际上,如果我们希望定义一个只要使用一次的类,就可考虑使用匿名内部类。匿名内部类的本质作用是为了简化代码。
16.包装类
笔记小结:
最后
文章中涉及到的知识点我都已经整理成了资料,录制了视频供大家下载学习,诚意满满,希望可以帮助在这个行业发展的朋友,在论坛博客等地方少花些时间找资料,把有限的时间,真正花在学习上,所以我把这些资料,分享出来。相信对于已经工作和遇到技术瓶颈的朋友们,在这份资料中一定都有你需要的内容。