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

xiaoxiao2021-02-27  233

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();
转载请注明原文地址: https://www.6miu.com/read-10705.html

最新回复(0)