黑马程序员_面向对象(三)

来源:互联网 发布:约瑟夫环 数组 编辑:程序博客网 时间:2024/05/17 01:48
------- android培训、java培训、期待与您交流! ----------


一、单例设计模式


1、什么是单例设计模式?
    对于单例模式(Singleton Pattern)是一个比较简单的模式,他的定义如下:
        Ensure a class has only one instance,and provide a global point of access to it.
        意思是确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
        好处:保证对象的唯一性
        使用场景:比如多个程序都要使用一个配置文件中的数据,而且要实现数据共享和交换。必须要将多个数据封装到一个对象中。而且多个程序操作的是同一个对象。那也就是说必须保证这个配置文件对象的唯一性。

        如何能保证对象的唯一性?
            一个类只要提供了构造方法,就可以产生多个对象。完全无法保证唯一。既然数量不可    控,干脆不让其他程序建立对象。

        不让其他程序创建,对象何在?
            自己在本类中创建一个对象,好处,对象可控。

        创建完成后,是不是要给其他程序提供访问的方式?
            怎么实现这个步骤?
                    怎么就能不让其他程序创建对象呢?
                        直接私有化构造方法,不让其他程序创建的对象存在。
                    直接在本类中new一个本类对象
                    定义一个功能,其他程序可以通过这个功能获取到本类对象。
        

2、单例模式分成两种:懒汉式、饿汉式

3、下面就通过代码来看看这三种单例模式的区别
饿汉式:
//饿汉式class Singleton_1{    private static Singleton_1 s = new Singleton_1();    private Singleton_1(){}    public static Singleton_1 getInstance(){        return s;    }    }


懒汉式:(容易引发线程安全问题)
//懒汉式(单例模式的延迟加载方式),面试最多的是懒汉式class Singleton_2{    private static Singleton_2 s = null;    private Singleton_2(){}    public static Singleton_2 getInstance(){        if(s == null){            s = new Singleton_2();        }        return s;    }}


二、继承

1、假设有两个类,一个类是Student类,另外一类是Worker类,两个类中都有属性name和age,那么既然有共同的属性,我们能不能对他们进行抽取呢?
          答案:可以,可以将两个类中的name属性和age属性都抽取取来,放到另外一个类,这个类就是Person,因为学生是人,工人也是人,所以就抽取出来再建立Person类。
继承的关键字:extends

代码体现:
package day08.itcast01;public class ExtendsDemo {    public static void main(String[] args) {        Student s = new Student();        s.name = "林青霞";        s.age = 16;        s.show();                Worker w = new Worker();        w.name = "小二";        w.age = 28;        w.show();    }}class Student extends Person{//定义一个Student类,并提供一个成员方法    public void study(){        System.out.println("我在学习");    }}class Worker extends Person{  //定义一个Worker类,并提供一个成员方法    public void work(){        System.out.println("我在工作");    }}class Person{   //定义一个Person类,由Student类和Worker类抽取而来,是Worker类和Student类的父类(基类、超类)    String name;    int age;    public void show(){        System.out.println(name+"---"+age);    }}

继承的好处:提高了代码的复用性,为面向对象另一个特征多态提供了前提条件

什么时候定义继承?
必须保证类与类之间的所属(is a)关系,XX是XXX的一种
比如:狗是动物的一种,学生是人的一种

Java当中允许单继承,不允许多继承
单继承:一个子类只会有一个父类
多继承:一个子类会有多个父类

2、继承中子父类成员的特点:
    1、成员变量
            特殊情况:当子类和父类定义了同名的成员变量的时候,如何在子类中访问父类中的变量?
            通过关键字super来完成
            super的用法与this的用法类似,
                this代表的是本类对象的引用。
                super代表的是父类的内存空间
    2、成员方法
            当子类和父类定义了一模一样的的方法时,当子类调用该方法时,运行的是子类中的方法
            这种情况在子父类中被称之为方法的重写。
            何时需要重写方法?
                当子类的方法有自己的特有的功能的时候就需要重写。
        子类重写父类的方法必须保证权限要大于或者等于父类的权限
        静态只能覆盖静态的
        写法上需要注意的:必须一模一样,方法的返回值类型 方法名 参数列表都要一样。
代码体现:
package day09.itcast01;public class ExtendsDemo1 {    public static void main(String[] args) {        Zi zi = new Zi();        zi.age = 15;        zi.des();    }}class Fu{    int age;    public void des(){        System.out.println("父类的des方法"+"---父类的成员变量---"+age);    }}class Zi extends Fu{    int age;    public void des(){        super.age = 10;        super.des();        System.out.println("子类的des方法"+"---子类的成员变量---"+age);    }    }


    3、子父类中构造方法的的特点
        
package day09.itcast02;public class ExtendsDemo2 {    public static void main(String[] args) {        Son son = new Son();    }}class Father{    Father(){        System.out.println("Father is running");    }}class Son extends Father{    Son(){        System.out.println("Son is running");    }}

分析运行结果:
因为在子类的所有构造方法中的第一行都默认有一个super();它会调用父类的构造方法
 为什么会有super();呢?
因为在子类进行初始化的时候,先要对父类进行初始化,只有对父类进行初始化完成后,才能使用父类的一些方法

当父类没有空参的构造方法时,需要使用super关键字去调用相应的构造方法

如果在子类的第一行使用了this调用本类的其他构造方法,还会有super();吗?
没有,因为this()或者super()只能定义在构造方法的第一行

父类的构造方法中是否有super();
有,因为所有类的构造方法的第一行都有一个super();此时父类调用的是所有类的父类Object类。

如果默认的隐式super语句没有对应的构造函数,必须在构造函数中通过this或者super的形式明确调用的构造函数。

三、final关键字

继承的缺点:打破封装性,如何能保证继有继承又不会打破封装性呢?
就不让其他类继承该类,就不会重写方法。这时就需要用到final关键字
final的意思是最终,它用于修饰类,方法或者变量(成员变量、局部变量、静态变量)

final的特点:
1、final修饰的类是一个最终类,不能再派生子类
    如果一个类中的方法部分需要重写,部分不需要,就对不需要被重写的方法使用final修饰
2、final修饰的方法是最终方法,该方法不能被重写
3、final修饰的变量是一个常量,只能被赋值一次

什么时候需要在程序中定义final常量呢?
当程序中一个数据使用时是固定不变的,这时为了增加阅读性,可以给该数据起个名字。
这就是变量,为了保证这个变量的值不被修改,加上final修饰,这就是一个阅读性很强的常量。
书写规范:被final修饰的常量名所有的字母都是大写,如果该变量名是由多个单词组成的,每个字母都大写,并且单词之间使用"_"连接。



四、抽象类

对与狗和狼这两种动物他们都有一个吼叫的行为,而且他们还属于动物,对他们的共性进行向上抽取,可以使用继承,但是狗和狼吼叫的行为又不同。这时使用继承就显得不合适了,这时就需要使用另外一个关键字abstract(抽象的)对父类进行修饰。

抽象类的特点:抽象类和抽象方法都需要使用abstract修饰,抽象方法一定要定义抽象类中,抽象类中的方法不一定都是抽象方法。

只有覆盖了抽象类中的所有抽象方法后,其子类才可以实例化。否则该子类还是一个抽象类。

抽象类要实例化的话需要通过子类对父类进行实例化。(多态)

细节:
抽象类一定是一个父类
是的,因为抽象类就是子类的功能不断抽取出来的

抽象类中是否有构造方法?
有,不能给自己的对象实例化,可以给子类的对象进行初始化。

抽象类和普通类的异同点?
相同:它们都是用来描述事物的,它们之间都可以定义属性和行为

不同:一般类可以具体的描述事物,抽象类描述的事物信息不具体
抽象类可以多定义一个成员:抽象方法。
一般类可以创建对象,而抽象类不能创建对象。

抽象类中是否可以定义普通方法?
可以,如果抽象类中定义了普通方法,那么其抽象类的作用就是不能对该类进行实例化。

抽象关键字abstract不能与哪些关键字共存?
final
private
static

代码体现:
package day09;public class AbstractDemo {    public static void main(String[] args) {        Dog d = new Dog();        d.show();        d.speak();            }}abstract class Animal{    public void show(){        System.out.println("Animal");    }    public abstract void speak();}class Dog extends Animal{    public void speak(){        System.out.println("小狗叫");    }}


案例:
需求:公司中程序员有姓名,工号,薪水,工作内容。
项目经理除了有姓名,工号,薪水,还有奖金,工作内容。
分析:程序员和项目经历都属于公司里的员工,而且他们都有共性:姓名、工号、薪水以及工作内容
package day09.itcast02;public class AbstractDemo {    public static void main(String[] args) {        Programer p = new Programer("张三","448",5000);        Manager m = new Manager("李四","500",5000,2500);        p.content();        p.show();        m.content();        m.show();            }}abstract class Employee{    String name;    String number;    int salary;    public Employee(String name,String number,int salary){        this.name = name;        this.number = number;        this.salary = salary;    }    public abstract void content();    public abstract void show();    }class Programer extends Employee{    public Programer(String name,String number,int salary){        super(name,number,salary);    }    public void content(){        System.out.println("敲代码");    }    public void show(){        System.out.println(name+"---"+number+"---"+salary);    }}class Manager extends Employee{    int pay;//奖金    public Manager(String name,String number,int salary,int pay){        super(name,number,salary);        this.pay = pay;    }    public void content(){        System.out.println("写规划");    }    public void show(){        System.out.println(name+"---"+number+"---"+salary+"---"+pay);    }    }


五、接口

当一个抽象类中的方法都是抽象方法的时候,这个抽象类就有另外一个表现方式,叫做接口(interface)
定义接口使用关键字interface,格式:interface 接口名{}

接口中的成员已经被限定为固定的几种。
接口中的定义格式(两种)
    1、定义变量,但是变量必须有固定的修饰,public static final,所以接口中的变量也被称之为常量。
    2、定义方法,方法也有固定的修饰符,public abstract
        接口中的成员都是公共的。

特点:
接口不可以创建对象
子类必须覆盖掉接口中的所有抽象方法后,子类才可以被实例化。否则子类是一个抽象类。

定义接口的子类,类与类之间的关系是继承,而子类与接口之间的关系是实现(implements)。
格式:
interface Animal{  // 定义一个动物接口
}
class Dog implements Animal{ //定义犬类实现了动物的接口
}

接口解决了多继承中调用不明确的弊端,讲多继承机制在java中通过多实现完成了
    
接口的出现避免了单继承的局限性
父类中定义事物的基本功能。
接口中定义事物的扩张功能。

类与类之间是继承(is a)关系,类与接口之间是实现(like a)关系.

接口与接口之间是继承关系,而且可以多继承。

抽象类和接口的区别:
接口中定义的方法都是抽象方法,不允许定义普通方法。
抽象类中可以允许有普通方法,但是抽象方法一定定义在抽象类中。

接口的思想:
1、对功能实现了扩展。
2、定义了规则。
3、降低了耦合性。

三、多态

什么是多态?
举例子:学生和工人,但是他们都是人中的某一类,这就是多态。多态就是一种事物的不同体现。
多态的体现:
父类的引用或者接口的引用指向了自己的子类对象。
好处:提高了程序的扩展性。
弊端:通过父类的引用操作子类对象时,只能使用父类中已有的方法,不能操作子类特有的方法
多态的前提:继承或者实现。
多态通常都有重写操作。
代码体现:
package day10.itcast01;public class DuotaiDemo1 {    public static void main(String[] args) {        Person p = new Student();        p.eat(p);        Person p1 = new Worker();        p1.eat(p1);            }}abstract class Person{    public void eat(Person p){        if(p instanceof Student ){            System.out.println("吃饭");            p.method();        }else if( p instanceof Worker){            System.out.println("吃饭");            p.method();        }    }    public abstract void method();}class Student extends Person{    public void method(){        System.out.println("学习");    }}class Worker extends Person{    public void method(){        System.out.println("工作");    }}



2、多态调用子类的特有方法

Person p = new Studnet();
或者Person p = new Worker();
父类引用指向了子类的对象,这是让子类对象进行类型的提升(向上转型)
好处:提高了扩展性,隐藏了子类,弊端:不能使用子类的特有方法。

如果想要使用子类的特有方法,只有子类的对象才能使用,这时就需要使用向下转型。
向下转型属于强制类型转换,向上转型是自动类型转换。
package day10.itcast01;public class DuotaiDemo1 {    public static void main(String[] args) {        Person p = new Student();        p.eat(p);//        p.write();  报错        ((Student)p).writer();        Person p1 = new Worker();        p1.eat(p1);        ((Worker)p1).work();            }}abstract class Person{    public void eat(Person p){        if(p instanceof Student ){            System.out.println("吃饭");            p.method();        }else if( p instanceof Worker){            System.out.println("吃饭");            p.method();        }    }    public abstract void method();}class Student extends Person{    public void method(){        System.out.println("学习");    }    public void writer(){        System.out.println("学生的特有方法:写作业");    }}class Worker extends Person{    public void method(){        System.out.println("工作");    }    public void work(){        System.out.println("工人的特有方法:干活");    }}

注意:无论是向上转型还是向下转型,最终都是子类对象做着类型的变化。
【向下转型的注意事项】:
Person p = new Studnet();
Worker w = (Worker)p;
以上代码是不允许出现的,会导致ClassCastException异常
为了避免出现类转换异常,在进行类转换时常使用instanceof关键字对对象进行类型判断
格式:对象名 instanceof 类名

3、子父类成员的调用问题
        1、成员变量:当子父类中出现了同名的成员变量时
                编译时期:参考的是引用型变量所属的类是否有被调用的成员变量,没有,编译失败。   
                运行时期:参考的是引用型变量所属类中的成员变量。
                口诀:编译运行看左边。           
        2、成员方法:当子父类中出现了同名的成员方法时
                编译时期:参考左边,如果没有,编译失败
                运行时期:参考右边
                口诀:编译看左边,运行看右边。
                对于成员方法是动态绑定到对象上。
        3、静态方法
                编译和运行都参考左边
                静态方法是静态绑定到类上。

4、题目
看一下代码分析打印结果(通过画图分析)
package day10.itcast02;public class DuoTaiTest3{    public static void main(String[] args)    {        Fu f = new Zi();        System.out.println("main :" +f.getNum());    }}class Fu{    int x = 4;    Fu()    {        System.out.println("A fu() : "+getNum());    }    int getNum()    {        System.out.println("B fu getnum run...."+x);        return 100;    }}class Zi extends Fu{    int x = 5;    Zi()    {    //super();默认存在并调用父类的无参构造        System.out.println("C zi() : "+getNum());    }    int getNum()    {        System.out.println("D zi getnum run...."+x);        return 200;    }}

结果分析:
分析这道题首先从main方法开始,Fu f = new Zi(); 程序首先会调用子类的构造方法,进入到子类的构造方法时,因为在子类的构造方法中第一行都会有默认的super();调用父类的构造方法,所以程序进入到 父类的构造方法之中,在父类的构造方法中,有一条输出语句,输出语句中调用了getNum()方法,那么这时候调用的是哪个呢?因为在main方法中,使 用了多态,使父类的对象引用指向了子类,所以调用的是子类的getNum方法,在getNum方法中输出了一个x,因为从程序开始一直到现在都还未对x进 行显示的初始化,所以x的值为0,所以最想打印的是D zi getnum run....0,接着getNum方法返回了200到父类的构造方法中,所以接着打印了A fu() : 200,当打印了两条语句之后,又回到了子类的构造方法中,这时子类的构造方法中同样有一条输出语句,在语句中调用了getNum()方法,这个 getNum方法同样是子类中的方法,因为此时对子类中的x已经完成了显式初始化,所以打印了D zi getnum run....5,打印完成后又回到了子类的构造方法中执行了输出语句,此时打印了C zi() : 200,至此Fu f = new Zi()完成了所有的操作,最后mian方法中的输出语句中同样调用的是子类中的getNum()方法,所以打印的是main :200
综合以上的分析,可以得出最后的结果为:
D zi getnum run....0
A fu() : 200
D zi getnum run....5
C zi() : 200
main :200

 六、object类

object是所有的类的父类或者间接父类
在object类中有两个常用的方法
equlas和toString方法
equals:通常用来比较两象是个对否相等。
toString:返回对象的字符串表现形式,Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于:
getClass().getName() + '@' + Integer.toHexString(hashCode())

一般来说equals和toString方法都需要被重写
案例:
package cn.test;public class EqualsDemo {    public static void main(String[] args) {        Student s1 = new Student("谢群",18);        Student s2 = new Student("谢群",22);        boolean  result = s1.equals(s2);        System.out.println("result:"+result);        System.out.println(s2.toString());        s2.setAge(18);        result = s1.equals(s2);        System.out.println("result:"+result);        System.out.println(s2.toString());            }}class Student{    private String name;    private int age;    public Student(String name, int age){        this.name = name;        this.age = age;    }    public Student(){}    public void setName(String name){        this.name = name;    }    public String getName(){        return name;    }    public void setAge(int age){        this.age = age;    }    public int getAge(){        return age;    }    public boolean equals(Object s){        if(s == null){            return false;        }        if(!(s instanceof Student)){            return false;        }        if(s == this){            return true;        }        Student s1 = (Student)s;        return this.name.equals(s1.name) && this.age == s1.age;    }    public String toString(){        return this.name +"----"+this.age;    }        }

















0 0