面向对象真的难吗?其实我看不然,只不过我们学习的时候比较杂论,并且也没有真正领悟到他内部的强大。那么开始进入正题,众所周知面向对象三大概念:封装,继承,多态。封装中又可以实现构造方法,方法的重载。继承又体现出方法的重写,从而演变出abstract(抽象类)和interface(接口),而从继承中保留出来的代码的泄露问题又演变出final和static,最后体现出多态。从这一句话中,我们会发现原来我们学过的面向对象他是一个环环相扣的过程。(小编之前对面向对象也是理解的不是特别的糊涂,自从听了一位北京的大佬讲过之后,发现面向对象其实并没有想象中那么难,希望可以帮助大家)
这是我画的一张面向对象所演变图:
由此我们来一步一步的学习面向对象
1.封装
1) 封装其实面向对象中应该是最简单的一部分,get和set方法,但是封装到底是什么呢,讲一个例子,我们都有手机,我们只需要知道他能干吗,能拍照打电话,上网,不需要知道这个手机是哪个厂家生产的,
也不需要手机金属壳是在哪个工厂锻造出来的,这就是封装。
概念: 封装,隐藏对外内容,不许外部直接去操作,必须都在我们控制之下以普通类,把成员变量私有化,改成private要访问这个属性或者设置这个属性怎么办呢?提供getter和setter方法。
public class Person {
private Integer Id;
private String Name;
private Boolean Sex;
public Integer getId() {
return Id;
}
public void setId(Integer id) {
Id = id;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public Boolean getSex() {
return Sex;
}
public void setSex(Boolean sex) {
Sex = sex;
}
}
这个就是一个简单的封装,这些其实大家应该都不陌生。也不算是难点。那我们等一会去调用这个类,现在问题来了,既然这个对象有这些属性,对象是不是也能干一些事情啊,比如说人说sayhello方法,那么我们就在封装下边写一个sayhello方法。
public void sayhello(Integer id,String name,Boolean sex) { this.Id=id; this.Name=name; this.Sex=sex;
System.out.println("大家好,我叫" + Name + "我的编号是" + Id); }
这时候我们就可以做一个测试类去调用他这个类了吧,这时候我们就在新建一个类
public class TestPerson { public static void main(String[] args) { Person person =new Person(); person.sayhello(007, "渣渣辉", true); } }
这里呢输出的时候我们加上性别的属性,在项目开发中,性别属性通常都是用boolean类型去定义,上边的输出我要是加上sex属性,它输出的就是一个boolean类型的值,那么我们完全可以在上边定义一个字符串,在进行if判断把,根据真假值把性别
给字符串,然后在对字符串进行输出就可以吧。
String ssex; if(Sex){ ssex="男"; }else ssex="女";
其实这些都是简单的封装和调用方法,并没有难点,下面我们介绍构造方法
2)构造方法:什么是类:属性+方法+构造方法 ,利用这个方法来创建对象,如果没有创建java.exe会 进行优化,简单的理解为就是编译器自动给我们这个类增加了一个构造方法。创建类时,会自动调用默认构造方法
(所有的类创建必须通过构造方法)那我们就根据上边person类对他写一个构造方法:
public class Person { private Integer Id; private String Name; private Boolean Sex; public Person() { } public Integer getId() { return Id; }
注意:底下的代码我没有加上(节省空间),只需要知道在哪里创建就行(默认的构造方法创建的位置)。
构造方法规定:必须和类名一致
上边的我创建的就是一个默认的无参构造方法,既然有无参的构造方法,那就有有参数的构造方法
public class Person { private Integer Id; private String Name; private Boolean Sex; public Person() { System.out.println("这里是无参构造"); } public Person(Integer id,String name){ System.out.println("带参数的构造方法,进行初始化"); this.Id=id; this.Name=name; } public Integer getId() { return Id; } public void setId(Integer id) { Id = id; } public String getName() { return Name; } public void setName(String name) { Name = name; } public Boolean getSex() { return Sex; } public void setSex(Boolean sex) { Sex = sex; } public void sayhello(Integer id, String name, Boolean sex) { this.Id = id; this.Name = name; this.Sex = sex; System.out.println("大家好,我叫" + Name + "我的编号是" + Id); } }
public class TestPerson { public static void main(String[] args) { Person person =new Person(); person.sayhello(007, "渣渣辉", true); Person person1=new Person(007,"古天乐"); } }
这里是无参构造 大家好,我叫渣渣辉我的编号是7 带参数的构造方法,进行初始化
那上边就是两个构造方法,一个是默认的一个是我们后来自己定义的,在测试类里边我们进行调用可看见person1就是调用的带参数的构造方法,而在上边的带参数的构造方法中我并没有输出出传过去的值。如果想输出值,可以再构造方法里边加上你要输出的
输出语句就可以了。那么我们既然可以定义两个参数的构造方法,那我们可不可以定义一个,或者三个参数的构造方法呢,这个是完全可以的。
public Person() { System.out.println("这里是无参构造"); } public Person(Integer id,String name,Boolean sex){ System.out.println("带参数的构造方法,进行初始化"); this.Id=id; this.Name=name; } public Person(Integer id,String name){ System.out.println("带参数的构造方法,进行初始化"); this.Id=id; this.Name=name; } public Person(Integer id){ System.out.println("带参数的构造方法,进行初始化"); this.Id=id; }
这个就是三个带参数的构造方法和一个不带参数的构造方法,其实到这里你已经把封装中重载已经学完了,当我们定义构造方法时,每一个构造方法的参数个数不一样的时候,我们就成他为重载。
重载,方法名称相同,参数个数不同 public Person() {} public Person(Integer id) {} public Person(Integer id, String name) {}
2.继承:
什么是继承呢,我们之前理解的时候都是儿子继承父亲的属性,这个说法其实我感觉不能体现程序,下面我来分享下我对继承的另一种理解,在封装中我们看到了方法的调用就是sayhello方法,那么现实生活中,比如动物,不可能就一个动物会叫,会吃,但是会有一些属性不同,
例如老虎和老鹰,他们两个都可以叫,都吃肉类,但是他们的行走的方式不同,一个是奔跑,一个是飞。那他们两个是不是有共同的属性啊,吃和叫,如果在我们开发过程中以一百个共同的属性,我们难道就要每个类都写一百个吗,就算每个类写了一百了,后期维护的时候加入要修改,
难道我们还一个一个的修改吗,我们为什么不写一个类定义这些他们共有的方法,让老虎和老鹰两个类去连接他,去他那里拿那些方法呢,这样不就节省了大量的时间和空间吗。那么就演变出来了继承 extends 。
n面我们就来创建老虎和鹰这两个类,和他们共有的吃的一个特性类,和一个测试类。
首先是老虎和鹰两个公用类的共有特性
public class Animal { public void eat() { System.out.println("吃"); } }
其次是老虎和鹰的行动方式类:这里我们可以看到在建立tiger类后我们在tiger类后边加上了我们继承关键字extends ,在extends在加上要继承的类,这样我们就能把被继承的类的方法拿过来用了。但是并不在这里体现,要在我们建立测试类
后再测试类里边调用
public class Tiger extends Animal { public void run(){ System.out.println("跑"); } }
public class Eagle extends Animal { public void fly(){ System.out.println("飞"); } }
下面就是我们的测试类:这里我们可以看待虽然我们tiger中没有eat这个方法,但是因为我们继承了Animal类,Animal中的类我们就可以拿来用了,并没有报错,证明这种方法时完全可以行的通的
public class test { public static void main(String[] args) { Tiger tiger =new Tiger(); Eagle eagle =new Eagle(); tiger.eat(); tiger.run(); eagle.eat(); eagle.fly(); } }
这是我们的运行结果:
吃
跑
吃
飞
补充:在我们面向对象中,如果你调用一个类,对他做toString操作,默认的是不是打印的地址啊,但是如果我们在声明变量时,在source上选择添加一个tostring,我们在去执行toString是不是打印的就不是地址了,而是你的属性,那么其实这个就是一个重写toString方法。
那在我们实际的项目中经常呢有些系统会二次开发,新增加一些功能或者对之前的某一个功能进行修改,就假如我们上边父类中定义的吃的方法,我现在进行二次开发,我要修改,对吃进行更细化的变更,老鹰吃兔子,
老虎吃肉呢不变,这时候就演变出了我们的重写。其实能重写是非常简单只需要重写父类中的方法,这时候能输出的东西会以子类中的方法进行优先输出。
这时候我们开始对老鹰类进行重写eat方法
public class Eagle extends Animal { public void fly(){ System.out.println("飞"); } public void eat() { System.out.println("我是重写父类的eat()方法,我是老鹰我吃兔子"); } }
这时候我们测试代码不变在执行测试
吃
跑
我是重写父类的eat()方法,我是老鹰我吃兔子
飞
这个时候我们和上边对比就发现eat在老鹰类中输出的参数就变了。这就是我们重写父类的方法,对父类进行覆盖,下面对继承我们来在练习一个,众所周知java但是单继承机制,子类继承父类之后,那么我们父类可不可以在继承一个类呢,这样就解决了我们
java单继承机制,我用一张图来解释一下
儿子继承父亲的,那么父亲继承爷爷的,那么这样我门是不是就可以拿到了爷爷的属性了。那么我们就根据这个来写一个例子。
要求:爷爷:在北京有一套房子,爷爷今年80岁了
父亲:有一辆宝马车
儿子:啥也没有,我只有玩
显然可见,爷爷有两个属性,父亲这里只有一个属性,然后我们儿子继承他们,拿到他们的属性,房子和车子,但是爷爷有一个属性是年龄八十岁了,难道我们那了爷爷的房子,就要变成八十岁吗,显然是不能的,那么就要我们重写爷爷的年龄的方法
public class Son extends Father { public void play() { System.out.println("我啥都没有,我只会玩"); } }
public class Father extends Grandfa { public void Car() { System.out.println("我有一辆宝马车"); } }
public class Grandfa { public void house() { System.out.println("我在北京二环有一套四合院"); } public void Age() { System.out.println("我今年八十岁了"); } }
public class PersonTest { public static void main(String[] args) { Son son =new Son(); son.play(); son.Car(); son.house(); son.Age(); } }
我啥都没有,我只会玩
我有一辆宝马车
我在北京二环有一套四合院
我今年八十岁了
那么现在我们可以看到儿子拿到了父亲和爷爷的所有的属性,但是又一条是年龄,那岂不是儿子也变成了八十岁,那么解决这个问题有两种方法,第一种,我们重写这个年龄的方法就可以了,第二种,我们直接将他定义成私有的,不让你儿子访问,你调用不了
我年龄的属性是不是就可以了。那么这两种方法我都演示一下:
A:重写年龄方法:那们我们直接在年龄上重写就可以了,其他的方法是不是不用动啊。
public class Son extends Father { public void play() { System.out.println("我啥都没有,我只会玩"); } public void Age() { System.out.println("我是儿子,但是我今年才二十岁"); } }
这时候我们再来执行测试类:
我啥都没有,我只会玩
我有一辆宝马车
我在北京二环有一套四合院
我是儿子,但是我今年才二十岁
唉,年龄是不是就变过来了。这就是我们重写父类的父类的方法,接下来演示第二种
B:将Age定义成私有:这时候我们只需要改变父类的Age方法就可以了
这时候我是不是就像Age方法从public公用的改成了私有的private,然后我们在把儿子中年龄方法删除(千万别忘记这一步哦!!!)
public class Grandfa { public void house() { System.out.println("我在北京二环有一套四合院"); } private void Age() { System.out.println("我今年八十岁了"); } }
这时候我们清楚的看见当son调用Age方法时报错了。这样第二种方法我们就演示完成了。
那么接下来就出现一个问题,我怎么知道,哪个是父类,哪个是子类呢,即使我子类继承了父类,但是在某一个父类的方法我就是想调用它,我不想要调用子类重写的方法,这个时候怎么办呢,这个java.exe其实早就为我们准备好了,就是下边要说的super关键字。super关键字可能在大家之前学的时候,老师举一些构造的方法来验证他,但是这次,我用一个方法的方式去验证super关键字。请看下边例子。
定义一个类 ,里边写一个show方法。
public class father { public String show(String name){ System.out.println("父类"+name); return name; } }
再写一个子类去继承父类重写父类的show方法,ex是我写的一个调用的方法。
public class Son extends father { public String show(String name){ System.out.println("子类:"+name); return name; } public void ex(){ show("儿子"); } public static void main(String[] args) { Son son =new Son(); son.ex(); } }
我们来运行程序,这时候系统默认调用的是子类重写父类的show方法。
子类:儿子
现在正式来说明我们的问题,我们不想调用子类的show方法,调用父类的show方法:
public class Son extends father { public String show(String name){ System.out.println("子类:"+name); return name; } public void ex(){ show("儿子"); super.show("父亲"); } public static void main(String[] args) { Son son =new Son(); son.ex(); } }
只需要在show方法前边加上我们的关键字super,我们看下程序的运行结果:
子类:儿子
父类:父亲
继承是非常好的,但是也有不好的地方,破坏父类结构,父类中暴露公用,可以通过子类重写父类方法,恶意代码。
那么现在又出现一个问题:我想规定继承方法必须子类去实现。(
举例:就好比说在我们工作中,我们都会有一个小组长,小组长在收到上边的项目后,安排组员做东西,做一块是不是就是一个方法啊,现在我们的小组长规定是我的组员,你就必须把这个活给我干
了。那么就演变出我们的抽象类抽象类。
首先我们创建抽象类,抽象类的关键字abstract,(抽象类定义:抽象方法必须在抽象类中,抽象类中可以没有抽象方法 学过的人自然就理解,如果没有学过的也不用着急,下边我们会具体的体现)
这是我定义的抽象类 在public后边加上abstract,这样我们这个类就是抽象类(或者在创建项目的时候选择创建抽象类) abstract这个关键字放在public前后都是可以的,都不会报错,只不过默认的是放在public后边的
下边的代码我加上之后如果你放到编译器中,在project2哪里就会给你报错,为什么报错呢?我们看一下 :鼠标放上去后提示英文的意思为,将这个peoject2方法加上 abstract,如果我们不加abstract的方法是不是就是普通的方法
加上abstract 就变成了抽象方法了,
public abstract class Group { public void peoject1(){ System.out.println("项目一比较重要,我是组长我来干"); } public abstract void project2(); }
那么到现在我们还不足以验证上边抽象的结论:下面我们讲Group方法的修饰词abstract关键字去掉:
我们看见又报错了,这时候我们的方法中是不是有一个抽象方法啊:project2 ,我们把鼠标放在Group上选择第一个系统提示的,整个程序就会恢复正常了
这样就验证我们的抽象类的定义,抽象方法必须定义在抽象类中
下面来进行我们的操作,做抽象,所谓抽象只不过是一种定义,他的主要规则是,我在抽象类中定义的抽象方法,子类必须实现,大家呢不要把他想的太过复杂,他其实也是一个父类,只不过加上来规范
下面我们来创建一个类来继承我们得抽象类。
现在我们看,我这个组员这个类怎么又报错了呢,鼠标放在红线上选择第一个,哎,系统是不是帮我们创建了一个方法啊。哎这个方法的名字是不是就是和我继承的父类的抽象方法一样啊,这样就体现了我们的抽象类了
定义的抽象方法,子类必须去实现。
public class Crew extends Group{ @Override public void project2() { System.out.println("我是组员,我干简单的活"); } }
下面是测试类和输出的结果
public class Test { public static void main(String[] args) { Crew crew =new Crew(); crew.project2(); } }
我是组员,我干简单的活
现在问题又来了,抽象类是不是里边有可以自己的方法,就好比我们上边的例子,小组长是不是也有自己的活啊,那么现在我的要求变了,我现在是项目经理,我就啥也不相干,我就管分配任务,哎,我们的接口就这里演变出来了
接口是一种变相的继承,继承定义父类,让子类去继承,接口是我们定义一个接口,让其他的类去接入他。
首先我们创建接口的时候就不是创建类了,在选择创建的时候在与class下边第二个会有一个interface词,我们选择这个创建我们的接口:现在我是项目经理,我定制了一个项目,我让我的组员去做;
public interface manager { public void pro1(); public void pro2(); public void pro3(); }
下面我们去接入接口:
定义一个员工类,那么我们接入的时候就用到我们的关键字了implement,我们接入后会发现,哎又报错了,我们说接口是从抽象类演变过来的,是实现我项目经理规定项目,组员去实现项目的过程,
我们把鼠标放在红线上选择第一个,这样就不错了,让我的实现类去必须去实现这些方法
public class Crew implements manager { @Override public void pro1() { System.out.println("做的是项目1"); } @Override public void pro2() { System.out.println("做的是项目2"); } @Override public void pro3() { System.out.println("做的是项目3"); } }
这样之后我们建立测试类:哎,我们发现又报错了,这个时候我们先不看报错信息,我们看你调用的是manager,manager接口他里边只有一些方法,并没有实现这些方法,就是说,项目并不是项目经理做的,而是他的手下做的。
那么怎么解决呢:讲我们后边new的后边不去newmanager,我去new真正实现这些项目的类啊:
public class test { public static void main(String[] args) { manager m =new Crew(); } }
这样之后我是不是就不报错了,然后我们在去调用那些方法:
public class test { public static void main(String[] args) { manager m =new Crew(); m.pro1(); m.pro2(); m.pro3(); } }
做的是项目1
做的是项目2
做的是项目3
这就是我们的接口实现。
在我们现实中,经常会有一些项目会二次的开发,那么之前呢,美国发生过一个案件,在银行,有一个程序员在给银行的系统做开发之后,做了一些修改,什么修改呢,每个用户存钱之后都会给他这个程序员的账户转账一美分,那我们知道一美分并不多,
但是有无数的人在往银行存钱啊,这样他就获取了当量钱财,那么他在开发的时候,是不是就是重写了系统的加密算法啊,这样子类的方法就覆盖了父类的方法,这样就实现了给他赚钱的目的,那么我们为了防止这种现象的发生,我想定义一个一个类
我让一些方法可以去让子类重写,一些加密的类我不让你去重写,或者整个类我都不让你去继承,这时候就演变出来我们的final关键来,final关键字,不允许去继承和重写。
我现在就把上边继承的案例爷爷,父亲和儿子的案例做了一下修改,我吧Grandfa类加上final ,哎我们看父亲的类和儿子的类还有测试类都报错了,一旦定义了是不是就不让他们继承了,那测试调用爷爷的方法是不是也就不成立了,
3.多态
多态是面向对象中最难的一块,为什么难呢,小编之前也是在CSDN,博客园上看了众多的案例,是当时的案例理解了,但是我感觉别人问我,多态是什么,我还是回答不上来,为什么会这样,你这个案例你虽然懂了,但是多态的本质你并没有理解,
我们先不看多态,我们先看上边的封装和继承,我们发现最后我们的测试的时候啊,是不是直接调动的方法,而方法里边的东西是不是都写死了,对把,你在现实的开发中你写死的东西有哪些不好,是不是不利于维护啊,这样才演变出来我们的多态,下面我
说一下我对多态的理解,就一句话,
不到最后调用,我永远不知道我要干什么。
这句话现在看的话肯定是看不懂的,具体的我们要在例子中去体现,那么之前我们做某些方法的时候是不是就是传就是参数,固定的参数,固定的方法,固定的去执行这个方法,对不对,那么现在我在传的参数的时候我不传固定值了,我就去传一个能代表的
参数去替代他,到时候我们调用的时候我们再去说我是谁,我要干嘛对不对。就好比我们4s店,众多的车,不同的价格,对不对,你见那个买车的人去直接买固定的车,是不是都是相中那个了,我再去选择去购买那个车。好下面我就以买车为例:
先来分析一下:
A:我们是不是得有一个车得类:Car(接口和抽象类都可以)小编这里呢我就定义一个接口。
B:车的类里边只不过是车的方法,车的价格,但是具体哪个车就得体现来:我们来定义两个类:一个叫宝马(BM)一个叫奔驰(BC)
C:现在万事俱备只欠东风了,车,车的品牌价格都有了,我们是不是得有一个人去卖车啊:是不是的又一个sellCar的类去卖车啊
D:测试类
好我们按照步骤一步一步的来创建:两个方法一个品牌,一个价格
public interface Car { public String getname(); public Integer getprice(); }
两辆车
public class BM implements Car { @Override public String getname() { return "宝马"; } @Override public Integer getprice() { return 300000; } }
public class BC implements Car { @Override public String getname() { return "奔驰"; } @Override public Integer getprice() { // TODO Auto-generated method stub return 5000; } }
这样我们的车就搞好了,该卖车了,首先定义他的属性,没有什么可说的。
public class SellCar { private Integer money=0; private Integer sum=0; public Integer getSum() { return sum; } public void setSum(Integer sum) { this.sum = sum; } public Integer getMoney() { return money; } public void setMoney(Integer money) { this.money = money; } }
下面的才是重点,我定义一个sellcar方法:但是大家注意我传的参数是什么是不是接口的参数啊,这个才是真正体现面向对象的概念
public void sellcar(Car car) {//动态体现 System.out.println("型号"+car.getname()+"价格 "+car.getprice()); money+=car.getprice(); sum+=1; }
测试类:这里是不是到了最后我才说我要买奔驰车,之前是不是不知道,这样就体现了那句话:
不到最后调用,我永远不知道我要干什么。
public class testcar { public static void main(String[] args) { SellCar sellCar =new SellCar(); Car car;//体现多态 car=new BC(); sellCar.sellcar(car); System.out.println("销售总额为:"+sellCar.getMoney()+" 销售车的数量为"+sellCar.getSum()); } }
运行结果:
型号奔驰价格 5000 销售总额为:5000 销售车的数量为1
其实这只不过是一个简单的例子:因为就一个接口,我们也可以定义两个接口的项目,来一个小动物喂食的例子,那么多态体现的方法有很多很多种,
需求分析:动物:小猫。小猪
食物:鱼 米饭
两个接口就在这里了,是不是就是上边我们的车啊,然后在创建四个类去体现他
下面就是饲养员了,最后是测试类
动物接口类:
public interface Animal { public String GetAnimalName(); }
动物具体类:
public class Cat implements Animal{ @Override public String GetAnimalName() { return "猫"; } }
public class Pig implements Animal { @Override public String GetAnimalName() { // TODO Auto-generated method stub return "猪"; } }
食物接口类:
public interface Food { public String getname(); }
食物具体类:
public class Fish implements Food { @Override public String getname() { return "鱼"; } }
public class Rice implements Food { @Override public String getname() { return "米饭"; } }
上边这些都没有什么技术可言,真正体现的还是后边的:这里定义喂食方法,里边的参数是不是活的,不是死的,和上边的卖车一样最后我测试调用的时候,才能或取到参数。
public class Feeder { public void feed(Animal animal,Food food) { System.out.println(animal.GetAnimalName()+"吃"+food.getname()); } }
测试类:
public class test { public static void main(String[] args) { Feeder feeder = new Feeder(); Animal animal; Food food; animal=new Cat(); food =new Fish(); feeder.feed(animal, food); animal=new Pig(); food =new Rice(); feeder.feed(animal, food); } }
猫吃鱼
猪吃米饭
整体的到这里就结束来,小编感觉如果能把这些案例理解,就算你不能真正的理解面向对象,是不是对面向对象抽象有了一个系统的概念,感觉他并不是那么抽象,那么难以理解。希望可以帮到大家!!!