Java方法、数组、面向对象和异常
- 1.方法
- 1.1 什么是方法?
- 1.2 方法的定义
- 1.3 方法的调用
- 1.4 值传递和引用传递
- 1.5 方法的重载
- 1.6 命令行传参
- 1.7 可变参数
- 1.8 递归
- 2.数组
- 2.1 数组概述
- 2.2 数组声明创建
- 2.3 三种初始化及内存分析和总结
- (1)java内存分析
- (2)三种初始化
- (3)数组的基本特点
- (4)数组边界
- (5)小结
- 2.4 数组使用
- 2.5 多维数组
- 2.6 Arrays类
- 2.7 冒泡排序
- 2.8 稀疏数组
- 3. 面向对象编程
- 3.1.初识面向对象
- 3. 2.方法回顾和加深
- 3.3.对象的创建分析
- (1)创建与初始化对象:
- (2)构造器详解
- (3)创建对象内存分析
- (4)类与对象小结
- 3.4.面向对象三大特性(封装继承多态)
- (1)封装
- (2)继承
- 方法的重写(Override):需要有继承关系,子类重写父类的方法
- (3)多态
- (4)instanceof和类型转换
- (5)static详解
- 静态属性和方法
- 静态代码块
- 静态导入包
- 3.5.抽象类和接口
- (1)抽象类
- (2)接口
- 3.6.内部类
- (1)内部类
- 3.7.异常(```Exception```)
- (1)什么是异常
- 1) Error
- 2)Exception
- 3)Exception和Error的区别
- (2)异常处理机制
- (3)自定义异常
- (4)总结
1.方法
1.1 什么是方法?
Java方法是语句的集合,他们在一起执行一个功能。
- 方法是解决一类问题的步骤的有序组合
- 方法包含于类或者对象中
- 方法在程序中创建,在其他地方进行引用
设计方法的原则:
- 保持方法的原子性,一个方法只完成一个功能,有利于后期的扩展。
1.2 方法的定义
方法包含一个方法头和一个方法体,以下内容是一个方法的所有部分:
修饰符 返回值类型 方法名(参数类型 参数名){
…
方法体
…
return 返回值; //return也是终止方法的语句
}
eg:public static void main(String []args){}
- 修饰符:可选的,告诉编译器方法如何调用,定义了该方法的访问类型;
- 返回值类型:void(没有返回值)或者其他数据类型;
- 方法名:方法的实际名称,方法名和参数列表共同构成方法签名;
- 参数类型:参数像是一个占位符。当方法被调用时,传递值给参数,这个值称为实参或者变量。参数列表指的是方法的参数类型、顺序和参数的个数。参数是可选的,方法可以不包含任何参数。
- 形式参数(形参):再方法被调用时用于接收外界传入的数据
- 实际参数(实参):调用方法时实际传给方法的数据
- 方法体:方法体中包含具体的语句,定义方法的功能。
1.3 方法的调用
调用方法: 对象名.方法名(实参列表)
Java支持两种调用方法的方式,根据方法是否返回值来选择
- 当方法返回一个值的时候,方法调用通常被当做一个值,例如:
int larger = max(23,43);
- 当方法的返回类型是void,即没有返回值时,方法调用是一条语句;
System.out.println("Hello,neo!");
public class Method{
public static void main(String []args){
int a = max(23,45);
System.out.print(a);
}
public static int max(int num1,int num2){
return num1>num2 ? num1 :num2;
}
}
1.4 值传递和引用传递
**值传递(pass by value):**在调用函数时,将实际参数复制一份传递到函数中,在函数中对参数进行修改,不会影响到原来的实际参数;
引用传递(pass by reference):在调用函数时,将实际参数的地址直接传递到函数中。在函数中对参数进行的修改,会影响到实际参数;
Java传递数值参数,使用的是值传递,传递对象和动态创建String时,使用的是值传递。
值传递举例:
public class MyScanner {
public static void main(String[] args) {
// 基本数据类型
int i = 10;
add(i);
System.out.println("i = " + i);
System.out.println("==================");
// 引用数据类型中的String
String str = "你好";
append(str);
System.out.println("str : "+str);
System.out.println("==================");
String string = new String("Hello");
pass(string);
System.out.println("str:"+string);
}
public static void add(int num) {
num ++;
System.out.println("num = " + num);
}
public static void append(String str){
str += "=====";
System.out.println("string : " + str);
}
public static void pass(String str){
str = new String("world");
System.out.println("string:"+str);
}
}
//add函数中传入数值参数i,add 方法中对i进行操作,不会影响方法外i的值。
//append 函数中传入引用类型String的参数str,但是在方法中对str进行操作,也不会改变str自身在方法外的值。
// 输出结果
// num = 20
// i = 10
//==================
//string : 你好=====
//str : 你好
// ==================
// string:world
// str:Hello
引用传递举例
public class MyScanner {
public static void main(String[] args) {
// 引用数据类型中的String
StringBuilder string = new StringBuilder("Hello");
pass1(string);
System.out.println("str:"+string);
System.out.println("==================");
pass2(string);
System.out.println("str:"+string);
}
public static void pass1(StringBuilder str){
str = new StringBuilder("world");
System.out.println("string1:"+str);
}
public static void pass2(StringBuilder str){
str.append("!!!!");
System.out.println("string2:"+str);
}
}
// string1:world
// str:Hello
// ==================
// string2:Hello!!!!
// str:Hello!!!!
1.5 方法的重载
重载就是在 一个类中,函数名称相同但是形参不同的函数。
方法重载的规则:
- 方法名称必须相同;
- 参数列表必须不同(个数不同、类型不同或者参数排列顺序不同等);
- 方法的返回类型可以相同也可以不相同
- 仅仅返回类型不同不足以成为方法的重载
实现理论:
- 方法名称相同时,编译器会根据调用方法的参数个数、参数类型等去逐个匹配,以选择对应的方法,如果匹配失败,则编译器报错。
public class Method{
public static void main(String []args){
int a = max(23,45);
double d = max(23.1,45.0);
System.out.println(a); //45
System.out.println(d); //45.0
}
public static int max(int num1,int num2){
return num1>num2 ? num1 :num2;
}
public static double max(double num1,double num2){
return num1>num2 ? num1 :num2;
}
}
1.6 命令行传参
在命令行中编译完成,运行的时候可以传入对应的参数
(以下代码在idea中点击绿色三角执行会直接结束)
public class Method1 {
public static void main(String []args){
for (int i = 0; i <args.length ; i++) {
// 数组长度 args.length
System.out.println("args["+i+"]:"+args[i]);
}
}
}
在命令行中先编译,再执行,传入参数,结果如下:
( 注意: 如果在命令行中进行运行的文件在包中,编译的时候可以在包目录下,但是执行的时候要在包目录之外,否则会找不到)
1.7 可变参数
- JDK1.5开始,Java支持传递同类型的可变参数给一个方法。
- 在方法的声明中,在指定参数类型后面加一个省略号(…).
- 一个方法中只能指定一个可变参数,他必须是方法的最后一个参数。任何普通的参数必须在他之前进行声明。
package com.dande.base;
import java.util.Scanner;
public class VariableParam {
public static void main(String[] args) {
double max1 = getMax(new double[] {12.3,45.6,67.0});
double max2 = getMax(13.4,56.7,78,98.2);
System.out.println(max1);
System.out.println(max2);
}
public static double getMax(double ...numbers){
if(numbers.length == 0){
System.out.println("There is no number!");
return 0.0;
}
double result = numbers[0];
for (int i = 0; i <numbers.length ; i++) {
if (numbers[i] > result){
result = numbers[i];
}
}
return result;
}
}
1.8 递归
递归就是A方法调用A方法,自己调用自己!!
- 利用递归可以使用简单的程序来解决一些复杂的问题,通常将一个大型复杂问题的层层转换为一个与原问题相似的规模较小的问题来求解。
- 递归策略只需要少量的过程就可以描述出解题过程所需要的多次重复计算,大大的减少了程序的代码量。
- 递归的能力在于用有限的语句来定义对象的无限集合。
递归结构包括两部分:
- 递归头: 什么时候不调用自身方法(结束递归调用的条件)。如果没有递归头,将陷入死循环。
- 递归体: 什么时候需要调用自身方法。
递归实现小数字的阶乘
package com.dande.base;
public class Factorial {
public static void main(String[] args) {
Factorial fa = new Factorial();
int result = fa.factorial(5);
System.out.println(result);
}
public int factorial(int n){
if(n==1){
return 1;
}else{
return n*factorial(n-1);
}
}
}
注意:
- 尽量避免使用递归;
- 在使用递归时,如果数据量较大,自身调用次数太多会造成栈溢出(Stack Overflow),要使用其他的算法;
- 重要的是理解递归思想!
计算器练习
import java.util.Scanner;
public class Calculator {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while(true) {
System.out.print("请输入第一个操作数:");
double num1 = scanner.nextDouble();
System.out.print("请输入运算符(+ - * /):");
String operator = scanner.next();
System.out.print("请输入第二个操作数:");
double num2 = scanner.nextDouble();
double result = 0;
switch(operator) {
case "+":
result = num1 + num2;
break;
case "-":
result = num1 - num2;
break;
case "*":
result = num1 * num2;
break;
case "/":
result = num1 / num2;
break;
default:
System.out.println("无效的运算符");
break;
}
System.out.println(num1 + " " + operator + " " + num2 + " = " + result);
System.out.print("继续计算请按Y,退出请按其他键:");
String input = scanner.next();
if(!input.equals("Y")) {
break;
}
}
scanner.close();
}
}
package com.dande.base;
import java.util.Scanner;
public class Calculator {
public static void main(String[] args) {
// 写一个计算器,实现加减乘除功能,并且能够循环接收新的数据,通过用户交互实现
/**
* 思路:
* 1.写四个方法,加减乘除
* 2.利用循环加switch进行用户交互
* 3.传递需要操作的操作数
* 4.输出结果
*/
System.out.println("请输入数据和运算符+,-,*,/,用,分隔:");
// double nums[] = new double[100];
Scanner scanner = new Scanner(System.in);
String temp1 = scanner.nextLine();
String[] s = temp1.split(",");
double[] nums = new double[s.length-1];
for (int i = 0; i <s.length-1 ; i++) {
nums[i] = Double.parseDouble(s[i]);
}
String operator = s[s.length-1];
switch (operator){
case "+":
show(nums);
add(nums);
break;
case "-":
show(nums);
reduce(nums);
break;
case "*":
show(nums);
multi(nums);
break;
case "/":
show(nums);
divide(nums);
break;
}
scanner.close();
}
public static void show(double...nums){
System.out.print("您输入的数据为:");
for (double i :nums) {
System.out.print(i+"\t");
}
System.out.println();
}
public static void add(double ...nums){
if (nums.length==0){
System.out.println("您输入了0个数字,结果为0!");
}
double result = 0;
for (int i = 0; i <nums.length ; i++) {
result +=nums[i];
}
System.out.println("他们的求和结果为:"+result);
}
public static void reduce(double ...nums){
if (nums.length==0){
System.out.println("您输入了0个数字,结果为0!");
}
double result = nums[0];
for (int i = 1; i <nums.length ; i++) {
result -=nums[i];
}
System.out.println("他们的作差结果为:"+result);
}
public static void multi(double ...nums){
if (nums.length==0){
System.out.println("您输入了0个数字,结果为0!");
}
double result = nums[0];
for (int i = 1; i <nums.length ; i++) {
result *=nums[i];
}
System.out.println("他们的求积结果为:"+result);
}
public static void divide(double ...nums){
if (nums.length==0){
System.out.println("您输入了0个数字,结果为0!");
}
double result = nums[0];
for (int i = 1; i <nums.length ; i++) {
if(nums[i] !=0){
result /=nums[i];
}else{
System.out.println("除数不能为0");
return ;
}
}
System.out.println("他们的相除结果为:"+result);
}
}
2.数组
2.1 数组概述
数组的定义
- 数组是相同数据类型的有序集合;
- 数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成;
- 数组中的每一个数据乘坐一个数组元素,每个数组元素可以通过一个下标来访问他们。
2.2 数组声明创建
声明数组变量:
dataType[] arrayRefVar;
//首选方法(变量类型 变量名 = 变量的值;)- 或者
dataType arrayRefVar[];
//效果相同但是不是首选
创建数组:
- JAVA语言使用new操作符来创建数组,语法如下:
dataType[] arrayRefVar = new dataType[arraySize];
数组元素访问:
- 数组元素通过索引访问,数组索引从0开始
获取数组长度:
arrays.length;
package com.dande.base;
public class ArrayDemo1 {
public static void main(String[] args) {
// 1.声明一个数组(在栈中开辟空间
int[] nums;
// 2.创建一个数组(动态分配内存空间,堆中开辟空间)
nums = new int[10];
// 3.给数组元素赋值,访问数组元素nums[i[
// 单独给每一个数组元素赋值,nums.length为数组长度
nums[0] =1;
nums[1] =2;
nums[2] =3;
nums[3] =4;
nums[4] =5;
// for (int i = 0; i <nums.length ; i++) {
// nums[i] = i;
// }
// 声明创建赋值
int[] nums2 = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 4.访问数组元素 nums[i]
int result = 0;
for (int i = 0; i <nums.length ; i++) {
result +=nums[i];
}
System.out.println(result);
}
}
2.3 三种初始化及内存分析和总结
(1)java内存分析
(2)三种初始化
- 静态初始化:
int [] a = {1,2,3};
Man[] mans = {new Man(1,1),new Man(2,2)};
- 动态初始化
int[] a = new int[2];
a[0] = 1;
a[1] = 2;
- 数组的默认初始化
- 数组是引用类型,它的元素相当于类的实例变量,因此数组一旦分配空间,其中的每个元素也被按照实例变量统一的方式被隐式初始化,赋予对应数据类型的默认值。
package com.dande.base;
public class ArrayDemo1 {
public static void main(String[] args) {
//静态初始化:创建+赋值
int[] a = {1,2,3,4,5};
System.out.println(a[0]); //1
// 动态初始化:包含默认初始化
int[] b = new int[10];
b[0] = 1;
System.out.println(b[0]); //1
System.out.println(b[1]); //0
System.out.println(b[2]); //0
}
}
(3)数组的基本特点
- 数组长度是确定的,数组一旦被创建,其大小就不可以再改变;
- 数组元素类型必须是确定的,不允许出现混合类型;
- 数组中的元素可以是任何数据类型,包括基本类型和引用类型;
- 数组变量属引用类型,数组也可以看做是对象,数组中的每个元素相当于该对象的成员变量。数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的。
(4)数组边界
- 数组下标的合法区间是:[0,length-1],如果越界就会报错;
package com.dande.base;
public class ArrayDemo1 {
public static void main(String[] args) {
// 数组下标
int[] a = new int[2];
System.out.println(a[2]); //java.lang.ArrayIndexOutOfBoundsException
}
}
ArrayIndexOutOfBoundsException:数组下标越界!!
(5)小结
- 数组是相同类型的数据的有序集合(数据类型可以为任意类型)
- 数组也是对象,数组元素相当于对象的变量成员;
- 数组长度是确定的,不可变的。如果越界,则报错:ArrayIndexOutOfBoundsException。
2.4 数组使用
- for循环
- 增强for循环(for-each循环)
- 数组作为方法入参
- 数组作为返回值
package com.dande.base;
public class ArrayDemo1 {
public static void main(String[] args) {
// - for循环
// - 增强for循环(for-each循环)
// - 数组作为方法入参
// - 数组作为返回值
int[] arrays = {1,2,3,4,5,6,7};
int sum = 0;
int max = 0;
//for循环
for (int i = 0; i <arrays.length ; i++) {
sum +=arrays[i];
max = max > arrays[i] ? max : arrays[i];
}
System.out.println("The sum is "+sum +",The max is "+max);
printArr(arrays);
int[] reverseArray = reverse(arrays);
printArr(reverseArray);
}
//数组作为参数
public static void printArr(int[] array){
// for-each循环
for (int arr:array) {
System.out.print(arr+" ");
}
System.out.println();
}
//数组作为参数、数组作为返回值
public static int[] reverse(int[] array){
int[] result = new int[array.length];
for (int i = 0,j=array.length-1; i <array.length ; i++,j--) {
result[j] = array[i];
}
return result;
}
}
2.5 多维数组
- 多维数组可以看做是数组的数组,比如二维数组就是一个特殊的一维数组,数组的每一个元素都是一个一维数组。
- 二维数组:
int a[][] = new int [2][5]
package com.dande.base;
public class ArrayDemo1 {
public static void main(String[] args) {
//多维数组
int a[][] = {{1,2,5,6},{1,2,4,6},{1,2,3,4,5}};
/**
* a[0] {1,2,5,6}
* a[1] {1,2,4,6}
* a[2] {1,2,3,4,5}
*/
printArr(a[0]);
System.out.println("====================");
for (int i = 0; i <a.length ; i++) {
for (int j = 0; j <a[i].length ; j++) {
System.out.print(a[i][j]+"\t");
}
System.out.println();
}
}
public static void printArr(int[] array){
for (int arr:array) {
System.out.print(arr+" ");
}
System.out.println();
}
}
2.6 Arrays类
- 数组的工具类java.util.Arrays
- 数组对象本身并没有什么方法可以供我们调用,但是API中提供了一个工具类Arrays供我们使用,对数据对象进行一些基本的操作。
- JDK帮助文档
- Arrays类中的方法都是static修饰的静态方法,在使用的时候可以直接使用类名进行调用,而不用使用对象来调用(是不用不是不能!)
- Arrays类的常用功能:
- 给数组赋值:通过fill方法
- 对数组排序:sort方法
- 比较数组:equals方法比较数组中元素值是否相等(数组中相同的数据元素相同的数据才相同)
- 查找数组元素:通过binarySearch方法能对排序好的数组进行二分法查找操作。(如果数组中有该元素,则返回元素所在位置的索引,否则返回负的数组长度加1)
package com.dande.base;
import java.util.Arrays;
public class ArrayDemo1 {
public static void main(String[] args) {
int[] a = {12,4,56,3,7,8,65,422};
int[] b = {12,4,56,3,7,9,65};
System.out.println(a); // [I@1540e19d
System.out.println(Arrays.toString(a)); //[12, 4, 56, 3, 7, 8, 65, 422]
Arrays.sort(a); //默认将数组从小到大排序
System.out.println(Arrays.toString(a)); //[3, 4, 7, 8, 12, 56, 65, 422]
Arrays.fill(a,1); //将数组中的所有元素都变为1
System.out.println(Arrays.toString(a)); //[1, 1, 1, 1, 1, 1, 1, 1]
Arrays.fill(a,2,5,0); //将数组中的2-5号元素,即a[2],a[3],a[4]变为0
System.out.println(Arrays.toString(a)); // [1, 1, 0, 0, 0, 1, 1, 1]
System.out.println(Arrays.equals(a,b)); //false
System.out.println(Arrays.binarySearch(a,7)); //-9(-数组长度-1)
System.out.println(Arrays.binarySearch(b,7)); //4 (元素所在位置的索引)
}
}
2.7 冒泡排序
package com.dande.base;
import java.util.Arrays;
public class Bubble {
public static void main(String[] args) {
int[] a = {12, 3, 4, 5, 6, 45, 32, 234, 987};
Bubble(a);
System.out.println(Arrays.toString(a));
}
public static void Bubble(int[] a) {
int temp = 0;
// 外层循环,次数
for (int i = 0; i < a.length - 1; i++) {
//内层循环,交换数据
for (int j = 0; j < a.length-1- i; j++) {
if (a[j + 1] > a[j]) {
temp = a[j + 1];
a[j + 1] = a[j];
a[j] = temp;
}
}
}
}
}
冒泡排序的基本思想是通过相邻元素的比较和交换来将较大的元素逐渐向右移动至正确的位置。为了优化冒泡排序的性能,可以进行以下几个方面的改进:
-
设置标志位:在每一趟排序中,如果没有发生元素交换,则说明待排序数组已经有序,可以提前结束排序。
-
减少比较次数:在每一趟排序中,每次比较可以确定一个最大值,所以下一趟排序时,可以减少比较次数。具体做法是,在每一趟排序中记录下最后一次交换的位置,该位置之后的元素必然已经有序,下一趟排序时只需比较到该位置即可。
-
减少交换次数:冒泡排序每次交换都需要三次赋值操作,可以通过记录下最后一次交换的位置,来减少不必要的交换次数。具体做法是,在每一趟排序中记录下最后一次交换的位置,下一趟排序时,只需将待排序数组的长度设置为最后一次交换的位置即可
package com.dande.base;
import java.util.Arrays;
public class Bubble {
public static void main(String[] args) {
int[] b = {12, 3, 4, 5, 6, 45, 32, 234, 1023,45,987};
BubblePlus(b);
System.out.println(Arrays.toString(b));
}
public static void BubblePlus(int[] a) {
int temp = 0;
// 外层循环,次数
for (int i = 0; i < a.length - 1; i++) {
boolean flag = false;
//内层循环,交换数据
for (int j = 0; j < a.length - 1 - i; j++) {
if (a[j + 1] > a[j]) {
temp = a[j + 1];
a[j + 1] = a[j];
a[j] = temp;
flag = true;
}
}
if(!flag) break;
}
}
}
2.8 稀疏数组
- 当一个数组中大部分元素为0 ,或者为同一个值的数组时,可以使用系数数组来保存该数组。
- 稀疏数组的处理方式是:
- 记录数组一共有几行几列,有多少个不同值
- 把具有不同值的元素和行列及对应的值记录在一个小规模数组中,从而缩小程序的规模。
- 原始数组和稀疏数组:
稀疏数组的第一行数据为原始数组的总行列数和有效数字个数。
package com.dande.base;
public class ArrayDemo02 {
public static void main(String[] args) {
//1. 记录并打印原始数组 11行11列,0:无棋子;1:黑棋;2:白棋
int[][] array1 = new int[11][11];
array1[1][2] = 1;
array1[2][3] = 2;
array1[7][3] = 1;
array1[2][8] = 2;
System.out.println("原始数组为:");
for (int i = 0; i <array1.length ; i++) {
for (int j = 0; j <array1[i].length ; j++) {
System.out.print(array1[i][j]+"\t");
}
System.out.println();
}
// 2. 获取原始数组中有效数字的个数
int sum = 0;
for (int i = 0; i <array1.length ; i++) {
for (int j = 0; j <array1[i].length ; j++) {
if(array1[i][j]!= 0) sum++;
}
}
System.out.println("有效数字个数:"+sum);
// 3. 创建稀疏数组
int[][] array2 = new int[sum+1][3];
array2[0][0] = array1.length;
array2[0][1] = array1.length;
array2[0][2] = sum;
//稀疏数组取值
int count= 0;
for (int i = 0; i <array1.length ; i++) {
for (int j = 0; j <array1[i].length ; j++) {
if(array1[i][j] !=0){
count++;
array2[count][0] = i;
array2[count][1] = j;
array2[count][2] = array1[i][j];
}
}
}
System.out.println("稀疏数组为:");
for (int i = 0; i <array2.length ; i++) {
System.out.println(array2[i][0]+"\t"
+array2[i][1]+"\t"
+array2[i][2]+"\t");
}
// 4. 还原稀疏数组
//创建数组
int[][] array3 = new int[array2[0][0]][array2[0][1]];
// 数组取值
for (int i = 1; i <array2.length ; i++) {
array3[array2[i][0]][array2[i][1]] = array2[i][2];
}
System.out.println("还原后的数组:");
for (int i = 0; i <array3.length ; i++) {
for (int j = 0; j <array3[i].length ; j++) {
System.out.print(array3[i][j]+"\t");
}
System.out.println();
}
}
}
3. 面向对象编程
Java的核心思想就是面向对象编程(OOP)。
3.1.初识面向对象
- 面向过程思想
- 步骤清晰简单,第一步做什么,第二步做什么。。。。。。
- 面对过程适合处理一些较为简单的问题
- 面向对象思想
- 物以类聚,分类的思维模式,思考问题首先会解决问题需要安歇分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索。
- 面向对象适合处理复杂的问题,适合处理需要多人协作 的问题!
对于描述复杂的事物,为了从宏观上把握,从整体上合理分析,需要使用面向对象的思路分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理。
面向对象编程(Object-Oriented Programming,OOP)
面向对象编程的本质: 以类的方式组织代码,以对象的组织(封装)数据。
抽象
面向对象的三大特性:封装、继承、多态
从认识论角度考虑是现有对象后有类。对象是具体的事物,类是抽象的,对对象的抽象。
从代码运行角度考虑是先有类后有对象。类是对象的模板。
3. 2.方法回顾和加深
- 方法的定义:
- 修饰符:方法可引用范围
- 返回类型:void 或者其他返回类型
- break(跳出switch,结束循环)和return(结束方法,返回方法结果)的区别
- 方法名:见名知意,首字母小写,驼峰原则
- 参数列表:(参数类型,参数名)可变长参数…
- 异常抛出:throw
- 方法的调用
- 静态(static)方法:和类一起存在,可以通过类名直接调用类的方法
- 非静态方法:需要实例化类之后通过实例化的对象进行调用
- 形参和实参:两者数据类型必须保持一致
- 值传递和引用传递:
- this 关键字:
静态方法和非静态方法
package com.dande.oop;
public class Demo01 {
public static void main(String[] args) {
// 静态方法可以直接调用
produce();
Student.speak();
//非静态方法要通过类的实例化进行调用
Demo01 demo01 = new Demo01();
demo01.conduct();
Student student = new Student();
student.run();
}
public static void produce(){
System.out.println("This is a static method!");
}
public void conduct(){
System.out.println("This is a method!");
}
}
class Student {
public static void speak(){
System.out.println("speaking!");
}
public void run(){
System.out.println("Running!");
}
}
值传递和引用传递
package com.dande.oop;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
public class Demo02 {
public static void main(String[] args) {
// 值传递
int a = 18;
System.out.println(a); //18
passValue(a);
System.out.println(a); //18
// 引用传递:对象(本质传递的还是值)
Person person = new Person();
System.out.println(person.name); //null
passRef(person);
System.out.println(person.name); //yiwa
}
public static void passValue(int a){
a++;
}
public static void passRef(Person p){
p.name = "yiwa";
}
}
class Person{
String name;
int age;
public static void speak(){
System.out.println("Hello");
}
}
3.3.对象的创建分析
- 类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但是并不能代表某一个具体的事物。
- 动物、植物、手机、电脑、学生
- 对象是抽象概念的具体实例。
- 抽象概念:学生,具体实例:李华
- 能够体现出特点、完成特定功能的是具体的实例,而不是一个抽象的概念。
(1)创建与初始化对象:
- 使用new关键字创建对象
- 使用new关键字创建对象的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用。
- 类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下两个特点:
- 必须和类的名字相同
- 必须没有返回类型,也不能写void
- idea 快捷键:new Employee alt+enter
package com.dande.oop;
public class Application {
// 一个项目应该只有一个 main主程序,是项目的入口
public static void main(String[] args) {
// 类是抽象的,需要进行实例化,类实例化后会返回一个自己的对象。
// Employee实例化之后返回一个employee对象
// idea 快捷键:new Employee alt+enter
Employee Lily = new Employee();
Lily.age = 23;
Lily.name = "Lily";
Lily.introduce();
Employee Anna = new Employee();
Anna.age = 24;
Anna.name = "Anna";
Anna.introduce();
}
}
//员工类
class Employee{
//属性:字段
String name;
int age;
double salary;
//方法
public void introduce(){
//this表示当前类
System.out.println("Hello,my name is "+ this.name+",and I'm "+ this.age);
}
}
(2)构造器详解
- 一个类即使什么也不写,它也会存在一个方法,即类的默认的无参构造方法。
- 使用new关键字,必须要有构造方法,本质是调用构造器
- 一旦定义了有参构造,无参构造必须显式定义
- 构造器一般用来初始化值
- idea快捷键:alt+Fn+insert生成构造器
package com.dande.oop;
public class Application {
// 一个项目应该只有一个 main主程序,是项目的入口
public static void main(String[] args) {
Employer aaa = new Employer();
Employer bbb = new Employer("Janny");
Employer ccc = new Employer("Tom",24);
Employer ddd = new Employer("Jack",37,32000);
aaa.introduce();
bbb.introduce();
ccc.introduce();
ddd.introduce();
}
}
//雇主类
class Employer {
String name;
int age;
double salary;
//1.默认的无参构造方法,写了有参构造方法之后必须要把无参构造方法显式定义出来
public Employer(){}
//2. 只有一个参数的构造方法
public Employer(String name) {
this.name = name;
}
// 3.含有两个参数的构造方法
public Employer(String name, int age) {
this.name = name;
this.age = age;
}
// 4. 包含所有参数的构造方法
public Employer(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
//其他方法
public void introduce() {
//this表示当前类
System.out.println("Hello,my name is " +
this.name + ",and I'm " +
this.age+",My salary is "+this.salary);
}
}
(3)创建对象内存分析
程序从Application 类中进入,main方法以及常量"旺财"都在方法区,(年龄3 是int类型数据),类模板Pet也在方法区,main方法在栈中执行,创建两个引用变量名,实际指向的是堆中开辟的内存空间,存放实际的对象,对象可以直接调用静态方法区中的static方法。
- 程序开始,加载Application类,包括main方法和常量池"旺财";
- 加载完Application类之后,执行main方法,在栈的最底层,main方法执行完成弹出栈,程序对应也结束
- 执行main方法,加载Pet类模板(堆中)
- 在栈中加入引用变量名dog,指向在堆中开辟的存放Pet对象的内存空间
- 然后将Application类中常量池的数据"旺财"和传入的数据年龄3赋值给堆中的dog对象,调用方法区Pet类模板中的shout方法
package com.dande.oop;
public class Application2 {
public static void main(String[] args) {
Pet dog = new Pet();
dog.name = "旺财";
dog.age = 3;
dog.shout();
Pet cat = new Pet();
cat.shout();
Pet.eat();
dog.eat();
cat.eat();
}
}
class Pet{
String name;
int age;
public void shout(){
System.out.println(this.age+"岁的"+this.name+" is shouting!");
}
public static void eat(){
System.out.println("吃饭啦!!!");
}
}
(4)类与对象小结
- 类与对象: 类是一个模板,对象是具体的实例
- 方法: 定义(修饰符 返回值类型 方法名(参数列表){方法体})和调用
- 对象的引用:对象是通过引用来操作的。栈—>堆
- 属性: 字段field,成员变量
默认初始化:- 数字:0, 0.0
- 字符char: u0000
- 布尔值boolean:false
- 引用类型:null
- 对象的创建和使用
- 必须使用new关键字创造对象,默认调用无参构造器,写了有参的就必须把无参的显式定义
- 对象的属性 person.name
- 对象的方法:person.speak()
- 类:
- 静态的属性 : 属性
- 动态的行为: 方法
3.4.面向对象三大特性(封装继承多态)
(1)封装
整体封装留一个接口供外部调用
- 程序设计追求高内聚、低耦合;
- 高内聚: 类的内部数据操作细节自己完成,不允许外部干涉
- 低耦合: 仅暴露少量的方法给外部使用;
- 封装(数据(属性)的隐藏):
- 通常,应该禁止直接访问一个对象中数据的直接表示,而应该通过接口来访问,这称为信息隐藏。
- 属性私有(private ),get/set
示例代码:
idea快捷键:alt+Fn+insert设置Getter和Setter
package com.dande.oop;
import com.dande.oop.demo01.Student;
public class Application {
public static void main(String[] args) {
Student student = new Student();
student.setName("张三");
student.setAge(102); // 不合法的输入数据
System.out.println(student.getName()); // 张三
System.out.println(student.getAge()); //3
}
}
Student类
package com.dande.oop.demo01;
// 类 private:私有
public class Student {
// 属性私有
private String name;
private int age;
private char sex;
// 提供可以操作属性的方法:public set、get方法
//get方法,获取数据
public String getName() {
return name;
}
public int getAge() {
return age;
}
public char getSex() {
return sex;
}
//set 方法,给数据设置值
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
if(age>0 && age<100) {
this.age = age;
}else{
this.age = 3; // 对于不合法的传入参数做处理
}
}
public void setSex(char sex) {
this.sex = sex;
}
}
封装的意义:
- 提高程序的安全性,保护数据
- 隐藏代码实现细节
- 统一接口
- 提高系统可维护性
(2)继承
- 继承的本质是对某一批类的抽象,从而实现对现实世界更好地建模。
- extends的意思是“扩展”。子类是对父类的扩展。
- Java中只有单继承,没有多继承!(一个父类可以有多个子类,但是一个子类只能有一个父类。)
- 继承是类和类之间的一种关系,除此之外,类和类之间的关系还有依赖、组合、聚合等。
- 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示。
- 子类和父类之间,从意义上讲具有" 子类 is a 父类”的关系。
- 子类继承父类,会拥有父类公有的属性和方法。
- 在Java中,所有的类都直接或者间接继承Object类。
- 子类可以调用的方法都是自己的或者继承父类的;
- 父类可以指向子类,但是不能调用子类独有的方法;
- 被
final
修饰的类不能继承,没有子类
idea快捷键: Ctrl + H 打开类的继承树
public class Teacher extends Person {
}
super关键字(调用父类的方法和属性):
- super调用父类的构造方法,(子类默认继承了父类的无参构造方法,执行子类的构造方法之前会先执行父类的构造方法)显式定义时super()必须在子类构造方法的第一行,否则会爆错。
- super只能出现在子类的方法或者构造方法中
- super和this不能同时在一个构造方法中调用
- super表示父类对象的引用,调用父类的方法和属性
this和supper的区别:
- 代表的对象不同:
- this本身调用自身对象
- super代表父类对象的引用
- 适用前提
- this:没有继承也可以使用
- super:只能在继承条件下才可以使用
- 构造方法
- this():本类的构造方法
- super():父类的构造方法
方法的重写(Override):需要有继承关系,子类重写父类的方法
子类和父类的方法一致,方法体不同
-
方法名必须相同
-
参数列表必须相同
-
修饰符: 范围可以扩大但是不能缩小
public>protected>default>private
-
抛出的异常:范围可以缩小但是不能扩大
-
子类重写父类的方法,使用
@Override
注解(有功能的注释) -
静态方法的调用只和左边定义的数据类型有关
-
idea快捷键:ctrl+O 插入重写方法
package com.dande.oop;
import com.dande.oop.demo01.Teacher;
import com.dande.oop.demo01.Person;
public class Application {
public static void main(String[] args) {
Teacher teacher1 = new Teacher();
teacher1.print(); //I am Teacher !
teacher1.speak(); // Teacher speaking!
// 父类的引用指向了子类
// 静态方法的调用只和左边定义的数据类型有关
//对象能执行哪些方法主要看对象左边的类型
Person teacher2 = new Teacher();
teacher2.print(); //I am Person !
teacher2.speak(); // Teacher speaking!
}
}
package com.dande.oop.demo01;
//父类
public class Person {
public static void print(){
System.out.println("I am Person !");
}
public void speak(){
System.out.println("Person speaking!");
}
}
package com.dande.oop.demo01;
//子类
public class Teacher extends Person {
public static void print(){
System.out.println("I am Teacher !");
}
@Override //注解:有功能的注释
public void speak() {
// super.speak();
//子类重写了父类的方法
System.out.println("Teacher speaking!");
}
}
(3)多态
- 即同一方法可以根据发送对象的不同而采用多种不同的行为方式
- 一个对象的实际类型是确定的,但是可以指向对象的引用的类型有很多(父类和祖父类。。。)
多态存在的条件:
- 继承关系
- 子类重写父类方法
- 父类引用指向子类对象
注意:
- 多态是方法的多态 ,属性没有多态性
- 对象能执行哪些方法,取决于左边定义的类型,而不是右边创建的类型。
Student s1 = new Student();
Student为子类,Person为父类
Person s2 = new Student();
子类重写父类的方法,执行子类中的方法,父类不能调用子类独有的方法 - 子类 能调用的方法都是自己的方法或者继承自父类的方法;
- 父类和子类之间有继承关系才能进行转换,否则会有类型转换异常。(
ClassCastException!
) - 存在关系: 继承条件、方法需要重写,父类引用指向子类对象。
Father f1 = new Son();
- 方法重写:
- static方法属于类,不属于实例,不能重写
- 被final 修饰的方法,也不能重写
- 私有(private)方法不能重写
package com.dande.oop;
import com.dande.oop.demo01.Student;
import com.dande.oop.demo01.Person;
public class Application {
public static void main(String[] args) {
Student s1 = new Student(); //创建子类
Person s2 = new Student(); //父类的引用指向子类
s1.speak(); //student
s2.speak(); //student 子类重写了父类的方法,执行的是子类的方法
s1.exam(); //examing
// s2.exam(); //父类不能调用子类独有的方法
((Student)s2).exam(); //类型强转
}
}
package com.dande.oop.demo01;
//子类
public class Student extends Person {
@Override
public void speak() {
System.out.println("student");
}
public void exam(){
System.out.println("examing!");
}
}
package com.dande.oop.demo01;
//父类
public class Person {
public void speak(){
System.out.println("Person speaking!");
}
}
(4)instanceof和类型转换
instanceof
用于测试对象是否是指定类型(类或者子类或者接口)的实例。- instanceof 也称为类型比较运算符,将实例与类型进行比较,返回true或者false。
- 如果将instanceof运算符应用于任何具有空值的变量,它会返回false;
- 如果instanceof两边的实例类型不存在父子关系,则会编译报错
package com.dande.oop;
import com.dande.oop.demo01.Student;
import com.dande.oop.demo01.Person;
import com.dande.oop.demo01.Teacher;
public class Application {
public static void main(String[] args) {
//Object > String
//Object > Person >Teacher
//Object > Person >Student
Object s1 = new Student();
System.out.println(s1 instanceof Student); //true
System.out.println(s1 instanceof Person); //true
System.out.println(s1 instanceof Object); //true
System.out.println(s1 instanceof Teacher); //false
System.out.println(s1 instanceof String); //false
System.out.println("==========================");
Person s2 = new Student();
System.out.println(s2 instanceof Student); //true
System.out.println(s2 instanceof Person); //true
System.out.println(s2 instanceof Object); //true
System.out.println(s2 instanceof Teacher); //false
// System.out.println(s2 instanceof String); //编译不通过
Student s3 = new Student();
System.out.println("======================");
System.out.println(s3 instanceof Student); //true
System.out.println(s3 instanceof Person); //true
System.out.println(s3 instanceof Object); //true
// System.out.println(s3 instanceof Teacher); //编译不通过
// System.out.println(s3 instanceof String); //编译不通过
System.out.println("=========================");
//如果变量为null,则instanceof比较结果为false
Student s4 = null;
System.out.println(s4 instanceof Student); //false
}
}
- 父类引用指向子类
- 把子类转换为父类,向上转型(可能回丢失自己本来的一些方法)
- 父类转换为子类,向下转型(强制转换)
- 类型转换的目的:方便方法的调用,减少重复的代码,简洁!!!
(5)static详解
静态属性和方法
static(静态的)
修饰的属性和方法都是和类一起加载的,存放在堆中的方法区中的静态方法区内。
- 静态属性和方法可以通过类名直接调用,
- 非静态方法可以调用静态方法,但是静态方法不能直接调用非静态方法(静态方法随类加载出来之后非静态方法还未加载)
package com.dande.oop.demo02;
import java.sql.SQLOutput;
public class Student {
public String name;
public static double score;
public void run(){
go(); //非静态方法可以直接调用静态方法
System.out.println("run");
}
public static void go(){
// run(); 静态方法不能直接调用非静态方法
System.out.println("go");
}
public static void main(String[] args) {
Student s1 = new Student();
System.out.println(Student.score); // 0.0静态属性可以直接通过类名调用
// System.out.println(Student.name); // 非静态属性不能直接通过类名调用
System.out.println(s1.name); // null 非静态属性只能通过类的实例来调用
go(); //静态方法可以直接调用
Student.go(); // 静态方法也可以直接使用类名调用
// run();
// Student.run();
s1.run(); // 非静态方法只能通过类的实例对象来调用
}
}
静态代码块
静态代码块 static{}
,和 类一起加载,只执行一次
匿名代码块{}
,匿名代码块在创建对象的时候自动创建,常被用来赋初始值
静态代码块、匿名代码块和构造方法的执行顺序:静态代码块–>匿名代码块–>构造方法
package com.dande.oop.demo02;
public class Person {
// 匿名代码块,第二个执行,创建一个对象 执行一次
{
System.out.println("匿名代码块!");
}
// 静态代码块,第一个执行,只执行一次
static{
System.out.println("静态代码块!");
}
// 构造方法,第三个执行,创建一次对象执行一次
public Person(){
System.out.println("构造方法!");
}
public static void main(String[] args) {
Person p1 = new Person();
System.out.println("=================");
Person p2 = new Person();
System.out.println("=================");
Person p3 = new Person();
}
// 静态代码块!
// 匿名代码块!
// 构造方法!
// =================
// 匿名代码块!
// 构造方法!
// =================
// 匿名代码块!
// 构造方法!
}
静态导入包
静态导入包中的方法,可以直接使用
package com.dande.oop.demo01;
import static java.lang.Math.random;
import static java.lang.Math.round;
import static java.lang.Math.PI;
public class Test {
public static void main(String[] args) {
// System.out.println(Math.random());
//静态导入包中的方法和变量之后在代码中直接调用
System.out.println(round(random()*10));
System.out.println(PI);
}
}
3.5.抽象类和接口
(1)抽象类
abstract
修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,该类就是抽象类。
- 抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类。
- 抽象类不能使用new关键字来创建对象,它是用来让子类继承的。
- 抽象方法只有方法的声明没有方法的实现,它是用来让子类实现的。
- 子类继承抽象类,就必须要实现抽象类中没有实现的抽象方法,否则该子类也要声明为抽象类。
package com.dande.oop.demo02;
public abstract class Action {
String name;
public void introduce(){ //抽象类中也可以有非抽象方法
System.out.println("自我介绍!");
}
public abstract void job(); // 抽象方法所在的类必须是抽象类,抽象方法只有方法的声明,没有方法体
}
package com.dande.oop.demo02;
public class A extends Action { // 继承抽象类的类必须要实现抽象类中的抽象方法,或者将子类也声明为抽象类
@Override
public void job() {
System.out.println("working!");
}
public static void main(String[] args) {
A a1 = new A();
a1.job();
}
}
思考:
- 抽象类不能通过new来创建对象,它有构造器吗?
- 抽象类中是有默认的无参构造方法的。构造器的作用是用于子类实例化时调用父类的构造方法。抽象类的构造器不能被用于创建抽象类的实例,因为抽象类本身是不完整的,它可能包含抽象方法,这些方法需要在具体的子类中实现
- 抽象类存在的意义是什么?
- 抽象类的主要目的是作为其他类的基类,提供一些通用的属性和方法,并规定子类必须实现的抽象方法。通过继承抽象类,子类可以获得父类的属性和方法,并根据需要进行扩展和实现
- 主要作用是作为一个模板或者规范,为子类提供一些通用的行为和结构,并要求子类必须实现某些方法。
- 简化代码,将相同的类的方法但是不同的实现抽象出来,减少代码的重复性。
Action.class文件
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.dande.oop.demo02;
public abstract class Action {
String name;
public Action() {
}
public void introduce() {
System.out.println("自我介绍!");
}
public abstract void job();
}
(2)接口
普通类:只有具体实现
抽象类:具体实现和规范(抽象方法)都有!
接口:只有规范,自己无法写方法 !!
约束和实现分离,面向接口编程.
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是。。则必须能。。的思想。”
接口的本质是规约,(像现实中的法律一样,指定好后大家都遵守)
声明类的关键字是class,声明接口的关键字是interface
- 接口中定义的方法默认都是抽象的,public abstract
- 接口中的属性默认都是常量,使用public static final 修饰
- 接口都有一个实现类(
implements
) - 实现接口的类,必须要实现(重写)接口中的所有方法
- 可以利用接口的多重实现(
implements
)来实现“多继承” - 接口中没有构造方法,不能直接进行实例化
userServiceImpl.java
package com.dande.oop.demo03;
public class userServiceImpl implements userService ,timer{ // 接口可以多重实现
@Override // 实现接口的类必须重写接口中的方法
public void add() {
}
@Override
public void delete() {
}
@Override
public int query() {
return 0;
}
@Override
public String update() {
return null;
}
@Override
public void cost() {
}
}
timer.java
package com.dande.oop.demo03;
public interface timer {
void cost();
}
userService.java
package com.dande.oop.demo03;
public interface userService {
public static final int SCNO = 10027; //接口中的属性默认都是 public static final修饰的常量
public abstract void add(); // 接口中的方法默认是public abstract的
void delete();
int query();
String update();
}
3.6.内部类
各种内部类的参考博文(博主“Java新生代”的文章)
(1)内部类
内部类就是在一个类的内部定义一个类,比如,A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类。
- 成员内部类
- 成员内部类是定义在一个类的内部的普通类。它与其他成员变量和方法一样,属于外部类的成员,可以访问外部类的所有成员,包括私有成员。
- 成员内部类可以使用new关键字实例化,其实例化方式为:
外部类对象.new 内部类()
。
package com.dande.oop.demo04;
public class Outer {
private int id = 10;
public void out(){
System.out.println("这是外部类的方法");
}
public class Inner{
public void in(){
System.out.println("这是内部类的方法");
}
// 内部类可以获取外部类的私有属性
public void getID(){
System.out.println(id);
}
}
}
package com.dande.oop.demo04;
public class Application {
public static void main(String[] args) {
Outer outer = new Outer();
// 通过这个外部类的实例化内部类
Outer.Inner inner = outer.new Inner();
inner.getID(); //10
}
}
- 静态内部类
- 静态内部类是使用static关键字修饰的内部类。
- 它与成员内部类不同,静态内部类不依赖于外部类的实例,可以直接通过外部类名访问。
- 静态内部类只能访问外部类的静态成员,不能访问非静态成员。
- 静态内部类可以使用new关键字实例化,其实例化方式为:
外部类.内部类()
。
package com.dande.oop.demo04;
public class Outer {
private static int id = 10;
public void out(){
System.out.println("这是外部类的方法");
}
public static class Inner{
public void in(){
System.out.println("这是内部类的方法");
}
// 内部类可以获取外部类的私有属性
public void getID(){
System.out.println(id);
}
}
}
package com.dande.oop.demo04;
public class Application {
public static void main(String[] args) {
//静态内部类的实例化
Outer.Inner inner = new Outer.Inner();
inner.getID();
}
}
- 局部内部类
- 局部内部类是定义在一个代码块(如方法、循环体等)内部的类。
- 它的作用范围仅限于该代码块。
- 局部内部类可以访问外部类的所有成员,包括私有成员,但只能访问方法内部的final或者effectively final的局部变量。
- 局部内部类的没有任何访问修饰符
- 不能创造静态信息
package com.dande.oop.demo04;
public class Outer {
public void method(){
// 局部内部类
class Inner{
public void in(){
}
}
}
}
- 匿名内部类
package com.dande.oop.demo04;
public class Outer {
public static void main(String[] args) {
// 没有名字初始化类,不用将实例保存到变量中
new Person().eat();
}
}
class Person{
public void eat(){
System.out.println("chichichi");
}
}
3.7.异常(Exception
)
(1)什么是异常
- 异常是程序运行中出现的不期而至的各种状况,如文件找不到、网络连接失败、非法参数等。
- 异常发生在程序运行期间,会影响正常的程序执行流程。
异常分类:
- 检查性异常:用户错误或者问题引起的异常,是程序员无法预见的。(例如需要打开一个不存在的文件时,一个异常就发生了,这些异常在编译时不能被简单的忽略。)
- 运行时异常:可以在运行前检查避免,运行时异常可以在编译时被忽略。
- 错误
ERROR
:错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,他们在编译时检查不到。
异常处理:
java把异常当做对象来处理,并定义一个基类java.lang.Throwable
作为所有异常的超类。
在Java API 中已经定义了许多异常类,这些异常类分类两大类,错误ERROR
和Exception
.
1) Error
- Error 类由对象Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。
- Java虚拟机运行错误(Virtual MachineError),当JVM不再继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
- 发送在虚拟机试图执行应用时的错误是不可查的,例如类定义错误(NoClassDefFoundError)、链接错误(LinkageError),这些错误的发生在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。
2)Exception
- 在Exception分支中有一个重要的子类RuntimeException(运行时异常)
- ArrayIndexOutOfBoundsException(数组下标越界)
- NullPointerException(空指针异常)
- ArithmeticException(除数不能为0,。。。 )(算术异常)
- MissingResourceException(丢失资源)
- ClassNotFoundException(找不到类)等异常,这些异常不是检查异常,程序中可以选择捕获处理,也可以选择不处理。
- 这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常发生。
3)Exception和Error的区别
Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。
(2)异常处理机制
- 抛出异常 和 捕获异常
- 异常处理五个关键字:
- try,catch,finally,throw,throws
异常处理机制:
try{// try监控区域
// 可能发生异常的代码块
}catch(想要捕获的异常类型){
//捕获异常,处理异常(输出提示或者进行其他操作)
}catch(){
//...(可以捕获多个异常,范围从前往后依次扩大,前面的catch捕获异常之后后面的catch就不会再执行
}finally{ //处理善后工作,不论异常是否捕获,都会执行
// 最终执行代码块
}
throws 和throw
throws
关键字是用于方法声明中,用于表示该方法可能会抛出某种类型的异常。- 当一个方法可能会引发一个已检查异常时,可以在方法签名中使用throws关键字来声明该异常类型。这样做的目的是将异常处理的责任交给方法的调用者,即告诉调用者需要处理可能发生的异常
throw
键字用于在代码块中手动抛出一个异常对象。它可以用于任何地方,包括方法、构造器、代码块等。通过throw关键字,我们可以抛出自定义异常或者Java标准库中已定义的异常。当我们手动抛出一个异常时,程序将立即停止执行当前代码块,并开始查找与该异常匹配的异常处理器
idea 快捷键: 选中对应的代码之后按下,Ctrl+ Alt+T,可以选择对该代码进行异常捕获或者循环嵌套
package com.dande.oop.demo04;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
System.out.println("请输入要运算的两个整数,用空格隔开:");
int a = 0;
int b = 0;
Scanner scanner = new Scanner(System.in);
a = scanner.nextInt();
b = scanner.nextInt();
// double result1 = new Test().divide(a,b);
// System.out.println(result1); // 抛出异常,程序终止
// double result2 = new Test().divide2(a,b);
// System.out.println(result2); //Infinity
double result3 = new Test().divide3(a,b);
System.out.println(result3); // Finally ! Infinity
scanner.close();
}
public double divide(int a,int b){
if(b==0){
throw new ArithmeticException(); // 在方法中主动抛出异常
}else{
return a/((double)b);
}
}
// 在方法声明时抛出异常
public double divide2(int a,int b)throws ArithmeticException{
return a/((double)b);
}
// 使用try catch 捕获并处理异常
public double divide3(int a,int b){
double result =0;
try {
result = a/((double)b);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("Finally!!");
}
return result;
}
}
(3)自定义异常
- 使用java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。
- 用户自定义异常类,只需要继承Exception类即可。
- 在程序中使用自定义异常类的步骤:
- 创建自定义异常类
- 在方法中通过throw关键字抛出异常对象
- 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明出通过throws关键字指明要抛出给方法调用者的异常,继续在进行下一步操作
- 在出现异常方法的调用者中捕获并处理异常。
package com.dande.oop.demo04;
//自定义的异常类
public class MyException extends Exception{
// 异常处理:如果传入的数字大于100,则抛出异常
private int detail;
public MyException() {
}
public MyException(int a){
this.detail = a;
}
// toString 方法相当于异常的打印信息
@Override
public String toString() {
return "MyException{" +
"detail=" + detail +
'}';
}
}
package com.dande.oop.demo04;
public class Test {
// 可能会存在异常的方法
static void test(int a) throws MyException { //抛出异常给方法的调用者
System.out.println("传递的参数为:"+a);
if(a>100){
throw new MyException(a); // 抛出异常
}
System.out.println("OK");
}
//public static void main(String[] args) throws MyException{
public static void main(String[] args) {
try {
test(102); //调用方法时捕获处理异常或者对调用方法的方法声明出继续用throws抛出异常
} catch (MyException e) {
// e.printStackTrace();
System.out.println(e);
}finally{
System.out.println("Finish");
}
}
}
(4)总结
- 处理运行时异常时,采用逻辑去合理规避同时辅助
try-catch
处理 - 在多重catch块后面,可以加一个
catch(Exception)
来出来可能会被遗漏的异常 - 对于不确定的代码,也可以加上try-catch处理潜在的异常
- 尽量去处理异常,切记只是简单地调用
printStackTrace()
去打印输出 - 具体如何处理异常要根据不同的也无需要和异常类型去决定
- 尽量添加finally语句去释放占用的资源