Java接口、继承与多态

一、类的继承

1、继承

       继承的基本思想是基于某个父类的扩展,制定出一个新的子类,子类可以继承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父类中的某些方法。在Java中使用extends关键字来标识两个类的继承关系。

例:

class Test {
	public Test() { 
		System.out.println("父类Test构造函数");
	}
	
	protected void doSomething() { 
	}
	
	protected Test doIt() { // 方法返回值类型为Test类型
		return new Test();
	}
}

class Test2 extends Test { // 继承父类
	public Test2() { 
		super();				// 调用父类构造方法
		super.doSomething(); 	// 调用父类成员方法
		System.out.println("子类Test2构造函数");
	}
	
	public void doSomethingnew() { // 新增方法
		// SomeSentence
	}
	
	public void doSomething() { // 重写父类方法
		// SomeNewSentence
	}
	
	protected Test2 doIt() { // 重写父类方法,方法返回值类型为Test2类型
		return new Test2();
	}
}

public class classTest {
	
	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		Test2 test2 = new Test2();
	}

}

输出:

       在子类中可以连同初始化父类构造方法来完成子类初始化操作,既可以在子类的构造方法中使用super()语句调用父类的构造方法,也可以在子类中使用super关键字调用父类的成员方法等。但是子类没有权限调用父类中被修饰为private的方法,只可以调用父类中修饰为public或protected的成员方法。

       继承并不只是扩展父类的功能,还可以重写父类的成员方法。重写(还可以称为覆盖)就是在子类中将父类的成员方法的名称保留重写成员方法的实现内容更改成员方法的存储权限,或是修改成员方法的返回值类型(重写父类成员方法的返回值类型是基于J2SE 5.0版本以上编译器提供的新功能)。例如,子类中的doSomething()方法,除了重写方法的实现内容之外,还将方法的修饰权限修改为public

       在继承中还有一种特殊的重写方式,子类与父类的成员方法返回值、方法名称、参数类型及个数完全相同,唯一不同的是方法实现内容,这种特殊重写方式被称为重构

       当重写父类方法时,修改方法的修饰权限只能从小的范围到大的范围改变,例如,父类中的doSomethin()方法的修饰权限为protected,继承后子类中的方法doSomething()的修饰权限只能修改为public,不能修改为private。

       子类重写父类的方法还可以修改方法的返回值类型,但这只是在J2SE5.0以上的版本中支持的新功能。这种重写方式需要遵循一个原则,即重写的返回值类型必须是父类中同一种方法返回值的子,如上例中Test2类为Test的子类。

       在Java中一切都以对象的形式进行处理,在继承的机制中,创建一个子类对象,将包含一个父类子对象,这个对象与父类创建的对象是一样的。 两者的区别在于后者来自外部,而前者来自子类对象的内部。当实例化子类对象时,父类对象也相应被实例化,换句话说,在实例化子类对象时,Java 编译器会在子类的构造方法中自动调用父类的无参构造方法。

       在实例化子类对象时,父类无参构造方法将被自动调用,但有参构造方法并不能被自动调用,只能依赖于super关键字显式地调用父类的构造方法。

       如果使用finalize()方法对对象进行清理,需要确保子类的finalize()方法的最后一个动作是调用父类的finalize()方法,以保证当垃圾回收对象 占用内存时,对象的所有部分都能被正常终止

 

二、Object类

       在Java中,所有的类都直接或间接继承了java.lang.Object 类。Object 类是比较特殊的类,它是所有类的父类,是Java类层中的最高层类。当创建一个类时,总是在继承,除非某个类已经指定要从其他类继承,否则它就是从java.lang.Object类继承而来的,可见Java中的每个类都源于java.lang.Object类,如String、Integer 等类都是继承于Object类;除此之外,自定义的类也都继承于Object类。由于所有类都是Object子类,所以在定义类时,省略了extends Object关键字。

       Object 类中的getClass()、 notify()、 notifyAll()、wait()等方法不能被重写,因为这些方法被定义为final类型。

1、getClass()方法

       getClass()方法是Object类定义的方法,它会返回对象执行时的Class 实例,然后使用此实例调用getName()方法可以取得类的名称。

getClass().getname();

2、toString()方法

       toString()方法的功能是将一个对象返回为字符串形式,它会返回一个String 实例。在实际的应用中通常重写toStrin()方法,为对象提供一个特定的输出模式。当这个类转换为字符串或与字符串连接时,将自动调用重写的toString()方法。

例:

public class classTest {
	
	public String toString() { // 重写toString()方法
		return "在" + getClass().getName() + "类中重写toString()方法";
	}
	
	public static void main(String[] rgs) {
		System.out.println(new classTest()); // 打印本类对象
}

输出:

3、equals()

        equals()和“==”的区别在于:equals()方法比较的是两个对象的实际内容,而“==”比较的是两个对象的引用是否相等。

例:

class V{
	
}

public class classTest {	
	public static void main(String[] args) {
		String s1 = "123"; // 实例化两个对象,内容相同
		String s2 = "123";
		System.out.println(s1.equals(s2)); // 使用equals()方法调用
		V v1 = new V(); // 实例化两个V类对象
		V v2 = new V();
		System.out.println(v1.equals(v2)); // 使用equals()方法比较v1与v2对象
	}	
}

输出:

        从本实例的结果中可以看出,在自定义的类中使用equals()方法进行比较时,将返回false, 因为equals()方法的默认实现是使用“==”运算符比较两个对象的引用地址,而不是比较对象的内容,所以要想真正做到比较两个对象的内容,需要在自定义类中重写equals()方法。

 

三、对象类型转换和使用instanceof操作符判断对象类型

1、对象类型的转换

(1)向上转型

        向上转型就是把子类对象赋值给父类类型的变量。

(2)向下转型

        向下转型是将较抽象的类转换为较具体的类。这样的转型通常会出现问题,因为子类总是父类的一个实例,但父类不一定是子类的实例。

2、使用instanceof操作符判断对象类型

        当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例,就会发生ClassCastException异常,所以在执行向下转型之前需要养成一个良好的习惯,就是判断父类对象是否为子类对象的实例。这个判断通常使用instanceof 操作符来完成。可以使用instanceof操作符判断是否一个类实现了某个接口,也可以用它来判断一个实例对象是否属于一个类。语法如下:

myobject instanceof ExampleClass
  • myobject: 某类的对 象引用。
  • ExampleClass: 某个类。

        使用instanceof 操作符的表达式返回值为布尔值。如果返回值为true, 说明myobject 对象为ExampleClass的实例对象;如果返回值为false,说明myobject对象不是ExampleClass的实例对象。

例:

class Quadrangle {
	public static void draw(Quadrangle q) {
		// SomeSentence
	}
}

class Square extends Quadrangle {
	// SomeSentence
}

public class Parallelogram extends Quadrangle {
	public static void main(String args[]) {
		Quadrangle q = new Quadrangle(); // 实例化父类对象
		
		// 判断父类对象是否为Parallelogram子类的一个实例
		if (q instanceof Parallelogram) {
			Parallelogram p = (Parallelogram) q; // 向下转型操作
			System.out.println("父类对象Quadrangle为Parallelogram子类的一个实例");
		}else {
			System.out.println("父类对象Quadrangle不为子类对象Parallelogram的一个实例");
		}
		
		// 判断父类对象是否为Parallelogram子类的一个实例
		if (q instanceof Square) {
			Square s = (Square) q; // 进行向下转型操作
			System.out.println("父类对象Quadrangle为Square子类的一个实例");
		}else {
			System.out.println("父类对象Quadrangle不为子类对象Square的一个实例");
		}	
	}
}

输出:

 

四、方法重载

       方法的重载就是在同一个类中允许同时存在一一个以上的同名方法。方法重载除了同名外有以下几种形式:

  • 参数类型不同;
  • 参数个数不同;
  • 参数顺序不同。

       注:虽然在方法重载中可以使两个方法的返回类型不同,但只有返回类型不同并不足以区分两个方法的重载,还需要通过参数的个数以及参数的类型来设置。此外,使用不定长方法也是重载的一种方法。

例:

public class OverLoadTest {
	// 定义一个成员方法
	public static int add(int a, int b) { 
		return a + b;
	}
	
	// 重载1:参数类型不同的方法
	public static double add(double a, double b) {
		return a + b;
	}
	
	// 重载2:参数个数不同的方法
	public static int add(int a) {
		return a;
	}
	
	// 定义一个成员方法
	public static int add(int a, double b) { 
		return 1;
	}
	
	// 重载3:参数次序不同的方法
	public static int add(double a, int b) {
		return 1;
	}
	
	// 不定长方法
	public static int add(int... a) {
		int s = 0;
		for (int i = 0; i < a.length; i++) {
			s += a[i];			
		}
		return s;
	}
	
	public static void main(String args[]) {
		System.out.println("调用add(int,int)方法:" + add(1, 2));
		System.out.println("调用add(double,double)方法:" + add(2.1, 3.3));
		System.out.println("调用add(int)方法:" + add(1));
		System.out.println("调用add(int double)方法"  + add(1, 2.6));
		System.out.println("调用add(double int)方法"  + add(6.6, 1));
		System.out.println("调用 add(int... a)" + add(1, 2, 3, 4, 5));
	}
}

输出:

 

五、多态

       利用多态可使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。

例:

// 定义一个正方形类,继承四边形类
class Square extends Quadrangle{
	public Square() {
		System.out.println("正方形");
	}
}

//定义一个平行四边形类,继承四边形类
class Parallelogramgle extends Quadrangle{
	public Parallelogramgle() {
		System.out.println("平行四边形");
	}
}

// 四边形类
public class Quadrangle {
	// 实例化保存四边形对象的数组对象
	private Quadrangle[] qtest = new Quadrangle[6];
	private int nextIndex = 0;
	
	public void draw(Quadrangle q) {
		if(nextIndex < qtest.length) {
			qtest[nextIndex] = q;
			System.out.println(nextIndex);
			nextIndex++;
		}
	}
	
	public static void main(String args[]) {
		Quadrangle q = new Quadrangle();
		// 以正方形对象为参数调用draw()方法
		q.draw(new Square());
		// 以平行四边形对象为参数调用draw()方法
		q.draw(new Parallelogramgle());
	}
}

输出:

 

六、抽象类与接口

1、抽象类

       通常可以说四边形具有4条边,或者更具体一点,平行四边形是具有对边平行且相等特性的特殊四边形,等腰三角形是其中两条边相等的三角形,这些描述都是合乎情理的,但对于图形对象却不能使用具体的语言进行描述,它有几条边,究竟是什么图形,没有人能说清楚,这种类在Java中被定义为抽象类。

       在解决实际问题时,一般将父类定义为抽象类,需要使用这个父类进行继承与多态处理。回想继承和多态原理,继承树中越是在上方的类越抽象,如鸽子类继承鸟类、鸟类继承动物类等。在多态机制中,并不需要将父类初始化对象,我们需要的只是子类对象,所以在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:接口可以像类- -样被权限修饰符修饰,但public关键字仅限用于接口在与其同名的文件中被定义。
  • interface:定义接口关键字。
  • drawTest:接口名称。

      一个类实现一个接口,可使用implements关键字,代码如下:

public class Parallelogram extends Quadrangle implements dradTest{
    // ...
}

       注:在接口中定义的方法必须被定义为public或abstract形式其他修饰权限不被Java编译器认可,即使不将该方法声明为public形式,它也是public。

       在接口中定义的任何字段都自动是static和final的。

例:

// 定义接口
interface drawTest{
	public void draw();
}

//定义平行四边形类,该类继承了四边形类,并实现了drawTest接口
class ParallelogramgleUseInterface extends QuadrangleUseInterface implements drawTest{
	// 由于该类实现了接口,所有需要覆盖draw()方法
public void draw() {
		System.out.println("平行四边.draw()");
	}
}

// 定义正方形类,该类继承了四边形,并实现了drawTest接口
class SquareUseInterface extends QuadrangleUseInterface implements drawTest{
	public void draw() {
		System.out.println("正方形.draw()");
	}
	
	// 覆盖父类方法
	public void doAnyThing() {
		System.out.println("正方形.doAnyThing()");
	}
}

// 该类仅继承了四边形类
class AnyThingUseInterface extends QuadrangleUseInterface{
	public void doAnyThing() {
		System.out.println("AnyThingUseInterface.doAnyThing()");
	}
}

// 四边形类
public class QuadrangleUseInterface{
	public void doAnything() {
		System.out.println("QuadrangleUseInterface.doAnything()");
	}
	
	public static void main(String[] args) {
		// 在接口中存2个其实现类
		drawTest[] d = {
				new SquareUseInterface(),
				new ParallelogramgleUseInterface()
		};
		
		for(int i = 0; i < d.length; i++) {
			d[i].draw();
		}
	}
}

输出:

       在本实例中,正方形类与平行四边形类分别实现了drawTest 接口并继承了四边形类,所以需要覆盖接口中的方法draw()。

3、接口与集继承

       Java中不允许出现多继承,但使用接口就可以实现多重继承。一个类可同时实现多个接口,因此可将所有需要继承的接口放置在implements关键字后并使用逗号隔开。但这可能会在一个类产生大量的代码,因为继承一个接口时需要实现接口中所有方法。

       多重继承语法:

class 类名 implements接口1, 接口2, ..., 接口n

       在定义一个接口时,使该接口继承另一个接口。

interface intf1{
}
interface intf2 entends intf1{
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

原创文章 99 获赞 68 访问量 4万+

猜你喜欢

转载自blog.csdn.net/King_weng/article/details/103758235