Java核心技术之基础知识

本文是我考研之后,回顾以前学过的Java基础知识做的笔记,所以会比较简洁,很多基础的东西可能没有
文中所用到的JDK版本是1.8

1 Java基础了解及配置

1.1 JDK的版本更新时间表

  • 1996/JDK1.0(Sun/Oracle JDK, 下同)
  • 1997/JDK1.1
  • 1998/JDK1.2 (1.2-1.4 统称 Java 2)J2EE,J2SE,J2ME
  • 2000/JDK1.3
  • 2002/JDK1.4
  • 2004.5/JDK5.0(1.5)/2009.10 停止更新
  • 2006.12/JDK6.0(1.6)/2013.2 停止更新
  • 2011.7/JDK7.0(1.7)/2015.4 停止更新
  • 2014.3/JDK8.0(长期版本)/2019.1 停止更新
  • 2017.9/JDK9.0(非长期版本)/2018.3停止更新
  • 2018.3/JDK10.0 (非长期版本)/2018.9停止更新
  • 2018.9/JDK11.0(长期版本)/2026.9停止更新

Sun(Oracle) JDK所有版本集中地:https://www.oracle.com/technetwork/java/javase/archive-139210.html

1.2 Java分支

  • JavaSE(Standard Edition)面向PC级应用开发
  • JavaEE(Enterprise Edition)面向企业级应用开发
  • JavaME(Micro Edition)面向嵌入式应用开发
  • JavaFX
  • Java Card
  • Java TV
  • Java DB

1.3 JSR与JCP

JCP是Java Community Process ,他集合很多公司和开发者共同参与制定Java规范,即JSR(Java Specification Requests)

1.4 Java与C++区别

  • 无直接指针操作(Java中主要称为引用或对象)
  • 自动内存管理
  • 数据类型长度固定
  • 不用头文件
  • 不包含结构和联合
  • 不支持宏
  • 不用多重继承
  • 无类外全局变量
  • 无GOTO

1.5 Java编译与运行

graph LR;
A[.java文件 即代码文件] --编译 javac--> B[.class文件 包含与平台无关的虚拟机指令];
B --运行 java--> C[具体的JVM执行代码];

1.6 JRE与JDK

JRE即The Java Runtime Environment 即Java运行环境,JRE=JVM+API(Lib)
JRE运行有三种功能:

  • 加载代码
  • 校验代码
  • 执行代码

JDK是Java开发工具包,JDK=JRE+Tools

1.7 JDK提供的工具

  • Java编译器:javac
  • Java执行器:java
  • 文档生成器:javadoc
  • Java打包器:jar
  • Java调试器:jdb
  • 运行图形界面程序:javaw
  • 运行applet程序:appletViewer
  • 查看类信息和反汇编信息:javap

1.8 JavaDoc生成文档

javadoc -d 目录名 xxx.java
可以生成 /* / 中的注释内容
其中可以用以下标记

  • @author 对类 标明类开发者
  • @version 对类 标明类的版本
  • @see 对类、属性、方法 参考某类,会生成一个链接
  • @param 对方法 对方法中某个参数的说明
  • @return 对方法 对方法返回值的说明
  • @exception 对方法 对方法可能抛出的异常进行说明

1.9 javap

  • 使用javap查看类信息

javap 类名

  • 使用javap反汇编

javap -c 类名

1.10 面向对象

1.10.1 三大特征

  • 封装

将属性和行为封装在类中

  • 继承

更好抽象和分类,增强代码重用率,提高可维护性

  • 多态

一个语句可根据不同的对象执行不同的代码

1.10.2 面向对象设计思想

认为客观世界都由各种对象组成
程序的分析和设计都围绕:

  • 有哪些对象类
  • 每个类有哪些属性和方法
  • 类之间的关系(继承、关联等)
  • 对象之间发送消息(调用方法)

1.11 JDK环境变量配置

  • JAVA_HOME JDK安装路径,如:C:\Program Files\Java\jdk1.8.0_191
  • Path 增加如下信息: ;%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
  • classpath 值:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;

2 Java基本输入输出操作

2.1 使用Scanner类输入

import java.util.Scanner;
class ScannerTest{
  public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);  //写法是固定的
    int a = scanner.nextInt();  //读入一个int类型,还有nextDouble();next();等方法
    System.out.printf("%d的平方是%d\n",a,a*a);  //输出,printf()格式化输出,还有print()和println()
  }
}

2.2 使用in和out

//这种写法是较早期的写法
import java.io.*;
//核心代码
char c = " ";
try{
  c = (char) System.in.read(); //只能得到一个字符
}catch(IOException e){}
System.out.println("You have entered: "+ c );

2.3 使用BufferedReader读入一行

try{
  BufferedReader in = new BufferedReader( new InputStreamReader(System.in));
  s = in.readLine();
  n = Integer.parseInt(s);  //将文本转化为整数,用Double.parseDouble(s);转化为实数
}catch(IOException e){}

3 数据类型

分为:

3.1 基本类型:

3.1.1 数值型:

3.1.1.1 整数型:

byte(1字节 -128~127)/short(2字节 -2^15~2^15-1)/int(4字节 -2^31~2^31-1)/long(8字节 -2^63~2^63-1)

  • 十进制:12,-123
  • 八进制:0开头,012
  • 十六进制:0x或0X开头,0x12
  • 二进制:0b或0B开头,0b01010001 (Java7以上支持)
  • 默认整型为int型,声明long型在数值后加’l’或’L’,如:long l = 3L;
  • Java中无无符号数
3.1.1.2 浮点数:

float(4字节)/double(8字节)

  • 默认为double类型,声明float型需加f或F
  • Java7以上:123_456.789_000(支持千分位分隔符,用下划线表示)
  • 科学计数法:3.14e2 3.14E2

3.1.2 字符型:

char(两个字节,采用的是Unicode编码,可以用十六进制编码形式表示,如'\u0061'

常用的转义字符:

  • \ddd 1到3位八进制数表示的字符(ddd)
  • \uxxxx 1到4位十六进制表示的字符(xxxx)
  • \’
  • \”
  • \
  • \r 回车
  • \n 换行
  • \f 走纸换页
  • \t 横向跳格
  • \b 退格

3.1.3 布尔型:

boolean(只有true和false)

3.2 引用类型:

类(class)/接口(interface)/数组(array)/枚举(enum)

两种类型的区别:

  • 基本类型:变量值在栈中,赋值时复制的是值
  • 引用类型:栈中存储的是真实值的地址,指向堆里,赋值时复制的是引用,更改时同一个引用一块改

4 运算符

  • 算术运算符:+,-,*,/,%,++,–
  • 关系运算符:>,<,>=,<=,==,!=
  • 逻辑运算符:!,&,|,^,&&,||
    • && 与 || 是短路运算符,即如果第一个操作数可以得出结论,就不判断第二个操作数
  • 位运算符:&(按位与),|(按位或),^(按位异或),~(取反),>>,<<,>>>
  • 赋值运算符: =,+=,-=,*=,/=
  • 字符串连接运算符:+

4.1 计算机中整数表示&&位运算

在计算机中所有的整数均由补码表示
对正数,补码即原码;对负数,补码是原码取反加一

左移 “a << b”表示将二进制的a左移b位,空出的最低的b位补0。相当于乘以2
右移 “a >> b”表示将二进制的a右移b位,空出的最高的b位,如果是正数补0,如果是负数补1。相当于除以2
无符号右移 “a >>> b”表示将二进制的a右移b位,空出的最高的b位不管正负均补0

对int型整数移b位,系统会先将b对32取模,得到的结果才是真正移位的位数
对long型整数移b位,会将b对64取模
对低于int型的操作数,如byte,short,char 会先自动转化为int型再移位

public static void main(String[] args) {
    int a = 0b10101;
    int b = -a;
    System.out.println(a);
    out(a);
    out(-a);
    out(~a);
    out(~a+1);
    out(~b+1);
    out(a << 5);
    out(b << 5);
    out(a>>3);
    out(b>>3);
    out(b>>>3);
}

public static void out( int a ) {
    String binaryString = Integer.toBinaryString(a);
    while (binaryString.length() < 32)
        binaryString = '0' + binaryString;

    binaryString = binaryString.substring(0,4) + " "
            + binaryString.substring(4,8) + " "
            + binaryString.substring(8,12) + " "
            + binaryString.substring(12,16) + " "
            + binaryString.substring(16,20) + " "
            + binaryString.substring(20,24) + " "
            + binaryString.substring(24,28) + " "
            + binaryString.substring(28,32);

    System.out.println(binaryString);
}

运行结果是:

21                                       # a
0000 0000 0000 0000 0000 0000 0001 0101  # a的二进制形式
1111 1111 1111 1111 1111 1111 1110 1011  # -a
1111 1111 1111 1111 1111 1111 1110 1010  # ~a 取反
1111 1111 1111 1111 1111 1111 1110 1011  # ~a+1 取反加一,补码
0000 0000 0000 0000 0000 0000 0001 0101  # 对-a取反加一 变为正值的原码
0000 0000 0000 0000 0000 0010 1010 0000  # a左移5位
1111 1111 1111 1111 1111 1101 0110 0000  # b左移5位
0000 0000 0000 0000 0000 0000 0000 0010  # a右移3位
1111 1111 1111 1111 1111 1111 1111 1101  # b右移3位
0001 1111 1111 1111 1111 1111 1111 1101  # b无符号右移3位

5 流程控制

程序三种基本的流程:顺序、分支、循环

5.1 顺序

最简单的语句,即方法调用语句和赋值语句
需注意的是,Java中没有“表达式语句”,即类似“x+y;”的语句是不合法的,一个语句要么赋值,要么方法调用

5.2 分支语句

有两种:if-else语句和switch语句
switch语句:

  • 变量的类型:整数,字符,字符串和枚举
  • case 后边是常量
  • 默认的分支标识符是default
  • 注意添加break语句

5.3 循环语句

三种:for循环;while循环;do-while循环

5.4 goto语句及解决

Java中没有使用goto语句,其解决方式是:
在循环中:break 标号 ,continue 标号

5.4.1 break语句

break语句用于终止某个语句块的执行
break语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层的语句

//示例代码
public static void main(String[] args) {
    a:{
        System.out.println("This is the start of a");
        b:{
            System.out.println("This is the start of b");
            c:{
                System.out.println("This is the start of c");
                d:{
                    System.out.println("This is the start of d");
                    break b;
                }
                //System.out.println("This is the end of c");  这句话在idea编辑时是报错的
            }
            //System.out.println("This is the end of b");  这句话在idea编辑时是报错的
        }
        System.out.println("This is the end of a");
    }
}

//运行结果
This is the start of a
This is the start of b
This is the start of c
This is the start of d
This is the end of a

5.4.2 continue语句

continue语句用于跳过某个循环语句块的一次执行
出现在多岑嵌套的的循环语句体中时,可以通过标签指明要跳过的时哪一层循环

//示例代码:判断100以内的质数
public static void main(String[] args) {
    other:
    for (int i = 1; i <= 100; i+=2) {
        for (int j = 2; j < i; ++j) {
            if(i%j==0){
                continue other;
            }
        }
        System.out.println(i);
    }
}

6 注释

Java中有三种注释方式:

  • // 单行注释
  • / …… / 用于多行注释
  • /* …… / 用于生成doc文档的注释

关于Doc文档注释的使用可参考1.8的JavaDoc生成文档

7 数组

数组是多个相同类型数据的组合
数组的声明方式:

  • int[] a;
  • int []b;
  • int c[] , []d ,e; //e就是int型,不是数组

Java中一定要对数组分配空间,然后再使用
Java中int a[5]; 是非法的,因为数组类型在Java中是一种引用类型

数组的静态初始化,即在定义数组时就对数组元素分配空间并赋值

int[] a = {3,9,8};
int[] b = new int[]{3,9,8};
MyDate[] dates = {
  new MyDate(1,2,1996),
  new MyDate(3,4,1995),
  new MyDate(4,5,1888)
};

特别需要注意:数组是引用类型,已经分配空间,其中的每个元素也被按照成员变量的方式被隐式初始化
数值型为0,boolean型为false,引用型为null

数组复制的常用方法有4种

  1. for循环,效率最低
  2. System.arraycopy() 效率最高
  3. Arrays.copyOf() 效率次于第二种方法
  4. Object.clone() 效率次于第二种和第三种

多维数组定义方法是:

int[][] t = new int[3][];
t[0] = new int[2];
t[1] = new int[3];
t[2] = new int[4];
// int[][] t = new int[][3]; 在Java中是非法的,因为在Java中二维数组相当于一维数组,只不过是内容是数组的引用的数组

8 类、包、接口

8.1 类、字段、方法

类是Java程序的基本要素
类包含字段(变量)和方法(函数)

8.1.1 构造方法

构造方法是一种特殊的方法,用来初始化(new)该类的一个新的对象,与类名同名,且不写返回数据类型
访问对象的字段或方法,使用“.”
默认的构造方法是无形参的。

8.1.2 方法重载(overloading)

多个方法有相同的名字,通过方法的签名(signature)不同,即参数个数或类型不同,编译时可以识别出
方法重载可以实现多态
重载的返回值可以相同也可以不同

8.1.3 this

  1. 在方法和构造方法中,使用this来访问字段和方法
  2. 使用this可以解决局部变量和类变量的同名问题
  3. 构造方法中,可以使用this调用另一种构造方法,但注意:这条调用语句必须放在第一句

8.2 类的继承(extends)

继承是面向对象的程序设计中最为重要的特征之一
Java中支持单继承:即一个类只有一个父类
Java中使用extends关键字实现继承

8.2.1 方法重写(override)

是子类重新定义与父类同名同参数的方法,并覆盖父类的该方法。
在JDK1.5以后使用@Override的注记表示是一个重写的方法(不用也可以)

8.2.2 动态绑定

Java中,如果调用一个重写了的方法,会自动调用子类重写的方法定义

8.2.3 方法重载(overload)

相当于新加了一个方法

8.2.4 super

通过super可以访问父类的变量和方法,如父类中被子类隐藏的同名字段和方法
在构造方法中,可以通过super();来调用父类的构造方法。
注意:super() 必须放在第一句,即不可以用this来调用另一种构造方法了。

8.2.5 父类与子类的对象的兑换

  1. 子类对象可以被视为父类的一个对象,比如,如果一个方法的形参调用的是父类对象,那么调用这个方法时可以用子类对象作为实参
  2. 父类对象不可以被当作其某一个子类对象
  3. 如果父类对象引用指向的实际是一个子类对象,则这个父类对象的引用可以通过强制类型转换成子类对象的引用

8.3 包

同一包内各个类默认是可以互相访问的

8.3.1 import语句

使用星号(*)只能表示本层次所有类,不包括子层次下的类

8.3.2 CLASSPATH环境变量

用于指明包层次的根目录
可以在环境变量中设置
常用的设置是:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
设置中.;代表了当前目录,第二个是Java的类库目录

8.3.2.1 rt.jar / tools.jar / dt.jar的作用
  1. rt.jar是Java的基础类库,默认就在Root Classloader的加载路径中,因此classpath中不需要加入,同样的jar包还有jre/lib目录下的:jce.jar、jsse.jar、charsets.jar、resources.jar
  2. tools.jar 是工具类库,编译和运行需要的类库
  3. dt.jar是关于运行环境的类库,主要是swing的包,在用swing时最好加上

8.4 修饰符(modifiers)

8.4.1 访问控制符

8.4.1.1 成员变量的访问控制符
  同一个类文件中 同一个包中 不同包中的子类 不同包中的非子类
private yes yes    
默认(default) yes yes    
protected yes yes yes  
public yes yes yes yes
8.4.1.2 类的访问权限

只有两种:

  1. public 即所有的都能够访问
  2. default 即只能被同包中的类可访问

8.4.2 非访问控制符

  基本含义 修饰类 修饰成员 修饰方法内局部变量
static 静态的,属于整个类 可以修饰内部类 yes  
final 最终的,不可变的 yes yes yes
abstract 抽象的,不可实例化的 yes 用于修饰方法  
8.4.2.1 static
  • 静态字段属于整个类,存在与类的公共内存空间中,而不是存于某一个对象实例的内存区间
    • 类变量可以通过类名直接访问,也可通过实例对象访问
  • 静态方法,又称为类方法,与之相对的称为实例方法
    • 类方法本质属于整个类
      • 直接使用类名访问
      • 不能使用this和super
  • import static导入某个类的静态方法和字段,可以直接访问而不用使用类名。
8.4.2.2 final
  1. final类

表示类不能被继承,即不可能有子类

  1. final方法
    不能被子类重写
  2. final字段或局部变量
    一旦给定,不能被修改,是只读变量,只能被赋值一次
    一个字段被static final同时修饰时,可以用来表示常量

    关于赋值
    使用staic final修饰的字段,若不给定初始值,则按照默认值初始化(数值型为0,boolean型为false,引用型为null)
    只使用final修饰的字段,则必须且只能赋值一次,不能缺省。赋值方法有两种:一是定义变量时赋初始值,二是在每一个构造函数中进行赋值
    定义final局部变量时,必须也只能赋值一次,其值在变量存在期间不会改变

8.4.2.3 abstract
abstract类

即抽象类,该类不能被实例化

abstract方法

即抽象方法,只需声明,不需实现
抽象类可以包含抽象方法,也可不包含抽象方法;但如果一个类中有抽象方法,则类必须是抽象类
子类必须实现抽象方法,否则也是抽象类

8.5 接口(interface)

可以看成某种特征的约定,所有的接口都是public abstract
一个类可以实现多个接口,用implements实现
面向接口编程,而不是面向实现

8.5.1 接口的作用

通过接口可以实现不相关类的相同行为,而不需要考虑这些类的层次关系,从而在一定意义上实现了多重继承
可以指明多个类需要实现的方法
可以了解对象的交互界面,而不需要在意实现的类

8.5.2 接口定义

[ public ]interface interfaceName [extends listOfSuperInterface]{…}

  • public指明任意类均可实现该接口,默认情况只有同包下的类才可实现该接口
  • extends实现接口的继承,可以有多个父接口,用逗号隔开
  • 接口中所有方法都是抽象方法,默认即是public abstract
  • 接口中不能包含成员变量,只有常量,即被public static final修饰的
  • JDK1.8以上,接口还可以定义static方法和default方法,这两种方法可以被实现

9 变量

9.1 字段变量与局部变量

  • 字段变量定义于类中,局部变量是方法中定义或方法的参数
  • 字段变量作为对象的一部分,存于堆中,局部变量存于栈中
  • 生命周期不同
  • 字段变量可以自动赋初值,局部变量必须显式赋值
  • 字段变量可以被public、private、static、final修饰
  • 局部变量只可以被final修饰

10 多态

指一个程序中相同名字表示不同含义的情况
多态有两种情形

  • 编译时多态
    • 重载(overload):同名不同参数或返回值的方法
  • 运行时多态
    • 重写(override):子类对父类方法重写(覆盖)
    • 动态绑定(dynamic binding),也被称为虚方法调用(virtual method invoking)

10.1 上溯造型(upcasting)和虚方法调用

即把子类当成父类处理
在调用方法时,使用虚方法调用,即在运行时根据具体的实例类型来选择调用哪个方法

10.2 动态类型确定 instanceof关键字

可以通过变量 instanceof 类型 来确定是否是这个类型,是则返回true

10.3 什么时候不是虚方法调用

  • 默认的普通方法均是虚方法
  • static,private,final方法不是虚方法调用
  • static方法,以声明的类型为准,与实例类型无关
  • private方法子类看不见,不会被虚化
  • final方法子类不能重写,不存在虚化问题

11 对象的构造和初始化

所有的类都有构造方法,抽象类也有构造方法,虽然不能使用new来创建一个,但是子类会调用它

11.1 双花括号初始化

Java中创建对象初始化时,有一个特殊的写法,即双花括号初始化

Map source = new HashMap(){{
    put("firstName", "John");
    put("lastName", "Smith");
    put("organizations", new HashMap(){{
        put("it", new HashMap(){{
            put("id", "1234");
        }});
        put("oa", new HashMap(){{
            put("id", "5678");
        }});
    }});
}};

双花括号初始化实例,其实就是通过构造一个继承自Map的匿名类,然后再实例化这个匿名类
但不建议使用这种方式,原因可参见:永远不要使用双花括号初始化实例!!!

11.2 实例初始化和静态初始化

11.2.1 实例初始化

在类中直接写{ 语句…… }
实例初始化是先于构造方法中除super和this的语句执行

11.2.2 静态初始化

static { 语句…… }
静态初始化在第一次使用类的时候执行,执行的时机不确定,但总先于实例初始化

11.3 构造方法的执行过程

  1. 调用本类或父类的构造方法,直到最高层
  2. 按照声明顺序执行字段的初始化赋值
  3. 执行构造方法中的其他语句

如果在构造函数中调用了一个虚方法,会造成从语法上合法,但从事实上不合理
因此,可能的话,在构造器中尽量避免调用任何方法
唯一能够安全调用的是具有final属性的方法

12 对象的清除与垃圾回收

Java中的对象清理是由Java虚拟机的垃圾回收线程来完成的
简单的说,当一个对象,他的被引用数为零,即没有被人用,说明该对象可以回收
我们可以使用System.gc()方法要求系统进行垃圾回收

12.1 System.gc()方法

是System类的static方法,我们可以使用System.gc()方法要求系统进行垃圾回收,但它只是“建议”,即不能强制进行垃圾回收

12.2 finalize()方法

Java中没有“析构方法(destructor)”
但是Object的finalize()有类似的功能
系统在回收时会自动调用对象的finalize()方法
由于finalize()方法的调用时机不确定,所以一般不使用

12.3 try-with-resources

在进行一些非内存资源的关闭工作,如关闭文件等操作时,可以使用tru-with-resources语句(JDK1.7以上)
这个语句可以用于实现了java.lang.AutoCloseable的对象,他会自动调用其close()方法
语句的格式:

try(Scanner sccanner = new Scanner){
    ......
}

13 内部类和匿名类

13.1 内部类

  • 内部类是将类的定义 class xxx{…}放在一个类的内部
  • 编译器会生成xxxx$xxxx.class 的class文件
  • 内部类不能与外部类同名
  • 在封装内部类的类中使用,与普通的类使用方法相同
  • 在其他地方使用时,类名前要冠以外部类的名字,在用new创建内部类时,也要在new前冠以对象变量,即外补对象名.new 内部类名(参数)
  • 内部类可以直接访问外部类的字段和方法,即使是private
  • 如果内部类中有与外部类同名的字段或方法,可以用`外部类名.this.字段或方法
  • 修饰符
    • 与字段、方法一样是外部类的成员
    • public protected default private
    • final static
public class TestInnerClass {
    public static void main(String[] args) {
        Test.Test2 c = new Test(2).new Test2();
        System.out.println(c.getB());
    }
}

class Test{
    private int a ;
    private int b;
    class Test2{
        int b;
        public Test2(){
            this.b = Test.this.b;
        }
        public int getB(){
            return b;
        }
    }
    public Test(int a){
        this.a = a;
    }
    public int getA(){
        return a;
    }
}

13.2 static 修饰的内部类

用static修饰的内部类,其实表明这个内部类实际上是一种外部类。此时,他与外部类的实例无关
static类在使用时:

  • 实例化static类时,在new前不需要对象实例变量;
  • static类中不能访问外部类的非static的字段和方法,只能访问static成员
public class TestStaticInnerClass{
    public static void main(String[] args){
        Test.Test1 a = new Test.Test1();
    }
}
class Test{
    static class Test1{

    }
}

13.3 局部类

  • 在方法中定义的类
  • 使用同局部变量一样
  • 可以访问外部类的成员
  • 不能访问该方法的局部变量,除非时final局部常量

13.4 匿名类

特点:

  • 使用时,必须是继承一个类或实现一个接口
  • 匿名内部类中不能存在任何的静态成员变量和静态方法
  • 匿名内部类不能是抽象的,必须要实现继承的类或实现的接口的所有抽象方法
  • 编译器编译时会将内部类命名为:父类名/接口名$(1/2/3…)

示例:

Runable hello = new Runnable(){
    public void run(){
        System.out.print("hello");
    }
};

匿名内部类可以包含:

  • 字段
  • 方法
  • 实例初始化代码
  • 本地类

匿名内部类方法的访问方式

//方法1:直接在new A内部类后边加点加方法,这样访问,如果方法多了,调用太麻烦
new D(){
    @Override
    public void ShowContext() {
        System.out.println("hello");
    }
}.ShowContext();

//方法2:通过创建对象来访问,多态思想
D a=new D(){
    @Override
    public void ShowContext() {
        System.out.println("hello");
    }
};
a.ShowContext();

14 Lambda表达式

  • Lambda表达式时Java8以上开始支持
  • Lambda表达式其实就是实现一个匿名类,可以参考13.4节。
  • Lambda表达式其实就是Java的一个语法糖,即一个对语言功能没有影响,但更方便程序员编写代码的语法。
  • Lambda表达式写法: 参数 -> 结果
    • 参数是 () 或 1个参数 或 (多个参数)
    • 结果是 表达式 或 语句 或 {语句}
  • Lambda表达式的使用条件
    • 由于Lambda表达式只能实现一个函数,所以能写成Lambda表达式的接口要求有且仅有一个抽象函数
    • 这样的接口可以用使用注解@FunctionalInterface来表示,称为函数式接口
  • Lambda表达式不仅仅是简写了代码,更重要的是,它将代码当成了数据来处理,体现了函数式编程的思想
//示例——线程
//以前写法
Runable doIt = new Runable(){
    public void run(){
        System.out.print("aaa");
    }
};
new Thread(doIt).start;
//使用Lambda表达式改写后
Runable doIt = () -> System.out.print("aaa");
new Thread(doIt).start;
//或者是
new Thread(() -> System.out.print("aaa")).start;

15 装箱

Java中将几个基本类型(primitive type)包装成Object(引用类型),例如 int –> Integer。Java中共有8类:
Boolean,Byte,Short,Character,Integer,Long,Float,Double
装箱:Integer I = 10;
拆箱:int i = I;

实际上译为:

Integer I = Integer.valueOf(10);  
int i = I.intValue();  

和Lambda表达式一样,同样是一个语法糖

16 枚举(enum)

枚举是一种特殊的class类型,实际上生成了class Name extends java.lang.Enum

例如定义如下变量

public enum ColorEnum {
    RED,BLUE,GREEN
}

解析加工后得到枚举编译成的类的源代码

public final class ColorEnum extends Enum
{

    //返回存储枚举实例的数组的副本。values()方法通常用于foreach循环遍历枚举常量。
    public static ColorEnum[] values()
    {
        return (ColorEnum[])$VALUES.clone();
    }
    //根据实例名获取实例
    public static ColorEnum valueOf(String s)
    {
        return (ColorEnum)Enum.valueOf(ColorEnum, s);
    }

    //私有构造方法,这里调用了父类的构造方法,其中参数s对应了常量名,参数i代表枚举的一个顺序(这个顺序与枚举的声明顺序对应,用于oridinal()方法返回顺序值)
    private ColorEnum(String s, int i)
    {
        super(s, i);
    }

    //我们定义的枚举在这里声明了三个 ColorEnum的常量对象引用,对象的实例化在static静态块中
    public static final ColorEnum RED;
    public static final ColorEnum BLUE;
    public static final ColorEnum GREEN;
    //将所有枚举的实例存放在数组中
    private static final ColorEnum $VALUES[];

    static
    {
        RED = new ColorEnum("RED", 0);
        BLUE = new ColorEnum("BLUE", 1);
        GREEN = new ColorEnum("GREEN", 2);
        //将所有枚举的实例存放在数组中
        $VALUES = (new ColorEnum[] {
            RED, BLUE, GREEN
        });
    }
}

关于这方面更多的解析,可以参考Java基础——枚举详解

17 注解(annotation)

是在各种语法要素上加上附加信息,以供编译器或其他程序使用。
所有注解都是java.lang.annotation.Annotation的子类

常用的注解

  • @Override 表示覆盖父类的方法
  • @Deprecated 表示过时的方法
  • @SuppressWarnings 表示让编译器不产生警告

18 相等的问题

18.1 ==的问题

Java可以使用==判断两个变量是否相等。但是使用时要注意,基本类型是值相等,即数值内容是否相等;而引用类型是引用相等,是判断两者引用的是否是同一对象,而非内容相等

18.2 基本类型的相等

数值型直接比较即可,而对浮点数,最好不要直接用==,例如Double.NAN==Double.NAN返回的是false。所以浮点数用相减值足够小即可,至于精度可以自己设定。

18.3 基本类型装箱后

Java的包装类在虚拟机中会有缓存,在创建一个常量包装类时,会首先在缓存中查找,因此,

Integer m = 10 ;
Integer n = 10;
System.out.println(m=n);//输出是:true  因为有缓存

包装类的缓存范围:

  • Boolean:(全部缓存)
  • Byte:(全部缓存)
  • Character(<= 127缓存)
  • Short(-128 — 127缓存)
  • Long(-128 — 127缓存)
  • Integer(-128 — 127缓存)
  • Float(没有缓存)
  • Doulbe(没有缓存)

在缓存范围内的,即使没有被引用,垃圾处理也不回收这个资源

18.4 枚举类型是否相等

枚举类型内部进行了唯一实例化,可以直接判断

18.5 引用对象的引用

使用==是直接看两个引用是否是同一个对象,如要判断内容是否一样,要重写equals()方法,并最好同时重写hashCode()

18.6 String对象的特殊性

判断相等,一定不要用==,要用equals
不过,字符串常量会进行内部化(interned),相同的字符串常量是同一引用。

String hello = "Hello",lo = "lo";
String h = "Hello";
System.out.println(hello=="Hello");//true
System.out.println(h == hello);//true

System.out.println(hello==("Hel"+"lo"));//true
System.out.println(hello=="Hel"+lo);//false

System.out.println(hello== new String("Hello"));//false
System.our.println(hello == ("Hel"+lo).intern());//true  intern()是求它的内部化的一个字符串  

19 异常处理

异常处理通常的格式是:

try{
    ......
}catch (异常){
    ......
}finally{
    ......
}

finally语句不管有没有异常是都要运行的,即使有return,continue,break

19.1 finally与return语句哪个先执行

public static void main(String[] args) {
    int i = testReturn();
    System.out.println(i);

    StringBuilder a = testStringBuilder();
    System.out.println(a);

    String b = testString();
    System.out.println(b);
}

public static int testReturn(){
    int i = 1;
    try {
        ++i;
        return i;
    }finally {
        ++i;
        System.out.println("Return");
    }
}
public static StringBuilder testStringBuilder(){
    StringBuilder b = new StringBuilder("a");
    try {
        return b;
    }finally {
        b.append("b");
    }
}
public static String testString(){
    String a = "a";
    try {
        return a;
    }finally {
        a = "b";
    }
}

运行结果:

Return
2
ab
a

为什么两者的结果一个变化一个没有变呢?
首先,我们都知道Java中赋值是有两种,基本类型的变量赋值是数值赋值,引用类型的变量赋值是引用赋值。另外,编译器在编译方法时而会在方法的代码段的前端增加一个返回值类型的内存,执行时return语句时会将返回的内容写入这段内存中,这样执行int a = fangfa();这样的‘=’赋值时可以在内存中匹配到相同的类型。
因此问题便容易理解了,在内存中因为对基本类型的赋值是值赋值,在return语句执行时,会先进行代码段的赋值,再执行finally的语句,此时finally语句中再修改值也不会引起return的值的变化。而对引用变量,赋值是引用赋值,也就说这段内存中的内容是一个引用,而此时finally语句中对引用的内存内容修改时,不会改变这个引用,因此值会变化。而对String类型,这种类型一经创建是不能被修改的,所以代码中’a’的变量是重新被赋予了一个新的引用,但返回的那个引用没有被修改,因此值不变。

19.2 try…with…resource语句(JDK1.7)

任何实现了java.lang.AutoCloseable和java.io.Closeable的对象都可以使用try-with-resource来实现异常处理和关闭资源。
语句格式:

try(类型 变量1 = new 类型();
    类型2 变量2 = new 类型2()){
        ......
}

这个语句自动添加了finally{ 变量.close();}不管是否出现异常都会执行,执行顺序是按照声明顺序的倒序执行。

19.3 RuntimeException的异常

RuntimeException是Exception的子类。但与Exception不同的是,继承了这个类的异常类,在编译的时候不会强制性的要求用户处理异常,用户可以根据需要选择进行处理。如果用户没有处理,而在运行时出现了异常,则交给JVM默认处理。

19.3.1 常见的RuntimeException类

  • ClassCastException 强制类型转换异常
  • ArithmeticException 计算异常,如除数为零
  • FileSystemNotFoundException 文件未找到
  • NullPointerException 空指针异常

主要参考的资料:

中国大学MOOC课程——Java程序设计(北京大学 唐大仕)
Java教程|菜鸟教程
永远不要使用双花括号初始化实例!!!
java匿名类
Java基础——枚举详解
return与finally到底谁先执行


 上一篇
Ubuntu root用户远程登陆显示Access denied&&并通过密钥登陆系统 Ubuntu root用户远程登陆显示Access denied&&并通过密钥登陆系统
解决Access denied问题使用ssh客户端登陆虚拟机中的Ubuntu系统的root用户,发现提示Access denied.经查找,是因为在/etc/ssh/sshd_config文件中的PermitRootLogin的值是proh
2019-01-08
下一篇 
Hexo构建自己的博客 Hexo构建自己的博客
一、安装git和Node.jsGitNode.js因为npm在中国访问较慢,安装使用cnpm npm install -g cnpm --registry=https://registry.npm.taobao.org 二、安装Hex
2018-12-31
  目录