面对对象第八天

---------------------- android培训java培训、期待与您交流! ----------------------

  (一)多态-概念

多态就是某一个事物的多种体现形态.比如人有男人女人,动物有猫狗.

一般定义猫类型的时候这样定义的:猫 x=new 猫();但是也可以这样定义:动物 x=new 猫();这样这个对象就既具有猫类的属性又具有了动物类属性,但猫类必须是动物类的子类.

从以下四个方面来思考多态:

1.多态的体现

2.多态的前提

3.多态的好处

4.多态的应用


  (二)多态-扩展性

/*
需求:有动物,动物里面有猫和狗.需要在传入猫或者狗的时候打印出对应的属性.
*/
abstract class Animal//抽取出猫狗共有的特性就是都要吃东西
{
    public abstract void eat();
}
class Cat extends Animal
{
    public void eat()//猫继承了动物的吃东西
    {
        System.out.println("eat fish");
    }
    public void catchMouse()//还有自己特有的抓老鼠属性
    {
        System.out.println("catch mouse");
    }
}
class Dog extends Animal
{
    public void eat()//狗继承了动物的吃东西
    {
        System.out.println("eat bone");
    }
    public void watchHome()//还有自己特有的看家属性
    {
        System.out.println("watch home");
    }
}
class PolymorphismDemo
{
    public static void main(String[] args)
    {
        Cat c=new Cat();

        c.eat();

        c.catchMouse();

        Dog d=new Dog();

        d.eat();

        d.watchHouse();       

    }

}

这样当然是可以的,但是每个动物都要创建对象然后调用其方法未免太重复,所以可以把每个动物的功能封装,就写成:

class PolymorphismDemo
{
    public static void main(String[] args)
    {
        function(new Cat());
        function(new Dog());
    }
    public static void function(Cat c)//这一步就相当于Cat c=new Cat
    {
        c.eat();
        c.catchMouse();
    }
    public static void function(Dog d)//相当于Dog d=new Dog
    {
        d.eat();
        d.watchHouse();
    }
}

但是发现每一种动物都要去定义一个function,还是太繁琐.此时发现父类有eat(),而且子类覆盖了这个方法,并且子类继承了父类.这就是多态的两个前提了,继承关系和覆盖.因为eat()父类和子类都有,这里就先解决eat().

所以可以这样写:

class PolymorphismDemo
{
    public static void main(String[] args)
    {
        function(new Cat());
        function(new Dog());
    }
    public static void function(Animal a)//相当于Animal a=new 子类名();
    {
        a.eat();
    }

}

注意:

Animal a=new Cat();

a.eat();

此时引用变量a所属类型是Animal,编译的时候父类里面有所调用的函数eat(),编译通过,如果没有,则编译会失败,在运行的时候,对象所属类,就是Cat里面有没有所调用方法eat(),有,则运行子类里面的eat(),因为子类覆盖了父类的对应方法,没有,就运行父类的,因为子类会继承父类的方法.这就是下面要讲的多态中成员函数的特点.

多态的体现:父类的引用指向了自己的子类对象.换句话说,用父类的方法,传的是子类的对象.

多态的好处:多态的出现就极大提高了程序的扩展性.

多态的前提:必须有继承和实现关系,必须要有重写操作.

多态的弊端:提高了扩展性,但只能使用父类的引用访问父类的成员,所以要有重写操作.


  (三)多态-转型

接着上面的问题,首先说下多态的转型:

class PolymorphismDemo
{
    public static void main(String[] args)
    {
        function(new Cat());
        function(new Dog());
    }
    public static void function(Animal a)//相当于Animal a=new 子类名();
    {
        a.eat();
    }

}

在上面代码中这句代码Animal a=new 子类名();意思是把子类的对象的引用转换成了父类的引用,这叫向上转型.这个转型是自动完成的,类似变量里面的自动提升类型.

但是,解决了每种的动物的eat(),如何把每种动物特有的属性也定义在function里面,比如猫的catchmouse,狗的watchhouse.由于此时的a是Animal类型,所以要把a再转换回子类的引用,这叫向下转型,转换之后,就可以调用子类特有的方法了:

class PolymorphismDemo
{
    public static void main(String[] args)
    {
        function(new Cat());
    }
    public static void function(Animal a)
    {
        a.eat();
        Cat c=(Cat)a;
        c.catchMouse();//转换之后就可以调用子类特有方法了
    }

}

但是上面只转换了Cat没转换Dog,假如function里面传进去的是Dog(),那转换就会出错,所以加上判断语句:

class PolymorphismDemo
{
    public static void main(String[] args)
    {
        function(new Dog());
        function(new Cat());
    }
    public static void function(Animal a)
    {
        a.eat();
        if(a instanceof Cat)//instanceof判断a是否属于Cat类

        {
            Cat c=(Cat)a;
            c.catchMouse();
        }
        else if(a instanceof Dog)
        {
            Dog d=(Dog)a;
            d.watchHome();
        }
    }
}

小知识:instanceof是用来判断左边的引用是否是属于右边的类型.

注意:可以将子类对象的引用提升为父类引用,再降低为子类引用,但是如果父类能创建对象的话,不能直接把父类引用向下转型为子类引用,因为子类里面特有属性,父类是没有的.换句话说,转型,只能是子类自己上转下转,父类不能自己转型.例如:

Animal a=new Animal();

Cat c=(Cat)a;

这样是不行的.

小结:

1.多态的转型中始终只有子类对象在变化.

2.上面的判断方法只适用于子类比较少的情况,如果子类非常多,那么就要给每个子类都加上判断条件,就很繁琐了.

3.上面的eat()是每个子类都的属性,可以定义在父类里面,子类重写.在子类对象调用eat()的时候,每个对象都要写一次调用的语句,繁琐,于是封装,但是封装也只是封装某一个对象的对应方法,由于这个方法子类父类都有,并且子类覆盖了父类,所以封装的时候用多态的转型,也就是转成父类引用,如果要调用子类特有属性,在子类比较少的情况下可以用判断,将对应的父类引用转成子类.


  (四)多态-示例

继续上面的例子,上面的例子是把eat()和子类特有属性用多态封装在一起,但是是封装在主函数所在的那个类里面的,这个类一般都是直接面对用户的,所以最好封装在其他类里面,这时可以单独定义一个类,注意是类不是函数.用来封装子类的那个可以多态的属性,就是eat(),这样避免了用户看到eat(),尽量简洁,于是:

abstract class Animal
{
    public abstract void eat();
}
class Cat extends Animal
{
    public void eat()//猫继承了动物的吃东西
    {
        System.out.println("eat fish");
    }
    public void catchMouse()//还有自己特有的抓老鼠属性
    {
        System.out.println("catch mouse");
    }
}
class Dog extends Animal
{
    public void eat()//狗继承了动物的吃东西
    {
        System.out.println("eat bone");
    }
    public void watchHome()//还有自己特有的看家属性
    {
        System.out.println("watch home");
    }
}
class Function//把eat()单独封装

{
    public void eatFunction(Animal a)
    {
        a.eat();
    }
}
class PolymorphismDemo
{
    public static void main(String[] args)
    {
        Function f=new Function();//创建Function类以调用里面的方法
        f.eatFunction(new Cat());//把new Cat()传给Animal a,就相当于Animal a=new Cat()
        f.eatFunction(new Dog());
    }
}

小结:如果要调用子类的共性属性,而这些共性值不同,先将这些属性抽取为父类,再定义一个工具类,里面定义参数为父类型的方法,这些方法可以利用运行多态的特性调用子类的那个共性属性.用户只需创建那个工具类的对象,调用工具类里面的函数,把子类对象传进去.


  (五)多态中成员的特点

多态情况是指在父类指向子类对象的情况下,本类指向本类对象那不叫多态,比如:

Animal a=new Cat();

成员函数(注意是非静态的)特点:

1.在编译时期,参阅引用型变量(就是上面例子中的a)所属的类中(父类)是否有调用的方法,如果有,编译通过,如果没有,编译失败.

2.在运行时期,参阅对象所属类中(子类)是否有调用方法,有,就运行自己的,因为子类中有的话就覆盖掉父类的了,没有,就运行父类的,因为子类会继承父类.

成员变量的特点:无论编译还是运行,都只参阅引用型变量所属类(父类)中是否有被调用的变量.

补充:如果是静态的成员,在多态中都参考引用型变量所属类(父类)中是否有被调用成员,因为静态的成员不会用到对象,所以只看引用变量所属类.

知识补充:

静态方法一被定义,就和所属函数绑定在一起了,这叫静态绑定,非静态方法只看调用者是谁就和谁绑定,这叫动态绑定.
基本数据类型存的是数值本身,而引用类型变量在内存放的是数据的引用,那个十六进制地址,并不是数据的本身,引用类型变量是以间接方式去获取数据。
引用类型变量都属于对象类型,如:数组、类、字符串等都属于引用类型变量。所以,引用类型变量里面存放的是数据的地址。


  (六)多态的主板示例

/*
需求:
电脑运行示例,电脑运行基于主板.

PCI卡就是主板上插的声卡,网卡,显卡等,在主板上我们可以看到一些PCI插槽,这些插槽对应着相应的PCI卡,只有插入正确才能极其才能正常响应.
不同的PCI卡制造厂商生产PCI卡时要符合一个共同标准去制造,而不同的主板制造厂也要符合一个共同的标准去制造,只有符合一个标准PCI卡才能正确响应主板.所以先定义一个接口,就是那个PCI插槽,就是标准.
*/
interface PCI//定义一个接口,这里表示一个PCI标准,首先定义的就是这个标准,或者规则,接口的出现提高了功能扩展性,降低了PCI卡和主板的耦合性.
{
    public abstract void start();//当启动时
    public abstract void stop();//当停止时
}
class MainBoard//然后定义的是这个工具类,用来使用那个规则,也就是这里的PCI.
{
    public void run()//当主板运行时响应一个方法
    {
        System.out.println("mainboard run");
    }
    public void usePCI(PCI p)//相当于PCI p=new 子类名();就是接口型引用指向子类对象,就是多态的应用.

    //传来的是网卡的参数就调用网卡的方法,传来的是声卡的参数就调用声卡卡的方法
    {
        if(p!=null)//加上判断是为了防止p没有指向..
        {
            p.start();//由于多态,在运行的时候调用的p对应的子类里面的start()方法.
            p.stop();//同理
        }
    }
}
class NetCard implements PCI//一块网卡实现了PCI标准
{
    public void start()//启动时响应一个方法
    {
        System.out.println("netcard start");
    }
    public void stop()//停止时响应一个方法
    {
        System.out.println("netcard stop");
    }
}
class SoundCard implements PCI//一块声卡实现了PCI标准
{
    public void start()
    {
        System.out.println("soundcard start");//启动时响应一个方法
    }
    public void stop()
    {
        System.out.println("soundcard stop");//停止时响应一个方法
    }
}
class Test//这个就是最终的用户,通过工具类的对象使用
{
    public static void main(String[] args)
    {
        MainBoard mb=new MainBoard();//用户开始使用主板,就是那个工具类.
        mb.run();//主板运行.
        mb.usePCI(null);//用户没有向主板里面插入任何东西.主板就不会使用PCI.
        mb.usePCI(new NetCard());//用户向PCI卡槽里面插入一块网卡
        mb.usePCI(new SoundCard());//用户向PCI卡槽插入一块声卡
    }
}

小结:和上面的猫狗动物那个例子是一模一样的,只不过是接口类换成了动物类,声卡和网卡换成了猫和狗,主板换成了eatFunction,就是那个工具类.所以下面是我的思路:

1.首先思考事物的共有属性是什么,也就是他们什么方法的方法名,参数类型和参数个数都一样,但是方法体不同.比如猫狗都要吃东西,但是吃的东西不同.

2.定义一个父类,这个类一般是抽象的,或者抽取为一个接口.里面定义事物共有的那个方法,同样定义为抽象方法,

3.定义一个工具类,里面定义一个方法,来调用那个父类或者接口里面的方法,参数类型当然是那个父类型或者接口类型.

4.定义事物类描述事物的共有属性,这些类都要继承或者实现那个父类或者接口,比如描述猫吃东西的方法,方法体是吃鱼,狗也吃东西,方法体是吃骨头.

5.定义一个类,里面有主函数,这是面向用户的,用户创建工具类的对象,并创建事物类的对象,调用工具类里面的方法,需要运行哪个事物就向里面传入哪个事物类的对象,此时会用多态的特性判断并最终执行指定的事物类里面的方法.

举个例:

多态是一种面向对象的设计思想,也就是只给接口,让你去实现(可以替换实现),就是设计和实现的分离,这个做法有利于软件的重用。
举个例子,我创建了一个动物的抽象类或接口,里面有一些接口方法,这些接口方法必须是事物共有的,方便实现,

然后我造了一个动物园,这就是那个工具类,里面有有各种动物的奔跑、叫唤、吃食等动作作为方法,调用的是接口或抽象类的那个方法,但我并没有实现它,在没有任何动物之前我只用知道如何把这些动物都圈到笼子里,就有动物园了。
然后,从别处给我运来了老虎、狮子、猴子、狗、猫这些动物,都是实现了我的奔跑、叫唤、吃食这些方法了,这些就是具体动物的实现部分,我把他们挂到你动物园上就行了。这就是多态。



   (七)多态的扩展示例

/*
需求:数据库操作.
数据是用户信息.
操作步骤是:
1.连接数据库.用JDBC或者Hibernate
2.操作数据库.比如CRUD,就是creat,read,update,delete.
3.关闭数据库连接.
*/
class UserInfoJDBC//工具类,用JDBC的方法连接数据库操作用户数据.
{
    public void add(User user)//User类是用户信息
    {
        1.使用JDBC连接数据库
        2.使用sql添加语句添加数据
        3.关闭数据库连接
    }
    public void delete(User user)
    {
        1.使用JDBC连接数据库
        2.使用sql添加语句删除数据
        3.关闭数据库连接
    }
}
class DataBaseOperate
{
    public static void main(String[] args)
    {
        UserInforJDBC ui=new UserInfoJDBC();
        ui.add(user);
        ui.delete(user);
    }
}
这里就产生一个问题,某一天我想换一种连接数据库的方式,把JDBC换成Hibernate,就必须要更改每一个工具类里面的连接方式,太繁琐.

或者我不该原来的工具类代码,干脆重新写一个工具类,用Hibernate连接数据库.如下:

class UserInfoHibernate
{
    public void add(User user)
    {
        1.使用Hibernate连接数据库
        2.使用sql添加语句添加数据
        3.关闭数据库连接
    }
    public void delete(User user)
    {
        1.使用Hibernate连接数据库
        2.使用sql添加语句删除数据
        3.关闭数据库连接
    }
}

class DataBaseOperate
{
    public static void main(String[] args)
    {
        UserInforHibernate ui=new UserInfoHibernate();
        ui.add(user);
        ui.delete(user);
    }
}

但是这样又要更改主函数里面的创建工具类对象的语句,这样也不好,因为一般是用户通过创建事物的对象(也就是创建猫狗的对象)来调用事物的方法,用户有什么新的事物要用,就创建这个事物的对象并传到工具类的函数里面,而不是要自己去创建工具类的对象,所以最好不要改动创建工具类对象的语句.

但是,发现每个工具类里面有相同的方法,都是添加和删除,不同的是方法体,所以两个工具类不就是两个有相同方法不同方法体的事物了.思考到这就应该考虑定义一个具备这些方法的接口了,就是规则,

interface UserInforDao//dao就是data access objects数据访问对象.
{
    public abstract add(User user);
    public abstract delete(User user);
}
class Function//工具类
{
    public void function(UserInforDao f)
    {
        f.add(User user);
        f.delete(User user);
    }
}
class UserInfoJDBC implements UserInforDao
{
    public void add(User user)
    {
        1.使用JDBC连接数据库
        2.使用sql添加语句添加数据
        3.关闭数据库连接
    }
    public void delete(User user)
    {
        1.使用JDBC连接数据库
        2.使用sql添加语句删除数据
        3.关闭数据库连接
    }
}
class UserInfoHibernate implements UserInforDao
{
    public void add(User user)
    {
        1.使用Hibernate连接数据库
        2.使用sql添加语句添加数据
        3.关闭数据库连接
    }
    public void delete(User user)
    {
        1.使用Hibernate连接数据库
        2.使用sql添加语句删除数据
        3.关闭数据库连接
    }
}
class DataBaseOperate
{
    public static void main(String[] args)
    {
        Function ui=new Function();
        ui.function(new UserInforJDBC());
        ui.function(new UserInforHibernate());
    }
}

  (八)Object类-equals()

Object是所有类的直接或间接父类,是类层次结构的根类.该类中定义的是所有对象都具备的功能.

注意:java里面所有的对象都能比较是否相等.通过查看api文档发现,Object类里面已经定义了多种方法,所以只要定义一个类,这个类的对象就可以调用Object类里面已经定义好的方法了.

下面举例Object类中的equals(Object obj)方法的应用,

class Demo{}
class Demo2{}
class ObjectDemo
{
    public static void main(String[] args)
    {
        Demo d1=new Demo();
        Demo d2=new Demo();
        Demo2 d3=new Demo2();
        Demo2 d4=new Demo2();
        System.out.println(d1.equals(d3));
        System.out.println(d3.equals(d4));
    }
}

从上面例子可以看出任意类的对象都可以调用这个equals()方法,用来比较同一个类中的或者不同类中的对象的地址值是否相同.

接下来我定义了一个新方法compare()用来比较对象的属性值是否相同.
class Demo
{
    private int num;
    Demo(int num)
    {
        this.num=num;
    }
    public boolean compare(Demo d)
    {
        return this.num==d.num;
    }
}
class ObjectDemo
{
    public static void main(String[] args)
    {
        Demo d1=new Demo(3);
        Demo d2=new Demo(4);
        System.out.println(d1.compare(d2));
    }
}

但是发现Object类中已经定义了比较对象是否相同的方法,所以如果自定义类中也有比较是否相同的功能,那么就没必要再重新定义这么一个比较功能了,只要沿袭Object中的equals方法,建立自己特有的比较内容即可,这就是覆盖.于是这样写:
class Demo
{
    private int num;
    Demo(int num)
    {
        this.num=num;
    }
    public boolean equals(Object x)
    {
        return this.num==x.num;
    }
}
class ObjectDemo
{
    public static void main(String[] args)
    {
        Demo d1=new Demo(5);
        Demo d2=new Demo(5);
        System.out.println(d1.equals(d2));
    }
}

但是报错提示找不到num符号,因为在Object类中没有定义num变量,所以无法调用Demo类特有变量,此时需要向下转型:

class Demo
{
    private int num;
    Demo(int num)
    {
        this.num=num;
    }
    public boolean equals(Object x)
    {
        Demo y=(Demo)x;//注意这一步,向下转型
        return this.num==y.num;
    }
}
class ObjectDemo
{
    public static void main(String[] args)
    {
        Demo d1=new Demo(5);
        Demo d2=new Demo(5);
        System.out.println(d1.equals(d2));
    }
}

但此时又会出现一个问题,比如再定义一个新的类,叫Person,然后这样写:

class Demo
{
    private int num;
    Demo(int num)
    {
        this.num=num;
    }
    public boolean equals(Object x)
    {
        Demo y=(Demo)x;
        return this.num==y.num;
        
    }
}
class Person
{
    int num;
}
class ObjectDemo
{
    public static void main(String[] args)
    {
        Demo d1=new Demo(4);
        Person p=new Person();
        System.out.println(d1.equals(p));
    }
}

此时会在运行的时候提示不能将p转换为Demo类型,因为p要传给x,要把x转型为Demo,此时可以在向下转型前加上一个判断,判断下x是否是Demo类:

class Demo
{
    private int num;
    Demo(int num)
    {
        this.num=num;
    }
    public boolean equals(Object x)
    {
        if(!(x instanceof Demo))//注意判断语句的写法
            return false;
        Demo y=(Demo)x;
        return this.num==y.num;
        
    }
}
class Person
{
    int num;
}
class ObjectDemo
{
    public static void main(String[] args)
    {
        Demo d1=new Demo(0);
        Person p=new Person();
        System.out.println(d1.equals(p));
    }
}

注意:假如这样写:此时没有加判断语句

class Demo
{
    private int num;
    Demo(int num)
    {
        this.num=num;
    }
    public boolean equals(Object x)
    {
        Demo y=(Demo)x;
        return this.num==y.num;        
    }
}
class Person
{
    int num;
}
class ObjectDemo
{
    public static void main(String[] args)
    {
        Demo d1=new Demo(0);    
        Person p=new Person();
        System.out.println(p.equals(d1));
    }
}
编译和运行都没有问题,结果是false,原因是Person的对象只能调用Person里面的方法,此时调用equals方法的是p,p是Person类,而Person类中没有定义equals方法,所以调用的是父类Object的equals方法,而不是Demo中的equals方法.其实此时比较的还是p和d1的地址值.


  (九)Object类-toString()

toString():能把任意对象转换成字符串,返回类型是String.
class Demo
{
    private int num;
    Demo(int num)
    {
        this.num=num;
    }
}
class ObjectDemo
{
    public static void main(String[] args)
    {
        Demo d1=new Demo(0);
        System.out.println(d1.toString());
    }
}
结果是Demo@166afb3.也就是这个对象所属的类名@这个对象的哈希值.


由于所有的对象都有哈希值,所以在Object类中就定义了返回哈希值的方法:hashCode(),用于返回该对象的十进制哈希值.比如:
System.out.println(d1.hashCode());
但是这样调用的话,返回的确实是该对象的哈希值,但是不是十六进制的,所以要转换进制.
System.out.println(Integer.toHexString(d1.hashCode()));

另外在Object类中还有一个getClass()方法,用来返回调用这个方法的对象所属的类,当然返回值类型是class.

由于每个类都有一些共性的东西,比如都有类名,都有构造函数,都有一般方法,所以抽出这些共性,定义一个包含共性的类.java中就定义了这么一个类用来描述所有类的共性,class Class,注意类名比较特殊,是Class.

在Class类中有getMethods()用来返回一个类中的所有方法,包括私有方法.还有getName(),以字符串的形式返回这个类文件对象所指向的实体(比如类,接口等)名称.比如:
class Demo
{
    private int num;
    Demo(int num)
    {
        this.num=num;
    }
}
class ObjectDemo
{
    public static void main(String[] args)
    {
        Demo d1=new Demo(0);
        Class c=d1.getClass();//c就是类文件对象.注意这里的c变量并没有指向对象,而是指向了

d1所属的类
        System.out.println(c.getMethods());//返回c所指向的对象也就是Demo中的方法
        System.out.println(c.getName());//返回c所指向的实体的名称,也就是Demo类的类名
        System.out.println(Integer.toHexString(d1.hashCode()));
        System.out.println(d1.toString());
    }
}

既然知道了如何返回对象所属类的名称,以及如何显示对象的十六进制哈希值,所以toString()其实就是这样:
class Demo
{
    private int num;
    Demo(int num)
    {
        this.num=num;
    }
}
class ObjectDemo
{
    public static void main(String[] args)
    {
        Demo d1=new Demo(0);
        Class c=d1.getClass();
        System.out.println(c.getMethods());
        System.out.println(c.getName());
        System.out.println(c.getName()+"@"+Integer.toHexString(d1.hashCode()));
        System.out.println(d1.toString());
    }
}



---------------------- android培训java培训、期待与您交流! ----------------------详细请查看:http://edu.csdn.net/heima

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值