【Java修行之路】--面向对象的三大基本特征:封装、继承、多态

Java面向对象的三大基本特征(封装、继承、多态)

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

1.Java访问权限修饰符

​ 在学习Java面向对象三大特征之前先了解一下关于Java访问权限修饰符的知识。

​ Java中有四种访问权限:公有(public)私有(private)保护(protected)默认(default)。但默认权限没有访问权限修饰符。默认访问权限是包访问权限,即在没有任何修饰符的情况下定义的类,属性和方法在一个包内都是可以访问的。具体如下:

公有私有保护默认
可以被所有类访问只有内部类允许私有,只有在当前类中被访问只有内部类可以设保护权限,相同包中的类和其子类可以访问可以被当前包中的类访问
属性可以被所有的类访问只能被当前的类访问可以被相同包中的类和当前类的子类访问可以被相同包中的类访问
方法可以被所有的类访问只能被当前的类访问可以被相同包中的类和当前类的子类访问可以被相同包中的类访问

2.封装

我们用例子更加形象的解释封装。

2.1封装问题引例

【例1-一只品质不可控的猫🐱】

public class TestCat {
	public static void main(String[] args) {
		MyCat aCat=new MyCat();
		aCat.weight=-10.0f;//设置MyCat的属性值
		float temp=aCat.weight;//获取MyCatde的属性值
		System.out.println("The weight of a cat is"+temp);
		
	}

}
class MyCat{
	public float weight;//通过public修饰符,开放MyCat的属性给外界
	MyCat(){}
}

在这里插入图片描述

​ 运算结果如上,来分析一下这个例子代码:
​ 1.首先我们来分析一下MyCat类。第15行,通过public修饰符,开放MyCat的属性(weight)给外界,这意味着外界可以通过“对象名.属性名”的方式来访问(读或写)这个属性。第16行声明一个无参构造方法,在本例中无明显含义。

​ 2.第05行,定义一个对象aCat。第07行通过点操作符获得这个对象的值。第08行输出这个对象的属性值。我们需要重点关注第06行,它通过“点操作符”设置这个对象的值(-10.0 )f。

​ 3.一般意义上,"-10.0f"是一个普通的合法的单精度浮点数,因此在纯语法上,它给weight赋值没有任何问题。但是对于一个真正的对象(猫)来说,这是完全不能接受的,一个猫的重量(weight)怎么可能为负值?这明显是“一只不合格的猫”,但是由于weight这个属性开放给外界,“猫的体重值”无法做到“独立自主”因为它的值可被任何外界的行为所影响。

2.2封装问题实例

【例2-一只难以访问的猫🐱】

public class TestCat2 {
	public static void main(String[] args) {
		MyCat aCat=new MyCat();
		aCat.weight=-10.0f;//设置MyCat的属性值
		float temp=aCat.weight;//获取MyCatde的属性值
		System.out.println("The weight of a cat is"+temp);
		
	}

}
class MyCat{
	private float weight;//通过private修饰符,开放MyCat的属性给外界
	MyCat(){}
}

​ 与例1不同的是在声明MyCat类中的属性weight时,用的是private(私有)修饰符。而这使下面的代码编译无法通过:

MyCat aCat=new MyCat();
aCat.weight=-10.0f;//设置MyCat的属性值
float temp=aCat.weight;//获取MyCatde的属性值

这是因为weight是私有的,所以外界不能由对象直接进行访问这些私有属性。

2.3私有属性的Setter和Getter方法

虽然可以通过封装,达到外界无法访问私有属性的目的。但是如果非要给对象的属性赋值的话,这一矛盾该如何解决呢?程序设计人员一般在类的设计时,都会设计存或取这些属性的公共接口这些接口的外在表现形式都是(public)方法。而在这些方法里,我们可以对存或取属性的操作,实施合理的检查,以达到保护属性数据的目的。通常,对属性值设置的方法被命名为 Setxxx0,其中Xxx为任意有意义的名称,这类方法可统称为 Setter方法.而对取属性值的方法通常被命名为 Getyyy,其中Yyy为任意有意义的名称,这类方法可统称为 Getter方法

【例3-一只品质可控的猫🐱】


public class TestCat3 {
	public static void main(String[] args) {
		MyCat aCat=new MyCat();
		aCat.SetWeight(-10f);//设置MyCat的属性值
		
		float temp=aCat.GetWeight();//获取MyCatde的属性值
		System.out.println("The weight of a cat is"+temp);
		
	}

}
class MyCat{
	private float weight;//通过private修饰符,封装MyCat的属性
	public void SetWeight(float wt) {
		if(wt>0) {
			weight=wt;
		}
		else {
			System.out.println("weight设置非法(应>0).\n 采用默认值");
			weight =10.0f;
		}
	}
	public float GetWeight() {
		return weight;
	}
}

运行结果为:

在这里插入图片描述

【代码详解】:
1.加入 Setweight(oatw)和 float Getweight0等公有方法,外界可以通过这些接口来设置和取得类中的私有属性 weight。
2.调用了Setweight0方法,同时传进一个-10的不合理体重。
3.在完成设置体重时,程序中加了些判断语句,如果传入的数值大于0,则将值赋给weight.属性,否则给出警告信息,采用默认值。通过这个方法可以看出,经由公有接口来对属性值实施操作,我们可以在这些接口里对这些值实施“管控”,从而更好地控制属性成员。

2.4方法的封装使用

由此可以知道,用 prvate可以将属性封装起来,当然用 private也可以封装方法,封装的形式如下:


封装属性:private 属性类型 属性名

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


【例4-方法的封装使用】

  
public class TestCat4 {
	public static void main(String[] args) {
		MyCat aCat=new MyCat();
		aCat.SetWeight(-10f);//设置MyCat的属性值
		
		float temp=aCat.GetWeight();//获取MyCatde的属性值
		System.out.println("The weight of a cat is"+temp);
		aCat.MakeSound();//类型MakeSound()不可视
	}

}
class MyCat{
	private float weight;//通过private修饰符,封装MyCat的属性
	public void SetWeight(float wt) {
		if(wt>0) {
			weight=wt;
		}
		else {
			System.out.println("weight设置非法(应>0).\n 采用默认值");
			weight =10.0f;
		}
	}
	public float GetWeight() {
		return weight;
	}
	private void MakeSound() {
		System.out.println("Meow meow,my weight is"+weight);
	}
}

在这里插入图片描述

​ 由运行结果可知,MyCat中的方法MakeSound()不可视。因为方法MakeSound()被private修饰符设置了只能在MyCat类中被访问的权限。正确的方式可以是:

public float GetWeight(){
    MakeSound();  //方法内添加的方法调用
    return weight;
}

2.5使用构造函数实现数据的封装

【例5】:


public class TestCat5 {
	public static void main(String[] args) {
		MineCat aCat=new MineCat(12,-5);//通过公有接口设置属性值
		
		float ht=aCat.GetWeight();
		float wt=aCat.GetHeight();
		System.out.println("The height of cat is"+ht);
		System.out.println("The weight of cat is"+wt);
	}

}
class MineCat{
	private float weight;
	private float height;
	//在构造函数中初始化私有变量
	public MineCat(float height,float weight) {
		SetHeight(height);
		SetWeight(weight);
	}
	
	private void SetHeight(float ht) {
		if(ht>0) {
			height=ht;
		}
		else {
			System.out.println("Height设置非法(应>0).\n采用默认值20");
		}
	}
	//创建公有方法GetHeight()作为与外界的通信的接口
	public float GetHeight() {
		return height;
	}
	private void SetWeight(float wt) {
		if(wt>0) {
			weight=wt;
		}
		else {
			System.out.println("weight设置非法(应>0)。\n 采用默认值20");
		}
	}
	public float GetWeight() {
		return weight;
	}
}

2.6 封装问题的总结

  • 在Java中,最基本的封装单元是类,类是基于面向对象思想编程语言的基础,程序员可以把具有相同业务性质的代码封装在一个类中,通过接口方法向外部代码提供服务,同时向外部代码屏蔽类里服务的具体实现方式。

  • 数据封装的最重要的目的是在于要实现“信息隐藏( Information Hidding)”。在类中的“数据成(属性)”或者“方法成员”,可以使用关键字“ public”"、" private"、" protected”来设置各成员的访问权限。

  • 封装性是面向对象程序设计的原则之一。它规定对象应对外部环境隐藏它们的内部工作方式。良好的封装可以提高代码的模块化程度,它防止了对象之间不良的相互影响。使程序达到强内聚(许多功尽量在类的内部独立完成,不让外面干预),弱耦合(提供给外部尽量少的方法调用)的最终目标。

3.继承

继承是一种提高程序代码的可重用性,以及提高系统的可扩展性的有效手段。继承有一定的适应情形,不能在代码中随便建立继承关系。

3.1生活中的继承

​ 在自然界,兔子和羊都是食草动物,他们具有食草动物的基本特征和行为。此时如果把食草动物称为“父类”,则兔子和羊是食草动物的子类。同理,狮子和老虎是食肉动物的子类。

​ 从大的角度来看,无论食肉动物还是食草动物都具有动物的基本特征和行为。所以,此时的的动物是父类,而食草动物和食肉动物是子类。

​ 在继承关系中,子类具有父类的特征和行为,还具有一些自己的特殊的特征和行为。在继承关系中,父类和子类需要满足is-a的关系。

is-a关系:(subsumption)包含架构,纯粹的继承关系,Student is a person、Dog is a animal.

​ 另外,需要提醒的是,有各种表达父类和子类的术语,比如父类和子类,超类和子类,基类和派生类,他们表达的是一个意思。

3.2为什么需要继承?

​ 如果有两个类中存在着大量重复的代码。这显然违背了我们在做面向对象编程时,“Write once,only once”的原则。

​ 这时候“继承”就要开始大显身手了!

​ 我们可以在这两个类中抽象出一个父类,把这两个类中都具有的属性和方法提取到父类中去实现,让子类自动继承这些属性和方法。

3.3如何实现继承

​ 在Java语言中,用extends关键字来表示一个类继承了另一个类,例如:

public class JavaTeacher2 extends HZTeacher{ }

​ 这段代码表示JavaTeacher类继承了HZTeacher

**注意Java中不允许多重继承(但可以使用多层继承),只能继承一个类而不能继承多个类!以下代码是错的:**

public class Child extends Base1, Base2{ }

​ 多层继承如下:

class A
{   }
class B extends A
{   }
class C extends B
{   }
3.3.1继承演示程序

public class TestStudent {
	public static void main(String[] args) {
		//实例化一个Student对象
		Student s=new Student("张三",25,"工业大学");
		s.speak();
		s.study();
				
	}

}
class Person{
	String name;
	int age;
	Person(String name,int age){
		this.name =name;//为了区分构造方法Person()中同名的形参和类中属性名,“=”左侧的“this”表明左侧的name和age是来自类中
		this.age =age;
	}
	void speak() {
		System.out.println("我的名字:"+name+"我"+age+"岁");
	}
}
//声明Student子类,有三个属性成员:name、age、school;三个方法:Student()、study()、Person()
class Student extends Person{ 
	String school;
	Student(String name,int age,String school){
		//super调用父类的构造方法
		super(name,age);
		this.school=school;
	}
	void study() {
		System.out.println("我在"+school+"读书");
	}
}
3.3.2 super VS this
super注意点

​ 在子类的构造方法中,通过super和this关键字调用父类的构造方法。注意:创建对象时,先创建父类对象,在创建子类对象。如果没有显示调用父类的构造方法,将自动调用父类的无参构造方法。

  1. 也就是说,调用父类构造的语句(即super语句)必须是构造方法中的第一条语句。
  2. super必须只能出现在子类地方法和构造方法中
  3. super和this不能同时调用构造方法,两者是二选一的关系
super VS this
区别thissuper
查找范围先从本类找到属性或方法,本类找不到再查找父类不查询本类的属性及方法,直接由子类调用父类的指定属性及方法
调用构造this调用的是本类构造方法由子类调用父类构造
特殊表示当前对象------

1.代表对象不同

​ this:本身调用着这个对象

​ super:代表父类对象地应用

2.前提不同

​ this:没有继承也能调用

​ super:只有在继承条件下才能使用

3.构造方法

​ this:本类的构造

​ super:父类的构造

【错误示例】
class Base{
    public String name;
    public Base (String pName){
        name=pName;
}
}
class Child extends Base{
    public Child(){
        name="hello";
        super=("child");
    }
}

解析:在Child类的构造方法中,使用super语句调用了父类的构造方法,但是把他放错了位置!!!因为在创建对象时,要先创建父类对象,在创建子类对象。

【例6-super调用父类的构造方法】

public class SuperDemo {
	 public static void main(String[] args) {
		 Student2 s =new Student2("Jack",30,"HAUT");
		 System.out.println("name:"+s.name+"\tAge:"+s.age+"\tSchool:"+s.school);
	 }

}
class Person2{
	String name;
	int age;
	//父类的构造方法
	public Person2(String name,int age) {
		this.name=name;
		this.age=age;
	}
}
class Student2 extends Person2{
	String school;
	//子类的构造方法
	public Student2 (String name,int age,String school) {
		super(name,age);//调用父类的构造方法
       /*相当于this.name=name;
		       this.age=age;
		*/
		this.school=school;
	}
}
【例7-super调用父类的属性和方法】

调用父类的属性或方法:

super . 父类中的关键字

super. 父类中的方法()


public class SuperDemo2 {
	 public static void main(String[] args) {
		 Student3 s =new Student3("Jack",30,"HAUT");
		 System.out.println("\t I am from:"+s.school);
	 }

}
class Person3{
	String name;
	int age;
	//父类的构造方法
	public Person3(){
		
	}
	public String talk() {
		return "I am:"+this.name+"\t I am:"+this.age+"years old";
		
	}
}
class Student3 extends Person3{
	String school;
	//子类的构造方法
	public Student3 (String name,int age,String school) {
        //用super调用父类中的属性
		super.name=name;
		super.age=age;
		//这里用super调用父类中的talk()方法
		System.out.print(super.talk());
		//调用本类中的school属性
		this.school=school;
	}
}

4.多态

4.1多态的含义

多态,从字面意思上看,就是一种类型表现出多种状态。

​ 在Java中,多态分为两类:

  • 方法多态性,体现在方法的重载与覆写上。
  • 对象多态性,体现在父、子对象之间的转型上。

​ 多态中的一个核心概念就是,子类(派生类)对象可以视为父类(基类)对象。这是容易理解的,如下图所示的继承关系中,鱼(Fish)类、鸟(Bird)类和马( Horse)类都继承于父类 Animal(动物),对于这些实例化对象,我们可以说,鱼(子类对象)是动物(父类对象);鸟(子类对象)是动物(父类对象);同样的,马(子类对象)是动物(父类对象)。

在Java编程里,我们可以用下图表示:

Animal a;
Fish f= new Fish();
Bird b= new Bird();
Horse h= new Horse();
a=f;//鱼儿游
a=b;//鸟儿飞
a=h;//马儿跑

4.2父、子对象之间的转型

  • 向上转型( Upcast)(自动转型):父类 父类对象=子类实例。
    将子类对象赋值给父类对象,这样将子类对象自动转换为父类对象。这种转换方式是安全的。例
    如:我们可以说鱼是动物,鸟是动物,马是动物。这种向上转型在多态中应用得很广泛。
  • 向下转型( Downcast)(强制转型):子类 子类对象=(子类)父类对象。
    将父类对象赋值给子类对象。这种转换方式是非安全的。例如,如果我们说动物是鱼,动物是鸟,
    动物是马,这类描述是不全面的。因此,在特定背景下如果需要父类对象转换为子类对象,就必须使用
    强制类型转换。这种向下转型用的比较少。

【例8-了解多态】


public class Poly {
	public static void main(String[] args) {
		//此处,父类对象由子类实例化
		Person6 p=new Student6();
		//调用fun1(),观察这里调用的是哪个类中的fun1()
		p.fun1();
		p.fun2();
	}

}
class Person6{
	public void fun1() {
		System.out.println("fun1()我来自父类Person");
	}
	
	public void fun2() {
		System.out.println("fun2()我来自父类Person");
	}
}

class Student6 extends Person6{
	//这里覆写了父类中的fun1()
	public void fun1() {
		System.out.println("fun1()我来自子类Student");
	}
	public void fun3() {
		System.out.println("fun3()我来自子类Student");
	}
}

运行结果:

在这里插入图片描述

对于语句: Person p= new Student0,我们分析如下:在赋值运算符“=”左侧,定义了父类 Person对象p,而在赋值运算符“=”右侧,用“ new Student0”声明了一个子类无名对象,然后将该子类对象赋值为父类对象p,事实上,这时发生了向上转型。本例中,展示的是一个父类仅有个子类,这种“一对一”的继承模式,并没有体现出“多”态来。

【例9-方法多态性的使用】

public class Sum {
	void sum1(int i) {
		System.out.println("数字和为:"+i);
	}
	void sum1(int i,int j) {
		System.out.println("数字和为:"+(i+j));
	}
	public static void main(String[] args) {
		Sum demo = new Sum();
		demo.sum1(1);
		demo.sum1(2,3);
	}

}

运行结果:

在这里插入图片描述

对于语句: Person p= new Student0,我们分析如下:在赋值运算符“=”左侧,定义了父类 Person对象p,而在赋值运算符“=”右侧,用“ new Student0”声明了一个子类无名对象,然后将该子类对象赋值为父类对象p,事实上,这时发生了向上转型。本例中,展示的是一个父类仅有个子类,这种“一对一”的继承模式,并没有体现出“多”态来。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰非一日之寒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值