黑马程序员-Java基础知识预备之Java面向对象的理解(part2)

时间:2023-02-14 12:01:50

--------- android培训java培训、期待与您交流!----------



本博文涉及的主要内容:

   1.继承

   2.重写(覆盖)父类的方法

   3.super关键字

   4.多态

   5.final修饰符

   6.抽象

   7.接口


一、继承

1、Java的继承是通过extends关键字来实现的,实现继承的类称为子类,被继承的类称为父类,也称为基类或超类。

2、Java中是单继承,一个子类只能有一个父类。

3、父类和子类的关系,是一种一般和特殊的关系。如:水果和苹果的关系,苹果继承了水果,苹果是水果的子类,则苹果是一种特殊的水果。

4、关于继承的几点注意:

①父类有的,子类也有

②父类没有的,子类可以增加

③父类有的,子类可以改变

④构造方法不能被继承

⑤方法和属性可以被继承

⑥子类的构造方法隐式地调用父类的不带参数的构造方法

⑦当父类没有不带参数的构造方法时,子类需要使用super来显式地调用父类的构造方法,super指的是对父类的引用

⑧super关键字必须是构造方法中的第一行语句。


二、重写(覆盖)父类的方法

1、重写只能存在于子类和父类之间

2、重写的原则:(两同两小一大)

两同:方法名相同、形参列表相同

两小:

①子类方法返回值类型应比父类返回值类型更小或相等。

②子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等。

一大:子类方法的访问权限应比父类方法的访问权限更大或相等。

3、子类覆盖方法不能比父类被覆盖方法抛出更多异常

4、覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,如下代码就会引发错误:

class BaseClase{
public static void test(){}
}
class SubClass extends BaseClass
{
public void test(){}
}

5、如果父类方法具有private访问权限,则该方法对其子类隐藏,子类也就无法覆盖其父类的方法。如果子类中定义了一个与父类private修饰的方法具有相同的方法名、相同的形参列表、相同的返回值类型的方法,依然不是重写,而中是在子类中重新定义了一个新方法。如下代码是正确的。

class BaseClass{    //test方法是private的,子类不能覆盖该方法    private void test(){}}class SubClass extends BaseClass{    //此处并不是方法的重写,所以可以增加static关键字。    public static void test(){}}

6、方法重写与方法重载之间的关系:重载发生在同一个类内部的两个或多个方法。重写发生在父类与子类之间。


三、super关键字

1、在子类定义的实例方法中可以通过super来访问父类中被隐藏(被覆盖)的Field,如下:

class BaseTest{    private int a = 3;}public class SubClass extends BaseClass{    public int a = 9;    public void accessOwner()    {        System.out.println(a);    }    public void accessBase()    {        //通过super来限定访问从父类继承得到的a Field        System.out.println(super.a);    }    public static void main(String[] args)    {        SubClass sc = new SubClass();        //输出9;        sc.accessOwner();        //输出3        sc.accessBase();    }}

2、如果在子类里定义了与父类中已有变量同名的变量,那么子类中定义的变量会隐藏父类中定义的变量。注意不是完全覆盖,因此系统在创建子类对象时,依然会为父类中定义的、被隐藏的变量分配内存空间。所以会出现如下特殊情况:

class Parent{    public String tag = "黑马程序员";}class Derived extends Parent{    private String tag = "Java学习";}public class HideTest{    public static void main(String[] args)    {        Derived d = new Derived();        //程序不可访问d的私有变量tag,所以下面语句会编译错误        //System.out.println(d.tag);        //将d变量显式地向上转型为Parent后,即可访问tag实例变量        //但程序输出:黑马程序员        System.out.println(((Parent)d).tag);    }}

3、调用父类构造器

子类构造器调用父类构造器分如下几种情况:

  • 子类构造器执行体的第一行使用super显式调用父类构造器,系统将根据super里传入的实参列表调用父类对应的构造器。

  • 子类构造器执行体的第一行代码使用this显式调用本类中重载的构造器,系统将根据this里传入的实参列表调用本类中的另一个构造器。执行本类中另一个构造器时即会调用父类构造器。

  • 子类构造器执行体中既没有super调用,也没有this调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器。

如:生物与动物的例子。

class Creature{    public Creature()    {        System.out.println("Creature无参数的构造器");    }}class Animal extends Creature{    public Animal(String name)    {        System.out.println("Animal带一个参数的构造器,"+"该动物的name为"+name);    }    public Animal(String name, int age)    {        //使用this调用同一个重载的构造器。        this(name);        System.out.println("Animal带两个参数的构造器,"+"其age为"+age);    }}public class Wolf extends Animal{    public Wolf()    {        //显式调用父类有两个参数的构造器。        super("狼", 4);        System.out.println("Wolf无参数的构造器");    }    public static void main(String[] args)    {        new Wolf();    }}/*-------------------------运行结果:Creature无参数的构造器Animal带一个参数的构造器,该动物的name为狼Animal带两个参数的构造器,其age为4Wolf无参数的构造器。-------------------------*/


四、多态

1、多态(Polymorphism >:用我们通俗易懂的话来说就是子类就是父类(猫是动物,学生也是人),因此多态的意思就是:父类型的引用可以指向子类的对象。

2、方法的重写、重载与动态连接构成多态性。Java之所以引入多态的概念,原因之一是它在类的继承问题上和C++不同,后者允许多继承,这确实给其带来的非常强大的功能,但是复杂的继承关系也给C++开发者带来了更大的麻烦,为了规避风险,Java只允许单继承,派生类与基类间有IS-A的关系(即"猫"is a"动物")。这样做虽然保证了继承关系的简单明了,但是势必在功能上有很大的限制,所以,Java引入了多态性的概念以弥补这点的不足,此外)抽象类和接口也是解决单继承规定限制的重要手段。同时,多态也是面向对象编程的精髓所在。

3、在一个类中,可以定义多个同名的方法,只要确定它们的参数个数和类型不同,这种现象称为类

的多态。类的多态性体现在两方面:一是方法的重载上)包括成员方法和构造方法的重载;二是在继承

过程中,方法的重写。多态性是面向对象的重要特征。方法重载和方法覆写实际上属于多态性的一

种体现,真正的多态性还包括对象多态性的概念。

4、对象多态性主要是指子类和父类对象的相互转换关系。

a)向上类型转换(upcast):比如说将Cat类型转换为Animal类型,即将子类型转换为父类型。对于向上类型转换,不需要显式指定。

b)向下类型转换(downcast):比如将Animal类型转换为Cat类型,即将父类型转换为子类型。对于向下类型转换,必须要显式指定(必须要使用强制类型转换)。

5、高深的理解

① Java中除了static和final方法外,其他所有的方法都是运行时绑定的。private方法都被隐式指定

为final的,因此final的方法不会在运行时绑定。当在派生类中重写基类中static, final、private

方法时,实质上是创建了一个新的方法。

②在派生类中,对于基类中的private方法,最好采用不同的名字。

③包含抽象方法的类叫做抽象类。注意定义里面包含这样的意思,只要类中包含一个抽象方法,该类就

是抽象类。抽象类在派生中就是作为基类的角色)为不同的子类提供通用的接口。

④对象清理的顺序和创建的顺序相反,当然前提是自己想手动清理对象,因为大家都知道Java垃圾回收器。

⑤在基类的构造方法中小心调用基类中被重写的方法,这里涉及到对象初始化顺序。

⑥构造方法是被隐式声明为static方法。

⑦用继承表达行为间的差异,用字段表达状态上的变化。


五、final修饰符

1、修饰成员:

类Field:必须在静态初始化块中或声明该Field时指定初始值

实例Field:必须在非静态初始化块、声明该Field或构造器中指定初始值。

public class FinalVariableTest{    //定义成员变量时指定默认值,合法。    final int a = 6;    //下面变量将在构造器或初始化块中分配初始值    final String str;    final int c;    final static double d;    //既没有指定默认值,又没有在初始化块、构造器中指定初始值,    //下面定义char Field是不合法的。    //final char ch;    //初始化块,可对没有指定默认值的实例Field指定初始值    {        //在初始化块中为实例Field指定初始值,合法        str = "Hello";        //定义a Field时已经指定了默认值,        //不能为a重新赋值下面赋值语句非法        //a = 9;    }    //静态初始化块,可对没有指定默认值的类Field指定初始值    static    {        //在静态初始化块中为类Field指定初始值,合法        d = 5.6;    }    //构造器,可对既没有指定默认值、有没有在初始化块中    //指定初始值的实例Field指定初始值    public FinalVariableTest()    {        //如果初始化块中对str指定了初始化值,        //构造器中不能对final变量重新赋值,下面赋值语句非法        //str = "java";        c = 5;    }    public void changeFinal()    {        //普通方法不能为final修饰的成员变量赋值        //d = 1.2;        //不能在普通方法中为final成员变量指定初始值        //ch = 'a';    }    public static void main(String[] args)    {        FinalVariableTest ft = new FinalVariableTest();        System.out.println(ft.a);        System.out.println(ft.c);        System.out.println(ft.d);    }}

2、修饰局部变量

public class FinalLocalVariableTest{    public void test(final int a)    {        //不能对final修饰的形参赋值,下面语句非法        //a = 5;    }    public static void main(String[] args)    {        //定义final局部变量时指定默认值,则str变量无法重新赋值        final String str = "hello";        //下面赋值语句非法        //str = "Java";        //定义final局部变量时没有指定默认值,则d变量可被赋值一次        final double d;        //第一次赋初始值,成功        d = 5.6;        //对final变量重复赋值,下面语句非法        //d = 3.4;    }}

3、修饰基本类型变量和引用类型变量的区别

使用final修饰的引用类型变量不能被重新赋值,但可以改变引用类型变更所引用对象的内容。

import java.util.*;class Person{    private int age;    public Person(){}    //有参数构造器    public Person(int age)    {        this.age = age;    }    //省略age Field的setter和getter方法    //age Field的setter和getter方法    public void setAge(int age)    {        this.age = age;    }    public int getAge()    {        return this.age;    }}public class FinalReferenceTest{    public static void main(String[] args)    {        //final修饰数组变量,iArr是一个引用变量        final int[] iArr = {5, 6, 12, 9};        System.out.println(Arrays.toString(iArr));        //对数组元素进行排序,合法        Arrays.sort(iArr);        System.out.println(Arrays.toString(iArr));        //对数组元素赋值,合法        iArr[2] = -8;        System.out.println(Arrays.toString(iArr));        //下面语句对iArr重新赋值,非法        //iArr = null;        //final修饰Person变量,p是一个引用变量        final Person p = new Person(45);        //改变Person对象的age Field,合法        p.setAge(23);        System.out.println(p.getAge());        //下面语句对p重新赋值,非法        //p = null;    }}

4、可执行"宏替换"的final变量

   对一个final变量,不管它是类Field、实例Field,还是局部变量,只要该变量满足3个条件,这个。

   final变量就不再是一个变量,而是相当于一个直接量。

  • 使用final修饰符修饰。

  • 在定义该final变量时指定了初始值。

  • 该初始值在编译时期就被确定下来。

public class FinalReplaceTest{    public static void main(String[] args)    {        //下面定义了4个final“宏变量”        final int a = 2 + 4;        final double b = 3.14 / 3;        final String str = "黑马" + "程序员";        final String book = "JDK:" + 7.0;        //下面的book2变量的值因为调用了方法,所以无法在编译时被确定下来        final String book2 = "JDK" + String.valueOf(7.0);        System.out.println(book == "JDK:7.0");//自动转型        System.out.println(book2 == "JDK:7.0");    }}

5、final方法

   final修饰的方法不可被重写,如果出于某些原因,不希望子类重写父类的某个方法,则可以使用final修饰该方法。如:Java提供的Object类里就有一个final方法:getClass();因为不希望其子类重写该方法,所以使用final修饰符修饰,把个这个方法密封起来,但对于Object类的toString()以及equals等方法,都允许重写。


6、final类

   final修饰的类不可以有子类,例如java.lang.Math类就是一个final类,它不可以有子类。


六、抽象

1、抽象类(abstract class):使用了abstract关键字所修饰的类叫做抽象类。抽象类无法实例化,

也就是说,不能new出来一个抽象类的对象(实例)。抽象方法(abstract method):使用abstract

关键字所修饰的方法叫做抽象方法。抽象方法需要定义在抽象类中。相对于抽象方法,之前所定义

的方法叫做具体方法(有声明,有实现)。

2、如果一个类包含了抽象方法,那么这个类一定是抽象类。

3、如果某个类是抽象类,那么该类可以包含具体方法(有声明、有实现)。

4、如果一个类中包含了抽象方法,那么这个类一定要声明成abstract class,也就是说,该类一定是抽象类;反之,如果某个类是抽象类,那么该类既可以包含抽象方法,也可以包含具体方法。

5、无论何种情况,只要一个类是抽象类,那么这个类就无法实例化。

6、在子类继承父类(父类是个抽象类)的情况下,那么该子类必须要实现父类中所定义的所有抽象方法;否则,该子类需要声明成一个abstract class.

7、当使用abstract修饰类时,表明这个类只能被继承;当作用abstract修饰方法时,表明这个方法必须由子类提供实现(即重写),而final修饰的类不能被继承,final修饰的方法不能被重写,因此final和abstract永远不能


七、接口

1、Java接口的特征:

(1)ava接口中的成员变量默认都是public, static, final类型的(都可省略),必须被显示初始化,即接口中的成员变量为常量(大写,单词之间用"_"分隔)

(2)Java接口中的方法默认都是public,abstract类型的(都可省略),没有方法体,不能被实例化

(3) Java接口中只能包含public, static, final类型的成员变量和public,abstract类型的成员方法

(4)接口中没有构造方法,不能被实例化

(5)一个接口不能实现((implements)另一个接口,但它可以继承多个其它的接口

(6)Java接口必须通过类来实现它的抽象方法:public class A implements B{...}

(7)当类实现了某个Java接口时,它必须实现接口中的所有抽象方法,否则这个类必须声明为抽象的

(8)不允许创建接口的实例(实例化),但允许定义接口类型的引用变量,该引用变量引用实现了这个接口的类的实例

(9)一个类只能继承一个直接的父类,但可以实现多个接口,间接的实现了多继承.

(10)通过接口,可以方便地对己经存在的系统进行自下而上的抽象,对于任意两个类,不管它们是否属于同一个父类,只有它们存在相同的功能,就能从中抽象出一个接口类型.对于己经存在的继承树,可以方便的从类中抽象出新的接口,但从类中抽象出新的抽象类却不那么容易,因此接口更有利于软件系统的维护与重构.对于两个系统,通过接口交互比通过抽象类交互能获得更好的松祸合.

(11)接口是构建松祸合软件系统的重要法宝,由于接口用于描述系统对外提供的所有服务,因此接口中的成员变量和方法都必须是public类型的,确保外部使用者能访问它们,接口仅仅描述系统能做什么,但不指明如何去做,所有接口中的方法都是抽象方法,接口不涉及和任何具体实例相关的细节,因此接口没有构造方法,不能被实例化,没有实例变量。


2、抽象类与接口的相同点

(1)代表系统的抽象层,当一个系统使用一颗继承树上的类时,应该尽量把引用变量声明为继承树的上层抽象类型,这样可以提高两个系统之间的送祸合

(2)都不能被实例化

(3)都包含抽象方法,这些抽象方法用于描述系统能提供哪些服务,但不提供具体的实现。


3、抽象类与接口的不同点

(1)在抽象类中可以为部分方法提供默认的实现,从而避免在子类中重复实现它们,这是抽象类的优势,但这一优势限制了多继承,而接口中只能包含抽象方法.由于在抽象类中允许加入具体方法,因此扩展抽象类的功能,即向抽象类中添加具体方法,不会对它的子类造成影响,而对于接口,一旦接口被公布,就必须非常稳定,因为随意在接口中添加抽象方法,会影响到所有的实现类,这些实现类要么实现新增的抽象方法,要么声明为抽象类

(2)一个类只能继承一个直接的父类,这个父类可能是抽象类,但一个类可以实现多个接口,这是接口的优势,但这一优势是以不允许为任何方法提供实现作为代价的三,为什么Java语言不允许多重继承呢?当子类覆盖父类的实例方法或隐藏父类的成员变量及静态方法时,Java虚拟机采用不同的绑定规则,假如还允许一个类有多个直接的父类,那么会使绑定规则更加复杂。

(3)结论:

   因此,为了简化系统结构设计和动态绑定机制,Java语言禁止多重继承.而接口中只有抽象方法,没有实例变量和静态方法,只有接口的实现类才会实现接口的抽象方法(接口中的抽象方法是通过类来实现的),因此,一个类即使有多个接口,也不会增加Java虚拟机进行动态绑定的复杂度.因为Java虚拟机永远不会把方法与接口绑定,而只会把方法与它的实现类绑定.


4、使用接口和抽象类的总体原则:

(1)用接口作为系统与外界交互的窗口站在外界使用者(另一个系统)的角度,接口向使用者承诺系统能提供哪些服务,站在系统本身的角度,接口制定系统必须实现哪些服务,接口是系统中最高层次的抽象类型.通过接口交互可以提高两个系统之间的送耦合系统A通过系统B进行交互,是指系统A访问系统B时,把引用变量声明为系统B中的接口类型,该引用变量引用系统B中接口的实现类的实例。

(2)Java接口本身必须非常稳定,Java接口一旦制定,就不允许随遇更加,否则对外面使用者及系统本身造成影响

(3)用抽象类来定制系统中的扩展点,抽象类来完成部分实现,还要一些功能通过它的子类来实现


--------- android培训java培训、期待与您交流!----------

本文出自 “汐朔” 博客,请务必保留此出处http://1139347837.blog.51cto.com/7481349/1274334