持续更新-老九Java一阶训练营个人笔记

Java基础

JDK

官网

Oracle

环境变量配置

  1. 将JDK根目录定义为JAVA_HOME变量
  2. 在PATH变量下添加%JAVA_HOME%\bin路径

JDK API

Java 1.8 APIs

命令行引用外部jar命令

编译

javac -cp *.jar HelloWorld.java
javac -cp *.jar;**.jar HelloWorld.java
  • -cp 参数有多个时,中间用分号分开,在linux下则是冒号

执行

java -cp *.jar HelloWorld

IDE

Eclipse IDE

官网

eclipse

Eclipse 新建工作区后设置

设置字体

Window -> Preferences -> General -> Appearance -> Colors and Fonts -> Basic -> Text Font -> Edit

设置tab替换为空格
  1. Window -> Perferences -> General -> Editors -> Text Editors -> Instert spaces for tabs

  2. Window -> Perferences -> Java -> Code Style -> Formatter -> New -> Profile name -> OK -> Indentation -> Tab policy -> Spaces only -> Apply -> OK

设置编码格式

Window -> Preferences -> General -> Workspace -> Text file encoding -> Other -> UTF-8

设置换行符

Window -> Preferences -> General -> Workspace -> New text file line delimiter -> Other -> Windows / Unix

设置JDK

Window -> Preferences -> Java -> Installed JREs -> Add/Edit

设置智能提示

Window -> Perferences -> Java -> Editor -> Content Assist

Eclipse 引用外部jar包

  1. 把外部jar包复制到工程目录下的lib目录(如果没有lib目录就创建一个)
  2. 右击要引用的外部jar包
  3. Build Path
  4. Add to Build Path

导入工程

File -> Import -> Existing Projects into Workspace

设置命令行参数

Run -> Run Configurations… -> New launch configuration -> Arguments -> Program arguments:

SVN

冲突

更新后遇到冲突:

右击冲突文件-> Team -> 编辑冲突Edit Conflicts

编辑完冲突后保存

右击冲突文件 -> Team -> 标记为解决Mark Resolove…

选择对应的解决方式

  • Conflicts hava been resolved in the file 合并
  • Resolve the conflict by using my version of the file 用我的版本
  • Resolve the conflict by using the jncoming version of the file 使用别人的版本,自己的不要了
  • Resolve the conflict by using the base version of the file 自己和别人的都不要了,用原始的

重新提交

Idea 引用外部 Jar 包

在项目文件夹上右击新建文件夹,命名为lib

复制 Jar 包到 lib 文件夹

File -> Project Structure… -> Libraries -> +号 -> Java -> 选中 lib 文件对应的 Jar 包 -> ok

IDEA 下访问 mysql 数据库

右键工程名文件夹–> New -> New Directory -> lib

将jar 包拷贝到lib文件夹

File -> Project Structure… -> Libraries -> + -> Java -> 选中对应的lib文件夹下的jar包 -> ok -> ok

Maven

管理jar包的工具软件

下载Maven

配置环境变量

设置配置文件

conf -> settings.xml

配置本地仓库路径

<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->

以上代码下面增加本地仓库路径设置,可以设置为非系统盘,仓库会放很多jar包,会变大。

<localRepository>C:\MavenRepository</localRepository>

配置文件保存的时候可能会需要管理员权限

配置镜像地址

<!-- mirror
 | Specifies a repository mirror site to use instead of a given repository. The repository that
 | this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
 | for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
 |
<mirror>
  <id>mirrorId</id>
  <mirrorOf>repositoryId</mirrorOf>
  <name>Human Readable Name for this Mirror.</name>
  <url>http://my.repository.com/repo/path</url>
</mirror>
 -->

以上代码下面增加镜像地址

<mirror>
  <id>nexus</id>
  <mirrorOf>*</mirrofOf>
  <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</mirror>
<mirror>
  <id>nexus-public-snapshots</id>
  <mirrorOf>public-snapshots</mirrofOf>
  <url>http://maven.aliyun.com/nexus/content/repositories/snapshots</url>
</mirror>

IDEA 创建 Maven 工程

New Project -> Maven -> Create from archetype -> org.apache.maven.archetypes:maven-archetype-quickstart

GroupId: 包名

ArtifactId: 工程名ID(尽量小写)

-> Next

-> 进入项目后右下角选择 Enable Auto-Import

pom.xml : 用来管理当前工程所引用jar 包的核心配置文件

Maven仓库

<dependencies> <!-- 用来配置当前工程依赖的所有Jar包说明 -->
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.11</version>
  <scope>test</scope>
</dependency>
<!-- 添加对MySQL8的依赖 https://mvnrepository.com/ -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.19</version>
</dependency>
</dependencies>

IDEA 单元测试

在要测试的类名右边alt + insert键 -> Test… -> JUnit4

Java 反编译

工具

JD-GUI

变量与数据类型

存储单位

8 bit = 1 byte

1024byte = 1kb

原码、反码和补码

符号位

正数的最高位为0,负数的最高位为1

原码

符号位加上真值的绝对值

反码

正数的反码是其本身

负数的反码是在其原码的基础上,符号位不变,其余各位取反

补码

正数的补码就是其本身

负数的补码是在原码的基础上,符号位不变,其余各位取反,最后+1,即在反码的基础上+1

运算符和表达式

算术运算符

负数的取模结果的符号取决于第一个操作数的符号

小数也可以取模

逻辑运算符

判断闰年条件

isLeapYear = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;

位运算符

运算符 名称
& 按位与 两个操作数同时为1结果为1
| 按位或 两个操作数只要有一个为1,结果为1
~ 按位非 按位取反
^ 按位异或 两个操作数相同,结果为0;不相同结果为1
<> 按位右移 左侧空位补0,规律:右移n位,就是除以2的n次方

循环

控制台进度条

/**
 * 控制台进度条
 */
public class ProgressBar {
    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 10; i++) {
            String star = "";
            for (int j = 0; j <= i; j++) {
                start += "*";
            }
            System.out.printf("\r%3.0f%%[%-10s]", (i + 1) * 10.0, start);
            Thread.sleep(100);
        }
    }
}

数组

二维数组定义时第一维(第一个中括号)必须给定维数(行数),第二维可以不给定。

二维数组中nums[row]中存放的就是一个一维数组的地址;m维数组中的元素存放的就是m-1维数组的地址

数组操作

Arrays.toString()

/**
 * @param a 数组
 * @return 数组中的内容的字符串形式
 */
public static String toString(int[] a)

Arrays.copyOf()

/**
 * 把源数组复制到一个新的指定长度的数组中并返回新数组
 * @param original 要复制的数组
 * @param newLength 要返回的副本的长度
 * @return 原始数组的副本,被截断或用零填充以获得指定的长度
 */
public static int[] copyOf(int[] original,
                           int newLength)

System.arraycopy()

/**
 * 将指定源数组中的数组从指定位置复制到目标数组的指定位置
 * @param src 要复制的源数组
 * @param srcPos 源数组的起始位置
 * @param dest 目标数组(要复制到的数组)
 * @param destPos 目标数组的起始位置
 * Wparam length 要复制的长度
 */
public static void arraycopy(Object src,
                             int srcPos,
                             Object dest,
                             int destPos,
                             int length)

Array.sort()

/**
 * 对数组排序
 * @param a 要排序的数组
 */
public static void sort(int[] a)

排序算法

冒泡排序

for (int i = 0; i < nums.length - 1; i++) {
    for (int j = 0; j < nums.length - i - 1; j++) {
        if (nums[j] < nums[j + 1]) {
            int temp = nums[j];
            nums[j] = nums[j + 1];
            nums[j + 1] = temp;
        }
    }
}

选择排序

for (int i = 0; i < nums.length; i++) {
    int minIndex = i;
    for (int j = i + 1; j < nums.length; j++) {
        if (nums[j] < nums[minIndex]) {
            minIndex = j;
        }
    }
    if (minIndex != i) {
        int temp = nums[minIndex];
        nums[minIndex] = nums[i];
        nums[i] = temp;
    }
}

高效选择排序

int count = nums.length;
for (int i = 0; i < count; i++) {
    int min = i;
    int max = count -1;
    for (int j = i; j < count; j++) {
        if (nums[j] < nums[min]) {
            min = j;
        }
        if (nums[j] > nums[max]) {
            max = j;
        }
    }
    if (min != i) {
        int temp = nums[i];
        nums[i] = nums[min];
        nums[min] = temp;
        if (max == i) {
            max = min;
        }
    }
    if (max != count -1) {
        int temp = nums[count - 1];
        nums[count - 1] = nums[max];
        nums[max] = temp;
    }
    count--;
}

查找

二分查找法

前提

数组已排序好

基本原理

首先将要查找的元素(key)与数组的中间元素比较

  1. 如果key小于中间元素,只需要在数组的前一半元素中继续查找
  2. 如果key和中间元素相等,匹配成功,查找结束
  3. 如果key大于中间元素,只需要在数组的后一半元素中继续查找
二分查找法算法
Arrays.sort(nums);
int low = 0;
int high = nums.length - 1;
int mid = (low + high) / 2;
// int searchNum = ...;
int searchIndex = -1;
while (low <= high) {
    mid = (low + high) / 2;
    if (searchNum > nums[mid]) {
        low = mid + 1;
    } else if (searchNum < nums[mid]) {
        high = mid - 1;
    } else {
        searchIndex = mid;
        break;
    }
}

手写ArrayList-主播系统

MyArray

package array;

/**
 * @author air
 */
public class MyArray {
    /** 被管理的对象数组,可以装载任意类型的对象,初始情况下元素个数为0,插入新元素时需要分配空间 */
    private Object[] elements = {};
    /** 元素数组的初始容量 */
    private static final int DEFAULT_CAPACITY = 10;
    /** 每次扩容时增加的容量大小 */
    private static final int DEFAULT_GROUTH_CAPACITY = 50;
    /** 当前数组中的元素个数 */
    private int size;

    public MyArray() {
        ensureCapacity(DEFAULT_CAPACITY);
    }

    /**
     * 添加元素
     */
    public void add(Object newElement) {
        add(newElement, size);
    }

    /**
     * 将element插入到elements的index下标处
     * @param newElement 要插入的element
     * @param index 要添加到的下标
     */
    public void add(Object newElement, int index) {
        // 1. 先判断elements数组的容量是否充足,如果不够就需要扩容
        if (size == elements.length) {
            ensureCapacity(size + DEFAULT_GROUTH_CAPACITY);
        }

        System.arraycopy(elements, index, elements, index + 1, size - index);
        // 2. 在index处赋值即可
        elements[index] = newElement;
        size++;
    }

    /**
     * 为数组重新分配空间
     * @param newCapacity
     */
    public void ensureCapacity(int newCapacity) {
        if (newCapacity < size) {
            return;
        }

        // elements = Arrays.copyOf(elements, newCapacity);
        // 为了理解内部机制,我们不采用默认实现Arrays.copyOf,因为Arrays.copyOf太完美了。

        // 1. 将数组的原内容保存下来
        Object[] oldElements = elements;        // 本方法调用完毕后,就会自动回收elements原来的空间
        // 2. 创建一个新数组
        elements = new Object[newCapacity];
        // 3. 再将原来的内容复制到新数组中
        System.arraycopy(oldElements, 0, elements, 0, size);
        // 4. 将局部变量指向空,使数组原来的堆空间被gc清理
        oldElements = null;
    }

    public Object remove(int index) {
        if (index < 0 || index >= size) {
            System.err.println("index参数的范围越界!");
            return null;
        }
        // 记录所删除的元素,以便返回
        Object delElement = elements[index];
        System.arraycopy(elements, index + 1, elements, index, size - index - 1);
        size--;
        return delElement;
    }

    /**
     * 将数组的容量缩小,与元素的总数一致
     */
    public void trimToSize() {
        ensureCapacity(size);
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public Object get(int index) {
        if (index < 0 || index >= size) {
            System.err.println("index参数的范围越界!");
        }
        return elements[index];
    }

    public int getSize() {
        return size;
    }

    public int getCapacity() {
        return elements.length;
    }
}

Test

package com.oldnine.air.array;

public class MyArrayListTest {
    public static void main(String[] args) {
        // 我们自定义的数组类MyArray主要用来封闭数组的常用/通用操作
        // 可以旋转任何类型的对象,可以很方便的插入、查询以及删除
        // 还可以取出某个对象,继续使用
        // MyArray在程序中的作用:相当于我们现实生活中的大货车
        MyArray array = new MyArray();
        for (int i = 0; i < 5; i++) {
            Shower shower = new Shower(i + 1, "test" + i, null, null);
            array.add(shower);
        }
        System.out.println("元素个数:" + array.getSize());
        System.out.println("容量大小:" + array.getCapacity());

        array.add(new Shower(1000, "test1K", null, null), 2);

        for(int i = 0; i < array.getSize(); i++) {
            // 因为MyArray中的对象数组是Object类型,所以取出来的也是Object
            Shower shower = (Shower)array.get(i);
            System.out.println(shower);
        }
    }
}

shower

package com.oldnine.air.array;

import java.util.Random;

/**
 * 主播实体类
 * @author air
 */
public class Shower {
    private long id;
    private String name;
    private String special;
    private long fansCount;
    private String intro;
    private double face;
    private String imagePath;

    public Shower() {
        this(0, "默认主播", "默认特长", "默认主播的彪悍人生不需要解释!");
    }

    public Shower(long id, String name, String special, String intro) {
        this(id, name, special, new Random().nextInt(1001) + 1000, intro, 0, null);
    }

    private Shower(long id, String name, String special, long fansCount, String intro, double face, String imagePath) {
        super();
        this.id = id;
        this.name = name;
        this.special = special;
        this.fansCount = fansCount;
        this.intro = intro;
        this.face = face;
        this.imagePath = imagePath;
    }

    @Override
    public String toString() {
        return id + "\t" + name + "\t" + special + "\t" + fansCount + "\t" + intro;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSpecial() {
        return special;
    }

    public void setSpecial(String special) {
        this.special = special;
    }

    public long getFansCount() {
        return fansCount;
    }

    public void setFansCount(long fansCount) {
        this.fansCount = fansCount;
    }

    public String getIntro() {
        return intro;
    }

    public void setIntro(String intro) {
        this.intro = intro;
    }

    public double getFace() {
        return face;
    }

    public void setFace(double face) {
        this.face = face;
    }

    public String getImagePath() {
        return imagePath;
    }

    public void setImagePath(String imagePath) {
        this.imagePath = imagePath;
    }
}

方法

数组参数

遍历main()方法的命令行参数

public static void main(String[] args) {
    for (int i = 0; i < args.length; i++) {
        // args[i]
    }
}

不定长参数

public static void Fun(int num, int ... nums) {
    ...
}

使用不定长参数时,必须是参数的最后一个

一个方法只准有一个不定长参数

Math类提供的数学方法

指数函数方法

方法 作用说明
Math.exp(x) 返回算术常量E的x次方
Math.log(x) 返回x的自然底数–Math.log(Math.E)的值为1.0
Math.log10(x) 返回x的以10为底的对数
Math.pow(a,b) 返回a的b次方
Math.sqrt(x) 对于0以上的数字,返回x的平方根

取整方法

方法 作用说明
Math.ceil(x) 向上取整最接近x的整数
Math.floor(x) 向下取整最接近x的整数
Math.rint(x) 取整为x最接近的整数,距离相等则返回偶数整数
Math.round(x) 四舍五入取整–返回(int)Math.floor(x+0.5)
Math.abs(x) 返回绝对值
Math.min(x, y) 返回最小值
Math.Max(x, y) 返回最大值

图形函数

两点间距离公式

\left| AB \right| = \sqrt{ ( x_1 – x_2 ) ^ 2 + ( y_1 – y_2 ) ^ 2 }

字符串

创建字符串

String对象是不可变的,字符串一旦创建,内容不能再变

常量赋值

String s = "string";

在Java中以双引号包括的字符串,只要内容相同,无论在程序代码中出现几次JVM都只会建立一个实例放在字符串池(String pool)中维护,而使用String类的构造方法则创建新对象

使用构造方法

String s = new String("string");
String s = new String(str.getBytes("gbk"), "gb2312");

String类常用的构造器

构造器 说明
String() 创建一个空字符串对象
String(String orignal) 用字符串直接量创建新字符串对象
String(char value[]) 用字符数组创建一个字符串
String(byte bytes[]) 用字节数组创建一个字符串
String(byte bytes[], String charsetName) 用字节数组和指定字符集创建一个字符串

String类的常用方法

方法 说明
length() 获取字符串中的字符个数
charAt(index) 返回字符串中指定下标的字符
concat(str) 拼接字符串,返回一个新字符串对象
toUpperCase() 返回一个新字符串,所有字母大写
toLowerCase() 返回一个新字符串,所有字母小写
trim() 返回一个新字符串,去掉了两边的空格
char[] toCharArray() 将此字符串转换为一个新的字符数组

字符串的比较

方法 说明
equals(str) 逐字符比较,相等返回true,不等返回false
equalsIgnoreCase(str) 忽略大小写比较
compareTo(str) 根据比较大小分别返回小于0,0,大于0的整数
compareToIgnoreCase(str) 忽略大小写比较
startsWith(prefix) 如果字符串以特定前缀开始,返回true
endsWith(suffix) 如果字符串以特定后缀结束,返回true
contains(str) 如果str是字符串的子字符串,返回true(包含)

获取子串的方法

方法 说明
indexOf(ch) 返回字符串中出现的第一个字符ch下标,没有匹配返回-1
indexOf(ch, fromIndex) 返回字符串中fromIndex之后出现的第一个ch下标,无匹配返回-1
indexOf(s) 返回字符串中出现的第一个字符串s的下标,无匹配返回-1
indexOf((s, fromIndex) 返回字符串中fromIndex后出现的第一个字符串s下标,无匹配返回-1
lastIndexOf(ch) 返回字符串中出现的最后一个字符ch下标,无匹配返回-1
lastIndexOf(ch, fromIndex) 返回字符串中fromIndex之前出现的最后一个ch下标,无匹配返回-1
lastIndexOf(s) 返回字符串中出现的最后一个字符串s下标,无匹配返回-1
lastIndexOf(s, fromIndex) 返回字符串中fromIndex之前出现的最后一个s下标,无匹配返回-1
substring(begin) 返回该字符串的子字符串,从begin下标(包含)到字符串的结尾
substring(begin, end) 返回该字符串的子字符串,从begin下标(包含)到end(不含)下标之间

字符串的拆分

string.split("arg")方法将一个字符串用参数传来的分割符将字符串分割为子字符串,结果作为字符串数组返回

参数示例:

string.split(",");以”,”分割

string.split(",\\.");以”,.”分割

string.splie(",|\\.");” 遇到”,”和”.”都进行分割

string.split(",|\\.|\\|");遇到”,”和”.”和”|”都进行分割

点和竖线符号是转义序列,用作分隔符必须以双斜线开头

单竖线|可以理解做“或者”

split方法的反向操作是String.join()方法,这是一个静态方法

StringBuffer/StringBuilder

与String类不同的是,StringBuffer和StringBuilder类的对象能够被多次的修改,并且不产生新的未使用对象

StringBuilder类在Java 5中被提出,它和StringBuffer之间的最大不同在于StringBuilder的方法不是线程安全的(不能同步访问)

构造方法 说明
StringBuffer() 构造一个默认缓存为16的StringBuffer对象
StringBuffer(int capacity) 构建一个指定缓存容量的StringBuffer对象
StringBuffer(String str) 构建一个指定字符串值的StringBuffer对象

修改StringBuffer中字符串

方法 说明
StringBuffer append(String str) 将指定的字符串追加到此字符序列
StringBuffer reverse() 反转字符序列
StringBuffer void delete(int start, int end) 移除此字符序列中的字符
StringBuffer void insert(int offset, String str) 将字符串str插入到字符序列中(多个重载方法)
StringBuffe void replace(int start, int end, String str) 使用给定的字符串str替换字符序列中的字符
void setCharAt(int index, int char) 在指定索引位置设定字符
int capacity() 返回StringBuffer当前的缓存容量
void setLength(int newLength) 设置StringBuffer的新长度
void trimToSize() 如果缓冲区大于其当前的字符序列,那么它可能会调整大小,以便更加节省空间

Java面向对象

类和对象

判断某个对象的类型

obj instanceof Object

单例模式

private static Printer printer = null;
private Printer() {}
public static Printer getInstance() {
    if (null == printer) {
        printer = new Printer();
    }
    return printer;
}

final关键字

  1. 修饰变量:常量,值不可被修改
  2. 修饰方法:不能被重写
  3. 修饰类:不能被继承(太监类,防止被修改和重写)

类的关系

关联

一种常见的二元关系,描述两个类之间的活动。

在类图中由两个类之间的实线表示,每个关系由一个可选的黑色三角表明关系的方向,多重性可以是一个数字或一个区间,m…n表示对象处于m到n之间(包括m和n),“*”表示无数个对象

聚集

聚集是关联的一种特殊形式,代表了两个对象之间的归属关系

聚集建模has -a 关系。

所有者对象称为聚集对象,它的类是聚集类;所属对象被称为被聚集对象,它的类称为被聚集类

一个对象可以被多个其他聚集对象所拥有

组合

如果一个对象只归属于一个聚集对象,那么它们之间的关系就称为组合

组合的图形表示为黑色菱形,聚合表示为空心菱形

继承

包装类

基本类型的值不是对象,可以使用JavaAPI中的包装类包装成对象

自动装箱操作,相当于调用了包装类的valueOf()静态方法

数字型自动装箱时,如果数字不在-128到127之间,就会创建新的对象,否则就会返回缓存中的对象

int num1 = 10;
Integer iNum1 = new Integer(num);    // 标准装箱
Integer iNum2 = num;                 // 自动装箱
Integer iNum3 = Integer.valueOf(num1);
int num2 = iNum2.intValue();         // 标准拆箱
int num3 = iNum2;                    // 自动拆箱
iNum1.compareTo(iNum2);              // 比较

包装类没有无参构造,所有包装类的实例都是不可变的。一旦创建对象后,它们的内部值就不能再改变

finalize() 方法

/**
 * 当本对象被回收时,自动调用,不要手工调用
 */
@Override
protected void finalize() throws Throwable {
    super.finalize();
}

继承 (Inheritance)

访问修饰符和访问权限

修饰符 子类 不同包
default X X
public
protected X
private X X X

子类不能削弱父类中定义的方法的可访问性。

例如,父类的public成员,子类的可访问性也必须是public

类图中,空心/三角表示继承关系

子类继承父类使用extends关键字

子类继承父类后,可以在子类中重写父类方法

Java中只能继承一个父类

子类不能访问父类的private成员

子类可以访问父类的protected成员

子类可以访问父类的public成员

子类如果访问父类中类似get set这种可以访问private成员的方法。则也可以在子类中间接的访问父类的private成员

继承关系是一种is-a关系:父类和子类之间必须存在is-a关系

静态不能被继承,静态的属性和方法属于类的属性和方法,而不是此类的实例对象方法。调用静态属性和方法时无须实例化,所以静态不能被继承。静态破坏了继承。

任何类如果没有显式的指定继承关系,则默认继承自Object类

父类构造和子类构造

在子类构造(包括默认构造/无参构造和有参构造)中可以使用super关键字调用父类构造(包括默认构造/无参构造和有参构造)。

在子类构造(包括默认构造/无参构造和有参构造)中,都会隐含第一句为super();来调用父类的默认构造。

构造方法链:习惯上定义一个最多参数的构造,参数少的构造调用参数多的构造,形成构造方法链

在构造方法中调用其它构造(即在构造方法中使用this()super())时注意一定要放在构造方法的第一句

构造方法不可继承

多态

同一个实体,同时具有多种形式

父类类型变量可以通过子类构造实例化

当父类和子类有同名成员变量时,那么父类类型的子类对象访问该名称的变量时,访问的是父类内存空间中的变量

当父类和子类有同名方法时,其实就是重写,那么父类类型的子类对象调用该名称的方法时,会执行在子类中被重写的方法;当在子类重写父类方法后,子类对象就会调用重写后的方法,而在重写前则调用父类方法。子类重写了父类的方法时,可以通过super.方法名()来调用父类的方法

如果子类与父类有同名属性,在子类中通过this关键字调用的是子类成员,在父类中通过this关键字调用的是父类成员。但是在调用子类对象的gettersetter方法时,如果子类没有重写父类的getter setter则会访问父类的getter setter要想通过子类对象访问子类与父类同名属性,则在子类中也要有相应的getter setter

变量多态看左边,方法多态看右边,静态多态看左边

静态没有多态,所以肯定看左边,跟着父类走

工厂模式

工厂类:根据传入的参数返回对应的对象,工厂类类名一般在后面加个Factory后缀

// 让工厂生产
MyClass myClass = MyClassFactory.getMyClass("balabalala");

类的设计原则

内聚性:类要描述单一的实体,所有类操作应该在逻辑上相互配合,支持一个一致的目的

一致性:遵循标准Java程序设计风格和命名习惯

封装性:应该使用private修饰符隐藏其数据,以免用户直接访问它

清晰性:类还应该有一个清晰的合约,从而易于解释和理解

完整性:一个类应该通过属性和方法提供多种方案以适应不同用户的需求

抽象类

抽象就是不被实现的,想象中的

抽象类不能直接使用new创建自己的实例

获得抽象类实例的方法

定义getInstance()方法,返回子类对象。

获得抽象类的实例的方法:通过工厂模式获得相应的子类对象

动态绑定

Class.forName("packagenameandclassname").newInstance();

Class.forName("packagenameandclassname")从虚拟机中加载对应的类

Class.newInstance()实例化类

抽象类是天生的父类

通过abstract关键字创建抽象类

public abstract class Hero {
}

为什么要有抽象类:当业务逻辑更加复杂的时候,我们会期望有类专门用来封装一些通用代码,而不用被实例化。接口就是从抽象类发展而来的(接口是更加抽象的抽象类)。

抽象方法

抽象方法也是通过abstract关键字声明

抽象方法只需要声明不需要实现,即写完方法签名后加个分号就行了。

抽象方法必须放在抽象类中,但是抽象类里可以没有抽象方法

子类不是抽象类时必须重写父类中的所有抽象方法

子类是抽象类时仍然可以继续声明成抽象方法

如果没有类实现抽象方法,编译器会报错

只有抽象类和抽象方法,没有抽象变量,所以abstract关键字不能用来修饰变量

abstract关键字和static不能同时使用

模板模式

抽象类中,我们一般会定义已经实现的方法和部分抽象方法在已实现的方法中,就可以直接调用抽象方法。

JDK中的经典抽象类

Number, Calender, GregorianCalender

接口

接口是一种与类相似的结构,只包含常量和抽象方法,在Java中,接口被看做是一种特殊的类,包含常量和待实现的方法,与抽象类相似,不能使用new操作符直接创建接口的实例

抽象类中可以定义成员变量,接口中不允许存在成员变量。接口中如果定义变量,那么一定是静态的常量

接口的目的是指明相关或不相关类的多个对象的共同行为(功能)

接口的例子:可以指明对象是可比较的,可食用的,可被攻击的、可飞翔的,可克隆的……

接口的继承是集中在共同行为,共同功能上的,并不集中在属性上

public interface Assailable {
    // 常量及抽象方法声明
}

接口名一般可以在前面加个I或在后面加个able

接口必须由子类实现,实现类不能实现的方法可以继续标识为abstract

接口中所有的数据域都是public static final,所有方法都是public abstract,默认情况可以省略这些关键字

在类图中,实现接口的类用一条带空心三角箭头的虚线连接并指向接口

继承是实心的

实现接口

抽象类或接口继承接口使用extends关键字

普通类继承实现类使用extends关键字

实现类实现接口使用implements关键字

implement是实现的意思extends是延伸、扩展、继承的意思。所以继承都用extends,而实现用implement

默认实现

在djk1.7及以前都是抽象类中可以有抽象方法,接口是特殊的抽象类,因为接口中的所有方法都必须是抽象的,不能有方法实现,而到了Java8,我们发现在接口中也可以通过default关键字给出方法的默认实现。

默认实现建议只封装一些简单的逻辑

interface Eat {
    // Only jdk1.8 after
    default public void eating() {
        // 可以写具体的方法实现
    }
}

接口的特点

接口中所有方法都需要实现类去实现(Java8前)

接口可以被多继承(用逗号分隔)

使设计和实现完全分离

能够更自然的使用多态

可以更容易搭建程序框架

可以更容易更换实现

实现类的类名一般加个Impl后缀

实现类如果实现的多个接口中有同名方法:

抽象类和普通类一样,都有属性和方法,但是不能使用new实例化

非抽象类中不能包含抽象方法

如果抽象类的子类没有实现所有被继承父类的抽象方法,该子类必须是抽象类

包含抽象方法的类一定是抽象类,反之抽象类不一定包含抽象方法

即使父类是具体的,子类也可以是抽象的

抽象类可以有构造方法,该方法可以在子类中被调用。而接口不能有构造方法

Java集合框架

Collections

Collections用于简化对Java集合框架的常见操作

方法 描述
void sort(list) 对指定的列表进行排序
void sort(list, comparator) 使用指定的比较器进行排序
int binarySearch(list, key) 使用二分查找法搜索有序列表中的键值
void reverse(list) 颠倒指定的列表
Comparator reverseOrder() 返回逆序的比较器
void shuffle(list) 随机打乱指定的列表
void shuffle(list, random) 使用随机对象打乱指定的列表
void copy(desList, srcList) 将源列表复制给目标列表,注意复制的目标集合需要具有空间并且执行的是浅复制。
List nCopies(n, obj) 返回包含某对象n个副本的列表
void fill(list, obj) 使用对象填充列表
Object max(collection) 返回集合中的max对象
Object min(collection) 返回集合中的min对象
boolean disjoint(collection1, collection2) 比较两个集合中是否没有交集,如果collection1和collection2没有公共元素则返回true
int frequency(collection, obj) 返回集合中指定元素出现的次数

binarySearch() 方法

int index = Collections.binarySearch(list, "String");

copy() 方法

目标集合必须有足够的空间容纳源集合

list<String> newList = new ArrayList<String>();
// 这个for循环是为了给目标集合分配空间
for (int i = 0; i < oldList.size(); i++) {
    newList.add("");
}
Collections.copy(newList, oldList);

该方法使用起来不方便,不如使用addAll方法

该方法执行的是浅复制,复制的是线性表中元素的引用

fill() 方法

Collections.fill(list, "填充的内容");

使list中的每一个元素都为填充的内容

disjoint() 方法

比较两个集合中没有相同的元素(是否有交集),返回true表示没有

Collections.disjoint(list1, list2);

某个元素出现的次数

Collections.frequency(list, "string");

Collection接口

方法 描述
boolean add(E e); 向集合中添加元素e / 返回集合中的元素个数
int size(); 返回集合中的元素个数
boolean addAll(Collection c); 将集合c中的所有元素添加到当前这个集合
boolean contains(Object o); 如果该集合中包含对象o,返回true
boolean containsAll(Collectionc); 如果该集合中包含集合c中的所有元素,返回true
boolean isEmpty(); 如果集合不包含任何元素,返回true
void clear(); 删除集合中的所有元素
Iterator iterator(); 返回该集合中元素所有的迭代器
boolean remove(Object o); 从集合中删除元素o
boolean removeAll(Collection c); 从集合中删除集合c中的所有元素
boolean retainAll(Collection c); 保留c和该集合都有的元素(交集)
Object[] toArray(); 返回该集合元素构成的Object数组

Collection虽然是集合的最高父接口,但是直接使用Collection接口会造成操作意义不明确,所以在实际开发中不提倡直接使用Collection接口。

List接口

List用来存储多个对象信息,并以索引方式的方式保存

不仅可以存储重复的元素,也可以指定元素存储的位置

特点:有序、不唯一

List接口的方法

方法 描述
public void add(int index, E element) 在指定位置增加元素
public boolean addAll(int index, Collection c) 在指定位置增加一组元素
E get(int index) 返回指定位置的元素
E set(int index, E element); 替换指定位置元素
public int indexOf(Object o) 查找指定元素的位置
public int lastIndexOf(Object o) 从后往前查找指定元素的位置
public ListIterator listIterator() 获得List迭代器对象-升级版的迭代器
public E remove(int index) 删除指定位置的元素
public List subList(int fromIndex, int toIndex) 取出集合中的子集合-String里的substring[1,4)

将集合转为普通对象数组

MyClass[] myClasses = new MyClass[];
myClasses = (MyClass[])list.toArray(myClasses);

将普通对象数组转换成List类型

// 使用asList方法转换回普通对象数组是大小固定的,不能添加和删除
List<MyClassArray> list = Arrays.asList(myClassArray);

List接口扩展了Collection接口的方法,这些方法使用起来比父接口更加方便,要使用List接口,需要对List接口的实现类实例化

迭代器

使用Iterator接口遍历集合,Iterator提供一种通用遍历集合的方法,迭代器可以让任何的集合都拥有统一的遍历方法

迭代器每用完一次都要更新

默认迭代器

实例化 Iterator
Iterator<MyClass> myIterator = list.iterator();
迭代
while(myIterator.hasNext()) {
    // myIterator.next() ...
}

hasnext()仅仅只判断下一个下标有没有元素,并没有让光标移动

next()才会让下标往后移一个,同时返回下一个下标处的元素

ListIterator

实例化 ListIterator
ListIterator<myClass> listIterator = list.listIterator();
正序迭代
while(listIterator.hasNext()) {
    // listIterator.next() ...
}
逆序迭代
while(listIterator.hasPrevious()) {
    // listIterator.previous() ...
}
是否包含某元素
listIterator.contains(myClass) // 需要重写对象的equals方法

比较器

Compareble接口

实现Compareble 接口

实现Compareble的实现类要重写Compareble接口的compareTo()方法

public MyClass implements Compareble {
    private int num;
    @Override
    public int compareTo(Object o) {
        if (null == 0) return 1;
        MyClass myClass = (MyClass)o;
        return num == myClass.getNum() ? 0 : num > myClass.getNum() ? 1 : -1;
    }
}

Compareble实现类的对象数组可以用Arrays.sort()或Collections.sort()用来排序

Compareble的缺点是只能根据某一个指定的规则去排序

排序
Collections.sort(list);

Collections.sort()方法的参数 List中的T类型必须是Compareble接(比较器)的子类实现

Comparetor接口

Comparetor接口可以弥补Comparable接口的不足,可以更加灵活地实现不同的排序规则,可以对同一个类的对象按需要选择不同的规则排序

匿名写法

匿名写法排序

Collections.sort(list, new Comparator<MyClass>() {
    @Override
    public int compare(MyClass o1, MyClass o2) {
        // 书写比较算法
    }
});

匿名写法逆序

Collections.sort(list, new Comparator<MyClass>() {
    @Override
    public int compare(MyClass o1, MyClass o2) {
        // 书写比较算法
    }
}.reversed());
正经写法

比较器类

public class MyClassComparator implements Comparator<myClass> {
    @Override
    public int compare(myClass o1, myClass o2) {

    }
}

工厂类

public class MyClassComparaterFactory {
    public static final int COMPARATER_A = 1;
    // ...
    public static Comparator<MyClass> GetComparator(int type) {
        switch(type) {
            case COMPARATER_A:
                return new MyClassComparator();
            // other case...
        }
    }
}

生产

正序

Collections.sort(list, MyClassComparaterFactory.GetComparator( MyClassComparaterFactory.COMPARATER_A));

逆序

Collections.sort(list, MyClassComparaterFactory.GetComparator( MyClassComparaterFactory.COMPARATER_A).reversed);

List接口的实现类

ArrayList(数组线性表)

是一个大小可变的数组,在内存中分配连续的空间

遍历元素和随机访问元素的效率比较高

LinkedList(链表)

采用链表存储方式

提供从线性表两端提取、插入和删除元素的方法

插入、删除元素效率比较高

创建LinkedList对象

子类实现父类

适合通用的场合,能够实现灵活的更换子类实现

List<String> list = new LinkedList<String>();
直接实例化子类

适合一般使用场景,临时用一下

LinkedList<String> linkedList = new LinkedList<String>();

LinkedList 的特有方法

addFirst() / addLast() 方法
linkedList.addFirst("first");
remove() 方法
linkedList.remove();
linkedList.remove(index);
linkedList.remove(obj);     // 会调用equals方法

Vector 向量类

Vector类是过时的,一般常用于JavaME、智能卡等微型版中使用,ArrayList在微型版中是无法使用的,Vector是线程同步的,线程安全,在处理大量数据的时候,速度较慢。

构造方法 描述
Vecotr() 创建初始容量为10空向量
Vector(Collection c) 创建一个来自现有集合的向量
Vector(int initialCapacity) 创建指定初始容量的向量
Vector(int initialCapacity, int incr) 创建一个指定容量和增量的向量
方法 描述
int capacity() 获得向量当前的容量
void addElement(E e) 在向量末尾追加一个元素
void setSize() 设置向量的新尺寸
void trimeSize() 将向量容量缩小到他的尺寸
void ensurCapacity() 增加向量的容量
void copyInto(Object[] array) 将向量中的元素复制到数组
E elementAt(int index) 返回指定下标的元素
Enumeration elements() 返回向量的枚举
E firstElement() 返回向量的第一个元素
E lastElement() 返回向量最后一个元素
insertElement(e, index) 将元素插入到指定下标处
void removeAllElement() 删除向量中的所有元素
boolean removeElement(Object o) 删除向量中第一个匹配的元素
void removeElementAt(index) 删除指定下标处的元素
setElementAt(e, index) 在指定下标处设置一个新元素
补充一个老版本的遍历方式

可以理解成迭代器的前身

Enumeration<String> enumeration = vector.elements();
while(enumeration.hasMoreElements()) {
    // enumeration.nextElement()...
}

这种遍历方式已经不在使用,现在一般使用迭代器

Set接口

用来操作存储一组唯一、无序的对象(不允许有重复的元素)

set不能add空元素,语法不报错,执行会报错。而list插入null就没问题

HashSet

用来存储互不相同的任何元素

Set<String> set = new HashSet<String>();
set.add("string");

迭代

只支持普通迭代器,不支持list迭代器,list迭代器是List专属的

Iterator<String> iterator = set.iterator();
while(iterator.hasNext()) {
    // iterator.next()...
}

在一个Set中插入另一个Set

set1.addAll(set2);

判断交集

当前集合是不是包含另一个集合

set1.retainAll(set2)

LinkedHashSet

使用链表扩展实现HashSet类,支持对元素的排序

如果不需要维护元素被插入的顺序,就应该使用HashSet,更加高效

将任意Collection集合转换为LinkedHashSet

方法一:利用构造方法

linkedHashSet = new LinkedHashSet<String>(hashSet);

方法二

linedHashSet.addAll(hashSet);

TreeSet

TreeSet默认就是有序的,不需要手动调用排序方法,插入对象只要支持Comparable接口,插入时会自动进行排序。

TreeSet内部实现了SortedSet接口,所以默认是有序的,默认情况下,会调用对象的compareTo方法进行比较排序

Set<String> set = new TreeSet<String>();
set.add("string");

常用方法

获得首元素
treeSet.first();
获得尾元素
treeSet.last();
返回某元素之前的元素
treeSet.headSet();
返回某元素之后的元素
treeSet.tailSet();
返回小于某个元素的元素
treeSet.lower()

还有higher, floor, ceiling等方法

删除第一个或最后一个元素
treeSet.pollFirst();

Queue接口

Queue通常用于操作存储一组队列方式的对象信息

Deaue支持在两端插入和删除元素,是双端队列的简称(double-ended queue),支持从两端操作队列的元素

定义的方法:addFirst(e), removeFirst(e), addLast(e), getFirst()/getLast()

一般存储方式为先进先出

队列的特点就是先进先出

实现类:LinkedList, ArrayDeque

方法 描述
boolean offer(element) 向队列中插入一个元素(类似add方法)
E poll() 获取并删除队列头元素,如果队列为空返回null
E remove() 获取并删除队列头元素,如果队列为空抛出异常
E peek() 获取但不删除列头元素,如果队列为空返回null
E element() 获取但不删除列头元素,如果队列为空抛出异常
Queue<String> queue = new LinkedList<String>();

入队

注意不要用add方法,add方法不能入队

queue.offer("string");

出队

while(!queue.isEmpty()) {
    // queue.poll()...
}
int len = queue.size();    // 因为在出队的时候队列的size会变化,所以先用一个别的变量记录下来。
for (int i = 0; i < len; i++) {
    // queue.poll()...
}

循环

Iterator<String> iterator queue.iterator();
while(iterator.hasNext()) {
    // interator.next()...
}

PriorityQueue

优先队列,在默认情况下,优先队列会使用Comparable以元素的自然顺序排序,拥有最小值的元素被分配最高优先级,删除时,会优先删除优先级高的。如果拥有相同优先级的元素,任意一个元素都可能从队列中删除,我们也可以通过Comparator来指定一个顺序

PriorityQueue<String> priorityQueue = new PriorityQueue<String>();

ArrayDeque

ArrayDeque<String> arrayDeque = new ArrayDeque<String>();

作为栈使用

栈:先进后出

尽量使用ArrayDeque作为栈,避免使用java.util.Stack,因为Stack已经老了。

ArrayDeque<String> stack = new ArrayDeque<String>();
入栈
stack.push("string");
出栈
stack.peek();   // 出栈(不删除)
stack.pop();    // 出栈(删除)

作为栈和作为队的区别

作为队: {入队:offer;出队:poll}

作为栈:{入栈:push;出栈:pop}

peek即可用于队中出队亦可用于栈中出栈

Map

以键值对存储元素的容器

方法 描述
V put(key, value) 将一个键/值映射放入图中
V get(key) 根据键获取对应的value值
Set keySet() 返回包含键的规则集
Collection values() 返回包含值的集合
boolean containsKey(key) 返回图中是否包含键值key
Set&lt;Map.Entry&gt; entrySet() 返回一个图中包含条目的规则集
int size() 返回图中的键值对个数
V remove(key) 删除指定键对应的条目

HashMap

Map<String, Integer> hashMap = new HashMap<String, Integer>();

添加键值对

hashMap.put("str", 123);

遍历

KeySet
Set<String> keySet = hashMap.keySet();
for (String k : keySet) {
    // hashMap.get(k)...
}
values()

只能遍历所有值

Collection<Integer> values = hashMap.values();
EntrySet

Entry是在Map接口内部定义的接口

效率最高的遍历方式

1、先获得entrySet返回的Set<Entry>

Set<Entry<String, Integer>> entrySet = hashMap.entrySet();

2、使用遍历Set的方式遍历

for (Entry<String, Integer> entry : entrySet) {
    // entry.getKey() or entry.getValue()...
}
EntrySet + Iterator
Iterator<Entry<String, Integer>> iterator = hashMap.entrySet().iterator();
while(iterator.hasNext()) {
    // interator.next()...
    // entry.getKey() or entry.getValue()...
}

TreeMap

键有序

/**
 * 用来实现Map中键排序的比较器
 */
static class MapKeyComparator implements Comparator<String> {
    @Override
    public int compare(String key1, String key2) {
        // 比较算法
    }
}
Map<String, Integer> treeMap = new TreeMap<String, Integer>(new MapKeyComparator());

value有序

/**
 * 用来实现Map中值排序的比较器
 */
static class MapValueComparator implements Comparator<String> {
    private Map<String, Integer> map;

    public MapValueComparator(Map<String, Integer> map) {
        this.map = map;
    }

    @override
    public int compare(String key1, String Key2) {
        return map.get(key1).compareTo(map.get(key2));
    }
}
Map<String, Integer> hashMap = new HashMap<String, Integer>();
// hashMap.put()
Map<String, Integer> treeMap = new TreeMap<String, Integer>(new MapValueComparator(treeMap));
// 逆序写法:Map<String, Integer> treeMap = new TreeMap<String, Integer>(new MapValueComparator(treeMap).reversed());
treeMap.putAll(hashMap);

LinkedHashMap

Properties

方法 描述
String getProperty(key) 通过指定的键获得配置文件中对应的值
Object setProperty(key, value) 通过调用父类的put方法来设置键-值对
void load(instream) 从输入流读取属性列表
void store(outStream, comments) 将属性列表(键值对)写入到对应的配置文件中
void clear() 清除所有的键值对

使用方法

在src文件夹下创建一个config文件夹

在config文件夹下创建一个SysConfig.properties文件(SysConfig_en.properties)

Properties prop = new Properties();
prop.load(类名.class.getClassLoader().getResourceAsStream("config/SysConfig.properties"));

各种Map的特点

HashMap的查询、插入和删除比较高效

LinkedHashMap支持元素的插入顺序

TreeMap在遍历有序的键值时非常有效

Properties一般用于操作属性文件

深复制和浅复制

java.lang.Cloneable接口

一个带空体的接口称为标记接口(marker interface)

既不包含常量也不包含方法,用来表示一个类拥有某些特定的属性

实现Cloneable接口的类标记为可克隆的

Java类库中的很多类如Calendar, Date等都实现了这个接口

实现克隆功能的方案1:自己重写Object类的clone()方法

public MyClass implements Cloneable {
    // ...
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone(); // 默认返回的是Object类型,在外部得到之后可能需要强转成相应的类型
    }
}

浅复制:如果使用Object默认的clone方法,那么引用类型克隆的是地址,而不是内容。只会复制值类型成员的值。

深复制:将引用类型的成员也调用clone方法进行复制,而不只是复制应用类型成员的地址

public MyClass implements Cloneable {
    private String[] strs;
    private String str;

    // ...
    @Override
    public Object clone() throws CloneNotSupportedException {
        return deepClone(); 
    }

    private MyClass deepClone() throws CloneNotSupportedException {
        // 先进行浅复制
        MyClass cloneMyClass = (MyClass)super.clone();
        // 将每个引用类型单独调用clone方法
        cloneMyClass.str = new String(str);
        cloneMyClass.strs = strs.clone();
        return cloneMyclass;
    }
}

Java 中的时间和日期

传统时间操作&时间格式化

Date类

Date today = new Date();

时间格式化

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
System.out.println("格式化后的当前时间:" + dateFormat.format(today));

Calendar和GregorianCalendar

Calendar是一个抽象的日历基类,可以提取出详细的日历信息(年、月、日、时分秒等)

Calendar类的子类可以实现特定的日历系统,如公历(Gregorian历)、农历和犹太历

GregorianCalendar是Calendar类的一个具体实例

Calendar类的常量

常量 说明
YEAR 日历的年份
MONTH 日历的月份,0表示1月
DATE 日历的天(几号)
HOUR 小时(12时制)
HOUR_OF_DAY 小时(24时制)
MINUTTE 分钟
SECOND
DAY_OF_WEEK 一周的天数(周几),1是星期日
DAY_OF_MONTH 同DATE(同号)
DAY_OF_YEAR 当前年的天数,1是一年第一天
WEEK_OF_MONTH 当前月内的星期数,从1开始
WEEK_OF_YEAR 当前年内的星期数,从1开始
AM_PM 0表示上午,1表示下午

获得日历类的实例:

// 获得抽象类的常用方式:getInstance()方法
Calendar calendar = Calendar.getInstance();

调用Calendar.getInstance()方法时,实际上是在内部返回的子类对象(GregorianCalendar的对象)

System.out.println(calendar.getClass());

通过静态属性获得对应的时间部分:

System.out.println("年份:" + calendar.get(Calendar.YEAR));
System.out.println("月份:" + (calendar.get(Calendar.MONTH) + 1));
System.out.println("日期:" + calendar.get(Calendar.DATE));
System.out.println(calendar.get(Calendar.AM_PM));
System.out.println("小时:" + calendar.get(Calendar.HOUR));
System.out.println("24制小时:" + calendar.get(Calendar.HOUR_OF_DAY));
System.out.prinltn("分钟:" + calendar.get(Calendar.MINUTE));
System.out.println("秒  :" + calendar.get(Calendar.SECOND));
System.out.println("毫秒:" + calendar.get(Calendar.MILLISECOND));

为日历设置固定时间

calendar.set(year, month, date, hourOfDay, minute, secound);

时间的加减:

calendar.add(Calendar.YEAR, 5);
calendar.add(Calendar.YEAR, -5);

时间的比较:

calendar.compareTo(Calendar.getInstance());

计算时间差:

long diff = Calendar.getInstance().getTimeInMillis() - calendar.getTimeInMillis();

getActualMaximum

calendar.getActualMaximum(Calendar.YEAR);  // 当前系统支持的最大年份
calendar.getActualMaximum(Calendar.DAY_OF_MONTH);  // 获得一个月中的天数

判断一个年份是否是闰年

GregorianCalendar greCalendar = (GregorianCalendar)calendar;
boolean isLeapYear = greCalendar.isLeapYear(2000);

其它常用操作:

System.out.println(dateFormat.format(calendar.getTime()));
System.out.println("星期:" + calendar.get(Calendar.DAY_OF_WEEK));
System.out.println("当年的第" + calendar.get(Calendar.DAY_OF_YEAR) + "天");

NumberFormat数字格式化类

可以按照本地的风格习惯进行数字的显示

方法 说明
public static Locale[] getAvailableLocales() 返回所有语言环境的数组
public static final NumberFormat getInstance() 返回当前默认语言环境的数字格式
public static NumberFormat getInstance(Locale) 返回指定语言环境的数字格式
public static final NumberFormat getCurrencyInstance() 返回当前缺省语言环境的通用格式
public static NumberFormat getCurrencyInstance(Local) 返回指定语言环境的数字格式

异常

异常就是一种对象(Exception),表示阻止程序正常执行的错误或情况

在程序运行的过程中,如果JVM检测出一个不可能执行的操作,就会出现运行时错误

在Java中,运行时错误(Runtime Error)会作为异常(对象)抛出

如果异常没有被处理,程序将会非正常终止

try {
    // ...
} catch(Exception e) {
    log.log(Level.WARNING, e.getMessage());
}

异常类的层次结构

RuntimeException和Error以及他们的子类都称为免检异常。所有其他异常都称为必检异常。

常见的异常类型

异常类型 说明
Exception 异常层次结构的根类
NullPointerException 尝试访问未实例化(null)对象
ArrayIndexOutOfBoundsException 数组下标越界
ArithmeticException 算术计算异常,如把零作为除数
IllegalArgumentException 方法接收到非法参数
InputMismatchException 实际输入的类型与预想得到的数据类型不匹配(如在input.nextInt()时输入浮点数字)
NumberFormatException 数字格式转换异常,如把“abc”转换成数字
ClassCastException 两个类型间转换不兼容时引发的运行时异常
ClassNotFoundException 无法找到指定的类异常(通常在程序通过字符串加载类时抛出)
SQLException 扫行aql语句时的异常
IOException 进行文件IO操作时出现的异常

创建自定义异常类

可以通过继承Exception类来定义一个自定义异常类

尽量使用Java中提供的异常类而不要创建自己的异常类

自定义异常类

public class MyException extends Exception {
    public MyException() {
    }
    public MyException(String message) {
    }
}

在代码中抛出自定义的异常

public class myClass {
    pulic void method() throws MyException {
        throw new MyException(); // or throw new MyException("string");
    }
}

I/O

Java.io.File

用来处理文件目录问题

方法 描述
File(String pathname) 通过将给定路径的字符串来创建一个表示对应文件的File实例
exists() 判断此File的实例对应的文件是否存在
isFile() 判断此File实例是否是一个标准文件
isDirectory() 判断此File实例是否是一个文件夹
getName() 获取此File实例对应的文件或文件夹的名称
getAbsolutePath() 获取此File实例对应文件或文件夹的绝对路径
lastModified() 返回此File实例的最后修改时间
length() 返回此File实例的长度,单位字节
canRead() 文件是否可读
canWrite() 文件是否可写
canExecute() 文件是否可执行(仅linux有效)
isHidden() 文件是否隐藏
File file = new File("pathname");

构造方法

File(String pathname)

File(URL uri)

File(File parent, String child)

File(String parent, String child)

创建新文件/文件夹

File file = new File("pathname");
if (!file.exists) {
    try {
        if (file.createNewFile()) {
        }
    } catch(IOException e) {
    }
}

文件夹常用操作

File file = new File(".");

得到当前计算机的所有盘符

File[] roots = file.listRoots()

得到当前目录下的所有文件和文件夹

String[] filesName = file.list();

按照过滤器得到当前目录下的过滤后的文件/文件夹

String[] filesName = file.list(new FilenameFilter() {
    @Override
    public boolean accept(File dir, String name) {
        // 过滤显示的文件名
        if (name.endWith(".java") || name.endWith(".class")) {
            return true;
        }
        return false;
    }
});

文件树

/**
 * 文件树类
 */
public class FileTree {
    /** 用来存放读取出的所有文件 */
    private List<File> filelist = new ArrayList<File>();
    /** 用来存放读取出的所有文件夹 */
    private List<File> dirList = new ArrayList<File>();

    public void addFile(File file) {
        fileList.add(file);
    }

    public void addDir(File dir) {
        dirList.add(dir);
    }

    public void showFileList() {
        showList(fileList);
    }

    public void showDirList() {
        showList(dirList);
    }

    private void showList(List<File> list) {
        for(File file : list) {
            System.out.println(file.getName());
        }
    }

    /**
     * 添加传入文件树的两个集合
     */
    public void addFileTree(FileTree fileTree) {
        this.fileList.addAll(fileTree.fileList);
        this.dirList.addAll(fileTree.dirList);
    }

    /**
     * 以startDir为根目录,向集合中添加文件及文件夹
     */
    public static FileTree GetDir(File startDir) {
        FileTree fileTree = new FileTree();
        if (startDir.isDirectory()) {
            File[] fileArray = startDir.listFiles();
            for (int i = 0; i < fileArray.length; i++) {
                if (fileArray[i].isFile()) {
                    fileTree.addFile(fileArray[i]);
                } else {
                    fileTree.addDir(fileArray[i]);
                    //
                    fileTree.addFileTree(GetDir(fileArray[i]));
                }
            }
        }
        return fileTree;
    }
}
FileTree fileTree = FileTree.GetDir(new File("."));
fileTree.showDirList();
System.out.println("-----------------");
fileTree.showFileList();

输入/输出流

流的参照物是内存

在Java.io包中操作文件内容主要有两大类:字节流、字符流,两类都分为输入和输出操作

流中保存的实际上全都是字节文件,字符流中多了对编码的处理

在字节流中输入数据使用的是InputStream, 输出数据主要是使用OutputStream

InputStream和OutputStream是为字节流设计的,主要用来处理字节或二进制对象

在字符流输入数据主要使用Reader完成,输出数据主要使用Writer完成

Reader和Writer是为字符流(一个字符占2个字节)设计的,主要用来处理字符或字符串

字节流架构

InputStream & OutputStream

方法 描述
int read() 从输入流中读取下一个字节数据(0-255),已达最后没有可读取数据时返回-1
read(byte[] b) 从输入流中读取b.length个字节到数组b中,并返回实际读取的字节数(到最后时返回-1)
int read(byte[] b, int off, int len) 读取字节保存在b数组的下标off到len-1处,到最后时返回-1
long skip(long n) 从输入流中跳过并丢弃n字节的数据,返回实际跳过的字节数
int available() 返回可以从输入流中读取的字节数(估计值)
void close() 关闭输入流,释放占用的任何系统资源
void write(int b) 将指定的字节写入到输出流中
void write(byte[] b) 将字节数组的内容写入到输出流中
write(byte[] b, int off, int len) 将字节数组中下标off到len – 1的元素定稿到输出流中
void flush() 清除输出流中的缓存,强制写出任何缓冲的输出字节
实例化

InputStream是抽象类,不能直接实例化,需要借助子类实例化

InputStream inStream = null;
try {
    inStream = new FileInputStream(filePath);
} catch(FileNotFoundException e) {
} finally {
    try {
        if (null != inStream) inStream.close();
    } catch (IOException e) {
    }
}

先进写法:

try (InputStream inStream = new FileInputStream(filePath)){
    // use inStream in here...
} catch(IOException e) {
}

必须是Closeable的实现类才支持自动关闭资源的写法

一字性读进一个字节数组

inStream.read(byteArray);   // 小文件读取时使用

逐字节读入字节数组

int read = -1;
for (int i = 0; (read = inStream.read()) != -1; i++) {
    byteArray[i] = (byte)read;
}

BufferedInputStream & BufferedOutputStream

带缓冲的输入输出流,用于读取大文件

public static long ReadByBufferedInputStream() throws IOException {
    FileInputStream fin = new FileInputStream(file);
    BufferedIOStream bin = new BufferedInputStream(fin);
    byte[] bytes = new byte[bin.available()];
    // 将读取的文件分块,每块多少字节
    int blocks = bytes.length / 2048;   // blocks就是文件块的数量
    // 最后一块有可能不是正好是块的字节数
    if (bytes.length % (2048) != 0) {
        blocks++;
    }
    for (int i = 0; i < blocks; i++) {
        if (i == blocks -1) {
            // 最后一块需要特殊处理,因为不够一块的字节数
            bin.read(bytes, i * 2048, bytes.length % 2048);
        } else {
            bin.read(bytes, i * 2048, 2048);
        }
    }
    bin.close();
    fin.close();
}

字符流架构

读取文件

FileReader

BufferedReader

FileReader reader = new FileReader("file.txt");
// Reader reader = new FileReader("file.txt");
BufferedReader bReader = new BufferedReader(reader);
while ((line = bReader.readLine()) != null) {
    content.append(line).append("System.getProperty("line.separator")");
}
System.out.println(content);
bReader.close();
reader.close();

System.getProperty() 用来获得操作系统下定义的分隔符

System.getProperty(“line.separator”) 获得当前系统的换行符

写入文件

FileWriter

BufferedWriter

FileWriter writer = new FileWriter("file.txt", true);
// Writer writer = new FileWriter("file.txt", true);
BufferedWriter bWriter = new BufferedWriter(writer);
writer.write(content.toString());
bWriter.flush();
bWriter.close();
writer.close();

借助InputStreamReader把字节流转换成字符流

InputStream inStream = new FileInputStream("file.txt");
InputStreamReader inStreamReader = new InputStreamReader(inStream, "gbk");
BufferedReader reader = new BufferedReader(inStreamReader);
// 读取字符流的固定代码
reader.close();
inStreamReader.close();
inStream.close();

Demo

try (BufferedReader reader = 
    new BufferedReader(new InputStreamReader(new URL(strUrl).openStream()))) {
    String line = null;
    while ((line = reader.readLine()) != null) {
        content.append(line);
        content.append(System.getProperty("line.separator"));
    }
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
    writer.writer(content);
} catch (FileNotFoundException e) {
} catch (IOException e) {
}

try-with-resources & PrintWriter and Scanner

一个try-with-resources语句可以像普通的try语句那样有catch和finally块

在try-with-resources语句中,任意的catch或者finally块都是在声明的资源被关闭以后才运行

java.io.PrintWriter写入的同时对写入的数据进行格式化,类中的方法不会抛出I/O异常

try(PrintWriter out = new PrintWriter(new File("file.txt"))) {
    out.printf("...");
    out.printf("...");
} catch (FileNotFoundException e) {
}
try(Scanner input = new Scanner(new File("file.txt"))) {
    while(input.hasNext()) {
        System.out.println(input.next());
    }
} catch (FileNotFoundException e) {
}
InputStreamReader streamReader = new InputStreamReader(System.in);
BufferedReader reader = new BufferedReader(streamReader);

String line = reader.readLine();

reader.close();
streamReader.close();
PrintWriter out = new PrintWriter("text.txt");
out.println();
out.flush();
out.close().
Scanner scanner = new Scanner(new File("test.txt"));
while(scanner.hasNextLine()) {
    System.out.println(scanner.nextLine());
}

对象I/O(序列化和反序列化)

ObjectInputStream & ObjectOutputStream 可以读/写可充列化的对象

序列化的作用就是把对象以二进制写入到文件(Save)

反序列化的作用是把二进制文件转换为对象(Load)

什么情况下需要序列化

  1. 当想把内存中的对象状态保存到一个文件中或者数据库中的时候
  2. 当想用套接字在网络上传送对象的时候
  3. 当想通过RMI(远程方法调用)传输对象时

注意

  1. 序列化时,只对对象的状态进行保存,而不管对象的方法
  2. 当一个父类实现序列化,子类自动实现序列化,不需要显式地实现Serializable接口
  3. 当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化

要被序列化的类

class Player implements Serializable {
    private String name;
    // Player()...
    // getter setter other...
}

序列化

Player player = new Player();
FileOutputStream fileOutStream = new FileOutputStream("Player.bin");
ObjectOutputStream objOutStream = new ObjectOutputStream(fileOutStream);
objOutStream.writeObject(player);
objOutStream.flush();
objOutStream.close();
fileOutStream.close();

反序列化

FileInputStream fileInputStream = new FileInputStream("Player.bin");
ObjectInputStream objInStream = new ObjectInputStream();
Object obj = objInStream.readObject();
if (obj instanceof Player) {
    Player serPlayer = (Player)obj;
    // ...
}
objInStream.close();
fileInStream.close();

RandomAccessFile

使用顺序流打开的文件称为顺序访问文件,顺序访问文件的内容不能更新;RandomAccessFile允许在文件的任意位置上进行读写(随机访问文件)

方法 描述
RandomAccessFile(File f, String mode) 使用指定的File对象和模式创建随机文件流 (r/rw)
long getFilePointer() 返回以字节计算的文件偏移量(从文件开始计),作为下一个Reader或Writer的起点
long length() 返回文件中的字节数
setLength(long) 为文件设置新长度
int read() 从文件中读取字节数据
int read(byte[]) 从文件中读取字节数据
void seek(long pos) 设置流的偏移量(以字节为单位)
int skipBytes(int n) 跳过n个字节
write(byte[] b) 将指定的字节数组写入到文件中(从当前的文件指针开始写)

读写本地文件

RandomAccessFile randomAccessFile = new RandomAccessFile("./", "r");
String line = null;
StringBuilder content = new StringBuilder();
while ((line = randomAccessFile.readLine()) ! = null) {
    content.append(line).append(System.getProperty("line.separator"));
}
String text = new String(content.toString().getBytes("iso-8859-1"), "UTF8");

RandomAccessFile randomAccessFile = new RandomAccessFile("./test.txt", "rw");
randomAccessFile.write(text.getBytes());
randomAccessFile.close();

Lambda表达式

Lambda表达式也称为闭包,允许把函数作为一个方法的参数进行传递

语法

param -> expression;
(param1, param2, ...) -> expression;
(param1, param2, ...) -> {
    expression1;
    expression2;
    ...
}
() -> 1024; // 不需要参数,直接返回数值
value -> Math.pow(value, 2); // 接收一个参数,返回2次幂
(num1, num2) -> num1 * num2; // 接收两个参数,返回乘积
(int num1, int num2) -> num2 - num2; // 接收两个整形参数,返回他们的差
(value) -> System.out.println(value); // 接收一个字符串类型,只打印,不返回值

Lambda表达式的几个重要特征

  1. 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值(自动类型推断)
  2. 可选的参数圆括号:如果只有一个参数,可以省略小括号
  3. 可选的大括号:函数主体只包含一条语句可以省略大括号
  4. 可选的返回关键字:如果主体只有一个表达式返回值,编译器会自动返回值,大括号需要指明表达式返回了一个值

Swing

所有组件都是Container的子类

Swing容器

JFrame

常用方法

方法 作用
setDefaultCloseOperation() 设置窗体是否使用默认关闭操作
setAlwaysOnTop() 继承自Window的方法,将窗体置于最顶层
setLocationRelativeTo() 继承自Window的方法,如果参数组件为null,默认置于屏幕中心,否则置于参数组件中心
dispose() 继承自Window的方法,关闭窗体
setVisible 继承自Window的方法,设置窗体是否可见
setSize 继承自Window的方法,设置窗体大小
setTitle 继承自Fram的方法,设置窗体标题
setUndecorated(true) 隐藏标题栏
setResizable(false) 禁止缩放尺寸,同时禁用最大化按钮

JFrame上不能直接放置控件,需要获得内容面板,再在内容面板上再添加其他控件

JFramDemo

JFrame frame = new JFrame("title");
// frame.set...
frame.setVisible(true);

使用继承技术书写JFrame子类

JFrame子类Demo
public class MyFrame extends JFrame {

    public MyFrame() {
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        this.setTitle("title");
        setSize(800, 600);
        setLocationRelativeTo(null);
    }
}

main方法调用

public class AppMain{
    public static void main(String[] args) {
        new MyFrame().setVisiible(true);
    }
}

面板容器

JPanel

方法 说明
JPanel()
JPanel(LayoutManager layout)
void add(Component comp)
void setLayout(LayoutManager layout)

创建JPanel容器的步骤(使用布局浏览器的方法)

创建一个JPanel对象

Jpanel panel = new JPanel();

设置界面布局方式

panel.setLayout(new BorderLayout());

设置容器大小

Dimension dimension = new Dimension(800, 600);
panel.setPreferredSize(dimension);

添加到父容器

frame.getContentPane().add(panel);

创建JPanel容器的步骤(无布局,手动设置控件的边界及大小方法)

Jpanel pnl = new Jpanel();
getContentPane().setLayout(null);
pnl.setBounds(50, 50, 200, 200);    // (x, y, width, height)
getContentPane().add(pnl)

JScrollPane

方法 说明
JScrollPane()
JScrollPane(Component view)
void setVerticalScrollBarPolicy(int const)
void setHorizontalScrollBarPolicy(int const)
创建JScrollPane的步骤

创建滚动面板

JScrollPane scrollPane = new JScrollPane(panel);

设置容器大小

scrollPane.setSize(200, 200);   // (width, height)

设置水平/垂直滚动条约束条件:总是显示/需要时显示,从不显示

scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

添加到父窗口

getContentPane().add(scrollPane);

控件

JTextArea – 多行文本框

方法

JTextField类的方法 描述
JTextArea() 预定义文本输入框
JTextArea(String text) 带默认文字内容的文本输入框
setText(String text) 设置文本内容
getText() 获取输入的文字内容
setLineWrap(boolean b) 设置启用或禁用自动换行
setEditable(boolean b) 设置是否可编辑

JLabel

方法

JLabel类的方法 描述
JLabel(Icon icon) 图标标签
JLabel(String text) 文本标签
JLabel(Icon icon, String text) 带图标的文本标签
setIcon(Icon icon) 设置图标
setText(String text) 设置文本内容
setHorizontalAlignment(int Alignment) 设置文本的水平对齐方式
setVerticalAlignment(int Alignment) 设置文本的垂直对齐方式

Demo

lbl.setFont(new Font("微软雅黑", Font.BOLD, 24));
lbl.setForeground(Color.RED);
lbl.setIcon(new ImageIcon("./images/1.png"))
ImageIcon[] images = new ImageIcon[N];
for(int i = 0; i < images.length; i++) {
    images[i] = new ImageIcon("images/" + i + ".png");
}

Thread thread = new Thread(new UpdateImageTask());
thread.start();

class UpdateImageTask implements Runnable {
    private int index = 1;
    @Override
    public void run() {
        while(true) {
            try {
                if (index <= 0 || index > 10) index = 1;
                Thread.sleep(400);
            } catch (InterruptedException e) {
            }
            lblImage.setIcon(images[index - 1]);
            index++;
        }
    }
}

设置鼠标经过时变成一只手

lbl.setCursor(new Cursor(Cursor.HAND_CURSOR));

设置鼠标经过时变色

lbl.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseEntered(MouseEvent e) {
        // 鼠标经过事件
        lbl.setForeground(Color.RED);
    }
    @Override
    public void mouseExited(MouseEvent e) {
        // 鼠标离开事件
        lbl.setForeground(Color.BLUE);
    }
});

JTextField / JPasswordField

方法

JTextField类的方法 描述
JTextField() 预定义文本输入框
JTextField(String text) 带默认文字内容的文本输入框
setText(String text) 设置文本内容
getText() 获取输入的文字内容
setHorizontalAlignment(int Alignmetn) 设置文本的水平对齐方式
setVerticalAlignmetn(int Alignmetn) 设置文本的垂直对齐方式
setEditable(boolean b) 设置是否可编辑(文本输入框设置不可编辑后,就是文本标签)

Demo

JTextField textField = new JtextField();
getContentPane().add(textField);
textField.setBounds(0, 100, 400, 60);
textField.setHorizontalAlignment(JTextField.LEFT);

JButton

方法

JButton类的方法 描述
JButton() 预定义按钮
JButton(Icon icon) 带默认图标的按钮
JButton(String name) 带默认文字的按钮
setPressedIcon(Icon icon) 设置按下时显示的图标
setRolloverIcon(Icon icon) 设置鼠标悬浮时显示的图标
setEnabled(boolean b) 设置按钮禁用/启用
addAciontListener(ActionListener I) 注册事件监听器

Demo

JButton btn = new Jbutton("New button");
getContentPane().add(btn);

JCheckBox

方法

方法 描述
JCheckBox() 预定义复选框
JCheckBox(String text) 带默认文本标签的复选框
setText(String text) 设置文本内容
setSelected(boolean b) 设置勾选状态
isSelected() 获取勾选状态
setEnabled(boolean b) 设置按钮禁用/启用

Demo

JCheckBox[] chbArray = new JCheckBox[N];
String[] chbTexts = {
    "", "", ""
};
Font defaultFont = new Font("微软雅黑", Font.BOLD, 24);

JPanel contentPanel = (JPanel)getContentPane();
contentPanel.setLayout(null);

Rectangle defaultRect = new Rectangle(50, 50, 120, 30);
for (int i = 0; i < chbArray.length; i++) {
    chbArray[i] = new JCheckBox(chbTexts[]);
    chbArray[i].setFont(defaultFont);
    if (i == 0) {
        chbArray[i].setBounds(defaultRect);
    } else {
        chbArray[i].setBounds(chbArray[i - 1].getX(),
                              chbArray[i - 1].getY() + chbArray[i - 1].getHeight() + 10,
                              chbArray[i - 1].getWidth(),
                              chbArray[i - 1].getHeight()
                             );
    }
    chbArray[i].addMouseListener(new MouseAdapter() {
        @Override
        public void mouseClicked(MouseEvent e) {
            // e.getSource()获得事件源对象
            JCheckBox checkbox = (JCheckBox)e.getSource;
            if (checkbox.isSelected()) {
                // checkbox.getText()
            }
        }
    })
    contentPanel.add(chbArray[i]);
}

JRadioButtion

单选按钮逻辑分组

ButtonGroup group = new GuttonGroup();
group.add(rbArrary[i]);

JCombobox

方法

方法 描述
JComboBox() 预定义单选按钮
JComboBox(ComboBoxModel aModel) 通过数据模型构建下拉菜单
setModel(ComboBoxModel aModel) 设置下拉菜单组件显示的数据模型
setSelectedIndex(int index) 设置选中的下拉菜单项索引
getSelectedIndex() 获取选中的下拉菜单项索引
addItemListener(ItemListener I) 注册下拉菜单选中项事件监听器

为组合框控件添加选择事件

cmb.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // codes...
    }
});

设置组合框的默认选项

cmb.setSelectedIndex();

布局管理器

各组件默认布局

组件 默认布局
Frame BorderLayout
Panel FlowLayout
ScrollPanel BorderLayout

流式布局

方法

方法 说明
FlowLayout() 构造新的中间对齐的FlowLayout,将垂直和水平间距保留为5个像素
setAlignment(int align) 设置指定布局的对齐方式
setHgap(int hgap) 设置指定布局的水平间距
setVgap(int vgap) 设置指定布局的垂直间距

Demo

JPanel contentPane = (Jpanel)getContentPane();
contentPane.setLayout(new FlowLayout());
flowLayout.setAlignment(FlowLayout.LEFT);
contentPane.add(lbl);

边框布局

方法 说明
BorderLayout() 创建新的BorderLayout, 组件之间没有间距
setHgap(int Hgap) 将组件间的水平间距设置为指定的值
setVgap(int vgap) 将组件间的垂直间距设置为指定的值

Demo

getContentPane().setLayout(new BorderLayout(10, 20));
JButton btnNorth = new Jbutton("North");
btnNorth.setPreferredSize(new Dimension(800, 10));
getContentPane().add(, BorderLayout.NORTH);

网格布局

方法

方法 描述
GridLayout() 创建网格布局,默认为每个组件占用一整列和一单行
GridLayout(int rows, int cols) 用指定的行数和列数创建风格布局
GridLayout(int rows, int cols, int hgap, int vgap) 用指定的行数和列数、指定的水平和垂直间距创建网格布局
setColumns(int cols) 将相应布局中的列数设置为指定值
setHgap(int hgap) 将相应布局中的水平间距设置为指定值
setVgap(int vgap) 将相应布局中的垂直间距设置为指定值
setRows(int rows) 将相应布局中的行数设置为指定值

事件

事件源

事件源 描述
按钮 当按下按钮时产生动作事件
复选框 当选中或取消选中复选框时产生条目事件(item)
选项 当选项改变时产生条目事件
列表 当双击某个选项时产生动作事件,当选中或取消选中某个选项时产生条目事件
菜单项 当选中菜单项时产生动作事件,当选中或取消选中某个可复选的菜单项时产生条目事件
滚动条 当操作滚动条时产生调整事件
文本组件 用户输入字符时产生文本事件
窗口 当激活、关闭、冻结、最大化、最小化、打开或退出窗口时产生窗口事件

事件类型

事件类 描述
ActionEvent 当按下按钮、双击某个列表项或选中某个菜单项时产生的事件
AdjustmentEvent 当操作滚动条时产生的事件
ComponentEvent 当组件被隐藏、移动、改变大小或变得可见时产生的事件
ContainerEvent 当组件被添加到窗口中或从窗口中删除时产生的事件
FocusEvent 当组件获得或失去键盘焦点时产生的事件
InputEvent 所有组件输入事件的抽象超类
ItemEvent 当复选框或列表项被单击时产生的事件;当选中某个选择选项、选中或取消选中某个可复选的菜单项时也会产生这种事件
KeyEvent 当从键盘接受输入时产生的事件
MouseEvent 当拖动、移动、单击、按下或释放鼠标时产生的事件;当鼠标进入或退出组件时也会产生这种事件
MouseWheelEvent 当移动鼠标滑轮时产生的事件
TextEvent 当文本域或文本框的值发生变化时产生的事件
WindowEvent 当激活、关闭、冻结、最大化、最小化、打开或退出窗口时产生的事件

监听器

事件监听器接口 描述
ActionListener 定义了一个用于接收动作事件的方法
AdjustmentListener 定义了一个用于接收调整事件的方法
ComponentListener 定义了4个方法,分别用于识别组件何时被隐藏、移动、改变大小或显示
ContainerListener 定义了两个方法,分别用于识别组件何时被添加到窗口中或从窗口中移除
FocusListener 定义了两个方法,分别用于识别组件何时获得或丢失键盘焦点
ItemListener 定义了一个用于识别选项状态何时发生变化的方法
keyListener 定义了一个用于识别何时按键被按下、释放或键入字符的方法
MouseListener 定义了5个方法,分别用于识别鼠标何时被单击、进入组件、退出组件、被按下或被释放
MouseMotionListener 定义了两个方法,分别用于识别鼠标何时被拖动或移动
MouseWheelListener 定义了一个用于识别鼠标滚轮何时被移动的方法
TextListener 定义了一个用于识别何时文本值发生变化的方法
WindowFocusListener 定义了两个方法,分别用于识别何时窗口获得或丢失输入焦点

委托事件模型

一个源可以产生多种类型的事件,必须分别注册每种事件

一个对象也可以注册能接收多种类型的事件,但是为了接收这些事件,必须实现需要的所有接口

使用时需要遵循的步骤

在监听器中实现合适的接口,从而使监听器可以接收其外观类型的事件

实现注册或注销(如果需要的话)监听器的代码,监听器作为事件通知的接收者

JOptionPane

构造方法

JOptionPane();
JOptionPane(Object message);
JOptionPane(Object message, int messageType);
JOptionPane(Object message, int messageType, int optionType);
JOptionPane(Object message, int messageType, int optionType, Icon icon);

方法

方法 描述
void showMessageDialog(Component parenetComponent, Object message) 标题为“消息的对话框”
void showMessageDialog(Component parenetComponent, Object message, String title, int messagetType) 使用由 messageType 参数确定的默认图标来显示信息的消息框
void showMessageDialog(Component parentComponent, Object message, String title, int messageType, Icon icon) 用于显示消息和指定所有参数的消息框
int showOptionDialog(Component parentComponent, Object message, String title, int optionType, int messageType, Icon icon, Object[] options, Object initialValue) 具有指定图标的消息框,其中初始选项由initialValue参数决定,而选项的数目由optionType参数决定
/**
 * @param parentComponent 指定当前对话框的父组件
 * @param message 对话框中显示的消息提示
 * @param title 标题
 * @param optionType 对话框的选项类型
 # @param messageType 消息类型
 * @param icon 图标
 * @param options 选项按钮数组 可以是字符串,也可以是任意的Swing组件
 * @param initialVale 默认选中的选项
 * @result 按了左1按钮返回0 按了2号按钮返回1 按了3号按钮返回 点关闭返回-1
 */
int showOptionDialog(Component parentComponent,
                     Object message,
                     String title,
                     int optionType,
                     int messageType,
                     Icon icon,
                     Object[] options,
                     Object initialValue);
JOptionPane.showOptionDialog(JOptionPaneDemo.this,
                             "消息提示",
                             "title",
                             JOptionPane.DEFAULT_OPTION,
                             JOptionPane.QUESTION_MESSAGE,
                             Icon icon,
                             Object[] options,
                             Object initialValue)

菜单

JMenu

JPopupMenu

创建 JPopupMenu对象

JpopupMenu popupMenu = new JpopupMenu();

创建 JMenuItem 对象并添加其事件

JMenuItem menuItem1 = new JMenuItem("打开");
menuItem1.addActionListener(e -> {
    JFileChooser fileChooser = new JFileChooser();
    fileChooser.showDialog(MyJFram.this, "打开文件...");
});

创建 JSeparator对象

JSeparator menuItem2 = new JSeparator();

创建 JMenuItem 对象并添加其事件

JMenuItem menuItem3 = new JMenuItem("退出");
menuItem3.addActionListener(e -> {
    System.exit(0);
});

向 JPopupMenu中添加对象

popupMenu.add(menuItem1);
popupMenu.add(menuItem2);
popupMenu.add(menuItem3);

将 JPopupMenu中添加到窗体或容器的事件响应中

容器.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent e) {
        // BUTTON1 左键
        // BUTTON2 中键
        // BUTTON3 右键
        if (e.getButton() == MouseEvent.BUTTON3) {
            popupMenu.show(e.getComponent(), e.getX(), e.getY());
        }
    }
})

MVC架构

JTree

树表组件的结点

树表组件的层次结构中,每一行称为一个结点

每个树表组件都有一个根结点,可由这个根结点展开所有结点

结点包含实际的数据

结点的类型

根结点,结点上不包含结点

枝结点:结点上下都包含结点

叶结点:结点下不包含结点

DefaultMutableTreeNode root = new DefaultMutableTreeNode("根结点");
root.add(new DefaultMutableTreeNode("子结点"));

DefaultTreeModel treeModel = new DefaultTreeModel(root);
JTree tree = new JTree();
tree.setModel(treeModel);
getContentPane().add(new JScrollPane(tree))

系统托盘

public class SystemTrayDemo extends JFrame {
    public SystemTrayDemo() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        initComponents();
        setTitle(系统托盘示例);
        setSize(800, 600);
        setLocationRelativeTo(null);
    }

    private void initComponents {
        // 获得系统托盘对象
        SystemTray sysTray = SystemTray.getSystemTray();
        PopupMenu popup = new PopupMenu();
        popMenu.add(new MenuItem("显示/隐藏"));
        popMenu.add(new MenuItem("退出"));
        ImageIcon icon = new ImageIcon("*.png");
        TrayIcon trayIcon = new TrayIcon(icon.getImage(), "系统托盘图标测试", popMenu);
        try {
            sysTray.add(trayIcon);
        } catch(AWTException e) {
        }
    }

    public static void main(String[] args) {
        SystemTrayDemo().setVisible(true);
    }
}

缩放图片

/**
 * 用来获得缩放后的图片
 */
public static ImageIcon ScaleImage(Image image, double scale) {
    int newWidth = (int)image.getWidth(null) * scale;
    int newHeight = (int)image.getHeight(null) * scale;
    Image scaledImage = image.getScaledInstance(newWidth, newHeight, Image.SCALE_DEFAULT);
    return new ImageIcon(scaledImage);
}

多线程

进程(Process)

进程是指一种正在运行的程序,有独立的内存空间和系统资源

线程(Thread)

指一个任务从头至尾的执行流

线程提供了运行一个任务的机制

线程可以理解为进程中执行的程序片段

CPU调度和分派的基本单位

进程中执行运算的最小单位,可以完成一个独立的程序控制流程

进程与线程的区别

进程间是独立的,这表现在内存空间,上下文环境;线程运行在进程空间内。

进程无法突破进程边界存取其他进程内的存储空间(不使用特殊技术);而线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间。

同一进程中的两段代码不能同时执行,除非引入线程。

线程属于进程,当进程退出时该进程所产生的线程都会被强制退出并清除。

线程占用的资源要少于进程所占用的资源。

进程和线程都可以有优先级。

多线程

多线程的基本概念

如果在一个进程中同时运行了多个线程用来完成不同的工作,称为多线程。注意:多个线程交替占用CPU资源,并非真正的并行执行

多线程的好处

资源得用率更好

简化了编程模型

程序响应更快

主线程(Main Thread)

当 Java 应用程序启动时,就会有一个进程被操作系统创建,与此同时一个线程也立即运行,这个线程通常叫做程序的主线程

main()方法是主线程的入口

主线程是可以产生其他子线程的线程

通常必须最后完成执行操作(如各种关闭动作)

java.lang.Thread类

方法 描述
Thread() 创建一个空线程
Thread(task : Runnable) 为指定任务创建一个线程
start() : void 启动线程,使 run() 方法能够被 JVM 调用
isAlive() : boolean 返回当前线程是否正在运行
setPriority(priority : int) : void 设置线程的优先级 (1-10)
join() : void 等待当前线程结束
sleep(millis : long) : void 使线程休眠指定毫秒数
yield() : void 使线程暂停并允许执行其他线程
interrupt() : void 中断线程

Thread实现了Runnable类,可以定义Thread的子类并覆盖run()方法实现自定义的线程类。

不推荐这种方式,因为这将任务和运行任务的机制混在了一起。

任务与线程分离更好的体现了面向对象思想,避免了由Java单继承带来的局限及有得于代码被多个线程共享。

创建线程的方法:继承 Thread

package demos;

/**
 * 使用继承 Thread 的方式实现线程
 * @author wangchichuen
 *
 */
public class ExtendsThreadDemo extends Thread {

    private int num;    // 让这个线程每隔 200 毫秒,就报数并-1, 一直到0

    public ExtendsThreadDemo(int num, String name) {
        this.num = num;
        setName(name);    // 给当前线程起个名字
    }

    @Override
    public void run() {
        // 线程中,必须实现 run 方法,线程开启时,会自动调用 run 方法,不需要程序员手动调用
        while (num >= 0) {
            System.out.println("num = " + num);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num--;
        }
        System.out.println(Thread.currentThread().getName() + "线程执行完毕");
    }
}
package demos;

public class ExtendsThreadDemoMain {

    public static void main(String[] args) {
        ExtendsThreadDemo extendsThreadDemo1 = new ExtendsThreadDemo(150, "报数1");
        ExtendsThreadDemo extendsThreadDemo2 = new ExtendsThreadDemo(50, "报数2");
        // 在主线程中,开启另外一个新建的子线程
        extendsThreadDemo1.start();
        extendsThreadDemo2.start();
        System.out.println(Thread.currentThread().getName() + "线程代码执行完毕,等待子线程执行");
    }

}

创建线程的方法:实现 Runnable

ProgressTask

package demos;

import java.util.concurrent.TimeUnit;

import javax.swing.JProgressBar;

/**
 * 用来更新进度条的任务
 * @author wangchichuen
 *
 */
public class ProgressTask implements Runnable {

    private JProgressBar jProgressBar;
    private boolean isRunning = true;

    public ProgressTask(JProgressBar jProgressBar) {
        this.jProgressBar = jProgressBar;
    }

    public void stop() {
        isRunning = false;
    }

    @Override
    public void run() {
        while (jProgressBar.getValue() <= 100 && isRunning) {
            int oldValue = jProgressBar.getValue();
            jProgressBar.setValue(oldValue + 1);
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

LanterTask

package demos;

import javax.swing.JFrame;

public class LanterTask implements Runnable {

    private JFrame frame = null;
    private String title = null;

    public LanterTask(JFrame frame) {
        this.frame = frame;
        this.title = frame.getTitle();
    }

    @Override
    public void run() {
        StringBuilder newTitle = new StringBuilder(title);
        while (true) {
            newTitle.insert(0, " ");
            if (newTitle.toString().length() == 40) {
                newTitle = new StringBuilder(newTitle.toString().trim());
            }
            frame.setTitle(newTitle.toString());
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }



}

ProcessBarDemo

package demos;

import java.awt.BorderLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;

@SuppressWarnings("serial")
public class ProcessBarDemo extends JFrame {

    private JProgressBar jProgressBar = new JProgressBar();
    private JButton btnStart = new JButton("开启线程");
    private JButton btnPause = new JButton("暂停线程");
    private ProgressTask progressTask = null;
    private LanterTask lanterTask = null;

    public ProcessBarDemo() {
        setTitle("使用多线程控制进度条");
        initComponents();
        initEvents();
        jProgressBar.setValue(30);
        pack();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    private void initComponents() {
        JPanel topPanel = new JPanel();
        topPanel.add(btnStart);
        topPanel.add(btnPause);
        getContentPane().add(topPanel, BorderLayout.SOUTH);
        getContentPane().add(jProgressBar, BorderLayout.CENTER);
        jProgressBar.setStringPainted(true);
    }

    private void initEvents() {

        btnStart.addActionListener(e -> {
            progressTask = new ProgressTask(jProgressBar);
            Thread thread1 = new Thread(progressTask);
            thread1.start();

            lanterTask = new LanterTask(ProcessBarDemo.this);
            new Thread(lanterTask).start();
        });

        btnPause.addActionListener(e -> {
            progressTask.stop();
        });

    }

}

main

package demos;

import javax.swing.SwingUtilities;

public class ProcessBarDemoMain {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                new ProcessBarDemo().setVisible(true);
            }
        });
    }

}

线程的状态

新建

线程被创建

就绪

调用.start()方法后处于就绪状态

运行

由 JVM 自动调用.run()方法后处于运行状态,在运行状态下.yield()或超时后回到就绪状态

阻塞(Blocking)

join()等待其它线程完成后我再执行,即等其它线程执行完成后,这个线程回到就绪状态

sleep()等待超时,超时后回到就绪状态

wait()等待通知,得到信号后回到就绪状态

结束

run()方法完成后

线程优先级

Thread thread = new Thread();
thread.setDaemon(true);

设置某线程为后台线程后,其优先级会更低一些,不会影响前台线程的执行

join() 方法

使当前线程暂停执行,等待其它线程结束后再继续执行本线程

package joindemo;

/**
 * join 方法,阻塞当前线程,等待其它线程执行
 * @author wangchichuen
 *
 */
public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread printThread = new Thread(new PrintNumTask(), "打印线程");
        printThread.start();
        for (int i = 0; i < 20; i++) {
            if (i == 10) {
                printThread.join(); // 等待其它线程执行完毕后这个线程才继续执行
            }
            System.out.println("主线程打印的数字:" + i);
        }
        System.out.println("主线程结束");
    }
}

class PrintNumTask implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "打印的数字:" + i);
        }
    }

}

yield()方法 线程的礼让

暂停当前线程,允许其他具有同等优先级的线程获得运行机会

yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获得执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!

只是提供一种可能,不能保证一定会实现礼让!

调用yield()方法后,本线程给其它线程一个执行的机会,但是这个机会最终也可能是本线程抢到了。

多线程同步的问题

一个同步方法在执行之前需要加锁,对于实例方法,要为调用该方法的对象加锁。

静态方法则是给类加锁

如果一个线程调用一个对象上的同步实例方法(静态方法),首先给该对象(类)加锁,然后执行该方法,最后解锁,解锁前,另一个调用那个对象中方法的线程将被阻塞,直到解锁

同步方法

public synchronized void beAttacked(long attack) {
    if (hp > 0) {
        hp -= attack;
    }
}

同步代码块

public void beAttacked(long attack) {
    synchronized(this) {
        if (hp > 0) {
            hp -= attack;
        }
    }
}

volatile

volatile是一个类型修饰符,指令关键字,作用是确保指令不会因编译器的优化而省略。

保证不同线程对这个变量进行操作时是立即可见的,实现可见性。

volatile只能保证单次读/写的原子性,对复合语句则无效

可以在多线程程序让变量更加安全

等待与通知

Object中定义的方法

可以通过wait()notify()notifyAll()控制线程释放对象的锁定,这三个方法只能在同步方法或者同步代码块中使用

或通知线程参与锁定竞争

方法 描述
final void wait() 调用该方法的线程进入WAITING状态,并将当前线程放置到对象的等待队列,只有等待另外线程的通知或被中断才会返回,需要注意,调用wait()方法后,会释放对象的锁
final void wait(timeout) 超时等待一段时间,这里的参数时间是毫秒,如果没有通知就超时返回
final void wait(timeout, nanos) 对于超时时间更细力度的控制,可以达到纳秒
final void notify() 通知一个在对象上等待的线程,由WAITING状态变为BLOCKING状态,从等待队列移动到同步队列,等待CPU调度获取该对象的锁,当该线程获取到了对象的锁后,该线程从wait()方法返回
final void notifyAll() 通知所有等待在该对象上的线程,由WAITING状态变为BLOCKING状态,等待CPU调度获取该对象的锁

生产者与消费者模型

MySQL

相关资源

官网

MySQL

下载地址

MySQL Community Server 8.0.19

示例数据库

test_db

表 (Table)

一个表可包含若干字段

数据库类型

纯文本数据/数据岛(XML)

关系型数据库

关系型数据库模型是把复杂的数据结构归结为简单的二元关系,即二维表格形式。

在关系型数据库中,对数据的操作几乎全部建立在一个或多个关系表格上,通过对这些关联的表格分类、合并、连接或选取等运算来实现对数据库的管理。

非关系型数据库 (NoSQL)

Not Only SQL, 意即“不仅仅是SQL”,适合于超大规模和高并发场景,在特定场景下可以发挥出难以想象的高效率和高性能,是对传统关系型数据库的一个有效补充。

SQL语言

Structured Query Language

组成

数据定义语言(DDL)

用来定义和管理数据对象,如数据库、表等

create, drop, alter

数据操作语言(DML)

用于操作数据库对象中包含的数据

insert, update, delete

数据查询语言(DQL)

select

数据控制语言(DCL)

grant, commit, rollback

安装MySQL

添加data文件夹

添加配置文件

my.ini,使用UTF-8编码

[mysqld]
# 设置服务端口为3306
port=3306
# 设置mysql的安装目录,注意目录需要使用\\连接
basedir=C:\\Program Files\\mysql-8.0.19-winx64
# 设置mysql数据库的数据的存放目录,注意目录需要使用\\连接
datadir=C:\\Program Files\\mysql-8.0.19-winx64\\data
# 允许最大连接数
max_connections=200
# 允许连接失败的次数。这是为了防止有人从该主机试图攻击数据库系统
max_connect_errors=10
# 服务端使用的字符集默认为UTF8
character-set-server=utf8mb4
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB

# secure_file_priv=null 表示不允许导入导出
# secure_file_priv=指定文件夹 表示mysql的导入导出只能发生在指定的文件夹
# secure_file_priv没有设置时 表示没有任何限制
# 默认使用“mysql_native_password”插件认证

default_authentication_plugin=mysql_native_password
[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8mb4
[client]
# 设置mysql客户端连接服务端时默认使用的端口
port=3306
default-character-set=utf8mb4

配置环境变量

MYSQL_HOME

path = %MYSQL_HOME%\bin;path

以管理员身份运行cmd

runas /user:administrator "cmd/k"

初始化mysql

mysqld --initiallize --console

要记录初始化后的管理员登录密码

注册mysql服务

mysqld --install MySQL8

删除服务

sc delete MySql8

启动mysql服务

net start MySql8

停止mysql服务

net stop MySQL8

登录mysql

mysql -h localhost -u root -p

修改密码

5.7及以下:
set password for root@localhost = password('新密码');
8.x以上
alter user 'root'@'localhost' identified with mysql_native_password by 'Aa123456';

数据库操作

显示当前所有数据库

show databases;

创建数据库

create database `MyDataBase`;

删除数据库

drop database `MyDataBase`;

使用数据库

use `MyDataBase`;

可视化工具

WorkBench

workbench

数据类型

如果添加的数据精度高于定义的精度,系统会自动四舍五入。

unsigned属性

zerofill属性

如果某数值字段指定了zerofill属性,将自动添加unsigned属性

字符串、日期类型

字符串

字符串类型 说明 长度
char[(M)] 定长字符串 M字节
varchar[(M)] 可变字符串 可变长度

其它不常用的:tinytext, text

日期类型

日期类型 格式
datetime YY-MM-DD hh:mm:ss:
timestamp YYYYMMDDHHMMSS

其它不常用的:date, time, year

完整性约束

约束条件 说明 完整性类型
primary key 主键,一行数据的唯一标识 实体完整性
foreign key 外键,关联另外一张表的主键 引用完整性
not null 非空约束,字段不允许为空 域完整性
unique 唯一约束,设置字段的值是唯一的,允许为空,但只能有一个空值 域完整性
auto_increment 自增列,一般用作主键 实体完整性
default 赋予某字段默认值 域完整性
check 检查约束,用户自定义约束条件 域完整性

DOS窗口乱码

避免DOS窗口乱码,可执行set names gbkset names utf-8,显示字段属性默认值里的乱码改不了。

表操作

创建表

create table [if not exists] `表名`(
    `字段名` 数据类型 [字段属性] [完整性约束条件] [索引] [注释],
    ...
    `字段名n` 数据类型 [字段属性] [完整性约束条件] [索引] [注释],
)[表类型] [表字符集] [注释];

利用已有表创建新表

create table AddressBook(
    select StuName as newStuName, LoginPwd as newLoginPwd from Students
);

as符号为字段起新名,可以省略用旧名字。

删除表

drop table [if exists] 表名;

显示当前数据库中所有的表

show tables;

显示表的描述

describe users;

修改表

修改表名

alter table 旧表名 rename [to] 新表名;

增加字段

alter table 表名 add 字段名 数据类型 [属性];

修改字段

alter table 表名 change 原字段名 新字段名 数据类型 [属性];

修改字段约束

alter table `Score` modify column Score float comment '将成绩列修改为可以为空';

删除字段

alter table 表名 drop 字段名;

添加主键

alter table 表名 add constraint 约束名 primary key 表名 (主键字段);

添加外键

alter table 表名 add constraint 外键名 foreign key (外键字段) references 关联表名 (关联字段);

外键值可以为null

外键字段去想用一张表的某个字段的时候,被引用的字段必须具有unique约束

有了外键引用之后,表分为父表(被引用的表)和子表

创建时先创建父表(被引用的表)

删除时先删除子表数据(引用表)

插入时先插入父表数据(被引用的表)

删除外键关系

alter table `表名Score` drop foreign key `外键名`;

建表建字段建约束示例代码-(学生表,成绩表,年级表,学科表)

show global variables like '%character_set%';
show databases;
drop database if exists mydatabasee;
create database mydatabase;
use mydatabase;
show tables;
drop table if exists `Students`;
create table `Students`(
    `StuId` int primary key auto_increment comment '学生学号',
    `StuName` varchar(50) not null comment '姓名',
    `LoginPwd` varchar(50) not null comment '密码',
    `Sex` char(2) not null default('男') comment '性别',
    `GradeId` int unsigned comment '年级编号,外键,对应年级表中的年级id',
    `Mobile` varchar(50) comment '电话',
    `Address` varchar(255) default('地址未详') comment '地址',
    `Email` varchar(50) comment '邮件账号',
    `IdentityCard` char(18) unique comment '身份证号',
    `CreateDate` datetime default(now()) comment '录入日期'
)charset=utf8mb4 engine=InnoDB comment '学生信息表';
describe `Students`;
select * from `Students`;

insert into `Students` values(default, '杨幂', '123456', '女', 1, '12345678901', '北京·朝阳', 'yangmi@163.com', '123456', default);

create table if not exists `Grade`(
    `GradeId` int primary key auto_increment comment '年级编号',
    `GradeName` varchar(50) comment '年级名称'
)charset=utf8mb4 engine=InnoDB comment '年级表';
alter table `Grade` add `NickName` varchar(20) default('年级别称');
alter table `Grade` change `NickName` `NewNick` varchar(20) default('修改后的默认别称');
alter table `Grade` drop `NewNick`;
describe `Grade`;
select * from `Grade`;

create table if not exists `Subject`(
    `SubjectId` int primary key auto_increment comment '课程编号',
    `SubjectName` varchar(50) comment '课程名称',
    `CourseHours` int comment '学时',
    `GradeId` int comment '年级编号'
)charset=utf8mb4 engine=InnoDB comment '学科表';
describe `Subject`;
select * from `Subject`;

drop table if exists `Scores`;
drop table if exists `Score`;
create table if not exists `Score`(
    `ScoreId` int primary key auto_increment comment '主键',
    `StuId` int not null comment '学生学号[Students]-(StuId)',
    `SubjectId` int not null comment '课程编号[Subject]-(SubjectId)',
    `ExamDate` datetime not null comment '考试日期',
    `score` float not null comment '考试成绩',
    foreign key(StuId) references `Students`(`StuId`)
)charset=utf8mb4 engine=InnoDB comment '成绩表';
alter table `Score` add constraint `fk_subjectid` 
foreign key(`SubjectId`) references `Subject`(`SubjectId`);
alter table `Score` auto_increment = 10;
describe `Score`;
select * from `Score`;

insert into `Score` value (default, 1, 1, now(), 125);
# alter table `Score` rename to `Scores`;

DML DQL

存储引擎

显示当前数据库所支持的所有存储引擎

show engines;

查看数据库默认使用的引擎

show variables like '%storage_engine%';

修改表的存储引擎

alter table `表名` engine = 引擎类型;

查看某个表当前使用的存储引擎

select * from information_schema.tables`; # 查询结果为所有表的所有数据
select * from information_schema.tables where Table_Name=`要查询的表名`; # 查询结果为要查询的表的所有数据
# 查询结果为要查询的表的engine
select engine from information_schema.tables where Table_Name=`要查询的表名`;
# 指定数据库
select engine from information_schema.tables where Table_SCHEMA='mydatabase' and Table_Name=`要查询的表名`;

插入数据-单条

insert into 表名[(字段1, 字段2, ... 字段n)] values(值1, 值2, ... 值n);

字段名是可选的,如省略则依次插入所有字段

多个列表和多个值之间使用逗号分隔

值列表和字段名列表一一对应

如插入的是表中部分数据,字段名列表必填

插入数据-多条

insert into 新表名 (字段1, 字段2, ... 字段n)
values
(值1, 值2, ... 值n),
(值1, 值2, ... 值n),
...;

更新

update 表名
set 字段1 = 值1, 字段2 = 值2, ..., 字段n = 值n
[where 条件]

删除

delete from 表名 [where 条件]

删除数据时要当心不要忘记where子句

delete from Students;                   # 删除Students表中的所有数据
delete from Students where Sex = '男';    # 删除所有男生
truncate table Students;    # 清空表中数据,自增计数归零

sql 中的运算符

比较运算符

=, <>, !=, >, >=, <, <=

<>, != 都是不等,但是一般用<>更通用些

布尔运算符

not, and, or

操作符

like, between-and, is null

between 数字/日期 and 数字/日期
# 包含两边的数字/日期

通配符

%匹配零个或多个字符

_匹配任意单个字符

查询数据-简单查询

select 列名|表达式|函数|常量
from   表名
[where 查询条件表达式]
[order by 排序的列名[ASC或DESC]];

查询会产生一个虚拟表

看到的是表形式显示的结果,但结果并不真正存储

每次执行查询只是从数据表中提取数据,并按照表的形式显示出来

select StuId, StuName, Sex, Address
from StuInfo
where Gradeld between 2 and 4
order by StuId desc;
= * from StuInfo
where StuName not like '张_%';
select StuId as 学号, StuName as 姓名
from StuInfo
where Address  '河北廊坊';

查询时将两个字段合并成一个新字段

select concat(StuId, '-', StuName) as 新名称 from StuInfo;

如果是合并两个字符串列要用concat,不能使用+,如果是数字列用+号的效果就是数值相加。

常用函数-聚合函数

内置函数 作用
AVG() 返回某字段的平均值
COUNT() 返回某字段的行数(非空)
MAX() 返回某字段的最大值
MIN() 返回某字段的最小值
SUM() 返回某字段的和
select avg(StuId) as 平均值 from Students

如果在聚合函数帝出现普通列,那么必须在后面进行group by分组

常用函数-字符串函数

函数 作用 示例
CONCAT(str1, str1…strn) 字符串连接 select concat(‘My’, ‘s’, ‘QL’); # => MySQL
INSERT(srt, pos, len, newstr) 字符串替换 select insert(‘这是Oracle数据库’, 3, 8, ‘MySQL’); # => 这是MySQL数据库
LOWER(str) 将字符串转为小写 select lower(‘MySQL’); # => mysql;
UPPER(str) 将字符串转为大写 select upper(‘MySQL’); # => MYSQL
SUBSTRING(str, pos, len) 字符串截取 select substring(‘莫只顾埋头赶路,还要抬头看路。’, 4, 4); # 埋头赶路

常用函数-时间日期函数

函数 作用 示例
CURDATE 获取当前日期 SELECT CURDATE();
CURTIME() 获取当前时间 SELECT CURTIME();
NOW() 获取当前日期和时间 SELECT NOW();
WEEK(date) 返回日期date为一年中的第几周 SELECT WEEK(NOW());
YEAR(date) 返回日期date的年份 SELECT YEAR(NOW());
HOUR(time) 返回时间time的小时值 SELECT HOUR(NOW());
MINUTE(time) 返回时间time的分钟值 SELECT MINUTE(NOW());
DATEDIFF(date 1, date2) 返回日期参数date1和date2之间相隔的天数 SELECT DATEDIFF(NOW(), ‘1949-10-1’);
ADDDATE(date, n) 计算日期参数date加上n天后的日期 SELECT ADDDATE(‘1949-10-1’, 5)

计算时间差

select datediff( now(), '1949-10-1'); # 间隔天数-前面的日期减后面的日期
select timestampdiff(hour, '1949-10-1', now()); # 后面的减前面的
select timestampdiff(month, '1949-10-1', now());
select timestampdiff(year, '1949-10-1', now());

日期计算函数

select adddate('1949-10-1', 92); # 加天数
select date_add('1949-10-1', interval '-30' day);

字符串转日期

select str_to_date('19950303', '%Y%m%d');

常用函数-数学函数

函数名 作用 示例
CEIL(x) 返回大于或等于数值x的最小整数 select ceil(2.4)
FLOOR(x) 返回小于或等于数值x的最大整数 select floor(2.4)
RAND() 返回0~1之间的随机数 select rand()

LIMIT子句

limit子句的作用是截取结果集中的n条数据

使用limit子句时,第1条记录的位置是0

语法

select <字段名列表>
from <表名或视图>
[where <查询条件>]
[group by <分组的字段名>]
[order by <排序的列名>[asc 或 desc]]
[limit [位置偏移量,]行数];

示例

select StuId, StuName, Mobile, Address, BornDate
from StuInfo
where GradeId = 1
order by StuId desc
limit 4; # 显示前4条记录
# limit 4, 4 #从第5条开始显示4条

子查询

select * from StuInfo
where LoginPwd = (select LoginPwd from StuInfo where StuName = '迪丽热吧');

先执行子查询,返回所有来自子查询的结果;再执行外围的父查询,返回查询的最终结果

将子查询和比较运算符联合使用时,必须保证子查询返回的值不能多于一个

学用in替换=号,in后面的子查询可以返回多条记录

Exists子查询

子查询有返回结果:exists子查询结果为true

子查询无返回结果:exists子查询结果为false,外层查询将不执行

# 查看所有参加“思想道德修养与法律基础”的考试的学生信息

# 方法一、
# step 1: 根据科目查询科目的id
select SubjectId from `Subject` where subjectName = '思想道德修养与法律基础';
# step 2: 查询出参加思修考试的学生id
select StuId from score where SubjectId = (select SubjectId from `Subject` where subjectName = '思想道德修养与法律基础');
# step 3: 根据学生id查找学生的详细信息
select * from students where StuId in (select StuId from score where SubjectId = (select SubjectId from `Subject` where subjectName = '思想道德修养与法律基础'));

# 方法二、
select * from students where exists (
    select StuId from score where stuId = students.stuid and SubjectId = (select SubjectId from `Subject` where subjectName = '思想道德修养与法律基础')
);

分组查询

select  from 
[where 条件]
[group by 列1,列2,列3... 列n]
[having 分组条件]

在分组查询中,select后出现的列一定要参与分组

select 中的列名列表只能包含:

被分组的列

为每个分组返回一个值的表达式,如聚合函数

# 统计每门课程平均分各是多少
# 经验:用来分组的列,一般出现在select的后面,聚合函数一般也会出现在select后面
# 
select subjectid, avg(score) as 平均分, sum(score) as 总分, max(score) as 最高分 from score group by subjectid;

# 统计每门课程平均分并显示课程名称
select (select subjectname from `subject` where subjectid = score.subjectId) as 学科名称,
avg(score) as 平均分, sum(score) as 总分, max(score) as 最高分 
from score
group by subjectid;

# 统计每门课程平均分并四舍五入,保留1位小数
select (select subjectname from `subject` where subjectid = score.subjectId) as 学科名称,
round(avg(score), 1) as 平均分,
sum(score) as 总分, max(score) as 最高分 
from score
group by subjectid;

# 统计指定那天参加考试的每门课程平均分
select subjectid, avg(score) as 平均分, sum(score) as 总分, max(score) as 最高分, count(score) as 参考人数 from score where ExamDate = '2020-05-05 00:00:00'
group by subjectid;

# 查询参考人数在4人以上的学科
select subjectid, avg(score) as 平均分, sum(score) as 总分, max(score) as 最高分, count(score) as 参考人数 from score
group by subjectid
having count(score) &gt;= 4; # 等价写法 having: 参考人数 &gt;= 4;

多列分组

# 分别统计每个年级男、女生的人数
select (select gradename from grade where gradeid=students.gradeid) as 年级, sex, count(*) as 人数 from students group by gradeid, sex order by gradeid asc;

分组筛选

# 筛选平均分大于85分的科目
select subjectid, round(avg(score), 1) as 平均分 from score group by subjectid having 平均分 &gt;= 85 order by 平均分 asc;

各子句作用

where 子句:用来筛选from子句中指定的操作产生的行

group by 子句:用来分组where子句的输出

having 子句:用来从分组的结果中筛选行

多表连接查询

常见的连接类型

连接类型 语法
内连接 inner join .. on
左外连接 left join … on / left outer join on
右外连接 right join … on / right outer join on
全连接 union / union all

需要从多个表中选择或者比较数据项的情况,就需要使用到多表连接查询

多表连接查询实际上是通过各个表之间共同列的关联性来查询数据的,它是关系数据库查询最主要的特征

内联接

两个表的交集

语法

select ...
from 表1
inner join 表2
on ...
select ...
from 表1, 表2
where ...
# 内连接
select * from students;
select * from score;

select * from students
inner join score
on students.stuid = score.stuid;

select students.stuid, stuname, gradeName, subjectName, score from students
inner join score        on (students.stuid = score.stuid)
inner join grade        on (students.gradeid = grade.gradeid)
inner join `subject`    on (score.subjectid = `Subject`.subjectid);

select students.stuid, stuname, gradeName, subjectName, score from students, score, grade, `subject`
where students.stuid = score.stuid and students.gradeid = grade.gradeid and score.`subjectId` = `subject`.subjectid;

左外联接

语法

select ... from 左表
left join 右表
on ...

左表的记录将会全部显示出来

右表只会显示符合搜索条件的记录

右表不足的地方均为NULL

# 查询从来参加过考试的学生
select stuid, stuname from (
    select students.stuid, stuname, score.score from students
    left join score
    on students.stuid = score.stuid
) as temp
where score is null;

右外连接

语法

select ... from 右表
right join 左表
on ...

左表只会显示符合搜索条件的记录

右表的记录将会全部显示出来

左表不足的地方均为null

全连接

语法

select 列1, 列2, 列3, ..., 列n from 表1
union / union all
select 列1, 列2, 列3, ..., 列n from 表2

通过union连接的语句,它们分别取出的列数必须相同

如果不要求合并的列名相同,则以第一条语句中的列名为准

使用union 时,单个子句不用写order by, 因为不会有排序效果,但可以对最终的结果集进行排序

union 和 union all 的区别

union 完全相等的行会自动合并,合并比较耗时

union all 联合结果集时不会自动合并操作,所以比较省时

JDBC

JDBC API 中的4个主要接口

接口 作用
java.sql.Driver 数据库连接驱动接口,由 DriverManager 类根据数据库的不同管理对应的驱动程序
java.sql.Connection 负责连接数据库并承担传送数据的任务
java.sql.Statement 由 Connection 产生,负责执行 SQL 语句
java.sql.ResultSet 负责保存Statement扫行后产生的查询结果(行和列的方式)

连接到数据库的一般步骤

// step 1: 加载JDBC驱动
Class.forName(JDBC驱动类);
// step 2: 与数据库建立连接
Connection connection = DriverManager.getConnection(URL, 用户名, 密码);
// step 3: 发送sql 语句并得到返回结果
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(查询语句);
// step 4: 处理返回的结果-操作结果集;
// step 5: 释放资源;

连接驱动

驱动程序是一个实现接口java.sql.Driver的具体类

如果访问不同的数据库,必须加载他们各自的驱动程序

数据库 驱动程序类 来源
MySQL com.mysql.jdbc.Driver
/com.mysql.cj.jdbc.Driver(MySQL Connector 6.0以上版本使用)
mysql-connector-java-x.x.x.jar
Oracle oracle.jdbc.OracleDriver ojdbcx.jar
Access sun.jdbc.odbc.jdbcOdbcDriver JDK内置

URL

连接到数据库

Connection connection = DriverManager.getConnection(URL, 用户名, 密码);

URL是数据库在Internet上的唯一标识符

数据库 驱动程序类
MySQL jdbc:mysql://localhost:3306/数据库名称 (MySQL 8.0版本之前)
jdbc:mysql://localhost:3306/StuDB?useUnicode=true&characterEncoding=utf8&useSSL=false&severTimezone=GMT%2B8 (MySQL 8.0)
Oracle jdbc:oracle:thin:@localhost:1521:数据库的SID
Access jdbc:odbc:数据源名称

MySQL连接URL与之前版本最大的区别就是需要添加时区的设置

&serverTimezone=GMT%2B8 表示东八区,也可以书写为:&serverTimezone=Asia/Shanghai

Statement 常用方法

方法名 作用
int executeUpdate(String sql) 可以执行插入、删除、更新等语句,或者是不返回任何内容的SQL语句如DDL返回值是执行该操作所影响的行数
boolean execute(String sql) 返回值为 true 时表示执行的是查询语句,可以通过 getResultSet() 获得结果集;返回值为 false 时执行的是更新语句或 DDL 语句
int getUpdateCount() 获取更新的记录数量
ResultSet executeQuery(String sql) 执行 SQL 查询语句并返回 ResultSet 结果集对象

PreparedStatement

安全漏洞

String user = "abc' or true or '1";
String logPwd = "abcdefg";  // 任意的密码
String sql = String.format("select * from StuInfo where StuName = '%s' and LoginPwd = %s';", ser, loginPwd);
// abc' or true or '1
// select * from Students where StuName = 'abc' or true or '1' and LoginPwd = '123';

解决方案

使用PreparedStatement创建参数化的SQL语句

String sql = "select * from StuInfo where StuName = ? and LoginPwd = ?;";
PreparedStatement preparedStatement = connection.preparedStatement(sql);
preparedStatement.setString(1, "易烊千玺");
preparedStatement.setString("123456");

示例

V1

private static void loginV1() {
    String userName;
    String password;
    try (Scanner scanner = new Scanner(System.in)) {
        System.out.print("请输入用户名:");
        userName = scanner.nextLine();
        System.out.print("请输入密码:");
        password = scanner.nextLine();
    }

    String sql = String.format("select * from Students where StuName = '%s' and LoginPwd = '%s';",
            userName, password
    );
    Connection connection = JDBCUtil.GetConnection();
    Statement statement = null;
    ResultSet resultSet = null;
    try {
        statement = connection.createStatement();
        resultSet = statement.executeQuery(sql);
        if (resultSet.next()) {
            System.out.println("登陆成功,身份合法!");
        } else {
            System.out.println("登陆失败");
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        JDBCUtil.Close(connection, null, resultSet, null);
    }
    System.out.println(sql);
}

V2

private static void loginV2() {
    Scanner scanner = new Scanner(System.in);
    System.out.print("请输入用户名:");
    String userName = scanner.nextLine();
    System.out.print("请输入密码:");
    String password = scanner.nextLine();

    String sql = "select * from Students where StuName = ? and LoginPwd = ?";
    Connection connection = JDBCUtil.GetConnection();
    Statement statement = null;
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
    try {
        preparedStatement = connection.prepareStatement(sql);
        // 为每个问号参数赋值,索引从1开始
        preparedStatement.setString(1, userName);
        preparedStatement.setString(2, password);
        // 执行 SQL 语句
        resultSet = preparedStatement.executeQuery();
        if (resultSet.next()) {
            System.out.println("登陆成功,身份合法!");
        } else {
            System.out.println("登陆失败");
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        JDBCUtil.Close(connection, null, resultSet, preparedStatement);
    }
    System.out.println(sql);
}

ResultSet 常用方法

方法名 作用
boolean next() 将游标从当前位置向下移动一行
boolean previous() 将游标从当前位置向上移动一行
int getInt(int colIndex) 以整型方式获取结果集中指定列号的值
int getInt(String colLabel) 以整型方式获取结果集中指定列名的值
String getString(int) 以字符串方式获取结果集中指定列号的值
String getString(String) 以字符串方式获取结果集中指定列名的值
Object getObject(int) 以对象方式获取结果集中指定列号的值
Object getObject(String) 以对象方式获取结果集中指定列名的值

ResultSet的一条记录的索引位置是从1开始的,而不是从0开始

实际开发中,经常把 ResultSet 中的内容储存在对象集合中

while (rs.next()) {
    Student stu = new Student();
    stu.setId(rs.getInt("StuId"));
    stu.setStuName(rs.getString("StuName"));
    stuList.add(stu);
}

JDBCDemo

package jdbcdemo;

import java.sql.*;

public class JdbcDemo {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        // 1. 加载连接驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        // 2. 通过驱动管理器连接到数据库,并获得活动连接对象
        String url = "jdbc:mysql://127.0.0.1:3306/Studb?useUcicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai";    // serverTimezone=GMT%2B8
        // GMT%2B8即GMT+8,表示东八区,%2B表示+号
        String userName = "root";
        String password = "CCWang19920508";
        Connection connection = DriverManager.getConnection(url, userName, password);
        // connection.setCatalog("studb"); // 设置当前连接到的数据库(相当于SQL语句:use StuDB)
        // 3. 为了执行sql语句,需要通过连接对象创建语句对象
        Statement statement = connection.createStatement();
        // 4. 执行sql语句
        String sql = "select * from Students;";
        ResultSet resultSet = statement.executeQuery(sql);
        // 5. 操作结果集的固定方式
        // resultSet.next();   // 1、将游标往下移动一行; 2、返回当前行是否有数据,有数据返回真,无数据返回假
        while (resultSet.next()) {
            // 读取某行数据-列的下标从1开始
            String result = String.format("%d\t%s\t%s\t%s",
                    resultSet.getInt(1),    // 列的下标从1开始
                    resultSet.getString("StuName"),
                    resultSet.getObject("LoginPwd"),
                    resultSet.getObject("Sex")
            );
            System.out.println(result);
        }
        // 6. 关闭资源
        resultSet.close();
        statement.close();
        connection.close();
    }
}

JDBC 封装

./resources/dbconfig/DataBaseConfig.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/StuDB?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai&amp;useSSL=false
jdbc.username=root
jdbc.password=CCWang19920508

JDBCUtil

package org.jdbcdemo.util;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

/**
 * 封装了 JDBC 中的常用操作
 */
public class JDBCUtil {
    private static Properties properties = new Properties();
    private static final String DATA_BASE_CONFIG = "dbconfig/DataBaseConfig.properties";

    static {
        // 加载配置文件
        InputStream inStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(DATA_BASE_CONFIG);
        try {
            properties.load(inStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 加载连接驱动
        try {
            Class.forName(properties.getProperty("jdbc.driver"));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据配置文件中的相关参数获得数据库连接对象
     * @return 数据库连接对象
     */
    public static Connection GetConnection() {
        String url = properties.getProperty("jdbc.url");
        String user = properties.getProperty("jdbc.user");
        String password = properties.getProperty("jdbc.password");
        try {
            return DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 执行 sql 语句(更新:插入操作)
     * @param sql
     * @return
     */
    public static int ExecuteUpdate(String sql) {
        Connection connection = GetConnection();
        Statement statement = null;
        try {
            statement = connection.createStatement();
            return statement.executeUpdate(sql);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            Close(connection, statement, null, null);
        }
        return 0;
    }

    /**
     * 批量执行 sql 语句
     * @param sqlBatch
     * @return
     */
    public static int[] ExecuteBatch(String[] sqlBatch) {
        Connection connection = GetConnection();
        Statement statement = null;
        try {
            statement = connection.createStatement();
            for (String sql : sqlBatch) {
                statement.addBatch(sql);
            }
            return statement.executeBatch();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            Close(connection, statement, null, null);
        }
        return null;
    }

    /**
     * 关闭资源
     * @param connection
     * @param statement
     * @param resultSet
     */
    public static void Close(Connection connection, Statement statement, ResultSet resultSet, PreparedStatement preparedStatement) {
        try {
            if (null != resultSet) resultSet.close();
            if (null != statement) statement.close();
            if (null != preparedStatement) preparedStatement.close();
            if (null != connection) connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

Test

package org.jdbcdemo.util;

import org.junit.After;
import org.junit.Test;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;

import static org.junit.Assert.*;

public class JDBCUtilTest {

    @Test
    public void TestQueryAll() {
        Connection connection = JDBCUtil.GetConnection();
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            statement = connection.createStatement();
            resultSet = statement.executeQuery("select * from Students;");
            while (resultSet.next()) {
                System.out.println(resultSet.getInt(1) + resultSet.getString(2));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtil.Close(connection, statement, resultSet, null);
        }
    }

    @Test
    public void testExecuteQuery() {
        String sql = "select * from Students;";
        Connection connection = JDBCUtil.GetConnection();
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            statement = connection.createStatement();
            resultSet = statement.executeQuery(sql);
            while (resultSet.next()) {
                System.out.println(resultSet.getInt(1) + ", " + resultSet.getString(2));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtil.Close(connection, statement, resultSet, null);
        }
    }

    /**
     * 封装前
     */
    @Test
    public void testInsertNew() {
        String insertSql = String.format("insert into Students values (default, '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', now())",
                "王智全", "chichuen", "男", 5, "12345678900", "陕西三原", "wangchichuen@sina.com", "610422199309083920"
        );
        Connection connection = JDBCUtil.GetConnection();
        Statement statement = null;
        int effectCount = -1;
        try {
            statement = connection.createStatement();
            effectCount = statement.executeUpdate(insertSql);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtil.Close(connection, statement, null, null);
        }
        if (effectCount == -1) {
            System.out.println("执行失败!");
        } else if (effectCount == 0){
            System.out.println("插入失败!");
        } else {
            System.out.println("插入成功!" + effectCount + "行受影响");
        }
    }

    /**
     * 封装后
     */
    public void testInsertNewV2() {
        String insertSql = String.format("insert into Students values (default, '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', now())",
                "王智全", "chichuen", "男", 5, "12345678900", "陕西三原", "wangchichuen@sina.com", "610422199309083920"
        );
        int effectCount = JDBCUtil.ExecuteUpdate(insertSql);
        if (effectCount == -1) {
            System.out.println("执行失败!");
        } else if (effectCount == 0){
            System.out.println("插入失败!");
        } else {
            System.out.println("插入成功!" + effectCount + "行受影响");
        }
    }

    /**
     * 批量执行 sql 语句,<em>不支持查询语句</em>
     */
    @Test
    public void testExecuteBatch() {
        Connection connection = JDBCUtil.GetConnection();
        Statement statement = null;
        String[] sqlBatch = {
                "insert into `Grade1` values (default, '大一')",
                "insert into `Grade1` values (default, '大二')",
                "insert into `Grade1` values (default, '大三')",
                "insert into `Grade1` values (default, '研一')",
                "insert into `Grade1` values (default, '研二')",
                "insert into `Grade1` values (default, '研三')"
        };
        int[] effectCounts = null;
        try {
            statement = connection.createStatement();
            for (String sql : sqlBatch) {
                statement.addBatch(sql);
            }
            effectCounts = statement.executeBatch();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtil.Close(connection, statement, null, null);
        }
        System.out.println("影响的行数:" + Arrays.toString(effectCounts));
    }

}

数据库元数据

可以用来获取数据URL、用户名、驱动程序等数据库范围的信息

package org.jdbcdemo;

import org.jdbcdemo.util.JDBCUtil;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

/**
 * 数据库元数据
 */
public class DataMetaDataDemo {
    public static void main(String[] args) throws SQLException {
        Connection connection = JDBCUtil.GetConnection();
        // 通过活动连接获得数据库的元数据
        DatabaseMetaData databaseMetaData = connection.getMetaData();
        String url = databaseMetaData.getURL();
        String userName = databaseMetaData.getUserName();
        String driverName = databaseMetaData.getDriverName();
        String driverVersion = databaseMetaData.getDriverVersion();
        String databaseProductName = databaseMetaData.getDatabaseProductName();
        System.out.println("url: " + url);
        System.out.println("user name: " + userName);
        System.out.println("driver name: " + driverName);
        System.out.println("driver version: " + driverVersion);
        System.out.println("database product name: " + databaseProductName);

        // catalog: 表所在的类别名称(表所在的数据库名称)
        // schemaPattern: 表所在的模式名称
        // tableNamePattern: 我们要获得表名的格式符 'abc%'
        // types: 获得表的类型
        ResultSet tables = databaseMetaData.getTables("studb", null, null, new String[] {"TABLE", "VIEW"});
        List results = new ArrayList();
        while (tables.next()) {
            String tableCate = tables.getString("TABLE_CAT");   // 表的类别 可以为 null
            String tableSchem = tables.getString("TABLE_SCHEM");    // 表的模式(在 Oracle 中是表的命名空间)
            String tableName = tables.getString("TABLE_NAME");  // 表名
            String tableType = tables.getString("TABLE_TYPE");  // 表的类型
            String remarks = tables.getString("REMARKS");   // 表的注释
            StringBuilder result = new StringBuilder();
            result.append(tableCate).append(", ").append(tableSchem).append(", ").append(tableName).append(", ").append(tableType).append(", ").append(remarks);
            results.add(result.toString());
        }
        ListIterator listIterator = results.listIterator();
        while (listIterator.hasNext()) {
            System.out.println(listIterator.next());
        }
    }
}

JavaFX显示数据库元数据

package org.jdbcdemo;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
import org.jdbcdemo.util.JDBCUtil;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;

public class DataBaseViewer extends Application {

    private ComboBox cmbDataBase = new ComboBox();
    private ComboBox cmbTables = new ComboBox();

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        FlowPane root = new FlowPane();
        root.setAlignment(Pos.CENTER);
        root.getChildren().addAll(cmbDataBase, cmbTables);
        Scene scene = new Scene(root, 400, 200);
        primaryStage.setScene(scene);
        primaryStage.setTitle("显示当前连接下的所有数据库及对应的表");
        primaryStage.show();
        initEvents();
    }

    private void initEvents() {
        cmbDataBase.setValue("所有数据库");
        try (Connection connection = JDBCUtil.GetConnection()) {
            DatabaseMetaData databaseMetaData = connection.getMetaData();
            ResultSet resultSet = databaseMetaData.getCatalogs();
            while (resultSet.next()) {
                cmbDataBase.getItems().add(resultSet.getString(1));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        cmbDataBase.setOnAction(e -&gt; {
            cmbTables.getItems().clear();
            // 获得当前用户选择的数据库名称
            String dbName = cmbDataBase.getSelectionModel().getSelectedItem();
            try (Connection connection = JDBCUtil.GetConnection()) {
                DatabaseMetaData databaseMetaData = connection.getMetaData();
                ResultSet resultSet = databaseMetaData.getTables(dbName, null, null, new String[] {"table", "SYSTEM TABLE"});
                // 添加表名到右侧下拉框中
                while (resultSet.next()) {
                    cmbTables.getItems().add(resultSet.getString("TABLE_NAME"));
                }
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        });
    }
}

结果集元数据

package org.jdbcdemo;

import org.jdbcdemo.util.JDBCUtil;

import java.sql.*;

/**
 * 结果集的元数据
 */
public class ResultSetMetaDataDemo {
    public static void main(String[] args) {
        Connection connection = JDBCUtil.GetConnection();
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            statement = connection.createStatement();
            resultSet = statement.executeQuery("select * from Students;");
            // 获得结果集的元数据
            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
            System.out.println("当前结果集中共有" + resultSetMetaData.getColumnCount() + "列");
            for (int i = 1; i &lt;= resultSetMetaData.getColumnCount(); i++) {
                System.out.print(&quot;列名:&quot; + resultSetMetaData.getColumnName(i) + &quot;, &quot;);
                System.out.print(&quot;是否是自增列:&quot; + resultSetMetaData.isAutoIncrement(i) + &quot;, &quot;);
                System.out.print(&quot;本列的类型:&quot; + resultSetMetaData.getColumnTypeName(i));
                System.out.println(&quot;是否允许为空:&quot; + resultSetMetaData.isNullable(i));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

JDBC连接sql server2000

驱动

msbase.jar

mssqlserver.jar

msutil.jar

database.properties

jdbc.drivers=com.microsoft.jdbc.sqlserver.SQLServerDriver
jdbc.url=jdbc:microsoft:sqlserver://192.168.17.5:1433;DatabaseName=MIS
jdbc.userName=autotest
jdbc.password=ncs123!auto

test

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;

public class test {

    public static void main(String[] args) throws Exception {
        getConnection();
    }


    public static Connection getConnection() throws IOException {
        Connection conn = null;
        Properties props = new Properties();// 创建一个properties
        FileInputStream in = new FileInputStream(&quot;./config/database.properties&quot;);
        props.load(in); //把文件database.properties的内容读入对象props in.close(); 
        String drivers = props.getProperty(&quot;jdbc.drivers&quot;);
        if (drivers != null) {
            try {
                Class.forName(drivers).newInstance();
                String url = props.getProperty(&quot;jdbc.url&quot;);
                String userName = props.getProperty(&quot;jdbc.userName&quot;);
                String password = props.getProperty(&quot;jdbc.password&quot;);
                conn = DriverManager.getConnection(url, userName, password);                                
                System.out.println(&quot;数据库连接成功&quot;);
            }catch(Exception e) {
                e.printStackTrace();
                System.out.println(&quot;数据库连接失败&quot;);
            }
        }else {
            System.out.println(&quot;数据库驱动不存在&quot;);
        } 
        return conn;
    }

}

Java随机数

随机数类

int bound = 2;
Randowm random = new Random();
int randomNumber = random.nextInt(bound)

结果为[0, bound)之间的随机整数。

数学类里的随机数

Math.random()

结果为[0.0, 1.0)的随机double型数值

区间

开区间定义:

直线上介于固定的两点间的所有点的集合(不包含给定的两点),用(a, b)来表示(不包含两个端点ab

开区间的实质仍然是数集,该数集用符号(a, b)表示,含义一般是在实数a和实数b之间的所有实数,但不包含ab。相当于{x|a<x<b},记作(a, b)取值不包括a、b。

闭区间定义:

直线上介于固定两点间的所有点的集合(包含给定的两点)。闭区间是直线上的连通的闭集。由于它是有界闭集,所以它是紧致的。

闭区间的函数为小于等于的关系,即-\infty \le a +\infty,在数轴上为实心点。闭区间的余集(就是补集)是两个开区间的并集。实数理论在有著名的闭区间套定理。

代表符号:[x, y],即从x值开始到y值,包含xy。比如:x的取值范围是3到5的闭区间,那么用数学语言表示即为[3, 5],也就是从3(含)到5(含)之间的数。

java转exe

  1. 把Java工程导出成jar包
  2. 使用exe4j工具把jar包导出成exe
  3. 使用Inno Setup Compiler把整个工程打包成安装程序

JNI

JNI实现控制台中英文对齐

JNI, Java Native Interface

在java里写native方法

使用javah命名生成头文件

javah -jni 带包名的类名

生成的头文件方法参数列表有一个JNIEnv * 类型参数代表Java运行时环境

编辑.cpp文件

最后一个jboject类型是在java中传入的参数

编译c++程序生成.dll文件

g++ -m64 -static-libgcc -static-libstdc++ -I&quot;JDK的include文件夹路径&quot; -I&quot;JDK的include文件夹下的win32文件夹路径&quot; -shared -o 目标文件名.dll 源文件.cpp
System.load(&quot;dllPath&quot;);

在Java中使用cmd命令

class useJavaOpenExcelFile {
    public static void main(String[] args) {
        try {
            Runtime.getRuntime().exec(&quot;cmd /c start ./test.xlsx&quot;);
        } catch (IOException e) {
            System.out.println(&quot;IOException!&quot;);
        }
    }
}

JVM内存结构

堆(FIFO, First Input First Output)

数据结构特点:队列优先,先进先出

JVM中只有一个堆区被所有线程共享

堆存放在CPU的二级缓存中,调用对象的速度相对慢一些

生命周期由虚拟机的垃圾回收机制决定

堆用来存储new出来的对象和数组

优点:可以动态地分配内丰大小,生命周期不确定;

缺点:速度略慢,因为存放在于二级缓存中

栈(FILO, First Input Last Output)

数据结构特点:先进后出

栈是限定仅在表头进行插入和删除操作的线性表

暂存数据的地方,每个线程都会包含一个栈区

栈存放在CPU的一级缓存中,存取速度较快

栈用来存储基本类型变量和对象引用变量的地址

优点:速度快;

缺点:存在于栈中数据的大小和生命周期必须是明确的,缺少灵活性

方法区

用来存放方法和static变量

栈内存和堆内存的区别

  1. 应用程序所有的部分都是用堆内存,然后栈内存能过一个线程运行来使用
  2. 无论对象什么时候创建,都会存储在堆内存中,栈内存中包含的是它的引用。栈内存只包含原始值(基本类型)变量和堆中对象变量的引用
  3. 存储在堆中的对象是全局可以被访问的,而栈内存不能被其他线程访问
  4. 栈中的内存管理使用先入后出的方式完成,堆内存管理要复杂得多。堆内存是全局被访问的,堆内存又可以被分为年轻一代、老一代等。。。
  5. 栈内存的生命周期很短,而堆内存的生命周期从程序的运行开始到结束
  6. 当栈内存满的时候,会抛出java.lang.StackOverFlowError。堆内存满时会抛出java.lang.OutOfMemeryError: Java Heap Space错误
  7. 和堆内存相比,栈内存要小很多,但栈内存的速度比堆内存快

Java中的垃圾回收机制(Garbage Collection)

为了给类添加终结器,可以简单地定义finalize()方法

强制进行垃圾回收

System.gc();

Java中的内存管理

所谓内存释放,就是清理那些不可达的对象由GC决定,GC会监控每个对象的状态,包括申请内存、引用、被引用和赋值时

Java播放音乐

File soundFile = new File(&quot;musicFileFullName&quot;);
AudioClip sound = Applet.newAudioClip(sound.toURL());
sound.play();
sound.stop();

团队项目流程

确定项目主题 做什么项目 收集idea

编制需求文档

产品经理绘制产品原型图

免费工具:墨刀

用得比较多的工具:Axure

绘制功能流程图

技术官绘制类图

技术官 – 产品架构设计 – 不要具体实现 – 方法名 参数

大坑。整不好会被程序员打死

定开发环境,开发工具,编码格式,操作系统版本,JDK版本(估选1.8)、注释规范、命名规范、编码规范、参考阿里Java编码规范

分工

开发

整合

组内测试

上线发布

软件架构的六大原则

Single Responsibility Principle

单一职责原则定义

不要存在多于一个导致类变更的原因

简而言之就是每个类只担任一个职责,即每个类只有一个引起它变化的原因;多个职责耦合在一起,会影响复用性

问题由来

之所以会出现单一职责原则就是因为在软件设计时会出现以下类似场景:

T负责两个不同的职责:职责P1,职责P2,由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。也就是说职责P1和P2被耦合在了一起

解决办法

遵守单一职责原则,将不同的职责封装到不同的类或模块中

Liskov Substitution Principle

里式替换原则

Dependence Inversion Principle

依赖倒置原则

Interface Segregation Principle

接口隔离原则

Law Of Demeter

迪米特法则

Open Closed Principle

开闭原则

1 评论

发表评论