导图社区 Java接口、继承与多态
这是一篇关于Java接口、继承与多态的思维导图,主要内容有一、类的继承二、Object类三、对象类型的转换四、使用instanceof操作符判断对象类型等。
编辑于2022-12-08 17:45:08 贵州微信小程序开发知识整理,系统地介绍了微信小程序开发的基础知识,帮助开发者更好地理解和掌握小程序开发的要点和流程。
SpringFramework技术的介绍,SpringFramework,通常简称为Spring,是一个开源的企业级Java应用程序框架,由Rod Johnson创建,并于2004年首次发布。Spring Framework的主要目标是简化企业级Java开发,提高开发效率和应用程序的可维护性。
Maven的特性以及使用部署方法,Maven介绍:Maven是一款为Java项目构建管理、依赖管理的工具(软件),使用Maven可以自动化构建、测试、打包和发布项目,大大提高了开发效率和质量。
社区模板帮助中心,点此进入>>
微信小程序开发知识整理,系统地介绍了微信小程序开发的基础知识,帮助开发者更好地理解和掌握小程序开发的要点和流程。
SpringFramework技术的介绍,SpringFramework,通常简称为Spring,是一个开源的企业级Java应用程序框架,由Rod Johnson创建,并于2004年首次发布。Spring Framework的主要目标是简化企业级Java开发,提高开发效率和应用程序的可维护性。
Maven的特性以及使用部署方法,Maven介绍:Maven是一款为Java项目构建管理、依赖管理的工具(软件),使用Maven可以自动化构建、测试、打包和发布项目,大大提高了开发效率和质量。
Java接口、继承与多态
一、 类的继承
1. 概念:Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。 这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。
在Java中使用extends关键字来表示两个类的继承关系
实例代码: class Test{ public Test(){ System.out.println("这是父类构造函数"); } protected void doSomething(){ System.out.println("这是父类成员函数"); } } class Test1 extends Test{ public Test1(){ //子类构造函数 super(); //调用父类构造函数 super.doSomething(); //调用父类成员函数 } public void doSomething(){ //重写父类方法 System.out.println("随便输出一下"); } public void doSomethingnew(){ System.out.println("这是子类新增的方法"); } } public class class_extendsTest { public static void main(String[] args) { Test1 t=new Test1(); //实例化对象 t.doSomething(); //调用子类重写的方法 t.doSomethingnew(); //调用子类新增的方法 } }
在子类中可以连同初始化父类构造方法来完成子类初始化操作,既可以在子类的构造方法中使用super()语句调用父类的构造方法,也可以在子类中使用super关键字调用父类的成员方法等。但是子类没有权限调用父类中被修饰为private的方法,只可以调用父类中修饰为public或protected的成员方法。例如,子类构造方法中可以使用super关键字调用父类的doSomething()方法,因为doSomething()方法的权限修饰符为protected。同时在子类中也可以定义一些新方法,如子类中的doSomethingnew()方法。 继承并不只是扩展父类的功能,还可以重写父类的成员方法。重写(还可以称为覆盖)就是在子类中将父类的成员方法的名称保留,重写成员方法的实现内容,更改成员方法的存储权限,或是修改成员方法的返回值类型(重写父类成员方法的返回值类型是基于J2SE 5.0版本以上编译器提供的新功能)。例如,子类中的doSomething()方法,除了重写方法的实现内容之外,还将方法的修饰权限修改为public。 在继承中还有一种特殊的重写方式,子类与父类的成员方法返回值、方法名称、参数类型及个数完全相同,唯一不同的是方法实现内容,这种特殊重写方式被称为重构。
当重写父类方法时,修改方法的修饰权限只能从小的范围到大的范围改变,例如,父类中的doSomething()方法的修饰权限为protected,继承后子类中的方法doSomething()的修饰权限只能修改为public,不能修改为private。
2. 在Java中一切都以对象的形式进行处理,在继承的机制中,创建一个子类对象,将包含一个父类子对象,这个对象与父类创建的对象是一样的。两者的区别在于后者来自外部,而前者来自子类对象的内部。当实例化子类对象时,父类对象也相应被实例化,换句话说,在实例化子类对象时,Java编译器会在子类的构造方法中自动调用父类的无参构造方法。
实例代码: class Test01{ Test01(){ System.out.println("调用父类Tes01构造方法"); } } class Test02 extends Test01{ Test02(){ System.out.println("调用子类Tes02构造方法"); } } public class class_extendsTest1 extends Test02{ class_extendsTest1(){ System.out.println("调用子类class_extendsTest1构造方法"); } public static void main(String[] args) { class_extendsTest1 c=new class_extendsTest1(); } }
如果使用finalize()方法对对象进行清理,需要确保子类finalize()方法的最后一个动作是调用父类的finalize()方法,以保证当垃圾回收对象占用内存时,对象的所有部分都能被正常终止。
在实例化子类对象时,父类无参构造方法将被自动调用。有参构造方法不能被自动调用,用户只能使用super关键字显式地调用父类的构造方法。
二、 Object类
在开始学习使用class关键字定义类时,就应用到了继承原理,因为在Java中所有的类都直接或间接继承了java.lang.Object类。Object类是比较特殊的类,它是所有类的父类,是Java类层中的最高层类。用户创建一个类时,除非已经指定要从其他类继承,否则它就是从java.lang.Object类继承而来的。Java中的每个类都源于java.lang.Object类,如String、Integer等类都是继承于Object类;除此之外,自定义的类也都继承于Object类。由于所有类都是Object子类,所以在定义类时可省略extends Object关键字,如图所示。
在Object类中主要包括clone()、finalize()、equals()、toString()等方法,其中常用的两个方法为equals()和toString()方法。由于所有的类都是Object类的子类,所以任何类都可以重写Object类中的方法。
Object类中的getClass()、notify()、notifyAll()、wait()等方法不能被重写,因为这些方法被定义为final类型。
1. getClass()方法:getClass()方法是Object类定义的方法,它会返回对象执行时的Class实例,然后使用此实例调用getName()方法可以取得类的名称。 语法如下:getClass().getname();
可以将getClass()方法与toString()方法联合使用
2. toString()方法:toString()方法的功能是将一个对象返回为字符串形式,它会返回一个String实例。在实际的应用中通常重写toString()方法,为对象提供一个特定的输出模式。当这个类转换为字符串或与字符串连接时,将自动调用重写的toString()方法
实例代码: public class ObjectInstance { public String toString(){ return "在:"+getClass().getName()+"类中重写toString()方法"; } public static void main(String[] args) { System.out.println(new ObjectInstance()); } }
在本实例中重写了父类Object的toString()方法,在子类的toString()方法中使用Object类中的getClass()方法获取当前运行的类名,定义一段输出字符串,当用户打印ObjectInstance类对象时,将自动调用toString()方法。
运行结果: 在:ObjectInstance类中重写toString()方法
3. equals()方法:equals()方法用于比较两个对象的实际内容,而“==”用于比较两个对象的引用(地址)是否相等,而equals()方法比较的是两个对象的实际内容。
实例代码: class V{ }; public class OverWriteEquals { public static void main(String[] args) { String s1="123"; String s2="123"; V v1=new V(); V v2=new V(); System.out.println(s1.equals(s2)); System.out.println(v1.equals(v2)); } }
从本实例的结果中可以看出,在自定义的类中使用equals()方法进行比较时,将返回false,因为equals()方法的默认实现是使用“==”运算符比较两个对象的引用地址,而不是比较对象的内容,所以要想真正做到比较两个对象的内容,需要在自定义类中重写equals()方法。
运行结果: true false
三、 对象类型的转换
对象类型的转换在Java编程中经常遇到,主要包括向上转型与向下转型操作。
1. 向上转型
因为平行四边形是特殊的四边形,也就是说平行四边形是四边形的一种,那么就可以将平行四边形对象看作是一个四边形对象。例如,鸡是家禽的一种,而家禽是动物中的一类,那么也可以将鸡对象看作是一个动物对象
实例代码: class Quadrangle{ //四边形类 public static void draw(Quadrangle q){ //四边形类方法 System.out.println("这是父类方法"); } } public class Parallelogram extends Quadrangle{ //平行四边形类,继承四边形类 public static void main(String[] args) { Parallelogram p=new Parallelogram(); //实例化平行四边形类对象引用 draw(p); //调用父类方法 } }
平行四边形类继承了四边形类,四边形类存在一个draw()方法,它的参数是Quadrangle(四边形类)类型,而在平行四边形类的主方法中调用draw()时给予的参数类型却是Parallelogram(平行四边形类)类型的。这里一直在强调一个问题,就是平行四边形也是一种四边形的类型,所以可以将平行四边形类的对象看作是一个四边形类的对象,这就相当于“Quadrangle obj = new Parallelogram();”,就是把子类对象赋值给父类类型的变量,这种技术被称为“向上转型”。试想一下正方形类对象可以作为draw()方法的参数,梯形类对象同样也可以作为draw()方法的参数,如果在四边形类的draw()方法中根据不同的图形对象设置不同的处理,就可以做到在父类中定义一个方法完成各个子类的功能,这样可以使同一份代码毫无差别地运用到不同类型之上,这就是多态机制的基本思想
2. 向下转型
通过向上转型可以推理出,向下转型是将较抽象的类转换为较具体的类。这样的转型通常会出现问题,例如不能说四边形是平行四边形的一种,所有的鸟都是鸽子,因为这不符合逻辑。也就是说,子类对象总是父类的一个实例,但父类对象不一定是子类的实例。
实例代码: class Quadrangle{ //四边形类 public static void draw(Quadrangle q){ //四边形类方法 System.out.println("这是父类方法"); } } public class Parallelogram extends Quadrangle{ //平行四边形类,继承四边形类 public static void main(String[] args) { draw(new Parallelogram()); //将平行四边形类对象看作四边形对象,称为向上转型 Quadrangle q=new Parallelogram(); //Parallelogram p=q; //将父类对象赋予子类对象,这种写法是不正确的 Parallelogram p=(Parallelogram) q; //将父类对象赋予子类对象,并强制转换为子类型,写法正确 } }
如果将父类对象直接赋予子类,会发生编译器错误,因为父类对象不一定是子类的实例。例如,一个四边形不一定就是指平行四边形,它也许是梯形,也许是正方形,也许是其他带有四条边的不规则图形。是具体的对象,具有的特性越多;越是抽象的对象,具有的特性越少。在做向下转型操作时,将特性范围小的对象转换为特性范围大的对象肯定会出现问题,所以这时需要告知编译器这个四边形就是平行四边形。将父类对象强制转换为某个子类对象,这种方式称为显式类型转换。在程序中使用向下转型技术时,必须使用显式类型转换,向编译器指明要将父类对象转换为哪一种类型的子类对象。
四、 使用instanceof操作符判断对象类型
当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例,就会发生ClassCastException异常,所以在执行向下转型之前需要养成一个良好的习惯,就是判断父类对象是否为子类对象的实例。这个判断通常使用instanceof操作符来完成。可以使用instanceof操作符判断是否一个类实现了某个接口,也可以用它来判断一个实例对象是否属于一个类。
instanceof的语法格式如下: myobject instanceof ExampleClass
instanceof是Java语言的关键字,在Java语言中的关键字都为小写
1||| MyObject:某类的对象引用
2||| ExampleClass:某个类
使用instanceof操作符的表达式返回值为布尔值。如果返回值为true,说明myobject对象为ExampleClass的实例对象;如果返回值为false,说明MyObject对象不是ExampleClass的实例对象
实例代码: class Quadrangle{ //四边形类 public static void draw(Quadrangle q){ //四边形类方法 System.out.println("这是父类方法"); } } class Squara extends Quadrangle{ //SomeSentence } class Anything{ //SomeSentence } public class Parallelogram extends Quadrangle{ //平行四边形类,继承四边形类 public static void main(String[] args) { Quadrangle q=new Quadrangle(); //实例化父类对象 if(q instanceof Parallelogram){ //判断父类是否为子类的实例 Parallelogram p=(Parallelogram) q; //向下转型操作 } if(q instanceof Squara){ Squara s=(Squara) q; //向下转型 } } }
由于q对象不为Anything类的对象,所以 q instanceof Anything是不合法的
五、 方法的重载
1. 方法的重载就是在同一个类中允许存在一个以上的同名方法,只要这些方法的参数个数或类型不同即可。
实例代码: public class OverLoadTest { public static int add(int a,int b) { return a+b; } public static double add(double a,double b) { return a+b; } public static int add(int a) { return a; } public static int add(int a,double b) { return 1; } public static int add(double a,int b) { return 1; } public static void main(String[] args) { System.out.println("调用add(int,int)方法:"+add(1,2)); System.out.println("调用add(double,double)方法:"+add(3.2,6.8)); System.out.println("调用add(int)方法:"+add(6)); } }
本实例中分别定义了5个方法,在这5个方法中,前两个方法的参数类型不同,并且方法的返回值类型也不同,所以这两个方法构成重载关系;前两个方法与第3个方法相比,第3个方法的参数个数少于前两个方法,所以这3个方法也构成了重载关系;最后两个方法相比,发现除了参数的出现顺序不同之外,其他都相同,同样构成了重载关系。虽然在方法重载中可以使两个方法的返回类型不同,但只有返回类型不同并不足以区分两个方法的重载,还需要通过参数的个数以及参数的类型来设置。
运行结果: 调用add(int,int)方法:3 调用add(double,double)方法:10.0 调用add(int)方法:6
2. 不定长参数方法
语法:返回值 方法名(参数数据类型...参数名称)
实例代码: public class OverLoadTest2 { public static int add(int a,int b) { return a+b; } public static double add(double a,double b) { return a+b; } public static int add(int a,double b) { return 1; } public static int add(double a,int b) { return 1; } public static int add(int... a) { //不定长参数方法 int sum=0; for(int i=0;i<a.length;i++) { sum+=a[i]; } return sum; } public static void main(String[] args) { System.out.println("调用不定长参数方法"+add(1,2,3,4,5,6,7,8,9)); System.out.println("调用不定长参数方法:"+add(1)); } }
运行结果: 调用不定长参数方法45 调用不定长参数方法:1
六、 多态
利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。如果需要定义一个绘制正方形的方法,通过定义一个正方形类来处理正方形对象,会出现代码冗余的缺点;通过定义一个正方形和平行四边形的综合类,分别处理正方形和平行四边形对象,也没有太大意义。 如果定义一个四边形类,让它处理所有继承该类的对象,根据“向上转型”原则可以使每个继承四边形类的对象作为draw()方法的参数,然后在draw()方法中作一些限定就可以根据不同图形类对象绘制相应的图形,从而以更为通用的四边形类来取代具体的正方形类和平行四边形类。这样处理能够很好地解决代码冗余问题,同时也易于维护,因为可以加入任何继承父类的子类对象,而父类方法也无须修改。
实例代码: public class Sibianxing { private Sibianxing[] stest=new Sibianxing[8]; //实例化保存四边形对象数组对象 private int nextIndex=0; public void draw(Sibianxing s) { if(nextIndex<stest.length) { stest[nextIndex]=s; System.out.println(nextIndex); nextIndex++; } } public static void main(String[] args) { Sibianxing sibianxing=new Sibianxing(); sibianxing.draw(new Zhengfangxing()); //以正方形对象为参数调用draw()方法 sibianxing.draw(new Pingxingsibianxing()); //以平行四边形对象为参数调用draw()方法 } } //定义一个正方形类,继承四边形类 class Zhengfangxing extends Sibianxing { public Zhengfangxing(){ System.out.println("正方形"); } } //定义一个平行四边形类,继承四边形类 class Pingxingsibianxing extends Sibianxing { public Pingxingsibianxing(){ System.out.println("平行四边形"); } }
运行结果: 正方形 0 平行四边形 1
七、 抽象类与接口
通常可以说四边形具有4条边,或者更具体一点,平行四边形是具有对边平行且相等特性的特殊四边形,等腰三角形是其中两条边相等的三角形,这些描述都是合乎情理的。但对于图形对象,却不能使用具体的语言进行描述,它有几条边,究竟是什么图形,没有人能说清楚,这种类在Java中被定义为抽象类
1. 抽象类
在解决实际问题时,一般将父类定义为抽象类,需要使用这个父类进行继承与多态处理。在继承树中越在上方的类越抽象,并且在多态机制中,并不需要将父类初始化对象,我们需要的只是子类对象。在Java语言中设置抽象类不可以实例化对象,因为图形类不能抽象出任何一种具体图形,但它的子类可以。
抽象类的语法如下: public abstract class Test { abstract void testAbstract(); }
使用abstract关键字定义的类称为抽象类,而使用这个关键字定义的方法称为抽象方法。抽象方法没有方法体,这个方法本身没有任何意义,除非它被重写,而承载这个抽象方法的抽象类必须被继承,实际上抽象类除了被继承之外没有任何意义。 反过来讲,如果声明一个抽象的方法,就必须将承载这个抽象方法的类定义为抽象类,不可能在非抽象类中获取抽象方法。换句话说,只要类中有一个抽象方法,此类就被标记为抽象类。 抽象类被继承后需要实现其中所有的抽象方法,也就是保证相同的方法名称、参数列表和相同返回值类型创建出非抽象方法,当然也可以是抽象方法。继承抽象类的所有子类需要将抽象类中的抽象方法进行覆盖。这样在多态机制中,就可以将父类修改为抽象类,将draw()方法设置为抽象方法,然后每个子类都重写这个方法来处理。但这又会出现我们刚探讨多态时讨论的问题,程序中会有太多冗余的代码,同时这样的父类局限性很大,也许某个不需要draw()方法的子类也不得不重写draw()方法。如果将draw()方法放置在另外一个类中,让那些需要draw()方法的类继承该类,不需要draw()方法的类继承图形类,又会产生新的问题:所有的子类都需要图形类,因为这些类是从图形类中导出的,同时某些类还需要draw()方法,而Java中规定类不能同时继承多个父类。为了应对这种问题,接口的概念便出现了。
2. 接口
接口是类的延伸,可以将它看做是纯粹的抽象类,接口中的所有方法都没有方法体,对于上述抽象类中遗留的问题,可以将draw()方法封装到一个接口中,使需要使用draw()方法的类实现这个接口,同时也继承图形类。
接口使用interface关键字进行定义,语法如下: public interface drawTest { void draw(); }
在接口中,方法必须被定义为public或abstract形式,其他修饰权限不被Java编译器认可。或者说,即使不将该方法声明为public形式,它也是public。在接口中定义的任何字段都自动是static和final的。
interface drawTest //定义接口 { public void draw(); } class PingxingsibianxingUseInterface extends SibianxingUseInterface implements drawTest { @Override public void draw() { //由于该类实现了接口,需要覆盖draw()方法 System.out.println("画平行四边形"); } } class ZhengfangxingUseInterface extends SibianxingUseInterface implements drawTest { public void draw(){ System.out.println("画正方形"); } } class AnythingUseInterface extends SibianxingUseInterface { void doAnythings(){ //任意语句 } } public class SibianxingUseInterface { public void doAnything(){ //任意语句 } public static void main(String[] args) { drawTest[] d={new PingxingsibianxingUseInterface(),new ZhengfangxingUseInterface()}; //接口也可以进行向上转型 for (int i=0;i<d.length;i++) { d[i].draw(); } } }
运行结果: 画平行四边形 画正方形
在Java中无论是将一个类向上转型为父类对象,还是向上转型为抽象父类对象,或者向上转型为该类实现接口,都可以。
3. 接口与继承
1. Java中不允许出现多重继承,但使用接口就可以实现多重继承。一个雷可以同时实现多个接口,因此可以将所有需要继承的接口放置在implements关键字后并使用逗号隔开。但这可能会在一个类中产生庞大的代码量,因为继承一个接口时需要实现接口中所有的方法。
多重继承的语法: class 类名 implements 接口1,接口2,...,接口n
2. 后期的学习和实践中一定要仔细揣摩继承与多态机制,因为继承和多态本身是比较抽象的概念,深入理解需要一段时间。使用多态机制必须扩大自己的编程视野,将编程的着眼点放在类与类之间的共同特性以及关系上,使软件开发具有更快的速度、更完善的代码组织架构,以及更好的扩展性和维护性
3. 小结: 后期的学习和实践中一定要仔细揣摩继承与多态机制,因为继承和多态本身是比较抽象的概念,深入理解需要一段时间。使用多态机制必须扩大自己的编程视野,将编程的着眼点放在类与类之间的共同特性以及关系上,使软件开发具有更快的速度、更完善的代码组织架构,以及更好的扩展性和维护性