本文主要是介绍黑马程序员——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=6System.out.println("内部类:"+this.x);//打印内部类中成员变量x=4System.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.ccclass 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 //创建包名personclass 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。
四种访问权限:
这篇关于黑马程序员——Java面向对象(三)之内部类、异常、包等的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!