Java 概念HandBook

参考:https://leetcode-cn.com/leetbook/detail/java-interview-highlights/

1. 基本语法

1.1 基本数据结构

在这里插入图片描述

  • 注意:尽管charshort都是16位,但是char一定是正的。
  • 布尔类型不能转换成其他基本数据类型,其他基本数据类型也不能转换成布尔类型。

1.2 方法

  • 方法名参数表共同构成方法签名

    • 如:testAdd(int a, int b), testAdd(double a, double b)
  • 修饰符:修饰符是可选的,告诉编译器如何调用该方法。

  • 返回值类型:方法可以返回一个值,此时返回值类型是方法要返回的值的数据类型。方法也可以没有返回值,此时返回值类型是 void。

  • 方法的重载

  • 方法签名只由方法名和参数列表共同构成,因此被重载的方法必须具有不同的参数列表,而不能通过不同的修饰符和返回值类型进行方法的重载。

  • 如果一个方法调用有多个可能的匹配,则编译器会调用最合适的匹配方法(会自动进行如int -> double的类型转换),如果编译器无法判断哪个方法最匹配,则称为歧义调用,会导致编译错误。

⚠️ :

  • Java 中只有值传递值传递的含义是将实参的值传递给形参
    • 当参数类型是基本数据类型时,传递的是实参的值
    • 当参数类型是对象时,传递的是对象的引用,但是不能让实参引用新的对象

1.3 递归

优点
如果问题满足递归的特点,即可以分解成子问题且子问题与原始问题相似,则可以使用递归给出自然、直接、简单的解法。

缺点

  • 每一次函数调用都需要在内存栈中分配空间,对栈的操作还需要时间。
  • 不加记忆化的情况下,会产生重复计算。
  • 可能导致调用栈溢出。

优化方法:

  1. 记忆化
  2. 尾递归:最后返回的表达式只有自己这个递归方法
int sum(int n) {
    if (n == 1) {
        return 1;
    }
    return n + sum(n - 1);
}
// 尾递归
int sumTail(int n, int s) {
    if (n == 1) {
        return 1 + s;
    }
    return sumTail(n - 1, n + s);
}

2. 面向对象

2.1 面向对象的概念

面向对象和面向过程的区别

  • 面向过程:将问题分解成步骤,然后按照步骤实现函数,执行时依次调用函数。数据和对数据的操作是分离的

  • 面向对象:将问题分解成对象,描述事物在解决问题的步骤中的行为。对象与属性和行为是关联的

  • 面向过程的优点是性能比面向对象高,不需要面向对象的实例化;缺点是不容易维护、复用和扩展。

  • 面向对象的优点是具有封装、继承、多态的特性,因而容易维护、复用和扩展,可以设计出低耦合的系统;缺点是由于需要实例化对象,因此性能比面向过程低。

对象和类
对象是现实世界中可以明确标识的实体,对象有自己独有的状态和行为。

  • 对象的状态由数据域的集合构成,对象的行为由方法的集合构成。

是定义同一类型对象的结构,是对具有相同特征的对象的抽象。类是一个模板,用来定义对象的数据域和方法。可以从一个类创建多个对象,创建对象称为实例化

  • 类:class animal [对“对象”的抽象]
  • 实例化为对象:duck = animal();

2.2 构造方法 Constructor

  • 使用new操作符调用构造方法,通过调用构造方法创建对象。

2.3 静态(static)和实例

  • 使用关键字 static 修饰的类成员是静态的类成员,不使用关键字 static 修饰的类成员则是实例的类成员。
  • 静态的是更广义上的功能,不依赖于特定的某一个对象。
  • 对于数学类 Math,所有的类成员都不依赖于具体的实例,因此都被定义成静态的类成员。

区别

  1. 尽管可以通过实例调用静态的类成员(如static 的方法),但是建议通过类名(class的名字)调用静态的类成员,因为通过类名调用静态的类成员是不需要创建对象的,如String.format
  2. 静态方法只能访问静态的类成员,不能访问实例的类成员。[即一些不是static的属性没法访问]
    实例方法既可以访问实例的类成员,也可以访问静态的类成员。[实例方法可以访问所有的属性]

2.4 初始化块

代码初始化块属于类成员,在加载类时或创建对象时会隐式调用代码初始块。使用初始化块的好处是可以减少多个构造器内的重复代码。

初始化块的分类

  • 静态初始化块: 在加载类时被隐式调用。static{}
  • 非静态初始化块: 在创建对象时被隐式调用。

单个类的初始化块的执行顺序: 静态初始化块 -> 静态方法 ->非静态初始化块 -> 构造器
静态初始化块会在静态方法之前被执行,非静态初始化块会在构造器和实例方法之前被执行。

  • 由于静态初始化块在加载类时被调用,因此静态初始化块会最先执行,且只会执行一次。
  • 由于非静态初始化块在创建对象时被调用,因此每次创建对象时都会执行非静态初始化块以及执行构造器。

存在继承关系的初始化块的执行顺序
对于两个类的情况,即一个父类和一个子类,执行顺序如下:

  1. 执行父类的静态初始化块。
  2. 执行子类的静态初始化块。
  3. 执行父类的非静态初始化块。
  4. 执行父类的构造器。
  5. 执行子类的非静态初始化块。
  6. 执行子类的构造器。

例子:
代码中定义了四个类,分别是 Main、Class1、Class2 和 Class3。Class 1 -> Class 2 -> Class 3
静态方法 main 定义在 Main 中,创建了 Class3 的实例。

public class Main {
    static {
        System.out.println("Static initialization of Main");
    }
	
	// 只有创建了实际例子下面两个才会被print出来。
    {
        System.out.println("Instance initialization of Main");
    }
    
    public Test() {
        System.out.println("Constructor of Main");
    }

    public static void main(String[] args) {
        new Class3();
    }
}

class Class1 {
    static {
        System.out.println("Static initialization of Class1");
    }

    {
        System.out.println("Instance initialization of Class1");
    }

    Class1() {
        System.out.println("Constructor of Class1");
    }
}

class Class2 extends Class1 {
    static {
        System.out.println("Static initialization of Class2");
    }

    {
        System.out.println("Instance initialization of Class2");
    }

    Class2() {
        System.out.println("Constructor of Class2");
    }
}

class Class3 extends Class2 {
    static {
        System.out.println("Static initialization of Class3");
    }

    {
        System.out.println("Instance initialization of Class3");
    }

    Class3() {
        System.out.println("Constructor of Class3");
    }
}

输出:

Static initialization of Main
Static initialization of Class1
Static initialization of Class2
Static initialization of Class3
Instance initialization of Class1
Constructor of Class1
Instance initialization of Class2
Constructor of Class2
Instance initialization of Class3
Constructor of Class3

总结:

  • 静态初始化块只在类初始化的时候被调用1次,创建了多少个对象,就会执行多少次非静态初始化块。
  • (如有继承,则是最父辈->自己的)静态初始化块 -> 静态方法 -> (如有继承,则是最父辈->自己的)(非静态初始化块 -> 构造器)。

2.5 关键字 this

  • 关键字 this 不可以在静态方法中使用。因为关键字 this 代表的是对象的引用,而静态方法不依赖于类的具体对象。
  • 用于特指某一个对象的属性,避免和输入的参数弄混。
public Square(int side) {
	this.side = side;
}
  • 在构造方法中,可以通过关键字 this 调用其他构造方法,具体用法是this(参数列表)。Java 要求,在构造方法中如果使用关键字 this 调用其他构造方法,则 this(参数列表) 语句必须出现在其他语句之前。
class Square {
    int side;
	
	// 如 Square tmp = new Square();
	// tmp.side = 1;
    public Square() {
        this(1);
    }

    public Square(int side) {
        this.side = side;
    }

    public int getPerimeter() {
        return this.side * 4;
    }

    public int getArea() {
        return this.side * this.side;
    }
}

2.6 可见性修饰符和数据域封装

可见性修饰符: public,默认,private
在这里插入图片描述

2.7 字符串

在 Java 中,常见的字符串类型包括 String、StringBuffer 和 StringBuilder。

String: (可以想像成 final char[])

  • String 使用数组存储字符串的内容,数组使用关键词 final 修饰且该数组是private的(即不能通过外部改变其内部元素的值),因此数组内容不可变,使用 String 定义的字符串的值也是不可变的。
  • 因此每次对 String 的修改操作都会创建新的 String 对象,导致效率低下且占用大量内存空间。

StringBuffer和StringBuilder

  • 在需要经常对字符串的内容进行修改的情况下,应使用 StringBuffer 或 StringBuilder,在时间和空间方面都显著优于 String。
  • StringBuffer 对定义的方法或者调用的方法使用了关键词synchronized修饰,效率略低,但是在多线程的环境下更安全。
  • StringBuilder 的方法没有使用关键词synchronized修饰

⚠️ : final的作用是使得变量不能指向其他的对象(类似于不能修改它pointer的地址)。

final int[] a = new int[5];
a = new int[5]; // 错误,不能让 a 指向其它对象
a[0] = 2; // 正确,a[0] 可以指向其它对象

StringBuilder的使用

StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
	sb.append(i);
    sb.append(',');
}
String s = sb.toString();

2.8 继承

Class 2 extends Class 1

  • 子类从父类中继承可访问的类成员。
  • 如果一个类在定义时没有指定继承,它的父类默认是 Object。

super

  • 关键字 super 指向当前类的的父类。
  • 关键字 super 可以用于两种途径,一是调用父类的构造方法super(),super(参数),二是调用父类的方法super.方法名(参数)

子类的Constructor

  • 如果子类的构造方法没有显式地调用同一个类中其他的构造方法或父类的构造方法,将隐性地调用父类的无参数构造方法,即编译器会把 super() 作为构造方法的第一个语句。
  • 构造一个类的实例时,将会沿着继承链调用所有父类的构造方法,父类的构造方法在子类的构造方法之前调用,称为构造方法链
  • 换言之,应该从最远的父类的构造方法开始运行(如果一个构造方法调用了其他的构造方法也要运行)。
// Class3 -> Class2 -> Class1
public class Class3 extends Class2 {
    public static void main(String[] args) {
        new Class3();
    }

    public Class3() {
        System.out.println("D");
    }
}

class Class2 extends Class1 {
    public Class2() {
        this("B");
        System.out.println("C");
    }

    public Class2(String s) {
        System.out.println(s);
    }
}

class Class1 {
    public Class1() {
        System.out.println("A");
    }
}

输出:

A
B
C
D

方法的重写 (@Override)

  • 如果方法的返回值类型是基本数据类型或者 void,则要求子类的方法的返回值类型和父类的方法的返回值类型相同。如果方法的返回值类型是引用类型,则要求返回值类型相同或者子类的方法的返回值类型是父类的方法的返回值类型的子类。
  • **实例方法只有当可访问时才能被重写。**由于私有方法不能在定义该方法的类外访问,因此私有方法不能被重写。
  • 静态方法可以被继承,但是不能被重写。

在这里插入图片描述

public class Main {
    public static void main(String[] args) {
        Class2 obj = new Class2();
        obj.method(2);
    }
}

class Class1 {
    public void method(int num) {
        System.out.println(2*num);
    }
}

class Class2 extends Class1 {
	// 重写
    @Override
    public void method(int num) {
        System.out.println(num);
    }
    // 重载
    public void method(double num) {
        System.out.println(3*num);
    }
}

可见性修饰符: public,protected,默认,private
在这里插入图片描述

  • 关键字 final 可以用于声明常量,表示常量不会改变。如final int a = 1;则a的值不能被改变
  • final int[] a = new int[5]; 不能修改它的地址。
  • 使用 final 修饰的类是终极类,不能被继承。例如,String、StringBuffer 和 StringBuilder 类都使用了关键字 final 修饰,因此这些类不能被继承。
  • 使用 final 修饰的方法不能被子类重写。

2.9 Object类的部分方法

toString

  • 方法定义:public String toString()
  • 该方法返回一个代表该对象的字符串。

equals

  • 方法定义:public boolean equals(Object obj)
  • 如果不指定,返回的结果就是比较两者的地址是否一致。
  • 重写了equals的方法也要重写hashCode方法!以确保相同的元素有相同的hashCode
public class Main {
    public static void main(String[] args) {
        A a1=new A(1);
        A a2=new A(1);
        System.out.println(a1==a2);
        System.out.println(a1.equals(a2));
    }
}

class A {
    int a;
    public A(int a){
        this.a = a;
    }
    
    public boolean equals(A other) {
        return this.a==other.a;
    }
}

hashCode

  • 方法定义:public native int hashCode()
  • 关键字native表示实现方法的编程语言不是 Java。
    ⚠️ 如果两个对象相同,它们的散列码一定相同,因此如果在子类中重写了 equals 方法,必须在该子类中重写 hashCode 方法,以保证两个相等的对象对应的散列码是相同的。[重写了equals的方法也要重写hashCode方法!以确保相同的元素有相同的hashCode]

finalize

  • 方法定义:protected void finalize() throws Throwable
  • 该方法用于垃圾回收。如果一个对象不再能被访问,就变成了垃圾,finalize 方法会被该对象的垃圾回收程序调用。
  • 该方法的默认实现不做任何事,如果必要,子类应该重写该方法。

clone: 对应类需要实现java.lang.Cloneable接口,才能被复制。

  • 方法定义:protected native Object clone() throws CloneNotSupportedException
  • 该方法用于复制一个对象,创建一个有单独内存空间的新对象。
  • 不是所有的对象都可以复制,只有当一个类实现了java.lang.Cloneable接口时,这个类的对象才能被复制。

getClass

  • 方法定义:public final native Class<?> getClass()
  • 该方法返回对象的元对象。元对象是一个包含类信息的对象,包括类名、构造方法和方法等。
  • **一个类只有一个元对象。**每个对象都有一个元对象,同一个类的多个对象对应的元对象相同。
  • 相当于找到对应元素所属的类别信息。

2.10 抽象类和接口

抽象类: abstract (public abstract void …) - extends - @Override
接口: interface - implements, interface A extends B,C
一个正常类别的类应该完全实现接口中所描述的所有方法,否则应该定义为一个抽象类。

抽象类: 类似于继承的想法,用extends abstractClass

  • 包含抽象方法的类必须声明为抽象类,但是抽象类可以不包含抽象方法。
  • 如果一个抽象父类的子类不能实现所有的抽象方法,则该子类也必须声明为抽象类。

例子:

public class Main {
    public static void main(String[] args) {
        Person p1 = new Student();
        Person p2 = new Teacher();
        p1.run(); // Student.run
        p2.run(); // Teacher.run
    }
}

abstract class Person {
	// 一旦含有抽象方法,一定要写作"abstract class"
    public abstract void run();
    // 抽象类可以含有不抽象的方法
    public static void print(){
        System.out.println("haha");
	}
}

class Student extends Person {
    @Override
    public void run() {
        System.out.println("Student.run");
    }
}

class Teacher extends Person {
    @Override
    public void run() {
        System.out.println("Teacher.run");
    }
}

接口interface nameInterface, ... implements ...

  • 接口是抽象类的进一步抽象,接口只包含可见性为 public 的常量和抽象方法,不包含变量和具体方法
  • 一个类只能继承一个父类,但对接口允许多重继承。一个接口可以继承多个接口,这样的接口称为子接口。
  • 在接口中,可以定义default方法。
interface Person {
    String getName();
    default void run() {
        System.out.println(getName() + " run");
    }
}

class Student implements Person {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

// 则 Person a =  new Student("FeHanHan");
// a.run(); // FeHanHan run.

继承类和接口的区别

  • 相同点:
    • 抽象类和接口都不能通过 new 操作符创建实例,只能通过具体类创建实例。
  • 不同点:
    在这里插入图片描述
  • 抽象类的变量没有限制,接口只包含常量,即接口的所有变量必须是 public static final
  • 抽象类包含构造方法,子类通过构造方法链调用构造方法,接口不包含构造方法
  • 抽象类的方法没有限制,接口的方法必须是 public abstract 的实例方法
  • 一个类只能继承一个父类,但是可以实现多个接口。一个接口可以继承多个接口

接口继承

interface Hello {
    void hello();
}
// 此时Person有3个抽象方法public abstract
interface Person extends Hello {
    void run();
    String getName();
}

2.10 例子:自定义比较方法 Comparable,

Comparable
… implements Comparable
public int compareTo(T o);
Arrays.sort(T[ ]); //(默认从小到大)
Comparator
class myComparator implements Comparator{
@Override
public int compare(T o1, T o2);
}
Arrays.sort(T[ ],new myComparator);

Comparable 定义

public interface Comparable<T>{
	// 1:表示this 大于 o
	// -1:表示小于
	// 0:表示相等
	public int compareTo(T o);
}

实现用Arrays.sort()对类别排序。

public class Main {
    public static void main(String[] args) {
        A[] tmp = new A[3];
        tmp[0] = new A(3);
        tmp[1] = new A(2);
        tmp[2] = new A(1);
        Arrays.sort(tmp);
        for(int i=0;i<3;i++){
            System.out.println(tmp[i].a);
        }
    }
}

class A implements Comparable<A>{
    int a;
    public A(int a){
        this.a = a;
    }    
    public int compareTo(A other){
        return this.a-other.a;
    }
}

Comparator:用于原Class没有implements Comparable的附加品。

public class Main {
    public static void main(String[] args) {
        A[] tmp = new A[3];
        tmp[0] = new A(3);
        tmp[1] = new A(2);
        tmp[2] = new A(1);
        myComparator comparator = new myComparator();
        Arrays.sort(tmp, comparator);
        for(int i=0;i<3;i++){
            System.out.println(tmp[i].a);
        }
    }
}

class A{
    int a;
    public A(int a){
        this.a = a;
    }
}

class myComparator implements Comparator<A>{
    /**
     * 如果o1小于o2,返回一个负数;如果o1大于o2,返回一个正数;如果他们相等,则返回0;
     */
    @Override
    public int compare(A o1, A o2) {
        return o1.a-o2.a;
    }
}

2.11 基本数据类型和包装类

为什么要引入包装类?

  • 因为Java8种基本数据类型不属于对象,而有些时候我们需要把对象作为参数,因此要把基本数据类型包装成为对象,对应的类称为包装类。
  • 包装类的实例都是不可变的,一旦创建了包装对象,其内部的值就不能再改变。

自动装箱和自动拆箱
从 JDK 1.5 开始,基本数据类型和包装类之间可以进行自动转换。
基本数据类型 --装箱-> 包装类
基本数据类型 <-拆箱-- 包装类
在这里插入图片描述

2.12 面向对象思想

2.12.1 类的关系

关联

在 Java 代码中,关联可以用数据域和方法进行实现,一个类中的方法包含另一个类的参数。

class Depositor {
    private List<Bank> bankList; // 用来存储一个用户在不同银行的信息
    public Depositor() {
        bankList = new ArrayList<Bank>();
    }
    public void addBank(Bank account) {
        bankList.add(account);
    }
}


class Bank {
    private List<Depositor> depositorList; // 用来存储每一个用户在该银行的信息
    public Bank() {
        depositorList = new ArrayList<Depositor>();
    }
    public void addUser(Depositor depositor) {
        depositorList.add(depositor);
    }
}
聚集和组合 “has-a”
  • 如果一个对象被一个聚集对象所专有,该对象和聚集对象之间的关系就称为组合,反之就是聚集关系。
  • 下面的例子中,Car这个对象可以被多个聚集类所拥有,所以Driver 和 Car 之间的关系是聚集关系。
  • 由于一张身份证只能被一个人拥有,因此 Driver 和 IDCard 之间的关系是组合关系。
// 聚集类
class Driver {
    private Car car; // 聚集关系
    private IDCard idCard; // 组合关系 
}
// 被聚集类
class Car {
}
class IDCard {
}
依赖 “client-supplier”
  • 依赖指两个类之间一个类使用另一个类的关系,前者称为客户(client),后者称为供应方(supplier)。
  • 在 Java 代码中,实现依赖的方式是,客户类中的方法包含供应方类的参数。
// Date是supplier,client是Calendar
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
    public final void setTime(Date date) {
        setTimeInMillis(date.getTime());
    }
}
继承 “is-a”

强是(strong is-a)关系描述两个类之间的直接继承关系,弱是(weak is-a)关系描述一个类具有某些属性。

  • 强是关系可以用类的继承表示,弱是关系可以用接口表示。

2.12.2 类的设计原则

内聚性

同一个类/模块的所有操作应该有高度关联性,支持共同的目标,只负责一项任务,即单一责任原则,称为高内聚
不同类/模块之间的关联程度应该尽量低,对一个类/模块的修改应该尽量减少对其他类/模块的影响,称为低耦合

换言之,
“高内聚”:同一模块负责一项任务。
“低耦合”:不同模块关联程度尽可能低。

封装性

类中的数据域应该使用 private 修饰符隐藏其可见性,避免从外部直接访问数据域。
如果需要从外部读取数据域的值,则提供读取器方法。如果需要从外部修改数据域的值,提供设置器方法。

  • 如果一个方法只在类的内部使用,则应该对该方法使用 private 修饰符,避免从外部调用该方法。
实例和静态

依赖于类的具体实例的数据域和方法应声明为实例数据域和实例方法,反之应声明为静态数据域和静态方法。

  • 如果一个数据域被所有实例共享,该数据域应声明为静态数据域。
  • 如果一个方法不依赖于具体实例,该方法应声明为静态方法。

所有实例都有的共性:静态。

继承和聚集

继承模拟是(is-a)关系,聚集模拟具有(has-a)关系。应考虑两个类之间的关系为是(is-a)关系还是具有(has-a)关系,决定使用继承(父类完全包含于子类)或聚集(只是用作一个属性)。

2.13 序列化和反序列化 ❓

把对象转换成字节序列的过程称为对象的序列化,把字节序列恢复成对象的过程称为对象的反序列化

可序列化接口 Serializable

  • 只有当一个类实现了 Serializable 接口时,这个类的实例才是可序列化的
  • Serializable 接口是一个标识接口,用于标识一个对象是否可被序列化,该接口不包含任何数据域和方法
  • 如果试图对一个没有实现 Serializable 接口的类的实例进行序列化,会抛出 NotSerializableException 异常。
  • 将一个对象序列化时,会将该对象的数据域进行序列化,不会对静态数据域进行序列化

关键字 transient

  • 如果一个对象的类实现了 Serializable 接口,但是包含一个不可序列化的数据域,则该对象不可序列化。
  • 在序列化的过程中,加了关键字 transient 的数据域将被忽略。
    • 为了使该对象可序列化,需要给不可序列化的数据域加上关键字 transient。
    • 如果一个数据域可序列化,但是不想将这个数据域序列化,也可以给该数据域加上关键字 transient。

2.14 反射机制 ❓

  • Java 反射机制的核心是在程序运行时动态加载类以及获取类的信息,从而使用类和对象的数据域和方法。

Class 类
Class 类的作用是在程序运行时保存每个对象所属的类的信息,在程序运行时分析类。一个 Class 类型的对象表示一个特定类的属性。

有三种方法可以得到 Class 类型的实例。

  1. 第一种方法是对一个对象调用 getClass 方法,获得该对象所属的类的 Class 对象。
  2. 第二种方法是调用静态方法 Class.forName,将类名作为参数,获得类名对应的 Class 对象。 [最常用]
  3. 第三种方法是对任意的 Java 类型 T(包括基本数据类型、引用类型、数组、关键字 void),调用 T.class 获得类型 T 对应的 Class 对象,此时获得的 Class 对象表示一个类型,但是这个类型不一定是一种类。

Class 类的常用方法

  • Class 类中最常用的方法是getName,该方法返回类的名字。
  • Class 类中的 getFields、getMethods 和 getConstructors 方法分别返回类中所有的公有(即使用可见修饰符 public 修饰)的数据域、方法和构造方法。
  • Class 类中的 getDeclaredFields、getDeclaredMethods 和 getDeclaredConstructors 方法分别返回类中所有的数据域、方法和构造方法(包括所有可见修饰符)。
  • Class 类中的 getField、getMethod 和 getConstructor 方法分别返回类中单个的公有(即使用可见修饰符 public 修饰)的数据域、方法和构造方法。
  • Class 类中的 getDeclaredField、getDeclaredMethod 和 getDeclaredConstructor 方法分别返回类中单个的数据域、方法和构造方法(包括所有可见修饰符)。

3. 异常处理

3.1 三类错误:语法错误、逻辑错误和运行错误(需要异常处理)

编程错误可以分成三类:语法错误、逻辑错误和运行错误

  1. 语法错误(也称编译错误)是在编译过程中出现的错误,由编译器检查发现语法错误。
  2. 逻辑错误指程序的执行结果与预期不符,可以通过调试定位并发现错误的原因。
  3. 运行错误是引起程序非正常终端的错误,需要通过异常处理的方式处理运行错误。

3.2 异常类型 Exception <-- Throwable

Java 的异常类型是 Exception 类,它是 Throwable 类的子类。
Exception 类描述由程序和外部环境引起的错误,可以通过程序捕获和处理这些异常。
在这里插入图片描述

异常处理的操作:声明异常(throws)、抛出异常(throw)和捕获异常(Try-catch)

声明异常(throws)

如果一个方法可能抛出异常,则需要在方法声明中使用关键字 throws 声明异常。如果一个方法可能抛出多种类型的异常,则需要在关键字 throws 之后依次列举可能抛出的异常类型。

public class Main {
    public static void main(String[] args) {
        try {  
            function1();  
        } catch (Exception e) {  
            System.out.println("Error "+e.getMessage());  
        }  
    }
    
    public static void function1() throws NumberFormatException{  
        System.out.println(Double.parseDouble("abc"));  
        System.out.println("第二条语句。");        
    }  
}

抛出异常(throw)

如果程序检查到错误,则可以创建一个异常的实例并抛出该异常实例。使用关键字 throw 抛出异常。

public class Main {
    public static void main(String[] args) {
        try {  
            function(null);  
        } catch (Exception e) {  
            System.out.println("Error "+e.getMessage());  
        }  
    }
    
    public static void function(String s) {
        if (s==null) {
            throw new NullPointerException();
        }
    }
    
}

捕获异常(Try-catch)

Java 提供了 try-catch 块的结构,用于捕获并处理异常。该结构包括 try 块和 catch 块两部分,分别以关键字 try 和 catch 开始,catch 块包含特定异常类型的参数。在 try 块中包含可能抛出异常的语句,当 try 块中的一个语句抛出异常且和 catch 块的参数的异常类型一致时,try 块中剩下的语句会被跳过,catch 块中的语句会被执行。

  • JVM在捕获到异常后,会从上到下匹配catch语句,匹配到某个catch后,执行catch代码块,然后不再继续匹配。
  • 即只有一个catch会被执行。子类异常对应的 catch 块必须出现在父类异常的 catch 块之前,否则会出现编译错误。
  • 在 finally 子句中的代码总是会被执行,无论异常是否出现或者被捕获。因此在 finally 子句中放置必须被执行的代码。
try{
	...
}catch (Exception ex) {
	System.out.println(ex.getMessage());
} finally {
	System.out.println("The finally block");
}
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String str1 = scanner.next();
        String opStr = scanner.next();
        String str2 = scanner.next();
        scanner.close();
        System.out.println(calculate(str1, opStr, str2));
    }

    public static int calculate(String str1, String opStr, String str2) {
        int result = 0;
        try {
            int num1 = Integer.parseInt(str1);
            int num2 = Integer.parseInt(str2);
            char op = opStr.charAt(0);
            switch (op) {
            case '+':
                result = num1 + num2;
                break;
            case '-':
                result = num1 - num2;
                break;
            case '*':
                result = num1 * num2;
                break;
            case '/':
                result = num1 / num2;
                break;
            default:
                throw new RuntimeException("Invalid expression");
            }
            return result;
        } catch (NumberFormatException ex) {
            System.out.println("NumberFormatException");
        } catch (ArithmeticException ex) {
            System.out.println("ArithmeticException");
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        } finally {
            System.out.println("The finally block");
        }
        return result;
    }
}

其他:


1.java源文件编译生成.class文件(字节码)
2.字节码由JVM解释运行。
因为java程序既要编译同时也要经过JVM的解释运行,所以java被称为半解释语言。

JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。

具体来说 JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,需要安装 JDK。


Java的集合类定义在java.util包中,支持泛型,主要提供了3种集合类,包括List,Set和Map。Java集合使用统一的Iterator遍历,尽量不要使用遗留接口:

Hashtable:一种线程安全的Map实现;
Vector:一种线程安全的List实现;
Stack:基于Vector实现的LIFO的栈。

问题:

  1. JDK是什么?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值