06 Java语言核心知识 - 9

类的三大特性:封装、继承、多态

  • 类的封装,相当于一个黑匣子,放在黑匣子中的东西,你是看不到的。
  • 继承是类的一个重要属性,可以从一个简单的类继承出相对复杂高级的类,这样可使得程序编写的工作量大大减轻。
  • 多态可以动态地对对象进行调用,使对象之间变得相对独立。

1 类的封装

1.1 封装的基本概念

类的封装使用范例1

class Person{					//声明了一个新的类Person,
	String name;				//类中有name、age两个属性,
	int age;
	void talk(){				//还有一个talk()方法用于输出信息
		System.out.println("我是"+name+",今年"+age+"岁");
	}
}

public class TestPersonDemo2{
	public static void main(String[] args){
		//声明并实例化一个Person对象p
		Person p = new Person();
		//给p中的属性赋值
		p.name="张三";
		//在这里将对象p的年龄属性赋值为-25
		p.age=-25;
		//调用Person类中的talk()方法
		p.talk();
	}
}

# 运行结果
我是张三,今年-25

将年龄赋值为-25,这明显是一个不合法的数据,最终程序在调用talk()方法的时候会打印错误的信息。导致这种错误的原因是,程序在入口处没有检验,输出的数据有问题。

1.2 类的封装

之前所列举的程序都是用对象直接访问类中的属性,这在面向对象法则中是不允许的。所以为了避免程序中这种错误的发生,在一般的开发中往往要将类中的属性封装(private)。

class Person{
	private String name;
	private int age;
	void talk(){
		System.out.println("我是"+name+",今年"+age+"岁");
	}
}

public class TestPersonDemo1{
	public static void main(String[] args){
		//声明并实例化一个Person对象p
		Person p = new Person();
		//给p中的属性赋值
		p.name = "张三";
		//在这里将p对象中的年龄赋值为-25
		p.age = -25//调用Person类中的talk()方法
		p.talk();
	}
}

运行结果:
在这里插入图片描述
报错提示为:属性name、age为私有的,所以不能由对象直接进行访问,这样就可以保证对象无法直接去访问类中的属性。
程序人员一般在设计类时,会对属性增加一些方法,如setXxx()、getXxx()这样公有方法来解决这一矛盾。

属性封装

class Person{
	private String name;
	private int age;
	
	void talk(){
		System.out.println("我是"+name+",今年"+age+"岁");
	}
	
	public void setName(String str){
		name = str;
	}
	
	public void setAge(int a){
		if (a>0) {
			age = a;
		}
	}
	
	public String getName(){
		return name;
	}
	
	public int getAge(){
		return age;
	}
}

public class TestPersonDemo1{
	public static void main(String[] args){
		//声明并实例化一个Person对象p
		Person p = new Person();
		//给p中的属性赋值
		p.setName = "张三";	//调用了Person类中的setName()方法,并赋值为“张三”
		//在这里将p对象中的年龄赋值为-25
		p.setAge = -25//调用setAge()方法,传进了一个-25的不合理年龄
		//调用Person类中的talk()方法
		p.talk();
	}
}

# 运行结果
我是张三,今年0

加入一些setXxx()、getXxx()方法,主要用来设置和取得类中的私有属性。
调用了Person类中的setName()方法,并赋值为 张三 。在设置Person中属性的时候,因为不满足条件而不能被设置成功,所以age的值为自己的默认值。
由此,用private可以将属性封装起来,当然用private也可以封装方法,封装的形式如下:

封装属性:private 属性类型 属性名
封装方法:private 方法返回类型 方法名称(参数)

注意:
用private声明的属性或方法只能在其类的内部被调用,而不能在类的外部被调用。
读者可以先暂时简单地理解:在类的外部不能用对象去调用private声明的属性或方法。

方法封装

class Person{
	private String name;
	private int age;
	private void talk(){
		System.out.println("我是:"+name+",今年:"+age+"岁");
	}
	public void setName(String str){
		name=str;
	}
	public void setAge(int a){
		if(a>0)
			age=a;
	}
	public String getName(){
		return name;
	}
	public int getAge(){
		return age;
	}
	public getTalk(){
		return "我是:"+name+",今年:"+age+"岁";
	}
}

public class TestPersonDemo4{
	public static void main(String[] args){
		//声明并实例化一个Person对象p
		Person p = new Person();
		//给p中的属性赋值
		p.setName("张三");
		//给p对象的年龄赋值为-25
		p.setAge(-25);
		//调用Person类中的talk()方法
		p.talk();
	}
}

程序中加入一些setXxx()、getXxx()方法,主要用来设置和取得类中的私有属性。
private可用来声明属性和方法,但这个方法只能在类的内部被访问了。

提示:
要设置或取得属性值,只能用setXxx()、getXxx()方法,这是一个明确且标准的规定。
一般来说,设计较好的程序的类中的属性都是需要封装的。

类的继承

2.1 继承的基本概念

通过继承可以简化类的定义,扩展类的功能。在Java中支持类的单继承和多层继承,但不支持多继承,即一个类只能继承一个类而不能继承多个类。

实现继承的格式如下:

class 子类名 extends 父类

Java继承只能直接继承父类中的公有属性和公有方法,而隐含的(不可见地)继承了私有属性。

假设现在有一个Person类,里面有name、age两个属性,而另外一个Student类,需要有name、age、school等3个属性。在此可以看到Person类中已经存在有name和age两个属性,所以不希望在Student类中再重新声明这两个属性,这个时候就需要考虑是不是可以将Person类中的内容继续保留到Student类中,这就引出了接下来要介绍的类的继承概念。
在这里插入图片描述
在这里希望Student类能够将Person类的内容继承下来后继续使用,可用下图表示。
在这里插入图片描述
Java类的继承可用下面的语法来表示。

class 父类					// 定义父类
{}
class 子类 extends 父类		//用extends关键字实现类的继承
{}

2.2 类的继承实例

class Person{					//声明了一个名为Person的类,里面有name和age两个属性
	String name;
	int age;
}

class Student extends Person{	//声明了一个名为Student的类,并继承自Person类。
	String school;
}

public class TestPersonStudentDemo{
	public static void main(String[] args){
		Student s = new Student();		//声明并实例化了一个Student类的对象s
		//访问Person类中的name属性
		s.name = "张三";
		//访问Person类中的age属性
		s.age = 25;
		//访问Student类中的school属性
		s.school="北京";
		System.out.println("姓名:"+s.name+",年龄"+s.age+",学校:"+s.school);
	}
}

# 运行结果:
姓名:张三,年龄25,学校:北京

分别用Student类的对象调用程序中的name、age、school属性。可以调用name和age。这是因为Student类直接继承自Person类,也就是说Student类直接继承了Person类中的属性,所以Student类的对象才可以访问到父类中的成员。
在这里插入图片描述

提示:
在Java中只允许单继承,而不允许多重继承,也就是说一个子类只能有一个父类。
但在Java中却允许多层继承

  1. 多重继承
    在这里插入图片描述
class A
{}
class B
{}
class C extends A,B
{}

类C同时继承了类A、类B,也就是说类C同时继承了两个父类,这是不允许的。

  1. 多层继承
    在这里插入图片描述
class A
{}
class B extends A
{}
class C extends B
{}

类B 继承了类A,而类C又继承了类B,也就是说类B是类A的子类,而类C则是类A的孙子类。

3 类的继承专题研究

3.1 子类对象的实例化过程

子类可以继承直接父类中的方法和属性

class Person{		//声明了一个Person类,此类中有一个无参构造方法。
	String name;
	int age;
	//父类的构造方法
	public Person(){
		System.out.println("1. public Person(){}");
	}
}

class Student extends Person{	//声明了一个Student类,此类继承自Person类,也有一个无参构造方法。
	String school;
	//子类的构造方法
	public Student(){
		super();		//实际上程序在这里隐含了这样一条语句
		//super主要的功能是完成子类调用父类中的内容,也就是调用父类中的属性或方法
		System.out.println("2. public Student(){}");
	}
}

public class TestPersonStudentDemo1{
	public static void main(String[] args){
		Student s = new Student();		//声明并实例化了一个Student类的对象s。
	}
}

3.2 super关键字的使用

在以前的程序中曾经提到过super的使用,那super到底是什么?从TestPersonStudentDemo1中读者应该可以发现,super关键字出现在子类中,而且是去调用了父类中的构造方法,所以可以得出结果:super主要的功能是完成子类调用父类中的内容,也就是调用父类中的属性或方法。
将程序做相应的修改,构成以下范例:

class Person{		//声明了一个名为Person的类,里面有name、age两个属性,并声明了一个含有两个参数构造方法。
	String name;
	int age;
	//父类的构造方法
	public Person(String name,int age){
		this.name = name;
		this.age = age;
	}
}

class Student extends Person{	//声明了一个Student的类,此类继承自Person类。声明了一个子类的构造方法,在此方法中用super("张三",25)调用父类中有两个参数的构造方法。
	String school;
	//子类的构造方法
	public Student(){
		//在这里用super调用父类中的构造方法
		super("张三"25);
	}
}

public class TestPersonStudentDemo2{
	public static void main(String[] args){
		Student s = new Student();	//声明并实例化了一个Student类的对象s
		//为Student类中的school赋值
		s.school="北京";
		System.out.println("姓名:"+s.name+",年龄:"+s.age+",学校:"+s.school);
	}
}

# 运行结果:
姓名:张三,年龄:25,学校:北京

注意:
用super调用父类中的构造方法,只能放在程序的第一行。

super关键字不仅可用于调用父类中的构造方法,也可用于调用父类中的属性或方法,例如下面的格式。

super.父类中的属性;
super.父类中的方法();

示例:

class Person{
	String name;
	int age;
	//父类的构造方法
	public Person(){
	}
	public String talk(){
		return "我是:"+this.name+",今年:"+this.age+"岁";
	}
}

class Student extends Person{
	String school;
	//子类的构造方法
	public Student(String name,int age,String school){
	//在这里用super调用父类中的属性
	super.name = name;
	super.age = age;
	
	//调用父类中的talk()方法
	System.out.print(super.talk());

	//调用本类中的school属性
	this.school = school;
	}
}

public class TestPersonStudentDemo3{
	public static void main(String[] args){
		Student s = new Student("张三",25,"北京");
		System.out.println(",学校"+s.school);
	}
}


# 运行结果
我是:张三,今年:25,学校

3.3 限制子类的访问

有些时候,父类不希望子类可以访问自己的类中全部的属性和方法,所以需要将一些属性与方法隐藏起来,不让子类去使用。为此可在声明属性或方法时加上 private 关键字,表示私有。

class Person{
	//在这里将属性封装
	private String name;
	private int age;
}

class Student extends Person{
	//在这里访问父类中被封装的属性
	public void setVar(){
		name = "张三";
		age = 25;
	}
}

class TestPersonStudentDemo4{
	public static void main(String[] args){
		new Student().setVar();
	}
}

运行结果报错
从编译器的错误提示可以看到,对name和age属性,在子类中无法进行访问。

3.4 覆写

覆写 的概念与 重载 相似,它们均是Java 多态 的技术之一。所谓 重载 ,即是方法名称相同,但却可在不同的场合做不同的事。当一个子类继承一个父类,而子类中的方法与父类中的方法的名称、参数个数、类型等都完全一致时,就称子类中的这个方法覆写了父类中的方法。同理,如果子类中重复定义了父类中已有的属性,则称子类中的属性覆写了父类中的属性。

class Super{
	访问权限 方法返回值类型 方法1(参数1{}
}

class Sub extends Super{
	访问权限 方法返回值类型 方法1(参数1{}
}

子类覆写父类的实现

class Person{
//声明了一个名为Person的类,里面声明了name和age两个属性,和一个talk()方法
	String name;
	int age;
	public String talk(){
		return "我是:"+this.name+",今年"+this.age+"岁";
	}
}

class Student extends Person{
//声明了一个名为Student的类,此类继承自Person类,也就继承了name和age属性,同时声明了一个与父类中同名的talk()方法,也可以说此时Student类中的talk()方法覆写了Person类中的talk()方法。
	String school;
	public Student(String name,int age,String school){
		//分别为属性赋值
		this.name = name;
		this.age = age;
		this.school = school;
	}
	//此处覆写Person中的talk()方法
	public String talk(){
		return "我在"+this.school+"上学";
	}
}

class TestOverDemo1{
	public static void main(String[] args){
		Student s = new Student("张三",25,"北京");			//实例了一个子类对象,并同时调用子类构造方法为属性赋初值。
		//此时调用的是子类中的talk()方法
		System.out.println(s.talk());	//调用talk(),但调用的不是子类的talk()方法。
	}
}

# 运行结果
我的北京上学

上个程序调用的是子类的talk()方法,我们如何调用父类的talk()方法呢?

class Person{
//声明了一个名为Person的类,里面声明了name和age两个属性,和一个talk()方法
	String name;
	int age;
	public String talk(){
		return "我是:"+this.name+",今年"+this.age+"岁";
	}
}

class Student extends Person{
//声明了一个名为Student的类,此类继承自Person类,也就继承了name和age属性,同时声明了一个与父类中同名的talk()方法,也可以说此时Student类中的talk()方法覆写了Person类中的talk()方法。
	String school;
	public Student (String name,int age,String school){
		//分别为属性值
		this.name = name;
		this.age = age;
		this.school = school;
	}
	//此处覆写Person类中的talk()方法
	public String talk(){
		return super.talk()+",我在"+this.school+"上学";		//通过super.talk()方式,调用了父类中的talk()方法。
	}
}

class TestOverDemo2{
	public static void main(String[] args){
		Student s = new Student("张三",25,"北京");		//实例化了一个子类的对象,并同时调用子类构造方法为属性赋初值
		System.out.println(s.talk());
		//用子类调用talk()方法,但此时调用的是子类的talk()方法。
	}
}

# 运行结果
我是:张三,今年25,我在北京上学

在子类中,通过super.方法()调用父类中被子类覆写的方法。

4 类的多态

4.1 多态的基本概念

什么是多态性?读者应该还清楚之前解释过重载的概念,重载的最终效果就是调用同一个方法名称,却可以传入参数的不同而得到不同的处理结果,这其实就是多态性的一种体现。

class Person{
//声明了一个Person类,此类中有fun1()、fun2()两个方法。
	public void fun1(){
		System.out.println("1.Person{fun1()}");
	}
	public void fun2(){
		System.out.println("2.Person{fun2()}");
	}
}

//Student类扩展自Person类,也就继承了Person类中的func1()、func2()方法
class Student extends Person{
//声明了一个Student类,此类继承自Person类,并覆写了fun1()方法
	//在这里覆写了Person类中的fun1()方法
	public void func1(){
		System.out.println("3.Person{fun1()}");
	}
	public void func3(){
		System.out.println("4.Person{fun3()}");
	}
}

class TestJavaDemo1{
	public static void main(String[] args){
		//此处,父类对象由子类实例化
		Person p = new Student();		//声明了一个Person类(父类)的对象p,之后由子类对象去实例化此对象。
		//调用fun1()方法,观察此处调用的是哪个类里的fun1()方法
		p.fun1();		//由父类对象调用fun1()方法
		p.fun2();
	}
}

# 运行结果
3.Person{fun1()}
2.Person{fun2()}

p是父类的对象,但调用fun1()方法的时候并没有调用其本身的fun1()方法,而是调用了子类中被覆写了的fun1()方法。之所以会产生这样的结果,最根本的原因就是因为父类对象并非由其本身的类实例化,而是通过子类实例化,这就是所谓的对象多态性,即子类实例化对象可以转换为父类实例化对象。

4.2 类的多态实例

  1. 向上转型
    父类对象通过子类对象去实例化,实际上就是对象的向上转型。向上转型是不需要进行强制类型转换的,但是向上转型会丢失精度。

  2. 向下转型
    与向上转型对应的一个概念就是 向下转型 ,所谓向下转型,也就是说父类的对象可以转换为子类对象,但需要注意的是,这时则必须要进行强制的类型转换。

理解:
一天,有个小孩在马路上看见了一辆跑车,他指着跑车说那是汽车。相信读者都会认为这句话没有错,跑车的确符合汽车的标准,所以把跑车说成汽车并没有错误,只是不准确而已。不管是小轿车也好,货车也好,都是汽车,这在现实生活中是说得通的。在这里读者可以将这些小轿车、货车都想象成其策划的子类,它们都是扩展了汽车的功能,都具备了汽车的功能,所以它们都叫做汽车,那么这种概念就成为向上转型。而相反,假如说把所有的汽车都说成跑车,拿结果肯定是不正确的,因为汽车有很多种,必须明确地指明是那辆跑车才行,需要加一些加一些限制,这时候就必须明确地指明是哪辆车,所以需要进行强制的说明,叫做向上转型需要强制条件。

概括:
(1)向上转型可以自动完成。
(2)向下转型必须进行强制类型转换。

提示:并非是全部的父类对象都可以转换为子类对象,请看下面范例。

class Person{
	public void fun1(){
		System.out.println("1.Person{fun1()}");
	}
	public void fun2(){
		System.out.println("2.Person{fun2()}");
	}
}

//Student类继承Person类,也就继承了Person类的fun1()、fun2()方法
class Student extends Person{
	//在这里覆写了Person类中的fun1()、fun2()方法
	public void fun1(){
		System.out.println(3.Student{fun1()});
	}
	public void fun3(){
		System.out.println(4.Student{fun3()});
	}
}

class TestJavaDemo2{
	public static void main(String[] args){
		//此处,父类对象由自身实例化
		Person p = new Person();
		//将p对象向下转型
		Student s = (Student) p;
		p.fun1();
		p.fun2();
	}
}

在这里插入图片描述
在程序中可以看到,Person对象p是由Person类本身实例化的,将Person对象p强制转换为子类对象,这样写在语法上是没有任何错误的。但是在运行时可以发现出了异常,这是为什么?为什么父类不可以向子类转换了呢?
这个问题不难理解,想一想现实生活中的例子,假如你今天刚买完一些生活用品,回家的路上碰见了一个孩子,这个孩子忽然对你说:“我是你的儿子,你把你的东西给我吧!”,这个时候你肯定不会把你的东西给这个孩子,因为你不确定他是否跟你有关系,怎么能给呢?那么在这程序中也是同样的道理,父类用其本身类实例化自己的对象,但并不知道谁是自己的子类,那肯定在转换时出现错误,那么这个错误如何修正呢?如下所示:

Person p = new Student();

这个时候相当于是由子类去实例化父类对象,也就是说这个时候父类知道有这么一个子类,也就相当于父亲知道了自己有这么一个孩子,所以下面运行时,不会再有问题了。

总结:


	*********************
	*	Person	父类	*
	*	Student	子类	*
	*********************1)向上转型可以自动完成。
Person p = new Student();
此处,父类对象由子类实例化。
声明了一个Person类(父类)的对象p,之后由子类对象去实例化此对象。


(2)向下转型必须进行强制类型转换。
	()	Person p = new Student();
	由子类去实例化父类对象,这个时候父类知道有这么一个子类

	(X)	Person p = new Person();
	此处,父类对象由自身实例化,父类Person不知道自己有子类Student
	Student s = (Student) p;
	强制转换不会报错,但程序依然不能运行,因为父类不知道Person是自己的子类

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值