重温Java基础(五)包-单例设计模式-异常

在Java中,可以将一个大型项目中的类分别独立出来,分门别类地存到功能类似的程序里,保存到不同的包中,再将这些包中的程序文件一起编译执行,这样的程序代码将更易于维护。同时再将类分割开后,对于类的使用也就有了相应的访问权限。

包的定义

在Java程序中的包主要用于将不同功能的文件进行分割。在之前的代码开发中,所有编译后的* class文件都保存在同一个目录中,这样一来就会带来一个问题:如果出现了同名文件,就会发生文件的覆盖问题,因为在同一个目录中不允许有重名文件。而要想解决同名文件冲突的问题,就必须设置不同的目录,因为在不同的目录下可以有重名文件。所谓的包实际上指的就是文件夹。在Java中使用package关键字来定义包,此语句必须写在*java文件的首行。

例如定义包:

package com.app.demo
public class Hello{
    
    
	public static void main(String args[]){
    
    
		...
	}
}

本程序代码的功能就是在屏幕上输出一个字符串信息,但是唯一的区别是将 Hello程序类定义在了个“com.app.demo”的包中(在定义包时出现“.",就表示子目录)
当程序中出现包的定义后,如果在编译程序时,就必须使生成的 Hello. class保存在指定的目录下(此时应该保存在 com\app\demo目录下,与包名称结构相同)。

包的导入

使用包可以将一个完整的程序拆分为不同的文件进行分别保存,这样就会造成一个问题,不同包之间有可能要进行互相访问,此时就需要使用包的导入( Import语句)操作。

Tips:语句的定义顺序。
如果一个程序定义在包中,并且需要引入其他程序类,那么 Import语句必须写在package语句之后,同时 Import应该编写在类的定义之前。

package app;
import test.first.Hello;;
public class Message2 {
    
    
	Hello hello = new Hello();
}

现在就可以完整地总结出关于 public class与 class声明类的如下区别。

  • public class:文件名称必须与类名称保持一致,在一个*.java文件里面只能有一个public class声明,如果一个类需要被不同的包访问,那么一定要定义为 public class;*
  • class:文件名称可以与类名称不一致,并且一个java文件里面可以有多个 class定义,编译后会形成多个 class文件,如果一个类使用的是 class定义,那么表示这个类只能被本包所访问。
    另外,在以后实际的项目开发中,绝大多数情况下都只会在一个*.Java文件里面定义一个类,并且类的声明绝大多数使用的都是 public class完成的。

Tips:使用“包.”与“包.类”性能是一样的。
实际上即便代码中使用了“ Import包.
”的操作,也不会将本包中的所有类都导入进来,类加载时也只是加载所需要的类,不使用的类不会被加载,所以两种写法的性能是一样的。

既然出现了导包操作,那么就必须有一个重要的问题需要注意,有可能同一个代码里面会同时导入不同的包,并且不同的包里面有可能会存在同名类。

//由于类名称冲突,所以为了准确地描述使用的类,必须使用类的完整名称
com.app.util.Message msg = new com.app.util Message();
msg.print();

jar命令

在任何一个项目里一定会存在大量的class文件,如果将这些 class文件直接交给用户使用,就会造成文件过多,并且会导致程序没有结构。所以在交付用户使用之前,会使用iar命令针对* class文件进行压缩,最终交付用户使用的往往是Java归档( Java Archive,jar)文件。
JDK已经为用户默认提供了生成jar文件的工具(jar.exe),直接在命令行方式下就可以看见这个命令的使用.

访问控制权限

对于封装性,实际上之前只讲解了 private,而封装性如果要想讲解完整,必须结合4种访问权限来看,这4种访问权限的定义如图示。

图1

对图可以简单理解为:private只能在一个类中访问;

default只能在一个包中访问;

protected在不同包的子类中访问;

public为所有都可以。
实际上,给出的4种权限中,有3种权限( private、 default、 protected)都是对封装的描述,也就是说面向对象的封装性现在才算是真正讲解完整。从实际的开发使用来讲,几乎不会使用到 default权限,所以真正会使用到的封装概念只有两个权限private、 protected对于访问权限,要把握以下两个基本使用原则即可。
属性声明主要使用 private权限,方法声明主要使用 public权限。

命名规范

命名规范的主要特点就是保证程序中类名称或方法等名称的标记明显一些,可是对于Java而言,有如下一些固定的命名规范还是需要遵守的。

  • 类名称:每一个单词的开头首字母大写,例如:TestDemo;
  • 变量名称:第一个单词的首字母小写,之后每个单词的首字母大写,例如:studentName;
  • 方法名称:第一个单词的首字母小写,之后每个单词的首字母大写,例如:printInfo0;
  • 常量名称:每个字母大写,例如:FLAG;
  • 包名称:所有字母小写,例如:cn.mldnjava.util

单例设计模式

  • 设计模式是在大量的实践中总结和理论化之后优选的代码结构、編程风格、以及解决问题的思考方式。设计模免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱——套路
  • 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法,如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为 private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。

饿汉式

public class SingletonTest1 {
    
    
		public static void main(String arg[]) {
    
    
			Bank bank1 = Bank.getInstance();
			Bank bank2 = Bank.getInstance();
			System.out.println(bank1 == bank2);
		}
}
class Bank{
    
    
	
	//1.私有化构造器
	private Bank()
	{
    
    
		
	}
	//2.内部创建类的对象
	//4.要求此对象也必须声明成静态的
	private static Bank instance = new Bank();
	//3.提供公共的静态方法,返回类的对象
	public static Bank getInstance() {
    
    
		return instance;
	}
}

true

懒汉式

public class SinglelonTest2 {
    
    
			
	public static void main(String arg[]){
    
    
		Order order1 = Order.getInstace();
		Order order2 = Order.getInstace();
		System.out.println(order1 == order2);
	}
		
}
class Order{
    
    
	//1.私有化类的构造器
	private Order(){
    
    
		
	}
	//2.声明当前类的对象,没有初始化
	private static Order instance = null;
	//3.声明返回当前类对象的方法
	public static Order getInstace() {
    
    
		if(instance ==null){
    
    
			instance = new Order();
		}
		return instance;
	}
}

饿汉式:提前写好对象 = new

懒汉式:何时用何时再造对象 =null

单例设计模式:

  1. 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例

  2. 如何实现?
    饿汉式 or 懒汉式

  3. 区分饿汉式和懒汉式

    饿汉式:

    坏处:对象加载时间过长

    好处:饿汉式是线程安全的

    懒汉式:

    好处:延迟对象的创建目前的写法

    坏处:线程不安全—>到多线程内容,再修改(即可能同时进入判断null然后new多个对象)

单例模式的优点:
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。

如Runtime

多例设计模式

单例设计模式只留下一个类的一个实例化对象,而多例设计会定义出多个对象。例如:定义一个表示星期的操作类,这个类的对只能有7个实例化对象(星期一~星期日);定义一个表示性别的类,只能有2个实例化对象(男、女)。定义一个表示颜色基色的操作类,只能有3个实例化对象(红、绿、蓝)。这种情况下,这样的类就不应该由用户无限制地去创造实例化对象,应该只使用有限的几个,这个就属于多例设计。不管是单例设计还是多例设计,有一个核心不可动摇,即构造方法私有化。

Tips:

在JDK1.7之前, switch只能利用int或char进行判断,正因为如果纯粹是数字或字符意义不明确,所以增加了 String的支持。

小结

  1. Java中使用包可以实现多人协作的开发模式,避免类名称重复的麻烦
  2. 在Java中使用 package关键字来将一个类放入一个包中。
  3. 在Java中使用 Import语句,可以导入一个已有的包
  4. 如果在一个程序中导入了不同包的同名类,在使用时一定要明确地写出“包类名称”。
  5. Java中的访问控制权限分为4种:private、 default, protected、 public
  6. 使用jar命令可以将一个包压缩成一个jar文件,供用户使用。
  7. 单例设计模式的核心意义是整个类在运行过程中只能存在一个实例化对象,并且构造方法必须封装。

Tips:变量的来源需要明确

interface A{
    
    
	int x = 0;
}
class B {
    
    
	int x = 1;
}
public class C extends B implements A{
    
    
	public void pX(){
    
    
		//不通过 ,x不明确
		//System.out.println(x);
		System.out.println(super.x);
		System.out.println(A.x);
	}
	public static void main(String[] args) {
    
    
		new C().pX();
	}

}

1
0

异常

public class Except {
    
    
	public static void main(String arg[]) {
    
    
		System.out.println(10/0);
	}
}

Exception in thread “main” java.lang.ArithmeticException: / by zero
at app.Except.main(Except.java:5)

在本程序中产生了数学异常(10/0将产生“ ArithmeticException”异常),由于程序没有进行异的任何处理,所以默认情况下,会进行异常信息打印,同时将终止执行异常产生之后的代码。
通过观察可以发现,如果没有正确地处理异常,程序会出现中断执行的情况。
为了让程序在出现异常后依然可以正常执行完毕,必须引入异常处理语句来完善代码编写。

处理异常

Java针对异常处理提供了3个核心关键字:try,catch,finally

try {

//有可能出现异常的语句

}catch(异常类型 对象)
//异常处理;

}catch(异常类型 对象)
//异常处理

}catch(异常类型 对象){
//异常处理
}…finally{

不管是否出现异常,都执行统一的代码

}

提示:异常的格式组合。
在以上格式中发现 catch与 finally都是可选的。实际上这并不是表示这两个语句可以同时消失,异常格式的组合,往往有3种:try…catch、try…catch…finally、try…finally。

public class Except {
    
    
	public static void main(String arg[]) {
    
    
		try {
    
    
			System.out.println(10/0);
		} catch (ArithmeticException e) {
    
    
			// TODO: handle exception
			e.printStackTrace();
		}
		
	}
}

java.lang.ArithmeticException: / by zero
at app.Except.main(Except.java:6)

所有的异常类中都会提供 printStackTrace0方法,而利用这个方法输出的异常信息,会明确地告诉用户是代码中的第几行出现了异常,这样非常方便用户进行代码的调试。

Tips:

实际上在本程序中只是处理了一个简单的数学计算异常,但是其他异常本程序并不能够正常进行处理。而对于不能够正常进行处理的代码,程序依然会中断执行,而一旦中制执行,最后的输出语句肯定不会执行;但是 finally依然会执行。这一点在随后的代码中可以发现区别。
finally往往是在开发中进行一些资源释放操作的。

异常的处理流程

进行异常处理,以及异常处理对于程序正常执行完整的重要性。但是此时会出现这样一个问题:如果每次处理异常时都要去考虑所有的异常种类,那么直接使用判断来进行处理不是更好吗?所以为了能够正确地处理异常,就必须清楚异常的继承结构以及处理流程为了解释异常的继承结构,首先来观察以下两个异常类的继承关系。

图2

通过这两个异常类可以发现所有的异常类型最高的继承类是 Throwable,并且在 Throwable下有两个子类。

  • Error:指的是JVM错误,这时的程序并没有执行,无法处理;
  • Exception:指的是程序运行中产生的异常,用户可以使用异常处理格式处理。

提示:注意Java中的命名。
可以发现,在Java进行异常类子类命名时都会使用 XxxError或 XxxException的形式,这样也是为了从名称上帮助开发者区分。

(1)当程序在运行的过程中出现了异常,会由JVM自动根据异常的类型实例化一个与之类型匹配的异常类对象(此处用户不用去关心如何实例化对象,由JVM负责处理)
(2)产生异常对象后会判断当前的语句是否存在异常处理,如果现在没有异常处理,就交给JVM进行默认的异常处理,处理的方式:输出异常信息,而后结束程序的调用
(3)如果此时存在异常的捕获操作,那么会先由try语句来捕获产生的异常类实例化对象,再与try语句后的每一个 catch进行比较,如果有符合的捕获类型,则使用当前 catch的语句来进行异常的处理,如果不匹配,则向下继续匹配其他 catch
(4)不管最后异常处理是否能够匹配,都要向后执行,如果此时程序中存在finally语句,就先执行finally中的代码。如果之前已经成功地捕获了异常,就继续执行 finally之后的代码,如果之前没有成功地捕获异常,就将此异常交JVM进行默认处理(输出异常信息,而后结束程序执行)。
整个过程就好比方法传递参数一样,只是根据 catch后面的参数类型进行匹配。既然异常捕获只是一个异常类对象的传递过程,那么依据Java中对象自动向上转型的概念来讲,所有异常类对象都可以向父类对象转型,也就证明所有的异常类对象都可以使用 Exception来接收,这样就可以简单地实现异常处理了。

注意:处理多个异常时,捕获范围小的异常要放在捕获范围大的异常之前处理。
如果说项目代码中既要处理:“ ArithmeticException”异常,也要处理“ Exception异常,那么按照继承的关系来讲, ArithmeticException一定是 Exception的子类,所以在编写异常处理时, Exception的处理一定要写在 ArithmeticException处理之后,否则将出现语法错误。

Exception的捕获范围一定大于 ArithmeticException,所以放在后面的编写的“ catch ArithmeticException e)”语句永远不可能被执行到,那么编译就会出现错误。

throws关键字

throws关键字主要在方法定义上使用,表示此方法中不进行异常的处理,而是交给被调用处处理。

class MyMath{
    
    
    public static int div(int x,int y)throws Exception(
    return x/y;
    )
}

本程序定义了一个除法计算操作,但是在div()方法上使用了 throws关键字进行声明,这样就表示在本方法中所产生的任何异常本方法都可以不用处理(如果在方法中处理也可以),而是直接交给程序的被调用处进行处理。由于div()方法上存在 throws抛出的 Exception异常,则当调用此方法时必须明确地处理可能会出现的异常。

		try {
    
    
			System.out.println(MyMath.div(10,2));
		} catch (ArithmeticException e) {
    
    
			// TODO: handle exception
			e.printStackTrace();
		}

本程序由于MyMath类的div()方法定义上已经明确地抛出了异常,所以调用时必须写上异常处理语句,否则会在编译时出现语法错误。

之前的所有异常类对象都是由JVM自动进行实例化操作的,而现在用户也可以自己手工地抛出一个实例化对象(手工调用异常类的构造方法),就需要通过throw完成。

手工抛出异常

		try {
    
    
			System.out.println(10/0);
			throw new Exception("自己定义的异常");
		} catch (Exception e) {
    
    
			// TODO: handle exception
			e.printStackTrace();
		}

本程序首先实例化了一个 Exception异常类对象,然后利用 throw进行抛出,这时就必须明确进行异常处理。

Tips:请解释 throw和 throws的区别。
throw和 throws都是在异常处理中使用的关键字,这两个关键字的区别如下。

  • throw:指的是在方法中人为抛出一个异常类对象(这个异常类对象可能是自己实例化或者是抛出已存在的);
  • throws:在方法的声明上使用,表示此方法在调用时必须处理异常。
class MyMath{
    
    
	public static int div(int x,int y) throws Exception{
    
    
		System.out.println("开始");
		int result = 0;
		try {
    
    
			result = x/y;
		} catch (Exception e) {
    
    
			// TODO: handle exception
			throw e;
		}finally {
    
    
			System.out.println("--结束--");
		}
		return result;
	}
}
public class Except {
    
    
	public static void main(String arg[]) {
    
    
		try {
    
    
			System.out.println(MyMath.div(10, 0));
		} catch (Exception e) {
    
    
			e.printStackTrace();
		}
		
	}
}

开始
–结束–
java.lang.ArithmeticException: / by zero
at app.MyMath.div(Except.java:8)
at app.Except.main(Except.java:21)

本程序的开发已经满足基本的要求,不管是否出现异常,都会将异常交被给调用处输出,同时每次操作都会指定固定的输出。

RuntimeException 类

在Java中为了方便用户代码的编写,专门提供了一种 RuntimeException类。
这种异常类的最大特征在于:程序在编译时不会强制性地要求用户处理异常,用户可以根据自己的需要有选择性地进行处理,但是如果没有处理又发生了异常了,将交JVM默认处理。也就是说 Runtime Exception的子异常类,可以由用户根据需要有选择性地进行处理

如果要将字符串转换为int数据类型,可以利用 Integer类进行处理,因为在 Integer类中定义了如下方法

字符串转换为int:public static int parseInt(String s) throws Number FormatException

此时 parselnt()方法上抛出了一个 NumberFormatException,而这个异常类就属于 Runtime Exception子类。

所有的 RuntimeException子类对象都可以根据用户的需要进行有选择性的处理,所以调用时不处理。

本程序在没有处理 parseInt()异常的情况下依然实现了正常的编译与运行,但此时一旦出现异常,就将交由JVM进行默认处理。

Tips:请解释一下 RuntimeException和 Exception的区别。

请列举出几个常见的 RuntimeException区别:RuntimeException是 Exception的子类; Exception定义了必须处理的异常,而 Runtime Exception定义的异常可以选择性地进行处理。常见的 RuntimeException:
NumberFormatException, ClassCastException, NullPointerException, ArithmeticException, ArrayIndexOutOfBoundsException。

自定义异常

Java本身已经提供了大量的异常,但是这些异常在实际的工作中往往并不够使用,例如:当你要执行数据增加操作时,有可能会出现一些错误的数据,而这些错误的数据一旦出现就应该抛出异常(如 AddException),但是这样的异常Java并没有,所以就需要由用户自己去开发一个自己的异常类。如果要想实现自定义异常类,只需要继承 Exception(强制性异常处理)或 RuntimeException(选择性异常处理)父类即可

class AddException extends Exception{
    
    
		public AddException (String msg) {
    
    
			super(msg);
		}
}
public class Except {
    
    
	public static void main(String arg[]) {
    
    
		int num =20;
		try {
    
    
			if (num>10) {
    
    
				throw new AddException("数值过大");
			}
		} catch (Exception e) {
    
    
			e.printStackTrace();
		}
		
	}
}

app.AddException: 数值过大
at app.Except.main(Except.java:13)

本程序使用一个自定义的 AddException类继承了 Exception,所以此类为一个异常表示类,因此用户就可以在程序中使用 throw进行异常对象的抛出。

小结

  1. 异常是导致程序中断运行的一种指令流,当异常发生时,如果没有进行良好的处理,则程序将会中断执行。

  2. 异常处理可以使用try… catch进行处理,也可以使用try… catch… finally进行处理,在try语句中捕捉异常,之后在 catch中处理异常, finally作为异常的统一出口,不管是否发生异常都要执行此段代码。

  3. 异常的最大父类是 Throwable,其分为两个子类:Exception、Error。Exception表示程序处理的异常;而 Error表示JVM错误,一般不是由程序开发人员处理的。

  4. 发生异常之后,JVM会自动产生一个异常类的实例化对象,并匹配相应的 catch语句中的异常类型,也可以利用对象的向上转型关系,直接捕获 Exception

  5. throws用在方法声明处,表示本方法不处理异常。

  6. throw表示在方法中手工抛出一个异常。

  7. 自定义异常类时,只需要继承 Exception类或 RuntimeException类。

猜你喜欢

转载自blog.csdn.net/kilotwo/article/details/103746749