final关键字、多态的概念、抽象类、接口、利用抽象类或接口实现多态

来源:互联网 发布:linux培训多少钱 编辑:程序博客网 时间:2024/05/22 10:51
final关键字
 继承中允许对方法进行重写,重写之后,父类的方法会被子类给覆盖掉,但有时候我们不希望子类去覆盖父类的功能
 只能让他使用父类的功能,java提供了一个关键字叫做final
final修饰类: 被final修饰类将不能再被继承
final修饰方法: final修饰的方法不能被重写
final修饰变量: 被final修饰变量不能被重新赋值,修饰过的变量就成了常量!


关于final修饰变量:
当修饰的是基本数据类型:int long float double... 
那么是表示变量的值不可以变
当修饰的是引用数据类型: 类 接口 数组
那么是表示其所存储的地址值不可以变量,而地址值对应的空间里面的值(存储在堆中)是可以变的


final修饰的变量是只可以赋值一次,注意:也可以先声明变量,之后再赋值,比如
class C{
final int c;

public C(){
c = 100;
}
}


多态:
1.父类引用指向子类对象
2.要有方法重写
3.要有继承


几种特殊情况:
成员变量在父子类中均存在且相同: 取父类中的
成员变量在子类中存在在父类中不存在,会编译报错,符号不存在
成员方法在父子类中均存在相同的: 会调用子类的重写过的方法,子类的方法将父类的重写了
在子类中存在一个成员方法,父类中没有的,会编译报错,提示符号不存在(即找不到方法)


多态的好处,由于使用多态后(即使用父类引用指向子类对象),通过父类引用调用父子类中的同名方法的时候,会调用子类中重写的方法
所以,利用这一点,可以大大方便代码的书写,从一个例子开始:
//动物类是父类
class Animal{
public void eat(){
System.out.println("eat");
}

public void sleep(){
System.out.println("sleep");
}
}


class Dog extends Animal{
public void eat(){
System.out.println("狗吃骨头");
}

public void sleep(){
System.out.println("狗躺着睡");
}

}
class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}

public void sleep(){
System.out.println("猫趴着睡");
}
}


//因为每次都要调用c.eat c.sleep 这样的方法,于是写个工具类,来方便调用
class AnimalTool{
//因为是工具类,所有的方法都应该通过类名调用即可,所以将构造方法私有化,致使其不可通过new的方式新建对象
private AnimalTool(){}

public static void useCat(Cat c){
c.eat();
c.sleep();
}

public static void useDog(Dog d){
d.eat();
d.sleep();
}

//通过这个方法,可以通过父类引用直接调用所有继承了Animal的子类的重写方法,只要父类引用指向的是那个子类对象。
public static void useAnimal(Animal a){
a.eat();
a.sleep();
}
}


class Demo{
public static void main(String [] args){
/*Fu f = new Zi();
System.out.println(f.num);
f.show();*/

Cat c1 = new Cat();
AnimalTool.useAnimal(c1);
Dog d1 = new Dog();
AnimalTool.useAnimal(d1);
}


多态的弊端:
父类引用是不能调用子类的特有功能的,即子类中定义了的,而父类中没有定义的方法


疑问:
可以将子类对象赋值给父类,那可以将父类引用再将其赋值给子类引用吗?
答:是可以的,而且对于这种情况,有个专门的名词叫做”向下转型“
即:
向上转型
Fu f = new Zi();
向下转型
Zi z = (Zi)f;
相对应,之前将子类对象赋值给父类引用,这种情况叫做”向上转型“


如下转换是可以的:
Fu f = new Zi();
Zi z = (Zi)f;
注意:以下代码有运行会报错,但是编译的时候检查不出来!
Animal是父类,Cat Dog 是子类,当Cat子类对象赋值给父类Animal的之后,
再将Animal的父类引用强制转换为子类Dog对象的引用,进行编译的时候并不会
报错,而是会在运行的时候报错,提示classcastException类转换异常
Animal a= new Cat();
Dog d =(Dog)a;

如果子类中有父类中没有的方法定义,那么即便将子类对象赋值给了父类引用,父类仍然是不可以
调用子类的特有方法的,而且这是会在编译的时候就进行报错的。


多态的成员访问特点:
编译看左边(引用),运行看右边(即对象)
即:
Fu f =new Zi()或者 new Fu();
当实际调用f.方法名的时候,具体运行的方法还得看右边的对象中有没有这个方法,只要有就调用
右边对象中的方法,如果没有就调用左边引用对应的类中的方法,如果都没有,就报错。


抽象类:
在前面我定义了动物类,用于抽象出猫 狗等这类动物的共同属性和方法,同样也新建动物对象,
还给其新建了对应的方法,并编写了方法体(比如 eat(){...}),但是,这样是错误的做法
正常的情况中,对于像这样子的父类,我们应该只需要给出方法的声明即可,而不需要方法的定义
即不需要方法体的存在,而具体的方法体/方法定义 只要写在子类中让子类去实现就可以了。
这个时候需要用到抽象类的定义了
1.抽象类或者方法用 abstract修饰
2.抽象类中不一定有抽象方法
3.有抽象方法的类必须定义为抽象类
4.抽象方法没有方法体,所谓没有方法体指的是 连 大括号{}都不能有,只要有{},就算是有方法体了,
空的大括号只不过表示什么也不做的方法体.
5.抽象类不能实例化
例子1:
abstract class Animal{
//错误!,抽象类中的方法必须有抽象修饰符 abstract
public void eat();
//抽象类是有构造方法的,但是不能实例化,它的作用是用于子类访问父类时的数据初始化。
public Animal();
}


class AbstractDemo{
public static void main(String[] args){

}
}
例子2:
//错误!有抽象方法的类必须定义为抽象类
class Animal{
public abstract void eat();
//抽象类是有构造方法的,但是不能实例化,它的作用是用于子类访问父类时的数据初始化。
public Animal();
}


class AbstractDemo{
public static void main(String[] args){

}
}
例子3:
//正确!类定义为抽象类,而且其中方法也定义为没有方法体的抽象方法
abstract class Animal{
public abstract void eat();
//抽象类是有构造方法的,但是不能实例化,它的作用是用于子类访问父类时的数据初始化。
public Animal();
}


class AbstractDemo{
public static void main(String[] args){

}
}


抽象类的子类:
1.重写抽象类的所有抽象方法
2.类定义为抽象类
以上2个条件必须满足且只满足一条!


抽象类必须通过“多态”的方式(即子类继承父类并重写父类方法)来实现,所以其实之前所讲的
多态是针对抽象类来实现的,而不是针对普通类来使用的。


抽象类的成员:
1.成员变量:既可以是变量也可以是常量,使用方法就和普通的类一致,当子类继承了父类后,就当作子类的变量或者常量使用
2.构造方法:抽象类也有构造方法,写法同普通类一致,
3.成员方法:抽象类中的方法既可以是抽象的也可以是非抽象的,当是非抽象的普通方法的时候,使用就
同普通类中的方法一致,这种方法不强制要求子类去重写


疑问:
抽象类中可以有普通方法吗?
可以有,而且写上普通方法后,不强制要求子类继承,而写上抽象方法之后,会强制要求子类必须
重写父类的抽象方法。
比如说我们定义一个 人类,那么,最起码我们要求每个人都必须有吃饭这个行为,只是对于不同地方
的人可以有不同的饮食习惯,那么就可以将吃饭定义为抽象方法


abstract class Person{
//只给出方法的声明,而不给出方法定义,具体的方法定义由子类来实现。
public abstract void eat();
}


以一个猫狗案例来熟悉抽象类以及多态的使用:
abstract class Animal{
//吃饭是猫和狗都具备的行为,但是不是完全一样的,所以声明为抽象方法,
//要求猫和狗的实现类必须要重写这个方法添加各自的定义
public abstract void eat();

//睡觉这个行为猫和狗都是完全一致的,所以直接定义为普通方法,猫狗类共用即可
public void sleep(){
System.out.println("动物睡觉");
}
//抽象类是有构造方法的,但是不能实例化,它的作用是用于子类访问父类时的数据初始化。
public Animal(String name,int age){
this.name = name;
this.age = age;
}

public Animal(){}
//类的各个公共的成员属性及get set方法
public int num = 10;

private String name;

private int age;


public void setName(String name){
this.name = name;
}

public void setAge(int age){
this.age = age;
}

public String getName(){
return name;
}

public int getAge(){
return age;
}
}


class Cat extends Animal{
//这个带参构造方法是必须写的,而且必须在方法中显示调用父类的带参构造方法
//这样才会调用到父类的构造方法(子类初始化前必须先初始化父类)
public Cat(String name,int age){
super(name,age);
}

public Cat(){}

public void eat(){
System.out.println("猫吃鱼");
}
}


class Dog extends Animal{
public Dog(){}

public Dog(String name,int age){
super(name,age);
}

public void eat(){
System.out.println("狗啃骨头");
}
}


class AbstractDemo{
public static void main(String[] args){
/*Animal a = new Dog();
System.out.println(a.num);*/

System.out.println("-----使用普通类的方式-----");
Dog d = new Dog();
d.setName("旺财");
d.setAge(3);
System.out.println(d.getAge()+"---"+d.getName());
d.eat();

Dog d2 = new Dog("小白",4);
System.out.println(d2.getAge()+"---"+d2.getName());
d2.eat();
System.out.println("-----使用多态的方式-----");

Animal a = new Dog();
a.setName("小黑");
a.setAge(5);
System.out.println(a.getAge()+"---"+a.getName());
a.eat();

Animal a2 = new Dog("小黄",6);
System.out.println(a2.getAge()+"---"+a2.getName());
a2.eat();

}
}


抽象类使用时需要注意的问题:
1.一个类如果没有抽象方法,可不可以定义为抽象类?如果可以,有什么意义?
答:可以,目的是不让外界创建对象
2.抽象修饰符abstract不能和哪些关键字共存?
一般情况下我们都将其与public 一同出现,这样定义为公共的权限后,子类也可以访问了,
加上使用了abstract修饰之后,子类还必须重写这个方法。
针对几个常见的关键字来说明下abstract与他们一起写的情况:
a: private:冲突
如果在一个类中用private修饰了方法之后,那么被修饰的这个方法将不能被子类共享,那么
子类也就不能重写父类的这个方法了,这与abstract修饰后强制要求子类重写方法是违背的、
冲突的。
b: final 冲突
同private类似,如果用final修饰后的方法,将无法被继承,与abstract冲突
c:static
编译会报错,而且这么写无意义,由于通过static修饰的方法意味着这个方法是属于类的一部分
与对象无关,同时也可以通过类名直接进行调用,但是使用abstract修饰后的方法,是不能写
方法体的,这点与static修饰的含义想违背(既要调用又不写方法体)
3.子类可以重写静态方法吗?
这个问题是由于提到static突发而感的问题
答:经过尝试,觉得并不可以,或者我觉得这个static修饰的静态方法根本不存在重写这么一说,如果是静态方法
那么在实际调用的时候主要取决于调用方法的变量的引用类型,如果变量的引用类型是父类的那么就调用父类
的静态方法,如果引用类型是子类的就调用子类的静态方法
class Fu{
public static sing(){
System.out.println("父类在唱歌");
}
}


class Zi extend Fu{
public static sing(){
System.out.println("子类在唱歌");
}

public staitc void main(String [] args){
Fu f = new Zi();
//由于左边的引用类型是父类的,所以调用的是父类的静态方法
f.sing();
Zi z = new Zi();
//由于左边的引用类型子类的,所以调用是子类的静态方法
z.sing();
}
}


接口:
再来从猫狗例子来引入:
前面定义的猫狗以及动物类中拥有吃饭 睡觉的行为,但是现实生活中有些驯兽师可以
通过训练让猫狗拥有 钻火圈、数数的能力,而这些能力不应该是直接定义在猫狗类或者
动物类中的,因为动物本身一开始并没有这些能力,而是后来通过给人为训练加上去的,
在java中提供了 接口 来实现了类似的特性,体现事物的扩展特性。


接口特点:
接口用关键字interface表示
格式:interface 接口名 {}
interface DogCount{}
类实现接口用implements表示
格式:class 类名 implements 接口名 {}
接口不能实例化
那么,接口如何实例化呢?
按照多态的方式,由具体的子类实例化。其实这也是多态的一种,接口多态。
接口的子类
要么是抽象类
要么重写接口中的所有抽象方法


1.接口其实也是抽象,不能实例化,要实例化,必须通过多态的方式使用子类进行实例化
由此可见:多态有3种方式
a: 具体类多态(几乎不会用到)
b: 抽象类多态(常用)
c: 接口多态(最常用)


接口的子类:
可以是抽象类,但是意义不大,因为不能实例化
可以说具体的类,但是要重写接口中的所有抽象方法


接口的成员:
成员变量:
接口的中的成员变量默认就是常量而且是静态的!且必须在第一次声明的时候进行初始化赋值
例如:
interface Inter{
//其实系统会默认将下面的语句转换为public static final int num =10;
public int num = 10;
}
成员方法:
1.接口方法不能带有方法体,接口中不能有普通方法。
2.接口方法默认是抽象的
void show();
上面的写法是没有问题,系统会默认加上public abstract,接口中的方法会默认加上public abstract修饰符


构造方法:
接口是没有构造方法的,仅仅只包含,功能方法的声明,那么子类在初始化的时候会调用哪个父类的构造
方法呢?(前面已经,不写构造方法的话,系统会默认加上无参构造方法,并且在构造方法第一行自动
加上父类的无参构造方法,其实每个类都会默认继承一个类: Object,如果不显示的写出继承的父类的
话,系统就会默认加上这个继承,当类初始化的时候,会先调用父类Object的无参构造方法。


抽象类与接口的关系:
类与类:
只能单继承
类与接口:
可以多实现
接口与接口:
可以多继承


实现了一个接口的类的对象可否调用实现了的另一个接口中的方法?
interface A{
public abstract void show();
}


interface B{
public abstract void sing();
}


class AImpl implements A,B{
public void show(){
System.out.println("show AImpl");
}

public void sing(){
System.out.println("sing BImpl");
}
}


public static void main(String[] args){
A ai = new AImpl();
ai.show();
//由于左边的引用类型是A接口,而A接口中并没有声明sing方法
ai.sing();
}
答:是不可以的。


一个接口继承另外一个接口的话,用父接口作为接口对象的引用去接受实现了接口的对象之后,这个引用
是否可以调用子接口中的方法?用子接口作为引用接收对象后,是否可以调用父接口中的方法。
interface C{
public abstract void show();
}
interface D extends C{
public abstract void sing();
}
class DImpl implements{
public void show(){
System.out.println("show DImpl");
}


public void sing(){
System.out.println("sing DImpl");
}
}
答:前者是不可以的,父接口作为引用的时候不可以调用到子接口的方法,后者是可以的,子接口作为
引用的时候可以调用父接口中的方法。


为了进一步深入理解接口的使用,还是从一个猫狗案例来引入:
因为会有部分猫或狗可以通过训练获得额外的能力,这些有额外能力的猫狗的
基本属性和行为和一般的猫狗是一致的
所以可以在前面猫狗案例的基础上进行增加即可,代码如下:


//定义一个跳高的接口,之后如果有需要这个能力的类,实现它即可
interface Jump{
//方法前面的public abstract修饰符可有可无,因为系统默认也会加上
public abstract void Jump();
}
//定义个会跳高的猫类
class JumpCat extends Cat implements Jump{
public JumpCat(){}
public JumpCat(String name,int age){
super(name,age);
}

public void jump(){
System.out.println("猫跳高");
}
}
直接将上面的代码加入到前面的猫狗案例的后面就可以了。


补充一点:
前面说过,在代码中使用多态的时候,最常用的情况就是利用接口了,其次是
抽象类和具体类。
也就是说 interface i{}
class C implements i{}用C实现i接口
i ii = new C();用接口的方式做到多态。
但是一般新建类的都是使用实现类来测试,所谓实现类也就是最底层那个实现了多个接口、
继承了多个类的最终的那个类,这样能够做到最大限度的调用所有的方法(如果一个类
实现了多个接口,那么我们直接使用这个类来接收对象的话,那么所有实现的方法都可以调用了
又比如说继承了父类的子类,但是子类中有自己特有的方法,如果使用父类引用接收对象的话,那么
也是无法调用子类的方法的)
比如:
class C extends ... implements ...{}
这样的类,那么我们测试的时候,就使用C来作为引用接收对象
C c =new C();
0 0
原创粉丝点击