对应网课为:【黑马程序员Java+AI智能辅助编程全套视频教程,java零基础入门到大牛一套通关】
项目代码地址如下(如果对你有帮助请给我一个免费的star):
gitcode链接(无需梯子)https://gitcode.com/CherryTaylor/AboutJava_first
github链接https://github.com/CarrieChai/AboutJava_first
Day05~08-面向对象编程
对象 是一种特殊的数据结构,可以用来记住一个事物的数据,从而代表该事物
客观事物的抽象,体现在程序里就是类
- 通过new关键字,每new一次就可以得到一个对象的实例
Day05-oop
(下面内容来自Java核心技术卷一基础知识第十版第四章p91-93)
面向过程的语言是先考虑对数据的操作,然后再决定如何组织数据
- Java语言是面向对象编程(OOP object-oriented programming)的,将数据放在第一位,然后去考虑对操作数据的算法
- ----类是构造对象的模版或蓝图,由类构造对象的过程称为创建类的实例(new 一个对象的书面用语)
- ----对象中的数据称为实例域,操纵数据的过程叫做方法,对于每一个类的实例都有一组特定的实例域值,这些值的集合就是这个对象的当前状态
- 面向对象编程的三个基本特征是封装、继承和多态
- 封装,从形式上看是把数据和行为组合在一起,并对对象的使用者隐藏数据的实现方式。
- 继承,通过扩展一个类来建立另一个类的过程称为继承。
回到黑马课堂笔记
对象在计算机中是什么?
已知,对象是类的实例,包含了数据和方法,是程序运行时的核心操作单元
对象在计算机中的存储和管理由JVM(Java虚拟机)通过栈内存、堆内存和方法区协同完成。
对象被创建出来之后,存储在堆内存中,包含:
- 对象头:存储运行时数据(如锁状态)、类元数据指针(指向方法区的类信息,即图中的2号箭头)等
- 对象体:存储实例变量的值(即图中的语文分数59、数学分数99)以及父类继承的属性
- 对齐字节:保证对象总大小为8字节的整数倍
对象在堆中分配空间,对象的引用(也就是它的内存地址,图中蓝色方框最顶部紫色的0x4f3f5b24)存储在栈内存中(也就是s1中)
JVM(Java虚拟机)
JVM是Java程序运行的执行引擎,负责内存分配、垃圾回收和字节码解释。其内存模型包含以下关键区域:
- 栈内存(Stack):每个线程私有的内存区域,存储方法调用的栈帧,包含局部变量、操作数栈和方法返回地址。栈内存分配速度快,生命周期与线程方法执行同步。
- 堆内存(Heap):存储所有对象实例和数组,由垃圾回收器(GC)管理,分为新生代(Young Generation)和老年代(Old Generation)。
- 方法区(Method Area):线程共享的区域,存储类元数据(如类结构、常量池、静态变量)、即时编译器编译后的代码等。JDK8后由“元空间(Metaspace)”实现,使用本地内存而非堆内存
堆和栈的对比
特性 | 栈内存 | 堆内存 |
---|---|---|
存储周期 | 局部变量、方法参数、返回地址 | 对象实例、数组 |
生命周期 | 方法结束即释放 | 由GC管理,对象无引用时回收 |
线程安全性 | 线程私有,无需同步 | 线程共享,需同步机制 |
分配速度 | 固定分配,速度极快 | 动态分配,速度较慢 |
内存碎片 | 无碎片 | 可能产生碎片 |
void method() {
int a = 10; // 栈中分配基本类型变量
String s = "Hello"; // 栈中存引用,字符串常量池在方法区
Object obj = new Object(); // obj引用在栈,对象实例在堆
}
内存分配的完整流程
对象创建过程
- JVM在堆内存中寻找连续空间,通过指针碰撞或空闲列表分配内存
- 初始化对象头,调用构造函数初始化对象体
- 栈内存中生成引用变量,指向堆内存中的地址
方法调用的内存行为
- 每次方法调用生成一个栈帧,存储局部变量表等信息
- 静态变量和常量池存储在方法区,对象引用可能跨栈、堆、方法区协同工作
总结
对象与内存的关系:对象实例在堆,引用在栈,类元数据在方法区
OOM(内存耗尽,out of memory)问题:堆内存溢出(对象过多)、方法区溢出(类加载过多)、栈溢出(递归过深)
类的成分
1 构造器(构造方法)day05-oop object Student+Test02
构造器:用于构造并初始化对象的一种特殊的方法,无返回值,方法名和类名一样
创建对象时,对象会调用构造器(反过来,构造器是伴随着new操作符的执行被调用,不能对一个已经存在的对象调用构造器来达到重新设置实例域的目的)
Student s1;
Student s2 = new Student();
第一行只是定义了一个对象变量s1,他可以引用Student类型的对象,但是变量s1不是对象也没有引用对象(就目前的代码而言)(s1类似于c++的指针)
第二行包含两个部分,表达式 new Student() 构造了一个Student类型的对象,他的值是对新创建对象的引用(一个堆内存中的地址),这个引用存储在了变量s2中。
创建对象时,对象会调用构造器,第二行调用的是无参构造器,创建类时系统会默认一个无参构造器,如果为类定义了有参构造器,类默认的无参构造器就没有了,此时需要自己手写一个无参构造器出来(即每个类至少有一个构造器)
this 关键字
this是一个变量,可以用在方法中,拿到当前对象实例(类似于指向当前对象的指针)
哪个对象实例调用方法,this就拿到哪个对象
在开发中常常用来解决成员变量和方法内部变量的访问冲突问题
封装
设计类来处理一个事物的数据时,应该把要处理的数据私有,把处理这些数据的方法公开
封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。
表现在代码上就是要使用private关键字把数据隐藏为类的私有成员,仅允许使用get、set方法来访问获更改,这两个方法是公有的(public)
实体类
实体类:成员变量必须私有,且要为他们提供get、set方法;必须有无参数构造器
实体类仅仅是一个用来保存数据的Java类,可以用它创建对象,保存某个事物的数据
实体类的对象应该只负责数据存取 对数据的业务处理交给其他类的对象来完成,以实现数据和数据业务处理相分离
day05-oop javabeen
static 关键字
静态 可以修饰成员变量和成员方法
day05-oop staticfield
- 使用static修饰的变量为静态变量,属于类,只加载一份,可以被类和类的全部对象访问(也可以说是被类的所有对象共享),一般使用类名.静态变量来访问(也可以用对象名.静态变量来访问 但是不推荐)
- 实例变量(没有static修饰的变量),属于对象,每个对象都有一份,所以必须用对象名.实例变量的方式访问 不能用类名访问
- 静态变量的应用场景:数据只需要一份,且需要被共享时(访问、修改)
static修饰方法
- 静态方法(类方法),属于类,可以直接用类名访问(也可以用对象访问,但不推荐)
- 无static修饰的方法是实例方法(对象的方法),属于对象,只能有对象访问
- 一般来说,如果一个方法只是为了做一个功能且不需要直接访问对象的数据,这个方法直接定义成静态方法;
- 如果这个方法是对象的行为,需要访问对象的数据,这个方法必须定义成实例方法
静态方法的应用场景 day05-oop VerifyCodeUtil Test2 Test3
可以用来设计工具类
- 工具类中的方法都是静态方法,每个类方法都是用来完成一个功能的
- 工具类可以有效提高代码的复用性;调用方便,提高开发效率
- 工具类使用静态方法的原因是可以通过类名.静态方法的方式直接调用,而不需要创建对象,从而避免了浪费内存(创建对象如果只为了调用方法的话,对象本身会在堆里面占内存)
tip:工具类不需要创建对象,建议将工具类的构造器私有化
静态方法、实例方法访问的相关注意事项
- 静态方法中可以直接访问静态成员(静态变量和静态方法),不可以直接访问实例成员
- 实例方法中既可以访问静态成员,也可以访问实例成员
- 实例方法中可以出现this关键字,静态方法中不可以出现this关键字(因为this保存的是对象的地址,静态方法在对象被创建之前就有了)
一个面向对象的案例 day05 demo
需求
- 展示系统中的全部电影信息(每部电影展示名称、价格)
- 允许用户根据电音编号(ID)查询出某个电影的详细信息
继承
day6 oop extends1demo People Teacher Test
- 什么是继承
Java中提供了一个关键字extends,用这个关键字,可以让一个类和另一个类建立起父子关系
public class B extends A{
}
上面的代码中A为父类(基类、或超类)
B为子类(派生类)
- 子类能继承什么
子类能继承父类的非私有成员(成员方法、成员变量) - 继承后对象的创建
子类的对象是由子类和父类共同完成的
权限修饰符
extends2modifiler extends3modifiler
权限修饰符就是用来限制类中的成员(成员变量、成员方法、构造器)能够被访问的范围。
- private 只能本类
- 缺省 本类、同一个包中的类
- protected 本类、同一个包中的类、子孙类
- public 任意位置
day06 extends4 test1 test2
- Java的类只能是单继承的,不支持多继承,支持多层继承
- Java的祖宗类 object 每个类都默认继承object类
- 在子类中访问成员使用就近原则,优先在子类中找,子类没有就找父类,父类没有就报错
- 如果出现重名成员,默认访问子类中的,非要访问父类中的,需要super.父类成员(变量或方法)
方法重写
day06 extends5 test
- 方法重写:子类中重写一个方法名+参数列表和父类中的一样的方法,去覆盖父类这个方法,就是方法重写 (建议方法重写时上方写一个@Override 方法重写的校验注解,要求方法名和参数列表必须与被重写的方法一致,否则就报错)
方法重写的其它注意事项
- 子类重写父类方法时,访问权限必须大于或者等于父类该方法的权限(public>protected>缺省)
- 重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小。
- 私有方法、静态方法不能被重写,如果重写会报错的。(私有方法可以被继承,但是不能被直接访问 所以不能被重写)(静态方法仅被类持有,所以不能重写,可以继承(如果在子类中写一个一模一样的方法 这就是一个新方法 不按照重写走 也就是不能写@Override))
方法重写应用场景
- 重写object中的toString方法,以便返回对象中的内容 day06 extends5 test2
子类构造器
day06 extends6 Test
子类的所有构造器,都会先调用父类的构造器,然后再执行自己
- 因为默认情况下子类的全部构造器代码第一行都是super()(写不写都有),他会调用父类的无参构造器
- 如果父类没有无参构造器,那我们必须在子类构造器的第一行手写super(参数列表…),指定去调用父类的有参构造器
子类构造器调用父类构造器的应用场景 day06 extends6 Test2 People Teacher
- 子类构造器可以通过调用父类否早起,把对象中包含父类这部分的数据先初始化赋值,再回来把对象里包含子类这部分的数据也进行初始化赋值
补充: this调用兄弟构造器
如果子类中有3个参数,其中一个参数未必每个对象都需要初始化(可能有默认值),就可以写两个有参构造器,一个是同时初始化三个参数的构造器,一个是仅初始化两个参数的构造器,这个时候就可以通过this调用兄弟构造器优化代码
public class Student {
private String name;
private int age;
private String sex;
private String schoolName;
public Student() {
}
public Student(String name, int age, String sex) {
// 这里我们需要初始化三个参数,使得最后一个参数选择默认值
// 就可以使用this关键字
this(name, age, sex, "默认值");
}
public Student(String name, int age, String sex, String schoolName) {
// 这里默认第一行有super()
this.name = name;
this.age = age;
this.sex = sex;
this.schoolName = schoolName;
}
...
}
注意:
- 用this关键字调用兄弟构造器的时候,这个构造器里第一行默认的super()就没有了,这是为了避免生成两个父类对象,因为被调用的兄弟构造器里一定有super()
- 换句话说,使用this(…)调用兄弟构造器的时候一定是方法中的第一行,且不能和super(…)同时出现
多态
day06 polymorphism1 test Animal Wolf Fish
多态是在继承/实现情况下的一种现象,表现为:对象多态,行为多态
多态的前提是有继承或实现关系、存在父类引用子类对象、存在方法重写
注意:多态是对象、行为的多态,Java中的属性(成员变量不谈多态)
day06 polymorphism2 test Animal Wolf Fish
- 多态的好处:
在多态形式下,右边对象是解耦合的,更便于扩展和维护
父类类型的变量作为参数,可以接受一个子类对象 - 多态的问题:多态下不能调用子类独有的功能
day06 polymorphism3 test Animal Wolf Fish
多态下的类型转换(解决调用子类独有功能的问题)
父类 f = new 子类( );
这时候想要调用子类的独有功能,可以强制类型转换
子类 s = (子类) f;
这个时候就可以调用子类独有的方法了
强制类型转换之前最好判断一下此时 f 的真实类型
案例 加油站支付小模块 day 06 com.itheima.demo
- 某加油站为了吸引更多的车主,推出了如下活动,车主可以办理金卡和银卡。
- 卡片信息包括:车牌号码、车主姓名、电话号码、卡片余额。
- 金卡办理时入存金额必须>=5000元,银卡办理时预存金额必须>=2000元,金卡支付时享受8折优惠,银卡支付时享受9折优惠,金卡消费满200元可以提供打印免费洗车票的服务
- 需求:请使用面向对象编程,完成该加油站支付机的存款和消费程序。
final关键字
修饰类、方法、变量
- 修饰类 该类被称为最终类 不能被继承(一般修饰工具类)
- 修饰方法 该方法被称为最终方法 不能被重写
- 修饰变量 该变量有且仅能被赋值一次(一般修饰静态变量) - 这里补充 变量分别成员变量(又分为静态成员变量和实例成员变量)和局部变量
修饰变量
- final修饰基本类型的变量,变量存储的数据不能改变
- final修饰引用类型的变量,变量存储的地址不能被改变,但地址所指向对象的内容是可以被改变的
常量
使用static final修饰的成员变量就是常量(通常命名使用大写英文单词,多个单词用下划线连接)
用来记录系统的配置信息
- 使用常量的优势:
代码可读性好、可维护性也好
程序编译后,常量会被宏替换,出现常量的地方全部会被替换成其记住的字面量,这样可以保证使用常量和直接用字面量一样
抽象类、接口
- 设计模式:一个问题多种解法,其中一种是最优的解法,这个最优解法被总结出来,称之为设计模式
- 设计模式有20多种,对应20多种软件开发中会遇到的问题
- 设计模式主要学习:解决什么问题?怎么写?
单例设计模式
day07 singleinstance
作用:确保某个类只能创建一个对象(解决的问题)
应用场景:任务管理器对象、获取运行时对象【在某些业务场景下,使用单例模式可以避免浪费内存】
怎么写?
饿汉式单例–获取类的对象的时候,对象已经创建好了 A类
把类的构造器私有,定义一个静态变量存储类的一个对象,提供一个静态方法返回对象
public class A {
// 1.私有化构造器:避免外部创建多个对象
private A(){}
// 2.创建一个私有的静态变量,保存唯一对象
private static A instance = new A();
// 3.提供一个共有的静态方法,返回唯一对象
public static A getInstance(){
return instance;
}
}
//调用
public class Test {
public static void main(String[] args) {
// 目标 设计单例类
A a1 = A.getInstance();
A a2 = A.getInstance();
System.out.println(a1 == a2);
}
}
懒汉式单例–要用类的对象时才创建对象(延迟加载对象) B类
把类的构造器私有,定义一个类变量用于存储类对象,提供一个静态类方法,保证返回的是同一个对象
//懒汉式单例类
public class B {
// 1.私有化构造方法
private B(){}
// 2.私有化静态变量
private static B b = null;
// 3.提供公共的静态方法,返回实例对象
public static B getInstance(){
if(b == null){
b = new B();
}
return b;
}
}
//调用
public class Test {
public static void main(String[] args) {
// 目标 设计单例类
B b1 = B.getInstance();
B b2 = B.getInstance();
System.out.println(b1 == b2);
}
}
枚举类
day07 enumdemo
枚举类是一种特殊类
//枚举类的代码段
修饰符enum 枚举类名{
名称1,名称2,...;
其他成员...
}
//举例
package com.itheima.enumdemo;
// 枚举类
public enum A {
// 枚举类的第一行,只能罗列枚举对象的名称,这些名称本质是常量
// 枚举类的第一行,默认是public static final
X,Y,Z;
}
//调用
package com.itheima.enumdemo;
public class Test {
public static void main(String[] args) {
// 目标:理解枚举类型的使用
A a1 = A.X;
System.out.println(a1); // X
A a2 = A.Y;
System.out.println(a2); // Y
System.out.println(a1.name()); // X
System.out.println(a1.ordinal()); // 0
System.out.println(a1.compareTo(a2)); // -1
System.out.println(a1.equals(a2)); // false
System.out.println(a1.toString()); // X
}
}
枚举类的特点:
- 第一行只能写枚举类的枚举对象名称,用逗号隔开,这些名称本质都是常量,每个常量都记住了枚举类的一个对象
- 枚举类都是最终类,不能被继承,枚举类本身默认继承自java.lang.Enum类
- 枚举类的构造器都是私有的(不写也默认是私有),因此枚举类不能对外创建对象
- 编译器为枚举类新增几个方法(枚举类的父类Enum会有的)比如上面代码中的toString() ordinal() equals()
应用场景
枚举类很适合做信息标志和分类
抽象类
day07 abstractdemo1
修饰符 abstract class 类名{
修饰符 abstract 返回值类型 方法名(形参列表);
}
public abstract class A{
public abstract void test();
}
Java中的关键词 abstract,用它修饰的类就是抽象类,用它修饰的成员方法就是抽象方法
- 抽象方法只有方法的签名,没有方法体(因为抽象方法是会被重写的)
抽象类的特点:
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 类有的成员:成员变量、方法、构造器,抽象类都可以有
- 抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现
- 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义为抽象类
抽象类的优势:更好的支持多态
day07 abstractdemo2
【应用场景一】每个子类都有的方法,但每个子类的情况不一样,父类就定义成抽象方法,子类重写抽象方法去实现。
【应用场景二】模版方法设计模式
day07 abstractdemo3
提供一个方法作为完成某类功能的模版,模版方法封装了每个实现步骤,但允许子类提供特定步骤的实现
模版方法设计模式能有效提高代码的复用并简化子类的设计模式
如下代码段所示
public abstract class People {
//public final void write(){
public void write(){
System.out.println("\t\t\t《我的爸爸》");
System.out.println("\t我的爸爸是一个好人");
writeMain();
System.out.println("\t我的爸爸真好");
}
public abstract void writeMain();
}
-----------------------------
public class Student extends People{
@Override
public void writeMain() {
System.out.println("\t我的爸爸是一个学生");
}
}
-----------------------------
public class Test {
public static void main(String[] args) {
People student = new Student();
student.write();
}
}
tips 建议使用final关键字修饰模版方法(因为模版方法本身就是给子类直接用的,不能被重写,一旦子类重写,模版方法就失效了)
接口
day07 interfacedemo1
Java提供了一个关键字 interface定义接口
JDK8之前接口中只能写常量和抽象方法
public interface 接口名{
// 成员变量(成员变量)
// 成员方法(抽象方法)
}
注意:接口实用类被类实现的,不能创建对象
实现接口:实现接口的类称为实现类,一个类可以同时实现多个接口
接口的优势:
day07 interfacedemo2
- 弥补了类单继承的不足,一个类可以同时实现多个接口,使类的角色更多,功能更强大
- 让程序可以面向接口编程,这样程序员就可以灵活方便的切换各种业务实现(更有利于程序解耦合)
接口应用案例
day07 interfacedemo3
需求
- 请设计一个班级学生的信息管理模块;
学生的数据有:姓名、性别、成绩
功能1:要求打印出全班学生的信息;
功能2:要求打印出全班学生的平均成绩
注意!以上功能的业务实现是有多套方案的,比如:
- 第1套方案:能打印出班级全部学生的信息;能打印班级全部学生的平均分。
第2套方案:能打印出班级全部学生的信息(包含男女人数);能打印班级全部学生的平均分(要求是去掉最高分、最低分)
要求:系统可以支持灵活的切换这些实现方案
JDK8之后,接口新增了三种方法
public interface A {
/*
* 1、默认方法(实例方法):使用default修饰,默认会被加上public修饰符,
只能使用接口的实现类对象来调用(接口的私有方法也可以调用)
* 2、静态方法:使用static修饰,默认会被加上public修饰符,
只能接口名来调用
* 3、私有方法:使用private修饰,默认会被加上private修饰符,
只能由接口中的其他实例方法来调用
* */
public default void defaultMethod(){
System.out.println("A接口的默认方法");
System.out.println("使用接口实现类对象调用");
privateMethod();
}
public static void staticMethod(){
System.out.println("A接口的静态方法");
System.out.println("使用接口类名调用");
}
private void privateMethod(){
System.out.println("A接口的私有方法");
System.out.println("只能由接口中的其他实例方法调用");
// 默认方法可以被接口的私有方法调用
defaultMethodTest();
}
public default void defaultMethodTest(){
System.out.println();
System.out.println("A接口的默认方法2");
}
}
- 默认方法(实例方法):使用default修饰,默认会被加上public修饰符,
只能使用接口的实现类对象来调用(接口的私有方法也可以调用) - 静态方法:使用static修饰,默认会被加上public修饰符,
只能当前接口名来调用(接口的子类名不能调用) - 私有方法:使用private修饰,默认会被加上private修饰符,(JDK9开始支持)
只能由接口中的其他实例方法来调用
新增的三种方法增强了接口的能力,更便于项目的扩展和维护
接口的注意事项
day07 interfacedemo5
- 接口和接口可以多继承:一个接口可以同时继承多个接口(重点)
- 一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承也不支持多实现
- 一个类继承了父类,同时实现了接口,如果父类中和接口中有同名的默认方法,实现类会优先使用父类的方法
- 一个类实习了多个接口,如果多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可
接口和抽象类的对比
相同点:
- 接口和抽象类都是抽象形式,都可以有抽象方法,都不能创建对象
- 接口和抽象类都是派生子类的形式:抽象类可以被继承,接口可以被实现
- 一个类继承抽象类/实现接口,必须重写其抽象方法,否则就要这个子类(或实现接口的类)成为抽象类
- 接口和抽象类都能支持多态,实现解耦合
不同点: - 抽象类中可以定义类的全部普通成员(成员变量、方法、构造器),接口中只能定义常量和抽象方法(JDK 8之后新增了默认方法、静态方法和私有方法)
- 抽象类只能被类单继承(一个类继承了抽象类就不能再继承其他类)
接口可以被类多实现(一个类实现了某个接口还可以继承其他类或实现其他接口) - 抽象类体现模版思想,更利于做父类,体现代码的复用性。 最佳实践
- 接口更适合做功能的解耦合,解耦合性更强更灵活。 最佳实践
综合案例-智能家居控制系统
day 07 demo
某智能家居系统,可以让用户选择要控制的家用设备(吊灯、电视机、洗衣机、落地窗),并可以对他呢进行打开或关闭操作
面向对象高级
代码块
day08 code
静态代码块:codedemo1
- ----格式:static{}
- ----特点:类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次
- ----作用:完成类的初始化,例如:对静态变量的初始化赋值
实例代码块:codedemo2
- ----格式:{}
- -----特点:每次创建对象时,执行示例代码块,并在构造器前执行
- -----作用:和构造器一样,都是用来完成对象的初始化的,例如:对实例变量进行初始化赋值。
内部类
- 如果一个类定义在另一个类的内部,这个类就是内部类
- 场景:当一个类的内部,包含了一个完整的事物,且这个事物没必要单独设计时,就可以把这个事物设计成内部类。
成员内部类属于外部类的对象持有
成员内部类的格式如下:day08 innerclass
public class Car{
// 内部类
public class Engine{
}
}
// 创建内部类对象时
外部类名.内部类名 对象名 = new 外部类(...).new 内部类(...);
成员内部类中访问其他成员的特点:
- 成员内部类可以直接访问外部类的实例成员、静态成员
- 成员内部类的实例方法中,可以直接拿到当前外部类对象,格式是:外部类名.this
静态内部类
有static修饰的内部类,属于外部类自己持有
静态内部类的格式如下:day08 innerclass2
public class Outer{
// 静态内部类
public static class Inner{
}
}
// 创建内部类对象时
外部类名.内部类名 对象名 = new 外部类.内部类(...);
Outer.Inner in = new Outer.Inner();
静态内部类中访问其他成员的特点:
- 静态内部类可以访问外部类的静态成员,不能直接访问外部类的实例成员(静态内部类不需要new外部类对象,自然也不存在外部类的实例成员了)
局部内部类(了解)
匿名内部类
- 是一种特殊的局部内部类
- 匿名,指的是程序员不需要为这个类声明名字,默认有个隐藏的名字
- 特点:匿名内部类本质是一个子类,并且会立即创建出一个子类对象
- 作用:用于更方便的创建一个子类对象
new 类或接口(参数值...){
类体(一般是方法重写);
};
new Animal(){
@Override
public void cry(){
}
};
比如说 day08 innerclass3 Test Test2
//Animal.java
package com.itheima.innerClass3;
public abstract class Animal {
public abstract void cry();
}
// Test.java
package com.itheima.innerClass3;
public class Test {
public static void main(String[] args) {
// 目标:认识匿名内部类。搞清楚其基本作用。
Animal a = new Animal() {
@Override
public void cry() {
System.out.println("狗叫...");
}
};
a.cry();
}
}
匿名内部类在开发中的常见形式(语法)
- 通常作为一个对象参数传输给方法
应用场景:day08 innerClass3 Test3
- 调用别人提供的方法实现需求时,这个方法正好可以让我们传输一个匿名内部类对象给其使用。
案例 使用comparator接口的匿名内部类实现对数组进行排序
package com.itheima.innerClass3;
import java.util.Arrays;
import java.util.Comparator;
public class Test4 {
public static void main(String[] args) {
// 目标:完成给数组排序,理解其中匿名内部类的用法
// 准备一个学生类型的数组,存放6个学生对象。
Student[] students = new Student[6];
students[0] = new Student("小明", 18, 1.75, "男");
students[1] = new Student("小红", 19, 1.70, "女");
students[2] = new Student("小刚", 20, 1.77, "男");
students[3] = new Student("小花", 21, 1.60, "女");
students[4] = new Student("小黑", 21, 1.88, "男");
students[5] = new Student("小黄", 22, 1.55, "女");
// 需求:安年龄升序排序,可以调用Sun公司写好的API直接对数组进行排序
// public static <T> void sort(T[] a, Comparator<? super T> c)
// 参数1 待排序的数组
// 参数2 通过声明一个Comparator比较器对象指定排序规则
// sort方法会调用匿名内部类对线的compare方法,对数组中的学生对象进行两两比较,从而实现排序
Arrays.sort(students, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 制定排序规则:先按照年龄升序排序
// 如果左边大于右边 返回正整数
// 如果左边小于右边 返回负整数
// 如果左边等于右边 返回0
// if (o1.getAge() > o2.getAge()) {
// return 1;
// } else if (o1.getAge() < o2.getAge()) {
// return -1;
// } else {
// return 0;
// }
// 一个更简化的做法
// return o1.getAge() - o2.getAge(); // 按照年龄升序
return o2.getAge() - o1.getAge(); // 按照年龄降序
}
});
// 遍历数组中的学生对象并输出
for (int i = 0; i < students.length; i++) {
System.out.println(students[i]);
}
}
}
函数式编程
day08 LambdaDemo1
这里的函数类似数学中的函数(强调做什么),只要输入的数据一致,返回的结果也是一致的。
- 数学中的函数示例:2x+1
- Java中的函数(Lambda表达式):(x)->2x+1
Lambda表达式从JDK 8开始新增的一种语法形式,它表示函数
函数式编程解决了什么问题?
- 使用Lambda函数替代某些匿名内部类对象,从而让程序代码更简洁,可读性更好
(被重写方法的形参列表)->{
被重写方法的方法体代码
}
注意:Lambda表达式只能替代函数式接口的匿名内部类
什么是函数式接口?
- 有且仅有一个抽象方法的接口
- 函数式接口的注解通常会有一个注解,@FunctionalInterface,该注解用于约束当前接口必须是函数式接口。
函数式编程、Lambda表达式…
- 什么是函数式编程?有什么好处?
使用Lambda函数替代某些匿名内部类对象,从而让代码程序更简洁,可读性更好 - Lambda表达式是什么?有什么用?怎么写?
JDK8新增的一种语法,代表函数;可以用于替代并简化函数式接口的匿名内部类 - 什么样的接口是函数式接口?怎么确保一个接口一定是函数式接口?
只有一个抽象方法的接口是函数式接口,加一个函数式接口的注解就可以确保@FunctionalInterface
案例 使用Lambda简化comparator接口的匿名内部类 day08 LambdaDemo2
Lambda简化规则
- 参数类型全部可以省略不写
- 如果只有一个参数,那么参数类型省略的同时“()”括号也可以省略,但是多个参数不能省略括号
- 如果Lambda表达式中只有一行代码,大括号可以不写,同时省略分号;如果这行代码是return语句,那么必须去掉“return”
方法引用
静态方法引用
类名::静态方法
- 使用场景
如果某个Lambda表达式里只是调用一个静态方法,并且“->”前后参数的形式一致,就可以使用静态方法引用
day08 method1reference demo1
// Arrays.sort(students,(Student o1, Student o2)->{
// return o1.getAge() - o2.getAge(); // 按照年龄升序
// });
// Arrays.sort(students,( o1, o2)->{
// return o1.getAge() - o2.getAge(); // 按照年龄升序
// });
// Arrays.sort(students,(o1, o2)-> o1.getAge() - o2.getAge()); // 按照年龄升序
// Arrays.sort(students,(o1, o2)-> Student.comparreByAge(o1, o2)); // 按照年龄升序
// 只调用一个静态方法,箭头前后参数的形式一致,静态方法引用
Arrays.sort(students,Student::comparreByAge); // 按照年龄升序
//Student类长这样
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
// 姓名 年龄 身高 性别
private String name;
private int age;
private double height;
private String sex;
public static int comparreByAge(Student s1, Student s2){
return s1.getAge() - s2.getAge();// 升序
}
}
实例方法引用 day08 method1reference demo2
- 对象名::实例方法
- 对比静态方法引用静态方法属于类,所以可以直接用类名,实例方法属于对象,所以用对象名(也就是说使用实例方法引用之前要先创建对象)
使用场景
- 如果某个Lambda表达式只是通过对象名称调用一个实例方法,并且“->”前后参数的形式一致,就可以使用实例方法引用
特定类型方法引用 day08 method1reference Demo3
- 特定类的名称::方法
使用场景
- 如果某个Lambda表达式里只是调用一个特定类型的实例方法,并且前面参数列表中的第一个参数是作为方法的主调,后面的所有参数都是作为该实例方法的参数,此时可以使用特定类型的方法引用
构造器引用 day08 method1reference Demo4
- 类名::new
使用场景
- 如果某个Lambda表达式里只是在创建对象,并且“->”前后参数情况一致,就可以使用构造器引用
常用API
String
String代表字符串,它的对象可以封装字符串数据,并提供了很多方法完成对字符串的处理
- 有什么用?
创建字符串对象
调用String提供的操作字符串数据的方法
String类创建字符串数据的多种方式
-
【推荐】直接使用双引号,此时字符串保存在方法区的常量池中,相同的数据只保存一份
-
【不推荐】使用new对象的方式,每new一个都会保存在堆内存中
顺便 == 比较的是地址,所以new出来的字符串尽管内容一样也会因为地址不同而导致使用 ==进行比较的时候返回false
- 用扫描器接字符串也可以
day08 stringdemo StringDemo1
String s1 = "hello"; //推荐做法 此时存储在方法区的常量池中
System.out.println(s1);
String s2 = new String(); // 不推荐 此时s2存储在堆内存中
System.out.println(s2); // 空字符串 内部是一个""
String s3 = new String("hello"); // 不推荐,此时s3存储在堆内存中
System.out.println(s3);
byte[] bytes = {97,98,99,100};
String s4 = new String(bytes); // 不推荐,此时s4存储在堆内存中
System.out.println(s4); // abcd
Scanner sc = new Scanner(System.in); // 用扫描器接字符串
String name = sc.next();
String提供的常用方法
案例 开发验证码
需求:实现随机产生验证码,验证码的每位可能是数字、大写字母、小写字母。
ArrayList
-
什么是集合?
集合是一种容器,用来装数据,类似于数组 -
有数组为什么要多一个集合?
数组定义完成并启动后长度就固定了
但是集合大小可变,功能丰富,开发中用的更多
ArrayListDemo1
- ArrayList是集合中最常用的一种,ArrayList是泛型类,可以约束存储的数据类型
- 创建对象:调用无参构造器 public ArrayList()初始化对象
- 调用增删改查数据的方法
GUI编程 (一般不用)
day08 gui
- GUI,graphical user interface ,图形用户界面
- 通过图形元素与用户进行交互
- 与命令行界面相比更加直观友好
为什么学习GUI编程
- 增强用户体验
- 广泛应用于桌面应用程序开发
- Java提供了强大的GUI编程支持
使用Swing 轻量级组件,不依赖与本地窗口系统(win和Apple操作系统都可以)
常用Swing组件:JFrame(窗口)、JPanel(用于组织其他组件的容器)、JButton(按钮组件)、JTextField(输入框)、JTable(表格)…
常见的布局管理器
…
事件处理
GUI编程中,事件的处理是通过事件监听器(Event Listener)来完成的。day08 gui Test
常用的事件监听器对象
- 点击事件监听器 ActionListener
- 按键事件监听器 KeyListener
- 鼠标行为监听器 MouseListener
事件的几种常用写法
- 直接提供实现类,用于创建事件监听对象 day08 gui Test2
- 直接使用匿名内部类的对象,代表事件监听对象 day08 gui Test2
- 自定义窗口,让窗口实现事件接口 day08 gui Test3 LoginFrame
至此,Java基础课的内容就结束啦,后面还有两个项目实战内容