Java

Java8帮助API文档:

https://docs.oracle.com/javase/8/docs/api/?xd_co_f=278ba860ac89178ee421598661572254

三大版本

javaSE:标准版,基础、核心(控制台开发、桌面程序)

javaEE:企业级开发,广泛应用(web端,服务器开发)

javaME:嵌入式开发,基本没人使用。(手机、小家电)

JDK/JRE/JVM

JDK:java development kit。 java开发环境。

JRE:java runtime environment。java运行环境。

JVM:java virtual machine。 java虚拟机(模拟了一个虚拟机专门运行java程序,屏蔽了底层操作系统的差异)

JDK包含JRE,JRE包含JVM。JDK ( JRE (JVM) )

jdk

java结构图: https://docs.oracle.com/javase/8/docs/

image-20200829164337413

程序运行的两种机制

  1. 编译型compile,预先翻译好。(c、c++)
  2. 解释型,随着运行随着翻译。(javascript等)

因为现在电脑性能比较高,所以解释型编译型的效率差距无关痛痒。

java两者兼而有之,先javac编译成class文件,再在jvm中解释class文件。

环境

安装

  1. 下载jdk7 / jdk8
  2. 配置环境变量
    • JAVA_HOME F:\xxxx\jdk1.8*
    • PATH %JAVA_HOME%\bin%JAVA_HOME%\jre\bin
      • %是应用变量的意思
  3. java -version 发现java已经安装

卸载

  1. 删除java安装目录
  2. 删除环境变量:JAVA_HOME / path下java相关
  3. java -version 发现java已经被卸载

jdk目录简介

bin 可执行文件,比如javac

include jdk使用c编写,此处是c引用的头文件

jre java的运行环境

lib java开发用到的库文件

src java资源类文件(java基础类的源代码)

hello word

1
2
3
4
5
6
// HelloWord.java
class HelloWord {
public static void main(String[] args) {
System.out.println("hello word!");
}
}

cmd编译执行

1
2
3
4
5
# 编译(编译得到.class文件)
javac HelloWord.java

# 运行(运行编译出来的.class文件)
java HelloWord

注意:

  1. java大小写敏感
  2. java名尽量英文
  3. 文件名和类名必须一致

操作符

所有操作符基本只能操作基础类型,但有如下特例:

操作所有对象

  • =
  • ==(只能判断引用地址,不能判断内容)
  • !=(只能判断引用地址,不能判断内容)

操作String对象

  • +
  • +=

“+”操作符作用

  1. 字符串拼接
  2. 字符串转换
  3. 数值计算

“=”操作符作用

  1. 基本类型=>深度复制
  2. 对象类型=>指向同一对象(仅复制引用地址,并没有复制对象本身内容)

判断内容相等

基本类型

  • ==
  • !=

对象

  • equals

    自定义类需要在类中覆盖

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

String

  • 当作基本类型赋值,则可以使用 ==equals。如String str1 = “Str123”;
  • 当作对象赋值,则使用equals。如String str3 = new String(“Str123”);
  • 建议使用equals。

幂运算

  • 2^3 = 2的3次方
  • Math.pow(2, 3)

逻辑运算

  1. && 与
  2. || 或
  3. ! 非
  4. 短路运算,如果前面的逻辑能够得到结果,后续运算不再执行。

位运算

符号 说明
& 与,两者皆1,才1
\ 或,两者有1,就1
^ 异或,两者不同,则1
~ 取反
>> 左移4位,每移1位除以2
<< 右移4位,每移1位乘以2。16=1<<4
>>>
取反

int型,4字节。最高位为符号位,0为正数,1为负数。

原码
1
2
正数 3的二进制:00000000	00000000	00000000	00000011
负数-3的二进制:10000000 00000000 00000000 00000011
反码
  1. 正数,反码就是原码。
  2. 负数,反码是将原码除符号外的位取反
1
2
正数 3的反码二进制:00000000	00000000	00000000	00000011
负数-3的反码二进制:11111111 11111111 11111111 11111100
补码
  1. 正数,补码就是原码
  2. 负数,补码是反码+1
1
2
正数 3的补码二进制:00000000	00000000	00000000	00000011
负数-3的补码二进制:11111111 11111111 11111111 11111101
取反

计算机中,都是用补码存储整数,取反是对补码取反。

  1. ~3,得到 - 4
  2. ~-3,得到2
1
2
正数 3的补码二进制:11111111	11111111	11111111	11111100
负数-3的补码二进制:00000000 00000000 00000000 00000010

包package

  1. 用来区别类名命名空间
  2. 一般用公司域名倒置作为包名。
1
2
3
4
5
定义包名:
package com.baidu;

引入包:
import com.baidu.(具体类名 或者 *);

JavaDoc

编写说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.baidu;

/**
* @author 作者
* @version 版本号
* @since 最早使用的JDK版本号
*/
public class Base {
String name;

/**
* 测试
*
* @param name 参数名
* @return name 返回值
* @throws Exception 异常抛出
*/
public String test(String name) throws Exception {
return name;
}
}

生成文档

命令行方式
1
javadoc -encoding UTF-8 -charset UTF-8 Base.java
Idea导出
1
2
3
Tools->Gerenate JavaDoc

Other command line arguments:-encoding utf-8 -charset utf-8

查看文档

  1. 打开index.html

Scanner

  1. 可以用来获取用户输入。
  2. 在Java5引入的的工具类。

1
2
3
4
import java.util.Scanner;
Scanner scanner = new Scanner(System.in);

scanner.close(); // io流的类都应该关闭,减少资源占用

关键方法

next
  • next();
  • hasNext();
  • 一直读取到【有效字符】才可以结束
  • 在【有效字符】前遇到的【空白】,直接去掉
  • 在【有效字符】后遇到的【空白】,作为分隔符或结束符
  • 不能得到带有【空白】的字符串

image-20200829172907167

nextLine
  • nextLine();
  • hasNextLine();
  • 以【回车enter】作为结束符
  • 可以获得【空白】

image-20200829172823026

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.luxia.scanner;

import java.util.Scanner;

public class Interactive {
public static void main(String[] args) {
testInputNext();
// testInputNextLine();
}

private static void testInputSimple() {
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
scanner.close();
}

private static void testInputNext() {
System.out.println("next-input please : ");
Scanner scanner = new Scanner(System.in);
if (scanner.hasNext()) {
String input = scanner.next();
System.out.println("your input is : " + input);
}
scanner.close();
}
private static void testInputNextLine() {
System.out.println("next line-input please : ");
Scanner scanner = new Scanner(System.in);
if (scanner.hasNextLine()) {
String input = scanner.nextLine();
System.out.println("your input is : " + input);
}
}
// 计算平均值和总和
private static void computeAvgSum() {
Scanner scanner = new Scanner(System.in);
double sum = 0;
int size = 0;
System.out.println("请输入数字:");
while (scanner.hasNextDouble()) {
String input = scanner.nextLine();
sum += Double.parseDouble(input);
size += 1;
System.out.println("继续输入数字:");
}
if (size == 0) {
System.out.println("没有输入数字");
return;
}
double avg = sum / size;
System.out.println("总和:" + sum + ";平均值:" + avg);
}
}

数组

  1. 元素是相同类型的、有序的一组数据。
  2. 元素可以是基本类型,也可以是引用类型。
  3. 长度是确定的,一旦创建,不可改变
  4. 引用类型(引用地址-,实质存储空间-)。
  5. 数组一经分配空间,他的每一个元素也都被隐式的初始化。
    1. int默认初始化为0
    2. String默认初始化为nulld等

声明

1
2
3
4
5
// 常用
int[] ages;

// C/C++演变而来,不常用
int counts[];

初始化(分配了堆空间))

静态初始化

1
2
int[] ages = new int[]{2,3, 4, 5};
int[] ages = {1, 2, 3, 4};

动态初始化

1
2
3
int[] ages = new int[4]; //仅分配空间
ages[0] = 1;
ages[1] = 2;

取值

1
2
3
4
5
6
7
8
9
10
// 总长度
ages.length

// 下标取值,下标范围[0,length - 1],否则会越界
ages[0]

// 直接取值
for(int age:ages) {

}

多维数组

数组内部的元素也是个数组。

1
2
3
4
5
int[][] a1= new int[3][2];
int[][] a2= {{2,3},{4,5},{6,7}}

a2[0][1] // 3
a2[1][1] // 5

Arrays类

数组的工具类java.util.Arrays

  1. fill 给数组赋值
  2. sort 给数组升序
  3. equals 比较数组元素
  4. binarySearch 搜索,对排序后的数组,进行二分查找
  5. asList 转换为List类型
  6. Arrays.toString()
  7. copyOf()

稀疏数组

当二维数组中,很多默认值是0,会记录没有意义的数据,可以用稀疏数组。

  1. 记录数组一共有几行几列,有多少个不同值
  2. 把不同值的行列及值记录在一个小规模的数组中,减少程序规模

image-20200906233936492

本质:抽象。以的方式组织代码,以对象组织(封装)数据。

三大特性

  1. 封装 (隐藏数据,属性私有:高内聚,低耦合,如get/set)
  2. 继承 (extends / super)
  3. 多态 (基类)

封装

  1. 保护属性,提高程序的安全性/可维护性
  2. 隐藏代码细节
  3. 统一接口

继承

  1. 所有类的基类是Object。
  2. 先执行父类,再执行子类。
super
  1. super只能在子类的方法或构造器中
  2. super() 调用父类的构造器,必须出现在构造器的最开始
  3. super(参数)
this
  1. 本类
  2. this 调用本类的方法或属性
  3. this() 本类的构造器,也必须出现在构造器的最开始。所以this()和super()不能同时调用
  4. this(参数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// extends
class Teacher extends Person {
public Teacher() {
//此代码默认隐藏,实则调用了父类的无参构造。
//如果父类没有无参构造,则此处也要显式编写super(参数)
super();

System.out.println("子类构造器");
}
private String name;
public void test() {
System.out.println(this.name);
// super父类
System.out.println(super.getName());
}
}

idea的ctrl + H,可以查看当前类的父祖类。

image-20200909223503930

####

多态

重写

指的是,子类继承父类,子类想重写父类的方法。

  1. 静态方法,是类的方法。静态方法,不能重写。
  2. 普通方法,是对象的方法。普通方法才能重写

重写和重载的区分

  1. 重写:①必须有继承关系,子类重写父类;②修饰符可以扩大范围(不能缩小);③方法名、参数列表,必须相同;④抛出的异常可以缩小范围(不能扩大)。只有方法体不同
  2. 重载:方法名相同,参数、返回值等不同。
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
class Person {
public void testOverride() {
System.out.println("Override Person父类");
}
public static void testStatic() {
System.out.println("Static Person父类");
}
public void test() {
System.out.println("Person父类");
}

}
class Teacher extends Person {
@Override // 注解:代表重写的注释
public void testOverride() {
System.out.println("Override Teacher子类");
}
public static void testStatic() {
System.out.println("Static Teacher子类");
}
public void test() {
System.out.println("Teacher子类");
}
}

// 调用
Teacher teacher = new Teacher();
teacher.testStatic(); // 静态方法,Teacher,子
teacher.test(); // 普通方法,Teacher,子
teacher.testOverride(); // 普通方法重写,Teacher,子

Person te = new Teacher();
te.testStatic(); // 静态方法,Person,父
te.test(); // 普通方法,Teacher,子
te.testOverride(); // 普通方法重写,Teacher,子
多态

多态是方法的多态,属性没有多态性。

即,同一方法,根据发送对象的不同,而有不同的行为方式。

存在条件:有继承关系,子类方法重写父类方法,父类引用Person指向子类对象Teacher。

1
2
3
4
5
Person teacher = new Teacher();
teacher.run();

Person student = new Student();
student.run();

instanceOf

object instanceOf Class

object 是Class的子孙类对象或对象,返回true,否则返回false

1
2
3
4
Person teacher = new Teacher();
System.out.println(teacher instanceof Teacher); // true
System.out.println(teacher instanceof Person); // true
System.out.println(teacher instanceof Object); // true

类型转换

1
2
3
4
5
6
// 子类-->父类(直接转换)
Person teacher = new Teacher();

// 父类-->子类
teacher.myown(); // 报错,Person类中找不到此方法
((Teacher)teacher).myown(); // 父向子类转换,调用子类方法。

class 类(抽象)

new 对象(实例化)

new创建对象时,①分配内存空间,②初始化对象(值),③调用类的构造器

初始值:

  1. 数字: 0 0.0
  2. char: u0000
  3. boolean: false
  4. 引用类型: null

构造器

使用new实例化对象,本质是在调用构造器。

构造器可以用来初始化对象的值。

  1. 必须和类名相同
  2. 必须没有返回类型,也不能写void
  3. 如果自定义了构造器,隐式默认构造器会失效

抽象类

abstract / extends

  1. abstract class 抽象类
  2. abstract 方法 抽象方法

抽象类和接口,都是一种顶层的约束规则,不负责真正的实现。只能依靠子类,或者接口实现类去实现。

因为抽象类不负责实现,所以不能new这个抽象类。但是抽象类是有构造器滴!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 抽象类
public abstract class AbstractTest {
// 可以写普通方法
public void run() {
}

// 抽象方法。抽象方法必须在抽象类中。
public abstract void go();
}

class Son extends AbstractTest {

// 父抽象类的抽象方法,必须被子类实现
@Override
public void go() {

}
}

接口

interface / implements

接口,就是规范!一般会先把接口设计好,再去实现。

  1. 类:只负责具体实现。
  2. 抽象类:有具体的实现,也有抽象的规则。本质是类,只能单继承。
  3. 接口:只有约束规则。一个类,可以实现多个接口!(类似多继承)

接口的方法,默认就是【public + 抽象】的(实际编程中,缩略不写)

接口的属性,默认都是【public + static + final 常量】!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface InterfaceTest {
int AGE = 23;

void run();

void go(String name);
}
// 实现了接口,就必须重写接口中的所有方法
class Animal implements InterfaceTest, InterfaceTest02 {

@Override
public void run() {
System.out.println(InterfaceTest.AGE);
}

@Override
public void go(String name) {

}
// InterfaceTest02的方法重写
}

内部类

在一个类A的内部,定义一个类B。B是内部类,A是外部类。

  1. 成员内部类
  2. 静态内部类
  3. 局部内部类
  4. 匿名内部类

成员内部类

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
import java.util.UUID;
public class InnerClassTest {
private UUID id = UUID.randomUUID();
public void run() {
System.out.println("我是外部类,我可以直接调用内部类");
Inner inner = new Inner();
inner.in();
}
class Inner {
public void in() {
System.out.println("我是内部类");
// 成员内部类,可以访问外部类的私有属性
System.out.println(id);
}
}
}
// 一个java类中,可以有多个class类,但只能有一个public类
class Test {
public static void main(String[] args) {
InnerClassTest innerClassTest = new InnerClassTest();
innerClassTest.run();
// 通过外部类,实例化内部类
InnerClassTest.Inner inner = innerClassTest.new Inner();
inner.in();
}
}

静态成员内部类(static class)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.luxia.base;

import java.util.UUID;

public class InnerClassTest {
private static UUID id = UUID.randomUUID();
static class Inner {
public void in() {
System.out.println("我是内部类");
// 内部类,可以访问外部类的私有静态属性
System.out.println(id);
}
}
}

class Test {
public static void main(String[] args) {
InnerClassTest innerClassTest = new InnerClassTest();
innerClassTest.run();
// 通过外部类,实例化内部类
InnerClassTest.Inner inner = new InnerClassTest.Inner();
inner.in();
}
}

局部内部类(方法内部)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class InnerClassTest {
private UUID id = UUID.randomUUID();

public void run() {
class Inner {
public void in() {
System.out.println("我是局部内部类");
System.out.println(id);
}
}
Inner inner = new Inner();
inner.in();
}
}

匿名内部类

初始化类时,不指定名字。不用将实例保留到变量中。

内存分析

  1. 存放基本变量(包括基本类型的值)
  2. 引用对象的变量(存放这个引用在堆里面的具体引用地址

  1. 存放new的对象和数组的实质内容
  2. 可被所有线程共享。

方法区(属于堆)

  1. 存放所有的class和static变量
  2. 可被所有线程共享。

示例

int[] myList = new int[4];

image-20200906174107698

image-20200909221955646

static

静态属性

多用于多线程。

1
2
3
4
5
6
7
8
9
10
11
12
public class StaticTest {
private static int age; // 静态属性
private int score; // 普通属性

public static void main(String[] args) {
System.out.println(StaticTest.age); // 静态属性,被类调用(推荐) 0

StaticTest staticTest = new StaticTest();
System.out.println(staticTest.age); // 静态属性,可被对象实例调用 0
System.out.println(staticTest.score); // 普通属性,只被对象实例调用 0
}
}

静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class StaticTest {
public void run() {
System.out.println("not static");
go(); // 普通方法,可以调用静态方法(因为静态是随着类早就存在了)
}

public static void go() {
System.out.println("static");
run(); // 报错!静态方法,不能调用普通方法(因为普通方法是动态的,静态方法存在时它还不存在)
}

public static void main(String[] args) {
go(); // 静态方法,属于类,可以直接被调用
StaticTest.go();

run(); // 报错!
staticTest.run(); // 普通方法,只能被对象实例调用
}
}

静态代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CodeBlock {
{
System.out.println("匿名代码块"); // 2,赋初始值
}

static {
System.out.println("静态代码块"); // 1,只执行一次
}

public CodeBlock() {
System.out.println("构造器"); // 3
}

public static void main(String[] args) {
new CodeBlock();
System.out.println("---------------------");
new CodeBlock();
}
}

结果

1
2
3
4
5
6
静态代码块
匿名代码块
构造器
---------------------
匿名代码块
构造器

静态导入包

1
2
3
4
5
6
// 常规调用:
System.out.println(Math.random());

// 静态导入方法:
import static java.lang.Math.random;
System.out.println(random());

方法

Java中,参数只有值传递

只不过对于对象参数,【值的内容】是【对象的引用】

静态方法

  1. 非静态方法,可调用静态方法,或普通方法。
  2. 静态方法,只能调用静态方法,不能调用普通方法。因为类创建时,静态方法已经创建,而普通方法还没有被创建(在实例化对象时才被创建)

可变参数

  1. jdk1.5开始,可以传递同类型的、可变参数。
  2. 类型后面加省略号(…)
  3. 可变参数只能是最后一个参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 调用
testRestParams("tom", 1, 2, 3);
testRestParams("jack", 11, 22, 13);
testRestParams("lucy", 51, 232, 1113);

// 定义
private static void testRestParams(String name, int... scores) {
if (scores.length == 0) {
System.out.println("no score passed");
return;
}
int max = scores[0];
for (int score : scores) {
if (score > max) {
max = score;
}
}
System.out.println(name + " max:" + max);
}

命令行传参

1
2
3
4
5
6
7
8
public class CmdLine {

public static void main(String[] args) {
for (String arg : args) {
System.out.println(arg);
}
}
}
idea传参

image-20200830180532275

命令行
  1. javac CmdLine.java 生成.class
  2. java com.example.base.CmdLine 运行.class

image-20200830181053267

for

增加型for循环,java5引入,多用于集合、数组。

1
2
3
4
int[] counts = {1, 2, 3, 4, 5};
for (int count : counts) {
System.out.print(count);
}

switch

  1. byte
  2. short
  3. int
  4. char
  5. String(JavaSE7)(字符的本质还是数字)
  6. 枚举

字符的本质是数字

idea反编译演示——

编译: .java => .class。从如下目录找到编译后的class文件

image-20200830155211670

反编译:.class 。将.class用idea打开,可以看到,将字符串转换成了数字。

image-20200830160056425

枚举

switch判断枚举值的时候,①枚举的是枚举类型本身;②case后的枚举类型,不能加枚举前缀(An enum switch case label must be the unqualified name of an enumeration constant)。

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
TypeEnum typeEnum = TypeEnum.GOOD;
switch (typeEnum) {
case NICE: {
System.out.println("你很优秀");
break;
}
case GOOD: {
System.out.println("good");
break;
}
case BAD: {
System.out.println("继续加油");
break;
}
default: {
System.out.println("还没出成绩");
}
}
enum TypeEnum {
NICE(1),
GOOD(2),
BAD(3);

private int code;

TypeEnum(int code) {
this.code = code;
}
}

String[]和List相互转换

1
2
3
4
5
6
7
8
9
10
11
12
13
// String[] 转换为 List<String>
// 将String[]转化为List<String>,不能对转化出来的List进行add,remove,因为他们并不是ArrayList,而是Arrays里面的内部类ArrayList
String[] strings = new String[]{"a", "b", "c", "d", "e"};
List<String> list = Arrays.asList(strings);
System.out.println(list.toString());

// List<String> 转换为 String[]
List<String> list1 = new ArrayList<>();
list1.add("nice");
list1.add("good");
list1.add("bad");
String[] strings1 = list.toArray(new String[]{});
System.out.println(Arrays.toString(strings1));

取最大值最小值

获取List中的最大最小值

List< Integer > 或者 List< double >

注意不能有null元素

1
2
Collections.min(Arrays.asList(1,2,3,4))
Collections.max(Arrays.asList(1,2,3,4))

List< 实体类>

1
// 自己写for寻找

异常

exception。

java把异常当做对象处理,java.lang.Throwable是所有异常的基类。

异常类分为两类:

  1. 错误Error
    • java虚拟机生成并抛出
      • OutOfMemoryError 内存溢出
      • NoClassDefFoundError 类定义错误
      • LinkageError 链接错误
    • 大多数错误,与代码开发者所执行的操作无关
    • 灾难性,致命,无法控制和处理,当发生时,JVM一般会终止线程
  2. 异常Exception
    • RuntimeException 运行时异常(由逻辑引起,是不检查异常,程序中可以自己选择是否捕获、是否处理)
      • 数组下标越界
      • 空指针
      • 算术异常
      • 丢失资源
      • ClassNotFoundException 找不到类
    • 检查异常
    • 可以被程序处理,在程序中应该尽可能的去处理。

分类

类别 特点
检查性异常 编译时。
运行时异常 运行时(编译时被忽略)
错误ERROR 错误,不是异常。如当栈溢出,发生错误,编译时无法检查到。

image-20200824233037728

异常处理的方法

ctr alt t

使用异常处理后,碰到异常,会正常执行,而不会终止。

try/catch 捕获
1
2
3
4
5
6
7
8
9
10
11
12
13
// 可多个catch
// 越基础的异常,越靠后
try{}
catch(异常类){}
catch(ArrithmeticException a){}
catch(Exception e){}
catch(Throwable t){
e.printStackTrace() // 打印错误的栈信息
// 尽量额外的逻辑来处理异常
}
finally{
// 选用,用于io、资源等的关闭操作,释放资源
}
throws 在方法上抛出异常

如果本方法无法处理异常,可以抛出异常到方法外。

1
2
3
4
5
void test() throws ArrithmeticException{	
if(a==0) {

}
}
throw 方法中主动抛出异常

一般在方法中使用,在方法中抛出。

1
2
3
4
5
void test(){	
if(a==0) {
throw new ArrithmeticException();
}
}

#####

自定义异常

只需要继承exception类。

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
public class MyException extends Exception {
Integer detail;
MyException(Integer e) {
this.detail = e;
}

@Override
public String toString() {
return "MyException{" +
"detail=" + detail +
'}';
}
}

// 调用
public class MyExceptionTest {
public static void main(String[] args) {
try {
new MyExceptionTest().testNumber();//在此处理异常
} catch (MyException e) {
System.out.println(e);
e.printStackTrace();
}
}
private void testNumber() throws MyException {// 异常抛出方法外处理
Integer a = 1;
if (a == 1) {
throw new MyException(a); // 主动抛出
}
}
}

时间

新老版本

Java原本自带时间类(单线程不会有问题,多线程有风险,且使用困难)

  1. java.util.Date;(JDK1.1已经被弃用)
    1. java.sql.Date是其子类
  2. java.util.Calendar;(JDK1.1推出)

Java8增加的时间类

  1. java.time(基于Joda-Time库)
繁琐程度对比
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 旧版本
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
Date birthDay = simpleDateFormat.parse("1991-11-23");
Date now = new Date();

long time = now.getTime() - birthDay.getTime();
long day = time / 1000 / 3600 / 24;
System.out.println(day);
} catch (ParseException e) {
e.printStackTrace();
}


// Java8新版本
long day2 = ChronoUnit.DAYS.between(LocalDate.of(1991, 11, 23), LocalDate.now());
System.out.println(day2);
线程对比
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
37
38
39
// 旧版本,全局共享的是同一个calendar对象,如果没有线程同步措施,会造成线程安全问题
// 错误写法
final static SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

private static void test02() {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Date date = SIMPLE_DATE_FORMAT.parse("1991-11-23 06:06:06");
System.out.println(date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}).start();
}
}
// 正确写法
final static SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
private static void test02() {

for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (SIMPLE_DATE_FORMAT) { // 增加同步控制
Date date = SIMPLE_DATE_FORMAT.parse("1991-11-23 06:06:06");
System.out.println(date);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
}).start();
}
}
Calendar避免直接使用数字,而应该使用枚举常量
1
2
3
4
Calendar calendar = Calendar.getInstance();
calendar.set(1991, 11, 23); // 避免直接使用数字
calendar.set(1991, Calendar.DECEMBER, 23); // 使用枚举常量
System.out.println(calendar.getTime());
Java8时间类API

java.time中的类

描述
Instant 时间戳。作为中间类转换。
Duration 时间间隔,秒、纳秒,适合处理较短、较精确
Period 一段时间,年月日
LocalDate 不可变,日期,年月日
LocalTime 不可变,时间,时分秒
LocalDateTime 不可变,日期+时间,年月日时分秒
ZonedDateTime 不可变,具有时区的日期+时间

java.time中的类都不生成不可变实例,也不提供公共构造函数,没办法通过new的方式直接创建,需要采用工厂方法实例化。

now方法获取当前时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Instant instant = Instant.now();
System.out.println(instant); //2020-08-02T09:30:01.526Z,是个国际标准时间

LocalDate localDate = LocalDate.now();
System.out.println(localDate);//2020-08-02

LocalTime localTime = LocalTime.now();
System.out.println(localTime);//17:30:01.773

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);//2020-08-02T17:30:01.774

ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);//2020-08-02T17:30:01.775+08:00[Asia/Shanghai]


Year year = Year.now();
System.out.println(year);// 2020

YearMonth yearMonth = YearMonth.now();
System.out.println(yearMonth); // 2020-08

MonthDay monthDay = MonthDay.now();
System.out.println(monthDay); // --08-02
of方法获取指定时间
1
2
3
4
5
6
7
8
9
10
11
LocalDate localDate = LocalDate.of(1991, 11, 23);
System.out.println(localDate);// 1991-11-23

LocalTime localTime = LocalTime.of(6, 6, 6);
System.out.println(localTime);// 06:06:06

LocalDateTime localDateTime = LocalDateTime.of(1991, 11, 23, 6, 6, 6);
System.out.println(localDateTime);// 1991-11-23T06:06:06

LocalDateTime localDateTime2 = LocalDateTime.of(localDate, localTime);
System.out.println(localDateTime2);// 1991-11-23T06:06:06
时区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 全部时区列表
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
for (String zoneId : zoneIds) {
System.out.println(zoneId);
}

// 默认时区
ZoneId defaultZoneId = ZoneId.systemDefault();
System.out.println(defaultZoneId); // Asia/Shanghai

// 给时间指定市区
LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(zonedDateTime); // 2020-08-02T18:01:06.660+08:00[Asia/Shanghai]

// 同时间,其他时区的时间
ZonedDateTime zonedDateTime1 = zonedDateTime.withZoneSameInstant(ZoneId.of("Europe/Berlin"));
System.out.println(zonedDateTime1); //2020-08-02T12:01:06.660+02:00[Europe/Berlin]

Date获取年月日等方法被弃用

1
2
3
4
5
6
7
8
Date date1 = new Date();
date1.getDate();
date1.getYear();
date1.getMonth();
date1.getDay();
date1.getHours();
date1.getMinutes();
date1.getSeconds();
方法一:Calendar类
1
2
3
4
5
6
7
8
9
10
11
12
13
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date()); //放入Date类型数据

calendar.get(Calendar.YEAR); //获取年份
calendar.get(Calendar.MONTH); //获取月份
calendar.get(Calendar.DATE); //获取日

calendar.get(Calendar.HOUR); //时(12小时制)
calendar.get(Calendar.HOUR_OF_DAY); //时(24小时制)
calendar.get(Calendar.MINUTE); //分
calendar.get(Calendar.SECOND); //秒

calendar.get(Calendar.DAY_OF_WEEK); //一周的第几天
方法二:SimpleDateFormat类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
String[] strNow1 = new SimpleDateFormat("yyyy-MM-dd").format(new Date()).toString().split("-");

Integer.parseInt(strNow1[0]); //获取年
Integer.parseInt(strNow1[1]); //获取月
Integer.parseInt(strNow1[2]); //获取日

String[] strNow2 = new SimpleDateFormat("hh:mm:ss").format(new Date()).toString().split(":");

Integer.parseInt(strNow2[0]); //获取时(12小时制)
Integer.parseInt(strNow2[1]); //获取分
Integer.parseInt(strNow2[2]); //获取秒

String[] strNow3 = new SimpleDateFormat("HH:mm:ss").format(new Date()).toString().split(":");

Integer.parseInt(strNow3[0]); //获取时(24小时制)
Integer.parseInt(strNow3[1]); //获取分
Integer.parseInt(strNow3[2]); //获取秒

时间转换案例

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 2017-01-01 19:01:01转化为19:01:01
void testDate() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

Date date = null;
try {
date = format.parse("2017-01-01 19:01:01");
} catch (ParseException e) {
e.printStackTrace();
}

format = new SimpleDateFormat("HH:mm:ss");
String time = format.format(date);
System.out.println(time);
}
// 判断当前时间是否在时间范围内,含年月日
private static boolean isEffectiveDate(Date nowTime, Date startTime, Date endTime) {
if (nowTime.getTime() == startTime.getTime()
|| nowTime.getTime() == endTime.getTime()) {
return true;
}

Calendar start = Calendar.getInstance();
start.setTime(startTime);

Calendar end = Calendar.getInstance();
end.setTime(endTime);

Calendar now = Calendar.getInstance();
now.setTime(nowTime);

return now.after(start) && now.before(end);
}
// 判断是否是工作时间,只有时间
public static Boolean isWorkTime(final Date datetime) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss");
try {
Date curDate = simpleDateFormat.parse(simpleDateFormat.format(datetime));
Date startDate = simpleDateFormat.parse("08:30:00");
Date endDate = simpleDateFormat.parse("18:00:00");

long curTime = curDate.getTime();
long startTime = startDate.getTime();
long endTime = endDate.getTime();
return curTime >= startTime && curTime <= endTime;
} catch (ParseException e) {
LOGGER.error("simpleDateFormat.parse解析失败", e);
return false;
}
}

第三方库Joda

https://www.ibm.com/developerworks/cn/java/j-jodatime.html

excel

  1. poi支持.xls和.xlsx
  2. jxl只支持.xls

POI:是对所有office资源进行读写的一套工具包、属于apache开源组织。

组成

  1. [.xls]excel 2003 HSSFWorkbook workbook = new HSSFWorkbook();
  2. [.xlsx]excel 2007 XSSFWorkbook workBook = new XSSFWorkbook();
元素 .xls excel2003 .xlsx excel 2007
工作簿 HSSFWorkbook XSSFWorkbook
工作表 HSSFSheet XSSFSheet
HSSFRow XSSFRow
单元格 HSSFCell XSSFCell
HSSFCellStyle XSSFCellStyle

引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>


<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
<!-- 为POI支持Office Open XML -->
<!-- import org.apache.poi.xssf.usermodel.XSSFWorkbook; -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>

多个sheet导出

1
2
3
4
5
6
7
8
9
10
// 创建Excel文件
HSSFWorkbook workbook = new HSSFWorkbook();
// 获取名称集合
List<String> nameList = Arrays.asList("learn1Sheet", "learn2Sheet");
for(int i=0;i<nameList.size();i++){
// 遍历名称
String sheetname = nameList.get(i));
// 创建工作簿
HSSFSheet sheet = workbook.createSheet(sheetname);
}

合并单元格

合并单元格之后单元格内容会保留第一个单元格的内容

1
2
3
4
5
//合并单元格
CellRangeAddress callRangeAddress = new CellRangeAddress(0, 1, (short)0, (short)1);

//加载合并单元格
sheet.addMergedRegion(callRangeAddress);

样式属性

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
37
38
39
40
41
42
43
44
45
46
// 行高,行高计算公式:x*20 (x为需要设置的行高)
row.setHeight((short)405); // 此处行高是405/20 = 20.25
// 列宽,列宽计算公式:256*x+184 (x为需要设置的列宽)
sheet.setColumnWidth(1,9656); // 1为列号,此处列宽为37

// 单元格
final CellStyle cellStyle = workBook.createCellStyle();


//自定义背景色
HSSFPalette palette = workbook.getCustomPalette();
palette.setColorAtIndex(IndexedColors.GREY_25_PERCENT.getIndex(), (byte) 214, (byte) 220, (byte) 228);
//单元格颜色
cellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
cellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);


//水平居中
cellStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER);
//垂直居中
cellStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
//水平居左
cellStyle.setAlignment(HSSFCellStyle.ALIGN_LEFT);


//下边框
cellStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN);
//左边框
cellStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN);
//上边框
cellStyle.setBorderTop(HSSFCellStyle.BORDER_THIN);
//右边框
cellStyle.setBorderRight(HSSFCellStyle.BORDER_THIN);

//单元格内容自动换行
cellStyle.setWrapText(true);


//字体
HSSFFont font = workbook.createFont();
//设置字体大小
font.setFontHeightInPoints(11);
//设置字体类型
font.setFontName("等线");
//加载字体
cellStyle.setFont(font);

输出流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ServletOutputStream out = null;
try {
out = resp.getOutputStream();
//添加时间,防止文件名字重复
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss");
String timer = sdf.format(new Date(System.currentTimeMillis()));
//设置信息头
resp.setHeader("Content-Disposition", "attachment;filename=" +
URLEncoder.encode("导出" + "(" + timer + ")", "UTF-8") + ".xls");
resp.setHeader("Content-Type", "application/octet-stream");
//写出文件
workbook.write(out);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
//10.关闭输出流
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

反射

Reflection

正常方式:

graph LR
A(包类名称) --> B(new实例化) --> C(得到实例化对象)

反射方式:

graph LR
A(实例化对象) --> B(getClass方法) --> C(得到包类名称)

类的加载过程与ClassLoader

Class对象是在运行时、在内存中产生,是动态的。

加载load:
  1. 将javac编译后的.class文件内容加载到内存中,即,将静态数据转换成方法区的运行时数据结构
  2. 加载类之后,在堆内存的方法区中,产生一个Class类型的对象(java.lang.Class)。该对象包含了完整的类的结构信息,可以通过这个对象看到类的结构。一个类只有唯一一个Class对象。就像镜子,称之为反射。
链接link:

目的是将Java类的二进制代码合并到JRE(java runtime environment)中。

  1. 验证:加载的java类信息符合JRE规范,没有安全问题。报错机制。

  2. 准备:

    • 为类变量(static)分配内存

    • 为类变量(static)设置默认值

  3. 解析:将常量名(虚拟机常量池内的符号引用)替换为直接引用地址

    • 常量:final static int = 324;
初始化:
  1. JVM执行类构造器()方法的过程(构造器初始化)
  2. 当初始化一个类时,如果父类还没有初始化,会先父类初始化
  3. 虚拟机保证一个类的()方法在多线程环境中被正确加锁和同步
什么时候发生类的初始化?

一、类的主动引用,一定会发生类的初始化:

  1. new 一个对象
  2. 反射
  3. 类的静态成员和静态方法(final常量除外)
  4. 首先初始化main方法所在类
  5. 首先初始化父类

二、类的被动引用,不会发生类的初始化:

  1. 当子类引用父类的静态变量,子类不会被初始化。【只有真正声明静态域的类才会被初始化】
  2. 通过【数组】定义类引用,类不会被初始化。
  3. 使用类的【final常量】,类不会被初始化。(链接link阶段已经放入常量池了)
graph TB

A(程序调用类) --> B{类是否在内存中}
B --否--> C
subgraph 类的初始化 
    C(类的加载Load) --> C2(类的链接Link) --> C3(类的初始化Initialize)
end

C -.- D1>类加载器,将类的.class读入内存并为之创建一个java.lang.Class对象]
C2 -.- D2>将类的二进制数据合并到JRE中]
C3 -.- D3>JVM负责对类初始化]

方法区(特殊的堆)

image-20200726153025301

哪些元素类型拥有Class对象

只要元素类型相同,则拥有同一个Class对象

  1. class,外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
  2. interface
  3. 数组
  4. 枚举
  5. 注解
  6. 基本数据类型
  7. void
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 类        
Class a1 = Object.class; // class java.lang.Object
Class a11 = Class.class; // class java.lang.Class

// 接口
Class a2 = Comparable.class; // interface java.lang.Comparable

// 数组
Class a3 = String[].class; // class [Ljava.lang.String;
Class a31 = int[][].class; // class [[I

// 枚举
Class a4 = ElementType.class; // class java.lang.annotation.ElementType

// 注解
Class a5 = Override.class; // interface java.lang.Override

// 基本数据类型
Class a6 = int.class; // int
Class a61 = Integer.class; // class java.lang.Integer

// void
Class a7 = void.class; // void

创建Class对象的方式

  1. 通过类的class获取。最安全可靠、性能最高
1
2
3
Class clazz = User.class;

Class clazz = String.class;
  1. 通过类的实例的getClass()方法获取。
1
2
User user = new User();
Class clazz = user.getClass();
  1. 通过Class.forName("类的全类名")获取。可能存在ClassNotFoundException异常。
1
2
3
Class clazz = Class.forName("demo.User");

Class clazz = Class.forName("java.lang.String");
  1. 内置基本类型的包装类可直接使用类名.Type
1
Class clazz = Integer.TYPE;
  1. 利用ClassLoader

Class对象的用途

动态的创建一个新的实例newInstance
  1. 默认调用类的无参构造器
  2. 类构造器必须有足够的访问权限
1
2
3
4
5
6
7
8
9
10
// 无参数构造器,构造对象 newInstance
Class userClazz = Class.forName("User");
User userNew = (User) userClazz.newInstance();
System.out.println(userNew);

// 有参数构造器,构造对象 constructor + newInstance
Class userClazz1 = Class.forName("User");
Constructor constructor = userClazz1.getDeclaredConstructor(String.class, int.class);
User userNew1 = (User) constructor.newInstance("niceId", 23);
System.out.println(userNew1);
调用方法
  1. getDeclaredMethod 根据方法名获取方法
  2. invoke 调用方法
  3. 反射操作方法,比直接调用更灵活,因为方法名,可以作为字符串参数传进去
1
2
3
4
5
6
7
8
9
10
11
12
Class userClazz = Class.forName("User");
User userNew = (User) userClazz.newInstance();

// 直接调用
userNew.setAge(23);
userNew.setId("newId");

// 反射的形式调用
Method setStudent = userClazz.getDeclaredMethod("setStudent", boolean.class);
setStudent.invoke(userNew, true);

System.out.println(userNew);
调用属性
  1. getDeclaredField 根据属性名获取属性
  2. set
  3. 默认情况下,由于类的属性是私有属性private,所以不能直接操作。通过setAccessible(true)来关闭程序的安全监测,从而可以操作私有属性。默认是setAccessible(false)。
1
2
3
4
5
6
7
8
Class userClazz = Class.forName("User");
User userNew = (User) userClazz.newInstance();

Field age = userClazz.getDeclaredField("age");
age.setAccessible(true); // true,则关闭程序的安全监测,从而可以操作私有属性
age.set(userNew, 123);

System.out.println(userNew);
setAccessible

用于启动(默认fasle)或关闭(true)访问安全检查的开关,如此可以直接处理类中的私有属性、私有方法等。以下都有setAccessible方法:

  1. Method 方法
  2. Field 属性
  3. Constructor 构造器

关闭安全检查,可以提高性能。

操作泛型
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
37
public class DbReflectAnnotation {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {

Class dbReflectAnnotationClazz = Class.forName("DbReflectAnnotation");
Method method01 = dbReflectAnnotationClazz.getMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = method01.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println(genericParameterType);
if (genericParameterType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}


Method method02 = dbReflectAnnotationClazz.getMethod("test02");
Type genericReturnType = method02.getGenericReturnType();
System.out.println(genericReturnType);
if (genericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}

public void test01(Map<String, Integer> map, List<String> list) {
System.out.println("参数是泛型");
}

public Map<Integer, String> test02() {
System.out.println("结果是泛型");
return null;
}
}
操作注解
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class DbReflectAnnotation {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class personClazz = Class.forName("Person");

// 获取指定注解
DBTable annotation = (DBTable) personClazz.getAnnotation(DBTable.class);
System.out.println(annotation);
System.out.println(annotation.value());

// 遍历全部注解
Annotation[] annotations = personClazz.getAnnotations();
for (Annotation annotation1 : annotations) {
System.out.println(annotation1);
}

Field[] fields = personClazz.getDeclaredFields();
for (Field field : fields) {
DBField field1 = field.getAnnotation(DBField.class);
System.out.println(field1);
System.out.println(field1.name());
System.out.println(field1.type());
}
}
}

@DBTable("person")
class Person {
@DBField(name = "id", type = "int")
private int id;

@DBField(name = "name", type = "varchar")
private String name;

@DBField(name = "age", type = "int")
private int age;
}

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface DBTable {
String value();
}

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface DBField {

String name();

String type();

}
性能

正常效率最高 > 反射(关闭检测) > 反射(启动检测)效率最低

-------------Keep It Simple Stupid-------------
0%