面向对象基础知识补充

前言

该文补充了关于包的基础知识,潜要的谈了谈单例设计模式.整理了异常处理方面的一些基本知识~是一篇轻量级的复习章节

在Java中,其实我们可以把包看作是一个文件夹,在平时使用文件夹的时候我们知道文件夹里的文件是不可以重名的,Java中的包也一样,在同一个包中不能够出现两个同名的对象,但是在开发中往往我们不可避免的需要起一些重复的名字以此来达到程序见名知意的效果,这时候就可以定义一个包,在两个不同的包中是可以有两个相同名称的类的,并且这两个类之间是互相不干扰的.有两点需要注意:

  • 一旦两个包中出现了同名的类,那么在使用这个类的时候必须明确的点出是那个包中的类, 使用方式: 包名.类名
  • 在Java的访问权限中,如果是默认访问权限,即包权限,那么只有当前包下的类可见,其他包中子类也无法访问

    定义与使用包

    定义:
    直接使用package关键字在整个程序的最上端来定义包名称
    包名称一般采用所在公司的域名的反转(总之要有自己的特征)
    使用包:在程序中如果想要使用某个包中的某个类,就需要先将这个包导入到当前程序当中去,导入包时使用关键字import导入包的操作也需要在整个程序的最开始执行

    类使用public修饰和不使用的区别

  • public class:使用public修饰的类必须与文件名称完全一致,并且一个文件中只能有一个主类;如果希望一个类被其他包进行访问,那么必须定义为public class
  • class: 文件名称可以与类名称不一致,在一个*.java中可以定义多个class,但是这个类不允许被其他包所访问

    四种访问控制权限

    访问控制权限

    在Java中,提供了四种访问控制权限,按照访问控制级别由高到低依次为:
    public>protected>default>private
    在这里主要说一下protected和default控制权限
  • protected访问控制权限称为保护权限:在不同包中,只有子类继承父类才能够访问父类中被protected修饰的属性或者方法
  • default访问控制权限称为默认保护权限:default就是包权限,被此关键字修饰的属性即为包属性,只有在同一个包中的对象能够对其进行访问

    jar命令

    jar本质上也是一种压缩文件,这个文件中全部都是*.class文件.也就是说在一个工程项目中,可能会有成百上千个类需要给用户使用,为了方便起见,就把这些类全部打包在一起交付给用户,而个这压缩包就是一个jar.
    在JDK中实现jar操作只需要一个jar命令就可以达到目的
    jar命令常见参数
  1. “c”:创建新档案
  2. “f”:指定档案文件名
  3. “v”:在标准输出中生成详细输出
    如果想要使用jar文件还需要在classpath中配置相对应的路径

    单例设计模式

    所谓单例设计模式就是指一个类只能有一个实例化对象
    原理: 要想使一个类只能有一个对象,那么这个类的构造方法就不能够是开放的(如果是开放的,那么只要能够调用这个类的构造方法就会产生一个实例化的对象),所以首先需要将构造方法修饰为private;一旦将构造方法私有了便不能够从外部进行对象的实例化了,那么就只能在类的内部进行对象的实例化.在类的内部进行对象的实例化那么产生的这个对象就只能作为类的一个普通属性出现,作为普通属性出现考虑到封装行的原因就需要使用private对其进行封装,然后提供一个getter方法将该对象拿出来.上述就是单例模式实现的一个基本的思路,具体细节以下代码展示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    //饿汉式
    public class Singleton {
    public static void main(String[] args) {
    Single single = Single.getInstance(); //获取的永远都是一个实例化对象
    single.print();
    }
    }

    class Single{

    //在类的内部进行类的实例化
    final static private Single instance = new Single();

    //设置一个无参的构造方法
    private Single(){

    }

    public static Single getInstance() {
    return instance;
    }

    public void print(){
    System.out.println("单例模式的一个方法");
    }

    }

上面这种单例模式成为饿汉式,不论是否调用getInstance方法,只要这个类被加载,那么就会产生一个Single的实例化对象.
还有一种单例模式叫做懒汉式,他在第一次调用对象的时候才会进行对象的实例化工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Singleton {

public static void main(String[] args) {
Single single = Single.getInstance(); //获取的永远都是一个实例化对象
single.print();
}
}

class Single{

//在类的内部进行类的实例化
static private Single instance = null;

//设置一个无参的构造方法
private Single(){

}

public static Single getInstance() {
if(instance == null){
instance = new Single();
}
return instance;
}

public void print(){
System.out.println("单例模式的一个方法");
}

}

懒汉式只有在我们真正需要对象的时候他才会去创建对象

异常与捕获

每一个程序都有可能会出现异常,为了保证在出现异常之后程序仍然能够正常的执行完毕,就需要进行异常处理
在Java的异常体系中有两个大类:Error和Exception,他们都继承自Throwable
下面是异常体系继承的关系图:

Error: 这种错误描述了Java内部运行时错误和资源耗尽的错误,应用程序不会抛出此类异常,这种内部错误一旦出现,除了告知用户并使程序安全结束之外再无其他方法.
Exception: Exception有两个子类,IOException和RuntimeException

  • RuntimeException:由于程序错误导致的异常(即我们自己失误造成的错误)
  • 程序本身没有问题,但由于像I/O错误这类问题导致的异常属于IOException

Java语言规范将Error和RuntimeException派生出来的所有类称之为非受查异常,即编译器不需要强制处置的异常,其余的异常全部称之为受查异常(编译器要求强制处置的异常

异常的影响

异常会导致程序非正常的结束,如果没有合理的处理异常,那么程序会在异常处停止,举例:

1
2
3
System.out.println("1");
System.out.println("10/0");
System.out.println("5");

程序执行后是会打印输出1的,但是不会打印输出5,程序会在第二个输出语句处被强行终止,而不会在进行第三条语句.而如果对这段代码进行了异常处理,那么在出现异常后仍然会进行第三条语句的执行,这就是异常体系带来的效果

异常处理格式

1
2
3
4
5
6
7
try{

}catch(异常类 对象){

}finally{
异常的出口
}

对于以上三个关键字,有如下三种组合方式:

try-catch

第一种处理方式中,是将异常进行捕获并进行处理

1
2
3
4
5
6
7
8
9
10
11
12
public class Throwable {

public static void main(String[] args) {
System.out.println(1);
try{
System.out.println(10/0);
}catch(ArithmeticException e){
System.out.println("此处运算出现了异常");
}
System.out.println(5);
}
}

运行结果:

1
2
3
1
此处运算出现了异常
5

可以看到出现异常的代码部分被我们合理处理了,并且不影响整个程序的进行.但是这样处理我们是不知道造成此种异常的原因的,如果想要知道造成异常的原因是什么就需要使用异常类中的printStackTrace() 方法,来获取具体的异常原因

1
2
//在catch块中添加此方法
e.printStackTrace();

运行后就会打印造成异常的原因(需要了解的是:异常原因的打印不会按照程序顺序进行打印,以为他是独立于main线程之外的一条子线程)

1
2
java.lang.ArithmeticException: / by zero
at io.github.异常示例.Throwable.main(Throwable.java:10)

try-catch-finally(总是执行的finally语句块)

除了上述的处理异常的方式外还可以使用方式进行异常的处理,同上面比较,此中格式的异常处理多出了一个finally语句块,finally语句块内的内容总会执行,所以finally会作为程序统一出口,举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Throwable {

public static int code(){
int a =5;
System.out.println(1); //1.
try{
System.out.println("我是try内的语句,try中还有一个return语句"); //2.
a=8;
return a;
}catch(ArithmeticException e){
System.out.println("此处运算出现了异常"); //3.
}finally{
a=9;
System.out.println(a); //4.
System.out.println("我是finally语句块"); //5.
}
System.out.println(5); //6.
return a;
}

public static void main(String[] args) {
System.out.println(code()); //7.
}
}

上面例子中1,2语句照常打印,没有异议.在2的后面a被重新赋值为8并且有一个return语句紧随其后,按照以往经验这时程序就应该结束了,但事实并非如此.
运行结果:

1
2
3
4
5
1
我是try内的语句,try中还有一个return语句
9
我是finally语句块
8

1,2是像我们预期的那样执行了,但是可以看到2执行过后打印了4和5,最后又返回到try中的return打印了8而不是9
finally和return谁先执行?—>解释: 在try-catch-fianlly结构中,finally中的内容总是会被执行,哪怕前面的语句块中有return语句,那么也会在执行return语句的前一刻跳转到finally语句块中执行finally中的内容,但是在finally中的变量赋值是不会被带回主程序中的,即在return之前的变量改变已经定性不会被finally中的重新赋值,这点需要注意~~

throws关键字

在方法定义时,如果想要告诉方法的使用者该方法可能会出现什么异常便可以使用关键字throws将异常抛出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ThrowsTest {

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

public static void main(String[] args) {
int x=5;
int y=6;

try {
div(x,y);
} catch (Exception e) {
e.printStackTrace();
}

}
}

div这个方法在定义时就告诉了使用者此方法可能会产生异常,使用throws抛出异常的方法在使用的时候必须进行异常的捕获,否则会出现受查异常
主方法也是一个方法,所以主方法也可以使用throws抛出异常,这个时候如果产生了异常则会交给JVM虚拟机去处理

throw关键字

throw是编写在语句当中,人为的手动抛出一个异常.如果现在异常类对象实例化不希望由JVM产生而由用户产生,就可以使用throw来完成
举例:

1
2
3
4
5
6
7
8
9
10
public class ThrowTest {

public static void main(String[] args) {
try{
throw new Exception("抛出来个异常玩玩");
}catch(Exception e){
e.printStackTrace();
}
}
}

运行结果:

1
2
java.lang.Exception: 抛出来个异常玩玩
at io.github.异常示例.ThrowTest.main(ThrowTest.java:9)

可以看到捕获的异常就是我们主动让他抛出的异常

throws和throw的区别

  1. throw用于方法内部,主要表示手工异常抛出。
  2. throws主要在方法声明上使用,明确告诉用户本方法可能产生的异常,同时该方法可能不处理此异常

    异常处理标准格式:

    需求:
  3. 在进行除法计算操作之前打印一行语句”**”。
  4. 如果在除法计算过程中出现错误,则应该将异常返回给调用处。
  5. 不管最终是否有异常产生,都要求打印一行计算结果信息。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class Test {

    //方法定义时指出可能会出现异常
    public static int div(int x,int y) throws Exception{

    int result = 0;
    try{
    result = x/y;
    }catch(Exception e){
    throw new Exception("参数传递错误");//如果出现异常直接抛出
    }finally{
    System.out.println("计算结束");
    }
    return result;
    }

    public static void main(String[] args) {
    int x=6;
    int y = 2;
    try {
    System.out.println(div(x,y));
    } catch (Exception e) { //在调用该方法时候如果出现异常就将其捕获
    e.printStackTrace();
    }
    }
    }

RuntimeException类

特点:使用RuntimeException类定义的异常可以不需要强制性的进行异常处理..
上面提到了一个方法如果可能会出现某个异常我们可以在方法定义的时候就使用throws关键字将异常抛出,方法的调用者在使用该方法的时候,必须使用try…catch进行异常的捕获.但是,如果该方法抛出的异常是由RuntimeException类定义的,那么调用该方法时可以不进行一场的捕获,这就是RuntimeException的一个特点
面试重点,Exception和RuntimeException的区,常见的RuntimeException

  • Exception是RuntimeException的父类,使用Exception定义的异常必须进行异常处理,除他的子类RuntimeException之外
  • 常见的RuntimeException:NullPointerException,ClassCastException

    自定义异常类

    在Java中异常的定义有限,可能在具体场景下没有合适的异常描述,这时候就需要我们自己来定义一个异常来描述程序的错误.
    自定义异常需要继承自Exception或RuntimeException
    示例:定义一个异常,当两个整数相加等于50时抛出异常
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class MyException {

    public static void main(String[] args) throws MyException1 {
    int num=20;
    int num2 = 30;
    if(num+num2 == 50){
    throw new MyException1("50异常");
    }
    }
    }

    class MyException1 extends Exception{

    public MyException1(String msg){
    super(msg);
    }
    }