java面向对象基础
一,设计对象并使用
在java中,必须先设计类,才能获取对象
- 类:是对象共同特征的描述
- 对象:是真实存在的具体的东西
//定义类
public class 类名{
成员变量(代表属性)
成员方法(代表行为)
构造器
}
//创建对象
类名 对象名=new 类名();
//拿到对象后
对象名.成员变量;
对象名.成员方法(参数);
-
用来描述一类事物的类叫javabean类
-
编写main方法的类叫测试类
-
java文件可以定义多个class类,但有且只能用一个public修饰,且public修饰的类必须为代码的文件名
-
实际开发中一个类一个文件
-
成员变量完整定义格式:
修饰符 数据类型 变量名 =初始值
如果不给初始值,存在默认值
数据类型 默认值 byte,short,int,long 0 float,double 0.0 boolean false 引用数据类型 null
java中成员变量使用数组的时候可不写数组的范围
直接int[] a;即可
二,封装
面向对象的四大特征之一
想象你养了一只猫,你不可能让它满屋子乱跑(否则它会抓坏沙发),而是把它关进一个带操作按钮的智能笼子。这个笼子就是“封装”的体现:
- 隐藏细节:你不需要知道笼子的锁是如何工作的
- 暴露接口:你只能通过笼子外部的按钮喂食、清理
- 保护对象:猫无法直接破坏你的家具
封装的三大目的:
目的 | 说明 | 示例 |
---|---|---|
数据保护 | 防止外部代码随意修改对象内部状态 | 禁止设置负数的银行余额 |
实现细节隐藏 | 使用者无需知道内部如何实现,只需调用接口 | 手机充电时不需要知道内部电路如何工作 |
提高代码可维护性 | 内部逻辑修改时,只要接口不变,外部代码无需改动 | 优化存款计算方式,外部调用代码不受影响 |
三,private关键字
特点:
- 权限修饰符
- 可以修饰成员(成员变量和成员方法)
- 被private修饰的成员只能在本类中访问
被private修饰的成员变量要在对应的类中提供getter和setter方法来提供赋值和取值通道。
四,this关键字
this
是指向当前对象自身的引用。想象你正在写一个类的代码,每个对象诞生后,都会自动携带一个隐藏的指针 this
,它就像对象的"身份证",始终指向自己。
作用:区分局部变量和成员变量
- 成员变量:定义在类中方法外的变量
- 局部变量:定义在方法内的变量
就近原则:如果成员变量和局部变量同名,那么调用的时候离哪个变量近调用哪个。
本质:代表方法调用者的地址值
public class Demo{
int a=10;
public void method(){
int a=20;
System.out.println(a);//就近原则,a=20
System.out.println(this.a);//this关键字a=10
}
}
五,构造方法
5.1 介绍
构造方法也叫构造器,构造函数
作用:在创建对象的时候给成员变量赋值
格式:
public class Student{
//构造方法
修饰符 类名(参数){
方法体
}
}
//如果没有参数就是空参构造
//有参数就是有参构造,并且构造方法必须和类名一致,构造方法没有返回值不能用return
//创建对象的时候构造方法由虚拟机调用,不能手动调用
//创建一次对象就调用一次构造方法
任何类定义出来,默认带了无参构造,可以不写,一旦定义了有参构造,无参构造就没有了,这个时候需要自己手动写
5.2 构造方法调用链,this() 的用法
在构造方法中通过 this()
调用本类其他构造方法,必须放在第一行。
public class Rectangle {
private int width;
private int height;
private String color;
// 全参数构造方法
public Rectangle(int w, int h, String c) {
this.width = w > 0 ? w : 1;
this.height = h > 0 ? h : 1;
this.color = c;
}
// 调用全参构造设置默认颜色
public Rectangle(int w, int h) {
this(w, h, "Black"); // 必须作为第一行
}
// 调用双参构造设置默认尺寸
public Rectangle() {
this(10, 10); // 再次调用双参构造
}
}
六,标准的javabean类
- 类名要见名知意
- 成员变量要用private修饰
- 至少提供2种构造方法
- 无参构造方法
- 有参构造方法
- 成员方法
- 提供每一个成员变量对应的Get和Set方法
- 提供类的行为
七,static关键字
7.1 介绍
static定义的变量,方法会存储在堆空间中的静态区,不跟随new 对象创建在堆内存这个地址中。之后创建多个对象,这些对象中static修饰的成员都是引用同一个静态区中的值。
static
表示类级别的成员(与对象实例无关),它在类加载时初始化,整个程序生命周期内只存在一份。就像公司里的公共打印机(static资源),所有人共享使用,而不是每人单独配一台。
7.2 静态变量
static修饰的成员变量叫静态变量
特点:
- 被该类所有对象共享
- 不属于对象,属于类
- 随着类的加载而加载,优先于对象
- 调用方式:
类名.静态变量
也可以对象名.静态变量
(不推荐)
7.3 静态方法
static修饰的成员方法叫静态方法
特点:
- 多用在测试类和工具类中
- javabean类中很少使用它
- 调用方式
类名.静态方法名()
也可以对象名字.静态方法名()
不推荐
工具类:帮助我们做一些事情,但不描述任何事物的类
javabean类:描述一类事务的类,比如Student,Dog等
测试类:用来检查其他类是否书写正确,带main方法的类,是程序的入口
工具类要求:
- 见名知意
- 带私有化构造方法(用private修饰构造方法)
- 方法是用static修饰的静态方法
7.4 static注意事项
- 静态方法只能访问静态的内容!
- 非静态方法可以访问静态变量或静态方法,也可以访问非静态变量和非静态方法
- 静态方法中是没有this关键字的!
7.5 静态 vs 非静态核心对比表
特性 | 静态成员 | 非静态成员(实例成员) |
---|---|---|
内存分配 | 类加载时分配,仅一份 | 对象创建时分配,每个对象独立 |
访问方式 | 类名.成员 |
对象.成员 |
生命周期 | 与类共存亡 | 与对象共存亡 |
可访问范围 | 只能访问静态成员 | 可访问静态和实例成员 |
this关键字 | 不可用 | 可用 |
典型应用 | 工具方法、全局配置、单例模式 | 对象特有属性/行为 |
八,继承
8.1 介绍
继承(Inheritance) 是面向对象编程中实现代码复用的核心机制。就像孩子会继承父母的特征,子类(派生类)可以自动获得父类(基类)的属性和方法,同时还能发展自己的独特功能。
继承面向对象四大特征之一,继承就是类之间的父子关系,用extends
关键字来让类与类之间建立继承关系。
public Student extends Person{}
Student成为子类(派生类),Person成为父类(基类或超类)
好处:
-
可以把多个子类中重复的代码抽取到父类中,提高代码复用性
-
子类可以在父类的继承上,增加其他功能,使子类更加强大
当类与类之间,存在相同(共性)的内容,并满足子类是父类中的一种,就可以考虑使用继承来优化代码
特点
- java中只支持单继承,不支持多继承,但支持多层继承
- 每一个类都直接或间接继承于
Ojbect类
,即Object类是所有类的最终父类
子类能继承父类的内容:
- 不能继承父类的构造方法
- 可以继承父类的全部成员变量(包括private修饰的,可以继承不能调用)
- 只能继承父类中非私有的成员方法
继承中成员变量的访问特点:满足就近原则
8.2 Super关键字
super关键字,调用成员的时候使用父类的成员
调用方法:super.变量名
,super.方法名()
public class Fu{
String name="Fu";
}
public class Zi extends Fu{
String name="Zi";
public void ziShow(){
String name="A";
//先找局部变量,局部变量没有再找子类中的成员变量,子类找不到再去父类找
//局部--》子类--》父类
System.out.println(name);//A
System.out.println(this.name);//Zi
System.out.println(super.name);//Fu
}
}
8.3 方法重写
方法的重写:当父类的方法不能满足子类现在的需求,需要进行方法重写
- 书写格式:在继承体系中,子类出现了和父类中一模一样的方法说明,我们就称子类这个方法是重写方法
- 在重新方法上方加上
@Override
重新注解
方法重写注意:
-
重写方法的名字,形参列表必须和父类一致
-
子类重写方法时,访问权限子类必须大于父类
-
子类重写方法时,返回值类型子类必须小于父类、
-
私有方法,静态方法不能重写
8.4 继承中构造方法的特点
-
父类中构造方法不会被子类继承
-
子类中所有的构造方法默认先访问父类中的无参构造再执行自己
因为子类在初始化的时候,可能会使用到父类中的数据,如果父类没有完成初始化,子类就无法使用父类的数据了。
所以子类初始化之前,一定要先调用父类构造方法先完成父类数据空间的初始化。
调用父类的无参构造:super()
这个语句会默认出现在子类无参构造的第一行,如果你只是想要父类的默认初始化就别写这玩意了。
如果想自己给父类的成员变量赋值就在子类的构造方法中第一行手写super进行调用赋值
写法:
public class Fu{
String name;
int age;
public Fu(){
System.out.println("父类的无参构造");
}
public Fu(String name,int age){
this.name=name;
this.age=age;
}
}
public class Zi extends Fu{
public Zi(){
super();
System.out.println("子类中的无参构造");
}
public Zi(String name,int age){
super(name,age);//这样写就可以指定参数给父类初始化了
}
}
8.5 总结
-
this代表一个变量,表示当前方法调用者的地址值
-
super代表父类的存储空间
关键字 | 访问成员变量 | 访问成员方法 | 访问构造方法 |
---|---|---|---|
this | this.成员方法 访问的是本类成员变量 | this.成员方法(参数列表) 访问的是本类成员方法 | this(参数列表) 访问的是本类构造方法 |
super | super.成员方法 访问的是父类成员变量 | super.成员方法(参数列表) 问的是父类成员方法 | super(参数列表) 问的是父类构造方法 |
九,多态
java的四大特性之一多态,多态就是同种类型的对象表现出不同形态。
多态的表现形式父类类型 对象名=new 子类对象()
9.1 多态的前提
- 有父类引用指向子类对象
Fu f=new Zi();
- 有继承和实现关系
- 有方法的重写
使用多态可以在方法中使用父类类型作为参数,接收所有的子类对象,并且根据传递的子类对象不同使用子类重写的方法
9.2 多态调用成员的特点
- 变量调用:编译看左边,运行也看左边
- 方法调用:编译看左边,运行看右边
当子类和父类中出现同名的成员变量的时候调用规则:
编译看左边:javac编译代码的时候,会看左边的父类中有没有这个变量,如果没有就编译失败,如果有就编译成功。
运行也看左边:java运行代码的时候,实际获取的是左边父类成员变量的值
当子类和父类中出现同名方法调用规则:
编译看左边:javac编译代码的时候,会看左边父类中有没有这个成员方法,如果没有就编译失败,如果有就编译成功
运行看右边:java运行代码的时候,实际上运行的是子类中的方法
理解
-
Animal a=new Dog()
和Dog d=new Dog()
子类继承了父类的这个name成员变量,导致存在俩个name
-
a因为是Animal父类类型的所以调用的是父类的name
-
d因为是Dog子类类型的所以调用的是子类中的name
-
-
成员方法:如果子类对方法进行了重写,那么在虚方法表中是会把父类的方法覆盖了的所以调用的是子类重写的方法
9.3 多态的优势
- 在多态形势下,右边对象可以实现解耦合,便于扩展和维护
- 定义方法的时候,使用父类类型作为参数,可以接收所有子类对象,体现多态的扩展性与便利性
9.4 多态的弊端
-
不能调用子类特有的功能(子类新写出来的父类中没有的方法)
因为在编译的时候会先检查左边父类有没有这个方法,父类没有故报错
解决方法:变回子类类型(强制类型转换!)
在强转的时候与真实类型不一致会导致报错
解决方法:
instanceof
关键字:判断变量的类型是否为指定类型a instanceof b
如果a是b的类型就返回truejkd14新特性
//a如果是Dog类型就强转为Dog类型并且转换后变量名为d,如果不是则不转换 if(a instanceof Dog d){ d.lookhome(); }else{ System.out.println("没有这个类型无法转换"); }
十,包
包就是文件夹,用来管理不同功能的java类,方便后期代码维护
import
关键字,导入其他包用来使用其他包的类
- 使用同一个包中的其他类,不需要导入包
- 使用
java.lang
包中的类,不需要导包 - 其他情况都需要导包
- 如果同时使用俩个包,并且来个包有同名类,需要使用全类名:
包名.类名
十一,final关键字
用final修饰的方法:代表该方法是最终方法,不能被重写
用final修饰的类:代表该类是最终类,不能被继承
用final修饰的变量:叫常量,只能被赋值一次
常量:实际开发中,常量一般作为系统的配置信息,方便维护,提高可读性
常量命名规则:
- 单个单词:全部大写
- 多个单词:全部大写,单词见用下划线隔开
细节:
- final修饰的变量是基本数据类型的:那么变量存储的数据值不能改变
- final修饰的变量是引用数据类型的:那么变量存储的地址值不能改变,对象内部可以改变
- 常量一般的写法
private static final 常量名=值
十二,权限修饰符
- 权限修饰符:用来控制一个成员能够被访问的范围
- 可以修饰成员变量,方法,构造方法,内部类
修饰符 | 同一个类中 | 同一个包中 | 不同包下的子类 | 不同包 |
---|---|---|---|---|
private | √ | |||
空着不写 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
十三,代码块
代码块:用大括号括起来的部分
13.1 局部代码块
局部代码块:写在方法里面的一对单独的大括号
作用:因为变量作用范围是在所属大括号内,这玩意用来节约内存空间,现在基本上用不到了!
public class Main {
public static void main(String[] args) {
{
int a=10;
}
System.out.println(a);//此时会报错,因为是局部代码块,变量a作用范围只在大括号内
}
}
13.2 构造代码块
作用:写在成员位置中,会优先于构造方法执行。一般用来把多个构造方法中重复的部分写在代码块中。
import lombok.*;
public class Student {
@Getter@Setter
private int age;
@Getter@Setter
private String name;
{
System.out.println("多个构造方法中重复的部分");
}
public Student(){
}
public Student(String name,int age){
this.age=age;
this.name=name;
}
}
13.3 静态代码块
用static修饰的代码块,随着类的加载而加载,并且自动触发,只执行一次!
在类加载的时候,给类中数据初始化的时候使用
格式:static{}
随着类的加载而加载,只执行一次!
创建多个同一个类的对象的时候,静态代码块只会第一次创建对象的时候执行,并且只执行一次
十四,抽象类和抽象方法
抽象是面向对象四大特性之一:抽象是设计思想,从具体中提取共性,定义规范。
14.1,生活类比:设计图纸
想象你要造一辆车:
- 工程师会先画设计图纸,定义必须有的部件(如发动机、方向盘)
- 但图纸不具体实现这些部件(比如不指定发动机是V6还是电动)
- 不同厂商根据这张图纸生产具体车型(燃油车、电动车)
映射到代码
- 抽象类 = 设计图纸(定义必须有的东西,但不具体实现)
- 抽象方法 = 图纸上的强制要求(如"必须要有发动机")
- 具体子类 = 实际生产的车型(实现具体要求)
14.2,什么是抽象方法?
定义
- 只有方法声明,没有方法体(没有大括号里的具体实现)
- 用
abstract
关键字标记 - 强制要求子类必须实现该方法
示例:
// 抽象方法示例
abstract void startEngine(); // 没有大括号,直接分号结束
14.3,什么是抽象类?
定义
- 用
abstract
修饰的类 - 可以包含抽象方法(普通类不能有抽象方法)
- 不能被实例化(不能直接
new AbstractClass()
) - 主要作用是为子类定义规范
代码结构:
abstract class Vehicle { // 抽象类
// 抽象方法(没有实现)
abstract void startEngine();
// 普通方法(有实现)
void turnOnHeadlight() {
System.out.println("打开大灯");
}
}
14.4 注意事项
- 抽象类不能实例化
- 抽象类中不一定有抽象方法(抽象类里面也可以有普通方法),但是有抽象方法的类一定是抽象类
- 抽象类可以有构造方法
- 抽象类的子类:要么重写抽象类中全部的抽象方法,要么子类也是一个抽象类
抽象类和抽象方法的意义:强制子类必须按照规定的格式书写!方便调用
14.5 使用场景
- 需要定义通用行为模板,但某些行为必须由子类自定义
- 多个类有共同特征,但部分功能实现方式不同
- 需要限制直接实例化(比如"动物"不应该被直接创建,但"猫"、"狗"可以)
典型案例
java.io.InputStream
(Java IO库中的抽象类)- GUI编程中的
javax.swing.JComponent
- 游戏开发中的
GameCharacter
基类
十四,接口
14.1 介绍
接口是一种规则,是对行为的抽象。想让哪个类有一个行为就让这个类实现对应的接口即可
抽象类更多是用在父类中,接口更多的用于行为。
接口的多态:当一个方法的参数是接口时,可以传递接口中所有的实现类的对象!
14.2 定义接口
定义接口:public interface 接口名{}
接口的特点:
- 接口不能实例化
- 接口和类之间是实现关系,通过implements关键字表示:
public class 类名 implements 接口名{}
- 接口的子类(实现类):要么重写接口中所有抽象方法,要么是抽象类
- 接口和类的实现关系,可以是单实现,可以是多实现:
public class 类名 implements 接口名1,接口名2{}
- 接口还可以在继承一个类的同时实现多个接口:
public class 类名 extends 父类 implements 接口1,接口2{}
接口中成员的特点:
- 接口中的变量只能是常量,默认修饰符
public static final
- 接口中没有构造方法
- jkd7之前接口中的成员方法只能为抽象方法,默认修饰符
public abstract
接口和类之间的关系
-
类和类之间
继承关系,只能单继承,不能多继承,但是可以多层继承
-
类和接口关系
实现关系,可以单实现,也可以多实现,还可与在继承一个类的同时实现多个接口
-
接口和接口间的关系
继承关系,可以单继承,也可以多继承
如果此时实现类是实现了最下面的子接口,那么要重写接口继承体系全部的抽象方法
14.3 JDK8以后接口中新增的方法
14.3.1 默认方法
允许接口中定义默认方法,需要使用关键字default
修饰
作用:解决接口升级问题
-
当接口新增了多个方法的时候,实现类需要实现接口中新增的方法。
此时在接口中写了默认方法,可以不用在每个实现类中实现对应的方法
当我们要用到某个方法的时候,在实现类中重写即可
想在调用接口中默认方法直接在实现类中调用即可
-
接口中默认方法的定义格式
public default 返回值类型 方法名(参数列){方法体}
-
注意事项:
默认方法不是抽象方法,不强制被重写,但是如果被重写,重写的时候去掉default关键字
public可以不写,default必须写
如果实现了多个接口,多个接口中存在名字相同的默认方法,子类就必须对该方法重写
14.3.2 静态方法
接口中定义静态方法,需要用static
关键字修饰
-
接口中静态方法的定义格式
public static 返回值类型 方法名(参数列){}
-
静态方法只能用接口名调用,静态方法不能被重写。
14.3.3 私有方法
-
定义格式:
私有方法
private 返回值类型 方法名(参数列){方法体}
静态的私有方法
private static 返回值类型 方法名(参数列){方法体}
-
私有方法是提供给接口中默认方法抽取重复部分代码使用的
-
静态私有方法是提供给接口中静态方法抽取重复部分代码使用的
-
JDK9以后添加的私有方法
十五,内部类
15.1 介绍
内部类是类的五大成员之一
类的五大成员:属性,方法,构造方法,代码块,内部类
在一个类中再定义一个类,这个类就叫内部类
定义内部类的时候遵守:
- 内部类表示的事物是外部类的一部分
- 内部类单独出现没有任何意义
- 比如说车和引擎,车就是外部类,引擎就是内部类
内部类访问特点:
- 内部类可以直接访问外部类的成员,包括私有的
- 外部类要访问内部类的成员,必须创建对象
内部类的作用:
- 逻辑分组:如果类A只被类B使用,可以将A放在B内部,避免污染全局命名空间。
- 访问外部类成员:内部类可以直接访问外部类的所有成员(包括私有成员)。
- 实现多重继承:通过内部类间接实现类似多重继承的效果(Java不支持多继承,但可以内部类模拟)。
- 隐藏实现细节:将内部类设为私有,对外完全隐藏。
15.2 成员内部类
定义:直接定义在外部类的成员位置(类似类的成员变量)。
特点:
- 必须依附外部类对象存在(不能独立存在)。
- 可以访问外部类的所有成员(包括私有)。
- 成员内部类可以被一些修饰符修饰:private,默认,protected,public等
public class Outer {
private int outerField = 10;
// 成员内部类
class Inner {
void print() {
System.out.println("访问外部类的私有字段: " + outerField); // 直接访问外部类私有成员
}
}
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // 必须通过外部类实例创建内部类
inner.print(); // 输出: 访问外部类的私有字段: 10
}
}
15.3 静态内部类
定义:用 static
修饰的内部类。
特点:
- 不依赖外部类实例(可直接创建)。
- 不能直接访问外部类的非静态成员(只能访问静态成员)。
public class Outer {
private static int staticOuterField = 20;
private int instanceOuterField = 30;
static class StaticInner {
void print() {
System.out.println("访问外部类的静态字段: " + staticOuterField);
// System.out.println(instanceOuterField); // 错误!不能访问非静态成员
}
}
public static void main(String[] args) {
Outer.StaticInner inner = new Outer.StaticInner(); // 直接创建,无需外部类实例
inner.print(); // 输出: 访问外部类的静态字段: 20
}
}
15.4 局部内部类
定义:定义在方法或代码块内部的类。
特点:
- 仅在方法或代码块内可见。
- 可以访问外部类的成员,但若访问方法的局部变量,该变量必须声明为
final
或等效 final(Java 8+)。
public class Outer {
private int outerField = 40;
public void someMethod() {
final int localVar = 50; // 局部变量必须final或等效final
class LocalInner {
void print() {
System.out.println("外部类字段: " + outerField);
System.out.println("局部变量: " + localVar);
}
}
LocalInner inner = new LocalInner();
inner.print(); // 输出: 外部类字段: 40 局部变量: 50
}
public static void main(String[] args) {
new Outer().someMethod();
}
}
15.5 匿名内部类(重点)
定义:没有名字的局部内部类,通常用于快速实现接口或抽象类。
- 匿名内部类的本质就是隐藏了名字的内部类
特点:
- 简洁但可读性差(适合一次性使用)。
- 常见于GUI事件处理(如按钮点击)或Java 8之前的Lambda替代。
匿名内部类创建格式:
new 类名或接口名(){
重写方法;//这里重写的是new对应的类or接口的方法
};//这里有分号!
//真正的类是
{
重写方法;
}
//new 类名或接口名()是创建这个类的对象
举例
public interface Greeting {
void greet();
}
public class Demo {
public static void main(String[] args) {
// 匿名内部类:直接实现接口
Greeting greeting = new Greeting() {
@Override
public void greet() {
System.out.println("你好,匿名内部类!");
}
};
greeting.greet(); // 输出: 你好,匿名内部类!
}
}
-
如果new的是接口那么是实现关系
-
如果new的是类那么是继承关系
应用场景:
public class Main {
public static void main(String[] args) {
//如果我想调用method方法,那么需要创建一个dog子类继承Animal类
//通过dog类来传递数据给method
//但是如果我只用dog类一次,还需要单独定义一个类就很麻烦!
//现在可以通过内部类直接写一个匿名实现类传递数据给mthod
method(
new Animal(){}//将这个当作Animal的子类对象!
);
}
public static void method(Animal a){
}
}
//当方法的参数是接口or类的时候
//以接口为例:可以传递这个接口的实现类对象
//如果实现类只用一次,就用匿名内部类简化代码
15.6 内部类的底层原理
成员内部类和匿名内部类在编译后会生成独立的 .class
文件,命名格式为:
- 成员内部类:
外部类$内部类.class
(如Outer$Inner.class
)。 - 匿名内部类:
外部类$1.class
(按顺序编号)。
内部类访问外部类成员时,实际上是通过持有外部类对象的引用(编译器自动处理)。
15.7 用内部类实现迭代器
public class MyList {
private int[] data = {1, 2, 3, 4, 5};
// 通过内部类实现迭代器(隐藏实现细节)
public Iterator getIterator() {
return new ListIterator();
}
// 成员内部类实现Iterator接口
private class ListIterator implements Iterator {
private int index = 0;
@Override
public boolean hasNext() {
return index < data.length;
}
@Override
public Integer next() {
return data[index++];
}
}
public static void main(String[] args) {
MyList list = new MyList();
Iterator it = list.getIterator();
while (it.hasNext()) {
System.out.print(it.next() + " "); // 输出: 1 2 3 4 5
}
}
}
十六,Lambda表达式
-
Lambda表达式最基础的特点就是优化匿名内部类的书写
在了解Lambda表达式之前需要知道函数式编程
函数式编程:忽略面向对象复杂的语法,强调做什么,而不是谁去做,即更加关注方法体中的逻辑,而不是对象。
-
Lambda表达式是JDK8开始后的一种新语法
书写格式:
()->{ }
()
对应方法的形参->
固定格式{}
对应着方法的方法体
-
举例:
//匿名内部类写法 Arrays.sort(arr,new Comparator<Integer>(){ @Override public int compare(Integer o1,Integer o2){ return o1-o2; } }); //Lambda简化后写法 Arrays.sort(arr,(Integer o1,Integer o2)->{ return o1-o2; });
小细节:
-
Lambda表达式可以用来简化匿名内部类的书写
-
Lambda表达式只能简化函数式接口的匿名内部类的写法
函数式接口:有且仅有一个抽象方法的接口叫函数式接口,接口上方可以加上
@FunctionalInterface
注解加上注解之后如果不满足函数式接口条件会报错
Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码,它可以写出更简洁,更灵活的代码。
作为一种更紧凑的代码风格,使Java语言表达能力得到提升。
-
Lambda表达式省略写法
写法规则:
- 参数类型可以不写
- 只有一个参数,小括号可以不写
- 如果Lambda表达式的方法体只有一行,大括号,分号,return可以不写,需要同时不写!
省略核心:可推导,可省略
public class Main {
public static void main(String[] args){
Integer[] arr=new Integer[]{5,1,2,7,8,3,4};
Arrays.sort(arr, (o1,o2)->o2-o1);//究极简化写法
}
}
了解即可,idea可以自动将匿名内部类转化为lambda表达式的。
十七,枚举类
17.1 介绍
枚举(Enum)是Java中一种特殊的类,用于定义一组固定的常量。它比传统的常量定义(如 public static final
)更安全、更灵活。
17.2 为什么需要枚举?
假设你需要表示“星期几”,用传统方式可能会这样写:
public class Day {
public static final int MONDAY = 1;
public static final int TUESDAY = 2;
// ...其他天
}
问题:
- 类型不安全:
int
类型可能被传入非法值(比如Day.MONDAY + 100
)。 - 可读性差:打印时只能看到数字,无法明确含义。
- 无法扩展:无法为每个常量添加额外属性或方法。
枚举的解决方案:
public enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
17.3 枚举的核心特性
-
枚举是类
枚举本质是一个类(继承自
java.lang.Enum
),编译后会生成.class
文件。每个枚举常量都是该类的一个实例(单例)。// 编译后:Day.class public enum Day { // 等同于调用构造函数:public static final Day MONDAY = new Day(); MONDAY, TUESDAY, // ... }
-
可以添加属性和方法
枚举可以像普通类一样定义字段、方法和构造函数。
public enum Planet { // 枚举常量 + 构造参数 MERCURY(3.303e+23, 2.4397e6), VENUS(4.869e+24, 6.0518e6); private final double mass; // 质量(kg) private final double radius; // 半径(m) // 构造方法(必须私有) private Planet(double mass, double radius) { this.mass = mass; this.radius = radius; } public double getMass() { return mass; } public double getRadius() { return radius; } }
-
可以实现接口
枚举可以实现接口,为每个常量提供不同的行为。
public interface Operation { double apply(double x, double y); } public enum BasicOperation implements Operation { PLUS { public double apply(double x, double y) { return x + y; } }, MINUS { public double apply(double x, double y) { return x - y; } }; }
-
可以在switch中使用
枚举与
switch
语句天然契合。Day day = Day.MONDAY; switch (day) { case MONDAY -> System.out.println("工作日开始"); case SATURDAY, SUNDAY -> System.out.println("休息日"); }
17.4 枚举的常用方法
枚举继承自 Enum
类,自带以下方法:
方法 | 作用 |
---|---|
values() |
返回所有枚举常量(如 Day.values() ) |
valueOf(String) |
根据名称返回枚举常量(如 Day.valueOf("MONDAY") ) |
name() |
返回枚举常量名称(如 MONDAY.name() → "MONDAY") |
ordinal() |
返回枚举常量的序号(从0开始) |
17.5 枚举的实际应用场景
-
. 状态机(如订单状态)
public enum OrderStatus { CREATED { @Override public void next(Order order) { order.setStatus(PAID); } }, PAID { @Override public void next(Order order) { order.setStatus(SHIPPED); } }, SHIPPED { @Override public void next(Order order) { order.setStatus(DELIVERED); } }; public abstract void next(Order order); // 抽象方法,每个状态必须实现 }
-
单例模式(线程安全)
```java
public enum Singleton {
INSTANCE; // 单例实例
public void doSomething() {
System.out.println("单例方法");
}
}
// 使用:Singleton.INSTANCE.doSomething();
```
-
错误码
public enum ErrorCode { SUCCESS(0, "成功"), PARAM_ERROR(1001, "参数错误"), SYSTEM_ERROR(5001, "系统错误"); private final int code; private final String message; ErrorCode(int code, String message) { this.code = code; this.message = message; } // Getter方法 }
17.6 枚举的底层原理
- 每个枚举常量都是
public static final
的实例。 - 枚举的构造方法默认是
private
,无法通过new
创建对象。 - 枚举在类加载时初始化,保证线程安全。
17.7 枚举 vs 常量类
对比维度 | 枚举 | 常量类(public static final) |
---|---|---|
类型安全 | 是(强类型) | 否(通常是int/String) |
可扩展性 | 可添加方法、属性 | 无法扩展 |
序列化 | 自动支持,安全 | 需要自行处理 |
switch支持 | 天然支持 | 仅支持基本类型 |
单例模式 | 天然线程安全 | 需额外处理(如双重检查锁) |
17.8 注意事项
- 不要滥用枚举:枚举比常量类占用更多内存(每个常量都是对象)。
- 避免在枚举中定义可变字段:枚举常量本质是单例,应保持不可变。
- 优先使用枚举代替常量:在需要类型安全、可扩展性时选择枚举。
17.9 代码示例:枚举实现策略模式
public enum FileFormat {
CSV {
@Override
public void export(Object data) {
System.out.println("导出CSV文件");
}
},
PDF {
@Override
public void export(Object data) {
System.out.println("导出PDF文件");
}
};
public abstract void export(Object data);
}
// 使用
FileFormat format = FileFormat.CSV;
format.export(data); // 输出:导出CSV文件
十八,泛型
18.1 泛型介绍
泛型:是JDK5中引用的特性,可以在编译阶段约束操作的数据类型,并检查。
泛型的格式:<数据类型>
注:泛型只能支持引用数据类型
-
JDK5引用泛型的原因。
如果没有给集合指定类型,默认认为所有的数据类型都是Object类型。这样在获取数据的时候,无法使用它特有的行为。
-
泛型的好处
统一了数据的类型,把运行期间的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定。
扩展:Java中的泛型是伪泛型
解释:
Java的泛型只是在编译阶段检查集合的数据类型,在数据存入集合后,数据在集合中还是Object类型。
当取出数据的时候,Java会在底层将取出的数据的数据类型强制转化为泛型限定的那个数据类型。
在编写.java文件的时候,泛型是存在的,但是当,java文件编译为.class文件的时候,泛型会消失。这个过程叫泛型的擦除。
泛型细节:
-
泛型中不能写基本数据类型
因为数据在集合中是转化为Object类型存储的,基本数据类型不能转化为Object类型
-
指定泛型的具体类型后,传递数据时,可以传入该类类型或其子类类型。
-
如果不写泛型,类型默认是Object
泛型可以写在:
- 类后面-->泛型类
- 方法上面-->泛型方法
- 接口后面-->泛型接口
18.2 泛型类
使用场景:当一个类中,某个变量的数据类型无法确定时,就可以定义带有泛型的类。
格式:
修饰符 class 类名<类型1,类型2,类型3...>{
}
public class ArrayList<E>{
//这里的E表示不确定的数据类型,只有创建该类对象时我们手动指定这个E的类型
//这里的E可以理解为变量,但不是用来记录数据的,而是记录数据类型的,可以写T,E,K,V等表示
//当我们定义了泛型类后,这个E就代表一种数据类型了!!可以和其他数据类型一样使用。
Object[] obj=new Object[10];
int size;
public boolean add(E e){
obj[size]=e;
size++;
return true;
}
public E get(int index){
return (E)obj[index];
}
@Override
public String toString() {
return Arrays.toString(obj);
}
}
//我们在创建这个类的对象的时候需要手动指定这个E的类型!
ArrayList<String> list=new ArrayList<>();//这里手动指定E为String类型了!
18.3 泛型接口
使用场景:当一个接口中数据类型不确定的时候使用
格式:
//定义单个泛型
修饰符 interface 接口名<类型>{
}
//定义多个泛型
修饰符 interface 接口名<类型1,类型2,类型3...>{
}
使用方式
//泛型接口
public interface text<E>{
}
//1.实现类给出具体的类型
public arry implements text<String>{
}
//2.实现类延续泛型接口的泛型,当创建实现类对象的时候再确定泛型类型
public arry<E> implements text<E>{
}
18.4 泛型方法
使用场景:当方法中形参类型不确定的时候,可以使用泛型
格式:
修饰符 <类型1,类型2,类型3...> 返回值类型 方法名(类型 变量名){
}
例:
public class ArrayList<E>{
public boolean add(E e){//类名后面定义的泛型
obj[size]=e;
size++;
return true;
}
}
public class ArrayList{
public <E> boolean add(E e){//在方法上面声明的泛型,这个泛型只有本方法可以使用!
obj[size]=e;
size++;
return true;
}
}
如果只是单一的一个方法不知道形参类型,建议使用泛型方法。
泛型方法的类型是当方法被调用的时候确定的!
18.5 泛型的继承和通配符
泛型不具备继承性,但数据具备继承性。
import java.util.ArrayList;
public class Main {
public static void main(String[] args){
ArrayList<Ye> list1=new ArrayList<>();
ArrayList<Fu> list2=new ArrayList<>();
ArrayList<Zi> list3=new ArrayList<>();
//泛型不具备继承性
method(list1);
//method(list2);报错,无法传入
//method(list3);报错,无法传入
//数据具备继承性
list1.add(new Ye());
list1.add(new Fu());
list1.add(new Zi());
}
public static void method(ArrayList<Ye> list){
}
}
class Ye{}
class Fu extends Ye{}
class Zi extends Fu{}
虽然上述method方法可以使用泛型方法来解决但是,利用泛型方法会存在:该方法可以接收任意的数据类型。
一般的希望:本方法虽然不确定类型,但以后希望只能传递 Ye Fu Zi这种具备继承结构的类型
解决方法:使用泛型的通配符
通配符:?
表示不确定的类型,可以进行类型的限定!
? extends E//表示可以传递E或者E的所有子类类型,上限技术
? super E//表示可以传递E或者E的所有父类类型 下限技术
? //表示可以接收所有类型
故上面问题可写出
public class Main {
public static void main(String[] args){
ArrayList<Ye> list1=new ArrayList<>();
ArrayList<Fu> list2=new ArrayList<>();
ArrayList<Zi> list3=new ArrayList<>();
method(list1);
method(list2);
method(list3);
}
public static void method1(ArrayList<? extends Ye> list){//这个表示只能接收Ye类和Ye类的子类
}
public static void method2(ArrayList<? super Zi> list){//这个表示只能接收Zi类和Zi类的父类
}
public static void method2(ArrayList<?> list){//这个表示可以接收所有数据类型
}
}
class Ye{}
class Fu extends Ye{}
class Zi extends Fu{}
应用场景:
-
如果我们在定义类,方法,接口的时候,类型无法确定,就可以定义泛型类,泛型方法,泛型接口
-
如果类型不确定,但是能找到只能传递某个继承体系中的数据,就可以使用泛型的通配符了!
通配符关键的:可以限定类的范围
-
泛型的通配符一般是用在方法中
-
通配符也可用做集合中
ArrayList<? super Number> list=new ArrayList<>();