导读:多态[概念、扩展性、转型、成员的特点、示例],Object类[equals()、toString()],内部类的访问规则,静态内部类,内部类定义原则,匿名内部类
1、多态—概念
l 面向对象的第三个特征:多态
l 多态:可以理解为事物(对象)存在的多种体现形态
l 函数也可以理解为是多态的(重载,覆盖)
l 例:人:男人,女人
动物:猫,狗。
猫 x = new 猫();
动物 x = new 猫(); //猫是动物中的一种。
2、多态—扩展性
(1)多态的体现
Ø 父类的引用指向了自己的子类对象。
Ø 父类的引用也可以接收自己的子类对象。(主函数直接写new,方法写父类引用)
(2)多态的前提
Ø 必须是类与类之间有关系。要么继承,要么实现。class Cat extendsAnimal才可以用Animal a = new cat();
Ø 通常还有一个前提:存在覆盖。前期预先调用功能,后期定义子类去实现功能,并把子类做为参数传递进来。就实现了后期的扩展性。
(3)多态的好处
Ø 多态的出现大大的提高程序的扩展性。(以后再出现别的东西,程序不用动,直接new就好了)
(4)多态的弊端:
Ø 提高了扩展性,但是只能使用父类的引用访问父类中的成员。(不能预先去使用子类,因为那时候子类还不存在呢。)
(5)多态的应用
Ø 多态的出现,将对象的调用变简单了。以前是指挥一个对象做事情,现在是指挥一批对象做事情。这是因为我找到了这些对象的共同所属类型。不断的将事物向上抽象,你总能找到共同点。由工具类完成相应的动作,最后主函数找工具类就可以了。我们对类型进行抽取,导致了多态的产生。操作同一个大的类型,对大类型中的所有的小类型都能进行操作,基于这个思想才提高了扩展性。
(6)多态的出现代码中的特点(多态使用的注意事项)
2、多态—转型
l Animal a = newcat(); //类型提升。向上转型(转成父类型)。
a.eat();
可是提升之后猫有动物吃的功能,但是没有抓老鼠的功能啊。
如果想要调用猫特有方法时,如何操作?
强制将父类的引用。转成子类类型。向下转型。
Cat c = (Cat)a;
c.catchMouse();
千万不要出现这样的操作,就是将父类对象转成子类类型。(如,Animal a = newcat(); Cat c = (Cat)a; 动物先出现,猫后出现,动物中没有抓老鼠的方法,而且也不符合现实生活原理。)我们能转换的是父类应用指向了自己的子类对象时,该应用可以被提升,也可以被强制转换。多态自始至终都是子类对象在做着变化。
l 毕姥爷 x = new 毕老师();
x.讲课();
毕老师 y = (毕老师)x;
y.看电影();
l 判断某一类型的引用,指向的对象到底符合什么类型的时候用关键字:instanceof
if(a instanceof Cat) //判断类型有限,要么是猫,要么是狗。没有其他的。
{
Cat c = (Cat)a; //判断后再操作
c.catchMouse();
}
else if(ainstanceof Dog)
{
Dog c = (Dog)a;
c.kanJia();
}
再来的猪的话,不是还要加语句。再加语句的话程序的扩展性极差。通常情况两种情况可以用。第一:子类的类型有限(人要么男人,要么女人)。这个可以判断,判断完之后,你传的是什么,再用相对应的特有的运行方式。。第二:你传的类型还有其他的操作,比如比较,我必须确定它是哪种子类型,我要调用它特有的方法来比较。这时用instanceof判断就行了。
3、多态中成员的特点
l 在多态中成员函数(非静态的情况)的特点:(开发中多见,有覆盖操作)
Ø 在编译时期:参阅引用型变量所属的类中是否有调用的方法。如果有,编译通过,如果没有编译失败。
Ø 在运行时期:参阅对象所属的类中是否有调用的方法。(正是由于重写的特性才在运行中看他的子类的)
Ø 简单总结就是:成员函数在多态调用时,编译看左边,运行看右边。
l 例: Fu f = new Zi(); //编译时先看是否有method1()、method2()、method3()没有method3编译失败。
f.method1(); //在运行的进候看new Zi()这个对象所属的类Zi。 Zi这个对象有没有method1()、method2(),若有运行自己的。
f.method2();
f.method3();
l 下面两种用在面试中:
Ø 在多态中,成员变量(非静态)的特点:
无论编译和运行,都参考左边(引用型变量所属的类)。(当在多态情况下,父类子类出现重名变量的时候)
Ø 在多态中,静态成员函数的特点:
无论编译和运行,都参考做左边。
class Fu
{
static void method4()
{
System.out.println("fumethod_4");
}
}
class Zi extendsFu
{
static void method4()
{
System.out.println("zimethod_4");
}
}
class DuoTaiDemo4
{
public static void main(String[] args)
{
Fu f = new Zi();
}
}
在调用时只要建立的是子类对象(new Zi())的话,这时父和子,这两个对象都会加载到内存。一加载到内存,静态的方法先存在了。这时是不需要对象的。只要想调用,用Fu.method4(),或者Zi.method4()就可以了。但当进行父类引用指向子类对象的时候只要引用还[f.method4();],它找的就是父类中的method4()方法(static方法先加载,不属于对象)。静态方法本身不访问对象特有的数据,所以只用类名调用的话,只看引用变量变量f所属类中的成员,它找的是静态区中的方法(不参考右边的对象)。当method4()一进内存,因为是静态方法,它就已经被绑定了,绑定在了方法所属的类上了(静态绑定)。
方法区有静态区和非静态区。非静态区中有两个引用,一个this,一个super。静态区中只有类名引用(Fu. Zi. 都是static静态的)
4、多态的主板示例
l 主板—扩展(网卡,声卡。通过接口(interface)PCI连接主板)
先有规则了(interface PCI),再各自回家做各自的卡去。
interface PCI //定义接口,规则
{
public void open();
public void close();
}
class MainBoard //类,中间有抽取出来的代码
{
public void run()
{
System.out.println("mainboardrun ");
}
public void usePCI(PCI p)//PCI p = newNetCard()//接口型引用指向自己的子类对象。
{
if(p!=null)
{
p.open();
p.close();
}
}
}
class NetCardimplements PCI //主板扩展1—实现接口
{
public void open()
{
System.out.println("netcardopen");
}
public void close()
{
System.out.println("netcardclose");
method();
}
}
class SoundCardimplements PCI //主板扩展2—实现接口
{
public void open()
{
System.out.println("SoundCardopen");
}
public void close()
{
System.out.println("SoundCardclose");
}
}
class DuoTaiDemo5
{
public static void main(String[] args)
{
MainBoard mb = new MainBoard();
mb.run();
mb.usePCI(new NetCard());
mb.usePCI(new SoundCard());
}
}
l 接口的出现增强了功能的扩展(还降低了耦合性,提供了规则),多态(接口的引用)的应用,提高了程序的扩展性。
4、Object类—equals()、toString()
l 子继承了父之后,父还有没有父类呢?
Ø 类Object是类层次的根类,每个类都使用Object作为超类。Object中定义了所有对象的功能。一个类只要存在,它就是Object的直接或间接子类,能继承它的所有的子类。
Ø Object:是所有对象的直接后者间接父类,传说中的上帝。该类中定义的肯定是所有对象都具备的功能。
Ø Java认为所有对象都具有比较性,都能比较两个对象是否相同(任何对象都有比较相同的功能)。p1.equals(p2);自己已经有了,把别的传进来。(equals(Object obj);多态的应用,equals就是运用==比较地址的方式完成的。)Object类中已经提供了对对象是否相同的比较方法。如果自定义类中也有比较相同的功能,没有必要重新定义。只要沿袭父类中的功能,建立自己特有比较内容即可。这就是覆盖。
l class Demo //extends Object
{
private int num;
Demo(int num)
{
this.num = num;
}
public boolean equals(Object obj) //Objectobj = new Demo();用到了多态,覆盖。类中可建立自己的比较方式,建立比较特征。
{
if(!(obj instanceof Demo)) //如果传入一个Person的对象,Person是不能强制转换为Demo的。所以当用到对象中的特殊句的话,要做判断和转型的动作
return false;
Demo d = (Demo)obj;
return this.num == d.num; // 不能直接写为this.num == obj.num; obj中无num属性
}
public String toString() //对Object类中的toString方法做一个覆盖的操作。建立对象特有的字符串对应内容
{
return "demo:"+num;
}
}
class Person
{
}
class ObjectDemo
{
public static void main(String[] args)
{
Demo d1 = new Demo(4);
System.out.println(d1);//输出语句打印对象时,会自动调用对象的toString方法。打印对象的字符串表现形式。输出结果为: Demo@c74638等
Demo d2 = new Demo(4); //对象不同,数据相同也为真
}
}
l 任意对象都可以做为字符串打印出来。
Ø toString() ;在 API 中是这样描述的。 getClass().getName() + '@' + Integer.toHexString(hashCode()) Ø 类对象都是依靠 class 文件创建的。 Class c = d1.getClass(); getClass() 返回值的类型就 Class 。 Class 是一个特殊的类,这个类的名字就叫做就叫做 Class 。它里面有很多个方法,如 getMethod() ,可以获取你的所有的方法,包括私有的方法。这种方式叫做所编译。如 getName() ,获取类的名字。l 所有对象都有哈希值,hashCode()。所有对象都有内存地址,内存中只要有位置,都有哈希值
5、内部类的访问规则
l 内部类定义在成员位置上
Ø 可以被private static成员修饰符修饰。
Ø 被static修饰的内部类只能访问外部类中的静态成员。
Ø 内部类可以直接访问外部类中的成员,包括私有。内部类之所以可以直接访问外部类中的成员,是因为内部类中持有了一个外部类的引用,格式:外部类名.this(省略了outer.this.)
外部类要访问内部类,必须建立内部类对象。再拿其中的内容。
l 内部类定义在局部位置上
Ø 也可以直接访问外部类中的成员。
Ø 同时可以访问所在局部中的局部变量,但必须是被final修饰的
l 好处:内部类想访问外部类中的成员,不用再创建对象。即便是私有化也可以(不是内部类的话,必须要创建对象才能访问内部类的东西)
l Inner不是独立存在的,它存在于Outer之中,所在在建立Inner对象的时候,你必须标识出Inner属于哪个外部类。如,Outer.Inner in =new Outer().new Inner();外部类在内部类的成员的位置上,我们有可能会将其进行封装(用private)。
l class Outer //外部类
{
private int x = 3;
class Inner //内部类。 Static class Inner当静态内部类
{
//int x = 4;
void function()
{
//int x = 6;
System.out.println("innner:"+Outer.this.x);
}
}
void method()
{
Inner in = new Inner();
in.function();
}
}
class InnerClassDemo //外部其他类
{
public static void main(String[] args)
{
Outer out = new Outer();
out.method();
//Outer.Inner in = new Outer().newInner(); //直接访问内部类中的成员。
//in.function();
}
}
6、静态内部类
l 访问格式:
(1)当内部类定义在外部类的成员位置上,而且非私有,可以在外部其他类中。可以直接建立内部类对象。
格式:
外部类名.内部类名 变量名 = 外部类对象.内部类对象;
Outer.Inner in = new Outer().new Inner();
(2)当内部类在成员位置上,就可以被成员修饰符所修饰 (比如,private:将内部类在外部类中进行封装。static:内部类就具备static的特性。) 。
当内部类被static修饰后,只能直接访问外部类中的static成员。出现了访问局限。(当一个类中几乎都是静态的话,可以用静态内部类,不涉及特有数据,都是共享数据,出现的次数不多。)
l 在外部其他类中,如何直接访问static内部类的非静态成员呢?
newOuter.Inner().function();
l 在外部其他类中,如何直接访问static内部类的静态成员呢?
uter.Inner.function();
l 注意:当内部类中定义了静态成员,该内部类必须是static的。
当外部类中的静态方法访问内部类时,内部类也必须是static的。
7、内部类定义原则
l 当描述事物时,事物的内部还有事物,该事物用内部类来描述。因为内部事务在使用外部事物的内容。(当一个类要直接访问另一个类中的成员的时候,就把这个内部类,直接写在外部类中,将这个内部类用private封装,不对外进行暴露。而对外提供方法,来访问内部事物。)
class Body
{
private class XinZang //心脏在人体里面,它直接访问里面的东西。
{
}
public void show()
{
new XinZang().
}
}
一般内部类中不会被公有修饰的。
l 内部类可以定义在一个类的任意位置上,包括成员也包括局部。当内部类定义在局部时:
(1)不可以被成员修饰符修饰
(2)可以直接访问外部类中的成员,因为还持有外部类中的引用。但是不可以访问它所在的局部中的变量。只能访问被final修饰的局部变量。
class Outer
{
int x = 3;
void method(final int a) //调用方法,内部类不执行,没对象不执行
{
final int y = 4; //内部类只能访问被final修饰的局部变量
class Inner //内部类定义在方法中,不能被private、static修饰(因为它们只修饰成员,可是现在它们在局部,不能再被修饰)
{
void function()
{
System.out.println(y);//System.out.println(Outer.this.x);
}
}
new Inner().function(); //创建内部类的对象
}
}
class InnerClassDemo3
{
public static void main(String[] args)
{
Outer out = new Outer();
out.method(7);
out.method(8);
}
}
8、匿名内部类(其实就是没有名字的内部类)
(1)匿名内部类其实就是内部类的简写格式。
(2)定义匿名内部类的前提:内部类必须是继承一个外部其它类或者实现接口。
(3)匿名内部类的格式: new 父类或者接口(传值 ){定义子类的内容}
(4)其实匿名内部类就是一个匿名子类对象(匿名内部类是一个匿名的内部类对象是把定义类和建立对象封装为一体的这么一个表现形式)。而且这个对象有点胖(可以理解为带内容的对象)。
(5)匿名内部类中定义的方法最好不要超过3个(2个或者1个)。
abstract class AbsDemo
{
abstract void show();
}
class Outer
{
int x = 3;
/*
class Inner extends AbsDemo
{
int num = 90;
void show() //复写父类
{
System.out.println("show:"+num);
}
void abc() //子类特有
{
System.out.println("hehe");
}
}
*/
public void function()
{
//后面跟一个大括号(表示类的大括号),将抽象方法给实现了。这时创建的东西就是一个匿名的内部类。创建对象后,这个对象还带着内容,把AbsDemo()中抽象的方法覆盖掉。这个整体是一个对象,它是AbsDemo的子类对象。如果给匿名对象起一个名字,如,AbsDemo d = newAbsDemo(),就是多态,是父类引用指向子类对象。这时就只能使用父类中的方法:d.show(),而不能使用子类中的方法: d.abc();了, 使用匿名就是为了简化书写,覆盖方法,可是这个时候abc();方法不能用了,就没有意义了。此部分是上面注释部分的简化写法。
new AbsDemo()
{
int num = 9;
void show() //复写父类
{
System.out.println("num==="+num);
}
void abc() //子类特有。
{
System.out.println("haha");
}
}.show(); //匿名对象只能调用一次,不能即调show()又调abc()。想调的话,再建立一个匿名的内部类。
}
class InnerClassDemo4
{
public static voidmain(String[] args)
{
newOuter().function();
}
}
l 匿名内部类,有好处有弊端:
Ø 好处:书写简便
Ø 弊端:直接调用自己的方法还不行。父类中抽相的方法过多不能使用匿名类。你使用了的话,阅读性非常差,你需要将其中所有的方法都给实现一遍。
l 练习:(补足代码,通过匿名内部类)
interface Inter
{
void method();
}
class Test
{
Static Inter function()
{
return new method()
{
public void method()
{
System.out.println(“methodrun”);
}
};
}
}
class InnerClassTest
{
public staic void main (String[] args)
{
Test.function().method(); //Test.function()说明类中有静态成员方法,后有method()方法,说明Test.function()是一个对象。什么对象能调用method(),必然是Inter的对象。
}
}
l 什么时候用匿名内部类?
当你使用的方法的参数是一个接口类型的时候,你去查看一下该接口,发现它里面的接口只有不超过三个,你可以定义一个匿名内部类,将匿名内部类把参数传进去也可以。
l 如果我没有父类的话,我也没有接口,还能写匿名内部类吗?我就想写一个方法让它运行一下,可以吗?
class InnerTest
{
public static void main(String[] args)
{
new Object()
{
public void function()
{
}
}.function();
}