Java基础学习

1、Java是如何实现跨平台的,原理是什么?

JVM,也就是 Java 虚拟机,就是一个平台,包含于 JRE 的下面。当你需要执行某个 Java 程序时,由 JVM 帮你进行编译和执行。我们编写的 Java 源码,编译后会生成一种 .class 文件,称为字节码文件。Java 虚拟机就是负责将字节码文件翻译成特定平台下的机器码然后运行。
JVM 是一个“桥梁”,是一个“中间件”,是实现跨平台的关键,Java 代码首先被编译成字节码文件,再由 JVM 将字节码文件翻译成机器语言,从而达到运行 Java 程序的目的。

2、Java的关键字和标识符

Java 所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符。

  • 类名:对于所有的类来说,类名的首字母应该大写。如果类名由若干单词组成,那么每个单词的首字母应该大写。

  • 方法名:所有的方法名都应该以小写字母开头。如果方法名含有若干单词,则后面的每个单词首字母大写。

  • 数据类型:byte:8 short:16 char:16 int:32 long:64 float:32 double:64 由低到高 自动类型转换 强制类型转换 隐含强制类型转换

    类型 说明
    double 双精度浮点数
    float 单精度浮点数
    int 整型
    char 字符型
    long 长整型
    short 短整型
    byte 字节型
    boolean 布尔型

3、Java变量类型

类变量、实例变量、局部变量

  • 类变量:独立于方法之外的变量,用 static 修饰。
  • 实例变量:独立于方法之外的变量,不过没有 static 修饰。
  • 局部变量:类的方法中的变量。局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。

4、Java修饰符

访问控制修饰符

  • public:对所有类可见。使用对象:类、接口、变量、方法
  • private:在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
  • protected:对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
  • default:在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。

非访问修饰符

  • static:static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。
  • final:final 表示”最后的、最终的”含义,变量一旦赋值后,不能被重新赋值。被 final 修饰的实例变量必须显式指定初始值。
  • abstract:抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。
  • synchronized:synchronized 关键字声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。

5、Java运算符

5.1 算术运算符

  • 一目运算符
    运算符 说明
    - 取反运算
    ++ 先取值再加一,或先加一再取值
    先取值再减一,或先减一再取值

++a || –a 先进行自增运算或者自减在进行表达式的运算
a++ || a– 先进行表达式的运算在进行自增或者自减运算

  • 二目运算符
    运算符 说明
    + 求 a 加 b 的和,还可用于 String 类型,进行字符串连接操作
    - 求 a 减 b 的差
    * 求 a 乘以 b 的积
    / 求 a 除以 b 的商
    % 求 a 除以 b 的余数

5.2 赋值运算符

变量名称 = 表达式内容
在 Java 语言中,“变量名称”和“表达式”内容的类型必须匹配,如果类型不匹配则需要自动转化为对应的类型。
不要将赋值运算符与相等运算符“==”混淆。

5.3 逻辑运算符

操作符 描述
&& 逻辑与运算符。当且仅当两个操作数都为真,条件才为真。
逻辑或操作符。如果任何两个操作数任何一个为真,条件为真。
逻辑非运算符。用来反转操作数的逻辑状态。如果条件为true,则逻辑非运算符将得到false。

5.4 关系运算符

也可以称为“比较运算符”,用于用来比较判断两个变量或常量的大小。关系运算符是二元运算符,运算结果是 boolean 型。当运算符对应的关系成立时,运算结果是 true,否则是 false。

5.5 位运算符

Java 定义的位运算(bitwise operators)直接对整数类型的位进行操作,这些整数类型包括 long,int,short,char 和 byte。
位运算符主要用来对操作数二进制的位进行运算。按位运算表示按每个二进制位(bit)进行计算,其操作数和运算结果都是整型值。

操作符 描述
& 如果相对应位都是1,则结果为1,否则为0
如果相对应位都是 0,则结果为 0,否则为 1
^ 如果相对应位值相同,则结果为0,否则为1
按位取反运算符翻转操作数的每一位,即0变成1,1变成0。
<< 按位左移运算符。左操作数按位左移右操作数指定的位数。
>> 按位右移运算符。左操作数按位右移右操作数指定的位数。
>>> 按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。

5.6 条件运算符

Java 提供了一个特别的三元运算符(也叫三目运算符)经常用于取代某个类型的 if-then-else 语句。条件运算符的符号表示为“?:”,使用该运算符时需要有三个操作数,因此称其为三目运算符。使用条件运算符的一般语法结构为:

1
result = <expression> ? <statement1> : <statement3>

其中,expression 是一个布尔表达式。当 expression 为真时,执行 statement1, 否则就执行 statement3。此三元运算符要求返回一个结果,因此要实现简单的二分支程序,即可使用该条件运算符。

6、for循环和增强for循环

6.1 for循环

1
2
3
for(初始化; 布尔表达式; 更新) {
//代码语句
}

关于 for 循环有以下几点说明:

  • 最先执行初始化步骤。可以声明一种类型,但可初始化一个或多个循环控制变量,也可以是空语句。
  • 然后,检测布尔表达式的值。如果为 true,循环体被执行。如果为false,循环终止,开始执行循环体后面的语句。
  • 执行一次循环后,更新循环控制变量。
  • 再次检测布尔表达式。循环执行上面的过程。

6.2 增强for循环

在遍历数组、集合方面,foreach 为开发者提供了极大的方便。foreach 循环语句是 for 语句的特殊简化版本,主要用于执行遍历功能的循环。

1
2
3
for(类型 变量名:集合) {
语句块;
}

example:

1
2
3
4
5
6
String[] urls = { "http://c.biancheng.net/java", "http://c.biancheng.net/c", "http://c.biancheng.net/golang/" };
// 使用foreach循环来遍历数组元素
// 其中book将会自动迭代每个数组元素
for (String url : urls) {
System.out.println(url);
}

7、String方法的运用

7.1 String和int型数据类型的转换

String转换为int

  • Integer.parseInt(str)
  • Integer.valueOf(str).intValue()

int转换为String

  • String s = String.valueOf(i);
  • String s = Integer.toString(i);
  • String s = “” + i;

7.2 Java字符串大小写转换

String 类的 toLowerCase()方法可以将字符串中的所有字符全部转换成小写,而非字母的字符不受影响。语法格式如下:

1
字符串名.toLowerCase()    // 将字符串中的字母全部转换为小写,非字母不受影响

toUpperCase()则将字符串中的所有字符全部转换成大写,而非字母的字符不受影响。语法格式如下:

1
字符串名.toUpperCase()    // 将字符串中的字母全部转换为大写,非字母不受影响

7.3 Java提取子字符串

在 String 中提供了两个截取字符串的方法,一个是从指定位置截取到字符串结尾substring(int beginIndex),另一个是截取指定范围的内容substring(int beginIndex,int endIndex)。注意:substring()方法是按字符截取,而不是按字节截取。

7.4 Java分割字符串

String类的split()方法可以按指定的分割符对目标字符串进行分割,分割后的内容存放在字符串数组中。该方法主要有如下两种重载形式:

1
2
str.split(String sign)//sign 为指定的分割符,可以是任意字符串。
str.split(String sign,int limit)//limit 表示分割后生成的字符串的限制个数,如果不指定,则表示不限制,直到将整个目标字符串完全分割为止。

7.5 字符串的替换

replace()方法用于将目标字符串中的指定字符(串)替换成新的字符(串),其语法格式如下:

1
2
字符串.replace(String oldChar, String newChar)//其中,oldChar 表示被替换的字符串;newChar 表示用于替换的字符串。
//replace() 方法会将字符串中所有 oldChar 替换成 newChar。

replaceFirst()方法用于将目标字符串中匹配某正则表达式的第一个子字符串替换成新的字符串,其语法形式如下:

1
字符串.replaceFirst(String regex, String replacement)//regex 表示正则表达式;replacement 表示用于替换的字符串。

replaceAll()方法用于将目标字符串中匹配某正则表达式的所有子字符串替换成新的字符串,其语法形式如下:

1
字符串.replaceAll(String regex, String replacement)//其中,regex 表示正则表达式,replacement 表示用于替换的字符串。

8、字符串的比较

字符串比较是常见的操作,包括比较相等、比较大小、比较前缀和后缀串等。在 Java 中,比较字符串的常用方法有 3 个:equals()方法、equalsIgnoreCase()方法、 compareTo()方法。

equals() 方法将逐个地比较两个字符串的每个字符是否相同。如果两个字符串具有相同的字符和长度,它返回 true,否则返回 false。对于字符的大小写,也在检查的范围之内。

1
str1.equals(str2);//str1 和 str2 可以是字符串变量, 也可以是字符串字面量。

equalsIgnoreCase() 方法的作用和语法与equals()方法完全相同,唯一不同的是 equalsIgnoreCase() 比较时不区分大小写。当比较两个字符串时,它会认为 A-Z 和 a-z 是一样的。

compareTo() 方法用于按字典顺序比较两个字符串的大小,该比较是基于字符串各个字符的 Unicode 值

1
2
3
4
str.compareTo(String otherstr);//如果按字典顺序 str 位于 otherster 参数之前,比较结果为一个负整数;
//如果 str 位于 otherstr 之后,比较结果为一个正整数;如果两个字符串相等,则结果为 0。

提示:如果两个字符串调用 equals() 方法返回 true,那么调用 compareTo() 方法会返回 0

8.1 equals() 与 == 的比较

equals()方法比较字符串对象中的字符。而 == 运算符比较两个对象引用看它们是否引用相同的实例。

下面的程序说明了两个不同的字符串(String)对象是如何能够包含相同字符的,但同时这些对象引用是不相等的:

1
2
3
4
String s1 = "Hello";
String s2 = new String(s1);
System.out.println(s1.equals(s2)); // 输出true
System.out.println(s1 == s2); // 输出false

变量 s1 指向由“Hello”创建的字符串实例。s2 所指的的对象是以 s1 作为初始化而创建的。因此这两个字符串对象的内容是一样的。但它们是不同的对象,这就意味着 s1 和 s2 没有指向同一的对象,因此它们是不==的。

因此,千万不要使用==运算符测试字符串的相等性,以免在程序中出现糟糕的 bug。从表面上看,这种 bug 很像随机产生的间歇性错误。

9、Java中容易混淆的空字符串和null

null 是空引用,表示一个对象的值,没有分配内存,调用 null 的字符串的方法会抛出空指针异常。
“”是一个长度为 0 且占内存的空字符串,在内存中分配一个空间,可以使用 Object 对象中的方法。例如:“”.toString()
new String() 创建一个字符串对象的默认值为 “”,String 类型成员变量的初始值为 null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
String str1 = new String();
String str2 = null;
String str3 = "";
System.out.println(str3.length()); // 空字符串""的长度为0
System.out.println(str2.length()); // 抛出空指针异常
System.out.println(str1); // 输出""
System.out.println(str1 == str2); // 内存地址的比较,返回false
System.out.println(str1.equals(str2)); // 值的比较,返回false
System.out.println(str2 == str3); // 内存地址的比较,返回false
System.out.println(str3.equals(str2)); // 值的比较,返回false
System.out.println(str1 == str3); // 内存地址的比较,返回false
System.out.println(str1.equals(str3)); // 值的比较,返回true
}

10、Java中StringBuffer 和 StringBuilder 类

StringStringBufferStringBuilder的区别:

类型 区别
String 字符串常量定长不可变
StringBuffer 线程安全字符变量
StringBuilder 线程不安全字符变量,速度高于StringBuffer高于String

10.1 StringBuffer

StringBuffer 类提供了 3 个构造方法来创建一个字符串,如下所示:

  • StringBuffer() 构造一个空的字符串缓冲区,并且初始化为 16 个字符的容量。
  • StringBuffer(int length) 创建一个空的字符串缓冲区,并且初始化为指定长度 length 的容量。
  • StringBuffer(String str) 创建一个字符串缓冲区,并将其内容初始化为指定的字符串内容 str,字符串缓冲区的初始容量为 16 加上字符串 str 的长度。

常用函数:

  • 追加字符串StringBuffer 对象.append(String str);
  • 替换字符StringBuffer 对象.setCharAt(int index, char ch);
  • 反转字符串StringBuffer 对象.reverse();
  • 删除字符串StringBuffer 对象.deleteCharAt(int index);或者StringBuffer 对象.delete(int start,int end);
  • 插入字符串StringBuffer对象.insert(int offset, String str)

11、Java数组

数组(array)是一种最简单的复合数据类型,它是有序数据的集合,数组中的每个元素具有相同的数据类型,可以用一个统一的数组名和不同的下标来确定数组中唯一的元素。根据数组的维度,可以将其分为一维数组、二维数组和多维数组等。
总的来说,数组具有以下特点:

  • 😃数组可以是一维数组、二维数组或多维数组。
  • 😃数值数组元素的默认值为 0,而引用元素的默认值为 null。
  • 😃数组的索引从 0 开始,如果数组有 n 个元素,那么数组的索引是从 0 到(n-1)。
  • 😃数组元素可以是任何类型,包括数组类型。
  • 😃数组类型是从抽象基类 Array 派生的引用类型。

数组的声明:

1
2
3
dataType[] arrayRefVar;   // 首选的方法

dataType arrayRefVar[]; // 效果相同,但不是首选方法

11.1 数组的相关操作

Java数组和字符串的相互转换

  • 字符串转换为数组

    • Java String 类中的 toCharArray() 方法将字符串转换为字符数组。
    • Java.lang 包中有 String.split() 方法,Java 中通常用 split() 分割字符串。
    • 如果要返回 byte 数组就直接使用 getBytes() 方法就可以了。
  • 数组转换为字符串

    • char 字符数组转化为字符串,使用 String.copyValueOf(charArray) 函数实现。
    • String 字符串数组转化为字符串,使用append()方法。String并不拥有append方法,所以借助 StringBuffer,在使用toString()方法。

Java比较两个数组是否相等

数组相等的条件不仅要求数组元素的个数必须相等,而且要求对应位置的元素也相等。Arrays 类提供了 equals() 方法比较整个数组。语法如下:

1
Arrays.equals(arrayA, arrayB);

Java数组填充

  • Arrays 类提供了一个 fill()方法,可以在指定位置进行数值填充。fill() 方法虽然可以填充数组,但是它的功能有限制,只能使用同一个数值进行填充。语法如下:

    • ```java
      Arrays.fill(array,value);

      1
      2
      3
      4
      5
      6

      其中,array 表示数组,value 表示填充的值。

      + ```java
      public static void fill(int[] a, int fromIndex,int toIndex, int val)://将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。填充的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。
      //(如果 fromIndex == toIndex,则填充范围为空。)

      参数:
      a - 要填充的数组
      fromIndex - 要使用指定值填充的第一个元素的索引(包括)
      toIndex - 要使用指定值填充的最后一个元素的索引(不包括)
      val - 要存储在数组所有元素中的值

Java数组查找指定元素

  • 第一种形式如下:

    1
    binarySearch(Object[] a,Object key);//a 表示要搜索的数组,key 表示要搜索的值。

    在进行数组查询之前,必须对数组进行排序(可以使用 sort() 方法)

  • 第二种形式如下:

    1
    binarySearch(Object[] a,int fromIndex,int toIndex,Object key);//a 表示要进行查找的数组,fromIndex 指定范围的开始处索引(包含开始处),toIndex 指定范围的结束处索引(不包含结束处),key 表示要搜索的元素。

    注意:实现对数组进行查找的方法很多,但是使用 Arrays 对象的 binarySearch() 方法是最简单、最方便的一种,因此该方法经常被应用。

Java复制(拷贝)数组的4种方法:

  • Arrays 类的 copyOf() 方法

    1
    Arrays.copyOf(dataType[] srcArray,int length);//Arrays.copyOf(dataType[] srcArray,int length);

    注意:目标数组如果已经存在,将会被重构。

  • Arrays 类的copyOfRange() 方法

    1
    2
    3
    4
    Arrays.copyOfRange(dataType[] srcArray,int startIndex,int endIndex);
    //srcArray 表示原数组。
    //startIndex 表示开始复制的起始索引,目标数组中将包含起始索引对应的元素,另外,startIndex 必须在 0 到 srcArray.length 之间。
    //endIndex 表示终止索引,目标数组中将不包含终止索引对应的元素,endIndex 必须大于等于 startIndex,可以大于 srcArray.length,如果大于 srcArray.length,则目标数组中使用默认值填充。

    注意:目标数组如果已经存在,将会被重构。

  • System 类的 arraycopy() 方法

    1
    2
    3
    4
    5
    6
    System.arraycopy(dataType[] srcArray,int srcIndex,dataType[] destArray,int destIndex,int length);
    //srcArray 表示原数组;
    //srcIndex 表示原数组中的起始索引;
    //destArray 表示目标数组;
    //destIndex 表示目标数组中的起始索引;
    //length 表示要复制的数组长度。

    使用此方法复制数组时,length+srcIndex 必须小于等于 srcArray.length,同时 length+destIndex 必须小于等于 destArray.length。

  • Object 类的 clone() 方法

    1
    2
    int[] targetArray=(int[])sourceArray.clone();
    //注意:目标数组如果已经存在,将会被重构。

注意:以上几种方法都是浅拷贝(浅复制)。浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化。深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变。

Java 数组排序(升序和降序)

  • 要对一个数组进行升序排列,只需要调用 Arrays.sort() 方法即可。

  • 在 Java 语言中使用 sort 实现降序有两种方法。

    1
    Arrays.sort(a, Collections.reverseOrder());

    实现 Comparator 接口的复写 compare() 方法,

12、Java排序

12.1 冒泡排序(Bubble Sort

冒泡排序的基本思想是:对比相邻的元素值,如果满足条件就交换元素值,把较小的元素值移动到数组前面,把大的元素值移动到数组后面(也就是交换两个元素的位置),这样数组元素就像气泡一样从底部上升到顶部。

1
2
3
4
5
6
7
8
9
10
for(i = 0; i < n-1; i++){
for(j = 0; j < n-1-i; j++){
if(a[j]>a[j+1]){
temp = a[j];
a[j] = a[j+1];
a[i+1] = temp;
count++;
}
}
}

12.2 快速排序(Quick sort

快速排序的基本思想是:通过一趟排序,将要排序的数据分隔成独立的两部分,其中一部分的所有数据比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此使整个数据变成有序序列。
具体做法是:假设要对某个数组进行排序,首先需要任意选取一个数据(通常选用第一个数据)作为关键数据,然后将所有比它小的数都放到它的前面,所有比它大的数都放到它的后面。这个过程称为一趟快速排序;递归调用此过程,即可实现数据的快速排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static int partition(int[] array, int low, int high) {
// 取最后一个元素作为中心元素
int pivot = array[high];
// 定义指向比中心元素大的指针,首先指向第一个元素
int pointer = low;
// 遍历数组中的所有元素,将比中心元素大的放在右边,比中心元素小的放在左边
for (int i = low; i < high; i++) {
if (array[i] <= pivot) {
// 将比中心元素小的元素和指针指向的元素交换位置
// 如果第一个元素比中心元素小,这里就是自己和自己交换位置,指针和索引都向下一位移动
// 如果元素比中心元素大,索引向下移动,指针指向这个较大的元素,直到找到比中心元素小的元素,并交换位置,指针向下移动
int temp = array[i];
array[i] = array[pointer];
array[pointer] = temp;
pointer++;
}
System.out.println(Arrays.toString(array));
}
// 将中心元素和指针指向的元素交换位置
int temp = array[pointer ];
array[pointer] = array[high];
array[high] = temp;
return pointer;
}

public static void quickSort(int[] array, int low, int high) {
if (low < high) {
// 获取划分子数组的位置
int position = partition(array, low, high);
// 左子数组递归调用
quickSort(array, low, position -1);
// 右子数组递归调用
quickSort(array, position + 1, high);
}
}

12.3 选择排序法

选择排序是指每一趟从待排序的数据元素中选出最大(或最小)的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 选择排序
*/
public static void algorithm4(){
//定义一个整数类型数组,用于排序的原始数据
int[] array={3,5,1,2,4};
//获取数组的大小
int length = array.length;
//第一个循环用来遍历数组中的所有数字
for (int i = 0; i < length; i++) {
//初始化一个变量,用来记录最小数字的下标。初始默认假设第一个数字就是最小数字
int minIndex = i;
//第二个循环,通过比较获取数组中最小的数字的下标。
for (int j = i+1; j < length ; j++) {
//如果找到更小的数字,
if (array[minIndex]>=array[j]) {
//将minIndex变量的值修改为新的最小数字的下标。
minIndex = j;
}
}

//所有数字一个个比较结束之后,就能确认那个数字最小了。
//将最小的数字替换到第一个位置,将第一个位置的数字放到最小数字原来的位置,就是一次交换。
int temp=array[i];
array[i]=array[minIndex];
array[minIndex]=temp;
}


//将排序之后的数组打印出来。
//下面的输出是不计算时间复杂度的,因为实际开发中这段输出代码会被删掉
for (int i = 0; i < length; i++) {
System.out.print(array[i]+",");
}

}

选择排序总共循环了所少次?
n+(n-1)+(n-2)+(n-3)+…+1
上述这个表达式,反过来写就是1+2+3+4+5+…+n。高斯算法就是(n+1)n/2

12.4 直接插入排序法

直接插入排序的基本思想是:将 n 个有序数存放在数组 a 中,要插入的数为 x,首先确定 x 插在数组中的位置 p,然后将 p 之后的元素都向后移一个位置,空出 a(p),将 x 放入 a(p),这样可实现插入 x 后仍然有序。

1
2
3
4
5
6
7
8
9
int[] number = { 13, 15, 24, 99, 4, 1 };
int temp, j;
for (int i = 1; i < number.length; i++) {
temp = number[i];
for (j = i - 1; j >= 0 && number[j] > temp; j--) {
number[j + 1] = number[j];
}
number[j + 1] = temp;
}

13、Java的输入输出流(I/O)、读写文件

13.1 读取控制台的输入

Java 的控制台输入由 System.in 完成。你可以把 System.in 包装在一个 BufferedReader 对象中来创建一个字符流。

1
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  • 调用read()方法从控制台读取多字符输入。
  • 调用readLine()方法从控制台读取字符串。
  • 直接使用System.in.reader()从控制台输出多字符。

13.2 读写文件

输入流:

可以使用字符串类型的文件名来创建一个输入流对象来读取文件:

1
InputStream f = new FileInputStream("C:/java/hello");

也可以使用一个文件对象来创建一个输入流对象来读取文件

1
2
File f = new File("C:/java/hello");
InputStream in = new FileInputStream(f);

输出流:

使用字符串类型的文件名来创建一个输出流对象:

1
OutputStream f = new FileOutputStream("C:/java/hello");

也可以使用一个文件对象来创建一个输出流来写文件。我们首先得使用File()方法来创建一个文件对象:

1
2
File f = new File("C:/java/hello");
OutputStream fOut = new FileOutputStream(f);

14、Java Scanner 类

下面是创建 Scanner 对象的基本语法:

1
Scanner s = new Scanner(System.in);

并通过 Scanner 类的 next() 与 nextLine() 方法获取输入的字符串。

14.1 next() 与 nextLine() 区别

next():

  • 1、一定要读取到有效字符后才可以结束输入。
  • 2、对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
  • 3、只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
  • next() 不能得到带有空格的字符串。

nextLine():

  • 1、以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
  • 2、可以获得空白。

15、Java 异常处理

Java中的异常又称为例外,是一个在程序执行期间发生的事件,它中断正在执行程序的正常指令流。为了能够及时有效地处理程序中的运行错误,必须使用异常类,这可以让程序具有极好的容错性且更加健壮。

在 Java 中一个异常的产生,主要有如下三种原因:

  • Java 内部错误发生异常,Java 虚拟机产生的异常。
  • 编写的程序代码中的错误所产生的异常,例如空指针异常、数组越界异常等。
  • 通过 throw 语句手动生成的异常,一般用来告知该方法的调用者一些必要信息。

15.1 常见的Error和Exception

运行时异常(RuntimeException):

  • NullPropagation:空指针异常;
  • ClassCastException:类型强制转换异常
  • IllegalArgumentException:传递非法参数异常
  • IndexOutOfBoundsException:下标越界异常
  • NumberFormatException:数字格式异常

非运行时异常:

  • ClassNotFoundException:找不到指定 class 的异常
  • IOException:IO 操作异常

错误(Error):

  • NoClassDefFoundError:找不到 class 定义异常
  • StackOverflowError:深递归导致栈被耗尽而抛出的异常
  • OutOfMemoryError:内存溢出异常

15.2 Java的异常处理机制

Java 的异常处理机制提供了一种结构性和控制性的方式来处理程序执行期间发生的事件。异常处理的机制如下:

  • 在方法中用 try catch 语句捕获并处理异常,catch 语句可以有多个,用来匹配多个异常。
  • 对于处理不了的异常或者要转型的异常,在方法的声明处通过 throws 语句拋出异常,即由上层的调用方法来处理。
1
2
3
4
5
6
7
8
9
10
try {
逻辑程序块
} catch(ExceptionType1 e) {
处理代码块1
} catch (ExceptionType2 e) {
处理代码块2
throw(e); // 再抛出这个"异常"
} finally {
释放资源代码块
}

15.3 finally关键字

在实际开发中,根据 try catch 语句的执行过程,try 语句块和 catch 语句块有可能不被完全执行,而有些处理代码则要求必须执行。

finally 关键字用来创建在 try 代码块后面执行的代码块。无论是否发生异常,finally 代码块中的代码总会被执行。在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。

16、Java继承

继承是面向对象的三大特征之一。

Java中的继承就是在已经存在类的基础上进行扩展,从而产生新的类。已经存在的类称为父类、基类或超类,而新产生的类称为子类或派生类。在子类中,不仅包含父类的属性和方法,还可以增加新的属性和方法。

1
2
3
4
5
6
7
修饰符 class class_name {
//父类
}
修饰符 class class_name extends extend_class {
// 类的主体
//子类
}

类的继承不改变类成员的访问权限,也就是说,如果父类的成员是公有的、被保护的或默认的,它的子类仍具有相应的这些特性,并且子类不能获得父类的构造方法

16.1 继承的特性

  • 子类拥有父类非 private 的属性、方法。
  • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
  • 子类可以用自己的方式实现父类的方法。
  • Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
  • 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。

16.2 继承的优缺点

在面向对象语言中,继承是必不可少的、非常优秀的语言机制,它有如下优点:

  1. 实现代码共享,减少创建类的工作量,使子类可以拥有父类的方法和属性。
  2. 提高代码维护性和可重用性。
  3. 提高代码的可扩展性,更好的实现父类的方法。

继承的缺点如下:

  1. 继承是侵入性的。只要继承,就必须拥有父类的属性和方法。
  2. 降低代码灵活性。子类拥有父类的属性和方法后多了些约束。
  3. 增强代码耦合性(开发项目的原则为高内聚低耦合)。当父类的常量、变量和方法被修改时,需要考虑子类的修改,有可能会导致大段的代码需要重构。

16.3 继承关键字

继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。

  • extends关键字:extends 只能继承一个类。

  • implements关键字:使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。

  • super关键字:由于子类不能继承父类的构造方法,因此,如果要调用父类的构造方法,可以使用 super 关键字。super 可以用来访问父类的构造方法、普通方法和属性。

    • super 关键字的功能:

      • 在子类的构造方法中显式的调用父类构造方法

      • 访问父类的成员方法和变量。

    • super调用父类构造方法

      1
      super(parameter-list);
    • super 访问父类中的成员

      1
      super.member
    • this关键字:this 指的是当前对象的引用。

      this 关键字的用法:

      • this.属性名:表示当前对象的属性
      • this.方法名(参数):表示调用当前对象的方法
    • final 关键字:

      final 可以用来修饰变量(包括类属性、对象属性、局部变量和形参)、方法(包括类方法和对象方法)和类。

      final 含义为 “最终的”。使用 final 关键字声明类,就是把类定义定义为最终类,不能被继承,或者用于修饰方法,该方法不能被子类重写:

      • 声明类:
      1
      2
      3
      final class 类名 {
      //类体
      }
      • 声明方法:
      1
      2
      3
      修饰符(public/private/default/protected) final 返回值类型 方法名(){
      //方法体
      }

16.4 super和this的区别

关于 Java super 和 this 关键字的异同,可简单总结为以下几条。

  1. 子类和父类中变量或方法名称相同时,用 super 关键字来访问。可以理解为 super 是指向自己父类对象的一个指针。在子类中调用父类的构造方法。
  2. this 是自身的一个对象,代表对象本身,可以理解为 this 是指向对象本身的一个指针。在同一个类中调用其它方法。
  3. this 和 super 不能同时出现在一个构造方法里面,因为 this 必然会调用其它的构造方法,其它的构造方法中肯定会有 super 语句的存在,所以在同一个构造方法里面有相同的语句,就失去了语句的意义,编译器也不会通过。
  4. this( ) 和 super( ) 都指的是对象,所以,均不可以在 static 环境中使用,包括 static 变量、static 方法和 static 语句块。
  5. 从本质上讲,this 是一个指向对象本身的指针, 然而 super 是一个 Java 关键字。

17、Java 重写(Override)

在子类中如果创建了一个与父类中相同名称、相同返回值类型、相同参数列表的方法,只是方法体中的实现不同,以实现不同于父类的功能,这种方式被称为方法重写(override),又称为方法覆盖。当父类中的方法无法满足子类需求或子类具有特有功能的时候,需要方法重写。

在重写方法时,需要遵循下面的规则:

  • 参数列表必须完全与被重写的方法参数列表相同。
  • 返回的类型必须与被重写的方法的返回类型相同。
  • 访问权限不能比父类中被重写方法的访问权限更低(public>protected>default>private)。
  • 重写方法一定不能抛出新的检査异常或者比被重写方法声明更加宽泛的检査型异常。
  • 声明为 final 的方法不能被重写。
  • 声明为 final 的方法不能被重写。(但是可以声明)
  • 构造方法不能被重写。
  • 如果不能继承一个方法,则不能重写这个方法。(重写的前提是继承)

18、Java重载(Overload)

如果同一个类中包含了两个或两个以上方法名相同的方法,但形参列表不同,这种情况被称为方法重载(overload)。

重载规则:

  • 被重载的方法必须改变参数列表(参数个数或类型不一样);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。

19、Java 多态

多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。

多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。

多态的优点

  • 消除类型之间的耦合关系
  • 可替换
  • 可扩充性
  • 接口性
  • 灵活性
  • 简化性

多态存在的三个必要条件

  • 继承
  • 重写
  • 父类引用指向子类对象:Parent p = new Child();

20、Java抽象(abstract)类

在 Java 中抽象类的语法格式如下:

1
2
3
<abstract>class<class_name> {
<abstract><type><method_name>(parameter-iist);
}

需要注意的是 abstract 关键字只能用于普通方法,不能用于 static 方法或者构造方法中。

抽象方法的 3 个特征如下:

  1. 抽象方法没有方法体
  2. 抽象方法必须存在于抽象类中
  3. 子类重写父类时,必须重写父类所有的抽象方法

抽象类的定义和使用规则如下:

  1. 抽象类和抽象方法都要使用 abstract 关键字声明。
  2. 如果一个方法被声明为抽象的,那么这个类也必须声明为抽象的。而一个抽象类中,可以有 0n 个抽象方法,以及 0n 个具体方法。
  3. 抽象类不能实例化,也就是不能使用 new 关键字创建对象。

抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。

继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。

21、Java 封装

封装的优点:

  1. 良好的封装能够减少耦合。
  2. 类内部的结构可以自由修改。
  3. 可以对成员变量进行更精确的控制。
  4. 隐藏信息,实现细节。

22、Java 接口(Interface)

Java 接口的定义方式与类基本相同,不过接口定义使用的关键字是 interface,接口定义的语法格式如下:

1
2
3
4
5
[public] interface interface_name [extends interface1_name[, interface2_name,…]] {
// 接口体,其中可以包含定义常量和声明方法
[public] [static] [final] type constant_name = value; // 定义常量
[public] [abstract] returnType method_name(parameter_list); // 声明方法
}

注意:一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类。

继承使用 extends 关键字,实现则使用 implements 关键字。

接口与类相似点:

  1. 一个接口可以有多个方法。
  2. 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
  3. 接口的字节码文件保存在.class结尾的文件中。
  4. 接口相应的字节码文件必须在与包名称相匹配的目录结构中。

接口与类的区别:

  1. 接口不能用于实例化对象。
  2. 接口没有构造方法。
  3. 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。
  4. 接口不能包含成员变量,除了 static 和 final 变量。
  5. 接口不是被类继承了,而是要被类实现。
  6. 接口支持多继承。

接口中的成员变量只能是 public static final 类型的。

23、Java匿名类

匿名类是指没有类名的内部类,必须在创建时使用 new 语句来声明类。其语法形式如下:

1
2
3
new <类或接口>() {
// 类的主体
};

匿名类有两种实现方式:

  • 继承一个类,重写其方法。
  • 实现一个接口(可以是多个),实现其方法。

如果匿名类位于一个方法中,则匿名类只能访问方法中 final 类型的局部变量和参数。

24、Java 包(package)

包的作用:

  1. 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
  2. 如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
  3. 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

import 关键字:

为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。使用 “import” 语句可完成此功能。

1
import package1[.package2…].(classname|*);

25、Java 集合框架

Set和List的区别:

  1. Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素。
  2. Set 检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>
  3. List 和数组类似,可以动态增长,根据实际存储的数据的长度自动增长 List 的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector>

Java 集合类型分为 Collection Map,它们是 Java 集合的根接口,这两个接口又包含了一些子接口或实现类。图 1 和图 2 分别为 CollectionMap 的子接口及其实现类。

Collection接口结构Map接口结构

26、Java List集合

List 是一个有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List 集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。List 集合默认按元素的添加顺序设置元素的索引,第一个添加到 List 集合中的元素的索引为 0,第二个为 1,依此类推。

26.1 ArrayList 类

ArrayList 类位于 java.util 包中,使用前需要引入它,语法格式如下:

1
2
3
import java.util.ArrayList; // 引入 ArrayList 类

ArrayList<E> objectName =new ArrayList<>();  // 初始化

ArrayList 是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。

ArrayList 类实现了可变数组的大小,存储在内的数据称为元素。对尾部成员的增加和删除支持较好。使用 ArrayList 创建的集合,允许对集合中的元素进行快速的随机访问,不过,向 ArrayList 中插入与删除元素的速度相对较慢。

常用方法

方法名称 说明
add() 方法 添加元素到 ArrayList
get() 方法 访问 ArrayList 中的元素
set() 方法 修改 ArrayList 中的元素,第一个参数为索引位置,第二个为要修改的值
remove() 方法 删除 ArrayList 中的元素
size() 方法 计算 ArrayList 中的元素数量
sort() 方法 对字符或数字列表进行排序
int index(Object o) 返回此集合中第一次出现指定元素的索引,如果此集合不包含该元素,则返回 -1
int lastIndexOf(Object o) 返回此集合中最后一次出现指定元素的索引,如果此集合不包含该元素,则返回 -1

26.2 LinkedList 类

LinkedList类采用链表结构保存对象,这种结构的优点是便于向集合中插入或者删除元素。需要频繁向集合中插入和删除元素时,使用 LinkedList 类比 ArrayList 类效果高,但是 LinkedList 类随机访问元素的速度则相对较慢。这里的随机访问是指检索集合中特定索引位置的元素。

以下情况使用 ArrayList :

  • 频繁访问列表中的某一个元素。
  • 只需要在列表末尾进行添加和删除元素操作。

以下情况使用 LinkedList :

  • 你需要通过循环迭代来访问列表中的某些元素。
  • 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。

LinkedList 类位于 java.util 包中,使用前需要引入它,语法格式如下:

1
2
3
4
5
6
// 引入 LinkedList 类
import java.util.LinkedList;

LinkedList<E> list = new LinkedList<E>(); // 普通创建方法
或者
LinkedList<E> list = new LinkedList(Collection<? extends E> c); // 使用集合创建链表

常用方法:

方法名称 说明
void addFirst(E e) 将指定元素添加到此集合的开头
void addLast(E e) 将指定元素添加到此集合的末尾
E getFirst() 返回此集合的第一个元素
E getLast() 返回此集合的最后一个元素
E removeFirst() 删除此集合中的第一个元素
E removeLast() 删除此集合中的最后一个元素

26.3 ArrayList 类和 LinkedList 类的区别

  1. ArrayList 是基于动态数组数据结构的实现,访问元素速度优于 LinkedList。
  2. LinkedList 是基于链表数据结构的实现,在批量插入或删除数据时优于 ArrayList。

27、Java set 集合

Set 集合类似于一个罐子,程序可以依次把多个对象“丢进”Set 集合,而 Set 集合通常不能记住元素的添加顺序。也就是说 Set 集合中的对象不按特定的方式排序,只是简单地把对象加入集合。Set 集合中不能包含重复的对象,并且最多只允许包含一个 null 元素。

27.1 HashSet 类

HashSet 是按照 Hash 算法来存储集合中的元素。因此具有很好的存取和查找性能。

HashSet 具有以下特点:

  • 不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化。
  • HashSet 不是同步的,如果多个线程同时访问或修改一个 HashSet,则必须通过代码来保证其同步。
  • 集合元素值可以是 null。

两个对象的 hashCode 值相等且通过 equals() 方法比较返回结果为 true,则 HashSet 集合认为两个元素相等。

HashSet 类位于 java.util 包中,使用前需要引入它,语法格式如下:

1
2
3
import java.util.HashSet; // 引入 HashSet 类

HashSet<String> sites = new HashSet<String>();

常用方法:

方法 说明
add() 方法 添加元素,集合中的每个元素都必须是唯一的。
contains() 方法 判断元素是否存在

27.2 TreeSet 类

TreeSet 类同时实现了 Set 接口和 SortedSet 接口。SortedSet 接口是 Set 接口的子接口,可以实现对集合进行自然排序,因此使用 TreeSet 类实现的 Set 接口默认情况下是自然排序的,这里的自然排序指的是升序排序。

TreeSet 类除了实现 Collection 接口的所有方法之外,还提供了如表 2所示的方法。

方法名称 说明
E first() 返回此集合中的第一个元素。其中,E 表示集合中元素的数据类型
E last() 返回此集合中的最后一个元素
E poolFirst() 获取并移除此集合中的第一个元素
E poolLast() 获取并移除此集合中的最后一个元素
SortedSet subSet(E fromElement,E toElement) 返回一个新的集合,新集合包含原集合中 fromElement 对象与 toElement 对象之间的所有对象。包含 fromElement 对象,不包含 toElement 对象
SortedSet headSet<E toElement〉 返回一个新的集合,新集合包含原集合中 toElement 对象之前的所有对象。 不包含 toElement 对象
SortedSet tailSet(E fromElement) 返回一个新的集合,新集合包含原集合中 fromElement 对象之后的所有对 象。包含 fromElement 对象

28、Java Map集合

Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含一个键(key)对象和一个值(value)对象。用于保存具有映射关系的数据。

Java 8 的新特性

1、Java Lambda 表达式

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。

Lambda 表达式标准语法形式如下:

1
2
3
(parameters) -> expression

(parameters) ->{ statements; }

->被称为箭头操作符或 Lambda 操作符,箭头操作符将 Lambda 表达式拆分成两部分:

  • 左侧:Lambda 表达式的参数列表。
  • 右侧:Lambda 表达式中所需执行的功能,用{ }包起来,即 Lambda 体。

Lambda 表达式实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. 不需要参数,返回值为 5  
() -> 5

// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x

// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y

// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y

// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)

在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

2、函数式接口

Lambda 表达式实现的接口不是普通的接口,而是函数式接口。如果一个接口中,有且只有一个抽象的方法(Object 类中的方法不包括在内),那这个接口就可以被看做是函数式接口。这种接口只能有一个方法。如果接口中声明多个抽象方法,那么 Lambda 表达式会发生编译错误:

1
The target type of this expression must be a functional interface

这说明该接口不是函数式接口,为了防止在函数式接口中声明多个抽象方法,Java 8 提供了一个声明函数式接口注解 @FunctionalInterface

提示:Lambda 表达式是一个匿名方法代码,Java 中的方法必须声明在类或接口中,那么 Lambda 表达式所实现的匿名方法是在函数式接口中声明的。

3、Java Lambda表达式的使用

  1. 作为参数使用Lambda表达式

    Lambda 表达式一种常见的用途就是作为参数传递给方法,这需要声明参数的类型声明为函数式接口类型。

  2. 访问变量

    • 访问成员变量

      静态方法中不能访问实例成员变量, Lambda 表达式中也不能访问实例成员变量,也不能访问实例成员方法。

      实例方法中能够访问静态成员变量和实例成员变量,当然实例方法和静态方法也可以访问。当访问实例成员变量或实例方法时可以使用 this,如果不与局部变量发生冲突情况下可以省略 this。

    • 访问局部变量

      访问局部变量时,变量必须是 final 类型的(不可改变)。

  3. 方法引用

4、Java 8 方法引用

方法引用可以理解为 Lambda 表达式的快捷写法,它比 Lambda 表达式更加的简洁,可读性更高,有很好的重用性。

Java 8 之后增加了双冒号::运算符,该运算符用于方法引用,注意不是调用方法。“方法引用”虽然没有直接使用 Lambda 表达式,但也与 Lambda 表达式有关,与函数式接口有关。 方法引用的语法格式如下:

1
2
ObjectRef::methodName 
//ObjectRef 是类名或者实例名,methodName 是相应的方法名。

注意:被引用方法的参数列表和返回值类型,必须与函数式接口方法参数列表和方法返回值类型一致。

Java 中 4 种不同方法的引用:

  • 构造器引用:它的语法是Class::new,或者更一般的Class< T >::new
  • 静态方法引用:它的语法是Class::static_method
  • 特定类的任意对象的方法引用:它的语法是Class::method
  • 特定对象的方法引用:它的语法是instance::method

5、Java Lambda表达式与匿名内部类的联系和区别

Lambda 表达式与匿名内部类的相同点如下:

  • Lambda 表达式与匿名内部类一样,都可以直接访问 effectively final 的局部变量,以及外部类的成员变量(包括实例变量和类变量)。
  • Lambda 表达式创建的对象与匿名内部类生成的对象一样,都可以直接调用从接口中继承的默认方法。

Lambda 表达式与匿名内部类主要存在如下区别:

  • 匿名内部类可以为任意接口创建实例——不管接口包含多少个抽象方法,只要匿名内部类实现所有的抽象方法即可;但 Lambda 表达式只能为函数式接口创建实例。
  • 匿名内部类可以为抽象类甚至普通类创建实例;但 Lambda 表达式只能为函数式接口创建实例。
  • 匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认方法;但 Lambda 表达式的代码块不允许调用接口中定义的默认方法。

6、Stream API的使用

6.1 什么是 Stream?

Stream(流)是一个来自数据源的元素队列并支持聚合操作

注意:

  1. Stream 自己不会存储元素。
  2. Stream 不会改变原对象,他们会返回一个持有结果的新Stream。
  3. Stream 操作是延迟执行的,这意味者他们会等到需要结果的时候执行。

6.2 Stream 操作的三个步骤

  • 创建Stream

    一个数据源(如:集合、数组等),获取一个流。

  • 中间操作

    一个中间操作链,对数据源的数据进行处理。

  • 终止操作(终端操作)

    一个终止操作。执行中间链操作,并产生结果。