目录
在此之前,你需要了解java基础语法:
https://blog.csdn.net/qq_44930306/article/details/155890237?spm=1011.2415.3001.5331
项目在VSCode运行需要在扩展商店搜索 Java Extension Pack → 安装
一、基础原理
1.1、java的运行机制
(1)、java程序运行的过程是怎么样的?
解答:Java文件 -->(编译为)class字节码文件 -->将结果(运行)在虚拟机上。
(2)、Java是直接运行在操作系统里的吗?解答:不是,运行在虚拟机里。
(3)、虚拟机的好处是什么?解答:可以跨平台。
(4)、为什么要跨平台?解答:写一套代码,可以在任意的操作中运行,大大降低了开发成本。
1.2、内存和内存地址
内存:软件在运行时,用来临时存储数据的
内存地址:内存中每一个小格子的编号,为了快速管理内存空间
1.3、变量和方法的内存分配
java
public static void main(String[] args) {
int a = 10;// 在栈内存中为变量 a 分配空间,存储值 10
int b = 20; // 在栈内存中为变量 b 分配空间,存储值 20
System.out.println("交换前:" + a + "," + b); // 交换前:10,20
change(a, b);
System.out.println("交换后:" + a + "," + b); // 交换后:10,20
}
public static void change(int a, int b) {
// 交换变量
int temp = a;
System.out.println(temp); // 10
a = b;
System.out.println(a); // 20
b = temp;
System.out.println(b); // 10
}
// Java 中基本数据类型(如 int)的参数传递是值传递,不是引用传递
// change 方法内部的变量交换只影响方法内部的局部变量
// 原始的 a 和 b 变量的值不会被改变,因为它们是通过值传递的
// Java中基本数据类型是值传递,无法直接修改原始变量的值
// 数组和对象是引用传递,可以修改其内容
(1)、Java虚拟机把内存分成了几个部分?
解答:栈、堆、方法区、本地方法栈、程序计数器
(2)、栈、堆、方法区的作用?解答:
栈内存=程序的主入口(main方法):开始执行是会进栈,代码执行完毕会出栈
堆内存=new关键字:在堆内存开辟空间并产生地址
方法区:存储字节码信息
(3)、基本数据类型在内存中的特点?解答:记录的是真实数据,传递的也是真实数据
(4)、引用数据类型在内存中的特点?解答:记录的是地址值,传递的也是地址值
1.4、数组的内存分配
java
public static void main(String[] args) {
// 在堆内存中创建一个包含5个整数的数组对象
// 在栈内存中为变量 arr 分配空间,存储指向堆中数组对象的引用
int[] arr = {1, 2, 3, 4, 5};
System.out.println("交换前");
printArray(arr); // 1 2 3 4 5
// 交换数组首位元素
change(arr);
System.out.println("交换后");
printArray(arr); // 5 2 3 4 1
}
public static void change(int[] arr) {
int temp = arr[0];
arr[0] = arr[arr.length - 1];
arr[arr.length - 1] = temp;
}
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
数组是引用类型:arr 变量存储的是指向堆中数组对象的引用。
方法参数传递:change 和 printArray 方法接收的是数组的引用,可以修改数组内容。
堆内存共享:所有对 arr 引用的操作都作用于同一个堆内存中的数组对象。
栈内存独立:每个方法的局部变量都在各自的栈帧中,互不影响。
二、面向对象进阶
2.1、静态变量(static)
Static:表示静态,是Java的修饰符,用来修饰(成员方法/成员变量)。
2.1.1、static修饰成员变量
特点:
(1)、叫做静态变量,被该类所有对象共享;
(2)、不属于对象,属于类;
(3)、随着类的加载而加载,优先于对象而存在。
(4)、只要赋值一次(只要有一个对象修改了静态变量的值),其他对象再次访问时就是修改之后的结果。
调用方式一:类名调用(推荐)
调用方式二:对象名调用
案例:有多个学生,最初都是小文老师教,后来有学生要申请换成大王老师
java
public class Student {
String name;
int age;
// 共享一个老师
static String teacherName;
}
java
public class Test {
public static void main(String[] args) {
Student.teacherName = "小文老师";
// 创建一个学生对象
Student s1 = new Student();
s1.name = "张三";
s1.age = 18;
// 创建第二个学生
Student s2 = new Student();
s2.name = "李四";
s2.age = 19;
s2.teacherName = "小王老师"; // 加完之后,结果里都变成了小王老师
System.out.println(s1.name + ":" + s1.age + ":" + Student.teacherName); // 张三:18:小文老师
System.out.println(s2.name + ":" + s2.age + ":" + Student.teacherName); // 李四:19:小文老师
}
2.1.2、static修饰成员方法(工具类)
static修饰的方法叫做静态方法,该方法多用在测试类和工具类中,javabean类中很少会用。
工具类 :不是用来描述一类事物的,也没有main方法,而是帮我们做一些事情的类。
要求:类名见名知意、私有化构造方法、方法定义为静态。
java
public class ArrayUitl {
// 私有化构造方法,目的:不让外界创建对象
private ArrayUitl() {
}
// 定义方法(静态)
// 1、提供一个方法printArray,用于打印数组【10,20,30,40】
public static void printArray(int[] arr) {
System.out.print("[");
for (int i = 0; i < arr.length; i++) {
if (i == arr.length - 1) {
System.out.print(arr[i] + "]");
} else {
System.out.print(arr[i] + ", ");
}
}
}
// 2、就数组平均值
public static double getAverage(int[] arr) {
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum * 1.0 / arr.length;
}
java
public class Test {
public static void main(String[] args) {
int[] nums = {10, 20, 30, 40};
ArrayUitl.printArray(nums);
double avg =ArrayUitl.getAverage(nums);
System.out.println("平均值是:" + avg); // 平均值是:25.0
}
2.1.3、注意事项
(1)、静态方法只能访问静态变量和其他的静态方法(静态只能调用静态);
(2)、**非静态方法(可以调用所有)**可以访问静态变量或者静态方法,也可以访问非静态的成员变量和成员方法;
(3)、静态方法中没有this关键字。
2.2、final关键字
final:表示最终,不可变。可以修饰变量、类、方法。
final int a=10; // final修饰的变量不能被修改
final Student stu=new Student(); // final修饰的引用不能被修改
java
public class Circle {
private double radius;
private final double PI = 3.14;
public Circle() {
}
public Circle(double radius) {
this.radius = radius;
}
public double getPI() {
return PI;
} // PI被final修饰,故而没有必要提供setPI方法
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
// 行为
public double findArea() {
return PI * radius * radius;
}
public double findPerimeter() {
return 2 * PI * radius;
}
}
java
public class Test {
public static void main(String[] args) {
Circle c1 = new Circle(1.5);
System.out.println(c1.getPI()); // 3.14
System.out.println(c1.getRadius()); // 1.5
// 获取圆的面积和周长
System.out.println(c1.findArea()); // 7.0649999999999995
System.out.println(c1.findPerimeter()); // 9.42
}
}
2.3、枚举(enum)
枚举是一个特殊的Javabean类,这个类的对象是有限个。
使用场景:订单状态、月份、星期、游戏角色职业、会议室预约状态、设备状态。
注意事项:(1)每一个枚举项,都是该枚举类的对象,每一个对象都是通过构造方法创建出来的
(2)枚举项在底层其实就是常量,默认用public static final修饰
(3)枚举类的第一行上必须是枚举项,枚举项之间用逗号隔开,以分号作为结尾
(4)枚举类的构造方法必须是private修饰,不让外界创建本类的对象
(5)编译器会给枚举类新增两个默认存在的方法:values()、valueOf()
java
public enum OrderState {
// 电商订单有以下6种状态
PAYMENT_PENDING("待支付"),
PROCESSING("处理中"),
SHIPPED("已发货"),
OUT_FOR_DELIVERY("配送中"),
DELIVERED("已送达"),
CANCELED("已取消");
private String name;
private OrderState(String name) {
System.out.println("每次都会执行");
this.name = name;
}
public String getName() {
return name;
}
}
java
public class Test {
public static void main(String[] args) {
OrderState o1 = OrderState.OUT_FOR_DELIVERY;
System.out.println(o1.getName());
switch (o1) {
case PAYMENT_PENDING -> System.out.println("待支付状态");
case PROCESSING -> System.out.println("订单在处理中");
case SHIPPED -> System.out.println("订单已发货");
case OUT_FOR_DELIVERY -> System.out.println("订单正在配送中"); // 执行这里
case DELIVERED -> System.out.println("订单已送达");
case CANCELED -> System.out.println("订单已取消");
}
// values() 获取本类所有的枚举项
OrderState[] values = OrderState.values();
for (OrderState value : values) {
System.out.println(value.getName());
}
// valueOf() 获取指定名称的枚举项
System.out.println(OrderState.valueOf("SHIPPED"));
}
}
2.4、封装
1、含义:用类设计对象处理某一个事物的数据时,应该要把处理的数据以及处理这些数据的方法,设计到一个对象中去,类就是一种封装 。
2、设计规范:合理隐藏、合理暴露。
3、代码层面如何对对象的成员进行公开或隐藏 ?
隐藏成员:使用private关键字修饰成员变量,就只能在本类中被直接访问,其他任何地方不能直接访问;
公开成员:使用public修饰的get和set方法合理暴露成员变量的取值和赋值。4、参考案例:score在Test里被赋值负数,所以在get/set里进行判断再暴露出去。
2.5、实体类(javabean类)
要求1:类中的成员变量全部私有,并提供public修饰的getter/setter方法
要求2:类中需要提供一个无参构造器。有参构造器可选
它仅仅只是一个用来保存数据的java类,可以用它创建对象,保存某个事物的数据,例如以上案例的Student、Circle类等。
三、面向对象高级
3.1、继承(extends)
含义:面向对象三大特征之一,可以让类跟类之间产生父子关系。
作用---操作:多个子类中重复的代码抽取到父类,子类可以直接使用。
作用---好处:减少代码冗余,提高代码复用性。
语法格式 :public class 子类 extends 父类 {...}
继承后子类的好处 ?好处1:子类可以得到父类的属性和行为,子类可以使用
好处2:子类可以在父类的基础上,增加其他功能,使子类更强大
3.1.1、继承的特点
(1)、java只支持单继承,不支持多继承,但支持多层继承
单继承:一个子类只能继承一个父类
不能多继承:子类不能同时继承多个父类
多层继承:子-父-爷
(2)、直接继承的父类叫做直接父类,间接继承的爷爷叫做间接父类
(3)、Java中的顶级父类为Object,每一个类都直接或者间接的继承于Object
3.1.2、继承中成员变量的特点
书写规则:把多个子类中相同的属性抽取到父类当中
遵守就近原则--先在局部位置找,本类成员位置找,父类成员位置找,逐级往上。如果出现了重名的成员变量怎么办?
解答:找子类的变量用this.xx,找父类的变量用super.xx
java
public class Test {
public static void main(String[] args) {
Zi zi = new Zi();
zi.Show();
}
}
class Fu {
String name = "父类name";
}
class Zi extends Fu {
String name = "子类name";
public void Show() {
String name = "静态方法";
System.out.println(name); // 方法里的name
System.out.println(this.name); // 子类name
System.out.println(super.name); // 父类name
}
}
3.1.3、继承中成员方法的特点
书写规则:把多个子类中共性的成员方法抽取到父类当中
调用规则:遵守就近原则
this调用**:先访问本类,再访问父类**
super调用**:直接访问父类**
java
public class Test {
public static void main(String[] args) {
// 情况一:外界创建子类的对象,并调用方法
Zi1 z = new Zi1();
z.drink();
z.eat();
}
}
class Fu1 {
public void eat() {
System.out.println("吃吃吃");
}
public void drink() {
System.out.println("喝水");
}
}
class Zi1 extends Fu1 {
// 情况二:本类中,调用其他方法
public void lunch(){
// 这三种都能拿到父类的方法
drink();
this.eat();
super.eat();
}
}
3.1.4、继承中构造方法的特点
(1)、父类中的构造方法不会被子类继承,只能通过super关键字调用
(2)、如果子类的构造方法不写super,JVM也会加一个默认的super(),先访问父类的无参构造
(3)、如果想要访问父类有参构造,必须手动书写super(参数)
(4)、Super()必须卸载构造方法第一行,先执行父类的构造,再执行子类的构造

3.1.5、总结this和super关键字
this内存角度:表示当前方法调用者的地址值
this代码角度:用它直接调用本类成员(成员变量、成员方法、构造方法)
super关键字:代表使用父类中的内容

3.1.6、方法的重写(@Override)
方法重写**:在子类中,把父类的方法再写一遍,方法申明保持一致**。
使用场景:如果父类的方法不能满足子类的要求了,子类中可以把该方法再重写一遍
注意事项(要求):
(1)、重写方法的名称、形参列表必须与父类中一致,方法体按照实际需求书写
(2)、重写的方法申明与父类保持一致即可
(3)、final修饰类为最终类 ,里面所有的方法不能被重写
(4)、private私有方法、static静态方法、final最终方法不能被重写
案例:模拟手机功能升级--第一代手机:只能打电话、第二代手机:打电话、发短信、第三代手机:打电话升级为视频通话、发短信、玩游戏。

3.1.7、带有继承结构的标准javabean类
参考以下案例:
第一步:利用画图法解决
第二步:分类
第三步:抽取共性的内容不断往上抽取(从下往上画)
第四步:写代码(从上往下写)

java
public class Express {
protected String sender;
protected int weight;
protected String receiver;
public Express(String sender, int weight, String receiver) {
this.sender = sender;
this.weight = weight;
this.receiver = receiver;
}
// 计算基础运费
public double calculateFee() {
return weight * 10; // 每公斤10元
}
}
java
public class Test {
public static void main(String[] args) {
Express e = new Express("EM001345", 5, "王五收");
SameCityExpres s = new SameCityExpres("SM003476", 8, "王五收");
AirExpress a = new AirExpress("AM007483", 20, "王五收");
System.out.println(e.sender + " " + e.weight + " " + e.receiver);
// 计算费用
System.out.println("基础快递费用:" + e.calculateFee()); // 基础快递费用:50.0=5*10
System.out.println("同城快递费用:" + s.calculateFee()); // 基础快递费用:90.0=8*10+10
System.out.println("异地空运费用:" + a.calculateFee()); // 基础快递费用:215.0=20*10+15
}
}
3.1.8、子类到底能继承父类的哪些内容
(1)、构造方法 :不能被子类继承,可以利用super关键字调用
(2)、成员变量 :可以被子类继承,private私有的也可以,但是私有的无法直接调用
(3)、成员方法 :虚方法(普通的成员方法,非final、非static、非private修饰的方法)可以被继承
(4)、final修饰的最终方法不能被继承,可以被调用;
(5)、static修饰的静态方法不能被继承,可以被调用;
(6)、private修饰的私有方法不能被继承,不能被调用
方法重写:子类替换虚方法表里面方法的地址值
3.2、多态
多态是在继承/实现情况下的一种现象,表现为:对象多态、行为多态。
代码体现:
People p1=new Student();
p1.run();
People p2=new Teacher();
p2.run();
前提 :有继承/实现关系;存在父类引用子类对象;存在方法重写
3.2.1、多态好处与存在的问题
好处:
(1)在多态形式下,右边对象是解耦合的,更便于扩展和维护
(2)定义方法时,使用父类类型的形参,可以接收一切子类对象,扩展性更强、更便利
存在问题:
多态下不能使用子类的独有功能,需要强制类型转换
3.2.2、多态下的类型转换
**自动类型转换:****父类 变量名=new 子类();****例如:**People p = new Teacher();
**强制类型转换:****子类 变量名=(子类) 父类变量;****例如:**Teacher t = (Teacher)p;它们可以把对象转换成真正的类型,从而解决了多态下不能调用子类独有方法的问题
注意事项:(1)存在继承/实现关系就可以在编译阶段进行强制类型转换,编译阶段不会报错
(2)运行时,如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常(ClassCastException)的错误
(3)强转前,建议使用instanceof关键字,判断当前对象的真实类型,再进行强转 例如:p instanceof Student
3.2.3、实战--支付模块(lombok技术)

java
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
// 1、创建卡片类,以便创建金卡或者银卡对象,封装车主数据
// 2、定义一个卡片父类:Card,定义金卡和银卡的共同属性和方法
// 3、定义一个金卡类,继承卡片类,金卡必须重写消费方法(8折优惠),可打印洗车票
GoldCard goldCard = new GoldCard("A4394MM", "张三", "18234564321", 5000);
// 4、定义一个银卡类,继承卡片类,银卡必须重写消费方法(9折优惠)
SilverCard silverCard = new SilverCard("A4876MM", "李四", "18232164321", 2000);
// 5、办张金卡:创建对象,交给一个独立的业务(支付机)来完成:存款、消费
// 6、办张银卡:创建对象,交给一个独立的业务(支付机)来完成:存款、消费
pay(goldCard);
pay(silverCard);
}
public static void pay(Card card) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入您当前消费的金额:");
double money = sc.nextDouble();
card.consume(money);
}
}
java
public class GoldCard extends Card {
public GoldCard(String cardId, String name, String phone, double money) {
super(cardId, name, phone, money); // 4 参
}
@Override
public void consume(double money) {
double realPay = money * 0.8;
if (realPay <= getMoney()) {
setMoney(getMoney() - realPay);
System.out.println("消费成功,余额为:" + getMoney());
if (realPay >= 200) {
printWashTicket();
} else {
System.out.println("您当前消费不满200,不能免费洗车");
}
} else {
System.out.println("余额不足");
}
}
/* 打印洗车票 */
public void printWashTicket() {
System.out.println("打印洗车票");
}
}
java
public class SilverCard extends Card {
public SilverCard(String cardId, String name, String phone, double money) {
super(cardId, name, phone, money); // 4 参
}
@Override
public void consume(double money) {
if (money <= getMoney()) {
setMoney(getMoney() - money * 0.9);
System.out.println("消费成功,余额为:" + getMoney());
} else {
System.out.println("余额不足");
}
}
}
java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor // 无参构造
@AllArgsConstructor // 全参构造(4 个参数)
public class Card {
private String cardId;
private String name;
private String phone;
private double money;
/* 预存金额 */
public void deposit(double money) {
this.money += money;
}
/* 消费金额 */
public void consume(double money) {
if (money <= this.money) {
this.money -= money;
System.out.println("消费成功,余额为:" + this.money);
} else {
System.out.println("余额不足");
}
}
}
扩展: lombok技术可以实现为类自动添加getter和setter方法 、无参数构造器、toString方法等
Alt+回车键:将'lombok'添加到类路径中、建议org.projectlombok:lombok:1.18.30,最后在编译器的设置里
File → Settings → Build, Execution, Deployment → Compiler → Annotation Processors → Enable annotation processing
也可以在VScode下载lombok.jar放在项目的lib文件夹:https://projectlombok.org/download
可以建一个settings.json再进行运行:
javascript
{
"java.project.referencedLibraries": [
"lib/lombok.jar"
]
}
3.3、抽象类(abstract)
抽象类、抽象方法都是用abstract修饰的;抽象方法只有方法签名,不能写方法体。
注意事项(特点):
(1)、抽象类中可以不写抽象方法,但有抽象方法的类必须是抽象类;
(2)、类有的成员:成员变量、方法、构造器,抽象类都具备;
(3)、最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现;
(4)、一个类继承抽象类,必须重写抽象类的全部抽象方法,否则这个类也必须定义成抽象类。

好处:
父类知道每个子类都要做某个行为,但每个子类要做的情况不一样,父类就定义成抽象方法,交给子类去重写实现 。设计这样的抽象类,就是为了更好的支持多态。
模板方法设计模式
建议使用final关键字修饰模板方法,为什么?
解答:模板方法是给子类直接使用的,不能被子类重写;
一旦子类重写了模板方法,模板方法就失效了。

3.4、接口(interface)
注意:接口不能创建对象
接口是用来被类实现(implements)的,实现接口的类称为实现类,一个类可以同时实现多个接口,这里实现类多个接口,必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类。
好处:
弥补了类单继承的不足,一个类同时可以实现多个接口,使类的角色更多,功能更强大。
JDK8开始,接口新增了哪些方法?
1、默认方法default :使用实现类的对象调用
2、静态方法static :必须用当前接口名调用
3、私有方法private:jdk9开始才有的,只能在接口内部被调用
新增的这些方法,增强了接口的能力,更便于项目的扩展和维护。
注意事项:
(1)、接口与接口可以多继承:一个接口可以同时继承多个接口;
(2)、一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承,也不支持多实现;
(3)、一个类继承了父类,又同时实现了接口,如果父类中和接口中有同名的默认方法,实现类会优先用父类的;
(4)、一个类实现了多个接口,如果多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。
案例---定义接口
java
package Interface2;
// usb充电器接口
public interface UsbCharger {
void charge();// 抽象方法: 充电
// 默认方法:检查充电状态
default String checkStatus() {
return "检查充电状态...";
}
double VOLTAGE = 5.0;// 常量:电压
}
实现接口 - 不同设备(Phone+Tablet)
java
package Interface2;
public class Phone implements UsbCharger {
private String brand;
public Phone(String brand) {
this.brand = brand;
}
// 必须实现UsbCharger接口的抽象方法
@Override
public void charge() {
System.out.println(brand + "手机正在充电...");
System.out.println(VOLTAGE + "V" + "的电压");
}
// 手机特有的方法
public void makeCall(String number) {
System.out.println(brand + "手机正在拨打电话给 " + number);
}
}
java
package Interface2;
public class Tablet implements UsbCharger {
private String model;
public Tablet(String model) {
this.model = model;
}
// 必须实现UsbCharger接口的抽象方法
@Override
public void charge() {
System.out.println(model + "平板电脑正在充电...");
}
// 重写默认方法
@Override
public String checkStatus() {
return model + "平板电脑充电状态良好。";
}
// 平板电脑特有的方法
public void watchVideo() {
System.out.println(model + "平板电脑正在观看视频: ");
}
}
使用接口 - 充电站
java
package Interface2;
public class ChargingStation {
public static void main(String[] args) {
// 1、使用接口类型声明变量
UsbCharger device1 = new Phone("小米");
UsbCharger device2 = new Tablet("MacBook");
// 2、调用接口方法(多态)
device1.charge();
device2.charge();
// 3、调用默认方法
System.out.println(device1.checkStatus());
System.out.println(device2.checkStatus());
// 4、访问接口常量
System.out.println(UsbCharger.VOLTAGE + "V");
// 5、类型转换获取具体功能
if (device1 instanceof Phone) {
Phone phone = (Phone) device1; // 向下转型
phone.makeCall("123456789");
}
// 6、数组中的多态应用
UsbCharger[] devices = { device1, device2 };
for (UsbCharger device : devices) {
device.charge();
}
}
}
输出结果:

3.5、抽象类与接口区别
相同点:
1、都是抽象形式,都可以有抽象方法,都不能创建对象
2、都是派生子类形式:抽象类是被子类继承使用,接口是被实现类实现
3、一个类继承抽象类**/** 实现接口,都必须重写完它们的抽象方法,否则自己要成为抽象类**/** 报错
4、都能支持多态和实现解耦合
不同点:1、抽象类中可以定义类的全部普通成员,接口只能定义常量和抽象方法(jdk8新增3种方式)
2、抽象类只能被类单继承,接口可以被类多实现
3、一个类继承抽象类就不能再继承其他类,一个类实现了接口(还可以继承其他类或者实现其他接口)
4、抽象类体现模板思想:更利于做父类,实现代码的复用性---最佳实践
5、接口更适合做功能的解耦合:解耦合性更强更灵活---最佳实践
|-------|----------------------------|--------------|
| 特性 | 接口 | 抽象类 |
| 多重继承 | 一个类可实现多个接口 | 只能继承一个抽象类 |
| 方法实现 | java8+支持默认方法 | 支持具体方法和抽象方法 |
| 构造方法 | 不能有 | 可以有 |
| 成员变量 | 只能是常量(public static final) | 可以有各种类型的成员变量 |
| 访问修饰符 | 默认public | 可以是各种访问修饰符 |
| 设计目的 | 定义行为契约、API | 代码复用、模板方法模式 |
3.6、类中的成分---代码块
代码块是类的5大成分之一(成员变量、构造器、方法、代码块、内部类)
分为静态代码块和实例代码块两种:
静态代码块:格式:static {}
特点:类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次
作用:完成类的初始化 ,例如:对静态变量的初始化赋值。
实例代码块:格式:{}
特点:每次创建对象时,执行实例代码块,并在构造器前执行
作用:和构造器一样,都是用来完成对象的初始化的,例如:对实例变量进行初始化赋值
3.7、内部类(x4)
如果一个类定义在另一个类的内部,这个类就是内部类。
1、成员内部类(静态内部类)
含义:就是类中的一个普通成员,类似前面我们学过的普通成员变量、成员方法。
外部类名.内部类名 对象名 = new 外部类(...).new 内部类(...);
成员内部类的实例方法中,访问其他成员有啥特点?
可以直接访问外部类的实例成员、静态成员
可以拿到当前外部类对象,格式是:外部类名.this
2、静态内部类(有static修饰的内部类 )
外部类名.内部类名 对象名 = new 外部类.内部类(...);
可以直接访问外部类的静态成员,不能直接访问外部类的实例成员
java
public class ComparisonDemo {
public static void main(String[] args) {
// 1、创建外部类实例
OuterClass outer = new OuterClass();
// 2、创建非静态内部类实例(需要外部类实例)
OuterClass.InnerClass inner = outer.new InnerClass();
inner.innerMethod();
// 3、创建静态内部类实例(不需要外部类实例)
OuterClass.StaticInnerClass staticInner = new OuterClass.StaticInnerClass();
staticInner.staticInnerMethod();
}
}
java
public class OuterClass {
private String outerField="外部类字段";
private static String staticOuterField="外部类静态字段";
//非静态内部类
class InnerClass{
public void innerMethod(){
// 可以访问外部类的成员
System.out.println("访问外部类的非静态字段1:"+outerField);
System.out.println("访问外部类的静态字段1:"+staticOuterField);
}
}
// 静态内部类
static class StaticInnerClass{
public void staticInnerMethod(){
// 只能访问外部类的静态字段。不能访问非静态字段
// System.out.println("访问外部类的非静态字段:"+outerField); // 这一行会报错
System.out.println("访问外部类的静态字段2:"+staticOuterField);
}
}
}
3、局部内部类
它是定义在方法中、代码块中、构造器等执行体中,了解即可,为了引出匿名内部类
3.8、匿名内部类(没有名字的一次性内部类)
1、匿名内部类的书写格式是什么样的?
new 父类/接口() {
// 实现抽象方法
// 可以添加新的方法和字段
// 不能有构造方法
};
2、匿名内部类有啥特点?
解答:它本质就是一个子类,并会立即创建出一个子类对象。
3、基本作用?
解答:更方便的创建出一个子类对象
案例一:创建一个按钮,并为按钮添加一个点击事件监听器
java
// 定义点击监听接口
interface onClickListener {
void onClick();
}
// 使用匿名类实现接口
public class ButtonDemo {
public static void main(String[] args) {
Button button = new Button(); // 创建按钮对象
// 使用匿名内部类设置点击监听器
button.setOnClickListener(new onClickListener() {
// 实现接口的抽象方法
@Override
public void onClick() {
System.out.println("点击了按钮");
}
});
button.click(); // 模拟按钮点击
}
}
// 按钮类
class Button {
private onClickListener listener;
public void setOnClickListener(onClickListener listener) {
this.listener = listener;
}
public void click() {
if (listener != null) {
listener.onClick();
}
}
}
案例二:继承类 - 线程创建
java
// Thread(线程) 是 Java 中代表执行线程的类。一个程序可以同时运行多个线程。
// Runnable 是一个接口,定义了线程要执行的任务:
// 线程演示类
public class ThreadDemo {
public static void main(String[] args) {
// 方式1:继承Thread类(匿名内部类)
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("线程1运行中: " + Thread.currentThread().getName()); // 输出 Thread-0
}
};
// 方式2:实现Runnable接口(匿名内部类)
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程2运行中: " + Thread.currentThread().getName()); // 输出 Thread-1
}
});
thread.start(); // 启动线程
thread2.start(); // 启动线程
}
}
案例三:排序比较器
java
import java.util.*;
public class SortDemo {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Alice", 22));
students.add(new Student("Bob", 20));
students.add(new Student("Charlie", 23));
System.out.println("排序前:");
students.forEach(System.out::println);// 会对每个 Student 调用 toString() 并换行显示
// 使用匿名类实现Comparator接口进行排序
Collections.sort(students, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return Integer.compare(s1.getAge(), s2.getAge());
}
});
System.out.println("排序后:");
students.forEach(System.out::println);
}
}
// 学生类
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + "}";
}
}
注意事项
1、只能 new 一次
没有名字 → 无法再次 new,也无法声明构造器(但可以用初始化块)。
2、this 的指向
在匿名类内部,this 指的是匿名类自己;想引用外部类要写 外部类名.this。
3、不能定义静态成员
匿名内部类里不允许出现 static 字段或方法(static final 编译期常量除外)。
4、对外部局部变量的修改
由于"值复制"机制,外部方法里再改变量,匿名类里不会感知;
如果确实需要"双向"通信,可把变量包进数组或 AtomicXXX。
5、内存泄漏
匿名类对象长期持有外部类引用(非 static 场景),小心注册监听器后忘 remove。
3.9、函数式编程---lambda
lambda表达式:JDK8开始新增的一种语法形式,它表示函数
可以用于替代某些匿名内部类对象,从而让程序更简洁,可读性更好
注意:Lambda表达式只能替代函数式接口的匿名内部类
函数式接口:
(1)、有且仅有一个抽象方法的接口
(2)它上面可能会有一个@FunctionalInterface的注解,该注解用于约束当前接口必须是函数式接口
省略规则
(1)、参数类型全部可以省略不写
(2)、如果只有一个参数,参数类型省略的同时"()"也可以省略,但多个参数不能省略"()"
(3)、如果lambda表达式中只有一行代码,大括号可以不写,同时要省略分号";"如果这行代码是return语句,也必须去掉return
java
import java.util.Arrays;
import java.util.List;
public class Lambdademo {
public static void main(String[] args) {
List<String> fruits=Arrays.asList("Apple","Banana","Orange","Mango");
// 使用匿名内部类遍历集合
fruits.forEach(new java.util.function.Consumer<String>() {
@Override
public void accept(String fruit) {
System.out.println("水果1:"+fruit);
}
});
System.out.println("--------");
// 使用Lambda表达式遍历集合
fruits.forEach(fruit -> System.out.println("水果2:"+fruit));
System.out.println("--------");
// 使用方法引用遍历集合
fruits.forEach(System.out::println);
System.out.println("--------");
}
}
3.10、函数式编程---方法引用
简单理解:方法引用就是Lambda表达式的"语法糖",让代码更简洁。当Lambda只是调用一个现有方法时,就可以使用方法引用。
静态方法引用:类名::静态方法 → 调用类方法
实例方法引用:对象::实例方法 → 调用某个对象的方法
特定类型引用:类名::实例方法 → 调用参数对象的实例方法
构造器引用:类名::new → 创建新对象

java
package neibulei;
import java.util.*;
import java.util.function.*;
public class MethodReferenceExplanation {
public static void main(String[] args) {
System.out.println("=== 四种方法引用详细对比 ===\n");
// 准备数据
List<String> names = Arrays.asList("张三", "李四", "王五", "赵六");
System.out.println("1. 静态方法引用 (类名::静态方法名)");
System.out.println("格式: ClassName::staticMethodName\n");
// 示例:将字符串转换为大写
System.out.println("示例: 转大写");
// Lambda表达式写法
names.stream()
.map(s -> s.toUpperCase()) // Lambda
.forEach(s -> System.out.print(s + " "));
System.out.println("\n↓ 使用静态方法引用 ↓");
// 静态方法引用写法(String类的toUpperCase是实例方法,不能这样用)
// 正确示例:使用自定义的静态方法
names.stream()
.map(String::toUpperCase) // 这是特定类型的实例方法引用,不是静态方法引用!
.forEach(System.out::println);
// 真正的静态方法引用示例
System.out.println("\n真正的静态方法引用:");
names.forEach(MethodReferenceExplanation::printName);
System.out.println("\n\n2. 实例方法引用 (对象::实例方法名)");
System.out.println("格式: instance::instanceMethodName\n");
// 创建一个前缀处理器
PrefixProcessor processor = new PrefixProcessor("学生-");
// Lambda表达式写法
System.out.println("Lambda表达式:");
names.stream()
.map(name -> processor.addPrefix(name))
.forEach(System.out::println);
// 实例方法引用写法
System.out.println("\n实例方法引用:");
names.stream()
.map(processor::addPrefix)
.forEach(System.out::println);
System.out.println("\n\n3. 特定类型的方法引用 (类名::实例方法名)");
System.out.println("格式: ClassName::instanceMethodName\n");
// 示例:字符串比较
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
System.out.println("按字母顺序排序:");
// Lambda表达式写法
fruits.stream()
.sorted((s1, s2) -> s1.compareTo(s2))
.forEach(s -> System.out.print(s + " "));
System.out.println("\n↓ 使用方法引用 ↓");
// 特定类型的方法引用写法
fruits.stream()
.sorted(String::compareTo) // compareTo是String的实例方法
.forEach(s -> System.out.print(s + " "));
// 另一个例子:获取字符串长度
System.out.println("\n\n获取每个字符串的长度:");
// Lambda表达式写法
fruits.stream()
.map(s -> s.length())
.forEach(len -> System.out.print(len + " "));
System.out.println("\n↓ 使用方法引用 ↓");
// 特定类型的方法引用写法
fruits.stream()
.map(String::length) // length()是String的实例方法
.forEach(len -> System.out.print(len + " "));
System.out.println("\n\n4. 构造器引用 (类名::new)");
System.out.println("格式: ClassName::new\n");
// 创建字符串列表
System.out.println("创建对象列表:");
List<String> stringValues = Arrays.asList("Java", "Python", "C++");
// Lambda表达式写法:创建StringBuilder对象
System.out.println("Lambda表达式创建StringBuilder:");
List<StringBuilder> builders1 = stringValues.stream()
.map(s -> new StringBuilder(s))
.toList();
builders1.forEach(sb -> System.out.print(sb + " "));
System.out.println("\n↓ 使用构造器引用 ↓");
// 构造器引用写法
List<StringBuilder> builders2 = stringValues.stream()
.map(StringBuilder::new) // StringBuilder::new 是构造器引用
.toList();
builders2.forEach(sb -> System.out.print(sb + " "));
// 数组的构造器引用
System.out.println("\n\n数组构造器引用:");
// Lambda表达式写法
IntFunction<int[]> arrayCreator1 = size -> new int[size];
// 构造器引用写法
IntFunction<int[]> arrayCreator2 = int[]::new;
int[] array = arrayCreator2.apply(5);
System.out.println("创建了长度为 " + array.length + " 的数组");
}
// 静态方法,用于演示静态方法引用
public static void printName(String name) {
System.out.println("姓名: " + name);
}
// 内部类,用于演示实例方法引用
static class PrefixProcessor {
private String prefix;
public PrefixProcessor(String prefix) {
this.prefix = prefix;
}
// 实例方法
public String addPrefix(String text) {
return prefix + text;
}
}
}
3.11、常用API
3.11.1、String
java
package Strings;
public class StringDemo {
public static void main(String[] args) {
// 1、创建字符串
String str1 = "Hello World"; // 字面量创建,存储在常量池中
String str2 = new String("Hello World"); // 使用new关键字创建,存储在堆内存中
System.out.println("str1===str2: " + (str1 == str2)); // false
System.out.println("str1.equals(str2): " + str1.equals(str2)); // true
// 2、字符串不可变性
String str3 = "Hello";
str3 = str3 + " Java"; // 创建新字符串,str3指向新对象
System.out.println("str3: " + str3); // Hello Java
// 3、常用方法
String text = "Hello World, Java Programming";
System.out.println("长度: " + text.length()); // 29
System.out.println("转大写: " + text.toUpperCase()); // HELLO WORLD, JAVA PROGRAMMING
System.out.println("转小写: " + text.toLowerCase()); // hello world, java programming
System.out.println("获取索引4的字符: " + text.charAt(4)); // o
System.out.println("截取6-11: " + text.substring(6, 11)); // World
System.out.println("从索引6开始截取: " + text.substring(6)); // World, Java Programming
System.out.println("是否以'Hello'开头: " + text.startsWith("Hello")); // true
System.out.println("是否以'ing'结尾: " + text.endsWith("ing")); // true
System.out.println("是否包含'Java': " + text.contains("Java")); // true
System.out.println("'Java'的位置: " + text.indexOf("Java")); // 13
System.out.println("最后的'g'的位置': " + text.lastIndexOf("g")); // 28
// 4、替换与分割
System.out.println("替换'World'为'Universe': " + text.replace("World", "Universe")); // Hello Universe, Java
// Programming
String csv = "苹果,香蕉,橙子,西瓜";
String[] fruits = csv.split(",");
for (String fruit : fruits) {
System.out.println("分割后fruit: " + fruit);
}
System.out.println("去除首尾空格: '" + " Hello World ".trim() + "'"); // 'Hello World'
// 5、字符串比较
String a = "hello";
String b = "HELLO";
System.out.println("a.equals(b): " + a.equals(b)); // false
System.out.println("a.equalsIgnoreCase(b): " + a.equalsIgnoreCase(b)); // true
System.out.println("a.compareTo('hello'): " + a.compareTo("hello")); // 0
System.out.println("a.compareTo('apple'): " + a.compareTo("apple")); // 7
// 6、字符串连接
String greet = "Hello";
greet = greet.concat(" ").concat("World").concat("!");
System.out.println("greet: " + greet); // Hello World!
// 使用StringBuilder进行高效字符串操作
StringBuilder sb = new StringBuilder();
sb.append("Hello").append(" ").append("World");
System.out.println("sb: " + sb.toString()); // Hello World
}
}
3.11.2、String生成验证码
java
package Strings;
public class Stringtest {
public static void main(String[] args) {
// 生成验证码
String code = getCode(4);
System.out.println(code);
};
public static String getCode(int n) {
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
String code = ""; // 记住验证码的随机字符
// 循环n次,生成n个随机字符
for (int i = 0; i < n; i++) {
int number = (int) (Math.random() * str.length());
code += str.charAt(number);
}
return code;
}
}
3.11.3、ArrayList集合
java
package Strings;
import java.util.*;
public class ArrayListDemo {
public static void main(String[] args) {
// 1、创建 ArrayList
ArrayList<String> list = new ArrayList<String>();
// 2、添加元素
list.add("hello");
list.add("world");
list.add("java");
System.out.println("初始列表" + list); // [hello, world, java]
// 3、插入元素
list.add(1, "android"); // 在索引1处插入android
System.out.println("插入元素后列表:" + list); // [hello, android, world, java]
// 4、获取元素
String s = list.get(2);
System.out.println("索引为2的元素是:" + s); // world
// 5、修改元素
list.set(0, "红苹果");
System.out.println("修改元素后列表:" + list); // [红苹果, android, world, java]
// 6、删除元素
list.remove(1); // 删除索引1处的元素
System.out.println("删除元素后列表:" + list); // [红苹果, world, java]
list.remove("java"); // 删除元素java
System.out.println("删除元素后列表:" + list); // [红苹果, world]
// 7、大小和判空
System.out.println("列表元素个数:" + list.size()); // 2
System.out.println("列表是否为空:" + list.isEmpty()); // false
// 8、遍历:for循环与迭代器
for (int i = 0; i < list.size(); i++) {
System.out.println("索引" + i + "的元素是:" + list.get(i));
}
for (String s1 : list) { // 增强for循环
System.out.println("元素是:" + s1);
}
Iterator<String> it = list.iterator(); // 迭代器
while (it.hasNext()) {
System.out.println("迭代:" + it.next());
}
// 9、清空列表
list.clear();
System.out.println("清空列表后列表:" + list); // []
System.out.println("列表大小:" + list.size()); // 0
}
}
3.11.4、String与ArrayList区别

ArrayList:是可以装多个东西的袋子(动态数组),能增删改查,容量自动调整
String:是写好的文字(字符序列),一旦写好就不能修改,想要不同内容就得重新写
