Java语言编程基础(中)

Java编程语言面向对象部分

一、类和对象

A、类的定义

1、对象是类的实例,类是对象的抽象
2、类可以认为是一种自定义的数据结构,可以使用类来定义变量
3、常见的类成员:属性、行为(即类中的成员变量和成员方法)、构造器、代码块和内部类(包括接口、枚举)

如何定义类:

[修饰符] class 类名{
	成员变量; // field(属性、域) 描述类具有的特性,对象的状态
	成员方法; // method(方法)描述类具有的功能,对象的行为
	构造器;
}
class Person{
    //属性
    String name;
    int age;
    boolean isMale;

    //方法
    public void eat(){
        // 具体实现
    }
    public void sleep(){
        // 具体实现
    }
    public void code(){
        // 具体实现
    }
}

B、对象的创建和操作

1、创建类的对象 new
2、通过“ 对象.属性 ”或者“ 对象.行为 ”调用对象的结构

public class Test {
    public static void main(String[] args){
        // 创建类的对象 = 类的实例化 = 实例化类
        Person p1 = new Person();

        /* 调用对象的属性和方法 */
        p1.age = 22;
        p1.name = "Test";
        p1.isMale = true;

        p1.eat();
        p1.sleep();
        p1.code();
    }
}

成员变量(属性)和局部变量

相同点:
1、声明变量的格式一样:数据类型 变量名 = 变量值
2、都是先声明再使用
3、都有对应的作用域范围

不同点:
1、声明的位置不同:
成员变量:声明在类的一对 { } 中
局部变量:声明在方法中、方法形参中、代码块内、构造器形参中、构造器内部
2、权限修饰符不同:
成员变量:在声明时,可以在数据类型前加上权限修饰符,指明其使用的权限
常见的权限修饰符:private、public、protected、缺省(没有写的时候就是缺省)
局部变量:不可以使用权限修饰符
3、默认初始化值:
成员变量:根据类型都有对应的默认初始化值
局部变量:没有默认初始化值,所以在使用局部变量前要先赋值(特别地,形参在调用的时候赋值就可)
4、在内存中加载的位置:
成员变量:加载在堆空间中(非static)
局部变量:加载在栈中

C、方法

表示程序中可重复使用的一段代码集合来完成某个功能

1、方法的定义

[修饰符] 返回值类型 方法名( [参数类型 形参1, 参数类型 参数2......] ){
	// 方法体
	[ return 返回值]; // 当返回值类型不是void的时候返回
}

参数是可有可无的
return 关键字就是用来返回值的,如果返回值类型不是void,方法中一定要执行到 return 语句

定义的位置:

1、在类中,其他方法之外(不能在方法中再定义一个方法)
2、方法之间的声明顺序无规定

2、方法参数的传递

形参:定义在方法小括号内的参数
实参:调用方法时,写在方法括号中,实际传递给方法的参数

是以值传递的方式传递的
1、参数是基本数据类型:直接传递实参的副本
2、参数是引用数据类型:传递的是实参的十六进制地址的副本

3、方法的重载

在一个类中,允许存在一个以上的重名方法,只要它们的参数个数或者类型不同即可。

规则:两同一不同

两同:同类中,相同的方法名
一不同:参数列表不同(注意参数顺序不同也认为参数列表不同)

判断是否是重载和方法的权限修饰符,返回值类型、方法体实现和参数名都没有关系

4、可变个数形参的方法

在定义方法的时候,在最后一个形参的类型后加三个点(…)表示该形参可以接受多个参数值;

public class Exe {
    public int getSum(int... num){
        return 0;
    }

    public static void main(String[] args) {
        Exe exe = new Exe();
		/* 都不会报错 */
        exe.getSum(1,2,3);
        exe.getSum();
    }
}

一个方法最多只有一个可变参数,而且必须放在参数列表最后

5、方法的递归

一个方法的实现中又调用了方法本身;
递归要向已知的方向递归,否则会造成无穷递归

import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        int n;
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        /*
        * 要调用Test类中的getSum方法
        * 因为getSum不是static方法
        * 所以需要new一个Test的对象来调用
        * */
        Test test = new Test();
        int sum = test.getSum(n);
        System.out.println("前" + n + "个数的和为:" + sum);
    }
    public int getSum(int n){
        if (n==1) return 1; //递归结束的条件
        else return getSum(n-1) + n;
    }
}

递归的关键就在于:递归边界和递归式

D、封装和隐藏

把对象封装成一个整体,隐藏对象内部的信息和实现的细节,对外暴露方法实现对内部信息的操作和访问
优点:安全性、重用性、高内聚低耦合
通过不同的权限修饰符,可以实现对成员变量和方法的隐藏

public class Person {
    /* private修饰,字段私有 */
    private String name;
    private int age;
    
    /* 提供public方法来访问私有属性 */
    public String getName(){ return name;}
    public int getAge(){return age;}
}

E、构造器

1、构造器的作用

创建对象(必须和 new 一起使用,通过 new 关键字来调用某个类的构造器)

Person p1 = new Person(); //这里最后的 Person() 就是构造器

在创建对象的时候,对成员变量进行初始化赋值(手动赋值)

/* 自己定义的构造器 */
public Person(int age){
	this.age = age;
}

//创建对象时赋值
Person p2 = new Person(12);

2、特点

构造器名和类名相同
不用定义返回值类型
构造器体中不需要 return 语句
一个类中至少存在一个构造器(默认或者显式定义的)

3、默认构造器

当没有显式的定义一个构造器的时候,系统会提供一个默认的构造器
特点:
没有形参
没有构造器体
访问权限修饰符和所在的类的访问权限修饰符相同

public Person(){
	// 没有构造器体
	}

如果人为显式的定义了构造器,默认的构造器就没有了

4、构造器的重载

自己定义构造器,实现重载
权限修饰符 类名( 形参列表 ){ 方法实现 }

F、成员变量的赋值顺序

1、默认初始化
2、显式初始化
3、构造器中初始化
4、通过对象调用成员变量或者方法赋值

G、this关键字

当一个对象创建以后,JVM会分配一个引用自身的引用:this(通俗可以理解为当前对象的)

1、this存在的位置

构造器中,或者实例方法中

2、this的使用

在类中我们使用 this 来访问方法和属性 this.方法或者== this.属性==
通常情况下我们省略 this,但是当方法的形参和类的属性同名时,this 不能省略,来指明该变量是成员变量,而不是形参

public void setAge(int age) {
	this.age = age;
}

this来调用构造器
this( [ 参数列表 ] )实现调用构造器,但是这句话必须作为构造方法的第一条
this 调用构造器只能构造其他构造器,不能调用自身,而且调用的其他构造器不能形成闭环
构造器内部不能写两条 this 调用构造器,最多只能声明一个 this 来调用其他构造器

public Person(){}
public Person(int age){
	this(); // 调用第1个构造器
	this.age = age;
}
public Person(int age,String name){
	this(age); // 调用第2个构造器
	this.age = age;
	this.name = name;
}

H、package、import关键字

1、package关键字

作用:把一个类放在指定的包结构下
包命名的规则:域名倒写.模块名.组件名
必须放在首行
同一个包下不可以命名同名的接口、类

//package 包名.子包名.子子包
package com.example.hello;

2、import关键字

导入指定包下的类、接口
类的全限定名:包名.类名 例如 java.util.Arrays
import语句应该在package之后,类定义之前
Java默认为所有源文件导入 java.lang 包下的所有类,但不包括其子包下的类
如果在源文件中使用不同包下的同名类,那么需要用类的全限定名使用

import java.util.Arrays; // 导入 java.util 包下的 Arrays 类
import java.util.*; // 导入 java.util 包下所有被当前类使用到的类

二、继承

A、继承

1、定义

子类对父类进行扩展,从一般到特殊的关系,父类放共性,子类放特性
继承可以理解为 is-a 的关系
子类继承父类中的所有属性和方法

语法格式
使用 extends 来继承父类

class A extends B{ }

A:子类、派生类、subclass
B:父类、超类、基类、superclass

2、作用

减少了代码的冗余,提高了代码的复用性
便于功能的拓展

3、注意

一个类可以被多个子类继承
但是一个子类只能有一个父类,不支持多继承
类是可以多重继承的,分为直接父类和间接父类
java.lang.Object 类是 Java 语言的根类,任何类都是 Object 类的子类 / 间接子类

B、方法的重写Override

1、定义

在子类继承父类以后,对父类中的同名、同参数的方法进行覆盖
不能重写父类中 private 、final 修饰的方法

2、原则(一同两小一大)

一同:重写的方法和被重写的方法的方法名和形参列表相同
一大:子类重写的方法的访问权限比父类被重写的方法的访问权限更大或相等(防止父类的方法失传)
两小:
1、子类方法的返回值类型和父类方法的返回值类型相同或者是父类的子类
(父类是 void 子类也只能是 void,返回值类型是基本数据类型,子类必须和父类类型相同 )
2、子类方法声明抛出的异常类和父类方法声明抛出的异常类相同或者是其子类(运行时异常除外)

子类方法中声明抛的异常小于或等于父类方法声明抛出的异常
子类方法可以同时声明抛出多个属于父类方法声明抛出异常类的子类

C、super关键字

1、定义

代表当前对象的父类对象
可以用来调用父类的成员变量、方法、构造器

2、用法

1、在子类的方法或者构造器中,通过 super.成员变量或者 super.方法,显式的调用父类中的成员变量和方法,通常 super 是省略的

特殊情况:
a、当子类和父类定义了同名的成员变量的时候,在子类中想调用父类中的成员变量必须使用 super 关键字表明调用的是父类的成员变量
b、当子类重写了父类中的方法,在子类中想调用父类的被重写的方法时,必须使用 super 关键字表明调用的是父类中被重写的方法

2、在子类的构造器中使用 super(形参列表) 来调用父类中声明的构造器

注意点:
a、super(形参列表)这句话必须放在子类构造器的首行
b、在类的构造器中,this(形参列表)和 super(形参列表)只能二选一,不能同时使用
c、在构造器的首行没有显式的声明 this(形参列表)或 super(形参列表)的时候,默认调用父类中的无参构造器,即子类构造器首行默认有 super()语句

三、多态性

A、定义

Java 引用变量有两种类型:
1、编译时类型:由声明该变量的时候使用的类型决定
2、运行时类型:由实际赋给该变量的对象决定
当编译时类型和运行时类型不一致的时候,就会出现多态

父类的引用指向子类的对象,或者说,子类对象赋给父类对象
可以理解为引用类型的向上转型

//Person是父类,Man是子类

/* 正常定义 */
Person p1 = new Person();

/* 多态性体现 */
Person p2 = new Man();

这里 p2 调用方法编译时看的是 Person 中的方法
如果子类重写了父类的方法,执行时执行的是子类的方法
编译看左边,运行看右边

/* Person 类*/
public class Person {
    public void eat(){ System.out.println("这是Person类中eat"); }
    public void walk(){ System.out.println("这是Person类的walk"); }
}

/* Man 类*/
public class Man extends Person {
    @Override
    public void eat() {
        super.eat(); //这里会调用父类的方法
        System.out.println("这是Man类中的eat");
    }
    @Override
    public void walk(){
        super.walk(); //这里会调用父类的方法
        System.out.println("这是Man类中的walk");
    }

    public void code(){
        System.out.println("这是Man特有的");
    }
}
/* Test 类 */
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person();
        Person p2 = new Man();
        Man man = new Man();
        /* 这是Person类中eat
           这是Person类的walk
        */
        p1.eat();
        p1.walk();

        /* 这是Person类中eat //这是因为子类重写使用了 super 关键字调用父类的方法
           这是Man类中的eat
           这是Person类的walk //这是因为子类重写使用了 super 关键字调用父类的方法
           这是Man类中的walk
        */
        p2.eat();
        p2.walk();
        //p2.code();  //p2调用Man中特有的方法会报错
        
        /* 这是Person类中eat
           这是Man类中的eat
           这是Person类的walk
           这是Man类中的walk
           这是Man特有的
        */
        man.eat();
        man.walk();
        man.code();
    }

B、前提

1、继承关系的出现,子类和父类
2、通常会有子类的方法的重写

C、作用

屏蔽不同子类或实现类之间的实现差异,从而可以做到通用编程

public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        
        /* 狗进食
           狗叫
        */
        test.func(new Dog()); //使用 dog 类型的对象去调用 func 方法
    }
	
	// 如果没有多态性的应用
	// 这里就还需要定义参数为 Dog、Cat 类型的func方法进行重载,实现不同类型对象的func方法
    public void func(Animal animal){
        animal.eat();
        animal.shout();
    }
}

class Animal{
    public void eat(){ System.out.println("动物进食"); }
    public void shout(){ System.out.println("动物叫"); }
}

class Dog extends Animal{
    public void eat(){ System.out.println("狗进食"); }
    public void shout(){ System.out.println("狗叫"); }
}
class Cat extends Animal{
    public void eat(){ System.out.println("猫进食"); }
    public void shout(){ System.out.println("猫叫"); }
}

D、注意

对象的多态性只适用于方法,但不适用于成员变量,成员变量就是看左边

编译时会检查父类(编译时类型)是否具有该实例方法,若不存在会报错
运行时会先从子类(运行时类型)中寻找实例方法,找到就执行该方法,找不到就去父类中找

有了对象的多态性,内存实际上是加载了子类中的特有的成员变量和方法的,但是由于变量被申明为父类,导致编译时,只能调用父类的成员变量和方法,不能调用子类特有的成员变量和方法

所以当对象有多种形态的时候,如果想调用子类特有的方法的时候,我们可以对引用变量进行向子类的强制类型转换

E、引用型变量的强制类型转换

语法格式:(Type) object
将父类往子类去转型

引用类型之间的转换只能在具有继承关系的两个类之间进行,否则会编译报错

Animal animal = new Animal();
Dog dog  = new Dog();
Cat cat = new Cat();
//dog = (Dog)cat; // 编译报错

只能将一个引用变量的类型强制转换为该变量实际引用的对象可以被定义的类型,否则会出现 ClassCastException 异常

Animal animal = new Animal();
Animal dog  = new Dog();
Animal cat = new Cat();
// 编译不报错 但是运行会出现异常
dog = (Dog)cat;

F、instanceof 运算符

判断该对象是否是某一个类 / 子类 / 实现类的实例,如果是,返回 true

String str = "ABC";
str instanceof Math; // false

四、补充知识点

A、Object 类

1、Object 类是所有 Java 类的根父类
2、如果在类的声明中没有使用 extends 关键字,则认为默认父类是 java.lang.Object 类

1、equals()方法

适用于引用型数据类型

在 Object 类中 equals() 方法的定义和 " == " 的作用是相同的,比较的是两个对象指向的地址值是否相同

Animal animal1 = new Animal();
Animal animal2 = new Animal();
System.out.println(animal2 == animal1); //false
System.out.println(animal1.equals(animal2)); //false

像 String、Date 、File、包装类等都重写了 equals() 方法,重写以后比较的就是对象的实体内容是否相同

String str1 = new String("ABC");
String str2 = new String("ABC");
String str3 = new String("ABCD");
System.out.println(str1.equals(str2)); //true
System.out.println(str1.equals(str3)); //false
System.out.println(str2.equals(str3)); //false

如果是我们自定义的类想实现 equals() 比较实体内容,可以对该方法进行重写

2、toString()方法

当我们输出一个引用对象的时候,相当于调用了 toString() 方法

Animal animal = new Animal();
System.out.println(animal); //Example.Animal@1b6d3586
System.out.println(animal.toString()); //Example.Animal@1b6d3586

像 String、Date 、File、包装类等都重写了 toString() 方法,所以在调用 toString() 方法,调用的时候返回的对象的实体内容

String str1 = new String("ABC");
String str2 = new String("ABC");
String str3 = new String("ABCD");
System.out.println(str1); //ABC
System.out.println(str1.toString()); //ABC
System.out.println(str2); //ABC
System.out.println(str2.toString()); //ABC
System.out.println(str3); //ABCD
System.out.println(str3.toString()); //ABCD

如果是我们自定义的类想实现 toString() 比较实体内容,可以对该方法进行重写

B、包装类

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
charCharacter
floatFloat
doubleDouble
booleanBoolean

1、装箱

装箱:
把基本数据类型转换成对应的包装类的对象
借用包装类的构造器实现转换

public static void main(String[] args) {
	int i = 1;
    Integer integer = new Integer(i);
    //System.out.println(i.toString()); // 报错
    System.out.println(integer.toString()); //重写过 输出 1
    }

特别的:
1、数据型的在调用构造器的时候传入的参数可以是 String 类型的,但是只能是数字

Integer integer1 = new Integer("123"); //赋值 123
Integer integer2 = new Integer("123abc"); //不对

2、Boolean型的在调用构造器的时候,只要传入的字符串内容和 true 字母一样就是 true 即可

public class Test {
    public static void main(String[] args) {
        Boolean b1 = new Boolean("true"); //true
        Boolean b2 = new Boolean(true); //true
        Boolean b3 = new Boolean("tRue"); //true
        Boolean b4 = new Boolean("true123"); //false

        System.out.println(b1);
        System.out.println(b2);
        System.out.println(b3);
        System.out.println(b4);
    }
}

2、拆箱

拆箱:
把包装类对象转换成对应的基本数据类型数据
调用包装类的 xxxValue() 方法

Integer integer = new Integer(123);
int i = integer.intValue(); //转化为 int 类型
System.out.println(i); //输出 123

3、自动装箱和自动拆箱

自动装箱:
把一个基本数据类型直接赋给对应的包装类变量或 Object 变量
在底层依然是手动装箱,使用的 Xxx.valueOf() 方法

int i = 10;
Integer integer = i; //自动装箱

自动拆箱:
把包装类对象直接赋给对应的基本数据类型变量
或者包装类对象和基本数据类型变量使用 " == "比较

Integer integer = new Integer(123);
int i = integer; //自动拆箱
int j = 123;
System.out.println( j == integer ); //true 自动拆箱

4、基本数据类型、包装类和字符串之间的转换

a、基本数据类型、包装类转换为字符串

方式一:
利用字符串的拼接运算

int num = 10;
String str = num + " ";

方式二:
使用 String.valueOf() 方法

float f1 = 123.3f;
Double d1 = new Double(123.6);
        
String str1 = String.valueOf(f1);
String str2 = String.valueOf(d1);

方式三:
使用 toString() 方法

Integer integer = new Integer(123);
String str = integer.toString();
b、字符串类型转换为基本数据类型或者包装类

调用包装类的 parseXxx() 方法

String str = "123";
int num = Integer.parseInt(str);

5、包装类的缓存设计

在使用自动装箱的时候,底层调用的是 Xxx.valueOf() 方法,这种方法是带有缓存的

1、Byte, Short, Integer, Long:使用 cache 数组默认缓存 -128 ~ 127 之间的整数自动装箱成的包装类对象
2、Character:使用 cache 数组默认缓存了 0 ~ 127 之间的整数自动装箱成的 Character 类对象。

Integer i = new Integer(1);
Integer j = new Integer(1);
/* 结果为 false
 * 因为这里是 new 了两个对象,地址值不同 */
System.out.println( i == j );

Integer m = 1;
Integer n = 1;
/* 结果为 true
 * 这里采用的自动装箱,-128 ~ 127 的数据都存在 IntegerCache 数组中
 * 这里直接使用的数组中的元素,没有去 new 对象,提升效率 */
System.out.println( m == n );

Integer x = 128;
Integer y = 128;
/* 结果为 false
 * 因为超过数组覆盖的范围,相当于又去 new 了两个对象 */
System.out.println( x == y );

五、代码块

1、概述

代码块用一对 { } 进行定义
用来初始化类、对象
代码块中的变量属于局部变量,仅在代码块的 { } 中有效

2、分类

由于代码块只能使用 static 修饰,所以可以将代码块分为静态代码块和非静态代码块

a、静态代码块

static {System.out.println("这是静态代码块");}

使用 static 修饰,随着类的加载而执行,且只执行一次
所以可以在加载类的时候,对类进行初始化
如果定义了多个静态代码块,按照声明的顺序执行
静态代码块内只能调用静态的成员变量和方法,不能调用非静态的
在同类中,静态代码块的执行优于 main 方法的

b、非静态代码块

{System.out.println("这是代码块");}

随着对象的创建而执行,每创建一个对象就执行一次非静态代码块
所以可以在创建对象时,对对象进行初始化
如果定义了多个非静态代码块,按照声明的顺序执行
非静态代码块的执行晚于静态代码块
非静态代码块内能调用静态的成员变量和方法,也能调用非静态的

3、代码执行顺序

父类静态代码块
子类静态代码块
父类非静态代码块
父类构造方法
子类非静态代码块
子类构造方法

4、成员变量的赋值最终顺序

1、默认初始化
2、显式初始化 / 在代码块中初始化
3、构造器中初始化
4、通过对象调用成员变量或者方法赋值

六、抽象类和抽象方法

A、抽象类

使用 abstract 修饰的类,抽象类是对逻辑的归纳;
有构造器,但是不能直接用来创建对象,只留给子类创建对象时调用;
抽象类中可以没有抽象方法,也可以有;
抽象类中可以有普通方法;
子类继承抽象类,如果抽象类中有抽象方法,那么子类必须重写所有的抽象方法,或者子类也声明为抽象类;

B、抽象方法

使用 abstract 修饰的方法,没有方法体(没有方法体和空方法体不一样)
包含抽象方法的类一定是一个抽象类或者抽象接口
抽象方法不能用 private,final 或 static 修饰

// 这种写法叫没有方法体
public abstract void eat();

七、接口

接口:interface
接口和类是并列的结构
接口定义了一种规范,是对功能的抽象

1、语法格式

[修饰符] interface 接口名 extends 父接口1,父接口2,....{
            常量;
            抽象方法;
            内部类、接口、枚举;
            默认方法或静态方法; // JDK 8 以上允许
}

2、特点

没有构造器,不能实例化
成员变量默认使用 public static final 修饰,全局静态常量
抽象方法默认使用 public abstract 修饰,公共的抽象方法,所以实现类中实现方法必须使用 public 修饰
内部类默认使用 public static 修饰,公共静态内部类

3、关系

类使用 implements 来实现接口,支持多实现:
1、一个类实现了一个或多个接口,这个类必须完全实现所有的抽象方法,否则该类定义为抽象类
2、实现接口方法的时候,必须用 public 修饰

[修饰符] class 实现类名 extends 父类 implements 接口1,接口2...{
}

接口之间可以使用 extends 继承,支持多继承

4、接口的多态声明方式

// 接口名 变量名 = new 该接口的实现类名();
public class Test {
    public static void main(String[] args) {
        Eat eat = new Person();
        eat.eat();

    }
}
interface Eat{ void eat();}

class Person implements Eat{
    public void eat(){System.out.println("人吃饭"); }
}

5、Java 8 接口新特性

1、接口中定义的静态方法,通过接口直接调用静态方法;
2、通过实现类的对象,可以调用接口中的默认方法,如果实现类重写了默认方法,则调用的是重写方法
3、如果子类(实现类)继承的父类和实现的接口中实现了同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数方法。
4、如果实现类实现了多个接口,多个接口中定义了重名重参数的默认方法,实现类在没有重写该方法的时候,会出现接口冲突。
5、在子类或实现类中调用父类、接口中被重写的方法,使用“ 接口名 . super . 方法 ”

interface Interface1{
    default void method(){};
}
interface Interface2{
    default void method(){};
}

class A implements Interface1,Interface2{
    @Override
    public void method() {
        Interface1.super.method();
        Interface2.super.method();
    }
}

八、内部类

1、定义

定义在类A结构中的另一个类B,编译后,每个类都会生成对应的 .class 文件
此时类 B 叫做内部类,类 A 叫做外部类

class Animal {
     //静态成员内部类
    static class Dog { }
	 //非静态成员内部类
    class Bird { }
}

2、分类

a、成员内部类

直接声明在类的内部
根据是否用 static 修饰可以分为静态成员内部类和非静态成员内部类

①作为外部类的成员

1、可以调用外部类的结构
2、可以用 static 修饰
3、可以被四种访问权限符修饰(原本类只能用 public 或者缺省)

②作为内部类

1、可以在类内定义成员变量、方法和构造器等
2、可以被 final 修饰,表示此内部类不能继承
3、可以用 abstract 修饰

b、局部内部类

定义在方法内,代码块中,构造器内的类

3、用法

a、实例化成员内部类对象

public class Test  {
    public static void main(String[] args) {
        // 创建静态成员内部类对象
        Animal.Dog dog = new Animal.Dog();
        dog.show();

        // 创建非静态成员内部类对象
        Animal animal = new Animal();
        Animal.Bird bird = animal.new Bird();
        bird.show();
    }
}

b、在成员类当中区分调用外部类的结构

调用没有重名的成员变量

public void show() {
	System.out.println("这是一只鸟");
	// 调用外部类的非静态成员变量
     Animal.this.eat();
}

如果有重名的成员变量

 // 内部类和外部类有重名的成员变量
public void dispaly(String name){
	System.out.println(name); //形参的 name
    System.out.println(this.name); // 内部类 bird 的 name
    System.out.println(Animal.this.name); // 外部类 Animal 的 name

九、异常

A、概述

Java 把非正常情况分为两种:异常(Exception)和错误(Error)
这两者都继承 Throwable 父类

Error:是 Java 虚拟机都无法解决的严重问题,例如:JVM 系统内部错误、资源耗尽等问题,像堆溢出和栈溢出。一般不编写针对性的代码进行处理。

Exception:其他编程错误或者偶然的外在因素导致的一般性的问题,可以使用针对性的代码进行处理。例如:
空指针异常
试图读取不存在的文件
网络连接中断
数据角标越界

B、编译时异常、运行时异常

1、编译时异常(Checked 异常)

编译器必须处理的异常,因此程序中出现这类异常,必须显式处理,否则编译无法通过;
常见的编译时异常:ParseException、InterruptedException等等

2、运行时异常(Checked 异常)

RuntimeException 及其子类
编译器不要求强制处理的异常,程序中出现这类异常的时候,可以不处理
常见的运行时异常:ArithmeticException、IndexOutOfBoundsException

C、异常处理方式

抛抓模型处理异常

抛:
程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出,一旦该对象抛出,其后的代码将不会继续执行。

抓:
对抛出异常的处理

1、try … catch 异常处理

a、语法结构
public class Demo {
    public static void main(String[] args) {
        try {
            //可能会出现异常的代码
        } catch (要捕获的异常类型A e){
            // 处理异常的代码:记录日志/打印异常信息/继续抛出异常等
        } catch (要捕获的异常类型B e){
            // 处理异常的代码:记录日志/打印异常信息/继续抛出异常等
        } finally {
            // 关闭资源对象、流对象等
        }
    }
}
b、注意点

1、在执行 try 块里的代码块出现出现异常以后,Java 运行时环境收到异常对象,会在 catch 块中寻找处理该异常对象的 catch 块,找到合适的 catch 块,则将异常对象交给 catch 块处理,一旦处理完,则跳出结构,处理后面的代码
2、如果 catch 中的异常有子父类的关系,要注意子类一定要声明在父类的上面

/* 运行结果:
   出现异常!
   2
* */
public class Demo {
    public static void main(String[] args) {
        String str = "123";
        str = "abc";
        try {
            // 出现异常的语句
            int num = Integer.parseInt(str); // 这个语句下面的语句不会被执行
            System.out.println("1");
        } catch (NumberFormatException exception){
            System.out.println("出现异常!");
            System.out.println("2");
        } catch (Exception exception){
            // 这个 catch 块中的代码不会再被执行
            System.out.println("这个块会执行吗?");
        }
    }
}

3、一般我们处理异常会使用 String getMessage() 方法或者是 printStackTrace方法
4、在 try 块中定义的变量,只能在 try 块中使用

public class Demo {
    public static void main(String[] args) {
        String str = "123";
        str = "abc";
        try {
            int num = Integer.parseInt(str);
        } catch (NumberFormatException exception){
            // 输出 For input string: "abc"
            System.out.println(exception.getMessage());
            
            /*  输出堆栈信息
                java.lang.NumberFormatException: For input string: "abc"
	                at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	                at java.lang.Integer.parseInt(Integer.java:580)
	                at java.lang.Integer.parseInt(Integer.java:615)
	                at Demo.Demo.main(Demo.java:12)
	         */
            exception.printStackTrace();
        }
    }
}

c、finally 使用

1、finally 块是可选的
2、finally 中声明的代码一定是会被执行的,即使 catch 中又出现了异常、try 中有 return 语句、catch 中有 return 语句等情况。

2、throws 异常处理

[修饰符] 返回值类型 方法名(参数列表) throws 异常类A,异常类B,....{
	// 方法体
}

1、在可能出现异常的方法上声明可能出现的异常类型,用于表示当前方法不处理异常,提醒该方法的调用者处理异常
2、调用者可以使用 try…catch 处理异常,也可以接着向上抛出异常
3、如果 main 方法也采用 throws 抛出异常,则会交给 JVM 虚拟机处理,虚拟机会打印异常的跟踪栈信息,并终止程序的执行
4、子类方法声明抛出的异常类和父类方法声明抛出的异常类相同或者是其子类

public class Demo {
    public static void main(String[] args) {
        double res;
        try {
            res = Fun(3);
        } catch (ArithmeticException exception){
            exception.printStackTrace();
        }
    }
    public static int Fun(int a) throws ArithmeticException {
        int b = 0;
        return a/b;
    }
}

3、总结

1、如果父类中被重写的方法没有使用 throws 抛出异常,则子类重写的方法也不能使用 throws,如果子类重写的方法有异常,只能使用 try…catch 处理
2、执行的方法A中先后又调用了几个方法,且这几个方法之间是递进执行的关系,建议这几个方法使用 throws 处理异常,A中可以使用 try…catch 进行异常处理

4、使用 throw 自行抛出异常

throw 语句可以单独使用,后面只能跟一个异常对象
有返回值的方法中,可以使用 throw 来避免返回一个空值

throw new 异常类("异常信息");
public class Demo {
    public static void main(String[] args)  {
        Student student = new Student();
        try {
            student.setId(-12);
        } catch (Exception exception){
            System.out.println(exception.getMessage());
        }
    }
}

class Student{
    private int id;
     public void setId(int id) throws Exception {
         if ( id > 0) this.id = id;
         else throw new Exception("输入数据非法");
     }
}

D、用户自定义异常类

class MyException extends RuntimeException{
    // 无参构造器
    public MyException(){
        super();
    }
    // 带一个字符串参数的构造器
    public MyException(String message){
        super(message);
    }
    // message 当前异常的原因/信息;cause 当前异常的根本原因
    public MyException(String message,Throwable cause){
        super(message,cause);
    }
}

自定义 Checked 异常,应继承 Exception
自定义 Runtime 异常,应继承 RuntimeException (推荐)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java编程基础知识包括以下内容: 1. Java语言特点:Java是一种面向对象的编程语言,具有跨平台性、强型和静态型检查、自动内存管理等特点。 2. Java的开发环境:Java的开发环境包括JDK(Java Development Kit)和IDE(Integrated Development Environment),如Eclipse、IntelliJ IDEA等。 3. 基本语法:Java的基本语法包括变量、数据型、运算符、控制流语句(if-else、for循环、while循环等)、数组等。 4. 面向对象编程Java是一种面向对象的编程语言,支持封装、继承和多态等面向对象的特性。和对象是Java程序的基本组成单元。 5. 异常处理:Java提供了异常处理机制,通过try-catch-finally块来捕获和处理异常,保证程序的健壮性。 6. 输入输出:Java提供了丰富的输入输出API,可以通过标准输入输出、文件操作、网络通信等方式进行数据的输入和输出。 7. 集合框架:Java提供了一套集合框架,包括List、Set、Map等常用的数据结构和算法,方便处理和操作数据。 8. 多线程编程Java支持多线程编程,可以通过Thread或Runnable接口创建线程,并使用synchronized关键字实现线程同步。 9. 强大的标准库:Java标准库提供了丰富的API,包括字符串处理、日期时间操作、网络编程、数据库操作等功能。 以上是Java编程的一些基础知识,希望对你有所帮助。如果你有其他问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值