1基础
- 定义子类
Java用extends表示继承,与C++的:相同。
public class Manager extends Employee
{
}
Java中,所有的继承都是公有继承,没有C++中的私有继承和保护继承。
- 覆盖方法
直接使用同名方法来覆盖父类的方法。
使用super.可以指定调用父类的方法,相当于C++中使用 父类名::。
//java
public double getSalary()
{
double baseSalary=super.getSalary();
return baseSalary+bonus;
}
//c++
public:
double getSalary()
{
double baseSalary=Employee::getSalary();
return baseSalary+bonus;
}
在Java中,方法默认是动态绑定的,不需要声明为virtual。如果不希望方法有虚拟特性,可以将其标记为final的。
- 构造函数的调用
子类构造函数可以使用super来调用父类构造函数,而在C++中,在子类的初始化列表中调用父类构造函数。
使用super调用父类构造函数的语句必须出现在子类构造函数的第一句。
//java
public Manager(String name,double salary,int year, int month,int day)
{
super(name,salary,year,month,day);
bonus=0;
}
//c++
Manager(String name,double salary,int year,int month,int day)
:Employee(name,salary,year,month,day),bonus(0){
}
如果子类没有显示地调用父类的构造函数,将会调用父类的默认构造函数,如果父类没有默认构造函数,编译器将报错。
在构造函数中,可以使用this.调用其它构造函数,该语句也必须出现在第一句,并且不能和super语句同时出现,否则编译器将报错。
- 多态
在java中,子类数组的引用可以转换成父类数组的引用, 而不需要采用强制类型转换。例如如下代码
Manager[] managers=new Manager[10];
Employee[] staff=managers;
staff[0]=new Employee();
该语句编译可以通过,但是我们将一个父类对象赋给了一个子类的引用,当使用managers[0].setBonus(1000)的时候,用到了一个不存在的成员变量。
- 方法的调用
对于语句
x.f(param);
Java调用方法的过程如下:
首先,如果x的类型为C,编译器将列举出C类型所有名为f的方法,以及其父类中访问属性为public名为f的方法。
第二步,根据参数列表的类型,从所有名为f的方法中筛选出参数列表符合的方法,(过程中可能涉及到类型转换)。如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,就会报告一个错误。
第三步,如果是private、static、final、或者构造函数,那么编译器可以准确知道该调用哪个方法,这称为静态绑定。
第四步,如果是需要动态绑定的方法,虚拟机会调用与x所引用对象的实际类型最合适的那个方法。假设x的实际类型是D,它是C的子类,如果D类中定义了方法f()且参数类型符合,就直接调用它。否则将在D类的父类中寻找合适的f()。虚拟机预先为每个类创建一个方法表(method table),包括了该类所有的方法和该类的父类所有的方法都在方法表中,在调用该类的方法时直接搜索该类的方法表就知道该调用哪个方法。如果使用了super关键字,将会在父类的方法表中搜索方法。
在覆盖一个方法时,子类方法不能低于超类方法的可见性,如果超类方法是public,子类就一定要是public。
- 阻止继承和覆盖方法——final关键字
final使用位置 | 作用 |
---|---|
成员变量声明前 | 该成员变量不可修改(相当于const) |
方法声明前 | 不可以被覆盖 |
类声明前 | 不可以被继承,其中的方法也默认是final的,但变量不是。 |
Java的内联是由编译器自动处理的。
- 强制类型转换
除了内置类型之间的转换外,类类型之间也可以进行转换,其要求如下:
- 只能在继承层次内进行类型转换
- 在将超类转换成子类之前,应该使用instanceof进行检查。
- 抽象类
- 包含一个或多个抽象方法的类本身必须被声明为抽象的,使用abstract关键字声明。
- 抽象类的子类如果实现了它全部的抽象方法,就可以不再是抽象类,否则仍然是抽象类。
- 类即使不含抽象方法也可以被声明为抽象类。
- 抽象类不能创建实例,但可以定义引用,只能引用到它的具体子类对象上。
- 在C++中,使用=0标记抽象方法,称为纯虚函数,含有纯虚函数的类就是抽象类。
- protected
- protected标记的方法和成员变量子类可以访问。
- 事实上,Java的protected和C++稍有不同,Java中的protected对所有子类及同一个包中的其他类可将。
- 四种访问修饰符的可见性
修饰符 | 可见性 |
---|---|
private | 仅本类中可见 |
protected | 本包和所有子类中可见 |
public | 所有类可见 |
默认(不需要修饰符) | 本包可见 |
2 Object——所有类的父类
- equals方法
- Object类中的equals方法判断两个对象是否有相同的引用。
- 可以重写子类的equals方法使其具有合适的意义。
- 相等测试与继承
- Java语言规范要求方法具有下面的特性
特性 | 意义 |
---|---|
自反性 | x.equals(x)应返回true |
对称性 | x.equals(y)和y.equals(x)应返回同样的结果 |
传递性 | 如果x.equals(y)&&y.equals(z),则x.equals(z) |
一致性 | 如果x、y引用的对象不变,则x.equals(y)不变 |
非空性 | 如果x非空,则x.equals(null)返回false |
- 一个编写出完美的equals方法的建议
//显式参数命名为otherObject
public boolean equals(Object otherObject)
{
//检测this与otherObject是否引用到同一个对象
if(this==otherObject) return true;
//检测otherObject是否为null
if(otherObject==null) return false;
//比较this与otherObject是否属于同一个类。如果equals的语义在每个子类中有所改变,就使用getClass检测
if(getClass()!=otherObject.getClass()) return false;
//如果所有的子类都拥有统一的语义,就使用instanceof检测
if(!(otherObject instanceof ClassName)) return false;
//将otherObject转换为相应的类类型变量
ClassName other=(ClassName)otherObject;
//比较所有需要比较的域
return field1==other.field1&&.......;
//如果在子类中重新定义了equals,就首先调用super.equals(other);
}
- 在覆盖equals方法时,参数一定是Object类型的,否则,实际上没有覆盖Object的equals方法。为了避免发生此类错误,可以用@Override对覆盖超类的方法进行标记,告诉编译器此处覆盖超类的方法,如果没有成功覆盖,编译器就会给出错误报告。
- hashCode方法
- Object类的默认hashCode方法导出对象的存储地址。
- 如果重新定义equals方法,就必须重新定义hashCode方法,也就是说如果x.equals(y)返回true,那么它们的hashCode也应该相等。
- 可以使用Objects.hash()传入多个参数,快速返回它们组合的hashCode。
public int hashCode()
{
//将会返回三者的hashCode组合后的结果
return Objects.hash(name,salary,hireDay);
}
- 对于数组,可以使用静态的Arrays.hashCode方法计算一个hashCode,这个hashCode由数组元素的hashCode组成。
- toString方法
- 绝大多数的toString方法都遵循这样的格式:类的名字+方括号括起来的域值。
- 一个建议的toString实现如下:
public String toString()
{
return getClass().getName()
+"[name="+name
+",salary"+salary
+",hireDay"+hireDay
+"]";
}
- 只要对象鱼一个字符串通过操作符“+“连接,编译器就会自动调用其toString方法。""+x相当于x.toString()。
- Object类的toString方法打印对象所属类名和散列码。
- 数组继承了Object类的toString方法,可以使用Arrays.toString打印数组,对于多维数组可以使用Arrays.deepToString方法。
- 强烈建议为自定义的每一个类增加toString方法。
3 泛型数组列表
ArrayList<T> 与C++的vector<T>类似,不同之处在于不支持下标操作[],而要使用get、set方法来访问和修改元素。
4 对象包装器与自动装箱
有时,需要将int这样的基本类型转换为对象,例如:声明ArrayList<int>就是不允许的,要使用ArrayList<Integer>(这是什么鬼特性,怀念STL),这时候就需要把int打包为Integer类型。各种基本类型对应的包装器类型如下
基本类型 | 包装器类型 |
---|---|
int | Integer |
long | Long |
floar | Float |
double | Double |
short | Short |
byte | Byte |
char | Character |
void | Void |
boolean | Boolean |
- 大部分情况下,编译器会为我们自动装箱和拆箱,我们可以像使用基本类型一样使用它们。
- ==运算符有所不同,它比较的是两个装箱对象的引用地址是否相同,应使用equals方法。
- 装箱对象是不可变的。
- 装箱类型是final的。
- 装箱类型可以自动类型升级,例如在一个表达式中同时含有Integer和Double时,编译器会自动将Integer升级为Double。
5 参数数量可变的方法
意味着方法可接受的参数数量可变,printf就是一个可变参数方法,其定义如下。
public class PrintStream
{
public PrintStream printf(String fmt,Object...args){return format(fmt,args);
}
其中Object…是java语法的一部分,表示这个方法可以接受任意数量的对象。
下面一段代码用于计算若干个double数据的最大值:
public static double max(double...values)
{
double result=Double.NEGATIVE_INFINITY;
for(double x:values) result=result>x?result:x;
return result;
}
6 枚举类
- 一个示例如下
public enum Size{SMALL,MEDIUM,LARGE,EXTRA_LARGE};
- 所有枚举类型都是Enum的子类,继承了它许多方法。