【JavaSE】JavaSE基础

539 阅读21分钟

标识符

标识符是为类、方法、变量、常量或其他用户定义项所定义的名称,标识符可以有一个或多个字符。

在Java语言中,标识符的构成规则如下:

  • 标识符由数字(0 ~ 9)、字母(A ~ Z和a ~ z)、美元符号($)、下划线(_)组成。
  • 标识符的第一个符号只能是字母、下划线、美元符号。

标识符包含两类:

  • 关键字:含有特殊含义的标识符,如:class、true、void、public、static、if、for。
  • 用户自定义标识符:由用户按标识符构成规则生成的非关键字的标识符,如:res、cnt。

关键字

关键字是对编译器有特殊意义的固定单词,不能在程序中做其他目的使用。

Java目前定义了51个关键字,这些关键字不能作为变量名、类名和方法名来使用。

以下对这些关键字进行了分类:

  • 数据类型:boolean、int、long、short、byte、float、double、char、class、interface。

  • 流程控制:if、else、do、while、for、switch、case、default、break、continue、return、try、catch、finally。

  • 修饰符:public、protected、private、final、void、static、strict、abstract、transient、synchronized、volatile、native。

  • 动作:package、import、throw、throws、extends、implements、this、super、instanceof、new。

  • 保留字:true、false、null、goto、const。

保留字是为Java预留的关键字,它们虽然现在没有作为关键字,但在以后的升级版本中有可能作为关键字。

字面量

在计算机科学中,字面量是用于表达源代码中的一个固定值。几乎所有计算机编程语言都具有对基本值的字面量表示,诸如:整数、浮点数以及字符串。而有很多也对布尔和字符类型的值也支持字面量表示;还有一些甚至对枚举、数组、对象等复合类型的值也支持字面量表示法。

Java中主要包含:

  • 整型字面量:10、-12、10L、02(八进制)、0x7A(十六进制)
  • 浮点型字面量:3.14f、5.65d、3.14E-41(科学计数法)
  • 布尔型字面量:true、false
  • 字符型字面量:'a'、'华'、'\n'(转义)、'\u0000'
  • 字符串型字面量:"abc"、"中国"
  • 引用类型空指向:null

数据类型

Java中创建变量时,需要在内存中申请空间,内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。因此,通过定义不同类型的变量,可以在内存中储存整数、浮点数或者字符等。

Java中有两大数据类型:

  • 基本数据类型
  • 引用数据类型

1. 基本数据类型

数据类型介绍占用默认值最小值最大值
byte有符号,以二进制补码表示的整数8bit0-128(-2^7)127(2^7 - 1)
short有符号,以二进制补码表示的整数16bit0-32768(-2^15)32767(-2^15 - 1)
int有符号,以二进制补码表示的整数32bit0-2亿多(-2^31)2亿多(-2^31 - 1)
long有符号,以二进制补码表示的整数64bit0L-2^632^63 - 1
float单精度,有符号,符合IEEE 754标准的浮点数32bit0.0f1.4E - 453.4028235E38
double双精度,有符号,符合IEEE 754标准的浮点数64bit0.0d4.9E - 3241.7976931348623157E308
boolean真或假32bit / 8bitfalse
char无符号,Unicode字符16bit\u0000\u0000(十进制0)\uffff(十进制65535)

需要注意的是:

  • 在Java定义的八种基本数据类型中,只有boolean类型没有给出具体的占用字节数,因为JVM中没有供boolean值专用的字节码指令,可以说JVM中根本就不存在boolean这个类型。boolean变量在编译后会使用int类型来代替,而boolean数组会被编译成byte数组,每个boolean元素占8bit。因此,boolean在单独使用时占32bit,作为数组元素占8bit

    之所以使用int代替boolean而不用byte或short的原因是:虽然byte和short更节省内存,但是对于当下32位的CPU来说(不是32位系统,CPU硬件层面),一次性处理数据是32位的,CPU直接处理一个占32位的数据具有高效存取的特点。

  • float和double的最小值和最大值都是以科学计数法的形式输出的,其中"E"之前的数字是尾数,"E"之后的数字是指数,10是基数。例如:3.14E3 = 3.14 × 10^3。

  • char类型可以赋值为整数,范围是[ 0,65535 ]。十进制、八进制、十六进制均可,输出的是字符码表中对应的字符。char类型变量可以进行运算的原因是:char字面量在ASCII等字符编码表中有对应的数值。

    char字符进行运算时,直接当作short类型的数值处理。

    例如:

    // 输出结果为200
    System.out.println('a' + 'g');
    

2. 引用数据类型

在Java中,引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象,但是引用变量中存放的不是对象的内容,而是对象的地址。

所有引用变量的默认值都是null。

引用变量的类型有三种:

  • :Class
  • 接口:Interface
  • 数组:Array

引用类型一般是通过new关键字来创建,它存放在内存的堆中,可以在运行时动态的分配内存大小。当引用类型变量不被使用时,Java内部的垃圾回收器GC会自动回收走。

类型转换

1. 基本类型转换

基本类型转换主要在:赋值、方法调用、算数运算三种情况下发生。

赋值、方法调用的转换规则

  • 大类(整数型、浮点型)相同情况下,低位类型到高位类型自动转换,高位类型到低位类型需要强制类型转换。

  • 大类不相同情况下,整数类型到浮点数类型自动转换,浮点数类型到整数类型强制类型转换。

注意:

  • 整数字面量默认是int类型,浮点型字面量默认是double类型。

  • 转换的数据类型必须是兼容的(char和整数类型兼容,boolean和所有类型不兼容)。

  • 强制类型转换可能会导致溢出或者精度丢失。

    例如:

    int a = 128;
    byte b = (byte)a;
    

    因为byte占8bit,最大值为127,当int强制转为byte是,128就会导致溢出。

  • 浮点数到整数的转换是通过舍弃舍弃小数得到的,而不是四舍五入。

算数运算的转换规则

  • 操作数大类相同情况下,低位类型转为最高位类型再参加运算,运算的结果也是最高位类型。
  • 操作数大类不相同情况下,整数类型转为最高位浮点数类型再参加运算,运算的结果也是最高位浮点数类型。

注意:

  • 如果运算符为"+="、"*="、"++"、"--"等缩略形式的运算符,运算结果会被强制转换为目标变量的类型。

    例如:

    float a = 1.2f;
    double b = 1.5d;
    a += b;
    //a = a + b 报错,需要强制转换,等价如下
    a = (float) a + b
    

    a为float类型,b为double类型,a + b为double类型,a += b会把结果直接从double强制转换成float类型。

2. 引用类型转换

转换规则:子类到父类或者父接口自动类型转换,父类到子类强制类型转换。

注意:

  • 基本类型(int、float等)与对应包装类(Integer、Float)可自动转换。

修饰符

修饰符用来定义类、方法或者变量,通常放在语句的最前端。

Java提供了很多修饰符,主要分为以下两类:

  • 访问控制修饰符
  • 非访问控制修饰符

1. 访问控制修饰符

Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。

Java 支持 4 种不同的访问权限:

  • private:在同一个类中可见。

    作用对象:方法、变量。

  • default:在同一个包中可见。

    作用对象:类、接口、方法、变量。

  • protected:在同一个包和所有子类中可见。

    作用对象:方法、变量。

  • public:在所有类中可见。

    作用对象:类、接口、方法、变量。

2. 非访问控制修饰符

为了实现一些其他功能,Java也提供了很多非访问控制修饰符。

  • static:作用对象:方法、变量。

    static不会影响到变量、方法的访问权限。

    static不允许修饰局部变量。

    • 静态方法:独立于对象的静态方法,静态方法中不能使用类的非静态变量。
    • 静态变量:独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝,被所有对象共享。
    • 静态代码块:在类加载时执行一次,静态代码块中不能使用类的非静态变量。
  • final:作用对象:类、方法、变量。

    final修饰的类不能被继承。

    final修饰的方法可以被子类继承,但是不能被子类重写。

    final修饰的变量一旦被赋值后,不能被重新赋值。

    final通常和static修饰符一起使用来创建类常量。

  • abstract:作用对象:类、方法

    • 抽象类:抽象类不能用来实例化对象,声明抽象类的唯一目的是给子类进行扩充。
    • 抽象方法:一种没有任何实现的方法,具体实现由子类提供。

    抽象类不能被final修饰。

    抽象方法不能被final和static修饰。

    抽象方法只能存在于抽象类中,但是抽象类中还可以定义非抽象方法。

  • synchronized:作用对象:类、方法

    synchronized修饰的类和方法同一时间只能被一个线程访问。

  • transient:作用对象:变量。

    transient包含在定义实例变量的语句中,用来预处理类和变量的数据类型。

    序列化的对象包含被transient修饰的实例变量时,JVM会跳过该变量。

  • volatile:作用对象:变量

    volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

构造方法

构造方法是类的一种特殊方法,用来初始化类的一个新的对象,在创建对象(new)之后自动调用。

Java中的每个类都有一个默认的无参构造方法,并且可以有一个以上的构造方法。

构造方法特点:

  • 方法名必须与类名相同。

  • 可以有0个、1个或者多个参数。

  • 返回值是当前类的实例,但是定义构造方法时不能写任何返回值(隐式),包括void。

    如果返回类型设置成void或者其他,那么编译器将该方法当成普通方法处理。

  • 返回类型就是对象类型本身。

  • 只能与new关键字结合使用。

构造方法可以被private、protected、public等访问权限修饰符修饰。

构造方法不能被static、final、abstract等非访问权限控制修饰符修饰。

构造方法不能被子类继承。

构造方法不是要求必须定义的,如果在类中没有任何一个构造方法,编译器会为该类生成一个默认的无参构造方法(Nullary),并且该无参构造方法方法体为空。如果类中显式定义了一个或多个构造方法,则编译器不会再提供默认的无参构造方法。如果自行编写无参且函数体为空的构造函数,则不是默认的无参构造方法了。

代码块

  • 普通代码块:类中方法的方法体。

    例如:

    public void method() {
     	// 普通代码块   
    }
    
  • 构造代码块:每次创建对象时都会被执行,优先于构造函数执行,构造代码块中只能定义非静态变量。

    例如:

    {
    	// 构造代码块
    }
    
  • 静态代码块:类初始化时(第一次加载)执行,静态代码块优先于构造代码块执行。

    例如:

    static {
        // 静态代码块
    }
    
  • 同步代码块:在多线程环境下,对共享数据进行读写操作是需要互斥进行的,否则会破坏数据一致性。常见的是使用synchronized修饰方法,如果方法中只有一小段代码需要访问共享资源,那么对该段代码使用同步代码块即可,无需synchronized整个方法。

    例如:

    synchronized(obj) {
        // 同步代码块
    }
    

类加载时,块和构造方法的执行顺序是:静态代码块(只执行一次)> 构造代码块 > 构造函数

方法重载

方法重载,英文全称为Overload,表示如果有两个方法的方法名相同,但是形参列表不相同,那么可以说一个方法是另一个方法的重载。

具体说明如下:

  • 方法名相同。
  • 方法形参列表中的参数类型、参数个数不相同。
  • 方法的返回值类型可以不相同。
  • 方法的修饰符可以不相同。
  • main方法也可以被重载。

this关键字

this关键字用来表示当前对象本身,或当前类的一个实例,通过this可以调用本对象的所有实例方法和成员变量。

作用:

  • 调用本对象实例方法和成员变量

    例如:

    int z = this.x + this.y;
    this.method(z);
    
  • 区分同名变量

    当成员变量和局部变量重名时,使用this在方法内调用成员变量。

    例如:

    public Demo(String name) {
        this.name = name;
    }
    
  • 作为方法名初始化对象

    相当于调用本类的其他构造方法,必须作为构造方法中的第一行。

    例如:

    public Demo(String name, int age) {
        this(name);
        this.age = age;
    }
    
  • 作为参数传递(匿名对象)

    将当前对象的一个引用作为参数传递到另一个方法的形参列表中。

    例如:

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

继承

20211220161919.png

特性:

  • 子类实际上会继承父类中所有属性和方法,但是对于非private的属性和方法属于显示继承,可以直接利用对象进行操作;而非private的属性和方法属于隐式继承,需要调用getter、setter方法间接操作。

    例如:

    public class Test {
        public static void main(String[] args) {
            B b = new B();
            b.setName("zhangsan");
            // 输出zhangsan
            System.out.println(b.getName());
        }
    }
    class A {
        private String name;
       	public void setName(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
    }
    class B extends A {
        
    }
    
  • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。

  • 子类可以用自己的方式重写父类的方法。

  • 在继承关系中,如果要实例化子类对象,会先默认调用父类构造方法,为父类中的属性初始化;之后再调用子类构造方法,为子类中的特有属性初始化。

    在默认情况下,子类会找到父类之中的无参构造方法;如果父类中没有无参构造方法,则需要在子类的构造方法中使用super调用父类中有参的构造方法完成父类属性的初始化。

    注意:super调用父类构造方法时,也一定要放在构造方法第一行。

    例如:

    class A {
        private String name;
        public A(String name) {
            this.name = name;
        }
    }
    class B extends A {
        public B(String name) {
            super(name);
        }
    }
    
  • 接口和接口之间是继承关系,接口可以实现多继承

方法重写

重写,英文全称为Override,是子类从父类上继承下来的允许访问的方法的实现过程进行重新编写, 返回值大类和形参都不能改变。即外壳不变,核心重写!

重写规则:

  • 重写方法和被重写方法的形参列表必须完全相同。

  • 重写方法和被重写方法的返回值类型大类必须一样,也就是说具体类型可以不相同,但是必须是被重写方法返回值类型的子类。

    JDK5以前类型必须严格一致,JDK7之后大类需要保持一致。

  • 重写方法的访问权限不能比被重写方法的访问权限更低。

  • 重写方法抛出的异常不能比被重写方法抛出的异常更宽泛。

  • 被重写方法如果被final修饰则不能被重写。

  • 被重写方法如果被static修饰则不能被重写,但是可以单独定义一个和被重写方法一样的方法,编译器会识别为两个方法,两个方法之间没有联系。

  • 构造方法不能被重写。

  • 不能重写不是父类的方法。

抽象类

在面向对象的概念中,所有的对象都是通过类来描绘的。但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

规定:

  • 抽象类不能被实例化
  • 抽象类可以继承抽象类
  • 抽象类不一定包含抽象方法,但是有抽象方法的类一定是抽象类。
  • 构造方法、静态方法不能声明为抽象方法。
  • 抽象方法不能用private进行修饰。
  • 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类

接口

接口在Java中是一个抽象类型,是抽象方法的集合。接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法,接口只描述类要实现的方法

规定:

  • 接口无法被实例化。

  • 接口中没有成员变量、构造方法。

  • 接口中只能包含公开的抽象方法(被public和abstract修饰的方法)和常量(被public、final和static修饰的变量)。

    JDK1.8之后,接口中可以包含有实现的静态方法。

  • 接口和接口中的抽象方法都是隐式抽象的,定义时可以省去abstract关键字。

  • 接口可以继承多个接口

  • 一个接口可以被多个类实现,一个类可以实现多个接口(抽象类同理)

  • 实现接口的类需要实现接口中所有抽象方法(抽象类例外)。

  • 没有任何方法和属性的接口叫做标记接口,它仅仅表明实现它的类属于一个特定的类型,相当于给实现它的类打一个标签,让这些类实例化出来的对象拥有某个或某些特权。

    具体功能如下:

    • 建立一个公共的父接口:正如Serializable接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口/类的父接口。例如:当一个类实现了Serializable接口,JVM就知道该类的实例可以被序列化。
    • 向一个类添加数据类型:这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法,但是该类通过多态变成一个接口类型。

异常

程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?

Java提供了更加优秀的解决办法:异常处理机制。

异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。

1. 异常的结构

20211220212637.png

  • Error:是程序中无法处理的错误,此类错误一般表示代码运行时JVM出现问题。通常有VirtualMachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。此类错误发生时,JVM将终止线程。当此类错误发生时,应用不应该去处理此类错误

  • Exception:程序本身可以捕获并且可以处理的异常。

    • 非受检异常:RuntimeException及其子类异常,编译器不会检查并且不要求处理此类异常。此类异常属于不可查异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。
    • 受检异常:非RuntimeException及其子类异常,编译器会检查此类异常。如果程序中出现此类异常,必须对该异常进行处理,要么使用try-catch捕获,要么使用throws语句抛出,否则编译不通过。

2. 异常的处理

  • 抛出异常:throw、throws

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

      例如:

      throw new 异常类名(参数);
      
    • throws:用在方法上,表示当前方法不处理异常,而是提醒该方法的调用者来处理异常。

      例如:

      public void method() throws 异常类名1, 异常类名2 ... {}
      
  • 捕获异常:try、catch、finally,这三个关键字连用来捕获异常。

    • try:监视代码执行过程,一旦发现异常跳转至catch,如果没有异常直接跳转至finally。
    • catch:可选择执行的代码块,如果没有异常则不会执行,如果发现异常则进行处理或向上抛出。
    • finally:必定会执行的代码块,不管是否有异常发生,即使发生内存溢出异常也会执行,通常用于处理善后清理工作。

    例如:

    try {
        ...
    } catch (XXXException e) {
        ...
    } finally {
        ...
    }
    

I/O流

20211221203642.png

按照"流"的数据流向,可以分为:

  • 输入流
  • 输出流

按照"流"处理数据的单位,可以分为:

  • 字符流
  • 字节流

字符流的抽象基类是:Reader、Writer

字节流的抽象基类是:InputStream、OutputStream

这四个子类名称都是以其父类名作为其后缀,如InputStream的子类FileInputStream,Reader的子类FileReader。

1. 字符流

字符流就是在字节流的基础上,加上编码,形成的数据流。

字符流读写文件的基本单位是:字符。

字符流出现的意义就是,因为字节流在操作字符时,可能会有中文导致的乱码,所以由字节流引申出了字符流。

传输一些包含纯中文字符的文本文件建议使用字符流操作。

1.1 字符输出流

  • FileWriter:文件输出流

  • BufferedWriter:缓冲字符输出流

    基于FileWriter构建,设置了缓冲区,将缓冲区写满了才写入文件中。使用BufferedWriter可以提高我们写入文件的效率。

    字符流写文件最推荐的流。

  • CharArrayWriter:字符数组输出流

    同样设置了缓冲区,缓冲区是一个char[],当数据写入流时,缓冲区会动态增长。

  • FilterWriter:字符过滤输出流

  • PipedWriter:字符管道输出流

    可以通过管道进行进程间的通信。

  • OutputStreamWriter:字节输出流到字符输出流的转换流

1.2 字符输入流

与上述字符输出流原理一致。

2. 字节流

字节流是字符流的基础流。

字符流读写文件的基本单位是:字节。

字节流的基本操作和字符流类相同,但它不仅可以读写字符,还可以读写其他媒体文件。

2.1 字节输出流

  • OutputStream:字节输出流

    抽象类,只定义了字节输出的基本约定,具体依赖于子类实现。

  • FileOutputStream:文件字节输出流

  • ObjectOutputStream:对象序列化输出流

    把对象转成字节数据的输出到文件中保存。

    对象的输出过程称为序列化,可实现对象的持久存储。

2.2 字节输入流

与上述字节输出流原理一致。

3. 转换流

字节流可以转换为字符流。

使用转换流读写纯字符文件,由于转换流可以指定输入输出所使用的字符集,所以一定程度上可以避免乱码。

  • OutputStreamWriter:将字节输出流转成字符输出流

    Writer对于文字的输出要比OutputStream方便。

  • InputStreamReader:将字节输入流转成字符输入流

    InputStream读取的是字节,不方便中文的处理。