面向对象 类(实例) 构成
方法局部变量(方法内定义)
代码块局部变量(代码块内定义)
- 局部变量不属于任何类或实例,总是保存在栈内存中,保存基本变量的值和引用变量的地址
- 局部变量定义后,必须经过显式初始化后才能使用
- 能用代码块局部变量就不用方法局部变量
- 扩大了变量的作用域,这不利于提高程序的内聚性。
- 增大了变量的生存时间,这将导致更大的内存开销。
- 在方法中创建实例 var p = new Person()
实例化
- 个数可变的形参(只能有一个)
- 递归方法
- 基准情形
- 不断推进
- 向已知基准情形推进
- 设计法则
- 假设所有的递归调用都能执行
- 合成效益法则
- 求解同一问题时,切勿做重复性的工作
- 方法重载(Overload)
- 只要形参列表不同 ,可以有多个同名方法
- 两同一不同
- 方法名必须相同("两同"),但参数列表必须不同("一不同")
- 返回值不能区分重载
- toString()方法
- 返回对象的类名,后跟对象的散列码(hash code)
- 所有的Java对象都可以和字符串进行连接运算,系统自动调用Java对象
toString()方法的返回值和字符串进行连接运算
- equals()方法
- Object类提供的一个实例方法,经常被重写使用
- ➢自反性:对任意x, x.equals(x)一定返回true
- ➢ 对称性 : 对任意x和y, 如果y.equals(x)返回 true,则x.equals(y)也返回true
- ➢ 传 递 性 : 对任意x,y,z,如果x.equals(y)返回ture,y.equals(z)返回true,则x.equals(z)一定返回true
- ➢ 一致性:对任意x和y, 如果对象中用于等价比较的信息没有改变, 那么无论调用x.equals(y)多少次,返回的结果应该保持一致,要么一直是true,要么一直是false
- ➢ 对任何不是null的x, x.equals(null)一定返回false
成员
类成员
(必须通过类来访问、调用)
- 类成员(包括成员变量、方法、初始化块、内部类和内部枚举)不能访问
实例成员(包括成员变量、方法、初始化块、内部类和内部枚举)
- 类方法
- 修饰符 static (void)方法名(形参列表){
}
public static void test(){}
- 类变量
- public(修饰符) static 变量名(abcABC)
[=默认值]
- 类变量生存范围几乎等同于该类的生存范围
- 类变量必须通过类来访问
- 类初始化块
- 负责对类进行初始化,总是比实例初始化块先执行
- 执行类初始化块时,系统会按照继承关系逐级上溯(到java.lang.Object类),执行每个父类的类初始化块,然后才执行当前类的类初始化块。
- static {可执行性代码}
- 初始化块的修饰符只能是static,使用static修饰的初始化块被
称为类初始化块(静态初始化块),没有static修饰的初始化块被称
为实例初始化块(非静态初始化块)
- 构造器
- public(修饰符) 构造器名(形参列表)
{构造器执行体}(构造器必须与类名相同)
- 没有定义构造器,系统将为它提供一个默
认的构造器,系统提供的构造器总是没有参数的
- 通过new关键字来调用构造器,从而返回该类
的实例
- 一旦为一个类提供了构造器,系统将不再为该类提供构造
器
- 返回值是隐式的
- 构造器是创建对象的重要途径,Java类必须包含一个或一个以上的构造器。
- 构造器的重载
- 如果一个构造器B完全包含了另一个构造器A,可以使用this关键字来调用构造器A,不会创建新的对象实例。这称为构造器重载,在一个构造器中调用另一个构造器以共享初始化代码。可以避免重复的初始化代码,并确保对象的一致性。
- 内部类
- public class OuterClass
{
//此处可以定义内部类
}
- 作用
- ➢ 内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。假设需要创建Cow类,Cow类需要组合一个CowLeg对象,CowLeg类只有在Cow类里才有效,离开了Cow类之后没有任何意义。在这种情况下,就可把CowLeg定义成Cow的内部类,不允许其他类访问CowLeg
- ➢ 内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以互相访问。但外部类不能访问内部类的实现细节,例如内部类的成员变量
- ➢ 匿名内部类适合用于创建那些仅需要一次使用的类。对于前面介绍的命令模式,当需要传入一个Command对象时,重新专门定义PrintCommand和SquareCommand两个实现类可能没有太大的意义,因为这两个实现类可能仅需要使用一次。在这种情况下,使用匿名内部类将更方便
- 区别
- ➢ 内部类比外部类可以多使用三个修饰符:private 、protected、static.外部类不可以使用这三个修饰符
- ➢ 非静态内部类不能拥有静态成员
- 4个作用域:同一个类、同一个包、父子类和任何位置,可以使用任意访问控制符
如private、protected和public等修饰
- 成员内部类
- class文件总是这种形式 :
OuterClass$InnerClass.class
- 非静态内部类
- 可以直接访问外部类的private成
员
- 保存
了一个它所寄生的外部类对象的引用(当调用非静态内部类的实例方
法时,必须有一个非静态内部类实例,非静态内部类实例必须寄生在
外部类实例里)
- 访问某个变量时,方法内-内部类内-包含内部类的外部类内
- 如果外部类成员变量、内部类成员变量与内部类里方法的
局部变量同名,则可通过使用this、外部类类名.this作为限定来区
分
- 非静态内部类可以访问外部类的实例成员,但外部类无法直接访问非静态内部类的实例成员。如果外部类需要访问非静态内部类的实例成员,必须通过创建内部类的实例来实现
- 当外部类对象被实例化时,非静态内部类的对象并不会自动存在或被创建
- 创建一个非静态内部类的实例,那么它一定会关联到一个外部类对象
- 外部类可以独立存在,而不包含内部类的实例
- 不允许在外部类的静态成员中直接使用非
静态内部类
- 不允许在非静态内部类里定义静态成员
- 不可以有静态初始化块,但可以包含普通初始
化块。非静态内部类普通初始化块的作用与外部类初始化块的作用
完全相同
- 静态内部类(static
修饰,类内部类)
使用static修饰可以将内部类变成外部类
相关,而不是外部类实例相关
- 类可以包含静态成员,也可以包含非静态成员
- 静态内部类不能访问外部类的实
例成员,只能访问外部类的类成员
- 即使是静态内部类的实例方法也
不能访问外部类的实例成员,只能访问外部类的静态成员
- 静态内部类对象只持有外部类的类引用,没有
持有外部类对象的引用
- 允许在接口里定义内部类,接口里定义的内部
类默认使用public static修饰,也就是说,接口内部类只能是静态内
部类
- 使用内部类
- 在外部类内部使用内部类
- 通过 new 关键字调用内部类的构造器来创建实例
- 不要在外部类的静态成员(包括静态方
法和静态初始化块)中使用非静态内部类
- 可以在外部类内部定义内部类的子类
- 在外部类以外使用非静态内部类
- 访问控制权限
- Private访问控制权限:不允许在外部类以外访问
- Public访问控制权限:允许在任何地方访问。
- 省略访问控制符:只能在与外部类位于同一包中的其他类中访问。
- Protected访问控制权限:允许在与外部类位于同一包中的其他类和外部类的子类中访问
- 内部类的完整类名
- 在外部类以外使用内部类时,内部类的完整类名:OuterClass.InnerClass varName。如果外部类有包名,则还应该增加包名前缀
- 创建非静态内部类的实例
- 在外部类以外创建非静态内部类的实例时,需要使用外部类实例和 new 调用内部类的构造器
outerInstance.new InnerConstructor()
- 创建非静态内部类的子类的实例
- 当创建非静态内部类的子类时,子类的构造器需要调用父类内部类的构造器,并且在构造子类的实例时需要确保外部类的实例存在
- 注意事项
- 静态成员无法访问非静态内部类,因为非静态内部类依赖于外部类对象的存在,而静态成员不依赖于具体对象的存在
- 在创建非静态内部类的子类实例时,需要确保父类内部类的构造器可以被调用,这要求外部类对象必须存在
- 如果有一个非静态内部类的子类对象存在,则一定存在一个对应的外部类对象
- 在外部类以外使用静态内部类
- OuterClass.StaticInnerClass obj = new OuterClass.StaticInnerClass();
- 创建内部类对象时,静态
内部类只需使用外部类即可调用构造器,而非静态内部类必须使用外
部类对象来调用构造器
- 相比之下,使用静态内部类比使用非静态内部类要简单很多,
只要把外部类当成静态内部类的包空间即可。因此当程序需要使用
内部类时,应该优先考虑使用静态内部类
- 内部类的类名不再是简单地由内
部类的类名组成,它实际上还把外部类的类名作为一个命名空间,作
为内部类类名的限制。因此子类中的内部类和父类中的内部类不能
完全同名,即使二者所包含的内部类的类名相同,但因为它们所处的
外部类空间不同,所以它们不可能完全同名,也就不可能重写。
- 局部内部类
- 作用范围仅限于包含它的方法内部,在方法的外部无法使用它。由于其作用范围的限制,局部内部类不能使用访问控制符或 static 修饰符
- 作用域限制
- 生命周期仅限于方法的调用和执行过程。
- 局部内部类因其局部性质,在实际开发中很少被使用。由于其作用域限制,无法离开定义它的方法,因此大部分情况下,定义一个类后希望该类能够被多次复用,而局部内部类无法满足这种需求
- 局部内部类的class文件
总是遵循如下命名格式:OuterClass$NInnerClass.class
- N是一个数字,表示不同方法中的局部内部类
- 同一个类里不可能有两个同名的成员内部类,但在同一个类里可以有两个以上同名的局部内部类(位于不同方法中)
- 匿名内部类
- new 实现接口( )或 父类构造器(实参列表)
{
//匿名内部类的类体部分
}
- 通常用于一次性的类实例创建
但由于其局限性,不适用于需要多次复用的情况
- 匿名内部类必须继承一个父类或实现一个接口,但最多只能继承一个父类或实现一个接口
- 规则限制
- 匿名内部类不能是抽象类,因为它会立即创建匿名内部类的对象
- 匿名内部类不能定义构造器,但可以使用实例初始化块来完成构造器的功能
- 实现接口的匿名内部类
- 匿名内部类可以实现接口并且可以重写接口中的方法
- 对于接口的匿名内部类,不能显式定义构造器,只能通过无参构造器创建实例
- 继承父类的匿名内部类
- 匿名内部类可以继承一个父类并重写父类中的方法
- 对于继承父类的匿名内部类,可以有和父类相似的构造器,根据需要传入参数或不传参数
- 从Java 8开始,被匿名内部类访问的局部变量可以被隐式视为 final,但不能重新赋值
包装类(Wrapper Class)
- Java中一组用于包装基本数据类型的类,这些基本数据类型包括整数、浮点数、字符、布尔值等。包装类的主要作用是将基本数据类型转换为对象
- 可以直接将基本数据类型赋给包装类
必须注意类型匹配,Java会自动进行转换
- 自动装箱(Autoboxing)
- 把一个基本类型变量直接赋给对应的包装类变量,或者赋给Object变量(Object是所有类的父类,子类对象可以直接赋给父类变量)
- 自动拆箱(AutoUnboxing)
- 允许直接把包装类对象直
接赋给一个对应的基本类型变量
- 比较包装类实例与数值类型的值
- Integer num = 42; // 创建一个Integer对象
int value = 42; // 创建一个int值
if (num == value) { System.out.println(“相等”); } else { System.out.println(“不相等”); }
- 实现基本类型变量和字符串之间的转换
- 利用包装类提供的parseXxx(String s)静态方法
( 除Character之外的所有包装类都提供了该方法)
- String str = "123";
int num = Integer.parseInt(str); 将字符串转换为整数类型
- 利用包装类提供的valueOf(String s)静态方法
- String str = "123";
Integer num = Integer.valueOf(str); 将字符串转换为 Integer 对象
- 整数隐式转换为字符串
- int number = 42;
String str = number + “”; // 将int转换为字符串
单例类(Singleton)
一个类始终只能创建一个实例,则这个类被称为单例类
将类的构造器私有化,以防止外部类创建对象
提供一个public静态方法作为该类的访问点,在这个方法中创建实例对象
只能通过 Singleton.getInstance() 方法来获取单例对象
使用一个静态变量来缓存已创建的对象,确保每次调用 getInstance() 方法时都返回相同的实例
必须缓存已经创建的对象,否则该类无法知道 是否曾经创建过对象,也就无法保证只创建一个对象
需要 使用一个成员变量来保存曾经创建的对象,因为该成员变量需要被上 面的静态方法访问,故该成员变量必须使用static修饰
final修饰符
定义
既可以修饰成员变量(包括类变量和实例变量),也可以修饰局 部变量、形参
final修饰的变量不可被改变,一旦获得 了初始值,该final变量的值就不能被重新赋值
final修饰的成员变量必须 由程序员显式地指定初始值 对于final成员变 量,程序当然希望总是能访问到它固定的、显式初始化的值
final 成员变量必须在对象构造过程中确保被初始化。如果在构造器、初始化块中对 final 成员变量进行初始化,确保在使用之前初始化,否则会出现默认初始化的情况
final局部变量
如果final修饰的局部变量在定义时没有指定默认值,则可以在后 面代码中对该final变量赋初始值,但只能一次,不能重复赋值
final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变
可执行“宏替换”的final变量
定义一个final变量并在声明时指定一个编译时确定的初始值,编译器在编译时将使用宏变量的地方直接替换为其值,以提高性能和减少代码的复杂性
当一个变量满足下面三个条件时,它被认为是一个常量(constant)或直接量(literal)
➢ 使用final修饰符修饰。
➢ 在定义该final变量时指定了初始值。
➢ 该初始值可以在编译时就被确定下来。
Java会使用常量池来管理曾经用过的字符串直接量,例如执行var a=”java”;语句之后,常量池中就会缓存一个字符串”java”;如果程序再次执行var b=”java”;,系统将会让b直接指向常量池中的”java”字符串,因此a==b将会返回true
final方法
final类
不可变类
创建该类的实例后,该实例的实例变量是不可改变的
创建自定义的不可变类
➢ 使用private和final修饰符来修饰该类的成员变量。
➢ 提供带参数的构造器(或返回该实例的类方法),用于根据传入参数来初始化类里的成员变量。
➢ 仅为该类的成员变量提供getter方法,不要为该类的成员变量提供setter方法,因为普通方法无法修改final修饰的成员变量
如果有必要,重写Object类的hashCode()和equals()方法 equals()方法根据 关键成员变量来作为两个对象是否相等的标准,除此之外,还 应该保证两个用equals()方法判断为相等的对象的hashCode() 也相等
缓存实例的不可变类
接口和抽象类
抽象类
接口
接口是从多个相似类中抽象出来的规范,接口不提供任何实现。接口体现的是规范和实现分离的设计哲学
一个Java 源文件里最多只能有一个public接口,如果一个Java源文件里定义 了一个public接口,则该源文件的主文件名必须与该接口名相同
[修饰符] interface 接口名 extends 父接口1, 父接口2… { 零个到多个常量定义(只能是静态常量) 零个到多个抽象方法定义 (只 能是抽象实例方法、类方法、默认方法或私有方法) 零个到多个内部类、接口、枚举定义 零个到多个私有方法、默认方法或类方法定义 接口里不能包含构造器和初始 化块定义 }
修饰符可以是public或者省略,如果省略了public访问控制 符,则默认采用包权限访问控制符,即只有在相同包结构下才 可以访问该接口
接口名应与类名采用相同的命名规则
一个接口可以有多个直接父接口,但接口只能继承接口,不能 继承类
接口里的常量、方法、内部类和内部枚举都是public访问权限
私有方法
静态常量
在 接口中定义成员变量时,不管是否使用public static final修饰符, 接口里的成员变量总是使用这三个修饰符来修饰
在接口中定义的
内部类、内部接口、内部枚举,默认都采用public static两个修饰符
方法,只能是抽象方法、类方法、默认方法或私有方 法,自动为 普通方法增加abstract修饰符
普通方法,接口里的普通方法总是使用public abstract来修饰
成员变量,不管是否使用public static final修饰符, 接口里的成员变量总是使用这三个修饰符来修饰
完全支持多继承
使用接口
接口和抽象类
接口:作为系统与外界交互的规范,规定了实现者必须向外提供哪些服务(方法),以及调用者可以如何调用这些服务。在程序中使用接口时,它是多个模块间的耦合标准。在多个应用程序之间使用接口时,它是多个程序之间的通信标准。接口类似于整个系统的“总纲”,制定了系统各模块应该遵循的标准。因此,一旦接口被改变,对整个系统或其他系统的影响将是辐射式的,可能导致系统中大部分类都需要修改
抽象类:作为系统中多个子类的共同父类,体现了一种模板式设计。抽象类是系统实现过程中的中间产品,它已经实现了系统的部分功能(那些已提供实现的方法)。然而,这个中间产品依然不能作为最终产品,需要有进一步的完善
相同
差别
➢ 接口里只能包含抽象方法、静态方法、默认方法和私有方法,不能为普通方法提供方法实现;抽象类则完全可以包含普通方法
➢ 接口里只能定义静态常量,不能定义普通成员变量;抽象类里则既可以定义普通成员变量,也可以定义静态常量。
➢ 接口里不包含构造器;抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
➢ 接口里不能包含初始化块;但抽象类则完全可以包含初始化块
➢ 一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java单继承的不足
Lambda表达式 Lambda表达式基本结构
形参列表: 允许省略类型,如果只有一个参数可以省略括号
箭头符号 ->:将参数列表与Lambda表达式的主体分隔开
主体: 包含Lambda表达式要执行的语句
如果Lambda表达式只有一个语句,可以省略花括号 {}
如果Lambda表达式只包含一条返回语句,可以省略 return 关键字 Lambda表达式需要返回值,而它的代码块中仅有一条 省略了return的语句,Lambda表达式会自动返回这条语句的 值
Eatable eat = () -> System.out.println(“Eating”); // 一个没有参数的Lambda表达式 eat.eat();
Flyable fly = distance -> System.out.println(“Flying “ + distance + “ miles”); // 一个带有一个参数的Lambda表达式 fly.fly(1000);
Addable add = (a, b) -> a + b; // 一个带有两个参数的Lambda表达式 System.out.println(add.add(5, 3));
Lambda表达式与函数式接口
特点
目标类型:Lambda 表达式的类型,也就是它的目标类型,必须是函数式接口
函数式接口:函数式接口代表只有一个抽象方法的接口。Lambda 表达式实现的是这个唯一的抽象方法
@FunctionalInterface注解:这个注解放在接口定义前,用于告诉编译器该接口必须是函数式接口。如果接口不符合函数式接口的条件,编译器会报错
限制
确保 Lambda 表达式的目标类型是函数式接口
public void execute(Runnable r) { r.run(); }
execute(() -> System.out.println(“Executing…”));
- ➢ 使用函数式接口对Lambda表达式进行强制类型转换
Object obj = (Runnable)() -> System.out.println(“Converting to Runnable…”);
在Lambda表达式中使用var
方法引用与构造器引用
与匿名内部类的联系和区别
使用Lambda表达式调用Arrays的类方法 面向接口编程 简单工厂模式
对创建对象过程的封装。通过简单工厂,可以将对象的创建逻辑集中管理,提高系统的灵活性
命令模式
三大特征 封装
目的
隐藏和封装
private(当前类访问权限):只能在当前类的内部被访问
default(包访问权限):不使用任何访问控制符 修饰 访问控制的成员或外部 类可以被相同包下的其他类访问
protected(子类访问权限):那么这 个成员既可以被同一个包中的其他类访问,也可以被不同包中 的子类访问,通常是希望其子类来重写这个方法。
public(公共访问权限):如果一个成员(包括成员变量、方法和构造器等)或者一个外 部类使用public访问控制符修饰,那么这个成员或外部类就可 以被所有类访问
访问控制符的使用
类里的绝大部分成员变量都应该使用private修饰,只有一些 static修饰的、类似全局变量的成员变量,才可能考虑使用 public修饰。
有些方法只用于辅助实现该类的其他 方法,这些方法被称为工具方法,工具方法也应该使用private 修饰。
如果某个类主要用做其他类的父类,该类里包含的大部分方法 可能仅希望被其子类重写,而不想被外界直接调用,则应该使 用protected修饰这些方法
希望暴露出来给其他类自由调用的方法应该使用public修饰。
多态(Polymorphism)
它编译时类型所定义的成员变量,而不是它运行时类型所定义的成 员变量。
方法重写(override)
向上转型
Java允许将子类对象直接赋给父类引用变量,无需任何显式类型转换。这个过程被称为向上转型(Upcasting),向上转型由系统自动完成
引用变量的强制类型转换
继承
类的继承
每个子类只有一个直接父类
public class 子类名 extends 父类名 { // 子类的成员变量和方法 }
子类只能从被扩展的父类获得成员变量、方法和内部类(包括 内部接口、枚举),不能获得构造器和初始化块。
继承适用于那些具有”is-a”关系的类,即子类是父类的一种特例。
重写父类的方法
调用父类构造器
继承与组合(类复用)
父类应有良好的封装性,不会被子类随意改变
尽量隐藏父类的内部数据
不要让子类可以随意访问、修改父类的方法
尽量不要在父类构造器中调用将要被子类重写的方法
何时需要从父类派生新的子类
利用组合实现复用