该文章是博主采集于各大博文,用于复习和总结相关知识点,将会持续的收集和更新。

一、 概述

1. JDK版本

目前只维护两个JDK版本,一个是8,一个是11(2018年)

2. Java语言特性

  • 可移植性,跨平台,因为Java有一个JVM虚拟机,虚拟机负责执行字节码文件
  • 健壮性,具有GC,有自动垃圾回收机制

3. Java的加载与执行过程

  • T.java(源文件)通过javac命令变成字节码文件
  • 字节码文件通过类加载器加载到JVM中
  • JVM屏蔽了和操作系统打交道的操作

4. JVM、JDK和JRE的区别

Java虚拟机(JVM)

  • 是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。

JDK

  • 是Java Development Kit,它是功能齐全的Java SDK。它拥有JRE所拥有的一切,还有编译器(javac)和工具(如javadoc和jdb)。它能够创建和编译程序。

JRE

  • 是 Java运行时环境。 它是运行已编译 Java 程序所需的所有内容的集合,包括 Java虚拟机(JVM),Java类库,java命令和其他的一些基础构件。
  • 但是,它不能用于创建新程序。

5. 机器码和字节码的概念与区别?

机器码:机器码是电脑CPU直接读取运行的机器指令,运行速度最快,但是非常晦涩难懂,也比较难编写,一般从业人员接触不到。

字节码:字节码是一种中间状态(中间码)的二进制代码(文件),需要直译器转译后才能成为机器码。

二、Java语言基础

1. 八种基本类型

八种基本数据类型:byte、short、int、long、float、double、boolean、char。

一个字节等于8位

IEE754标准(32位):1位是符号位,8位是阶码用移码表示,23位尾数

2. 字符编码

java支持中文,因为其采用的是 Unicode 编码,使之更趋于国际化

类型可以存放一个汉字, java 中的 char 使用 utf-16 编码

编码名称 解释
ASCII 字符编码 只支持英文字母、标点符号、数字字符等, ASCII 码占用 1 个字节,所以 ASCII 码最多可以表示 256 个字符. 小 a 97 大 A 65,’0’是 48
ISO-8859-1 有称 latin-1,是国际化标准或组织 ISO 制定的,主要为了西欧语言中的字符 编码,和 ASCII 兼容,仍不支持中文
GB2312/GBK/GB18030 主要是汉字编码,三种编码从容量上看是包含关系 简体中文: GBK < GB2312 < GB18030 繁体中文: Big5【大五码】
unicode Unicode 统 一 了 全 世 界 上 的 所 有 文 字 编 码 , unicode 有 几 种 实 现 : UTF-8,UTF-16,UTF-32 java 语言采用的是 Unicode 编码,所以在 java 中标识符也可以使用中文

3. 类型转换

  • 在 java 中基本类型可以相互转换, boolean 类型比较特殊不可以转换成其他类型

  • 转换分为默认转换和强制转换:

    • 默认转换:容量小的类型会默认转换为容量大的类型

      • byte–>short–> int–>long–>float–>double

      • byte、 short、 char 之间计算不会互相转换,首先先转换成 int

  • 强制转换:

    • 将容量大的类型转换成容量小的类型,需要进行强制转换
    • 注意:只要不超出范围可以将整型值直接赋值给 byte, short, char
    • 在多种类型混合运算过程中,首先先将所有数据转换成容量最大的那种,再运算
public class DataTypeTest08
{
    public static void main(String[] args){
        long x = 100L;
        int y = x;//编译不通过
        
        long a = 2147483648L;
        int b = (int)a;
        System.out.println(b);//出现精度丢失问题,大类型-->>小类型会出现问题,输出-2147483648
        
        byte a = 1000;//出现错误, 1000 超出了 byte 的范围
        
        long g = 10;
        int h = g/3;//出现错误,多个数值在运算过程中,会转换成容量最大的类型

        byte h3 = (byte)(int)g/3;//考察优先级,将g先转换成int,再强转成byte,再除以3得到int,赋值错误
        byte h4 = (byte)(int)(g/3);//正确的
        byte h5 = (byte)g/3;//考察优先级,先转换成byte,再运算
        byte h6 = (byte)(g/3);//正确
        short h7 = (short)(g/3);//正确
        
        short i = 10;
        byte j = 5;
        short k = i + j;//错误的,short和byte运算,首先会转换成int再运算
    }
}

4. 运算符

短路与和逻辑与的区别?

短路与比逻辑与智能,短路与效率高。

短路或和逻辑或的区别?

短路或:左边的算子结果是 true,右边的表达式不执行,发生短路

a += 3和 a = a + 3; 是一样的吗?

  • 结论(重点)扩展赋值运算符不改变运算结果的类型。初始类型和最终运算结果类型完全相同。
public class OperatorTest09
{
    public static void main(String[] args){
        byte b = 10;
        //编译错误
        //b = b + 3;
        //修改
        b = (byte)(b + 3);
        System.out.println(b); //13
        b += 3;
        System.out.println(b); //16
        b += 10000; //等同于 b = (byte)(b + 10000);
        System.out.println(b); //32
    }
}

5. 控制语句

switch 语句

  • switch 也称为多重分支,具体格式如下
    switch (表达式) {
    case 值 1:
    语句
    break;
    case 值 2:
    语句
    break;
    default:
    语句
    Break;
    }

  • 说明:

    • 表达式的值只能为: char、 byte、 short、 int 类型(JDK7 以后支持 String), boolean、 long、 float、
    • double 都是非法的
    • break 语句可以省略,但会出现 switch 穿透
    • default 语句也可以省略,一般不建议省略,并且放置在最后

    需求:

    假定系统给定学生的考试成绩,考试成绩可以带有小数。
    假定成绩是合法的[0-100],请根据学生考试成绩判断该
    学生成绩等级:
    [90-100] A
    [80-90) B
    [70-80) C
    [60-70) D
    [0-60) E

    <mark>以上业务只能使用 switch 语句完成,不允许使用 if 语句。</mark>

    public class SwitchTest04 {
        public static void main(String[] args) {
            //考试成绩合法
            double score = 100;
            //开始判断
            int grade = (int) (score / 10);//case条件不能为浮点数
    
            switch (grade) {
                case 10:
                    System.out.println("A");
                    break;
                case 9:
                    System.out.println("A");
                    break;
                case 8:
                    System.out.println("B");
                    break;
                case 7:
                    System.out.println("C");
                    break;
                case 6:
                    System.out.println("D");
                    break;
                default:
                    System.out.println("E");
            }
    
            //重点: case 是可以合并的
            switch (grade) {
                case 10:
                case 9:
                    System.out.println("A");
                    break;
                case 8:
                    System.out.println("B");
                    break;
                case 7:
                    System.out.println("C");
                    break;
                case 6:
                    System.out.println("D");
                    break;
                default:
                    System.out.println("E");
            }
        }
    }
    

for语句

for(;false;){//会出现编译错误,因为无法访问
    System.out.println("呵呵");
}
for(;true;){//死循环
	System.out.println("哈哈");
}

6. 方法

方法的返回值问题

public class MethodTest07
{
    //缺少返回语句,程序编译时无法判断是否能走到else,无法编译通过
    public static int m1(){
        boolean flag = true;
        if(flag){
            return 1;
        }
    }

    //正确
    public static int m2(){
        boolean flag = true;
        if(flag){
            return 1;
        }else{
            return 0;
        }
    }

    //编译错误
    public static int m3(){
        boolean flag = false;
        if(flag){//
            return 1;//return后不能接任何语句
            System.out.println("??????????");
        }
        System.out.println("??????????");
        return 0;
        System.out.println("??????????");
    }
}

三、 面向对象

1. 面向过程与面向对象的区别

  • 面向过程面向过程性能比面向对象高。 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发。但是,面向过程没有面向对象易维护、易复用、易扩展。

  • 面向对象面向对象易维护、易复用、易扩展。 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,面向对象性能比面向过程低

这个并不是根本原因,面向过程也需要分配内存,计算内存偏移量,Java性能差的主要原因并不是因为它是面向对象语言,而是Java是半编译语言,最终的执行代码并不是可以直接被CPU执行的二进制机械码。

而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比Java好。

2. 面向对象特征

封装

封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。

继承

继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。

关于继承如下 3 点请记住:

  1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有
  2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
  3. 子类可以用自己的方式实现父类的方法。(以后介绍)。

多态

所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。

抽象

  • 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。

  • 抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。比如,我们要设计一个学生成绩管理系统,考察学生这个对象时,我们只关心他的班级、学号、成绩等,而不用去关心他的身高、体重这些信息。

3. 参数传递

<mark>所有基本数据类型的都是值传递,其他类型的为址传递</mark>

4. 关键字

this 关键字:

  • this 关键字指的是当前调用的对象, 只能用在构造函数和实例方法内部,还可以应用在成员变量的声明上, static 标识的方法里是不能使用 this 的。

  • 作用:代码复用。

super 关键字

  • 调用父类的构造方法

    • 没有显示地调用 super(); 父类的无参构造方法也执行

    • 必须将 super 放到子类的构造函数的第一语句来调用父类的构造方法

  • 调用父类的成员方法

    • 需要注意: super 只能应用在成员方法和构造方法中,不能应用在静态方法中(和 this 是一样的),如果在构造方法中使用必须放在第一行

为什么会有 super 关键字?

  • 因为子类必须要调用父类的构造方法,先把父类构造完成,因为子类依赖于父类,没有父,也就 没有子
  • 有时需要在子类中显示的调用父类的成员方法

那么我们以前为什么没有看到 super,而且我们也有继承,如: Student 继承了 Person?

  • 因为子类中我们没有显示的调用父类构造方法,那么他会默认调用父类的无参构造方法,此种情况下
    如果父类中没有无参构造方法,那么编译时将会失败

注意构造方法不存在覆盖的概念,构造方法可以重载

static 关键字

可以用来修饰它可以用来修饰的成员变量成员方法被修饰的成员是属于类的,而不是单单是属于某个对象的。也就是说,既然属于类,就可以不靠创建对象来调用了。

static方法是否能重写?

  • 静态的方法可以被继承,但是不能重写,所以abstract修饰的方法是不可同时是static修饰。

  • 语法上子类允许出现和父类只有方法体不一样其他都一模一样的static方法,但是在父类引用指向子类对象时,通过父类引用调用的依然是父类的static方法,而不是子类的static方法。

  • 即:语法上static支持重写,但是运行效果上达不到多态目的

final关键字

final 表示不可改变的含义

  • 采用 final 修饰的类不能被继承
  • 采用 final 修饰的方法不能被覆盖
  • 采用 final 修饰的变量不能被修改
  • final 修饰的变量必须显示初始化
    局部变量: 一旦赋值不能重新赋值
    成员变量: 不能采用系统默认值,必须手动赋值
    如果修饰的引用,那么这个引用只能指向一个对象,也就是说这个引用不能再次赋值,但被指向的对象是可以修改的
  • 构造方法不能被 final 修饰 会影响 JAVA 类的初始化
  • final 定义的静态常量调用时不会执行 static 代码块等相关语句,这是由 java 虚拟机规定的。

修饰引用变量:

  • final 修饰引用变量,主要修饰的是变量的地址,那么这个引用只能指向一个对象,也就是说这个引用不能再次赋值,但被指向的对象是可以修改的
public class FinalTest05 {
    public static void main(String[] args) {
        Person p1 = new Person();
        //可以赋值
        p1.name = "张三";
        System.out.println(p1.name);
        final Person p2 = new Person();
        p2.name = "李四";
        System.out.println(p2.name);
        //不能编译通过
        //p2 采用 final 修饰,主要限制了 p2 指向堆区中的地址不能修改(也就是 p2 只能指向一个对象)
        //p2 指向的对象的属性是可以修改的
        p2 = new Person();
    }
}
class Person {
    String name;
}

final 和 static 联合修饰实例变量==常量 (尽量使用一个静态工具类抽取出常量)

  • 常量名要求全部大写[规范]
  • 常量都是 public static final 的
  • 常量在类加载的时候完成初始化,存储在 JVM 的方法区中
  • 常量是值不可改变的变量

5. 代码块

静态属性和静态代码块按照代码顺序执行,实例代码块和成员属性同理

静态代码块

使用 static 关键字可以定义静态语句块,静态语句块具有以下特点

  • 静态语句块在类加载时执行,在 main 方法执行之前就已经执行了。
  • 类只加载一次,所以静态语句块也是只执行一次
  • 一个类中可以编写多个静态语句块,执行顺序是: 自上而下依次执行。
  • 静态语句块的使用时机:当程序需要在类加载的时候就做一些事情,可以在静态语句块中来实现

实例语句块

实例语句块和静态代码块没有关系,实例语句块有以下特点

  • 实例语句块在构造方法执行之前执行,构造函数执行一次,实例语句块对应执行一次。
  • 每调用一次构造函数之前就会执行一次实例语句块
  • 实例语句块可以编写多个,也是按照自上而下的顺序依次执行。
  • 实例语句块使用时机: 当程序需要在对象初始化时刻就做一些事情,可以在实例语句块中实现
//静态语句块
static{
	System.out.println(1);
}

//实例语句块
{
	System.out.println(1);
}

6. 类的继承

如何实现Java多继承?

使用内部类就可以多继承,严格来说,还不是实现多继承,但是这种方法可以实现多继承所需的功能,所以把它称为实现了多继承。

  • 定义多个内部类,每个内部类都可以继承一个父类

  • 然后定义创建每个内部类的成员变量

  • 在方法中调用成员的方法

class Call {
    public void callSomebody(String phoneNum){
        System.out.println("我在打电话喔,呼叫的号码是:" + phoneNum);
    }
}
class SendMessage {
    public void sendToSomebody(String phoneNum){
        System.out.println("我在发短信喔,发送给 :" + phoneNum);
    }
}
public class Phone {
    private class MyCall extends Call{

    }
    private class MySendMessage extends SendMessage{

    }

    private MyCall call = new MyCall();
    private MySendMessage send = new MySendMessage();

    public void phoneCall(String phoneNum){
        call.callSomebody(phoneNum);
    }

    public void phoneSend(String phoneNum){
        send.sendToSomebody(phoneNum);
    }

    public static void main(String[] args) {
        Phone phone = new Phone();
        phone.phoneCall("110");
        phone.phoneSend("119");
    }
}

继承特征

  • 继承是面向对象的重要概念,软件中的继承和现实中的继承概念是一样的

  • 继承是实现软件可重用性的重要手段,如: A 继承 B, A 就拥有了 B 的所有特性,如现实世界中的儿子继承父亲的财产,儿子不用努力就有了财产,这就是重用性

  • Java 中只支持类的单继承,也就是说 A 只能继承 B, A 不能同时继承 C

  • Java 中的继承使用 extends 关键字,语法格式:

    [修饰符] class 子类 extends 父类 {
        
    }  
    

方法的重载的条件

  • 方法名相同
  • 方法的参数类型,个数,顺序至少有一个不同
  • 方法的返回类型可以不同(不依靠返回类型来区分重载)
  • 方法的修饰符可以不同,因为方法重载和修饰符没有任何关系
  • 方法重载只出现在同一个类中

方法的覆盖(Override)的条件:

  • 必须要有继承关系
  • 覆盖只能出现在子类中,如果没有继承关系,不存在覆盖,只存在重载
  • 在子类中被覆盖的方法,必须和父类中的方法完全一样,也就是方法名, 返回类型、参数列表,
    完全一样
  • 子类方法的访问权限不能小于父类方法的访问权限
  • 子类方法不能抛出比父类方法更多的异常,但可以抛出父类方法异常的子异常
  • 父类的静态方法不能被子类覆盖
  • 父类的私有方法不能覆盖
  • 覆盖是针对成员方法,而非属性

为什么需要覆盖?

  • 就是要改变父类的行为。
  • 方法重写之后,“子类对象”执行的一定是重写之后的方法,也体现了就近原则

7. static、构造方法和父子类的调用顺序

要点

  • 静态的代码块一定比构造方法先执行
  • 如果都是静态代码,一个类里面,按照先后顺序执行,父子之间,父类静态代码块先执行
  • 静态代码只会执行一次,多次 new 新的对象,构造方法,非静态代码块会多次执行
class Parent {
    static {
        System.out.println("父类的静态块");
    }
    private static String staticStr = getStaticStr();
    private String str = getStr();
    {
        System.out.println("父类的实例块");
    }
    public Parent() {
        System.out.println("父类的构造方法");
    }
    private static String getStaticStr() {
        System.out.println("父类的静态属性初始化");
        return null;
    }
    private String getStr() {
        System.out.println("父类的实例属性初始化");
        return null;
    }
}

class Child extends Parent {
    private static String staticStr = getStaticStr();

    static {
        System.out.println("子类的静态块");
    }

    {
        System.out.println("子类的实例块");
    }

    public Child() {
        System.out.println("子类的构造方法");
    }

    private String str = getStr();

    private static String getStaticStr() {
        System.out.println("子类的静态属性初始化");
        return null;
    }

    private String getStr() {
        System.out.println("子类的实例属性初始化");
        return null;
    }
}

public class Test {
    public static void main(String[] args) {
        new Child();
    }
}

分析

  • 首先先加载类到JVM的方法区中,则先加载静态的内容,比如静态代码块和静态属性,并且先加载父类,且按照代码顺序加载

  • 接着加载对象到堆内存中,先加载父类的实例语句块和实例属性,按照父类优先,根据代码顺序加载,最后加载构造方法

执行结果

父类的静态块
父类的静态属性初始化
子类的静态属性初始化
子类的静态块
父类的实例属性初始化
父类的实例块
父类的构造方法
子类的实例块
子类的实例属性初始化
子类的构造方法

8. 抽象类和接口(***)

抽象类

  • 在 java 中采用 abstract 关键字定义的类就是抽象类,采用 abstract 关键字定义的方法就是抽象方法

  • 抽象的方法只需在抽象类中,提供声明,不需要实现

  • 如果一个类中含有抽象方法,那么这个类必须定义成抽象类,一个抽象类不一定含有抽象方法

  • 如果这个类是抽象的,那么这个类被子类继承,抽象方法必须被重写。如果在子类中不复写该抽象方法,那么必须将此类再次声明为抽象类

  • 抽象的类是不能实例化的,就像现实世界中人其实是抽象的,张三、李四才是具体的

  • 抽象类不能被 final 修饰

  • 抽象方法不能被 final 修饰,因为抽象方法就是被子类实现的

抽象类中可以包含方法实现,可以将一些公共的代码放到抽象类中,另外在抽象类中可以定义一些抽象的方法,这样就会存在一个约束,而子类必须实现我们定义的方法,如: teacher 必须实现 printInfo 方法, Student也必须实现 printInfo 方法,方法名称不能修改,必须为 printInfo,这样就能实现多态的机制,有了多态的机制,我们在运行期就可以动态的调用子类的方法。所以在运行期可以灵活的互换实现。

抽象类和普通类的区别?

抽象类 普通类
不能被实例化,也就是使用new关键字 可以被实例化
权限限定于Public和Protected,因为需要子类去继承抽象类
JDK 1.8以前,抽象类的方法默认访问权限为protected
JDK 1.8时,抽象类的方法默认访问权限变为default
没有权限限制
如果一个类继承抽象类,则必须实现抽象类的抽象方法
如果没有实现抽象方法,则该类必须定义成抽象类
不强制实现父类的方法

接口

:JDK 1.8 以后,接口里可以有静态方法和方法体了。

接口我们可以看作是抽象类的一种特殊情况,在接口中只能定义抽象的方法和常量(完全抽象)

  • 接口中的方法默认都是 public abstract 的(可以省略写),不能更改
  • 接口中的变量默认都是 public static final 的(省略不写),不能更改,所以必须显示的初始化
    注意:接口里的所有数据都是 public 修饰的!
  • 如果一个非抽象的类实现了接口,那么接口中所有的方法必须实现
  • 一类可以实现多个接口,接口和接口之间支持也是多继承的 ,但接口之间不能实现

在 java 中接口其实描述了类需要做的事情,类要遵循接口的定义来做事,使用接口到底有什么本质的好
处?可以归纳为两点:

  • 采用接口明确的声明了它所能提供的服务
  • 解决了 Java 单继承的问题

接口和抽象类的区别?

接口 抽象类
不能被实例化 不能被实例化
需要被子类实现,并实现接口的方法 需要被子类继承,并实现抽象方法
只能做方法的声明(JDK1.8之后允许方法体) 可以做方法的声明,也可以做方法的实现
如果子类不能实现接口中的所有方法,则该类只能是抽象类 如果子类不能实现抽象类的所有抽象,则该类只能是抽象类
属性只能是静态的常量 没有限制
接口与接口之间可以多继承 只能单继承

9. 类之间的关系

泛化关系

  • 类与类之间的继承以及接口与接口之间的继承

实现关系

  • 类对接口的实现

关联关系

  • 一个类中属性是另个类

    public class 学生 {
        private 班级 班级;
        // getter/setter
    }
    public class 班级 {
    }
    

聚合关系

  • 是关联关系的一种,有着较强的关联关系

  • 在java中一个类是整体,使用对象数组包含另个类;另个类属于某个整体

    public class 汽车 {
        private 轮胎集合 轮胎;
        //getter/setter
    }
    public class 轮胎 {
        private 汽车 汽车;
        //getter/setter
    }
    

依赖关系

  • 依赖关系是比关联关系弱的关系,在 java 语言中体现为返回值,参数,局部变量和静态方法调用

    public class Test {
        public static void main(String[] args) {
            Person person = new Person();
        }
    }
    class Person {
    }
    

10. Object类

  • Object 类是所有 Java 类的根基类
  • 如果在类的声明中未使用 extends 关键字指明其基类,则默认基类为 Object 类

equals

equals的源码是这样写的

public boolean equals(Object obj) {
    return (this == obj);
}

所以,默认情况下比较的是地址值,但是可以让我们覆写该方法,实现对象的比较。

如何覆写equals方法?

  • 首先为了提高效率,需要用==判断是否是同一个对象,如果是直接返回true
  • 接着为了提高健壮性,判断是否对象是否是该类的一个对象,如果是,需要对其向下转型
  • 最后是比较的逻辑
public class ObjectDemo {
    public static void main(String args[]){
        Student student1 = new Student("生命壹号",22,"成都");
        Student student2 = new Student("生命壹号",22,"成都"); 
        System.out.println(student1==student2);
        System.out.println(student1.equals(student2));
    }
 }
 class Student {
     private String name;
     private int age;
     private String address;
     public Student(String name,int age,String address){
         this.name = name;
         this.age = age;
         this.address = address;
     }
     //重写Object类中的equals方法(比较两个对象的值是否相等)
     public boolean equals(Object obj){
         //为了提高效率:如果两个内存地址相等,那么一定是指向同一个对内存中的对象,就无需比较两个对象的属性值(自己跟自己比,没啥意义嘛)
         if(this==obj){
             return true;
         }
         
         //为了提供程序的健壮性
         //我先判断一下,obj是不是学生的一个对象,如果是,再做向下转型,如果不是,直接返回false。
         //这个时候,我们要判断的是对象是否是某个类的对象?
         //记住一个格式:对象名 instanceof 类名。表示:判断该对象是否是该类的一个对象 
         if(!(obj instanceof Student)){        
             return false;                
         }
         
         //如果是就继续
         Student s = (Student)obj;//强制转换,即向下转型(毕竟Object类型没有具体的对象属性) 
         return this.name.equals(s.name) && this.age == s.age && this.address.equals(s.address);//判断两个对象的属性值是否相等
     }
 }

==与 equals()区别?

== equals()
等号比较的是值, 特别是比较引用类型,比较的是引用的内存地址的那个值 默认源码使用的是==,但是可以通过覆写该方法,实现对象的比较

对于基本数据的包装类型(Byte, Short, Character,Integer, Float, Double, Long, Boolean)除了 Float和 Double 之外,其他的六种都是实现了常量池的,因此对于这些数据类型而言,一般我们也可以直接通过==来判断是否相等

Byte,Short,Integer,Long,Character,Boolean,前面 4 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据,Character创建了数值在[0,127]范围的缓存数据,Boolean 直接返回True Or False

如果超出对应范围仍然会去创建新的对象。

public class Test {
    public static void main(String[] args){
        Integer a = 127;
        Integer b = 127;
        System.out.println(a==b);//true

        Integer c = 128;
        Integer d = 128;
        System.out.println(c==d);//false
    }
}

因为 Integer 在常量池中的存储范围为[-128,127], 127 在这范围内,因此是直接存储于常量池的,而
128 不在这范围内,所以会在堆内存中创建一个新的对象来保存这个值,所以 m, n 分别指向了两个不同的
对象地址,故而导致了不相等。

finalize

当垃圾收集器将要收集某个垃圾对象时将会调用 finalize,建议不要使用此方法,因为此方法的运行时间不确定,如果执行此方法出现错误,程序不会报告,仍然继续运行

JVM当看到对象类含有finalize函数,会将该对象交给FinalizerThread处理,但是处理的时间不确定。

11. 访问控制权限

范围由大到小的排序public > protected > 缺省 > private

对类的修饰只有public和缺省,内部类除外

修饰符 类的内部 同一个包中 子类 任何地方
public Y Y Y Y
protected Y Y Y N
缺省 Y Y N N
private Y N N N

总结为一句话private修饰的只能类的内部调用;缺省的可以在一个包中调用;protected扩展到了子类中,比如继承某个类,则可以使用那个类的属性和方法;public可以在任何地方访问。

12 内部类

分为四种内部类

  • 实例内部类:在类的内部定义的普通类
  • 静态内部类:在类的内部定义的静态类
  • 局部内部类:在方法中定义的普通类
  • 匿名内部类:方法使用中定义的普通类,主要用来实现接口

实例内部类

特点

  • 创建实例内部类,外部类的实例必须已经创建
  • 实例内部类会持有外部类的引用,可以直接访问外部类的属性
  • 不允许有静态声明
public class OuterClass
{
    //静态变量
    private static String s1 = "静态变量";
    //实例变量
    private String s2 = "实例变量";
    //实例内部类
    public class InnerClass
    {
        //编译错误,实例内部类中不允许有静态的声明
        public static void m1(){}
        //实例方法
        public void m2(){
            System.out.println(s1);
            System.out.println(s2);
        }
    }
    //入口
    public static void main(String[] args){
        OuterClass oc = new OuterClass();
        InnerClass innerClass = oc.new InnerClass();//??
        innerClass.m2();
    }
}

静态内部类

特点

  • 静态内部类不会持有外部的类的引用
  • 创建时可以不用创建外部类,在静态内部类中只能直接访问外部类中所有的静态数据。
  • 静态内部类等同于静态变量
public class OuterClass
{
    //静态变量
    private static String s1 = "静态变量";
    //实例变量
    private String s2 = "实例变量";
    //静态内部类
    //静态内部类可以使用任何一个访问控制权限修饰符修饰。
    protected static class InnerClass{
        //静态方法
        public static void m1(){
            System.out.println(s1);
        //System.out.println(s2);
        }
        //实例方法
        public void m2(){
            System.out.println(s1);
        //System.out.println(s2);
        }
    } //入口
    public static void main(String[] args){
        OuterClass.InnerClass.m1();//外部类.可以省略
        InnerClass innerClass = new OuterClass.InnerClass();
        innerClass.m2();
    }
}

局部内部类

  • 局部内部类等同于局部变量
  • 局部内部类是在方法体中声明的类,该类只能在方法体中使用
  • 局部内部类不能使用 public 、 protected 、 private 修饰
  • 局部内部类访问本地变量的时候,方法中的参数需要使用 final 修饰
public class OuterClass {
    private int a = 100;
    //局部变量在内部类中使用必须采用 final 修饰
    public void method1(final int temp) {
        class Inner3 {
            int i1 = 10;
            //可以访问外部类的成员变量
            int i2 = a;
            int i3 = temp;
        }
        //使用内部类
        Inner3 inner3 = new Inner3();
        System.out.println(inner3.i1);
        System.out.println(inner3.i3);
    }
    public static void main(String[] args) {
        OuterClass out = new OuterClass ();
        out.method1(300);
    }
}

匿名内部类

  • 是一种特殊的内部类,该类没有名字
  • 通过new 关键字创建,并加上方法体
  • 主要用于实现接口
public class Test {
    public static void main(String[] args){
        //在方法中实现接口
        new Thread(new Runnable() {
            @Override
            public void run() {
                
            }
        }).run();
    }
}

四、异常

任意的异常都是在运行时发生的!!!

1. 异常的体系

所有的异常都是Throwable的子类

Thorwable有两个直接子类Error和Exception

Error:

  • 在 Java 中只要 Error 发生了就一种结果——退出 JVM,例如 StackOverError

Exception的直接子类

  • Exception 的直接子类叫做编译时异常、受控异常、检查异常。它虽然叫做编译时异常,但是它不是发
    生在编译阶段的异常, 之所以叫做编译时异常是因为编译时异常要求必须在程序编译的阶段就手动的处理,如果不处理这些异常的话,程序无法编译通过。

  • 对于编译时异常有两种手段处理,一是 try catch 捕获,一是 throws 抛出

RuntimeException 的直接子类

  • RuntimeException 的直接子类叫做运行时异常、非受控异常、非检查异常。这种异常不要求在程序编译
    阶段处理,编译也可以通过
  • 比如说除0异常

自定义异常

  • JDK 提供的异常不能够满足要求的情况下用户可以自己自定义异常,可以根据实际情况选择继承Exception 或者 RuntimeException 两种形式。

2. 说出几个常见的异常

Error

  • StackOverError(堆溢出)
  • OutOfMemoryError(内存溢出)

受控异常

  • IOException(IO异常)
  • SQLException(SQL异常)
  • ClassNotFoundException(找不到指定的类异常)

不受控异常

  • NullPointerException(空指针异常)

  • ArithmeticException(算术异常)

  • ArrayIndexOutOfBoundsException(数组下表越界异常)

  • ClassCastException(类型强制转换类型)

3. 异常处理

异常的捕获应该从小到大

一般有两种方式

  • try…catch…finally…

  • thorws抛给调用者

finally 在任何情况下都会执行,除非JVM挂掉,通常在 finally 里关闭资源

public class ExceptionTest12 {
    public static void main(String[] args) {
        int r = method1();
        //输出为: 100? 50?
        System.out.println(r);//输出是50
    }
    private static int method1() {
        int a = 10;
        try {
            a = 50;
            return a;//直接返回值
        }finally {
            a = 100;//该语句也会执行,只是a已经返回
        }
    }
}

throws 和 throw 的区别 ?

throws throw
thorws 是声明异常 thorws 是声明异常
用在函数上 用在函数内部