黑马程序员——Java面向对象(三)之内部类、异常、包等

-----------android培训java培训、java学习型技术博客、期待与您交流!------------

六、对象的其他重要内容

 1.单例设计模式

 1)什么是单例设计模式?

  单例设计模式是指能够确保一个类只有一个实例,并且能够自行向整个系统提供这个实例。

 2)目的:

  保证一个类在内存中只能存在一个对象。

 3)设计思想:

  a)对类的构造函数进行私有化,防止外部程序直接通过构造函数建立对象。

  b)在本类中自定义一个对象,并通过静态修饰保证只能建立一次,即始终保持类在内存中只有一个对象。

  c)为了方便外部程序对自定义对象的访问,给外部程序提供一个静态方法获取本类自定义对象。

 4)两种体现方式:

  a)饿汉式:

   Single类一进内存,就已经创建好了对象。开发一般用饿汉式,因为它安全、简单。

   代码示例:

//饿汉式
class SingleE
{

	static SingleE s= new SingleE;
	private SingleE()
	{}
	public static SingleE getInstance()
	{
		return s;
	}
}

  b)懒汉式:

   Single类进内存,对象还没有存在,只有类中的方法被调用时,才建立对象。

   代码示例:

//懒汉式
class SingleL
{
	private static SingleL s=null;
	private SingleL()
	{}
	
	public static SingleL getInstance()
	{
		if(s==null)
			s=new SingleL();
		return s;
	}
}
  在懒汉式示例中,如果多个程序同时访问容易引发安全问题,因此通过加锁进行同步,确保安全。

  改进的代码示例如下:

//安全懒汉式:通过加锁进行同步
class SingleSL
{
	private static SingleL s=null;
	private SingleL()
	{}
	
	public static SingleL getInstance()
	{
		if(s==null)
			synchronized(SingleSL.class)
			{
				if(s==null)
					s=new SingleL();
			}
		return s;
	}
}
  2.Object类

 1)什么是Object类?

  Object类是所有Java类的祖先,每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。

 2)特点:

  a)在不明确给出超类的情况下,Java在编译时会自动添加Object作为要定义类的超类。

  b)可以使用类型为Object的变量指向任意类型的对象。

  c)Object类有一个默认构造方法pubilc Object(),在构造子类实例时,都会先调用这个默认构造方法。

  d)Object类的变量只能用作各种值的通用持有者。要对他们进行任何专门的操作,都需要知道它们的原始类型并进行类型转换。

 3)重要的方法介绍:

  a)equals(Object  obj)

   比较其他的某个对象与此对象是否相等,比较的是两个对象的内存地址值。

  b)getClass()

   返回一个对象的运行时类。

  c)hashCode()

   返回此对象的哈希值。

  d)toString()

   返回该对象的字符串表示。

  e)wait()

   导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。

  f)notify()

   唤醒在此对象监视器上等待的单个线程。如有多个等待线程,唤醒的是最先等待的线程。

  g)notifyAll()

   唤醒在此对象监视器上等待的所有线程。

 3.内部类

 1)什么是内部类?

  当在一个类内部中定义另一个类,那么类中定义的类称为内部类(嵌套类或内置类)。

 2)访问规则:

  如果将Outer定义为外部类,Inter为内部类,那么定义的格式的方式有如下:

  a)外部其他类直接访问非私有内部类:Outer.Inter oi= new Outer.Inter()

  b)外部类访问内部类:Inter in= new Inter()

  c)外部其他类直接访问静态内部类的非静态成员:new Outer.Inner().非静态成员。

  d)外部直接访问静态内部类的静态成员:Outer.Inner.静态成员。

 3)特点:

  a)内部类可以直接访问外部类中的成员,包括私有的。

  b)外部类要访问内部类,必须建立内部类对象。

  c)内部类可以被成员修饰符所修饰,如被private修饰,修饰后外部不能直接建立内部类对象。当被static所修饰,内部类就具备了静态的特性。

  d)当内部类中定义了static成员,内部类必须是static的。当外部类中的静态方法访问内部类时,内部类也必须是static的。

  e)静态内部类即可以定义在成员位置上,也可以定义在局部位置上。

  f)内部类定义在局部时,不可以被成员修饰符所修饰,可以直接访问外部类中的成员,因为还持有外部类中的引用,但是不可以访问它所在的局部中的变量,只能访问被final修饰的局部变量。

  注:如有外部类中有成员变量x,内部类也有成员变量x,内部类的方法中也有局部变量x,那么:

   当在内部类方法中直接打印x,打印的为内部类局部变量x。

   当在内部类方法中打印this.x,打印的为内部类的成员变量x。

   当在内部类方法中打印Outer.this.x,打印的为外部类的成员变量x。

  结论:如果内部类方法打印变量x,程序默认会从里往外找,先找局部,再找内部类的成员,再到外部类中的成员。如需打印指定的x,需加特定的引用,如this、Outer.this。

 4)问题思考:

  a)为什么内部类可以直接访问外部类中的成员?

   因为内部类中持有了一个外部类的引用,该引用的格式为:外部类名.this。

  b)什么时候定义内部类?

   当描述事物时,事物的内部还是事物,该事物就用内部类描述。

 5)代码示例:

//外部类
class Outer
{
	private int x=3;

	//内部类
	class Inner
	{
		int x=4;
		void function()
		{
			int x=6;
			System.out.println("局部:"+x);//打印局部变量x=6
			System.out.println("内部类:"+this.x);//打印内部类中成员变量x=4
			System.out.println("外部类:"+Outer.this.x);//打印外部类中的成员变量x=3
		}
	}
	void method()
	{
		Inner in =new Inner();//外部类访问内部类需建立内部类对象
		in.function();
	}
}
class InterClassDemo 
{
	public static void main(String[] args) 
	{
		//外部其他类间接访问内部类
//		Outer out= new Outer();
//		out.method();

		Outer.Inner oi=new Outer().new Inner();//外部其他类直接访问内部类
		oi.function();
	}
}
 程序的运行结果如下图:



 4.匿名内部类

 1)什么是匿名内部类?

  内部类的简写格式就称为匿名内部类。

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

 3)定义格式:

   建立匿名内部类对象:new 父类或接口() {定义子类的内容}

   调用一个子类方法:new 父类或接口() {定义子类的内容}.子类方法

 4)利弊:

  好处:简化代码书写。

  弊端:

    a)如果继承的父类或接口有多个抽象方法,使用匿名内部类的阅读性非常的差,因此匿名内部类中定义的子类方法一般最好不要超过2个。

    b)如果给匿名内部类起名字,只能采用多态的形式命名,该方式不能做强转动作,也不能调用子类的特有方法。

  注:匿名内部类不能定义在成员位置上。

 5)代码示例:

//定义一个Inner接口
interface Inner
{
	public abstract void show();
} 


class InnerClassTest 
{
	public static void main(String[] args) 
	{
		//建立一个匿名内部类对象,并调用show()方法
		new Inner()
			{
				//复写show()方法
				public void show()
				{
					System.out.println("Inner run show");
				}
			}.show();
			
	}
}
 程序的运行结果如下图所示:


 小练习:用匿名内部类补足代码

interface Inner
{
	public void show();
	
} 

class Test
{
	//用匿名内部类补足代码
}

class InnerClassTest 
{
	public static void main(String[] args) 
	{
		Test.function().show();
			
	}
}

 分析:

   主要需要理解主函数中语句“Test.function().show();"的意思,分析如下:

   a)Test.function():表示Test类中有一个静态的方法function()。

   b).show():表示function()这个方法运算后返回的是一个对象,而且是一个Inner类型的对象。因为只有Inner类型的对象才可以调用show()方法。

 用匿名内部类的方式补足代码如下:

//定义一个Inner接口
interface Inner
{
	public void show();
	
} 

class Test
{
	//定义一个返回值类型为Inner类型的静态方法
	public static Inner function()
	{
		return new Inner()
		{
			//复写show()方法
			public void show()
			{
				System.out.println("Inner show run");
			}
		};
	}
}

class InnerClassTest 
{
	public static void main(String[] args) 
	{
		Test.function().show();
			
	}
}
 程序运行结果如下图:


 5.异常

 1)如何理解异常?

  当程序运行时出现不正常情况就称为异常,如:文件找不到、网络连接失败、非法参数等。 异常是Java中的重要机制,也使用了面向对象的思想,对其进行了封装,Java通过API中Throwable类的众多子类描述各种不同的异常。所有的Java异常类都是Throwable子类的实例,而异常类中所描述的就是程序中可能出现的错误或者问题。根据不同的情况,进行针对性的封装处理。

 2)异常体系:

   在Java中,所有的异常都可以用Throwable类来描述,它是所有异常的共同父类,后又根据问题的严重程度将Throwable划分为两大类,即Error(错误)和Exception(异常),因此就有了基本的异常体系结构:

  Throwable

    |--Error  //对于严重的问题,java通过Error类进行描述。对Error类一般不编写针对性的代码对其进行处理。

    |--Exception  //对于非严重的,java通过Exception类进行描述。对于Exception可以使用针对性的处理方式进行处理。

      |--RuntimeException //运行时异常,很特殊,抛时不需要声明。

  详细的异常体系划分如下图所示:


  Error(错误):是程序无法处理的问题,表示运行应用程序中出现较严重的情况。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。

  Error表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述 

  Exception(异常):是程序本身可以处理的问题。Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示“JVM 常用操作”引发的问题。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和数组角标越界异常( ArrayIndexOutOfBoundException)。

  注:异常能被程序本身进行处理,而错误不能处理。

 3)Excpetion异常的两大类:

  Exception异常被划分为两大类,即运行时异常(RuntimeException)和非运行时异常(也称编译时异常)。

  运行时异常(RuntimeException):都是RuntimeException类及其子类异常,如NullPointerException(空指针异)、IndexOutOfBoundsException(下标越界异常)等,对于这些程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

  非运行时异常(编译时异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常。

  总结:运行时异常编译器不会检查,即使没有用try—catch语句捕获,也没有用throws声明抛出,编译仍然能通过。而对于编译时异常,如果没有抛也没有用try-catch语句捕获处理,则编译不会通过。

 4)异常处理:

  异常处理语句:  

try
{
	需要被检测的代码;
}
catch (异常类  变量)
{
	异常处理的代码;
}
finally
{
	一定会执行的语句;
}
  try块:try后的一对大括号{}区域称为监控区域,里面存放的是可能发生异常的代码。 其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。如果try块中的代码发生异常,则java会创建相应的异常对象,并将该异常抛出监控区域外。

  catch块:catch后的一对大括号{}存放的是异常处理的代码。如果try块中发生异常,对应的异常将会抛给相应catch块并进行针对性的处理。

  finally块:finally中存放的是一定会被执行的代码。当某些代码一定要被执行的时候,如关闭资源、释放连接等,就需要使用finally来存放该代码,但当遇到以下情况时,fainally不会被执行:

   a)在finally语句块中发生了异常。

   b)在try或catch语句块中用了System.exit(0)退出程序。

   c)程序所在的线程死亡。

   d)关闭cpu。

  异常处理三种结合格式:

			格式1:
					try
					{

					}
					catch ()
					{

					}


			格式2:
					try
					{
						
					}
					finally
					{

					}

			格式3:
					try
					{
						
					}
					catch ()
					{

					}
					finally 
					{

					}

  异常处理的语法规则:

   a)对异常的处理,要么try,要么在函数上throws声明。

   b)一个 try 块可能有多个 catch 块。如果是这样,则执行第一个能够匹配catch块。即Java虚拟机会把实际抛出的异常对象依次和各个catch代码块声明的异常类型匹配,如果异常对象为某个异常类型或其子类的实例,就执行这个catch代码块,不会再执行其他的 catch代码块。

   c)对于多个catch块,如果catch块中的异常存在子父类的关系,则捕获父类异常的catch块应该放在子类的后面,否则捕获子类异常的catch块将永远执行不到。

   d)可嵌套 try-catch-finally 结构。

   e)在 try-catch-finally 结构中,可重新抛出异常。

  异常处理的执行顺序:

   a)当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句。

   b)当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行。

   c)当try语句块中的语句逐一被执行时,如果某一条语句出现异常,则程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句。

   d)当执行try语句块或catch语句块中的代码时,如果碰到return或throw关键字,则在执行该关键字语句之前,会先去执行finally块,且如果finally中没有出现上述关键字语句,则返回try块或catch块中继续执行完相应关键字语句,否则将不再返回执行。

  Throw和Throws的用法:

  Throws使用在函数上,后面跟的是异常类,可以跟多个,用逗号隔开,主要表示出现异常的一种可能性,并不一定会发生这些异常。主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获这个异常。

  Throw使用在函数内,后面跟的是异常对象,表示抛出了异常,执行Throw则一定是抛出了某种异常。Throw是具体向外抛异常的动作,它是抛出一个具体的异常实例。

  获取异常内容的常用方法:

  获取异常内容的方法都继承于父类Throwable,通常用在catch语句块中,通过对异常内容的获取可以更好的发现并完善程序所存在的问题。常用的方法有如下几种:

   a)getCause():返回值类型为Throwable,返回抛出异常的原因。如果cause不存在或未知,则返回null。

   b)getMessage():无返回值类型,返回此Throwable的详细消息字符串,即返回异常的信息。

   c)printStackTrace():无返回值类型,将此Throwable及其追踪输出至标准错误流,即返回异常类名和异常信息及异常出现在程序中的位置。

   d)printStackTrace(PrintStream  s):无返回值类型,将此Throwable及其追踪输出到指定的输出流。通常用该方法将异常内容保存在日志文件中,以便查阅。

   e)toString():返回值类型为String,返回此Throwable的简短描述,即返回异常类名和异常信息。

  注:catch语句块内,需要定义针对性的处理方式,不要简单的定义printStackTrace()或输出语句,也不要不写。

  异常处理的好处:

   a)将问题进行封装。

   b)将正常流程代码与问题处理代码相分离。

  代码示例:系统自动抛出算术条件异常ArithmeticException

class TestException {  
    public static void main(String[] args) {  
        int a = 6;  
        try // try监控区域 
		{  
            for (int b=2;b>-2 ; b--)
            {
				a=a/b;//如果b=0,系统将自动抛出算术条件异常(ArithmeticException)
				System.out.println("b=" + b);  
			}
        }  
        catch (ArithmeticException e) // catch捕捉异常  
		{ 
            System.out.println("程序出现异常,变量b不能为0。");  
        }  
        System.out.println("程序正常结束。");  
    }  
}  
  程序运行的结果如下图:


 5)自定义异常

  自定义异常是Java面向对象的思想的体现,可以让开发者自行针对程序特有的问题进行封装,提高了程序的拓展性和灵活性。自定义异常需继承Exception类或RuntimeException类,让自定义类具备可抛性,可抛性是Throwable体系独有的特点,只有继承Throwable体系的类才能被Throw和Throws关键字所操作

  定义自定义异常的步骤:

   a)定义一个异常类并继承Exception类或RuntimeException类。

   b)在定义的异常类函数中,针对特有问题,手动通过Throwable关键字抛出一个自定义异常对象。

   c)如果抛出的是编译时异常,则在定义的异常类的函数上应进行Throws声明。

  自定义异常代码示例:

//自定义异常类MyException,并继承Exception
class MyException extends Exception
{
	MyException(String message)
	{
		//调用父类的方法
		super(message);
	}
}
class Demo
{
	//对于抛出编译时异常,用Throws进行声明
	int div(int a,int b)throws MyException
	{
		//对函数特有问题进行异常封装
		if(b<0)
			//手动抛出一个自定义异常对象
			throw new MyException("出现异常了");
		return a/b;
	}
}
class ExceptionDemo 
{
	public static void main(String[] args) 
	{
		Demo d=new Demo();
		try
		{
			int x=d.div(4,-2);
			System.out.println("x="+x);
		}
		catch (MyException e)
		{
			//输出异常类和异常信息
			System.out.println(e.toString());
		}
		System.out.println("程序结束");
	}
}

  程序的运行结果如下图:


 6)异常在子父类的体现:

  a)当子类覆盖父类时,如果父类有抛出异常,则子类覆盖时只能抛出父类异常或父类的子异常,或者全部在子类内部进行处理。

  b)如果子类有自己的新异常发生,则只能在内部进行try处理,而不能抛出。

  c)如果父类没有异常抛出 ,则子类在覆盖的时候也不能抛出异常,只能在内部进行try处理。

  代码示例:

//A异常继承Exception
class AException extends Exception
{
}

//B异常继承A异常
class BException extends AException
{
}

//C异常继承Exception
class CException extends Exception
{
}

class Fu
{
	//抛出A异常
	void show()throws AException
	{
		
	}
}

//Zi类继承Fu类
class Zi extends Fu
{
	//只能抛出A异常或B异常
	void show() throws BException
	{
		System.out.println("zi run show");
	}
}
class ExceptionDemo
{
	public static void main(String[] args) 
	{
		try
		{
			new Zi().show();
		}
		catch (Exception e)
		{
			System.out.println(e.toString());
		}
		System.out.println("程序结束");
	}
}
  程序运行结果如下图:


 7)Java常见的异常

  RuntimeException异常:

   a) java.lang.ArrayIndexOutOfBoundsException:数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。

   b)java.lang.ArithmeticException:算术条件异常。如分母为0。

   c)java.lang.NullPointerException:空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。

   d)java.lang.ClassNotFoundException:找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。

   e)java.lang.IllegalArgumentException:非常参数异常。

  IOException异常:

   a)IOException:操作输入流和输出流是可能发生的异常。

   b)FileNotFoundException:文件未找到异常。

  其他异常:

   a)ClassCastException:类型转换异常。

   b)SQLException:数据库操作异常。
   c)IllegalAccessException:不允许访问某类异常。

  注:了解其他更多异常,请参阅API文档。

 6.包(package)

 1)概念:

  包(package)是Java提供的一种区别类的命名空间的机制,是类的组织方式,是一组相关类和接口的集合,它提供了访问权限和命名的管理机制。

 2)包的作用:

  a)把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。

  b)如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。

  c)包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

  总结:Java使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。

 3)包的语法格式:

  package pkg1.pkg2.pkg3. ...

  其中,package为关键字,pkg1、pkg2、pkg3等分别为包名,包名可以存在多级。

  示例:一个person.java文件的内容

package aa.bb.cc

class  Person
{
	...
}
  那么它的路径应该是 aa/bb/cc/Person.java 这样保存的。

 4)Java中的包:

  java.lang:打包基础类。

  java.io:包含输入输出功能的函数。

  开发者可以自己把一组类和接口等打包,并定义自己的package。而且在实际开发中这样做是值得提倡的,当你自己完成类的实现之后,将相关的类分组,可以让其他的编程者更容易地确定哪些类、接口、枚举和注释等是相关的。

 5)创建包:

  创建包需要使用package关键字,并且要起一个符合java规定的合法标示符名称,一般包名的命名规则要求名称的所有字母全部小写,避免与类名、接口名等混淆。创建包名必须要将package声明放在源文件的开头,即源文件的第一行(指的是代码的第一行)。

  如果一个源文件没有使用包声明,那么其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)中。

  通常,一个公司使用它互联网域名的颠倒形式来作为它的包名.例如:互联网域名是apple.com,所有的包名都以com.apple开头。包名中的每一个部分对应一个子目录。

  创建包的代码示例:定义一个Student类,将其放在person包中

package person //创建包名person

class  Student //定义Student类
{
	void method()
	{
		System.out.println("student study!");
	}
}

  当源文件带有包名时,在dos命令行的编译格式为:

   javac –d 指定的目录 源文件名.java

 -d:表示目录的意思。

举例:javac –d e:\person StudentDemo.java

即在e:\person目录下建立一个包目录,目录下存在刚编译完的class文件。

在dos中运行时,运行的命令格式为:

 java 包名.类名

   注:如果包目录没有建立在当前目录,则设置classpath指定包名所在的目录,或者切换到包名所在的目录。

 6)import(导入)关键字

  为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。使用"import"语句可完成此功能。在 java 源文件中 import 语句应位于 package 语句之后,所有类的定义之前,可以没有,也可以有多条,其语法格式为:

   import package1[.package2…].(classname|*);

  其中,*:表示包中所有的类。

  当存在多个包名时,各个包名之间使用“.”分隔,同时包名与类名之间也使用“.”分隔。如果在一个包中,一个类想要使用本包中的另一个类,那么该包名可以省略。

  示例1:导入person包中的Student类

   import person.Student;

  示例2:导入person包中的所有类

   import person.*;

  注:一般不建议使用通配符 * ,因为将不需要使用的类导入后,会占用内存空间。在编写程序时,要使用包中的哪些类,就导入哪些类即可。

 7)包之间的访问:

  a)要访问其他包中的类,需要使用类的全称,即包名.类名。在dos命令行运行时,也需要使用类的全称。

  b)包如果不在当前路径,需要使用classpath设定环境变量,为JVM指明路径。

  c)被访问的包中的类以及类中的成员,都需要被public修饰。

  d)不同包中的子类还可以直接访问父类中被protected权限修饰的成员。

  e)包与包之间可以使用的权限只有两种,public和protected。

  四种访问权限:




  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值