继承(inheritance),人们可以利用继承在已经存在的类的基础上创造新类,既可使用超类的方法和域,也可以在此基础上修改或者添加新的方法或者添加新的域,满足新的需求。
设计技巧
- 将公共操作和域放在超类。
- 不要使用受保护的域。
- 使用继承实现“is-a”关系。
- 除非所有继承的方法都有意义,否则不要使用继承。
- 在覆盖方法时不要改变预期的行为。
- 使用多态,而非类信息。
基础概念
- 超类和子类
利用extends关键字可以利用已经存在的类派生一个新的类,原先存在的类称之为超类(基类、父类),而新派生的类被称为子类(派生类、孩子类(难听...))。子类比超类拥有更丰富的功能,在设计类时应该更多的将通用的功能放在超类中,而将特殊用途的方法放在子类中。但是当超类中的一些方法不适用于子类时,应该选择创建一个同名的方法对超类中继承而来的方法进行覆盖。当我们想要对超类中的方法时,可以使用super关键字。但是绝对不能够删除任何继承的域或者方法。super只是一个指示编译器调用超类方法的关键字,并不是一个对象的引用,不能将其赋给另一个对象变量。 - super关键字
super可以调用超类的方法,也可以调用超类的构造器。但是调用构造器的语句只能作为另一个构造器的第一条语句出现。 - 继承层次
由一个公共超类派生出的各个子类的集称为继承层次。从某个类到其祖先的路径被称为该类的继承链。java不支持多继承。 - 阻止继承
如果不希望某个类被继承,就可以将其设计为final类(类前加上final关键字),这时其中的方法将自动的成为final(不包括域)。也可只单独的将一个公共类中的一些特定的方法设计为final,阻止子类覆盖这个方法。 - 强制类型转换
除了基本类型,类类型也可以进行强制类型转换,但是只能在继承层次内进行转换。有时候需要将某个类的对象的引用转换为另一个类的对象的引用,转换时的语法和基本类型是一致的。转换的原因是为了在暂时忽略对象的实际类型之后,使用对象的全部功能。在进行类型转换时,一个子类的引用可以赋给一个超类的变量,但是一个超类的引用要是赋给一个子类的变量就必须进行强制类型转换。在进行类型转换时,可以利用instanceof(测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。)运算符判断是否能够转换成功。但是因为有实现多态的动态绑定机制,所以有些情况下并不需要转换类型(稍后补充)。
public class Parent {
private String name;
private Integer age;
public Parent(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
}
public class Child extends Parent{
private String School;
public Child(String name,Integer age,String School) {
//简写形式
/*
子类的构造器不能够访问超类的私有域,必须通过super关键字实现对超类
构造器的调用。对于超类没有参数的构造器,如果子类没有显式的调用,那
么子类在创建对象时将自动的调用。对于含参的构造器如果子类没有显式的
调用,那么在编译时就会报错。
*/
super(name,age);
this.School = School;
}
}
public class ChildTest {
Child child = new Child("Bob",22,"JIALIDUN");
//不需要强制转换
Parent parent = child;
//需要强制转换
Child child1 = (Child) parent;
}
- 抽象类
超类一般具有更强的通用性,也就是更加抽象。利用abstract关键字可以将一个类声明为抽象类,抽象类只能作为派生其它类的基类,而不能够被实例化(不能new一个抽象类创建对象),依赖于子类进行实现。当类中的方法被声明为抽象的,则该方法不用实现,一个类中的方法如果有一个为抽象的那么该类就必须被声明为抽象的,但即使类不含有抽象方法也可以将其声明为抽象的。抽象类也可以含有具体的数据和方法。(等看到接口的时候在细说吧)
扩展抽象类的两种选择:
- 在抽象类中定义部分抽象类的方法或者不定义抽象类方法,这样就必须将子类也标记为抽象类。
- 定义全部的抽象方法,子类就不是抽象类了。
要看子类是否完全实现了超类中的全部抽象方法,如果没有完全实现的话,那么该子类必须被声明为抽象类。
- 受保护访问
最好将类中的域标记为private,将方法标记为public。但是这样做子类也不能够访问超类中的私有域,如果希望超类中的域或者方法被子类访问可以将该域或方法标记为protected。但是protected标记的域破坏了数据封装的原则,所以在设计时尽可能不要将域声明为protected,而方法是可以声明为protected的,可以限制超类中的方法被其它不相关类使用。
java中每个类都是由Object扩展而来,并且在创建类时不需要显式指出某个类继承于超类,默认继承于超类。可以使用Object类型的变量引用任何类型的对象,Object类型的变量只能用于作为各种值通用持有者,要想对其中的内容进行具体的操作还需要清楚对象的原始类型,并进行响应的类型转换。
Object类中主要方法介绍
-
equals方法
equals方法通过判断两个对象是否有相同的引用来检测一个对象是否等于另外一个对象。
基本判断的步骤:
1.显式参数命名为otherObj,
2.检测this与otherObj是否引用同一个对象,
3.检测otherObj是否为一个null对象,
4.比较this和otherObj是否属于同一个类,利用getClass()函数进行检测,如果所有的语义都相同,就是有instanceof进行检测,
5.将otherObj转换为相应的类类型变量,
6.对所有的域进行比较。
如果在子类中重新定义了equals方法,就需要先调用超类中的equals方法。要求:
1.自反性:对于任何非空的引用,x.equals(x)返回true。
2.对称性:x.equals(y)和y.equals(x)返回一致(交换律)。
3.传递性:x和y相等,y和z相等,那么x和z也相等。
4.一致性:x和y引用的对象没有发生变化,x.equals(y)应该返回同样的结果。
5.对于任意非空引用x,x.equals(null)为false。
疑问:在进行所属类的比较时,getClass()和instanceof如何判断使用哪个。。。
书上的原话:如果子类能够拥有自己的相等概念,则对称性需求将强制采用getClass进行检测,如果由超类决定相等的概念,那么就可以使用instanceof进行检测,这样可以在不同子类的对象之间进行比较。
下面给出代码实例:public boolean equals(Object otherObj){ //首先对传入的对象引用与this引用进行判断,如果引用同一个对象,则直接返回true。 if(this == otherObj) return true; //如果传入的参数为null必须要返回false if(otherObj == null) return false; //然后判断两个是否属于同一个类,此处应该使用否定来判断,以便进行判断之后的逻辑 if(getClass()!=otherObj.getClass()) return false; //将传入的参数强制转换为本类型的引用 ClassName other = (ClassName)otherObj; //利用逻辑与运算逐个判断参数是否一致,所有参数都一致的话,返回true return field1 == other.field1 && Objects.equals(field2,other.field2) && ...; }
-
hashCode方法
hashCode方法定义在Object中,每个对象都从object中继承到一个hashCode方法,其值为对象储存的地址。如果两个对象相等,则二者的hashCode应该也相等,前提是保证equals方法在被重写的同时hashCode也被重写。而二者的hashCode相等,并不一定表示两个对象就相等。hashCode是用来查找使用的,而equals是用来比较相等的。 - toString方法
toString方法用于返回表示对象值的字符串。
在使用toString方法时,可以除了利用对象去调用,也可以使用""+对象来调用,后者即便是基本类型也可以执行。而直接打印某个对象,就会调用该对象的toString方法。对于没有覆盖Object的toString方法的,将会按照Object的toString方法打印出对象所属的类名和散列码。比如数组。
- 类型描述这个变量可以引用的及能够引用的所有对象的类型。
- 访问修饰符控制可见性:
- private 仅对本类可见
- public 对所有类可见
- protected 对本包和所有子类可见
- 默认(不需要修饰符) 对本包可见
- 相关API
Class getClass():返回包含对象信息的类对象。
java.lang.class String getName():返回这个类的名字。
java.util.Objects static boolean equals(Object a,Object b):用于判断两个对象的域是否相等,如果二者中有某个域为null返回false,两个域都为null返回true,两个不为null的参数会利用a.equals(b)进行判断。
java.lang.Objects static int hash(Objects... objects):返回一个散列码,由提供的所有的对象的散列码组合而得到。
java.lang.hashCode(Object a):如果a为null返回0,否则返回a.hashCode()。
java.lang,Object int hashCode():返回对象的散列码。散列码可以是任意的整数,包括正数和负数。两个相等的对象要求返回相等的散列码。