【1】构造函数
为了在遵守某些约定的情况下对已有的程序进行扩充,java语言和一般的op语言一样拥有继承。继承是为了扩展,继承不是为了修改。
这里我们谈几点java继承机制中容易忽略但是很重要的几点。
1.子类中的构造函数
假如我们的超类中显示声明了一个构造函数,子类的实例化能用默认的构造函数么?答案是不能! 比如下面这个例子,子类中必须显示声明。
public class third { public static void main(String atgs[]) { a a1=new a(1, "i am class a"); System.out.print(a1.id+" "+a1.name); } } class a extends b{ a(int i,String n){//超类没有不带参数的构造函数 所以必须显示声明 super(i, n); } } class b{ public int id; public String name; b(int i,String n){ id=i; name=n; } }
我们得弄清楚构造函数不是方法也不可以被继承,所以依赖与超类的子类必须传递参数给超类。
2.构造函数的形式
上面我们讲了构造函数不是方法,那么他是什么?其实没有确切的准确的形容词来概括他,实际上类的初始化工作可以有不同的形式。
形式一 构造函数
形式二 定义的时候初始化(这在c++里面是不允许的,但是java可以)
形式三 构造块 这个比较特殊,用{}在类中来表示这是构造块,它可以执行普通语句 就和在函数里写一样 ,但是不需要定义方法名。
class b{ public int id=0;//定义初始化 public String name; {//初始化块 System.out.println(id); System.out.println(name); } b(){//无参数构造函数 } b(int i,String n){//带参数构造函数 id=i; name=n; } }
3.构造函数的顺序依赖
这个是老话题了,递归调用,从object类开始。
【2】继承和再定义成员
1.重载和覆盖方法
有的时候我们需要保留超类的某个函数并且对它的适用范围进行扩展,这时候我们就用到重载,重载的方法很简单:声明和超类同名但是参数不同的方法即可。
覆盖适用于不想保留超类方法的时候,我们可以在子类中用一个同名同参数的方法去覆盖超类中的方法。
覆盖的时候我们遵循子类不可以修改超类的约定,对于方法的访问权限只能是越来越宽松,比如可以从private修改到public,因为这被视作是子类对超类的扩展。但是反过来就不行,因为这样修改了超类的协议,超类中原本可以访问的方法子类却把它屏蔽了。因为如果我们使用超类构造一个对象但是我们实际引用了子类的实例,那么原来父类中private的方法会暴露为public,但是这不影响正确性,反过来就不行。
例如下面的代码是不会编译通过的
class a extends b{ a(int i,String n){//超类没有不带参数的构造函数 所以必须显示声明 super(i, n); } private void b_method(){ System.out.print("a overload b_method!"); } } class b{ public void b_method() { } b(int i,String n){//带参数构造函数 id=i; name=n; } }
2.隐藏域
域不会被覆盖,但是它可以被隐藏。如果在子类中声明和超类一样的域超类中的域依然会存在,但是不能直接通过域名去访问他。必须通过super或者是超类的引用去访问他。
如下:
public class third { public static void main(String atgs[]) { b a1=new a(1, "a");//超类引用子类 a a2=new a(2, "a");//子类 System.out.println(a1.classname); System.out.println(a2.classname); } } class a extends b{ public String classname="class a"; a(int i,String n){//超类没有不带参数的构造函数 所以必须显示声明 super(i, n); } public void b_method(){ System.out.print("a overload b_method!"); } } class b{ public String classname="class b"; public int id=0;//定义初始化 public String name; public void b_method() { } {//初始化块 System.out.println(id); System.out.println(name); } b(){//无参数构造函数 } b(int i,String n){//带参数构造函数 id=i; name=n; } }
但是方法的访问是不一样的,总的来说引用类型决定域,真实类型决定方法。这样就造成了不统一的问题,假如说我用一个超类类型引用子类对象,那么这个引用的域是超类的域,方法是子类的方法!!如下所示:
public static void main(String atgs[]) { b a1=new a(1, "a");//超类引用子类 System.out.println(a1.classname); a1.b_method(); } }
//输出结果:
class b
a overload b_method!
正因为这个原因,我们鼓励通过存取器(get/set函数)来操纵超类的域,因为一个子类当中的方法访问和超类中相同名字的域的时候他会选择本类中的域而不会去访问超类中的同名域,也就是说如果我们通过 a1.get_classnam()来获取域的话得到的就是class a。这样方法和域都是具体类的方法和域!!
【3】类型兼容和转换
1.兼容性
在类型层次中,层次越高兼容性越广。因为类型层次越高越不具体,覆盖范围越广。类型层次越低越具体,细节越丰富,覆盖范围越窄。
比如说:动物是超类 猫,狗是他的子类
动物兼容猫和狗,所以我们可以 猫 猫1=new 动物(),狗 狗2=new 动物()·····
反过来就不行,因为你不能说动物是狗。当一个高次的类型引用低层次的对象的时候 不会发生问题,这种转换我们称做宽转换。相反就会发生问题,称作窄转换。
2.类型测试
通过使用 instanceof 运算符就可以检查对象的类,如果其左边的表达式与右边的类型名是赋值兼容的话那么就会返回 true 否则返回 false 。
instanceof用来做一具体的类型判断,在网上看到一个例子还是挺好的。
instanceof有一些用处。比如我们写了一个处理账单的系统,其中有这样三个类:
public class Bill {//省略细节} public class PhoneBill extends Bill {//省略细节} public class GasBill extends Bill {//省略细节} 在处理程序里有一个方法,接受一个Bill类型的对象,计算金额。假设两种账单计算方法不同,而传入的Bill对象可能是两种中的任何一种,所以要用instanceof来判断: public double calculate(Bill bill) { if (bill instanceof PhoneBill) { //计算电话账单 } if (bill instanceof GasBill) { //计算燃气账单 } ... } 这样就可以用一个方法处理两种子类。 然而,这种做法通常被认为是没有好好利用面向对象中的多态性。其实上面的功能要求用方法重载完全可以实现,这是面向对象变成应有的做法,避免回到结构化编程模式。只要提供两个名字和返回值都相同,接受参数类型不同的方法就可以了: public double calculate(PhoneBill bill) { //计算电话账单 } public double calculate(GasBill bill) { //计算燃气账单 }
所以,使用instanceof在绝大多数情况下并不是推荐的做法,应当好好利用多态。
3.protected的确切含义
protected设计被用于一些“超类只对子类和同一个包的代码开放访问”的场合。
这里有几点要注意的
1.不同包,类自身的实例能访问protected成员么?不能!!假如我在package a里面有一个类class_a 我在另一个package里面导入package a并且实例化了一个class_a对象,这个对象是无法访问protected成员的。听起来有些匪夷所思,但是的确是这样的。
2.不同包,子类能访问超类的protected成员么?可以!!!
3.不同子类能通过对方访问相同父类的protected成员么? 不可以!!!
总的来说,只有子类内部或者是同一个包可以访问超类的protected成员,这与设计protected这个类型的初衷是一致的!!!
【4】Object类
object类是所有类的基类,位于最顶层。object类里面有所有类都具有的方法,他们分别是equals,hashcode,clone,getclass,finalize,tostring。用法不一一详细讲,这里面比较特别的是equals,clone,hashcode。
1.equals方法区别两个对象是否在内容上相同,注意是内容!!!‘==’用于判断两个对象是否指向同一个引用。
2.clone 方法返回一个与原对象指向同一类型的对象,但是需要注意的是这个对象只是原对象的浅拷贝,要想深拷贝原对象需要重载clone方法。(注意clone方法本身是protected的!!)
3.hashcode需要注意是因为他和equals的关系,这个需要大家回想一下hash存储算法。hash存储算法中,我们每次存储数据都会根据数据生成一个hashcode,这个code下面可能已经存有数据了,如果现有的数据和要存储的数据相同(注意:这里的相同判断就是用 equals !!!)那么我们就放弃存储。
【5】单继承和多继承
关于单继承和多继承 java的答案是只有单继承。为什么不支持多继承,主要原因是为了规避一些多继承带来的风险。这里有个很好的例子,假如超类有方法fun,超类有两个子类a,b他们重载了方法fun。这时候有个类c继承了a和b,那么c里面的fun方法该来自谁呢?就好像避免精神分裂带来的危险一样,子类的特点必须和唯一的超类一致。
为了解决摈弃多继承的缺点,所以java里面引进了接口。