文章目录
- Ch.V 继承:
- 5.1 类, 超类和子类:
- **其中几个关键的名词**:
- **继承方法**:
- **继承的访问限制:**
- **继承中方法的覆盖与访问:**
- **子类构造函数:**
- 虚函数:
- Java多态:
- 阻止继承&覆盖:
- 强制类型转换:
- 抽象类:
- 受保护的访问:
- 5.2 Object 所有类的超类:
- equals方法:
- 相等测试与继承:
- override强制覆盖:
- hashCode方法:
- toString方法:
- 5.3泛型数组列表:
- 类型化与原始数组列表:
- 5.4 对象包装器与自动装箱:
- 5.5 参数数量可变的方法:
- 5.6 枚举类:
- 5.7 反射:
- 5.8 继承设计技巧:
Ch.V 继承:
5.1 类, 超类和子类:
其中几个关键的名词:
- 超类=基类=父类
- 子类=派生类=孩子类
继承方法:
与C++相对应的是, Java中使用extends代替了C++中的冒号:
public class Manager extends Employee
{
添加方法和域
}
继承的访问限制:
Java中没有C++的继承访问限制, 所有的类都是public公有继承
继承中方法的覆盖与访问:
java中同样也存在这种问题, 子类的作用域内的同名方法会覆盖父类中的方法
C++使用父类名称+作用域运算符
解决
java使用特有的关键字 super
public double getSalaryO
{
double baseSalary = super.getSalary() ; //使用super关键字, 调用父类(超类)中的方法
return baseSalary + bonus;
}
super
实际上是编译器的特殊关键字, 但是其在使用方法上部分类似于this引用, 就是不能通过super赋值
子类构造函数:
使用super调用父类(超类) 的构造函数:
public Manager(String name, double salary, int year, int month, int day)
{
super(name, salary, year, month, day) ;
bonus = 0;
}
所以super与this有相似的特性:
- 用来访问对应类的成员
- 用来调用对应类的构造器
虚函数:
Java中, 不需要将方法声明为virtual
, 所有的方法都是默认虚拟的 , 都可以通过指针执行动态绑定
如果想要阻止动态绑定, 需要使用final
Java多态:
由于Java类对象的实质是指针的缘故, 所以每一个类都默认支持多态, 同时也遵循C++中关于多态的各种特性
关于是否需要多态, 不同的语言支持的点不同
- C++所有的方法默认都不是多态的(需要使用virtual变成多态)
- Java所有的方法都是多态的, 需要使用final取消多态
总而言之是两个极端
阻止继承&覆盖:
Java中使用final修饰类或方法, 使其无法被继承或覆盖:
public final class Executive extends Manager
{
...
}
public class Employee
{
public final String getName(){
return name;
}
}
注意, 如果一个类被声明为final, 其中的所有方法都变成final, 但是所有的域不是final (这两概念不同)
强制类型转换:
Java提供的强制类型转换的语法与C语言相同, 但是内部的功能类似于C++的dynamic_cast, 转换失败时会抛出ClassCastException异常:
Manager boss = (Manager) staff[1]; // Java
//等价于
Manager* boss = dynamic_cast<Manager*>(staff[1]); // C++
由于如果转换失败的话. 会抛出异常, 没有处理异常会导致程序终止( 与C++相似)
所以可以通过instanceof
运算符进行前置判定:
if (staff[1] instanceof Manager)
{
Manager boss = (Manager) staff[1];
...
}
instanceof为双目运算符:
boolean result = obj instanceof Class
其中 obj 为一个对象,Class 表示一个类或者一个接口
- 当obj无法转换为Class类型时, 编译器直接报错
- 当可以转换为Class类型时, 会根据运行时的动态绑定来判定:
当 obj 为 Class 的对象,或者是Class直接或间接子类,或者是Class接口的实现类,结果result 都返回 true,否则返回false
更好的记忆法: 检测的是obj是否与Class在同一个继承体系中, Class在obj的上层
有如下继承体系:
inter1
SuperClass
Class1
Class2
Class3
Class4
obj为Class2类型实例
inter1为interface
obj instanceof inter1; //always true
obj instanceof superClass; //always true
obj instanceof Class1; //Error, 无法转换
obj instanceof Class2; //always true
obj instanceof Class3; //Error, 无法转换
obj instanceof Class4; //always false
抽象类:
C++中, 使用virtual
+ =0
将一个函数表示为纯虚函数, 而包含纯虚函数的类自然就变成了抽象类
Java中, 使用abstract
关键字将一个类或一个方法设置为抽象方法
如果类中含有abstract抽象方法, 则这个类必须用abstract
设置为抽象类, 反之不然
受保护的访问:
Java中也有protected访问修饰符, 但是功能上与C++有所不同:
- 仅对本类可见 private
- 对所有类可见 public
- 对本包和所有子类可见 protected
- 对本包可见—默认, 不需要修饰符
而C++中的protected是对本类和子类可见
相比之下, Java的protected的封装性会差一点
5.2 Object 所有类的超类:
Object类为Java中所有的类的基类(超类)
其中的方法在所有的类中都是通用的 , 所以需要学习这部分的用法
拓展说明: 所有的自建类也是Object的子类
即使是没有显式的说明, 也会继承与Object类:
public class Employee extends Object{
...
}
//等价于:
public class Employee{
...
}
equals方法:
用于检测两个类对象是否相等
检测的方法是判断两个对象是否具有相同的引用, 即判断两个指针是否指向同一块内存区域
Java中规定equals应该具有以下特征:
在编写自定义类的equals方法时应当具备以下特点, 才是一个合格的equals
- 自反性:
对于任何非空引用 x, x.equals(x) 应该返回 truec - 对称性:
对于任何引用 x 和 y, 当且仅当 y.equals(x) 返回 true, x.equals(y) 也应该返回 true - 传递性:
对于任何引用 x、 y 和 z, 如果 x.equals(y) 返回 true, y.equals(z) 返回 true, x.equals(z) 也应该返回 true。 - 一致性:
如果 x 和 y 引用的对象没有发生变化,反复调用 x.equals(y) 应该返回同样的结果。 - 与null的对比:
对于任意非空引用 x, x.equals(null) 应该返回 false
相等测试与继承:
这里给出了在自定义类内设计equals方法的建议:
- 显式参数命名为 otherObject, 稍后需要将它转换成另一个叫做 other 的变量。
- 检测 this 与 otherObject 是否引用同一个对象:
if (this = otherObject) return true;
这条语句只是一个优化, 但实际上,这是一种经常采用的形式。因为计算这个等式要比一个一个地比较类中的域所付出的代价小得多。
- 检测 otherObject 是否为 null, 如 果 为 null, 返 回 false。这项检测是很必要的。
if (otherObject = null) return false;
- 比较 this 与 otherObject 是否属于同一个类
如果 equals 的语义在每个子类中有所改变,就使用 getClass 检测:
if (getClass() != otherObject.getClass()) return false;
如果equals在所有的子类都拥有统一的语义,就使用 instanceof 检测:
if (!(otherObject instanceof ClassName)) return false;
这里是因为, 如果equals的语义不同, 则代表每个子类对象互相无法比较, 所以当比对的对象不属于同一个子类时, 直接返回false, 否则继续比较
而equals语义相同时, 则和上头相反, 需要检测的仅仅是两个比对对象是否在一个继承关系中且为上下级关系
- 将 otherObject 转换为相应的类类型变量:
ClassName other = (ClassName) otherObject;
转化为相同类型, 以使用相同的equals函数
- 现在开始对所有需要比较的域进行比较了:
使用= 比较基本类型域,使用 equals 比较对象域。如果所有的域都匹配, 就返回 true; 否 则 返 回 false。
return fieldl == other.field&& Objects.equa1s(fie1d2, other.field2)
如果在子类中重新定义 equals, 就要在其中包含调用 super.equals(other)
override强制覆盖:
C++ 中 , 在类成员函数后头加上override可以指示编译器这个成员函数覆盖了基类中的相应成员
Java 中, 同样有类似的操作
使用@Override
如果由于各种错误而导致方法没有成功的将超类中的方法覆盖, 则编译器会给出警告
@Override public boolean equals(Object other);
hashCode方法:
关于hashCode的作用:
对于包含容器类型的程序设计语言来说,基本上都会涉及到hashCode。在Java中也一样,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable
具体见Ch.9
简而言之, hashCode主要用作查找上, 由于hash表的查找效率为o(1), 所以效率非常高
当需要在一个集合中查找有无重复元素时, 使用hash表查找远比equals的效率高
hashCode生成的方法:
Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值
Java中hashCode的规定:
最核心的是与equals()的一致性:
- 在一个应用程序运行期间,假设一个对象的equals方法做比较所用到的信息没有被改动的话。则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
- 假设两个对象依据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生同样的整数结果。
- 假设两个对象依据equals(Object o)方法是不相等的。则调用这两个对象中任一个对象的hashCode方法。不要求产生不同的整数结果。但假设能不同,则可能提高散列表的性能。
所以, 每一次修改equals()后, 都需要修改hashCode() 的实现方法
API的话, 要用的时候在查吧
toString方法:
将类对象的信息转换为String
实际上, 无论你是否愿意, 只要是公有接口中需要将类转换为String的, 都会调用类的toString方法
比如:
fucker testIII=new fucker();
System.out.println(testIII);
//调用fucker类的toString方法
以及使用了+
将任意字符串和类对象拼接的地方
Java中规定的toString的标准格式 (只是推荐, 并不一定遵守):
类的名字,随后是一对方括号括起来的域值
最为方便的是, 可以使用IDEA自动生成对应的toString方法:
@Override
public String toString() {
return "fucker{" +
"num3=" + num3 +
'}';
}
生成字符串的快捷方法:
由于使用了+
将任意字符串和类对象拼接的地方都会调用toString方法, 所以可以这么用:
String outStr= ""+classItem;
将classItem与一个空串进行拼接
最后, 强烈建议为每个类添加toString方法
这玩意是良好的调试与日志输出工具
5.3泛型数组列表:
Java中提供了ArrayList
类, 可以在添加和删除元素时, 自动调节数组容量大小, 而不需要编写其他的任何代码
这玩意实际上相当于C++中的STL泛型容器vector, 使用起来也差不多
ArrayList<Employee> staff = new ArrayList<Employee>();
//二者等价:
ArrayList<Employee> staff = new ArrayListo<> ();//在两头都是相同的容器时可以省略
//但还是不推荐使用
顺便一提, 类型化泛型容器时, 类型参数不允许是基本类型, 如果想使用基本数据类型, 需要使用其包装器:
ArrayList<Integer> list = new ArrayList<>();//正确
ArrayList<int> list = new ArrayList<>();//错误
包装器看5.4节
这玩意的更多功能的使用, 自行查看API, 用多少学多少
这里仅仅对一些常用的进行学习:
- 使用add()方法添加元素
- 使用set()方法修改元素
- 使用get() 方法获取(返回)元素
类型化与原始数组列表:
Java中允许泛型容器的参数列表为空, 此时容器变成原始容器
老版本的Java中原先就是没有泛型的
public class EmployeeDB
{
public void update(ArrayList list) { . . . } //update的参数为原始的ArrayList
public ArrayList find(String query) { . . . }
}
可以将类型化的ArrayList直接作为参数传递给update
编译器不会给出任何的警告, 但是这样的调用并不安全!
有可能在update函数中处理时出现异常
而反过来将原始的ArrayList赋值给类型化的ArrayList, 会得到一个警告, 仍然可以编译
出于版本以及素质考虑, 最好是不要使用这种B方法, 容易出问题!
5.4 对象包装器与自动装箱:
包装器的作用;
实际上就是将基本数据类型包装成一个类, 方便在需要类的地方使用( 如ArrayList的类型参数)
包装器的特点:
- 包装器构造出来的对象都是final常量, 不可以更改其中的值
- 包装器类都是final, 不可以派生出子类
- 使用基本数据类型构造包装器时 , 会发生自动装箱
list.add(3);
//将自动地变换成
list.add(Integer.value0f(3));
反之将包装器赋值给基本数据类型时, 会发生自动拆箱
int n = list.get(i);
//被翻译成
int n = list.get(i).intValue();
以上的工作均有编译器完成, 而非执行时的虚拟机
包装器的效率问题:
在ArrayList中, 使用包装器的效率远远低于相应类型的数组
因为包装器将数据包装在类中了
ArrayList<Integer> list = new ArrayList<>();
//效率远低于:
int[] list= new int[];
包装器的==
比较:
由于包装器是类对象, 所以遵守类对象的比较规则
即==比较的是其是否指向同一块内存区域, 即比较的是两指针的值
解决这个问题的方法是使用包装器的equals()函数
包装器中的方法:
包装器中同样嵌入了一些方便好用的方法, 比如将String转化为int的静态方法:
int x = Integer.parselnt(Str);
其他的方法自行查阅API
5.5 参数数量可变的方法:
类似于C语言中的可变参数strarg.h 与 C++中的initializer_list , Java中也提供了类似的功能:
使用类似于C语言的...
表示可变参数的部分:
public static double max(double... values) //本部分为核心
{
double largest = Double.NECATIVEJNFINITY;
for (double v : values){
if (v > largest) {
largest = v;
}
}
return largest;
}
不需要引用任何其他的东西, 语法就是这么简单!
直接在可变参数类型的后头加上三个点...
, 而后会将获取到的参数储存在values中
values即为 double []
5.6 枚举类:
这部分跳过
5.7 反射:
这部分是Java中非常高深的技术, 主要用于设计框架以及工具
由于仅仅是需要设计应用程序, 所以这部分暂时放着, 如果后期有必要, 在进行学习
5.8 继承设计技巧:
这里和C++ 中的思想高度相似, 就不进行拓展了