多态(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();
}
}