面向对象的三大特征
封装
什么是封装:
封装是把对象(类)的的状态信息隐藏在类的内部,不允许多部程序直接访问,而通过该类提供的方法来实现隐藏信息的操作和访问。
封装的具体步骤:
1,修改属性的可见性来限制对属性的访问。
2,为每个属性创建一对赋值(setter)方法和取值(getter)方法,用于对这些属性的存取。
3,在赋值方法中,加入对属性的存取控制语句。
封装的好处:
-
安全性:为修饰符为private的属性,提供public的方法来访问private属性,这样对数据就设置了访问权限,使得程序更加的安全.例如在类中私有化了age属性,并提供了对外的公共的get和set方法,当外界使用set方法为属性设值的时候 你可以在set方法里面做个if判断,把值设值在0-80岁,那样他就不能随意赋值了,防止数据被恶意破坏。
-
隐藏实现细节:外部访问时,就只管调用get set方法即可,不需要知道封装的具体实现原理.就好像你去买了一台笔记本一样,只知道如何使用笔记本就好了,而笔记本构成的具体的原理就不需要知道
封装时用到修饰符来修饰成员变量和方法,它们的作用范围:
修饰符 | 作用范围 |
---|---|
private | 成员变量和方法只能在其定义的类中被访问,具有类可见性 |
public | 可以被同一个项目中所有类访问,具有项目可见性,这是最大的访问权限 |
protected | 可以被同一个包中的类访问,被同一个项目中的不同包中的子类访问 |
默认 | 成员变量和方法只能被同一个包里的类访问,具有包可见性 |
举个例子:
public class Dog {
/**
* 宠物狗狗类
*/
private String name = "无名氏";//昵称,默认值是“无名氏”
private int health = 100;//健康值,默认值是100
private int love = 0;//亲密度
private String stain = "聪明的拉布拉多犬";//品种
/**
* 读取狗狗昵称
* @return 昵称
*/
public String getName(){
return name;
}
/**
* 指定狗狗昵称
* @param name 昵称
*/
public void SetName(String name){
this.name = name;
}
//读取狗狗的健康值
public int getHealth(){
return health;
}
//指定狗狗健康的范围,进行判断
public void setHealth(int health){
if(health>100 || health <0){
this.health = 40;
System.out.println("健康值应该在0和100之间,默认值为40");
}else {
this.health = health;
}
}
//读取狗狗的亲密度
public int getLove(){
return love;
}
//指定狗狗的亲密度
public void setLove(int love){
this.love = love;
}
//读取狗狗的品种
public String getStrain(){
return stain;
}
//指定狗狗的品种
public void setStrain(String stain){
this.stain = stain;
}
/**
* 通过构造方法指定狗狗的昵称、品种
*/
public Dog(String name,String strain){
this.name = name;
this.stain = stain;
}
/**
* 通过吃饭增加健康值
*/
public void eat(){
if(health>=100){
System.out.println("狗狗需要多运动啊");
}else {
health = health+3;
System.out.println("狗狗吃饱饭了");
}
}
/**
* 通过玩游戏增加与主人的亲密度,减少健康值
*/
public void play(){
if(health<60){
System.out.println("狗狗生病了");
}else {
System.out.println("狗狗正在和主人玩耍");
health-=10;
love+=5;
}
}
/**
* 输出狗狗信息
*/
public void print(){
System.out.println("宠物 的自白:\n我的名字叫:"+this.name+",健康值是:"+this.health+
",和主人的亲密度是:"+this.love+",我是一只"+this.stain+"。");
}
}
继承
什么是继承:
继承是从已知的一个类中派生出新的一个类,叫子类。子类实现了父类所有非私有化属性和方法,并能根据自己的实际需求扩展出新的行为。注意:Java中只支持单继承,也就是每个类只能有一个直接父类。就如同孩子只能有一个爸爸妈妈一样。
来个图:
图中的是我们没有使用继承时写的Dog类和Penguin类,由图可以看到,两个类的有些属性、方法都是一样的,可能连修饰符都一样,这样的话,就造成了代码的冗余、重复,为了使代码看起来简单、不啰嗦,我们就引出了继承。顾名思义,就是写一个父类,然后把一些相通的属性、方法都放到父类里面,然后让子类去继承父类,子类里面就不用写那些相同的属性、方法了。就类似与我们人类一样,孩子生下来就继承一些父亲的特征,比如眼睛或者鼻子毕竟像父亲。
再来个图:
这张图呢,就是使用继承之后的Dog类和Penguin类,是不是比上面看起来简洁多了。那我们该怎么实现继承呢?那我们就看一下继承的语法吧
继承的语法:
修饰符 SubClass extends SuperClass{
//类定义部分
}
来个代码,看着会比较详细些:
public class Pet {
protected String name = "无名氏";
protected int health = 100;
protected int love = 0;
public Pet(){
System.out.println("调用无参构造函数");
}
public Pet(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getHealth() {
return health;
}
public void setHealth(int health) {
this.health = health;
}
public int getLove() {
return love;
}
public void setLove(int love) {
this.love = love;
}
/**
* 输出宠物信息
*/
public void print(){
System.out.println("宠物的自白:\n我的名字叫"+this.name+",健康值是"+this.health+
",和主人的其密度是"+this.love+"。");
}
}
然后让Dog类继承Pet类:
public class Dog extends Pet{
private String strain;//品种
public Dog(String name,String strain){
super(name);//只能放到第一行
this.strain = strain;
}
//读取狗狗的品种
public String getStrain(){
return stain;
}
//指定狗狗的品种
public void setStrain(String stain){
this.stain = stain;
}
}
public class Test {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//1,创建宠物对象Pet并输出信息
Pet pet=new Pet("贝贝");
pet.print();
//2,创建狗狗对象并输出信息
Dog dog=new Dog("偶偶","雪瑞纳");
dog.print();
}
}
测试之后的结果是:
由上可知,Dog已经继承了父类的name health love属性,以及print()方法。
Dog类中的super(name)表示调用了父类Pet的有参构造方法。由上可知,Java中的继承是通过extends实现的,如果没有使用extends关键字,那么这个类直接继承Object类,因为在java中,所有的Java类都直接或者间接的继承了java.lang.Object类。
子类可以从父类那继承哪些“财产”?
- 继承public和protected修饰的属性和方法,无论子类和父类是否在同一个包里。
- 继承默认权限修饰符 的属性和方法,但是子类和父类必须在同一个包里。
- 不能继承private修饰的属性和方法
- 不能继承父类的构造方法。(构造方法也不能重写,后面会讲到重写。)
总结Java的访问修饰符的访问权限
访问修饰符 | 本类 | 同包 | 子类 | 其他 |
---|---|---|---|---|
private | √ | |||
默认 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
子类重写父类的方法
为什么要重写?
子类从父类继承的方法已经满足不了子类本身的需求,此时就应该在子类中对父类的方法进行同名重写(覆盖)
代码如下
public class Dog extends Pet {
//......前面的省略不写了
/**
* 重写父类的print()方法
*/
public void print(){
super.print();//调用父类的print()方法
System.out.println("我是一只 "+this.strain+"。");
}
}
此时再运行上面的测试类时,结果就会发生变化,如下图:
由图可知,这次运行的“dog.print()”方法调用的是子类重写过的print()方法,而不是Pet里面的print()方法了,符合需求,这便是重写。
重写的定义:
在子类中根据需求对从父类继承过来的方法进行重新编写,成为方法的重写或方法的覆盖。
重写需满足的要求:
- 重写方法和被重写方法必须有相同的方法名。
- 重写方法和被重写方法必须有相同的参数列表。
- 重写方法的返回值类型必须和被重写方法的返回值类型相同或者是其子类。
- 重写方法不能缩小被重写方法的访问权限。
继承关系中的构造方法
规则
前面我们说了继承关系中的重写的规则,那么继承关系中的构造方法又是怎么执行的呢?我们先看一段代码:
public Dog(String name,String strain){
super(name);//只能放到第一行
this.strain = strain;
}
我们之前写的Dog类中的“super(name);”是用来调用父类的一参构造方法的,如果把这一行代码删除,结果又是怎么呢?结果如下:
神奇,Dog类竟然调用了Pet的无参构造函数。可是我们没有写super()代码。这是源于java中的一个重要知识:继承条件下的构造方法的调用。
2. 如果子类的构造方法中通过super()显示的调用了父类的有参构造方法,则将执行父类的有参构造方法,不执行无参构造方法。
3. 如果子类的构造方法中通过this显示的调用自身的其他构造方法,则在相应构造方法中应用以上两天规则。
4. 特别注意是是,如果存在多级继承关系,则在创建一个子类对象时,以上规则会多次的向更高一级父类应用,一直执行顶级父类Object类的无参构造方法为止。
注意
1. 在构造方法中如果有this,或者super关键字,只能放在第一行。2. 在一个构造方法中不允许同时出现this,super关键字,否则就会有两个第一条语句了。
3. 在类方法(静态方法)中不允许出现this,或者super关键字。
4. 在实例方法中,this,super不要求是第一条语句,可以共存。
例子
下面举一个例子讲一下多级继承的情况:
public class Person {
String name;//姓名
public Person(){
//super();有没有改语句都一样,因为都是集成object父类,默认为无参构造函数
System.out.println("ecxute Person()");
}
public Person(String name){
this.name = name;
System.out.println("excute Person(name)");
}
}
public class Student extends Person {
String school;//学校
public Student(String name,String school) {
super(name);//显示调用了父类有参构造方法,将不执行无参构造方法
this.school = school;
System.out.println("excute Student(name,school)");
}
public Student(){
//Super();//有没有该条语句都一样
System.out.println("excute Student()");
}
}
public class PostGraduate extends Student{
String guide;//导师
public PostGraduate(String name,String school,String guide) {
super(name,school);
this.guide = guide;
System.out.println("excute PostGraduate(name,school,guide)");
}
public PostGraduate(){
System.out.println("excute PostGraducate()");
}
}
public class Test {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
PostGraduate postGraduate = null;
postGraduate = new PostGraduate();
System.out.println();
postGraduate = new PostGraduate("liuliu","北京大学","王老师");
}
}
测试结果如下:
继承初始化对象时的顺序:
父类属性→→父类构造方法→→子类属性→→子类构造方法
继承的好处:
- 可以重用父类代码,不用从头做起,提高代码的复用率。
- 也不会破坏现有的代码或者父类代码。
举个例子:
代码
多态
什么是多态?
多态是具有多种表现形态的能力的特征。更专业化的说法:同一个实现接口,使用不同的实例而执行不同的操作。
说白了,多态就是一种类型,多种表现形式。
举个例子:
就像打印机一样,打印机通过连接不同的接口,打印出的文档颜色就不一样。其实多态也跟这类似,可以把打印机看成是“父类”,“彩色打印机”,“黑白打印机”看成是“打印机”的两个子类,在父类中有“打印”的方法,每个子类用不同的方式实现“打印”的方法。很明显,这就是我们上面讲到的方法的重写。
子类到父类的转换(向上转型)
之前我们学过基本数据类型之间的转化。例如:
int到double之间的转化:
int i =2;
double j=i;
实际上,引用数据类型的子类和父类之间也存在数据类型转换。
在Dog类中添加一个方法,父类中不添加。
public void catching(){
System.out.println("主人陪狗狗玩飞盘游戏");
}
测试类
Dog dog=new Dog("泡泡", "雪瑞纳");//不涉及类型转换
dog.print();
Pet pet = new Dog("偶偶", "雪瑞纳");//有类型转换,子类到父类的转换
pet.print();//此时调用的是Dog类重写过的print()方法,而不是父类Pet的print()方法
pet.catchingFlyDisc();//编译错误,父类的引用无法调用子类的特有方法
总结一下向上转换:
1,将一个父类的引用指向一个子类对象,成为向上转型,自动进行类型转换。
2,此时通过父类引用调用的方法是子类重写过的方法,而不是父类的方法。
3,此时通过父类的引用变量无法调用子类特有的方法。
父类到子类的转换(向下转型)
上面已经提到,发生向上转型后,是无法调用子类特有的方法的,那如果需要调用子类特有的方法的时,可以通过将父类再转换为子类来实现。所以,将一个子类对象的父类引用赋值给一个子类的引用,称为向下转型。此时必须惊醒强制类型转换。
看一下代码
Pet pet = new Dog("偶偶", "雪瑞纳");//有类型转换,子类到父类的转换
pet.print();
Dog dog1=(Dog)pet;//强制类型转换成狗对象
dog1.catchingFlyDisc();//调用狗狗的特有方法
注意
向下转型转换时,不是随意写一个子类对象转换的,而是必须转换为父类指向的真实子类类型Dog,不是任意转换的。如果转换为Penguin类的话(Penguin p=(Penguin)pet;),将会报错。
实现多态的三种方式
方式一:使用父类作为方法形参实现多态
public class Master {
private String name = "";//姓名
private int money = 0;//元宝数
public Master(String name,int money){
this.name = name;
this.money = money;
}
/**
* 主人给宠物喂食
*/
public void feed(Pet pet){
pet.eat();
}
/*
public void feed(Dog dog){
dog.eat();
}
public void feed(Penguin penguin){
penguin.eat();
}
*/
/**
* 主人类
*/
public void play(Pet pet){
if(pet instanceof Dog){//如果传入的是狗狗
Dog dog = (Dog)pet;
dog.catchingFlyDisc();
}else if(pet instanceof Penguin){
Penguin penguin = (Penguin)pet;
penguin.swimming();
}
}
}
新写了一个主人类,如果不使用多态的话,就是按着注释写的那两个方法来给宠物喂食,也就是调用feed()方法,这样的话,会比较麻烦,每多一个宠物,主人类就会多一个feed()方法,只不过是参数不一样而已,比如添加一个Cat宠物类,那主人类是不是要添加一个feed(Cat cat)方法,这样会使代码量很大的,如果使用了多态就不需要那么麻烦了,就按着现在写的这种方式,就写一个feed()方法就好了,传一个父类对象作为参数,测试类调用方法传参时,传相对应的子类对象就好了,这样就会调用相对应子类重写过的父类eat()方法。
方式二:使用父类作为方法返回值实现多态
/**
* 主人领养宠物
*/
public Pet getPet(int typeId){
Pet pet = null;
if(typeId == 1){
pet = new Dog("旺旺", "雪瑞纳");
}else if(typeId == 2){
pet = new Penguin("花花", "Q妹");
}
return pet;
}
给主人类添加一个getPet()方法。
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("偶偶", "雪瑞纳");
Penguin penguin = new Penguin("南岸", "Q妹");
Master master = new Master("会", 100);
Scanner input = new Scanner(System.in);
System.out.println("欢迎来到宠物店!");
System.out.print("请选择要领养的宠物类型:1,狗狗 2,企鹅");
int typeId = input.nextInt();
Pet pet = master.getPet(typeId);//调用这个方法,并且接收返回值
if(pet != null){
System.out.println("领养成功!");
master.feed(pet);
master.play(pet);
}else {
System.out.println("对不起,没有此类型的宠物,领养失败");
}
}
}
结果:
实现多态的条件
通过以上的描述,我们可以总结出要想实现多态,必须满足的三个条件
1,继承的存在(继承是多态的基础,没有继承就没有多态)。
2,子类重写父类 的方法(多态下调用子类重写后的方法)。
3,父类引用变量指向子类对象(子类到父类的类型转换)。