Java学习笔记(持续更新中)

学习后经常温习所学的知识,从而得知新的理解与体会,不也很令人愉悦吗?学习本身就是一个不断累积、不断沉淀、不断总结的过程,本文是博主对自己的学习的记录以及相关知识的总结,如有表述不当的地方,还请谅解与指教。



前言


1 Java基础篇

1.1 面向对象

1.1.1 类与对象

​ 什么是类?类是现实事物的抽象,将现实事物描述成对应的类,其行为封装为方法,属性封装为成员变量。比如说人,人都有哪些属性?姓名,年龄,性别…等等这些都属于人的属性,可以将其封装为类的成员变量。人都有哪些行为?吃饭,睡觉…等等都属于人的行为,可以将其封装为类的成员方法。那么就可以定义一个Person类来描述人这一类事物!

public class Person { 
    private String name;//姓名 
    private int age;//年龄 
    //吃饭 
    public void eat(){ 
        System.out.println("人吃了"); 
    }
    //空参构造 
    public Person() { 
    }
    //带参构造 
    public Person(String name, int age) { 
        this.name = name; 
        this.age = age; 
    }
    //get/set 
    public String getName() { 
    	return name; 
    }
    public void setName(String name) { 
   		this.name = name; 
    }
    public int getAge() { 
    	return age; 
    }
    public void setAge(int age) { 
    	this.age = age; 
    } 
} 

​ 什么是对象?对象是从类中得到的具体实例!在定义Person类并没有一个具体的人出现,而创建对象后,就会出现一个具体的人,比如:

public class Test { 
	public static void main(String[] args) { 
        Person p = new Person(); 
        p.setName("张三"); 
        p.setAge(18); 
        p.eat(); 
	} 
} 

1.1.2 对象内存的解释

​ 对象在内存中的位置 : 对象由new关键字创建,如同数组,实体存在于堆内存中.任何事物均可以定义成类,创建对

象,属于引用类型 . 而对象的引用变量是一个普通变量。存储的值是该对象堆内存中的地址.

引用类型赋值代码解释

在这里插入图片描述

引用类型赋值对应内存图

在这里插入图片描述

1.1.3 匿名对象

匿名对象是指创建对象时,只有创建对象的语句,却没有把对象地址值赋值给某个变量。

public class Person{ 
    public void eat(){ 
    	System.out.println(); 
    } 
}
public class Test { 
    public static void main(String[] args) { 
        //创建一个普通对象 
        Person p = new Person(); 
        //创建一个匿名对象 
        new Person(); 
    } 
} 

匿名对象的特点

1.创建匿名对象直接使用,没有变量名。

new Person().eat() //eat方法被一个没有名字的Person对象调用了。

2.匿名对象在没有指定其引用变量时,只能使用一次。

new Person().eat(); 创建一个匿名对象,调用eat方法

new Person().eat(); 想再次调用eat方法,重新创建了一个匿名对象

3.匿名对象可以作为方法的参数和返回值使用

public class Test { 
    public static void main(String[] args) { 
        //调用method方法 
        Person p = new Person(); 
        method(p); 
        //匿名对象作为方法接收的参数 
        Demo.method(new Person()); 
    }
    public static void method(Person p){ 
    	p.eat(); 
    }
    public static Person getPerson(){ 
        //匿名对象作为方法的返回值。 
        return new Person(); 
    } 
}

1.2 继承

​ 在现实生活中,继承一般指的是子女继承父辈的财产。在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。例如学校中的讲师、助教、班主任都属于员工。这些员工之间会形成一个继承体系,具体如下图所示。
在这里插入图片描述

​ 在Java中,类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称作子类,现有类被称作父类,子类会自动拥有父类所有可继承的属性和方法。

1.2.1 继承的概念:

​ 当要定义一个类(讲师)时,发现已有类(员工)和要定义的类相似,并且要定义的类属于已有类的一种时,可以将要定义类定义为已有类的子类。同时也可以反过来思考,当多个类(讲师,助教,班主任)有共性内容,可以将共性内容向上抽取,抽取到一个新的类(员工)中,那么多个类和新的类形成的关系叫做继承。

1.2.2 继承的格式

通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:

class 父类 { 
	... 
}
class 子类 extends 父类 { 
	... 
} 

继承演示,代码如下:

/*
* 定义员工类Employee,做为父类 
*/ 
class Employee { 
    String name; // 定义name属性 
    // 定义员工的工作方法 
    public void work() { 
    	System.out.println("尽心尽力地工作"); 
	} 
}
/*
* 定义讲师类Teacher 继承 员工类Employee 
*/ 
class Teacher extends Employee { 
    // 定义一个打印name的方法 
    public void printName() { 
    	System.out.println("name=" + name); 
    } 
}
/*
* 定义测试类 
*/ 
public class ExtendDemo01 { 
	public static void main(String[] args) { 
        // 创建一个讲师类对象 
        Teacher t = new Teacher(); 
        // 为该员工类的name属性进行赋值 
        t.name = "小明"; 
        // 调用该员工的printName()方法 
        t.printName(); // name = 小明 
        // 调用Teacher类继承来的work()方法 
        t.work(); // 尽心尽力地工作 
    } 
} 

在上述代码中,Teacher类通过extends关键字继承了Employee类,这样Teacher类便是Employee类的子类。从运行结果不难看出,子类虽然没有定义name属性和work()方法,但是却能访问这两个成员。这就说明,子类在继承父类的时候,会自动拥有父类的成员。

1.2.3 继承的好处

  1. 提高代码的复用性

  2. 类与类之间产生了关系,是多态的前提

1.2.4 继承后的特点

成员变量

​ 当类之间产生了关系后,其中各类中的成员变量,又产生了哪些影响呢?

成员变量不重名的情况

​ 如果子类父类中出现不重名的成员变量,这时的访问是没有影响的。代码如下:

class Fu { 
    // Fu中的成员变量。 
    int num = 5; 
}
class Zi extends Fu { 
    // Zi中的成员变量 
    int num2 = 6; 
    // Zi中的成员方法 
    public void show() { 
        // 访问父类中的num, 
        // 访问子类中的num2 
        System.out.println("Zi num2="+num2); 
    } 
}

class ExtendDemo02 { 
public static void main(String[] args) { 
        // 创建子类对象 
        Zi z = new Zi(); 
        // 调用子类中的show方法 
        z.show(); 
    } 
}
演示结果: 
Fu num = 5 
Zi num2 = 6 

成员变量重名的情况

如果子类父类中出现重名的成员变量,这时的访问是有影响的。代码如下:

class Fu { 
    // Fu中的成员变量。 
    int num = 5; 
}
class Zi extends Fu { 
    // Zi中的成员变量 
    int num = 6; 
    public void show() { 
        // 访问父类中的num 
        System.out.println("Fu num=" + num); 
        // 访问子类中的num 
        System.out.println("Zi num=" + num); 
    } 
}

class ExtendsDemo03 { 
    public static void main(String[] args) { 
        // 创建子类对象 
        Zi z = new Zi(); 
        // 调用子类中的show方法 
        z.show(); 
    } 
}
演示结果: 
Fu num = 6 
Zi num = 6

子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用 super 关键字,修饰父类成员变量,类似于 this 。

使用格式:

super.父类成员变量名

子类方法需要修改,代码如下:

class Zi extends Fu { 
    // Zi中的成员变量 
    int num = 6; 
    public void show() { 
        //访问父类中的num 
        System.out.println("Fu num=" + super.num); 
        //访问子类中的num 
        System.out.println("Zi num=" + this.num); 
    }
}
演示结果: 
Fu num = 5 
Zi num = 6 

小贴士:Fu 类中的成员变量是非私有的,子类中可以直接访问。若Fu 类中的成员变量私有了,子类是不能直接访问的。通常编码时,我们遵循封装的原则,使用private修饰成员变量,那么如何访问父类的私有成员变量呢?对!可以在父类中提供公共的getXxx方法和setXxx方法。

成员方法

成员方法不重名的情况

如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应

的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。代码如下:

class Fu{ 
    public void show(){ 
   		System.out.println("Fu类中的show方法执行"); 
    } 
}
class Zi extends Fu{ 
    public void show2(){ 
    	System.out.println("Zi类中的show2方法执行"); 
    } 
}
public class ExtendsDemo04{ 
    public static void main(String[] args) { 
        Zi z = new Zi(); 
        //子类中没有show方法,但是可以找到父类方法去执行 
        z.show(); 
        z.show2(); 
    } 
}

成员方法重名

如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)。

方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效

果,也称为重写或者复写。声明不变,重新实现

class Fu { 
    public void show() { 
    	System.out.println("Fu show"); 
    } 
}
class Zi extends Fu { 
    //子类重写了父类的show方法 
    public void show() { 
    	System.out.println("Zi show"); 
    } 
}
public class ExtendsDemo05{ 
    public static void main(String[] args) { 
        Zi z = new Zi(); 
        // 子类中有show方法,只执行重写后的show方法 
        z.show(); // Zi show 
    } 
} 

重写的应用

子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。比如新的手机增加来电显示头像的功能,代码如下:

class Phone { 
    public void sendMessage(){ 
    	System.out.println("发短信"); 
    }
    public void call(){ 
    	System.out.println("打电话"); 
    }
    public void showNum(){ 
    	System.out.println("来电显示号码"); 
    }
}
//智能手机类 
class NewPhone extends Phone { 
    //重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能
    public void showNum(){ 
        //调用父类已经存在的功能使用super 
        super.showNum(); 
        //增加自己特有显示姓名和图片功能 
        System.out.println("显示来电姓名"); 
        System.out.println("显示头像"); 
    } 
}
public class ExtendsDemo06 { 
    public static void main(String[] args) { 
        // 创建子类对象 
        NewPhone np = new NewPhone()// 调用父类继承而来的方法 
        np.call(); 
        // 调用子类重写的方法 
        np.showNum(); 
    } 
} 

小贴士:这里重写时,用到super.父类成员方法,表示调用父类的成员方法。

注意事项

  1. 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。

  2. 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。

  3. 私有方法不能被重写(父类私有成员子类是不能继承的)

构造方法

当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?

首先我们要回忆两个事情,构造方法的定义格式和作用。

  1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。

  2. 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造

方法中默认有一个 super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。代码如

class Fu { 
    private int n; 
    Fu(){ 
    System.out.println("Fu()"); 
    } 
}
class Zi extends Fu { 
    Zi(){ 
        // super(),调用父类构造方法 
        super(); 
        System.out.println("Zi()"); 
    } 
}
public class ExtendsDemo07{ 
    public static void main (String args[]){ 
        Zi zi = new Zi(); 
    } 
}
输出结果: 
Fu() 
Zi() 

1.2.5 super 和 this

在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空

间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构

造方法调用时,一定先调用父类的构造方法。理解图解如下:

在这里插入图片描述

super和this的含义

super:代表父类的存储空间标识(可以理解为父亲的引用)。

this :代表当前对象的引用(谁调用就代表谁)。

super和this的用法

  1. 访问成员

this.成员变量 – 本类的

super.成员变量 – 父类的

this.成员方法名() – 本类的

super.成员方法名() – 父类的

  1. 访问构造方法

this(…) – 本类的构造方法

super(…) – 父类的构造方法

子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

注意事项

  1. Java只支持单继承,不支持多继承。

  2. Java支持多层继承(继承体系)

  3. 所有的类都直接或者间接继承了Object类,Object类是所有类的父类。

1.3 抽象类

1.3.1 概述

当编写一个类时,我们往往会为该类定义一些方法,这些方法是用来描述该类的功能具体实现方式,那么这些方法都有具体的方法体。分析事物时,发现了共性内容,就出现向上抽取。会有这样一种特殊情况,就是方法功能声明相同,但方法功能主体不同。那么这时也可以抽取,但只抽取方法声明,不抽取方法主体。那么此方法就是一个抽象方法。如:

描述讲师的行为:工作。

描述助教的行为:工作。

描述班主任的行为:工作。

​ 讲师、助教、班主任之间有共性,可以进行向上抽取。抽取它们的所属共性类型:员工。由于讲师、助教、班主任都具有工作功能,但是他们具体工作内容却不一样。这时在描述员工时,发现了有些功能不能够具体描述,那么,这些不具体的功能,需要在类中标识出来,通过java中的关键字abstract(抽象)修饰。当定义了抽象函数的类也必须被abstract关键字修饰,被abstract关键字修饰的类是抽象类。

定义

抽象方法 :没有方法体的方法。

抽象类:包含抽象方法的类。

使用 abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。定义格式:

修饰符 abstract 返回值类型 方法名 (参数列表);

代码举例:

public abstract void run();

1.3.2 抽象类的定义

如果一个类包含抽象方法,那么该类必须是抽象类。

定义格式:

public abstract class 类名字 { 

} 

代码举例:

public abstract class Animal { 
	public abstract void eat()} 

1.3.3 抽象类的使用

​ 继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。

代码举例:

public class Cat extends Animal { 
    public void eat (){ 
        System.out.println("猫吃猫粮")} 
}
public class CatTest { 
    public static void main(String[] args) { 
        // 创建子类对象 
        Cat c = new Cat(); 
        // 调用run方法 
        c.eat(); 
    } 
}
输出结果: 
猫吃猫粮 

此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法

1.3.4 注意事项

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

    理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

  2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

    理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

  3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

    理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象。

  4. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。

    理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

1.4 接口

1.4.1 概述

接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接

口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8)。

接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并

不是类,而是另外一种引用数据类型。

public class 类名.java–>.class

public interface 接口名.java–>.class

引用数据类型:数组,类,接口。

接口的使用,它不能创建对象,但是可以被实现( implements ,类似于被继承)。一个实现接口的类(可以看做是

接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。

1.4.2 接口基本的实现

实现的概述

类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似

继承,格式相仿,只是关键字不同,实现使用 implements 关键字。

非抽象子类实现接口:

  1. 必须重写接口中所有抽象方法。

  2. 继承了接口的默认方法,即可以直接调用,也可以重写。

实现格式:

class 类名 implements 接口名 { 
// 重写接口中抽象方法【必须】 
// 重写接口中默认方法【可选】 
} 

1.4.3 接口的多实现

​ 一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。

实现格式:

class 类名 [extends 父类名] implements 接口名1,接口名2,接口名3... { 
// 重写接口中抽象方法【必须】 
// 重写接口中默认方法【不重名时可选】 
}

接口中,有多个抽象方法时,实现类必须重写所有抽象方法**。如果抽象方法有重名的,只需要重写一次。

1.4.4 接口和抽象类的区别

相同点

都位于继承的顶端,用于被其他类实现或继承;

都不能直接实例化对象;

都包含抽象方法,其子类都必须覆写这些抽象方法;

区别

抽象类为部分方法提供实现,避免子类重复实现这些方法,提高代码重用性;

接口只能包含抽象方法;

一个类只能继承一个直接父类(可能是抽象类),却可以实现多个接口;(接口弥补了Java的单继承)抽象类为继承体系中的共性内容,接口为继承体系中的扩展功能。

成员区别

抽象类:变量,常量;有构造方法;有抽象方法,也有非抽象方法

接口:常量;抽象方法

关系区别

类与类:继承,单继承

类与接口:实现,可以单实现,也可以多实现

接口与接口:继承,单继承,多继承

设计理念区别

抽象类:对类抽象,包括属性、行为

接口:对行为抽象,主要是行为

1.5 多态

1.5.1 概述

引入

多态是继封装、继承之后,面向对象的第三大特性。

生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。

1.5.2 多态的定义

多态: 是指同一行为,具有多个不同表现形式。

前提【重点】

  1. 继承或者实现【二选一】

  2. 方法的重写【意义体现:不重写,无意义】

  3. 父类引用指向子类对象【格式体现】

多态体现的格式:

父类类型 变量名 = new 子类对象;

变量名.方法名();

父类类型:指子类对象继承的父类类型,或者实现的父接口类型。

public abstract class Animal {
    public abstract void eat(); 
}
class Cat extends Animal { 
    public void eat() { 
        System.out.println("吃鱼");
    } 
}
class Dog extends Animal { 
    public void eat() { 
        System.out.println("吃骨头"); 
    } 
}
public class Test { 
    public static void main(String[] args) { 
        // 多态形式,创建对象 
        Animal a1 = new Cat(); 
        // 调用的是 Cat 的 eat 
        a1.eat(); 
        // 多态形式,创建对象 
        Animal a2 = new Dog(); 
        // 调用的是 Dog 的 eat 
        a2.eat(); 
    }
} 

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写后的方法

1.5.3 多态的好处

实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。

1.5.4 引用类型转型

向上转型

向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。

当父类引用指向一个子类对象时,便是向上转型。

使用格式:

父类类型 变量名 = new 子类类型(); 
Animal a = new Cat(); 

向下转型

向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。

一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。

使用格式:

子类类型 变量名 = (子类类型) 父类变量名; 
Cat c =(Cat) a; 

为什么要转型

​ 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类有而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。

1.6 修饰符

1.6.1 final关键字

​ 子类可以在父类的基础上改写父类内容,比如,方法重写。那么我们能不能随意的继承API中提供的类,改写其内容呢?显然这是不合适的。为了避免这种随意改写的情况,Java提供了 final 关键字,用于修饰不可改变内容。final: 不可改变。可以用于修饰类、方法和变量。

类:被修饰的类,不能被继承。

方法:被修饰的方法,不能被重写。

变量:被修饰的变量,不能被重新赋值。

public final class String 、 public final class Math 、 public final class Scanner均为final类

修饰方法

修饰符 final 返回值类型 方法名(参数列表){ 
//方法体 
} 

重写被 final 修饰的方法,编译时就会报错。

修饰变量

  1. 局部变量——基本类型

基本类型的局部变量,被fifinal修饰后,只能赋值一次,不能再更改。

  1. 局部变量——引用类型

引用类型的局部变量,被fifinal修饰后,只能指向一个对象,地址不能再更改。但是不影响对象内部的成员变量值的修改,

被final修饰的常量名称,一般都有书写规范,所有字母都大写

1.6.2 static关键字

定义:static是静态修饰符,一般修饰成员。被static修饰的成员属于类,不属于单个这个类的某个对象。static修饰的成员被多个对象共享。static修饰的成员属于类,但是会影响每一个对象。被static修饰的成员又叫类成员,不叫对象的成员。

定义和使用格式

1,类变量

当 static 修饰成员变量时,该变量称为类变量。该类的每个对象都共享同一个类变量的值。任何对象都可以更改

该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作。

2,静态方法

当 static 修饰成员方法时,该方法称为类方法 。静态方法在声明中有 static ,建议使用类名来调用,而不需要

创建类的对象。调用方式非常简单。

静态方法调用的注意事项:

静态方法可以直接访问类变量和静态方法。

静态方法不能直接访问普通成员变量或成员方法。成员方法可以直接访问类变量或静态方法。

静态方法中,不能使用this关键字。

静态方法只能访问静态成员。

静态原理图解

static 修饰的内容:

是随着类的加载而加载的,且只加载一次。

存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。

它优先于对象存在,所以,可以被所有对象共享。

在这里插入图片描述

静态代码块:定义在成员位置,使用static修饰的代码块{ }。

位置:类中方法外。

执行:随着类的加载而执行且执行一次,优先构造方法的执行。

格式:

public class Person { 
private String name; 
private int age; 
	//静态代码块 
    static{ 
        System.out.println("静态代码块执行了"); 
    } 
} 

1.7 权限修饰符

1.7.1 概述

在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限,

public:公共的。

protected:受保护的

default:默认的

private:私有的

1.7.2 不同权限的访问能力

在这里插入图片描述

可见,public具有最大权限。private则是最小权限。

编写代码时,如果没有特殊的考虑,建议这样使用权限:

成员变量使用 private ,隐藏细节。

构造方法使用 public ,方便创建对象。

成员方法使用 public ,方便调用方法。

不加权限修饰符,其访问能力与default修饰符相同

1.8 Object类 及常用API

1.8.1 概述

​ java.lang.Object 类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。在对象实例化的时候,最终找的父类就是Object。如果一个类没有特别指定父类, 那么默认则继承自Object类。例如:

public class MyClass /*extends Object*/ { 
// ... 
} 

1.8.2 常用 API

常用api的使用,可查阅相关jdk api手册,这里不再赘述。

1.9 异常

1.9.1 概述

​ 异常,就是不正常的意思。在生活中:医生说,你的身体某个部位有异常,该部位和正常相比有点不同,该部位的功能将受影响.在程序中的意思就是:程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。

​ 在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行.

1.9.2 异常的体系

​ 异常机制其实是帮助我们找到程序中的问题,异常的根类是 java.lang.Throwable ,其下有两个子类:java.lang.Error 与 java.lang.Exception ,平常所说的异常指 java.lang.Exception 。

在这里插入图片描述

Throwable 体系:

Error:严重错误Error,无法通过处理的错误,只能事先避免,好比绝症。

Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。好比

感冒、阑尾炎。

Throwable 方法:

public void printStackTrace() :打印异常的详细信息。

包含了异常的类型*,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace*。

public String getMessage() :获取发生异常的原因。

提示给用户的时候*,*就提示错误原因。

public String toString() :获取异常的类型和异常描述信息(不用)。

异常(Exception)的分类:根据在编译时期还是运行时期去检查异常?

编译时期异常:checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常)

运行时期异常:runtime异常。在运行时期,检查异常.在编译时期,运行异常不会编译器检测(不报错)。(如数学异常)
在这里插入图片描述

1.9.3 异常的处理

Java异常处理的五个关键字:try catch fifinally、throw、throws

抛出异常throw

​ 在编写程序时,我们必须要考虑程序出现问题的情况。比如,在定义方法时,方法需要接受参数。那么,当调用方法使用接受到的参数时,首先需要先对参数数据进行合法的判断,数据若不合法,就应该告诉调用者,传递合法的数据进来。这时需要使用抛出异常的方式来告诉调用者。

在java中,提供了一个throw关键字,它用来抛出一个指定的异常对象。那么,抛出一个异常具体如何操作呢?

  1. 创建一个异常对象。封装一些提示信息(信息可以自己编写)。

  2. 需要将这个异常对象告知给调用者。怎么告知呢?怎么将这个异常对象传递到调用者处呢?通过关键字throw就可以完成。throw 异常对象。

throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。

使用格式:

throw new 异常类名(参数);

throw new NullPointerException("要访问的arr数组不存在"); 
throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围"); 

注意:如果产生了问题,我们就会throw将问题描述类即异常进行抛出,也就是将问题返回给该方法的调用者。

那么对于调用者来说,该怎么处理呢?一种是进行捕获处理,另一种就是继续将问题抛出去,使用throws声

明处理。

Throws 声明异常(将异常抛给方法的调用者)

声明异常:将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理,那么必须通过throws进行声明,让调用者去处理。关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常).

throws用于进行异常类的声明,若该方法可能有多种异常情况产生,那么在throws后面可以写多个异常类,用逗号隔开

public class ThrowsDemo2 { 
    public static void main(String[] args) throws IOException { 
    	read("a.txt"); 
    }
    public static void read(String path)throws FileNotFoundException, IOException { 
        if (!path.equals("a.txt")) {//如果不是 a.txt这个文件 
            // 我假设 如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常 throw 
            throw new FileNotFoundException("文件不存在"); 
        }
        if (!path.equals("b.txt")) { 
        	throw new IOException(); 
        } 
    } 
} 

try-catch-finally 捕获异常

如果异常出现的话,会立刻终止程序,所以我们得处理异常:

  1. throws不处理异常,而是声明抛出,由该方法的调用者来处理。

  2. 在方法中使用try-catch的语句块来处理异常。

try-catch的方式就是捕获异常。

捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。捕获异常语法如下

try{ 
//编写可能会出现异常的代码 
}catch(异常类型 e){ 
//处理异常的代码 
//记录日志/打印异常信息/继续抛出异常 
} 

注意:try和catch都不能单独使用,必须连用。

finally:有一些特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些语句执行不到。而fifinally就是解决这个问题的,在fifinally代码块中存放的代码都是一定会被执行的。什么时候的代码必须最终执行?

当我们在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),我们都得在使用完之后,最终关闭打开的资源。

finally的语法:

try…catch…fifinally:自身需要处理异常,最终还得关闭资源。

注意:fifinally不能单独使用

try catch finally 中 return的执行顺序

  1. 无异常的情况下,如果try 和 finally中都有return 则finally中的return 会覆盖 try中的return,如果只有try中有return,则finally中的操作,不会影响try中return的返回值。即将变量 从局部变量区复制到操作数栈顶,结果就是操作数栈顶的值。
  2. 有异常的情况,如果try和catch中都有return,则catch中的return同样会覆盖try中的return值

1.9.4 异常注意事项

1.运行时异常被抛出可以不处理。即不捕获也不声明抛出。

2.如果父类抛出了多个异常,子类覆盖父类方法时,只能抛出相同的异常或者是他的子集。

3.父类方法没有抛出异常,子类覆盖父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出

4.当多异常处理时,捕获处理,前边的类不能是后边类的父类。

5.在try/catch后可以追加fifinally代码块,其中的代码一定会被执行,通常用于资源回收。

多个异常使用捕获又该如何处理呢?

  1. 多个异常分别处理。

  2. 多个异常一次捕获,多次处理。

  3. 多个异常一次捕获一次处理

格式如下:

try{ 
//编写可能会出现异常的代码 
}catch(异常类型A e){try中出现A类型异常,就用该catch来捕获. 
//处理异常的代码 
//记录日志/打印异常信息/继续抛出异常 
}catch(异常类型B e){ //当try中出现B类型异常,就用该catch来捕获. 
//处理异常的代码 
//记录日志/打印异常信息/继续抛出异常 
} 

注意:这种异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。

1.9.5 自定义异常

异常类如何定义

  1. 自定义一个编译期异常: 自定义类 并继承于 java.lang.Exception 。

  2. 自定义一个运行时期的异常类:自定义类 并继承于 java.lang.RuntimeException 。

实例代码:

// 业务逻辑异常 
public class LoginException extends Exception { 
    /**
    * 空参构造 
    */ 
    public LoginException() { 
    }
    public LoginException(String message) { 
    	super(message); 
    } 
} 

1.10 反射

1.10.1 概述

**反射技术:**其实就是动态加载一个指定的类,并获取该类中的所有的内容。并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个类进行解剖。

反射的好处:大大的增强了程序的扩展性。

反射的基本步骤:

1.10.2 反射的基本步骤

1、获得Cl ass对象,就是获取到指定的名称的字节码文件对象。

2、实例化对象,获得类的属性、方法或构造函数。

3、访问属性、调用方法、调用构造函数创建对象。

1.10.3 获取class对象的三种方式

1:通过每个对象都具备的方法getClass来获取。弊端:必须要创建该类对象,才可以调用getClass方法。

String classname = "com.test.XXX";// 来自配置文件
Class clazz = Class.forName(classname);// 此对象代表Person.class

2:每一个数据类型(基本数据类型和引用数据类型)都有一个静态的属性class。弊端:必须要先明确该类。

前两种方式不利于程序的扩展,因为都需要在程序使用具体的类来完成。

Object obj = new Person();
Class clazz1 = obj.getClass();// 获得对象具体的类型

3:使用的Class类中的方法,静态的forName方法。指定什么类名,就获取什么类字节码文件对象,这种方式的扩展性最强,只要将类名的字符串传入即可。

Class clazz2 = Person.class;  

1.10.4 反射的用法

反射a类的各个组成部分,首先需要获得类的Class对象,获得Class对象的三种方式:

Class.forName(classname) 用于做类加载

obj.getClass() 用于获得对象的类型

类名.class 用于获得指定的类型,传参用

2)、反射类的成员方法:

Class clazz = Person.class;
Method method = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2});
method.invoke();

3)、反射类的构造函数:

Constructor con = clazz.getConstructor(new Class[]{paramClazz1, paramClazz2,...})
con.newInstance(params...)

4)、反射类的属性:

Field field = clazz.getField(fieldName);
field.setAccessible(true);
field.setObject(value);

获取了字节码文件对象后,最终都需要创建指定类的对象:

创建对象的两种方式(其实就是对象在进行实例化时的初始化方式):

1,调用空参数的构造函数:使用了Class类中的newInstance()方法。

2,调用带参数的构造函数:先要获取指定参数列表的构造函数对象,然后通过该构造函数的对象的newInstance(实际参数) 进行对象的初始化。

综上所述,第二种方式,必须要先明确具体的构造函数的参数类型,不便于扩展。所以一般情况下,被反射的类,内部通常都会提供一个公有的空参数的构造函数。

如何生成获取到字节码文件对象的实例对象。

Class clazz = Class.forName("cn.xx.xxx");//类加载
// 直接获得指定的类型
clazz = Person.class;
// 根据对象获得类型
Object obj = new Person("zhangsan", 19);
clazz = obj.getClass();
Object obj = clazz.newInstance();//该实例化对象的方法调用就是指定类中的空参数构造函数,给创建对象进行初始化。当指定类中没有空参数构造函数时,该如何创建该类对象呢?请看method_2();// 直接获得指定的类型
public static void method_2() throws Exception {
    Class clazz = Class.forName("com.xx.xxx");
    //既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。
    //获取一个带参数的构造器。
    Constructor constructor = clazz.getConstructor(String.class,int.class);
    //想要对对象进行初始化,使用构造器的方法newInstance();
    Object obj = constructor.newInstance("zhagnsan",30);
    //获取所有构造器。
    Constructor[] constructors = clazz.getConstructors();//只包含公共的
    constructors = clazz.getDeclaredConstructors();//包含私有的
    for(Constructor con : constructors) {
    System.out.println(con);
    }
}

反射指定类中的方法:

// 1 获取类中所有的方法。
public static void method_1() throws Exception {
    Class clazz = Class.forName("cn.xx.xxx");
    Method[] methods = clazz.getMethods();//获取的是该类中的公有方法和父类中的公有方法。
    methods = clazz.getDeclaredMethods();//获取本类中的方法,包含私有方法。
    for(Method method : methods) {
    	System.out.println(method);
    }
}
//2 获取指定方法;
public static void method_2() throws Exception {
    Class clazz = Class.forName("cn.xx.Person");
    //获取指定名称的方法。
    Method method = clazz.getMethod("show", int.class,String.class);
    //想要运行指定方法,当然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法即可,但是方法运行必须要明确所属的对		象和具体的实际参数。
    Object obj = clazz.newInstance();
    method.invoke(obj, 39,"hehehe");//执行一个方法
}
//3 想要运行私有方法。
public static void method_3() throws Exception {
    Class clazz = Class.forName("cn.xx.Person");
    //想要获取私有方法。必须用getDeclearMethod();
    Method method = clazz.getDeclaredMethod("method", null);
    // 私有方法不能直接访问,因为权限不够。非要访问,可以通过暴力的方式。
    method.setAccessible(true);//一般很少用,因为私有就是隐藏起来,所以尽量不要访问。
}
//4 反射静态方法。
public static void method_4() throws Exception {
    Class clazz = Class.forName("cn.xx.xxx.Person");
    Method method = clazz.getMethod("function",null);
    method.invoke(null,null);
}

猜你喜欢

转载自blog.csdn.net/qq_32300737/article/details/108415599