现在的位置: 首页 > 综合 > 正文

Java基础(七)——面向对象_多态、内部类、其它

2017年11月05日 ⁄ 综合 ⁄ 共 8478字 ⁄ 字号 评论关闭

1, 多态(Polymorphism)

        Java引用变量有两个类型:一个是编译时的类型,一个是运行时的类型,编译时的类型由声明该变量时使用的类型决定,运行时的类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就会出现所谓的多态。

 

2, 多态的演示:

class BaseClass
{
	public int book = 6;
	public void base()
	{
		System.out.println("父类的普通方法");
	}
	public void test()
	{
		System.out.println("父类的被覆盖的方法");
	}
}
class  SubClass extends BaseClass
{
	public String book = "Java核心技术";             //重新定义一个book实例属性覆盖父类的book实例属性
	public void test()
	{
		System.out.println("子类的覆盖父类的方法");
	}
	public void sub()
	{
		System.out.println("子类的普通方法");
	}
	public static void main(String[] args) 
	{
		BaseClass bc = new BaseClass();       //编译时类型和运行时类型完全一样,不存在多态
		System.out.println(bc.book);          //输出6
		bc.base();							  //两次都执行BaseClass的方法
		bc.test();
		System.out.println();
		SubClass sc = new SubClass();         //编译时类型和运行时类型完全一样,不存在多态
		System.out.println(sc.book);          //输出“Java核心技术”
		sc.base();							  //调用从父类继承到的base方法
		sc.test();                            //调用当前类的test方法
		System.out.println();
		BaseClass ploymorphicBc = new SubClass();  //编译时和运行时的类型不同,多态
		System.out.println(ploymorphicBc.book);    //输出6,表明访问的是父类属性
		ploymorphicBc.base();                      //调用父类继承到的base方法
		ploymorphicBc.test();                      //调用当前类的test方法
		//ploymorphicBc.sub();       //编译出错,因为变量编译时是BaseClass类型,BaseClass类没有sub方法
	}
}

运行结果:

2.1,说明:子类其实是一种特殊的父类,因此Java允许把一个子类对象直接赋给一个父类引用变量,无需任何类型转换,或者被称为向上转型(upcasting),

向上转型由系统自动完成。

        当把一个子类对象直接赋给父类引用变量,例如上面的BaseClassploymorphicBc = new SubClass();这个ploymorphicBc引用变量的编译时类型是BaseClass,而运行时类型是SubClass,当运行时调用该引用变量的方法时,其方法行为总是像子类方法的行为,而不是像父类的方法行为,这将出现相同类型的变量、执行同一个方法时呈现出不同的行为特征,这就是多态。

2.2,注意:

        引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。因此,编写Java代码时,引用变量只能调用声明该变量时所用类里包含的方法。例如我们通过Object p = new Person()代码定义一个变量p,则这个p只能调用Object类的方法,而不能调用Person类里定义的方法。

        属性不具备多态性。通过引用变量来访问其包含的实例属性时,系统总是试图访问它编译时所定义的属性,而不是它运行时类所定义的属性。

3, 多态的总结

3.1,多态的体现:

  父类引用指向自己的子类对象;

  父类引用也可以接收自己子类的对象;

3.2,多态的前提:

   必须是类与类之间有关系,要么继承,要么实现;

   通常还有一个前提:覆盖;

3.3,多态的好处:

   多态的出现大大提高了程序的扩展性;

3.4,多态的弊端:

   提高了扩展性,但是只能使用父类的引用访问父类中的成员。

3.5,在多态中成员函数的特点:

在编译时期:参阅引用变量所属的类中是否有调用的方法,如果有,编译通过,如果没有则编译失败。

在运行时期:参阅对象所属类中是否有调用的方法。

简单总结就是:在多态中调用成员函数时,编译看左边,运行看右边。

在多态中,成员变量的特点:

无论编译还是运行,都参考左边(引用型变量所属的类)。

在多态中,静态成员函数的特点:

无论编译还是运行,都参考左边。

示例代码:

class Fu
{
	void method1()
	{
		System.out.println("fu method_1");
	}
	void method2()
	{
		System.out.println("fu method_2");
	}
	static void method4()
	{
		System.out.println("fu method_4");
	}
}
class Zi extends Fu
{
	void method1()
	{
		System.out.println("zi method_1");
	}
	void method3()
	{
		System.out.println("zi method_3");
	}
	static void method4()
	{
		System.out.println("zi method_4");
	}
}
class DuoTaiDemo2 
{
	public static void main(String[] args) 
	{
		Fu f = new Zi();

		f.method1();
		f.method2();
		//f.method3();  //编译不通过

		f.method4();    //打印“fu method_4”
		Zi z = new Zi();
		z.method4();
	}
}

4, 引用变量的强制类型转换

        编写Java程序时,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它实际所引用对象确实包含该方法。

如果需要让这个引用变量来调用它运行时类型的方法,则必须把它强制类型转换成运行时类型,强制类型转换需要借助类型转换运算符。

类型转换运算符是(),类型转换运算符的用法如下:(type)variable,这种用法可以将variable变量转换成一个type类型的变量。

        注意:引用类型之间的转换只能把一个父类变量转换成子类类型,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出错。如果试图把一个父类实例转换子类类型,则必须这个对象实际上是子类实例才行(即编译时类型为父类类型,而运行时类型是子类类型),否则将在运行时引发ClassCastException异常。

        当把子类对象赋给父类引用变量时,被称为向上转型(upcasting),这种转型总是可以成功的,这也是从另一个侧面证实了子类是一种特殊的父类,这种转型只是表明这个引用变量的编译类型是父类,但实际执行它的方法时,依然表现出子类对象的行为方式,但把一个父类对象赋给子类引用变量时,就需要进行强制类型转换,而且还可能在运行时产生ClassCastException异常,使用instanceof运算符可以让强制类型转换更安全。

5, instanceof运算符:

        instanceof运算符的前一个操作数通常是一个引用类型的变量,后一个操作数是一个类(也可以是接口,可以把接口理解成一种特殊的类),它用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是,则返回true,否则返回false。

注意:instanceof运算符前面的操作数的编译类型要么与后面的类相同,要么是后面类的父类,否则会引起编译错误。

示例代码:

class InstanceofDemo
{
	public static void main(String[] args)
	{
		Object hello = "Hello";
		//String是Object的子类,所以返回true
		System.out.println("字符串是否是Object类的实例:"+(hello instanceof Object));
		System.out.println("字符串是否是String类的实例:"+(hello instanceof String));
		System.out.println("字符串是否是Math类的实例:"+(hello instanceof Math));
		System.out.println("字符串是否是Comparable接口的实例:"+(hello instanceof Comparable));
		String a = "Hello";
		//string类既不是Math类,也不是Math类的父类,所以下面的编译出错,报错:不可转换的类型
		//System.out.println("字符串是否是Math类的实例:"+(a instanceof Math));
	}
}

运行结果:

instanceof运算符的作用是:在执行强制类型转换之前,首先判断前一个对象是否是后一个类的实例,是否可以成功转换,从而保证代码更加健。

        instanceof和(type)是Java提供的两个相关的运算符,通常先用instanceof判断一个对象是否可以强制类型转换,然后再使用(type)运算符进行强制类型转换,

从而保证程序不会出现错误。

6, 内部类

        大部分时候,我们把类定义在一个独立的程序单元。在某些情况下,我们把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类(有时也叫嵌套类),包含内部类的类也被称为外部类(有时也叫宿主类)。内部类主要有如下作用:

        》》内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许用一个包中的其他类访问该类。

       》》内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以相互访问。但外部类不能访问内部类的实现细节,例如内部类的属性。

      》》匿名内部类适合用于创建那些仅需要一次使用的类。

6.1,非静态内部类和静态内部类

先看示例代码:

class Outer
{
	private int Num=3;
	//定义一个内部类
	class Inner
	{
		void function()
		{
			System.out.println("Inner:"+Num);  //内部类中可以直接访问外部类的内容(Num)
		}
	}
    /*
	class Inner
	{
		int Num=4;
		void function()
		{
			int Num=6;
			System.out.println("Inner:"+Num);  //输出6,以局部变量为主
			System.out.println("Inner:"+this.Num);  //输出4,以当前类成员为主
			System.out.println("Inner:"+Outer.this.Num);  //输出6,此种方式为默认方式
		}
	}
	*/
	void method()
	{
		Inner in=new Inner();
		in.function();
	}
}
class Outer2
{
	private static int Num=3;
	static class Inner2     //静态内部类
	{
		void function2()
		{
			System.out.println("Inner2:"+Num);
		}
	}
}
class Outer3
{
	private static int Num=3;
	static class Inner3     //静态内部类
	{
		static void function()
		{
			System.out.println("Inner2:"+Num);
		}
	}
}
class InnerClassDemo 
{
	public static void main(String[] args) 
	{
		//第一种访问方式,通过外部类访问内部类的成员
		Outer ou=new Outer();
		ou.method();
		//第二种访问方式,直接访问内部类成员
		Outer.Inner in=new Outer().new Inner();
		in.function();
		/*第二种访问方式仅出现在面试中,实际开发很少使用,因为内部类作为外部类的成员,经常会被私有化(private)*/
	
		//在外部类中访问静态内部类的非静态成员(实例看Outer2)
		new Outer2.Inner2().function2();
		//在外部类中访问静态内部类的静态成员(实例看Outer3)
		Outer3.Inner3.function();
	}
}

运行结果:

6.1.1,内部类访问规则:

6.1.1.1,内部类可以直接访问外部类的成员,包括私有;

      内部类可以访问外部类的成员,是因为内部类中持有一个外部类的引用,格式:外部类名.this.

6.1.1.2,外部类要访问内部类,必须建立内部类的对象;

6.1.2,访问格式:

6.1.2.1,当内部类定义在外部类的成员位置上,而且非私有,可以在外部类中直接定义内部类对象,格式:

    外部类名.内部类名变量名=外部类对象.内部类对象

       Outer.Innerin=new Outer().new Inner();

6.1.2.2,当内部类在成员位置上,就可以被成员修饰符所修饰

   比如,private:将内部类在外部类中封装

               static:内部类就具有static特性

               当内部类被static修饰后,只能直接访问外部类中的static成员,出现访问局限

              在外部其他类中,如何直接访问static内部类的非静态成员呢?

               new Outer2.Inner2().function2();

               在外部其他类中,如何直接访问static内部类的静态成员呢?

               Outer3.Inner3.function();

 

       注意:当内部类中定义了静态成员,该内部类必须是静态的。

                   当外部类中的静态方法访问内部类时,内部类也必须是static的

6.1.3,内部类的应用:

当描述事物时,事物的内部还有事物,该事物用内部类来描述,因为内部事物在使用外包园事物的内容。

6.2,局部内部类

内部类定义在局部时

6.2.1,不可以被成员修饰符修饰

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

   但是不可以访问它所在局部中的变量,只能访问被final修饰的局部变量

示例代码:

class Outer
{
	int x=3;
	void method()
	{
		final int y=4;     //在内部类中访问局部变量,需要被声明为最终类型
		class Inner
		{
			void function()
			{
				System.out.println(Outer.this.x);
				System.out.println(y);
			}
		}
		new Inner().function();
	}

	void method2(final int a)
	{
		final int y=4;     //在内部类中访问局部变量,需要被声明为最终类型
		class Inner
		{
			void function()
			{
				System.out.println(a);
			}
		}
		new Inner().function();
	}
}
class InnerClassDemo2 
{
	public static void main(String[] args) 
	{
		new Outer().method();
		Outer out=new Outer();
		out.method2(7);  //这是method2方法进栈,运算完以后出栈,虽a被final修饰,但对下一次调用method2无影响
		out.method2(8);  //method2再一次进栈,此时的a与上一次的a不同
	}
}

运行结果:

6.3,匿名内部类

6.3.1,匿名内部类其实就是内部类的简写格式;

6.3.2,定义匿名内部类的前提:内部类必须是继承一个类或者实现一个接口

6.3.3,匿名内部类的格式:new 父类或者接口(){定义子类的内容};

6.3.4,其实匿名内部类就是一个匿名子类对象,而且这个对象有点胖,可以理解为带内容的对象

6.3.5,匿名内部类中定义的方法最好不超过3个

示例代码:

abstract class AbsDemo
{
	abstract void show();
}
/*
//一般内部类
class Outer
{
	int x=3;

	class Inner extends AbsDemo
	{
		void show()
		{
			System.out.println("show:"+x);
		}
	}

	public void function()
	{
		new Inner().show();
	}
}
*/
//匿名内部类
class Outer
{
	int x=3;

	public void function()
	{
		new AbsDemo()
		{
			void show()
			{
				System.out.println("x==="+x);
			}
			void abc()
			{
				System.out.println("haha");
			}
		}.show();

		new AbsDemo()
		{
			void show()
			{
				System.out.println("x==="+x);
			}
			void abc()
			{
				System.out.println("haha");
			}
		}.abc();
		/*
		AbsDemo a=new AbsDemo()
		{
		void show()
		{
			System.out.println("x==="+x);
		}
		void abc()
		{
			System.out.println("haha");
		}
		};
		a.show();    //可以
		//a.abc();   //编译失败,AbsDemo类中没有定义abc()
        */
	}
}

class InnerClassDemo3 
{
	public static void main(String[] args) 
	{
		new Outer().function(); 
	}
}

运行结果:

总结:今天自学的内容在上面列出,知识点不是很多,代码量稍多,看代码区理解知识点比单纯的看知识点清单效果要好的多。

        1, 理解好多态,用自己的话总结起来,就是“父类引用指向子类对象,编译时看左边(父类),运行时看右边(子类对象)”,

这样就好理解为什么调用在子类有在父类没有的方法时,编译出现错误了。另外注意,属性不具备多态性!

         2, 虽然内部类不常用,但是作为Java的一部分,还是要掌握好,例如静态内部类,还是那句话,静态不能访问非静态。

还有各种内部类的访问方式,这个在面试中出现率很高,掌握是必须的。

        3, 理解匿名内部类,以及匿名内部类的用法,匿名内部类适合用于创建那些仅需要一次使用的类。

还有匿名内部类必须继承一个类或者实现一个接口,

实现多个接口不可以。

        4, 想到了一个问题,关于Object类中的equals方法。这个问题在黑马官方论坛上被多人多次提到,在这里我得加强一下理解,看示例代码:

class TestEqual 
{
	public static void main(String[] args) 
	{
		int it=65;
		float ft=65.0f;
		System.out.println("65和65.0f是否相等? "+(it==ft));   //此处返回true
		char ch='A';
		System.out.println("65和A是否相等? "+(it==ch));      //此处返回true
		String str1=new String("hello");
		String str2=new String("hello");
		System.out.println("str1和str2是否相等? "+(str1==str2));    //此处返回false
		System.out.println("str1和str2是否相等? "+str1.equals(str2));  //此处返回true
	}
}

               总结:

                         当两个基本类型的变量的数值相等,使用==,结果返回true

                         但对于引用类型变量,它们必须指向同一个对象时,结果才返回true

                         String类的equal方法只要求两个字符串的字符序列相同,就返回true

             

         5, Object类提供的toString方法总是返回“该对象实现类的类名+@+hashCode值”。

这个返回值不能真正实现“自我描述”的功能,因此如果想要自定义类能实现“自我描述”的功能,必须重写Object类的toString方法。

2013.03.21


抱歉!评论已关闭.