面向对象(面向对象真的难吗,你只不过没有理清思路而已)

面向对象真的难吗?其实我看不然,只不过我们学习的时候比较杂论,并且也没有真正领悟到他内部的强大。那么开始进入正题,众所周知面向对象三大概念:封装,继承,多态。封装中又可以实现构造方法,方法的重载。继承又体现出方法的重写,从而演变出abstract(抽象类)和interface(接口),而从继承中保留出来的代码的泄露问题又演变出final和static,最后体现出多态。从这一句话中,我们会发现原来我们学过的面向对象他是一个环环相扣的过程。(小编之前对面向对象也是理解的不是特别的糊涂,自从听了一位北京的大佬讲过之后,发现面向对象其实并没有想象中那么难,希望可以帮助大家)

这是我画的一张面向对象所演变图:

                                                                          

由此我们来一步一步的学习面向对象

1.封装

      1)   封装其实面向对象中应该是最简单的一部分,get和set方法,但是封装到底是什么呢,讲一个例子,我们都有手机,我们只需要知道他能干吗,能拍照打电话,上网,不需要知道这个手机是哪个厂家生产的,

也不需要手机金属壳是在哪个工厂锻造出来的,这就是封装。

概念: 封装,隐藏对外内容,不许外部直接去操作,必须都在我们控制之下以普通类,把成员变量私有化,改成private要访问这个属性或者设置这个属性怎么办呢?提供gettersetter方法。

 

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);
    }

}
猫吃鱼
猪吃米饭

整体的到这里就结束来,小编感觉如果能把这些案例理解,就算你不能真正的理解面向对象,是不是对面向对象抽象有了一个系统的概念,感觉他并不是那么抽象,那么难以理解。希望可以帮到大家!!!

原文地址:https://www.cnblogs.com/g2vbn/p/11323348.html