黑马程序员——Java基础知识——面向对象(三)

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! 


一、多态

       就是一个抽象的种类可以对应多种属于它这类的具体事物,多态就是事物存在的多种体现形态。例如:动物中有猫、狗,猫这个对象对应的类型是猫类型(猫 x=new 猫());同时猫也是动物中的一种,也可以把猫称为动物,可以这样表示:动物 y=new 猫();那么动物就是猫和狗这些具体事物中向上抽取出来的父类型,父类的引用可以指向子类型对象。

       多态的体现就是父类的引用指向或接收自己的子类对象。例如:动物 y=new 猫(),表示父类类型的变量y引用指向了子类型的对象。类与类之间必须有继承或实现 关系,并且 子类中复写了父类中的方法,这时才可以使用多态。

       多态的出现提高了程序的可扩展性和后期可维护性。例如需要一个父类的不确定的子类对象作为参数传递给一个函数时,定义时可以将这个父类的引用作为形式参数传递给这个函数,而在调用时传入一个子类对象。但在多态中父类引只能访问父类中的成员,也就是父类引用不能调用子类中特有的方法,如代表动物的y不能调用猫对象的抓老鼠的功能。

       父类的引用指向子类对象(例:动物 y=new 猫())的过程中,是猫类型向上转型,提升为动物类型。而当我们需要用父类的引用调用子类的特有功能时,就需要将父类的引用向下转型,转型成子类类型,例如把上面的动物类型向下转换为猫类型(猫 c=(猫)y),这样就可以调用抓老鼠的功能了。但注意不要将父类对象转成子类类型。我们能转换的是父类引用指向了自己的子类对象时,该引用可以向上提升,也可以向下转换,多态自始自终都是子类对象在做着变化。

          下面就以猫狗为例,介绍一下多态的使用。

//定义父类Animal,表示动物
abstract class Animal
{
	abstract void eat();
}

//定义Cat类,表示猫类,继承Animal类
class Cat extends Animal
{
    //复写父类中的方法
	public void eat()
	{
		System.out.println("吃鱼");
	}
	//定义特有抓老鼠方法。
	public void catchMouse()
	{
		System.out.println("抓老鼠");
	}
}

//定义表示狗的Dog类,继承Animal类
class Dog extends Animal
{
	//复写父类中的方法
	public void eat()
	{
		System.out.println("吃骨头");
	}
	//定义特有看家方法
	public void guardHome()
	{
		System.out.println("看家");
	}
}

class DuoTaiDemo 
{
	public static void main(String[] args) 
	{
		//父类Animal引用指向子类Cat类对象
		Animal a=new Cat();
		//调用共性方法
                a.eat();
		Animal b=new Dog();
		//将父类引用向下转换为Dog类型
		Dog d=(Dog)b;
		//调用特有方法。
		 d.guardHome();
		//在调用function方法时,将子类对象作为实际参数传给函数。
		function(new Cat());
	}
	//定义父类引用为形式参数,调用时传入其子类对象
	public static void function(Animal a)
	{	
		a.eat();
	}
	
}
       多态中成员的特点:

     (1)非静态成员函数的特点:

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

         在运行时期:参阅子类对象所属的类中是否有调用的方法。这就是说,子类复写了父类中的函数,在多态中,父类引用调用这个函数时,调用的是子  类中的方法。

        概括:成员函数在调用时,编译看左边,运行看右边。

     (2)成员变量的特点 

           无论编译和运行,都参考左边(引用变量所属的类)。如:多态中的父类引用访问成员变量时,如果父类和子类有同名的成员变量,那么被调用的是父   类中的成员变量。

     (3)静态成员函数的特点

          无论编译和运行,都参考左边。因为静态函数可以通过类名实现调用,在内存中静态函数随着类的加载而存在于静态方法区中,不需要创建对象。

  下面通过一个小程序了解一下多态的应用,如下:

/*
需求:
电脑运行实例,
电脑运行基于主板。
*/

//定义一个接口,表示插口规则
interface PCI
{
	public abstract void open();
	public abstract void close();
}
//定义表示主板的类
class MainBoard
{
	//定义核心功能,运行
	public void run()
	{
		System.out.println("mainboard run ");
	}
	//定义扩展功能,利用PCI
	public void usePCI(PCI p)//PCI p = new NetCard()//接口型引用指向自己的子类对象。
	{
		if(p!=null)
		{
			p.open();
			p.close();
			
		}
	}
}

//定义网卡,实现PCI,符合规则
class NetCard implements PCI
{
	//复写PCI的功能
	public void open()
	{
		System.out.println("netcard open");
	}
	public void close()
	{
		System.out.println("netcard close");
	}
	
}
//定义声卡,实现PCI,符合规则
class SoundCard implements PCI
{
	public void open()
	{
		System.out.println("SoundCard open");
	}
	public void close()
	{
		System.out.println("SoundCard close");
	}
}

class DuoTaiDemo 
{
	public static void main(String[] args) 
	{
		MainBoard mb = new MainBoard();
		//主板运行
		mb.run();
		//将网卡对象作为实际参数传入,实现上网功能。
		mb.usePCI(new NetCard());
		//将声卡对象作为实际参数传入,实现发音功能。
		mb.usePCI(new SoundCard());
		
	}
}


二、Object类

         Object类是类层次结构的根类,每个类都使用Object作为超类(父类),所有对象(包括数组)都实现这个类的方法,所以该类中的定义的是所有对象都具备的功能。

         Object中定义了用于判断对象是否相等的equals(Object obj)方法,注意在建立对象,自定义比较方法时,只要沿袭Object类中的功能,建立自己特有的比较内容,这就是覆盖,注意传入的参数类型一定要是Object类型,否则不是覆盖,而是重载。

     例如:

//定义一个类,系统会默认为Object的子类
class Demo 
{
	private int num;
	Demo(int num)
	{
		this.num = num;
	}
	//复写equals方法,用于定义比较内容
	public boolean equals(Object obj)//Object obj = new Demo();
	{
        //判断传入的实际参数是否是本类类型
		if(!(obj instanceof Demo))
			return false;
		//向下转换
		Demo d = (Demo)obj;

		return this.num == d.num;
	}
}

三、内部类

        当一个类定义在另一个类的里面,里面哪个类就称为内部类,也叫内置类、嵌套类。当描述事物时,有时事物的内部还有事物,这时就可以用内部类描述内部的事物。如人可以定义成一个类,那么人的器官属于人这个类,又有自己特有的功能,这时就可以用内部类描述器官,定义在人这个类中。

        在编译时,如果类中有内部类,生成的.class文件会含有这样的文件:Test$1.class。编译器将会把内部类翻译成用$分割外部类名和内部类名的常规类文件。

        内部类可以直接访问外部类中的成员,包括私有。是因为内部类中持有了一个外部类的引用(外部类名.this)。而外部类要访问内部类,则必须建立内部类的对象。

        外部其他类如何访问内部类,根据内部类的不同定义与在外部类中的不同位置,有下面几种不同的访问格式。

        (1)当内部类定义在外部类的成员位置上,并且非私有,则在外部其他类中,可以直接建立内部类对象。

            格式:外部类名.内部类名 变量名=外部类对象.内部类对象。例如:

<pre name="code" class="java">class Outer
{
   //在外部类的成员位置上定义一个内部类
   class  Inner
   {
       void method()
	  {
		   System.out.println("method run");
          }
  }

}

class InnerDemo
{
	public static void main(String[]args)
	{
		//建立内部类的对象
		Outer.Inner oi=new Outer().new Inner();
		//调用内部类的方法
		oi.method();
	}
}


 

   当内部类在外部类的成员位置上时,可以被成员修饰符修饰。例如:

        private:将内部类在外部类中进行封装。

        static:被static修饰的内部类就具有静态的特性,但只能访问外部类中的静态成员,出现了访问权限。

       在外部其他类中,访问static内部类的非静态成员的格式为:new 外部类名.内部类名().方法名();而访问static内部类的静态成员格式为:外部类名.内部类名.方法名()。例如:

class Outer
{
   //在外部类的成员位置上定义一个静态内部类
   static class Inner
   {
       //定义静态方法
	   static void open()
	   {
              System.out.println("open");   
	   }
	   void method()
	  {
             System.out.println("method run");
          }
   }
}

class InnerDemo
{
	public static void main(String[]args)
	{
		//调用静态内部内部类的非静态方法
		new Outer.Inner().method();
		//调用静态内部内部类的静态方法
	    Outer.Inner.method();
	}
}
       当内部类中定义了静态成员时,该内部类也必须定义成静态内部类。当外部类中的静态方法访问内部类时,内部类必须是静态内部类。在实际的开发中,内部类通常被定义成私有的。
        (2)内部类也可以定义在局部:内部类可以定义在外部类的方法中,创建这个类型的对象时,仅使用一次,那么可在这个方法中定义内部类。定义在局部的内部类不可以被成员修饰符(如public、private、static等)修饰,因为它的作用于被限定在了声明这个局部类的代码块中。定义在局部的内部类可以直接访问外部类中的成员,因为还持有外部类中的引用。要特别注意的是,内部类不能访问它所在的局部中非最终变量,只能访问被final修饰的局部变量。例如:

class Outer
{
	int x = 3;

	void method(final int a)
	{
		//内部类只能访问final修饰的局部变量
		final int y = 4;
        //在局部定义一个内部类		
		class Inner
		{
			void function()
			{
				System.out.println(a);
				System.out.println(y);
			}
		}
	    //在方法内部调用内部类的方法。
		new Inner().function();
		
	}
}


class  InnerClassDemo3
{
	public static void main(String[] args) 
	{
		Outer out = new Outer();
		out.method(7);
		out.method(8);
	}

}
执行结果为:
          

      被传入的参数是一个常量,但当方法被调用完后,传入的常量和方法都从栈内存中释放了,所以当再次调用这个方法时,就可以重新传入被final修饰的变量。

     匿名内部类:

     就是内部类的简写格式。定义一个匿名内部类,这个内部类必须继承一个类或者实现一个接口,格式:new 父类或接口(){定义子类的内容}。可以把匿名内部类理解为一个带内容的匿名子类对象。注意匿名内部类中定义的方法少于3个。

      匿名类的出现简化了代码书写,但匿名内部类只能调用父类或接口中有的方法,不能调用自己的特有方法,不能做强转动作,如果继承的父类或接口中方法较多时,使用内部类阅读性会很差,调用效率低,所以匿名内部类中定义的方法一般少于3个。 

     以一个匿名内部类的小程序,来表明其使用。例如:

abstract class InnerDemo
{
   abstract void show();
}
class Outer
{
   public void function()
	{
	   //定义一个匿名内部类
	   InnerDemo d = new InnerDemo()
		{   //复写父类方法
		    void show()
		    {
		      System.out.println("show");
		    }
	           //定义特有方法 
		   void run()
		   {
		      System.out.println("run");
		   }
		};
                 d.show();
		         d.run();//编译失败;不能调用特有方法。
        }
}


四、异常

        就是程序在运行时出现不正常情况。就像人都会生病、路上有时候会堵车,问题是现实生活中一个具体的事物,可以通过Java类的形式进行描述,并封装成对象。其实就是Java对不正常情况进行描述后的对象体现。在程序运行时各方面都可能会出现各种问题,例如用户输入一些非法参数、设备出现问题、存储空间不足、代码错误无法运行等。例如:

class ExceptionDemo
{
	public static void main(String[]args)
	{
		int x=5;
		int y=0;
	        int z=div(x,y);//由于y=0,无法运算,程序出现异常
	}
	public static int div(int x,int y)
	{
		return x/y;
	}
} 

       Java中问题划分为严重的问题和非严重的问题。对于严重的问题,Java通过Error类进行描述。对Error一般不编写针对性的代码对其进行处理;对于非严重的问题,Java通过Exception类进行描述。对于Exception可以使用针对性的处理方式进行处理。无论Error或者Exception都有一些共性内容。比如:出现的不正常情况的信息,引发的原因等。把共性内容向上抽取,最后构成了Java的异常体系:

        Throwable

            |----Error 

            |----Exception  

     Error和Exception有很多子类,它们的子类名都是以父类名作为后缀。

     异常体系中的所有类以及建立的对象都具备可抛性,也就是说可以被throw和throws操作,只有异常体系具备这个特点。

     Exception的大部分异常在编译时,如果没有处理(没有抛没有try),编译会失败。该异常被标示,代表着可以被处理;而在Exception有一个特殊的子类RuntimeException以及它的子类在编译时不需要处理,编译器不检查。该异常发生时,不处理,让程序停止,这时需要对代码进行修正。

      异常的处理:

      Java中提供了特有的语句对异常进行处理,格式如下:

      try
     {
需要被检测的代码;
    }
    catch(异常类 变量)

   {
处理异常的代码;(处理方式)
   }
  finally
 {
一定要执行的语句;
 }

     finally中的定义的通常是关闭系统资源的语句。如果要定义一些必须要执行的语句,可以用try{}finally{}格式,将语句放在finally代码中。但如果程序前面前面执行到System.exit(0),程序会结束,fianlly不会被执行。

    throw和throws:throw定义在函数内,用与抛出异常对象;throws定义在函数上,用于抛出异常类,可以抛出多个异常类,用逗号隔开。当函数内容有throw抛出异常对象,并未处理,必须要在函数上声明,否则编译失败。但是函数内如果抛出的是RuntimeException异常,函数上可以不用声明。

    当在函数中出现了throw抛出异常对象,要么在内部用try catch语句进行处理,要么在函数上声明让方法调用者去处理。一般情况下函数中出现异常,并未处理,函数上需要声明,通过throws的关键字声明该功能可有会出现的异常类型。而调用者需要进行处理。可以继续抛出或者trycatch处理;如果在函数中抛出RuntimeException异常对象。函数上不用声明,编译通过,而调用者也不需要进行处理。这是因为不需要让调用者处理,当该异常发生时,希望程序停止。因为程序运行时出现了无法继续运算的状况,程序停止后,对代码进行检查修正。

        catch中对捕获到的异常对象的常见操作:

             String getMessage();获取异常的信息

             String  toString(); 获取异常类名和异常信息             

             void   printStackTrace();获取异常类名和异常信息,以及异常出现在程序中的位置。JVM默认的异常处理机制,就是在调用printStackTrace方法,打印异常的堆栈的跟踪信息。
              void   printStackTrace(PrintStream s)//将异常内容保存在日志文件中,以便查阅。

       多异常的处理:声明异常时,建议声明更为具体的异常,这样处理的也可以更具体。对方声明几个异常,就对应有几个catch代码块,如果多个catch代 码块中的异常出现继承关系,把处理父类异常的catch代码块放在最下面。否则会报错,因为其余的catch语句执行不到。

       下面通过一段代码,看一下Java中对异常的处理。如下:


class Demo
{
	int div(int a,int b)throws ArithmeticException,ArrayIndexOutOfBoundsException//声明了该功能中存在的异常。
	{

		int[] arr = new int[a];

		System.out.println(arr[4]);

		return a/b;
	}
}


class  ExceptionDemo2
{
	public static void main(String[] args) //throws Exception
	{
		Demo d = new Demo();
		//检测异常
                try
		{
			int x = d.div(5,0);
			System.out.println("x="+x);
		}
		
		//处理异常
                catch (ArithmeticException e)
		{
			System.out.println(e.toString());
			System.out.println("被零除了!!");

		}
		catch (ArrayIndexOutOfBoundsException e)
		{
			System.out.println(e.toString());
			System.out.println("角标越界啦!!");
		}
		
		
	}
}
  自定义异常:
            因为项目中会出现特有的问题,而这些问题并未被 java 所描述并封装对象。所以对这些特有的问题可以按照 java 中的面向对象思想。将特有的问题,进行自定义的异常封装。定义类继承 Exception 或者 RuntimeException,这样做是为了让该类也具备可抛性和操作异常的共性方法。这就叫做自定义异常。

           在自定义的异常类中,要定义自定义的信息,可以使用父类已经定义好的功能。把异常信息传递给父类的构造函数。因为父类中已经把异常信息的操作都完成了。所以子类只要在构造时,将异常信息通过super语句传递给父类即可,那么就可以通过getMessage()等方法获取自定义的异常信息。在自定义异常时,如果该异常发生时,无法再继续运算,那么可以让自定义的异常类继承RuntimeException。

下面的代码就是表示一个自定义的异常类:

class MyException extends Exception
{
	MyException(String msg)
	{   //把信息传递给父类。
	    super(msg);
	}
}
          Java中的异常处理方式,可以将问题进行封装,将正常流程语句和问题处理代码分离,便于阅读。在处理异常时,如果catch代码块中处理不了异常,但该异常并不属于       该功能出现的异常,可以将异常转换后,再抛出和该功能相关的异常。或者异常可以处理,但需要将异常产生后和本功能相关的问题提供出去,让调用者知道,并处理。也可     以将捕获异常处理后,转换新的异常。例如:用ATM机给别人转账,当ATM机出现故障,可以去其他的地方转,也可以告诉对方转账不成功。还要注意的是当子类覆盖父类方     法涉及到异常时,子类抛出的异常必须是父类的异常的子类或子集,而父类或者接口没有异常抛出时,子类的方法内容中有异常,只能trycatch处理,不能抛。
   下面是一个处理异常的练习,如下:

/*
毕老师用电脑上课。
但电脑可能出现的问题:电脑蓝屏,电脑冒烟。

可是当电脑冒烟发生后,出现讲课进度无法继续。

出现了讲师的问题:课时计划无法完成。
*/

//定义电脑蓝屏异常
class LanPingException extends Exception
{
	LanPingException(String message)
	{
		super(message);
	}
}
//定义电脑蓝屏异常
class MaoYanException extends Exception
{
	MaoYanException(String message)
	{
		super(message);
	}
}

//定义老师讲课异常
class NoPlanException extends Exception
{
	NoPlanException(String msg)
	{
		super(msg);
	}
}

class Computer
{          
	private int state = 3;
	//电脑运行
        public void run()throws LanPingException,MaoYanException
	{
		if(state==2)
			throw new LanPingException("蓝屏了");
		if(state==3)
			throw new MaoYanException("冒烟了");

		System.out.println("电脑运行");
	}
	//电脑重启
        public void reset()
	{
		state = 1;
		System.out.println("电脑重启");
		
	}
}

class Teacher
{
	private String name;
	private Computer comp;

	Teacher(String name)
	{
		this.name = name;
		comp = new Computer();

	}

	public void teach()throws NoPlanException
	{
		try
		{
			comp.run();			
		}
		catch (LanPingException e)
		{
			comp.reset();
		}
		catch (MaoYanException e)
		{
			
			test();
			throw new NoPlanException("课时无法继续"+e.getMessage());
			
		}
		System.out.println("讲课");
	}
	public void test()
	{
		System.out.println("练习");
	}

}

class ExceptionTest 
{
	public static void main(String[] args) 
	{
		Teacher t = new Teacher("毕老师");
		try
		{
			t.teach();
		}
		catch (NoPlanException e)
		{
			System.out.println(e.toString());
			System.out.println("换老师或者放假");
		}
		
	}
}


-------------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值