5 面向对象
5.1 类和对象
可以把类当成一种自定义的引用类型
5.1.1 定义类
类是某一批对象的抽象,对象是一个具体存在的实体
定义类:
[修饰符] class 类名{ // 成员变量 // 成员方法 // 构造器 // 初始化块 // 内部类 }
- 类里面的各成员的顺序没有影响,可以相互调用
成员变量
[修饰符] 类型 成员变量名 (=初始值);
- 小驼峰命名
成员方法
[修饰符] 返回值类型 方法名(形参列表){ // 方法体 }
- 如果声明了返回值类型,方法体中必须有一个有效的return语句
- 小驼峰命名,建议以动词开头
static
修饰的成员表明它属于这个类本身,而不属于对象
构造器
[修饰符] 构造器名(形参列表){ // }
- 构造器是一个特殊的方法
- 构造器名必须和类名相同
- 实际上构造器是有返回值的,当使用
new
来调用构造器时,返回该类的对象 如果程序员没有为一个类编写构造器,则系统会提供一个默认的构造器
5.1.2 对象的产生和使用
创建对象的根本途径是构造器
#### 5.1.3 对象、引用和指针 **对于** `Person p = new Person();`这句代码,引用变量p里存放的仅仅是一个引用(地址),指向实际的对象,当访问p引用变量的成员变量和成员方法时,实际上访问的是p引用的对象的成员变量和成员方法。如下图: <iframe src="https://viewer.diagrams.net/?tags=%7B%7D&highlight=0000ff&edit=_blank&layers=1&nav=1&title=%E6%9C%AA%E5%91%BD%E5%90%8D%E7%BB%98%E5%9B%BE.drawio#R5ZnLcpswFIafhmUz3I2XieM0i3omHbfTZNVRQAG1AlFZxNCnrzASNxkbt7GdJtlEOrogfb84Ogdr1izOP1KQRgsSQKyZepBr1rVmmoZuefxfaSkqi2tPK0NIUSA6NYYl%2Bg3lSGHNUABXnY6MEMxQ2jX6JEmgzzo2QClZd7s9Edx9agpCqBiWPsCq9RsKWFRZPXPS2G8hCiP5ZMMV%2B4uB7Cx2sopAQNYtkzXXrBklhFWlOJ9BXMKTXKpxNwOt9cIoTNiYATBb2F8Tc3L3%2Febzl4VrObeL2QermuUZ4ExsWCyWFZIAn4XD5pWrdYQYXKbAL1vWXG9ui1iMec3gRTEVpAzmg2s06p3zIwNJDBkteBcxwHIFLHFapqK6btBbtrBFLeymJ4xAyB3WUzdEeEFAOQCQtx9QSEmWDm5eHEzwKLvrh0IxJl0ohqlSmW6DYh8LiqNA0XODK%2BO4E0%2FBw%2BkkASyn01%2FoDA0LpTIUzEx9HLOjIXNHvGhJcFl6LF5LSAK7YLoUYY7Yfav80Cpf5%2B1KISrV02CgeLsxWPkySUZ9uO9IqPhbuJ0tuKWNQgwYeu6ubZsG4gl3BPFV129I%2FVIVAye%2FWr4Y1faRciLZkQEaQqZ03Eheb%2BvvT8Hkfz4F51J32hNXP1Tc3jwn0tpQr46c%2F53AP%2B6%2BTuzeHVtfLy01Df2UvtGYqvfJq8NUR6RnwyT30MKUKpj4hlmXxYpR8hPOCCa08SlPCOOeCWAUJrzqc0CQ269KfIhHwpeiIUZBgIfiwK48L8DfsLr8LUvl727Bbx0Nv6ngT0AM364Ctt53FM6ZFVCzlTJ7ezcCmMa5BXDUcIXHEktRJZRFJCQJwPPG2sPS9PlESCr0%2BQEZK0TuDzJGuurJMEeWN2HOhbMz0OGEaVEPKisP7ZZm0KYmR%2FkZfd6stLlDdsZKwjQuODb1KuDYe8DV83BYAMXDR1C0OqRlhLMajq%2F6SbfMsIfiqH5%2FeSybk1Wt4EWjKXNbAuXi8kV%2F5IWwLGhzV7vUNc%2BTLfxJTeObdROG19PPO%2FdNuS3NUbRyNlq570sr%2B9VpNeIDVysl9TFYrZC%2FKyvtONF%2F8t36hTtxO%2F77wnIme3z4pnYHKeJ8SsHH5r1jfblM6%2FY68wFffpqc2e4lzbXPPjRpduzeREZvoiNn0aaaG3Iv8Ssrv5pf3TfFt%2BsynP7n3%2BOF4bza%2FB5RKdj8qmPN%2FwA%3D" allow-top-navigation="false" allow-forms="false" allowfullscreen="true" allow-popups="false" sandbox="allow-scripts allow-same-origin allow-popups"></iframe> **如果堆内存中的对象没有任何引用指向它,那么程序将无法访问该对象,这个对象就变成了垃圾,Java的垃圾回收器将回收该对象,释放该对象所占的内存** #### 5.1.4 对象的this引用 **Java提供的** `this`关键字可以理解为:是指向对象本身的一个引用 `this`的最大作用就是=让类中的一个方法访问该类的另一个方法或成员变量=,大部分时候,一个方法访问该类中定义的其他方法或成员变量时,加不加 `this`的效果是一样的
- 如果在
static
修饰的方法中使用this
关键字,则this
无法指向合适的对象,所以static
修饰的方法中不能使用this
,在静态方法中访问另一个普通方法,只能新建一个对象 - 如果方法中有局部变量和成员变量同名,又需要在这个方法中使用这个被覆盖的成员变量,就要使用
this
this
在构造器中代表正在被构造的对象5.2 方法详解
5.2.1 方法的所属性
Java里的方法不能独立存在,所有的方法都必须定义在类里,在逻辑上要么属于类,要么属于对象,执行方法时,必须使用类或对象作为调用者。
5.2.2 方法的参数传递机制
Java中方法的传参机制只有一种:=值传递=。所谓值传递就是将实际参数值的复制品传入方法内,而参数本身不会有任何影响。
5.2.3 形参个数可变的方法
从JDK1.5之后,如果在定义方法时,在最后一个形参的类型后,加
...
,则表明该形参可以接收多个参数值。public void method(int a, String... b)
- 一个方法中只能有一个可变形参
- 位置只能放在形参列表的最后
调用时,可以传入多个参数,也可以传数组
5.2.4 递归方法
5.2.5 方法重载
如果=同一个类=中包含了两个或两个以上=方法名相同=但=形参列表不同=的方法,被称为方法重载。
形参列表不同体现在:类型、个数、顺序
5.3 成员变量和局部变量
5.3.1 成员变量和局部变量
- 类变量从类的准备阶段开始存在,直到系统销毁这个类;实例变量从该实例被创建开始存在
- 类变量也可以让实例来访问,如果通过一个实例修改了类变量的值,会导致该类的其他实例访问这个变量时得到的是被修改之后的值
- 成员变量无需显式初始化,系统会在类的准备阶段或创建对象时默认初始化
- 局部变量除了形参外都需要显式初始化才能被访问
形参无需显式初始化,形参的初始化在调用方法时由系统完成:当调用方法时,系统会在该方法栈区为所有的形参分配内存空间,并将实参的值赋给对应的形参
5.3.2 成员变量的初始化和内存中的运行机制
略。注意类成员变量和实例成员变量的区别
5.3.3 局部变量的初始化和内存中的运行机制
局部变量定义后,必须经过显式初始化才能够使用,系统不会为局部变量默认初始化。定义局部变量后,系统并未为这个变量分配内存空间,直到这个变量被赋初值时,系统才会为这个局部变量分配内存,并将初始值保存在这块内存中
局部变量不属实任何类或实例。它总是被保存在其所在的方法的栈内存中。栈内存的变量无需垃圾回收,随着方法或代码块的结束而结束。
5.3.4 变量的使用规则
成员变量的使用情形:
- 用于描述某个类或某个对象的固有信息
- 用于保存该类或实例运行时的状态信息
用于在该类的多个方法间共享
使用局部变量也应该尽量缩小局部变量的作用范围,局部变量的作用范围越小,它在内存中停留的时间越短,程序运行的性能就越好
5.4 隐藏和封装
Java程序推荐将成员变量进行封装
5.4.1 理解封装
封装(Encapsulation),是面向对象的三大特征之一。它是指将对象的状态信息=隐藏=在对象内部,不允许外部程序直接访问对象内部消息,而是通过该类所=提供的方法=来实现对内部信息的操作和访问
封装的好处:
- 隐藏类的实现细节
- 让使用者只能通过预定的方法来访问数据,从而可以在方法里加入控制逻辑,限制不合理访问
- 可进行数据检查,从而保证对象信息的完整性
便于修改,提高代码的可维护性
为了实现良好的封装,应该:
- 将对象的成员变量和实现细节隐藏起来,不允许外部直接访问
把方法暴露出来,让方法来控制对这些成员变量进行安全的访问和操作
5.4.2 使用访问控制符
Java提供了4种访问级别,由小到大分别是:
private
<default
<protected
<public
private default protected public 同一个类中 ✔ ✔ ✔ ✔ 同一个包中 ✔ ✔ ✔ 子类中 ✔ ✔ 全局范围中 ✔ - 访问控制符用于控制一个类的成员是否可以被其他类访问,对于局部变量而言,其作用域就是它所在的方法,比不可能被其他类访问,所以无需访问控制符
对于外部类而言,它不在任何类的内部,它的上一级是包,要么在同一个包中要么不在同一个包中,所以它只有public或者默认
使用访问控制符的原则:
- 类里的绝大部分成员变量应该用
private
修饰,只有一些static
修饰的、类似于全局变量的成员变量,才考虑用public
修饰
有些方法只用于辅助实现该类的其他方法,被称作工具方法,也应该用private
修饰 - 如果某个类主要用作其他类的父类,该类里包含的方法仅希望被其子类重写,而不想被外界直接调用,应该使用
protected
修饰方法 希望暴露出来给其他类自由调用的方法应该用
public
修饰(类的构造器允许其他地方创建该类的实例,所以用public修饰)😎5.4.3 package、import、import static
各个公司提供的各种类,不可避免出现重名问题,怎么解决呢?
Oracle允许在类名前加一个前缀来限定这个类,Java引入了包(package)机制,提供了类的多层命名空间,用于解决类的命名冲突、类文件管理等问题
Java允许将一组功能相关的类放在用一个package下,组成逻辑上的类库单元,位于包中的每个类的完整类名都应该是包名和类名的组合(全限定类名)
package 包名;// 应放在第一个非注释行
5.4.4 Java的常用包
java.lang
:核心类。System Math String Threadjava.util
:工具类、集合。Arrays List Setjava.sql
:Java进行JDBC编程相关的类和接口java.io
:输入/输出相关类和接口java.net
:网络编程相关类和接口java.awt
:抽象窗口工具集,主要用于构建GUI程序java.text
:格式化相关的类java.swing
:Swing图形用户界面相关类和接口5.5 深入构造器
构造器是一种特殊的方法,用于创建实例时执行初始化。即使使用工厂模式、反射等方式创建实例,其本质依然是依赖于构造器,所以Java类必须有构造器。
5.5.1 使用构造器执行初始化
当创建一个对象时,系统会为这个对象的实例变量进行默认初始化(把所有的基本类型的成员变量初始化为
0
,把所有的引用类型的成员变量初始化为null
),如果想要改变这种默认初始化,就可以通过构造器实现当调用构造器时,系统会先为该对象分配内存空间,并执行默认初始化,对象已经产生。这个对象还不能被外部程序访问,只能在该构造器中通过
this
来引用如果程序员没有为一个类编写构造器,则系统会提供一个默认的无参构造器,这个构造器执行体为空;一旦程序员提供了自定义的构造器,系统就不再提供默认的构造器
构造器主要在其他类中用于创建类的实例,通常构造器的权限设成
public
,从而允许任何位置可以创建该类的对象。除非在极端的情况下,比如,设置成private
,阻止其他类创建该类的实例5.5.2 构造器重载
如果一个类里提供了多个形参列表不同的构造器,就形成了构造器的重载
构造器可以通过
this
来调用另一个构造器,且=必须放在第一行=public class Person { private String name; private int age; public Person(String name) { this.name = name; } public Person(String name, int age) { this(name);// here this.age = age; } }
5.6 类的继承
继承(Inheritance),是面向对象的三大特征之一,是实现软件复用的重要手段。Java的继承和C++不一样,是单继承。
5.6.1 继承的特点
子类 extends 父类/基类/超类
子类是一种特殊的父类(苹果是一种水果),父类的包含的范围比子类包含的范围要大
extends
作为继承的关键字,意思是‘扩展’,子类是对父类的扩展,是一种特殊的父类,可以获得父类全部的成员变量、成员方法和内部类(包括内部接口和枚举),但是不能获得父类的的构造器和初始化块Java的继承是单继承,就是只有一个直接父类,但是可以有多个间接父类。如果定义一个类的时候没有显式指定直接父类,则这个类默认继承java.lang.Object类。因此,java.lang.Object是所有类的父类,所有Java对象都可以调用java.lang.Object中的实例方法
5.6.2 重写父类的方法
子类包含与父类同名方法叫方法重写(override),也被称为方法覆盖
方法重写的原则:两同两小一大
- 两同:方法名相同,形参列表相同
- 两小:子类方法的=返回值类型=比父类的更小或相等;子类方法=抛出的异常类=比父类的更小或相等
- 一大:子类方法的=访问权限=比父类的更大或相等
- 要么都是类方法,要么都是对象方法
- 方法被定义为
final
不能被重写 如果父类的方法具有
private
权限,那么该方法对子类就是隐藏的,就不能被重写,就算子类中定义了与这个方法同名同参同返回的方法,依然不是重写而是一个新方法5.6.3 super限定
super
用于限定该对象调用从父类继承来的实例变量或方法- 当子类重写了父类的方法后,如果需要在子类中调用父类被覆盖的方法,可以使用
super
关键字
和this
一样,super
也不能出现在static
修饰的方法中,因为静态方法是属于类的,调用者是一个类而不是对象,因而super
就失去了意义 - 如果子类定义了和父类重名的实例变量,则会发生子类实例变量隐藏父类实例变量的情形(这时候会为子类对象分配两块内存,一块用于存储自身的实例变量,另一块用于存储继承来的实例变量)
如果被覆盖的是类变量,在子类中可以通过父类名作为调用者来访问被覆盖的类变量
如果在某个方法中访问名为a的变量,但没有显式指定调用者,查找a的顺序为:当前方法是否有名为a的局部变量 -> 当前类中是否有名为a的成员变量 -> 上溯当前类的所有父类是否有名为a的成员变量 -> 编译错误
当系统创建一个对象时,不仅会为当前类定义的实例变量分配内存,还会为从父类继承来的所有实例变量分配内存
5.6.4 调用父类的构造器
子类不会继承父类的构造器,但是子类的构造器可以调用父类构造器
class Base{ public double size; public String name; public Base(double size, String name) { this.size = size; this.name = name; } } public class Sub extends Base{ public String color; public Sub(double size, String name, String color) { super(size, name); this.color = color; } }
super调用父类构造器必须出现在子类构造器的第一行,所以super调用和this调用不能同时出现
不管是否使用super调用来执行父类构造器的初始化代码,子类构造器总会调用父类构造器一次:
- 使用super显式调用父类构造器
- 使用this显式调用本类中重载的构造器,执行本类中另一个构造器时会先调用父类构造器
系统会执行子类构造器之前,隐式调用父类的无参构造器
所以,创建任何对象,最先执行的总是java.lang.Object类的构造器
5.7 多态
Java的引用变量有编译时类型(由声明该变量时使用的类型决定)和运行时类型(由实际赋给该变量的对象决定),如果编译时类型和运行时类型不一致,就有可能出现多态。
5.7.1 多态性
因为子类是一种特殊的父类,所以Java允许把一个子类对象直接赋给一个父类类型的引用变量,无需类型转换,这被称为向上转型(upcasting)
相同类型的变量,调用同一个=方法=时呈现出多种不同的行为特征,这就是多态。与方法不同的是,实例变量不具多态性
5.7.2 引用变量的强制类型转换
引用变量只能调用它编译时类型含有的方法,而不能调用它运行时类型含有的方法,如果想要调用运行时类型的方法,必须把它强转为运行时类型
引用类型间的类型转换只能在具有继承关系的两个类型之间进行,考虑到进行强制类型转换有可能发生异常,因此在强转之前应先通过
instanceof
来判断一下5.7 .3 instanceof运算符
instanceof
的前面通常是一个引用类型的变量,后面是一个类或接口,它用于判断前面的对象是都是后面的类或者其子类、实现类的实例使用
instanceof
时,如果前后两者没有父子关系,就会引起编译错误❌5.8 继承与组合
5.9 初始化块
和构造器作用类似,用于对象的初始化操作
5.9.1 使用初始化块
(static) { // }
实例初始化块在创建对象时隐式执行,而且先于构造器执行;类初始化块在类初始化阶段自动执行
多个初始化块按顺序执行
当创建对象时,先为所有的实例变量分配内存(前提时该类已经被加载过),默认初始化。再然后执行初始化,顺序是:先执行实例初始化块或声明变量是的初始值,再执行构造器
5.9.2 实例初始化块和构造器
实例初始化块和构造器的作用相似,且优先于构造器执行
与构造器不同的是,实例初始化块是一段固定执行的代码,它不能接收参数,对所有对象执行的初始化操作都完全相同。
实际上实例初始化块是一个假象,代码经过编译后,实例出适合块中的代码会被“还原”到每个构造器的最前面
与构造器相同的是,创建对象时,不仅会执行当前类的实例初始化块,而且会一直上溯到java.lang.Object类
5.9.3 类初始化块
使用
static
修饰的代码块类初始化块在类初始化阶段执行,并且也会一直上溯到java.lang.Object类
6 面向对象
❌6.1 包装类
Java有8种基本数据类型,不具备“对象”的特性
Java为这8种基本数据类型分别定义了相应的引用类型,称之为包装类
基本数据类型 包装类 byte Byte short Short int Integer long Long char Character float Float double Double boolean Boolean 在JDK1.5之前,基本类型和包装类之间的转换需要借助包装类的方法,这有些繁琐。所以JDK1.5提供了自动拆装箱机制。
- 自动装箱(Autoboxing):把一个基本数据类型的值直接赋值给=对应的=包装类变量。例如:
Integer a = 1;
自动拆箱(AutoUnboxing):把包装类对象直接赋值给基本类型变量
包装类还可以实现基本类型和字符串之间的转换
字符串转基本类型
- 包装类提供的
parseXxx(String s)
静态方法 - 包装类提供的
valueOf(String s)
静态方法
- 包装类提供的
基本类型转字符串
String.valueOf()
基本类型 + “”
虽然包装类是引用数据类型,但包装类的实例可以与数值类型的值进行比较
6.2 处理对象
6.2.1 打印对象和toString方法
当使用
System.out.println()
方法打印对象时,实际上输出的是这个对象的toString()
方法的返回值toString()
是Object类中的一个实例方法,是一个“自我描述”的方法,通常用于这样的功能:输出该对象具有的状态信息直接打印一个对象得到的结果是这样的:
类名+@+hashCode
,这样并不能实现“自我描述”功能,所以需要重写Object的toString()
方法例如:
public String toString(){ return "Apple[color=" + color + ",weight=" + weighr + "]"; }
6.2.2 ==和equals方法
==
- 判断基本类型变量的数值是否相等
- 判断引用类型变量指向的是否是同一个对象
equals()
是Object类的一个实例方法,源码如下:public boolean equals(Object obj) { return (this == obj); }
因此,单纯的
equals()
并没有特殊的意义,如果希望自定义相等的标准,就要重写equals()
方法,比如:String重写了equals()
方法,它判断两个字符串相等的规则是字符序列相同- 通常重写
equals()
方法应遵循以下:
- 自反性
- 对称性
- 传递性
- 一致性
- null
6.3 类成员
static
6.3.1 理解类成员
类成员属于整个类,而不属于单个对象
当系统第一次使用该类时,系统会为该类变量分配内存空间并执行初始化,类变量开始生效,直到该类被卸载,该类变量分配的内存才会被垃圾回收机制回收
类变量既可以通过类来访问,也可以通过该类的对象来访问。但通过对象访问类变量时,实际上并不是访问该对象拥有的变量,因为系统创建对象时不会再为类变量分配内存执行初始化。
同一个类的所有对象访问类变量时,实际上访问的都是该类所持有的变量
当使用对象访问类成员时,实际上是委托该类来访问类成员,因此即使某个对象指向null,也依旧可以访问类成员;而一个null对象访问实例成员,则会出现
NullPointException
对于
static
而言,有一点非常重要:类成员不能访问实例成员。因为完全有可能出现类成员已经初始化完成,实例成员还没有初始化的情况❌6.3.2 单例(Singleton)类
6.4 final修饰符
final表示不可变
6.4.1 final成员变量
final修饰的成员变量,一旦有了初始值,就不能被重新赋值
final修饰的成员变量必须由程序员显式地指定初始值。要么在声明变量的时候指定,要么在初始化块,要么在构造器中指定
final修饰的成员变量在显式初始化之前不能直接访问,但是可以通过方法访问,这应该是一个缺陷,建议开发的时候避免在final成员变量显式初始化前访问它
6.4.2 final局部变量
final修饰的局部变量,无论是在定义的时候就指定值还是先定义再赋值都可以,但只能一次
final修饰的形参不能被赋值,它是由调用方法时传参来完成初始化的
6.4.3 final修饰基本类型变量和引用类型变量的区别
final修饰基本类型变量时,不能对基本类型变量重新赋值,也就是说基本类型变量不能改变了;final修饰引用类型变量时,只保证这个引用地址不变,即一直引用同一个对象,但是对象本身可以发生改变
理解:final只保证存的东西不能被改变
6.4.4 可执行“宏替换”的final变量
final变量只要满足以下三个条件,这个final变量就相当于一个直接量。这个final变量的本质就是一个“宏变量”,编译器会把程序中所有用到该变量的地方直接替换成该变量的值
- 通常重写
- 使用
final
修饰 - 定义变量时指定了初始值
该初始值可以在编译时就确定
总结:定义final变量时,指定可以在编译时就确定的初始值
例如:
final int a = 1;
对于这句代码而言,变量a就是不存在的,会直接用1替代6.4.5 final方法
final修饰的方法不可被重写(不是重载),如果不希望子类重写父类的某个方法,可以使用final修饰
对于父类的private方法,之前说过,子类无法访问,即使子类中有同名同参同返回的方法,那也是新方法,因此是否使用final修饰并没有什么影响
6.4.6 final类
final修饰的类不可以有子类,如果不希望某个类可以被继承,可以使用final修饰这个类
❌6.4.7 不可变类
❌6.4.8 缓存实例的不可变类
6.5 抽象类
类中定义方法用来描述类的行为方式,但在某种情况下,某个父类只知道子类应该包含什么样的方法,但不知道子类如何实现该方法。
(既然不知道子类中方法的具体实现,那父类中能不能直接不管呢?试想以下场景:父类引用指向子类实例,那么这个引用变量就无法调用这个方法,除非强转)
使用抽象方法就可以满足以上需求
6.5.1 抽象方法和抽象类
抽象方法是只有方法签名,没有方法实现的方法。有抽象方法的类必须被定义成抽象类,但是抽象类里可以没有抽象方法
抽象方法和抽象类的规则如下:
- 抽象方法和抽象类必须使用
abstract
修饰 抽象类不能被实例化,抽象类的构造器主要是用于被其子类调用
定义抽象方法:在普通方法上增加
abstract
,把普通方法的方法体替换成;
示例:
public abstract void test();
定义抽象类:在普通类上增加
abstract
利用抽象类和抽象方法的优势,可以更好地发挥多态的优势,使得程序更加灵活
抽象类只能被继承,抽象方法必须要重写,所以
abstract
和final
永远不可能同时使用静态方法不能被定义成抽象方法(调用一个没有方法体的方法会引起错误),所以
abstract
和static
不能同时修饰一个方法抽象方法必须被子类重写才有意义,否则这个方法永远不会有方法体,所以抽象方法不能是
private
权限,所以abstract
和private
不能同时使用6.5.2 抽象类的作用
抽象类是从多个具体类中抽象出来的父类,体现的是一种模板模式的设计,以这个抽象类作为子类的模板,避免子类设计的随意性,子类在抽象类的基础上进行扩展,但总体上会大致保留抽象类的行为方式
6.6 Java9改进的接口
6.6.1 接口的概念
接口定义了某一批类需要遵守的规范,接口不关心这些类的内部状态数据和方法实现细节,只规定这些类里必须提供某些方法。可见,接口是从多个相似类中抽象出来的规范,接口不提供任何实现,体现的是规范和实现分离的设计哲学
6.6.4 Java9中接口的定义
(public) interface 接口名 extends 父接口1, 父接口2 ... { // 成员变量(只能说静态常量) // 内部类、内部接口、内部枚举 // 抽象方法(无方法体) // 默认方法、类方法、私有方法(有方法体) }
一些说明
- 接口名应采用大驼峰命名方式,通常使用形容词
- 一个接口可以有多个直接父类接口,接口只能继承接口不能继承类
- 接口成员的可以省略访问控制修饰符,如果指定只能用
public
- 接口不能有构造器和初始化块
- 接口中定义的成员变量,系统会自动加上
public static final
修饰,且只能在定义时指定初始值 - 接口中定义的内部类、内部接口、内部枚举,系统会自动加上
public static
修饰 - 接口中定义的普通方法,系统会自动加上
public abstract
修饰,普通方法不能有方法体 - 接口中定义的默认方法出现在Java 8,必须使用
default
修饰;无论是否指定,默认方法总是public
;不能使用static
修饰,所以不能使用接口调用,只能使用接口的实现类的实例来调用。
默认方法就是有方法体的实例方法 - 接口中定义的类方法出现在Java 8,必须使用
static
修饰;无论是否指定,类方法总是public
;可以直接使用接口来调用 接口中定义的私有方法出现在Java 9,必须使用
private
修饰;Java 8允许带方法体的默认方法和类方法,势必有可能出现相同的实现逻辑代码,如果抽取出工具方法,工具方法应该被隐藏,就出现了私有方法6.3.3 接口的继承
接口支持多继承,获得父接口里的所有成员变量和抽象方法
6.6.4 使用接口
接口不能用于创建实例,但接口可以用于声明引用类型变量,这个引用类型变量必须引用到实现类的对象
接口的主要作用就是被实现类实现,实现使用
implements
关键字,实现一个接口可以获得接口里定义的成员变量、方法(包括抽象方和默认方法)一个类可以实现多个接口,这个类必须重写这些接口里所定义的全部抽象方法;否则,该类将保留从父接口实现来的抽象方法,这时候这个类就得定义成抽象类了
实现接口方法时,必须使用
public
修饰(子类方法不能缩小父类方法的访问权限)6.6.5 接口和抽象类
接口体现的是一种规范。对于接口的实现者而言,接口规定了实现者必须向外界提供哪些服务;对于接口的调用者而言,交口规定了调用者可以调用哪些服务以及如何调用这些服务。当在一个程序中使用接口时,接口是多个模块间的耦合标准;当在多个应用程序间使用接口时,接口是多个程序间的通信标准
抽象类体现的是一种模板式设计。抽象类作为多个子类的共同父类,可以当成系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能(已经实现的方法),但这个产品不能当成最终产品,必须进一步完善,这种完善可能有不同的形式
使用上的异同:
相同点
- 都不能被实例化,用于被其他类继承或实现
- 都可以包含抽象方法,继承抽象类或实现接口的普通类必须实现这些抽象方法
不同点
- 接口的成员变量必须是静态的;抽象类的成员变量可以不是静态的
- 接口不能有有方法体的普通方法而抽象类可以有
- 接口没有构造器而抽象类可以有
- 接口没有初始化块而抽象类可以有
- 接口是多继承,抽象类是单继承
❌6.6.6 面向接口编程
6.7 内部类
定义在其他类内部的类叫做内部类,包含内部类的类就被称为外部类
内部类的好处:
- 提供了更好的封装。内部类隐藏在外部类内部,不允许同一个包的其他类访问(比如Person 和 Leg)
- 内部类可以直接访问外部类的私有数据,但是外部类不能访问内部类的细节
匿名内部类适用于仅需要使用一次的类
内部类相比于外部类可以多使用以下三种修饰符:
private
`protected`static
6.7.1 非静态内部类
定义一个内部类仅需要把一个类放在另一个类内部即可,这个“内部”可以是任意位置,甚至是方法中(方法中的内部类叫局部类)
大部分时候,内部类都是被作为成员内部类定义而不是局部内部类。既然是成员,就可以使用访问控制符
使用
static
修饰的叫做静态内部类,反之就是非静态内部类成员内部类的class文件总是这种形式:
OuterClass$InnerClass.class
在非静态内部类的对象里,保存了一个它所寄生的外部类对象的引用
当在非静态内部类的方法中使用某个变量时,查找的顺序是:该法内的局部变量 -> 内部类的成员变量 -> 外部类的成员变量 -> 编译错误:提示找不到该变量
如果外部类成员变量、内部类成员变量、内部类方法局部变量重名,可以使用
this
、外部类类名.this
区分非静态内部类可以直接访问外部类的成员,但是反过来不行(创建外部类对象的时候,非静态内部类的对象还不存在)。如果外部类想要访问非静态内部类的实例成员,必须显式创建非静态内部类的的对象
根据静态成员不能访问非静态成员的规则,外部类的静态成员(静态方法、静态代码块)不能访问非静态内部类
非静态内部类不能拥有静态成员
6.7.2 静态内部类
使用
static
修饰的内部类叫静态内部类,也叫类内部类。静态内部类属于外部类本身,不属于外部类的某个对象静态内部类可以包含静态成员,也可以包含非静态成员。根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员
静态内部类是外部类的一个静态成员,所以外部类的所有方法、初始化块中都可以使用静态内部类来定义变量、创建对象
外部类不能直接访问静态内部类的成员,但可以使用静态内部类的类名来访问静态内部类的成员
❌6.7.3 使用内部类
6.7.4 局部内部类
定义在方法里的内部类叫局部内部类,局部内部类仅在方法中有效
如果需要用局部内部类定义变量、创建实例或派生子类,都只能在局部内部类所在的方法内部进行
局部内部类的class文件总是这种形式:
OuterClass$NInnerClass.class
,$
后面多了一个N
,因为同一个类里不能出现同名的成员内部类,但是可以出现同名的局部内部类(不在同一个方法中),使用数字做区分实际开发中很少使用局部内部类
6.7.5 匿名内部类
匿名内部类适用于那种只需要使用一次的类
定义方法:
new 接口() | 父类(){ // }
定义匿名内部类时无须
class
关键字,而是在定义匿名内部类时直接生成该匿名内部类的对象注意:
- 匿名内部类不能是抽象类(因为抽象类不能被创建对象,而系统创建匿名内部类时,会立即创建匿名内部类的对象)
匿名内部类不能定义构造器(匿名内部类没有类名,就无法定义构造器)
匿名内部类不能是抽象类,所以匿名内部类必须实现接口或抽象父类里的全部抽象方法,如果有需要,可以重写抽象父类种的普通方法
当通过实现接口来创建匿名内部类时,匿名内部类不能显式地定义构造器,因此匿名内部类只有一个隐式的无参构造器;当通过继承抽象父类来创建匿名内部类时,匿名内部类将拥有和父类相似的构造器,相似指的是有相同的形参列表
如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用了
final
修饰6.8 Java11增强的Lambda表达式
Lambda表达式是Java8的新特性,支持将代码块作为方法参数,允许使用更简洁的代码来创建只有一个抽象方法的接口的实例
6.8.1 Lambda表达式入门
Lambd表达式就相当于一个匿名方法,当使用Lambda表达式代替匿名内部类创建对象时,Lambda表达式的代码块就会替代匿名内部类中实现抽象方法的方法体
由三部分组成
- 形参列表。允许忽略形参类型;只有一个形参的时候,圆括号都可以省略
- ->。
代码块。代码块只有一条语句的话可以省略
{}
;Lambda表达式需要返回值,只有一条语句的话可以省略return
Lambda表达式整体就像一个“任意类型”的对象
6.8.2 Lambda表达式与函数式接口
Lambda表达式的类型被称为“目标类型(target type)”,Lambda表达式的目标类型必须是“函数式接口(functional interface)”,函数式接口是只包含=一个抽象方法=的接口(可以有多个默认方法、类方法)
如果采用匿名内部类创建函数式接口的实例,只需要实现一个抽象方法,这时可以采用Lambda表达式来创建对象,创建出来的对象的目标类型就是这个函数式接口
Java 8提供了
@FunctionalInterface
注解,用于编译器检查是否是函数式接口6.8.3 在Lambda表达式中使用var
6.8.4 方法引用与构造器引用
如果Lambda表达式中只有一条语句,还可以在代码块中使用方法引用和构造器引用
用
::
表示引用类方法
形式:类名::类方法
说明:函数式接口中被实现方法的全部参数传给该类方法作为参数
对应的Lambda表达式:(a,b,...) -> 类名.类方法(a,b,...)
示例:@FunctionalInterface interface Converter{ Integer convert(String from); } // 使用Lambda表达式创建对象 Converter converter = from -> Integer.valueOf(from); // 可以写成 Converter converter = Integer::valueOf; Integer number = converter.convert("100");// 100
引用特定对象的实例方法
形式:特定对象::实例方法
说明:函数式接口中被实现方法的全部参数传给该类方法作为参数
对应的Lambda表达式:(a,b,...) -> 特定对象.实例方法(a,b,...)
示例:@FunctionalInterface interface Converter{ Integer convert(String from); } // 使用Lambda表达式创建对象 Converter converter = from -> "fkit.org".indexOf(from); // 可以写成 Converter converter = "fkit.org"::indexOf;
3. **引用某类对象的实例方法**
**形式:**`类名::实例方法`
**说明:函数式接口中被实现方法的第一个参数作为调用者,后面的参数传给该实例方法作为参数**
**对应的Lambda表达式:**`(a,b,...) -> a.实例方法(b,...)`
4. **引用构造器**
**形式:**`类名::new`
**说明:函数式接口中被实现方法的全部参数传给该类方法作为参数**
**对应的Lambda表达式:**`(a,b,...) -> new 类名(a,b,...)`
#### ❌6.8.5 Lambda表达式与匿名内部类的联系与区别
#### ❌6.8.6 使用Lambda表达式调用Arrays的类方法
### 6.9 枚举类
**实例个数有限且固定的类被称为枚举类,比如季节**
#### 6.9.1 手动实现枚举类
**在早期的代码中,可能会使用简单的静态常量来表示枚举**
**也可采用定义类的方式来实现**
#### 6.9.2 枚举类入门
### ❌6.10 对象与垃圾回收
### ❌6.11 修饰符的适用范围
### ❌6.12 多版本JAR包
## 反射
8 条评论
?未来展望类?
文章紧扣主题,观点鲜明,展现出深刻的思考维度。
你的文章让我心情愉悦,每天都要来看一看。 https://www.4006400989.com/qyvideo/64694.html
《精忠岳飞》国产剧高清在线免费观看:https://www.jgz518.com/xingkong/37547.html
你的文章充满了智慧,让人敬佩。 http://www.55baobei.com/DRVeveO2Ng.html
传奇私服网页无法打开问题诊断与解决方法探讨:https://501h.com/jingpin/2024-10-23/44249.html
叼茂SEO.bfbikes.com
表评论7714