Java基础---多态、内部类

多态(Polymorphism)

多态的其他叫法:动态绑定,后期绑定,运行时绑定。

多态的前提:

1、类与类之间必须有关系,要么继承,要么实现。

2、存在覆盖。父类中有方法被子类重写。

例子:

/***
 * 多态的实例
 * @author LQX
 *涉及:向上转型,多态
 */
public class Test1 {

     //此处使用Animal类将使得调用更方便,如果写他的子类,那么显得冗余复杂,直接写他们的父类将会显得简单。
     //此处就是动态绑定(多态),运行时根据对象类型进行绑定
    public static void choose(Animal a)
    {
        a.eat();
    }

    public static void main(String[] args) {

        //   choose(new Cat());等价于Animal a = new Cat();choose(a);  涉及了:向上转型,Cat本质就是Animal类。 

        choose(new Cat());
        choose(new Dog());
    }

}
//动物类,父类
class Animal
{
    //动物都具有的行为,吃
    public void eat()
    {
        System.out.println("Animal eat!");
    }
}
//猫
class Cat extends Animal
{
    //猫的行为
    public void eat()
    {
        System.out.println("猫爱吃鱼!");
    }
}
//狗
class Dog extends Animal
{
    //狗的行为
    public void eat()
    {
        System.out.println("狗爱吃骨头!");
    }
}

总结:

1.绑定:

    将一个方法调用同一个方法主体关联起来被称作绑定。程序在执行前进行绑定(由编译器和连接程序实现)称为前期绑定。上述程序之所以迷惑,主要是因为前期绑定。因为只有一个Animal引用时,他无法知道调用Animal类还是Cat类还是Dog类的eat方法。

    解决办法就是后期绑定(多态),意思就是运行时根据传进去的对象类型进行绑定。

2.多态的体现

a. 父类的引用指向了自己子类的对象。 

b. 父类的引用也可以接收自己的子类对象。

如: Animal a = new Cat();

其中就将父类型的 a 引用指向了子类的对象。

3.多态的利与弊

    利:提高了程序的可扩展性和后期可以维护性。

    弊:只能使用父类中的引用访问父类中的成员。也就是说使用了多态,父类型的引用在使用功能时,不能直接调用子类中的特有方法。如:Animal a = new Cat(); 这代码就是多态的体现,假设子类Cat中有特有的抓老鼠功能,父类型的 a就不能直接调用。这上面的代码中,可以理解为Cat类型提升了,向上转型。

    如果此时父类的引用想要调用Cat中特有的方法,就需要强制将父类的引用,转成子类类型,向下转型。如:Cat c = (Cat)a;

注:如果父类可以创建对象,如:Animal a = new Animal(); 此时,就不能向下转型了,Cat c = (Cat)a; 这样的代码就变得不容许,编译时会报错。所以千万不能出现这样的操作,就是将父类对象转成子类类型。

    我们能转换的是父类引用指向了自己的子类对象时,该引用可以被提升,也可以被强制转换。多态至始至终都是子类对象在做着变化。

4.多态的特点

Animal a = new Cat();为例
引用类型是《Animal》
实际类型是《Cat》

a.成员函数(非静态)

    在编译的时候,编译器会先检查“引用类型”是否存在符合定义的“eat”的方法,如果没有,不能编译。
    在执行a.eat();的时候。检查本类,也就是实际类型的类,有没有符合的方法。如果没有,就执行父类继承过来的。如果有,就执行本类(子类)的。

    总结:编译看左边,运行看右边。
public class Test3 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Father f = new Son();
        f.run();
    }

}
class Father
{
    //此方法注释掉,编译通不过
    public void run()
    {
        System.out.println("Father run");
    }
}

class Son extends Father
{
    //此方法注释掉,将显示父类该方法
    public void run()
    {
        System.out.println("Son run");
    }
}

这里写图片描述

b.成员函数(静态)

    调用当前引用类型变量类型中的方法。
    因为静态是属于类的,由实例共享,所以只看当前引用变量所属的类中的静态方法。
    总结:编译和运行看左边
public class Test3 {

    public static void main(String[] args) {
        Father f = new Son();
        f.method();
    }

}
class Father
{
    public static void method()
    {
        System.out.println("Father static method");
    }
}

class Son extends Father
{
    public static void method()
    {
        System.out.println("Son static method");
    }
}

这里写图片描述

c.成员变量

    无论编译期还是运行期,都只参考引用类型变量所属的类中是否有对象的成员变量。有,编译或运行通过,没有,编译或运行失败
    简单说:编译和运行都参考等号的左边
public class Test2 {

    public static void main(String[] args) {
        //多态的成员变量看引用类型(左边),右边为实际类型
        Father f = new Son();
        System.out.println(f.i);

        Son s = new Son();
        System.out.println(s.i);
    }

}

class Father
{
    int i = 1;
}

class Son extends Father
{
    int i = 4;
}

这里写图片描述

小测验:

/***
 * 需求:
 * 电脑的运行实例。电脑的运行由主板控制,假设主板只是提供电脑运行,但是没有上网,
 * 听歌等功能。而上网、听歌需要硬件的支持。而现在主板上没有网卡和声卡,
 * 这时可以定义一个规则,叫PCI,只要符合这个规则的网卡和声卡都可以在主板上使用,
 * 这样就降低了主板和网卡、声卡之间的耦合性。用程序体现。 
 */
public class Test4 {

    public static void main(String[] args) {
        Mainboard m1 = new Mainboard();
        m1.run();
        m1.usePCI(new AudioCard());
        m1.usePCI(new NetworkCard());
    }
}
//主板
class Mainboard
{
    public void run()
    {
        System.out.println("主板启动");
    }
    //调用PCI接口
    public void usePCI(PCI p)
    {
        if(p!=null)
        {
            p.open();
            p.close();
        }
    }
}
//PCI接口
interface PCI
{
    void open();
    void close();
}
//声卡实现PCI接口
class AudioCard implements PCI
{
    @Override
    public void open() {
        System.out.println("声卡启动!");
    }

    @Override
    public void close() {
        System.out.println("声卡关闭!");
    }
}
//网卡实现PCI接口
class NetworkCard implements PCI
{
    @Override
    public void open() {
        System.out.println("网卡启动!");
    }

    @Override
    public void close() {
        System.out.println("网卡关闭!");
    }
}

这里写图片描述

内部类(Inner Class)

一、成员内部类

1、定义

  一个类的定义放在另一个类的内部,这个类就叫做内部类。 
  就好像,外部类的一个成员一样,故称成员内部类。
 public class First {
    public class Contents{
        public void f(){
        System.out.println("In Class First's inner Class Contents method f()");
        }
    }
  }
像这样的,Contents就叫做内部类 
内部类了解外围类,并能与之通信。

2、链接到外围类

  创建了内部类对象时,它会与创造它的外围对象有了某种联系,于是能访问外围类的所有成员,不需任何特殊条件。 
public class Test2 {
    //外部类定义的字符串
    String s = "abcdefg";
    class Innerclass
    {
        public void show()
        {
            //内部类可以访问
            System.out.println(s);
        }
    }
    public static void main(String[] args) {
        Test2 t = new Test2();
        Innerclass i = t.new Innerclass();
        i.show();
    }
}
在内部类InnerClass中,可以使用外围类成员变量s
实现方式:
    非静态内部类对象的创建需要先创建外部类对象,此时的内部类会秘密的获取到外部类对象的引用,通过这个引用可以来访问外围对象成员。
    通常,这些是由编译器所做的,我们所关注的应该是:
    创建内部类对象时,与外部类对象的关联。

3、使用关键字.this与.new

.this关键字用于在内部类中获取当前外部类引用,注意与new的区别。
public class Test1 {
    int i = 2;
    public Test1(){
    }
    public Test1(int i)
    {
        this.i = i;
    }
    class inner
    {
        public Test1 getTest1()
        {
            //使用.this后,得到时创建该内部类时使用的外围类对象的引用,
            return Test1.this;
        }
        public Test1 newTest1()
        {
            //new则是创建了一个新的引用。
            return new Test1();
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Test1 t1 = new Test1(5);
        //  必须是外围类对象.new(实例.new),而不能是外围类.new
        Test1.inner inner1 = t1.new inner();

        Test1 t2 = inner1.getTest1();

        Test1 t3 = inner1.newTest1();
        System.out.println(t2.i);
        System.out.println(t3.i);
    }
}

这里写图片描述

使用.this关键字,得到创建改内部类的外部类对象的引用,而new则是建立了一个新的引用。

使用.new关键字,会根据一个外部类对象来创建一个内部类对象。

形式是这样的:

      OutClass.InnerClass obj = outClassInstance.new InnerClass();
  注意使用的是外部类对象来new,而不是外部类。

4、内部类与向上转型

将内部类向上转型为基类型,尤其是接口时,内部类就有了用武之地。 
public class Mianboard {
    //内部类,网卡实现PCI接口
    private class NetworkCard implements PCI{
        @Override
        public void open() {
            System.out.println("Open NetworkCard!");
        }
    }
    //获取PCI接口实例
    public PCI getPCI()
    {
        return new NetworkCard();
    }
    public static void main(String[] args) {
        Mianboard m1 = new Mianboard();
        PCI p1 = m1.getPCI();
        p1.open();
    }
}
//PCI接口
interface PCI
{
    void open();
}

通过内部类,可以对外隐藏一些类的细节,除了他的外部类可以访问,其他人无法访问。并且,由于写在了内部,也降低了耦合性。

二、方法内部类

内部类定义在外部类中的某个方法中,创建了这个类型的对象时,且仅使用了一次,那么可在这个方法中定义局部类。

    1)不可以被成员修饰符修饰。如public、private、static等修饰符修饰。它的作用域被限定在了声明这个局部类的代码块中

    2)可以直接访问外部类中的成员,因为还持有外部类中的引用。

    3)方法内部的类也不是在调用方法时才会创建的,它们一样也被编译了。

注意:内部类不可以访问它所在的局部中非最终变量。只能访问被final修饰的局部变量。
原因:http://blog.csdn.net/craigyang/article/details/4680506

三、匿名内部类

1.定义:

   就是内部类的简化写法。

2.定义匿名内部类的前提

内部类必须实现了某个接口或者继承了某个类

3.格式

new外部类名或者接口名(){覆盖类或者接口中的代码,(也可以自定义内容。)};

4.实质

其实匿名内部类就是一个匿名子类对象。可以理解为带内容的对象。

5.什么时候使用匿名内部类呢?

通常使用方法是接口类型参数,并且该接口中的方法不超过三个,可以将匿名内部类作为参数传递

6.匿名内部类的利与弊

优:简化书写
弊:1.匿名内部类没有名字,也就不能调用自己的方法,且只使用一次。
2.没有引用,也就不可以做强转动作。
3.与正规的继承相比有些受限,因为匿名内部类既可以扩展类,也可以实现接口,但不能两者兼备。切如果实现接口,也只能实现一个。
4.内部类方法不宜过多,否则影响阅读性,一般不超过3个。
//方法一:
public class Mianboard2 {
    //匿名内部类,网卡实现PCI接口
    //获取PCI接口实例
    public PCI1 getPCI1()
    {
        return new PCI1(){
            public void open() {
                System.out.println("Open NetworkCard!");
            }
        };
    }
    public static void main(String[] args) {
        Mianboard2 m1 = new Mianboard2();
        PCI1  p = m1.getPCI1();
        p.open();
    }
}
//PCI接口
interface PCI1
{
    void open();
}
/////////////////////////////////////
//方法二:
public class Mianboard2 {
    //内部类,网卡实现PCI接口
    //获取PCI接口实例
    public void getPCI1(PCI1 p)
    {
        p.open();
    }
    public static void main(String[] args) {
        Mianboard2 m1 = new Mianboard2();
        m1.getPCI1(new PCI1(){
            public void open() {
                System.out.println("Open NetworkCard!");
            }
        });
    }
}
//PCI接口
interface PCI1
{
    void open();
}

7.匿名内部类中如果需要传递参数的话,那么这个参数就必须是final的。

/***
 * 匿名内部类传参必须是final的 在这个例子中,
 * 可以为A的构造方法传入一个参数 在匿名内部类中,并没有使用到这个参数。
 * 如果使用到了这个参数,那么这个参数就必须是final的。
 */
public class Test4 {

    public A getA(final int num) {
        return new A(num) {
            public int getNum() {
                return num;
            }
        };
    }
}

class A {
    private int num;

    public A(int num) {
        this.num = num;
    }
}

8. 由于类是匿名的,自然没有构造器,如果想模仿构造器,可以采用实例初始化({})

//匿名内部类想模仿构造器
public class Test5 {

    public B getB() {
        return new B() {
            void c() {
                System.out.println("dsds");
            }

            String s;
            int n;

            // 模仿构造器,采用实例初始化
            {
                s = "abcdefg";
                System.out.println("实例初始化");
            }

            public void show() {
                System.out.println(s);
            }
        };
    }

    public static void main(String[] args) {
        Test5 t = new Test5();
        B b1 = t.getB();
        b1.show();
    }
}

class B {

    public void show() {
    }
}

这里写图片描述

四、static内部类(嵌套类)

使用嵌套类时有两点需要注意:

   a、创建嵌套类对象时,不需要外围类 
   b、在嵌套类中,不能像普通内部类一样访问外围类的非static成员 
   c、被static修饰的内部类只能访问外部类中的静态成员
   d、如果内部类中定义了静态成员,该内部类也必须是静态的

嵌套类还有特殊之处,就是嵌套类中可以有static方法,static字段与嵌套类,而普通内部类中不能有这些。

//嵌套类(static内部类)总结
public class Test6 {

    static int i = 8;
    String s = "haha";

    static class C {
        //静态类中定义非静态方法是没有意义的,因为静态类是不可以被实例化的
        public void show() {
            // 嵌套类不可以获取外部类的非静态成员
            System.out.println("show" + new Test6().s);
            //非静态方法可以调用静态方法,反之不行,因为非静态方法需要实例
            take();
        }

        static void take() {
            // 嵌套类可以直接获取外部类的静态成员
            System.out.println("take" + i);
        }
    }

    public static void main(String[] args) {
        //嵌套类的使用
        Test6.C.take();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章