封装:将数据和行为组合成一个对象,并向对象的使用者隐藏实现细节
-
类的细节
-
一个对象变量并没有实际包含一个对象,Java中任何对象变量的值都是对存储在另外一个地方的一个对象的引用,new操作符的返回值也是一个引用
-
如果需要返回一个可变数据域的拷贝,就应该使用clone()
-
局部变量不会自动初始化为null,必须通过new或将他们设置为null来初始化
-
-
构造器
-
如果构造器中没有显式给域赋初值,那么就会被自动赋予初值:数值为0,布尔值为false,对象引用为null
-
构造器的具体处理步骤
-
所有数据初始化为默认值(0,false或null)
-
按照类声明中出现的次序,执行所有域初始化语句和初始化块
-
如果构造器第一行调用了第二个构造器,则执行第二个构造器主体(其他构造器调用必须在第一行,如果想同时调用父类构造器和本类其他构造器会报错,可以另外定义函数解决)
-
执行本构造器主体
-
-
-
方法与静态方法
-
方法
-
除了有括号内的显式参数,还有出现在方法名前的类对象,被称为隐式参数this,它是调用该方法的对象本身的引用,有些人把隐式参数称为方法调用的目标或者接收者
-
方法参数
-
按值调用:方法接收的是调用者提供的值
-
按引用调用:方法接收的是调用者提供的变量地址
-
Java总是采用按值调用,方法不能修改传递给它的任何参数变量的值
-
然而方法参数有两种类型:基本数据类型和对象引用
-
对于基本数据类型,方法无法修改参数的值
-
而对象引用可以被方法修改对象中的实例域(这里的参数是对象地址,而修改的是对象的值)
-
验证Java按值引用的例子:无法通过swap(Object o1, Object o2)来交换这两个对象
-
-
-
方法签名:方法名和方法参数类型构成方法签名,返回类型不是方法签名的一部分
-
-
静态方法
-
是一种不能操作对象的方法,它属于类而不属于类的对象。可以看作是一种没有隐式参数this的方法,静态方法可以访问自身类中的静态域
-
不推荐使用对象来调用静态方法,使用对象调用时没有多态,引用是什么类型就会去调对应类型的静态方法
-
-
-
static实例域
-
属于类而不属于类对象
-
每个类中只有一个这样的域,所有的类对象共享
-
-
final实例域和方法
-
定义为final的实例域在构建对象时必须初始化,并且在之后的操作中,不能再对它进行修改
-
这里的修改指的是不能将变量中的对象引用指向其他对象,但是被引用对象的状态是可以改变的
-
声明为final的方法子类不能覆盖
-
总结
-
修饰类:不能被继承
-
修饰方法:不能被覆盖
-
修饰变量:不能被改变
-
-
-
继承:复用已存在类的域和方法
-
super关键字
-
使用super来调用父类方法
-
super与隐式参数this不同,因为super并不是一个对象的引用,它只是一个指示编译器调用父类方法的特殊关键字
-
使用super调用构造器的语句必须是子类构造器的第一条语句
-
-
抽象类
-
包含一个或多个抽象方法的类必须声明为抽象的
-
除了抽象方法,抽象类还可以包含具体数据和具体方法
-
抽象类不能被实例化,但是可以定义一个抽象类的对象变量,它只能引用一个非抽象的子类对象
-
-
getClass()
-
返回一个对象所属的类
-
返回的是变量实际指向的对象的类,比如父类变量引用子类对象,那么返回的其实是子类(和多态一样实际操作的对象是隐含参数this)
-
-
equals()
-
编写一个完美的equals()方法
-
检测隐式参数this和显式参数otherObject是否引用同一个对象
-
检测otherObject是否为null
-
比较隐式参数this和显式参数otherObject是否是同一个类
-
若equals语义在每个子类中有所改变,就用getClass()是否是相同的(子)类
-
否则用instanceof 是否是同一个父类
-
-
开始比较域,基本类型用==,对象域使用equals()
-
-
如果在子类中重新定义equals,就要在其中包含调用super.equals()
-
如果重新定义equals,那么必须重新定义hashCode方法
-
因为equals与hashcode的定义必须一致,如果x.equals(y)返回true,那么x.hashCode()就必须与y.hashCode()具有相同的值
-
null安全的方法Objects.hashCode(obj),参数为null会返回0
-
组合多个散列值的方法,调用Objects.hash(param1, param2...),这个方法会对各个参数调用Objects.hashCode(),并组合这些散列值
-
数组类型的域,可以使用Arrays.hashCode()方法
-
标准库中你会看到很多成对出现的接口和使用工具类,如Object/Objects, Collection/Collections, Path/Paths等
-
-
int的==陷阱
-
整形变量使用 == 运算符比较时会进行自动装箱,使用Integer.valueof()来创建Integer实例,然后就是两个Integer对象的比较了
-
而整数类型在-128~127之间时,会使用缓存(在IntegerCache类中有一个Integer数组,用以缓存当数值范围为-128~127时的Integer对象)
-
如果已经创建了一个相同的整数,那么创建一个新的相同整数时不会使用new关键字,而是用已经缓存的对象,两个对象指向同一地址
-
所以此时 == 会相等
-
-
-
覆盖父类的方法
-
子类覆盖父类方法,可见性不能比父类方法可见性低
-
原因是这样与多态冲突:假设现在有一个父类变量指向一个子类对象,调用子类中重写的方法。如果此时子类中可见性更低,那么方法就无法被访问,这样多态就失效了
-
多态:一个对象变量可以指示多种实际类型的现象
-
动态绑定:运行时能自动地选择调用哪个方法的现象。
- 比如父类和子类都有getSalary()方法,现声明一个父类对象Employee e,而将它指向一个子类对象引用Manager m。那么调用e.getSalary()时实际调用的是Manager的方法(因为是通过this自引用来调用对象的方法)
-
若父类变量指向子类对象引用,调用子类定义而父类未定义的方法是非法的,因为此方法不是父类的方法
- 理解方法调用
-
编译器查看对象的声明类型和方法名,这一步将得到该类和父类中public的所有名为f的方法(所有可能被调用的方法)
-
将调用时提供的方法参数与上一步找到的所有方法参数匹配,这被称为重载解析。若找到多个匹配则会报错。
- 重载调用哪个方法,和参数的引用类型相关,和实际指向的类型无关
-
根据隐式参数的实际类型确定调用方法指令
-
动态绑定确认最适合的被调方法
-
- 理解方法调用
-
总结:能够调用哪些方法取决于引用,实际调用的方法取决于引用指向的对象,无论调用哪个方法,都是在一个实际的对象上执行的,即隐含参数this执行