Java多态

目录

一、多态简介:

二、向上转型和向下转型:

三、abstract关键字:

四、接口类(interface关键字):

五、内部类:

1.成员内部类:

2.静态内部类:

3.方法内部类:

 4.匿名内部类:


一、多态简介:

  • 是面向对象最重要的特征。允许不同类的对象对同一消息做出不同的响应。
  • 可分为:

①编译时多态(设计时多态):在编译状态就可以进行不同行为的区分,通常通过方法重载实现。

②运行时多态:直到程序运行时,系统才能动态地决定调用哪个方法。

注意此处指的是运行时多态

  • 实现多态的必要条件:

①满足继承关系。

②父类引用指向子类对象。

二、向上转型和向下转型:

  • 向上转型(隐式转型/自动转型):把一个子类对象转型为父类对象。由小向大转。此时父类引用指向子类实例,可以调用子类重写父类的方法和父类派生下去、可被子类直接使用的方法,但无法调用子类独有方法。注意:父类中static修饰的类方法是不允许被子类重写的!故向上转型后只能调用到父类原有的静态方法
  • 向下转型(强制类型转换):子类引用指向父类对象,必须强制类型转换,可以调用子类特有的方法。必须满足转换条件(向上转型的还原)才能进行转换。

可以通过instanceof运算符来判断引用对象的类型,避免类型转换出现安全性问题、提高代码的强壮性。

举例:

package com.animal;

public class Animal {
	//属性:昵称、年龄
	private String name;
	private int month;
	
	//无参构造
	public Animal() {
		
	}
	
	//带参构造
	public Animal(String name, int month) {
		this.name = name;
		this.month = month;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public int getMonth() {
		return month;
	}
	
	public void setMonth(int month) {
		this.month = month;
	}
	
	//方法:吃东西
	public void eat() {
		System.out.println("动物都有吃东西的能力!");
	}

}
package com.animal;

public class Cat extends Animal {

	// 属性:体重
	private double weight;

	// 无参构造
	public Cat() {

	}

	// 带参构造
	public Cat(String name, int month, double weight) {
		super(name, month); // 调用父类的双参构造
		this.weight = weight;
	}

	public double getWeight() {
		return weight;
	}

	public void setWeight(double weight) {
		this.weight = weight;
	}

	// 方法:跑
	public void run() {
		System.out.println("小猫快乐地跑!");
	}

	// 方法:吃东西(重写父类的方法)
	@Override
	public void eat() {
		System.out.println("猫吃鱼!");
	}

}
package com.animal;

import org.ietf.jgss.Oid;

public class Dog extends Animal {

	// 属性:性别
	private String sex;

	// 无参构造
	public Dog() {
		super();
	}

	// 带参构造
	public Dog(String name, int month, String sex) {
		this.setName(name);
		this.setMonth(month);
		this.setSex(sex);
	}

	public String getSex() {
		return sex;
	}

	public void setSex(String sex) {
		this.sex = sex;
	}

	// 方法:睡觉
	public void sleep() {
		System.out.println("小狗要午睡!");
	}

	// 方法:吃东西(重写父类方法)
	@Override
	public void eat() {
		System.out.println("狗吃骨头!");
	}

}
package com.animal;

public class Master {
	/*
	 * 喂宠物 
	 * 喂猫咪:吃完东西后,主人带猫跑步 
	 * 喂狗:吃完东西后,主人带狗去睡觉
	 */
	
//	//方案1:编写方法,传入不同类型的动物,调用各自的方法
//	public void feed(Cat cat) {
//		cat.eat();
//		cat.run();
//	}
//	
//	public void feed(Dog dog) {
//		dog.eat();
//		dog.sleep();
//	}
//	
	//方案2:编写方法传入动物的父类,方法中通过类型转换调用指定子类的方法
	public void feed(Animal a) {//向上转型
		if (a instanceof Cat) {  //如果满足Cat类的实力条件
				Cat x=(Cat)a;  //向下转型
				x.eat();
				x.run();
		}else if (a instanceof Dog) {
			Dog x=(Dog)a;
			x.eat();
			x.sleep();
		}
	}

}

方案1和方案2皆可,方案2用了向上转型和向下转型。

package com.test;

import com.animal.Cat;
import com.animal.Dog;
import com.animal.Master;

public class MasterTest {

	public static void main(String[] args) {
		Master master=new Master();
		Cat one=new Cat();
		Dog two=new Dog();
		master.feed(one);
		master.feed(two);

	}

}

三、abstract关键字:

  • 在class前面添加abstract关键字(abstract关键字可在访问修饰符前或后,随意),使该类变成抽象类。抽象类不允许直接实例化、只能被继承,可以通过向上转型指向其子类实例。
  • 应用场景:某个父类只是知道其子类应包含怎样的方法,但无法准确知道这些子类如何实现这些方法。这样借由父类和子类的继承关系,限制子类的设计随意性,也一定程度上避免了无意义的父类实例化。
  • 在方法的返回值前面添加abstract关键字,使该方法变成抽象方法抽象方法不能具有方法体(不需要具体实现),子类必须去实现(重写)父类的抽象方法。如果子类也是抽象类,则不用实现父类的抽象方法。
public abstract void eat();
  • 包含抽象方法的类一定是抽象类,但抽象类中可以没有抽象方法。
  • static(静态不能重写)、final(不允许重写)、private(私有)不可以与abstract关键字并存!

四、接口类(interface关键字):

  • 接口的必要性:因Java只支持单继承,故接口就是实现继承的另外一种方式!来替代继承的作用!
  • 接口定义了一批类需要遵守的规范。
  • 接口不关心这些类的内部数据和这些类的方法的实现细节,接口只规定这些类必须提供的方法。
  • 描述不同类型具有相似的行为特征,接口引用指向实现类来描述不同类型对于接口行为的实现。

①若接口的实现类中存在与接口类同名的信息(如常量),当用接口的引用指向实现类时,输出的仍是接口自己定义的常量信息;若调用实现类的实例,通过实现类对象输出的是实现类的信息。

例:

package com.phone;

/**
 * 接口的访问修饰符通常是public或默认
 * @author dell
 *
 */
public interface Inet {

	/* 接口中方法可以不写abstract关键字,
	 * 访问修饰符默认是public,
	 * 当类实现接口时,需要实现接口中的所有抽象方法,否则需要将该类设置成抽象类
	 * */
	void network();
	
	//接口中可以包含常量,默认public、static、final
	int TEMP=10;
	
}
package com.phone;

public class SmartWatch implements Inet{
	@Override
	public void network() {
		System.out.println("可以上网!");
		
	}
	
	public static final int TEMP=20;  //定义常量的格式

}
package com.test;

import com.phone.Inet;
import com.phone.SmartWatch;

public class PhoneTest {

	public static void main(String[] args) {
		

		System.out.println(Inet.TEMP);  //接口名.常量名 访问接口中的常量
		Inet b=new SmartWatch();
		System.out.println(b.TEMP);  //也可通过接口的引用 访问接口中的常量
		SmartWatch c=new SmartWatch();
		System.out.println(c.TEMP); 

	}

}

②有时接口中的方法并非皆为实现类所需要的,但实现类却必须实现接口中的所有方法,有什么解决方法吗?当然有!JDK1.8中提供了一种默认方法,这种默认方法由default关键字修饰可带有方法体,因此子类无需去实现

例:

public interface Inet {

	//default:默认方法,可以带方法体
	default void connection() {
		System.out.println("我是接口中的默认链接"); 
	}
}

 默认方法可通过接口引用去调用。默认方法也是可以由实现类重写的,此时系统生成的重写方法没有了default关键字,且加上了public修饰符:

package com.phone;

public class SmartWatch implements Inet{

	@Override
	public void connection() {
		// TODO Auto-generated method stub
		Inet.super.connection();  //调用接口中的默认方法:connection()
	}  
}

③JDK1.8中还提供了一种静态方法,这种静态方法由static关键字修饰,可带有方法体。

例:

package com.phone;

import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter.DEFAULT;

public interface Inet {

	//static:静态方法,可以带方法体
	static void stop() {
		System.out.println("我是接口中的静态方法"); 
	}
	
}

调用静态方法的格式为:接口名.静态方法名。静态方法不能在实现类中重写。

④在Java中,一个类可以实现多个接口,接口名用逗号隔开即可。对于多接口中相同的方法名,实现类中无法调用(因为不能确定是哪个接口中的方法),可以通过在实现类中重写该方法来调用。

⑤一个类可以在继承父类的同时实现(多个)接口。

子类 extendds 父类 implements 接口

若此时,继承的父类和实现的接口中也出现了同名的方法,子类默认调用的是父类中的方法。

当父类中的属性与接口中的常量同名时,子类中调用会无法分辨,此时需要在子类中去定义它独有的成员去解决该问题。

⑥Java中的接口可以继承,而且存在多继承。子接口去继承父接口。此时子接口的实现类就需要去实现子接口和父接口中的所有方法。当多个父接口中存在同名方法时,子接口调用时会无法分辨,解决方法还是在子接口中定义一个自己的同名方法。

⑦总结接口的重要性:

  • 在Java编程,abstract class 和interface是支持抽象类定义的两种机制。正是由于这两种机制的存在,才使得Java成为面向对象的编程语言。
  • 定义接口有利于代码的规范:对一个大型项目而言,架构师往往会对一些主要的接口来进行定义,或者清理一些没有必要的接口。这样做的目的一方面是为了给开发人员一个清晰的指示,告诉他们哪些业务需要实现;同时也可防止开发人员随意命名而导致的命名不清晰和代码混乱,影响开发效率。
  • 利于对代码维护:比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类。可是在不久将来,你突然发现现有的类已经不能够满足需要,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦。如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护、拓展的方便性。
  • 保证代码的安全和严密:一个好的程序一定符合高内聚低耦合的特征,那么实现低耦合,定义接口是一个很好的方法,能够让系统的功能较好地实现,而不涉及任何具体的实现细节。这样就比较安全、严密一些,这一思想一般在软件开发中较为常见。

五、内部类:

  • 在Java中,可以将一个类定义在另一个类里面或一个方法里面,这样的类称之为内部类。内部类隐藏在外部类内部,更好地实现了信息隐蔽、不允许其他类去随意访问。
  • 内部类分为:①成员内部类静态内部类方法内部类匿名内部类
  • 包含内部类的类成为外部类
  • 内部类的信息获取需要借助外部类去访问。

1.成员内部类:

  • 是最常见的内部类,也称之为内部类。
package com.people;

public class Person {
	
	public int age;
	
	public void eat() {
		System.out.println("吃东西!");
	}
	
	//成员内部类
	/*
	 * 1.内部类在外部使用时,无法直接实例化,需要借助外部类信息才能完成实例化。
	 * 2.内部类的默认访问权限是同包访问。内部类的访问修饰符可以任意,但访问范围会受影响。
	 * 3.内部类可以直接访问外部类的成员(包括成员属性和成员方法),若出现同名属性,优先访问内部类中定义的。
	 * 4.可以使用:外部类.this.成员 来访问外部类中的同名信息。
	 * 
	 */
	class Heart{
		int age=12;
		public String beat() {
			eat();
			return age+"岁的心脏在跳动!"+Person.this.age+"岁";  
			
		}
		
	}
	
	public Heart getHeart() {  //用于获取内部类的方法。
		/* 编码习惯,通常会在外部类中设置一个获取内部类的方法,以便于内部类对象的实例化操作。 */
		new Heart().age=1;  //5.外部类访问内部类信息需要通过内部类实例,无法直接访问
		return new Heart();
	}
}
package com.people;

import com.people.Person.Heart;

public class PeopleTest {

	public static void main(String[] args) {
		Person Lily=new Person();
		Lily.age=18;
		
		/* Heart myHeart=new Heart(); */ //出错!不能直接访问内部类!
		
		//获取内部类实例:
		//法1:new 外部类.new 内部类
		Person.Heart myHeart1=new Person().new Heart();
		System.out.println(myHeart1.beat());
		
		//法2:外部类对象.new 内部类
		Person.Heart myHeart2=Lily.new Heart();
		System.out.println(myHeart2.beat());
		
		//法3:外部类对象.获取方法
		Person.Heart myHeart3=Lily.getHeart();
		System.out.println(myHeart3.beat());
		
	}
}

注意内部类编译文件(.class文件)的名字格式:外部类名$内部类名

2.静态内部类:

  • 由static修饰。
  • 静态内部类对象可以不依赖于外部类对象,直接创建。
package com.people;

public class Person {
	
	public int age;
	
	public static void eat() {
		System.out.println("吃东西!");
	}
	
	public static void run() {
		System.out.println("跑步!");
	}
	
	
	//静态内部类
	/**
	 * 1.静态内部类中,只能直接访问外部类的静态成员①。若想调用非静态成员,可通过对象实例去调用②。
	 * 2.静态内部类对象实例化时,可以不依赖外部类对象③。
	 * 3.可通过:外部类.内部类.静态成员 去访问内部类中的静态成员④。
	 * 4.当内部类属性与外部类属性同名时,默认直接调用内部类中的成员。
	   *       若要访问外部类中的静态属性,可通过:外部类.属性  去访问。
	   *       若要访问外部类中的非静态属性,可通过:new 外部类( ).属性  去访问。
	 * @author dell
	 *
	 */
	static class Heart{
		static int age=12;  //静态成员
		static void say() {  //静态方法
			System.out.println("你好!");
		}
		public String beat() {
			eat();  //①
			new Person().run();  //②
			return age+"岁的心脏在跳动!"+new Person().age+"岁";  //②
		}
	}
}
package com.people;

import com.people.Person.Heart;

public class PeopleTest {

	public static void main(String[] args) {
		Person Lily=new Person();
		Lily.age=18;
		
		//获取静态内部类对象实例 ③
		Person.Heart myHeart=new Person.Heart();
		System.out.println(myHeart.beat());
		
		Person.Heart.say();  //④

	}

}

3.方法内部类:

  • 定义在外部类方法中的内部类,也称为局部内部类。故方法成员的约束对方法内部类同样有效。
package com.people;

public class Person {

	public int age;

	public static void eat() {
		System.out.println("吃东西!");
	}

	public static void run() {
		System.out.println("跑步!");
	}

	public Object getHeart() { 

		//方法内部类
		/*
		 * 1.定义在方法内部,作用访问也在方法内部。
		 * 2.和方法内部成员使用规则一致:在class前面不能添加public、private、protected、static。
		 * 3.类中不能包含静态成员。
		 * 4.类中可包含final,abstract(不推荐)修饰的成员。
		 */
		class Heart {
			int age = 12; 

			void say() { 
				System.out.println("你好!");
			}

			public String beat() {
				eat(); // ①
				new Person().run(); // ②
				return age + "岁的心脏在跳动!" + new Person().age + "岁"; 
			}
		}
		return new Heart().beat();
	}
}
package com.people;

public class PeopleTest {

	public static void main(String[] args) {
		Person Lily=new Person();
		Lily.age=18;
	
		System.out.println(Lily.getHeart());
		
	}
}

 注意内部类编译文件(.class文件)的名字格式发生了变化:

 4.匿名内部类:

  • 没有类名
  • 有时在程序中对某个类的实例只使用1次,此时该类的名字对整个程序而言就可有可无了。此时可将类的定义和类的创建放在一起 完成来简化程序。

例:

package com.annoymous;

public abstract class Person {
	private String name;

	public Person() {
		
	}

	public String getName() {
		return name;
	}

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

}
package com.annoymous;

public class Woman extends Person {

	@Override
	public void read() {
		 System.out.println("女人来自金星。");

	}
}
package com.annoymous;

public class Man extends Person {

	@Override
	public void read() {
		 System.out.println("男人来自火星!");

	}
}

要求根据不同的人的类型调用对应的read方法:

方案A:

package com.test;

import com.annoymous.Man;
import com.annoymous.Woman;
import com.annoymous.Person;

public class PersonTest {

	public void getRead(Man man) {
		man.read();
	}
	
	public void getRead(Woman woman) {
		woman.read();
	}
	
	public static void main(String[] args) {
		PersonTest test=new PersonTest();
		Man a=new Man();
		Woman b=new Woman();
		test.getRead(a);
		test.getRead(b);

	}
}

方案B:

package com.test;

import com.annoymous.Man;
import com.annoymous.Woman;
import com.annoymous.Person;

public class PersonTest {

	public void getRead(Person person) {
	person.read();
}
	
	public static void main(String[] args) {
		PersonTest test=new PersonTest();
		Man a=new Man();
		Woman b=new Woman();
		test.getRead(a);
		test.getRead(b);

	}
}

方案C:利用匿名类

package com.test;

import com.annoymous.Man;
import com.annoymous.Woman;
import com.annoymous.Person;

public class PersonTest {

	public void getRead(Person person) {
	person.read();
        }

	public static void main(String[] args) {

		PersonTest test=new PersonTest();

		//匿名类
		test.getRead(new Person() {
			
			@Override
			public void read() {
				System.out.println("男人来自火星!");
				
			}
		});
		
		test.getRead( new  Person() {
			@Override
			public void read() {
				System.out.println("女人来自金星。");
				
			}
		});
		
	}
}

  • 匿名内部类没有类型名称、实例对象名称。
  • 实例对象的同时完成对对象的内容的编写。
  • 无法使用private、public、protected、abstract、static修饰符。
  • 无法编写构造方法,可以添加构造代码块。
  • 不能有静态成员。
  • 匿名内部类可以实现接口也可继承父类,但是不可兼得!
  • 优点:对内存的损耗相对较小。
  • 缺电:不能重复调用。
  • 适用场景:①只用到类的一个实例。 ②类在定义后马上用到。 ③当给类命名并不会使代码更容易理解时。

此时的类编译文件(.class文件)命名是:外部类$数字.class

 分别对应:测试类、通过匿名类生成的man和woman类

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值