java基础入门
Jetbrains系统产品无限重置试用期教程(Windows/MacOS/Linux通用)
http://www.520xiazai.com/soft/jetbrains-eval-reset.html
JDK命令说明
java:这个可执行程序其实就是JVM,运行Java程序,就是启动JVM,然后让JVM执行指定的编译后的代码;
javac:这是Java的编译器,它用于把Java源码文件(以.java后缀结尾)编译为Java字节码文件(以.class后缀结尾);
jar:用于把一组.class文件打包成一个.jar文件,便于发布;
javadoc:用于从Java源码中自动提取注释并生成文档;
jdb:Java调试器,用于开发阶段的运行调试。
基础概念
类和方法
// class类名是Hello,class用来定义一个类,public表示这个类是公开的,花括号{}中间则是类的定义
public class Hello {
// 方法名main,还有用()括起来的方法参数,这里的main方法有一个参数,参数类型是String[],参数名是args,public、static用来修饰方法,这里表示它是一个公开的静态方法,void是方法的返回类型,而花括号{}中间的就是方法的代码。
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
编译
javac Hello.java
执行
java Hello
Hello, world!
- 一个Java源码只能定义一个public类型的class,并且class名称和文件名要完全一致;
基本数据类型
基本数据类型是CPU可以直接进行运算的类型。Java定义了以下几种基本数据类型:
整数类型(byte,short,int,long)
各种整型能表示的最大范围如下:
byte:-128 ~ 127
short: -32768 ~ 32767
int: -2147483648 ~ 2147483647
long: -9223372036854775808 ~ 9223372036854775807
浮点数类型(float,double)
float f1 = 3.14f;
float f2 = 3.14e38f; // 科学计数法表示的3.14x10^38
double d = 1.79e308;
double d2 = -1.79e308;
double d3 = 4.9e-324; // 科学计数法表示的4.9x10^-324
字符类型(char)
字符类型char表示一个字符
char a = ‘A’;
注意char类型使用单引号’,且仅有一个字符,要和双引号"的字符串类型区分开。
布尔类型(boolean)
boolean b1 = true;
boolean b2 = false;
boolean isGreater = 5 > 3; // 计算结果为true
int age = 12;
boolean isAdult = age >= 18; // 计算结果为false
引用类型
引用类型的变量类似于C语言的指针,它内部存储一个“地址”,指向某个对象在内存的位置
常量(final)
定义变量的时候,如果加上final修饰符,这个变量就变成了常量,常量名通常全部大写。
final double PI = 3.14;
常量在定义时进行初始化后就不可再次赋值,再次赋值会导致编译错误。
运算符
整数运算
+-*/
自增/自减:i++ / i--
与/或/非: && || !
boolean result = true && (5 / -1 > 0);
三元运算符 b ? x : y // 变量b是true则结果为x,false则结果为y
int age = 7;
boolean isPrimaryStudent = false;
System.out.println(isPrimaryStudent ? "Yes" : "No"); //No
强制类型转换
int n1 = (int) 12.3; 浮点数转整型
String s = "" + 12121; 数字转字符串
int y = Integer.parseInt(s); 字符串转数字
字符串
转义字符串
\" 表示字符"
\' 表示字符'
\\ 表示字符\
\n 表示换行符
\r 表示回车符
\t 表示Tab
\u#### 表示一个Unicode编码的字符
字符串连接“+”
// 如果用+连接字符串和其他数据类型,会将其他数据类型先自动转型为字符串,再连接
多行字符串
String s = "first line \n"
+ "second line \n"
+ "end";
字符串不可变
String s = "hello";
String t = s; // t在内存中指向了hello这个字符串
s = "world"; // s 指向了world这个字符串
System.out.println(t); // t还是"hello"
空字符串是一个有效的字符串对象,它不等于空值null,null不指向任何对象。
字符串常用操作
String s = "Hello ";
String ss = s.trim(); // Hello 去掉首尾空格
String sss = s.replace('H', '-'); // -ello 替换字符串
String s2 = "Hello";
System.out.println(s.equalsIgnoreCase(s2)); // 判断忽略大小写后相等
s = s.toUpperCase(); // HELLO 转换为大写
s = s.toLowerCase(Locale.ROOT); // hello 转换为小写
System.out.println(s.isEmpty()); // 判断字符串是否为空
System.out.println(s.length()); // 获取字符串长度
String s3 = "A,B,C,D";
String[] ss3 = s3.split("\\,"); // 字符串分割
System.out.println(Arrays.toString(ss3)); // [A, B, C, D]
String[] arr = {"A", "B", "C"};
String s4 = String.join("***", arr); // 连接/拼接字符串 "A***B***C"
String s5 = String.valueOf(true); // true 其它类型转字符串
int n1 = Integer.parseInt("123"); // 123 字符串转int
boolean b1 = Boolean.parseBoolean("true"); // true 字符串转bool
char[] cs = "Hello".toCharArray(); // String -> char[]
String s7 = new String(cs); // char[] -> String
数组
数组声明
* 声明int数组并赋值
int[] ns = new int[] { 68, 79, 91, 85, 62 };
int[] ns0 = { 68, 79, 91, 85, 62 }; // 上面的简写
System.out.println(ns.length); // 编译器自动推算数组大小为5
int[] ns1 = new int[5]; // 声明一个大小为5的数组
System.out.println(ns1[3]);
* 声明String数组names并赋值
// ***字符串是引用类型***
String[] names = {"ABC", "XYZ", "zoo"}; // 声明String数组names并赋值
String s = names[1]; // s指向了 "ABC"
names[1] = "cat"; // names[1]指向了 "cat"
System.out.println(s); // s还是"XYZ"
* 声明二维数组
int[][] ns = {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
System.out.println(ns.length); // 3
System.out.println(Arrays.deepToString(ns)); // 打印多维数组
System.out.println(ns[0][1]); // 根据下标访问元素
}
数组遍历
public class Tes {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i++) {
int n = ns[i];
System.out.printf("index=%d value=%d\n", i,n);
}
}
Arrays.toString()获取数组内容
直接打印数组变量,得到的是数组在JVM中的引用地址:[I@60e53b93
可以使用标准库的Arrays.toString()获取数组内容
int[] ns = { 1, 4, 9, 16, 25 };
System.out.println(ns); // [I@60e53b93
System.out.println(Arrays.toString(ns)); //[1, 4, 9, 16, 25]
Arrays.deepToString()获取多维数组内容
Arrays.sort()数组排序
int[] ns = { 3, 5, 10, 2, 11 };
Arrays.sort(ns);
System.out.println(Arrays.toString(ns)); //[2, 3, 5, 10, 11]
流程控制
浮点数计算判断
浮点数常常在计算机中精确表示,计算可能出现误差,判断浮点数用==号不准确,可以利用如下差值小于某个临界值计算。
public class Tes {
public static void main(String[] args) {
double x = 1 - 9.0 / 10;
if (Math.abs(x - 0.1) < 0.00001) {
System.out.println("x is 0.1");
} else {
System.out.println("x is NOT 0.1");
}
}
}
判断值类型的变量是否相等,可以使用==运算符。
判断引用类型变量是否相等,使用equals()。
public class Tes {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "HELLO".toLowerCase();
System.out.println(s1);
System.out.println(s2);
// 通过 && 多个条件,避免 NullPointerException 错误
if (s1 != null && s1.equals(s2)) {
System.out.println("s1 equals s2");
} else {
System.out.println("s1 not equals s2");
}
}
}
格式化输出/用户输入
double d = 3.1415926;
System.out.printf("%f\n", d); // 全都打印
System.out.printf("%.2f\n", d); // 只显示两位小数3.14
System.out.printf("%.4f\n", d); // 只显示4位小数3.1416
Scanner scanner = new Scanner(System.in); // 创建Scanner对象
System.out.print("Input your name: "); // 打印提示
String name = scanner.nextLine(); // 读取一行输入并获取字符串
System.out.print("Input your age: "); // 打印提示
int age = scanner.nextInt(); // 读取一行输入并获取整数
System.out.printf("Hi, %s, you are %d\n", name, age); // 格式化输出
if else 判断
if (条件) {
// 条件满足时执行
} else if (条件) {
// 条件满足时执行
} else {
// 上面条件都不满足时执行
}
switch case 多重选择
- 注意语句后的break,不加break会导致将所有的条件都匹配一次,危险!
int option = 1;
switch (option) {
case 3:
...
break;
case 2:
...
break;
case 1:
...
break;
}
while循环(先判断循环条件,再执行循环)
while (条件表达式) {
循环语句
}
public class Tes {
public static void main(String[] args) {
int sum = 0;
int n = 1;
while (n <= 100) {
sum = sum + n;
n ++;
}
System.out.println(sum); // 5050
}
}
do while循环(先执行循环,再判断条件)
do {
执行循环语句
} while (条件表达式);
public class Tes {
public static void main(String[] args) {
int sum = 0;
int n = 1;
do {
sum = sum + n;
n ++;
} while (n <= 100);
System.out.println(sum); // 5050
}
}
for循环
for (初始条件; 循环检测条件; 循环后更新计数器) {
// 执行语句
}
public class Tes {
public static void main(String[] args) {
int sum = 0;
for (int i=1; i<=100; i++) {
sum = sum + i;
}
System.out.println(sum);
}
}
break和continue
- break会跳出当前循环,也就是整个循环都不会执行了
- continue则是提前结束本次循环,直接继续执行下次循环
获取命令行参数
public class Tes {
public static void main(String[] args) {
for (String arg : args) { // 获取命令行参数
if ("-version".equals(arg)) { // 匹配命令行参数则打印
System.out.println("v1.0");
break;
}
}
}
}
java基础入门2
面向对象编程
方法
方法定义
修饰符 方法返回类型 方法名(方法参数列表) {
若干方法语句;
return 方法返回值;
}
public String getName(){
return name;
}
构造方法
class Person {
构造方法:名称就是类名。参数没有限制,且没有返回值
public Person() {
}
}
方法例子
- java中通常使用方法(method)操作private field,如get方法、set方法
- this始终指向当前实例。因此,通过this.field就可以访问当前实例的字段。
public class Tes {
public static void main(String[] args) {
Person p = new Person("zzd", 10); // 实例化class
p.setName("zzd"); // 赋值
p.setBirth(2000); // 赋值
System.out.println(p.getAge());
}
}
class Person { // class
private String name;
private int age;
private int birth;
// 构造方法:名称就是类名。参数没有限制,且没有返回值
// 默认构造方法,实例化时候:Person p = new Person("");
public Person(){
}
// 自定义构造方法,实例化时候:Person p = new Person("zzd", 10);
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// java中通常使用方法(method)操作private field
// set方法的主要作用就是可以针对传的参数做处理
public void setName(String name) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("invaild name");
}
this.name = name;
}
// 调用calcAge method计算年龄
public int getAge() {
return calcAge(2021);
}
public void setBirth(int birth) {
this.birth = birth;
}
// private 方法只能在内部调用
private int calcAge(int currentYear) {
return currentYear - this.birth;
}
}
可变参数写法
class Group {
private String[] names;
public void setNames(String... names) {
this.names = names;
}
}
方法重载(名称相同,参数不同)
public void hello(){
System.out.println("hello");
}
public void hello(String name){
System.out.printf("hello %s", name);
}
继承和多态
- 子类自动获得了父类的所有字段,严禁定义与父类重名的字段!所有类的根类是Object,关键字extends继承
- 子类无法访问父类的private字段或者private方法,若想访问使用protected
- 子类引用父类的字段时,可以用super.fieldName
- final关键字阻止继承
- 多态:子类可以覆写父类的方法(Override),覆写在子类中改变了父类方法的行为;
关键字 final 阻止类的继承
final class Me {
public int id;
}
// Cannot inherit from final 'Me'
继承使用 extends
class Student extends Person {
public int score;
}
多态:子类可以覆写父类的方法(Override),覆写在子类中改变了父类方法的行为
class Person {
public void run() {
System.out.println("person");
}
}
class Student extends Person {
// 同名方法Override
@Override
public void run() {
System.out.println("student");
}
}
抽象 abstract 和接口 interface
- abstract 抽象类和 abstract 抽象方法提供了"规范",规定子类继承时必须重写该 abstract 抽象方法,关键字 abstract
- 子类继承抽象类必须Override定义的 abstract 抽象方法
- interface 比抽象类还要抽象的纯抽象接口,因为它连字段都不能有,定义的所有方法默认都是public abstract的。使用implements实现接口,接口也可以继承从而实现扩展。
- interface default方法:当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。
public class Tes {
public static void main(String[] args) {
Person p = new Student();
p.run(); // Student.run
Person t = new Teacher();
t.run(); // Teacher.run
Cat c = new Cat("huahua");
c.run(); // huahua run
c.age(); // age
}
}
// abstract 抽象类和 abstract 抽象方法提供了"规范",规定子类继承时必须重写该 abstract 抽象方法,关键字 abstract
// 使用 abstract 抽象方法必须也把类定义为 abstract 抽象类
// 抽象类
abstract class Person {
// 抽象方法
public abstract void run();
}
// 子类继承抽象类必须Override定义的 abstract 抽象方法
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
class Teacher extends Person {
@Override
public void run() {
System.out.println("Teacher.run");
}
}
// interface 比抽象类还要抽象的纯抽象接口,因为它连字段都不能有,定义的所有方法默认都是public abstract的
interface Hi {
void say();
}
// 接口也可以继承,扩展了接口方法
interface Animal extends Hi {
void run();
// 实现类可以不必覆写default方法。default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。
default void age() {
System.out.println("age");
};
}
// 当一个具体的class去实现一个interface时,需要使用implements关键字
// 一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface
class Cat implements Animal {
private String name;
public Cat(String name) {
this.name = name;
}
public String getName(String name){
return this.name;
}
@Override
public void run() {
System.out.println(this.name + " run");
}
@Override
public void say(){
System.out.println("hello" + this.name);
}
}
静态字段和静态方法
- 对于 static 字段,无论修改哪个实例的静态字段,效果都是一样的:所有实例的静态字段都被修改了,原因是静态字段并不属于实例,且全局共用。
- 通过类名可以直接调用,可以访问静态字段和其他静态方法;静态方法常用于工具类
public class Tes {
public static void main(String[] args) {
Person ming = new Person("Xiao Ming", 12);
Person hong = new Person("Xiao Hong", 15);
ming.number = 88; // ming调用static field
System.out.println(hong.number); // 88 hong的 number field同步,可以看出是static field是全局共用
Person.setNumber(10); // 调用static方法
System.out.println(Person.number); // 10
}
}
class Person {
public String name;
public int age;
// 对于 static 字段,无论修改哪个实例的静态字段,效果都是一样的:所有实例的静态字段都被修改了,原因是静态字段并不属于实例;全局共用
public static int number;
// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 通过类名可以直接调用,可以访问静态字段和其他静态方法;
public static void setNumber(int value) {
number = value;
}
}
常用类
Math 数学计算
Math.abs(-10); // 10
Math.max(1, 2); // 2
Math.min(1.2, 2.3); // 1.2
Math.pow(2, 10); // 2的10次方=1024
double pi = Math.PI; // 3.141592..
double x = Math.random(); // x的范围是[0,1)
double min = 1;
double max = 100;
double y = x * (max - min) + min; // y的范围是[1,100)的小数
long n = (long) y; // n的范围是[1,100)的整数
System.out.println(y);
System.out.println(n);
enum 枚举类
- enum类型继承自java.lang.Enum,且无法被继承
- 无需通过new创建实例即可调用
- 定义的每个实例都是引用类型的唯一实例
- 可以将enum类型用于switch语句
public class Tes {
public static void main(String[] args) {
// 无需通过new创建实例即可调用
Gender g = Gender.BOY;
switch (g) {
case BOY:
System.out.printf("%d: %s", g.index, g);
break;
case GIRL:
System.out.printf("%d: %s", g.index, g);
break;
}
}
}
// enum 枚举类:个人理解属于自定义数据类型,Gender.BOY 属于Gender。(与常量定义比较优雅)
enum Gender {
// 定义 enum 中的常量
BOY(1, "男"), GIRL(2, "女");
public final int index;
private final String sex;
Gender(int index, String sex) {
this.index = index;
this.sex = sex;
}
// 默认情况下,对枚举常量调用toString()会返回和name()一样的字符串。但是,toString()可以被覆写,而name()则不行。我们可以给Gender添加toString()方法,使得输出结果更具可读性
@Override
public String toString() {
return this.sex;
}
}
异常处理
- throw new NullPointerException(); 主动抛出异常
- e.printStackTrace(); 打印异常堆栈
- try … finally 其中 finally 总是最后执行
- NullPointerException 是代码逻辑错误,用好的编程习惯尽量避免此类问题。如:初始化时传入默认值:private String name = “”; 在return时返回字符串""/空数组:return new String[0];
public class Tes {
public static void main(String[] args) {
// try ... catch捕获异常。catch捕获对应的Exception及其子类。
try {
process1();
// int[] ns = new int[] { 68, 79, 91, 85, 62 };
// System.out.println(ns[10]);
} catch (NumberFormatException e) { // 异常捕获的问题很重要,子异常要定义在基异常前面
e.printStackTrace(); // 打印异常堆栈
System.out.println("NumberFormatException err");
} catch (Exception e) { // Exception 为基异常
e.printStackTrace();
System.out.println("Exception error");
} finally { // finally语句不是必须的,可写可不写。总是最后执行。
System.out.println("finally");
}
}
static void process1() {
try {
process2(); // process1 调用 process2 从而触发了throw new 异常
} catch (NullPointerException e) {
throw new IllegalArgumentException(e);
}
}
static void process2() {
throw new NullPointerException(); // throw 语句主动抛出异常
}
}
/* output
java.lang.IllegalArgumentException: java.lang.NullPointerException // 最外层的异常
at Tes.process1(Tes.java:27)
at Tes.main(Tes.java:10)
Caused by: java.lang.NullPointerException // 通过 Caused by 找到根异常
at Tes.process2(Tes.java:33)
at Tes.process1(Tes.java:25)
... 1 more
Exception error
finally
* */
自定义异常
- 自定义异常体系时,推荐从RuntimeException派生“根异常”,再派生出业务异常;
- 自定义异常时,应该提供多种构造方法。
public class BaseException extends RuntimeException {
public BaseException() {
super();
}
public BaseException(String message, Throwable cause) {
super(message, cause);
}
public BaseException(String message) {
super(message);
}
public BaseException(Throwable cause) {
super(cause);
}
}
public class UserNotFoundException extends BaseException {
}
public class LoginFailedException extends BaseException {
}
日志处理
Logging
日志级别:
SEVERE
WARNING
INFO
CONFIG
FINE
FINER
FINEST
import java.util.logging.Level;
import java.util.logging.Logger;
public class Main {
public static void main(String[] args) {
Logger logger = Logger.getGlobal();
logger.info("start process...");
logger.warning("memory is running out...");
logger.fine("ignored.");
logger.severe("process will be terminated...");
}
}
反射
Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。
- 通过Class实例获取class信息的方法称为反射(Reflection)。
public class Main {
public static void main(String[] args) {
// 通过Class实例获取class信息
Class cls1 = String.class; // 法1 class java.lang.String
String s = "Hello";
Class cls2 = s.getClass(); // 法2 class java.lang.String
boolean sameClass = cls1 == cls2; // true
}
}
注解
注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”
注解可以被编译器打包进入class文件,是一种用作标注的“元数据”。
Java的注解可以分为三类:
第一类是由编译器使用的注解,例如:
@Override:让编译器检查该方法是否正确地实现了覆写;
@SuppressWarnings:告诉编译器忽略此处代码产生的警告。
这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。
第二类是由工具处理.class文件使用的注解,比如有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。
第三类是在程序运行期能够读取的注解,它们在加载后一直存在于JVM中,这也是最常用的注解。例如,一个配置了@PostConstruct的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)。
泛型
泛型就是编写模板代码来适应任意类型;
泛型的好处是使用时不必对类型进行强制转换,它通过编译器对类型进行检查;
集合
Java的集合类定义在java.util包中,支持泛型,Java集合使用统一的Iterator遍历,主要有如下3种:
- List:一种有序列表的集合,按索引排列
- Set:一种保证没有重复元素的集合
- Map:一种通过键值(key-value)查找的映射表集合
List
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
List list = new ArrayList();
list.add("hi"); // 增
list.add("World");
list.add("zzd");
// 法1:普通for循环
for (int i = 0; i < list.size(); i++) {
String s = (String) list.get(i);
System.out.println(s);
}
list.remove(0); // 删
// 法2:Iterator遍历效率更高
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
System.out.println(s);
}
}
}
/*
hi
World
zzd
World
zzd
* */
Map
HashMap
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>(); // 新建HashMap实例
map.put("zzd", 20); // 增
map.put("dada", 40); // 增
System.out.println(map.get("dada")); // 查
map.replace("zzd", 33); // 改
map.remove("dada"); // 删
// 法1:keySet获取key,再根据key获取value
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + " = " + value);
}
// 法2:使用entrySet可以直接获取key和value
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " = " + value);
}
// 用Map实现根据name查询某个Student
Student s = new Student("Xiao Ming", 99);
Map<String, Student> map1 = new HashMap<>();
map1.put("Xiao Ming", s); // 将"Xiao Ming"和Student实例映射并关联
map1.put("zhangsan", new Student("zhangsan", 100)); // 也可以在put里new
Student target = map1.get("Xiao Ming"); // 通过key查找并返回映射的Student实例
System.out.println(target == s); // true,同一个实例
System.out.println(target.score); // 99
Student another = map1.get("Bob"); // 通过另一个key查找
System.out.println(another); // 未找到返回null
for (String key : map1.keySet()) {
Student value = map1.get(key);
System.out.println(key + " = " + value.score);
}
}
}
class Student {
public String name;
public int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
}
/*
40
zzd = 33
zzd = 33
true
99
null
zhangsan = 100
Xiao Ming = 99
* */
EnumMap
如果Map的key是enum类型,推荐使用EnumMap,根据enum类型的key直接定位到内部数组的索引,既保证速度,也不浪费空间。
import java.util.Map;
import java.time.DayOfWeek;
import java.util.*;
public class Main {
public static void main(String[] args) {
Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);
map.put(DayOfWeek.MONDAY, "星期一");
map.put(DayOfWeek.TUESDAY, "星期二");
map.put(DayOfWeek.WEDNESDAY, "星期三");
map.put(DayOfWeek.THURSDAY, "星期四");
map.put(DayOfWeek.FRIDAY, "星期五");
map.put(DayOfWeek.SATURDAY, "星期六");
map.put(DayOfWeek.SUNDAY, "星期日");
System.out.println(map);
System.out.println(map.get(DayOfWeek.MONDAY));
}
}
SortedMap之TreeMap
SortedMap保证遍历时以Key的顺序来进行排序。
import java.util.Map;
import java.util.*;
public class Main {
public static void main(String[] args) {
Map<String, Integer> map = new TreeMap<>();
map.put("orange", 1);
map.put("apple", 2);
map.put("pear", 3);
map.put("cc", 4);
for (String key : map.keySet()) {
System.out.println(key);
}
// apple, cc, orange, pear
}
}
Set集合
Set用于存储不重复的元素集合
import java.util.*;
public class Main {
public static void main(String[] args) {
Set<String> set = new HashSet<>(); // 无序集合
// Set<String> set = new TreeSet<>(); // 有序集合,TreeSet是有序的,因为它实现了SortedSet接口。
System.out.println(set.add("xyz")); // true
System.out.println(set.add("abc")); // true
System.out.println(set.add("xyz")); // false,添加失败,因为元素已存在
System.out.println(set.contains("xyz")); // true,元素存在
System.out.println(set.contains("XYZ")); // false,元素不存在
System.out.println(set.remove("hello")); // false,删除失败,因为元素不存在
System.out.println(set.size()); // 2,一共两个元素
System.out.println(set); // [abc, xyz]
}
}
Queue 先进先出
PriorityQueue 优先队列
Deque 双端队列
Stack 后进先出
迭代器Iterator
Iterator是一种抽象的数据访问模型。使用Iterator相比for循环效率更高。
List<String> list = new ArrayList<>();
list.add("apple");
list.add("pear");
list.add("cc");
list.add("orange");
for (String s : list) {
System.out.println(s);
}
Collections操作集合
Collections类提供了一组工具方法来方便使用集合类:
排序/洗牌等操作。
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("apple");
list.add("pear");
list.add("cc");
list.add("orange");
// 排序前
System.out.println(list); // [apple, pear, cc, orange]
Collections.sort(list);
// 排序后
System.out.println(list); // [apple, cc, orange, pear]
List<Integer> list1 = new ArrayList<>();
for (int i=0; i<10; i++) {
list1.add(i);
}
// 洗牌前:
System.out.println(list1);
Collections.shuffle(list1);
// 洗牌后:
System.out.println(list1);
}
}
日期与时间
java.time包提供了新的日期和时间API
import java.time.*;
import java.time.format.*;
public class Main {
public static void main(String[] args) {
LocalDate d = LocalDate.now(); // 当前日期
LocalTime t = LocalTime.now(); // 当前时间
LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间
System.out.println(d); // 严格按照ISO 8601格式打印 2021-04-02
System.out.println(t); // 严格按照ISO 8601格式打印 10:43:22.827
System.out.println(dt); // 严格按照ISO 8601格式打印 2021-04-02T10:43:22.827
// 自定义打印时间
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
System.out.println(dtf.format(LocalDateTime.now())); // 2021/04/02 10:43:22
}
}
IO
文件/目录操作
File对象可以对文件/目录进行操作
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
/**
* 获取路径
* **/
File f = new File(".."); // 构造方法传入的路径(.表示当前目录,..表示上级目录)
System.out.println(f.getPath()); // ..
System.out.println(f.getAbsolutePath()); // 绝对路径,项目根目录: /Users/zhangzhidao/Desktop/project/java_test/..
System.out.println(f.getCanonicalPath()); // 规范路径: 取决于new File("") 中的路径 这里是上级目录: /Users/zhangzhidao/Desktop/project
/**
* 判断是文件/目录
* **/
File f1 = new File("/Users/zhangzhidao/Desktop/project/java_test");
File f2 = new File("/Users/zhangzhidao/Desktop/project/java_test/pom.xml");
System.out.println(f1.isDirectory()); // true
System.out.println(f2.isFile()); // true
/**
* 普通文件创建/删除,临时文件创建/删除
* **/
File file = new File("a.txt");
file.createNewFile(); // 创建文件
file.delete(); //删除文件
File file1 = new File("tmp/tt");
file1.mkdirs(); // 创建目录
File ftmp = File.createTempFile("tmp-", ".txt"); // 提供临时文件的前缀和后缀
f.deleteOnExit(); // JVM退出时自动删除
System.out.println(ftmp.isFile()); // true
System.out.println(ftmp.getAbsolutePath()); // /var/folders/v4/vrfcdf_s0h58vzl4vvzjp5q00000gn/T/tmp-4742037933201830112.txt
/**
* 列出所有文件和子目录
* **/
File[] fs1 = f.listFiles(); // 列出所有文件和子目录
for (File ff : fs1) {
System.out.println(ff);
}
}
}
文件读写
import java.io.*;
public class Main {
/**
* 以行为单位读取文件
* */
public static void readFileByLines(String fileName) {
File file = new File(fileName);
BufferedReader reader = null;
try {
System.out.println("以行为单位读取文件内容,一次读一整行:");
reader = new BufferedReader(new FileReader(file));
String tempString = null;
int line = 1;
// 一次读入一行,直到读入null为文件结束
while ((tempString = reader.readLine()) != null) {
// 显示行号
System.out.println("line " + line + ": " + tempString);
line++;
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
/**
* 追加内容至文件
* */
public static void appendMethod(String fileName, String content) {
try {
//打开一个写文件器,构造函数中的第二个参数true表示以追加形式写文件
FileWriter writer = new FileWriter(fileName, true);
writer.write(content);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
// 读取的文件
String fileName = "readme.txt";
// 待写入的内容
String content = "new append!";
Main.readFileByLines(fileName);
//追加
Main.appendMethod(fileName, content);
Main.appendMethod(fileName, "append end. \n");
//显示文件内容
Main.readFileByLines(fileName);
}
}
Maven
Maven是一个Java项目管理和构建工具,它可以定义项目结构、项目依赖,并使用统一的方式进行自动化构建.
- 使用pom.xml定义项目内容
- 在Maven中声明一个依赖项可以自动下载并导入classpath;
- 以-SNAPSHOT结尾的版本号会被Maven视为开发版本,开发版本每次都会重复下载
maven阿里镜像仓库配置
vim ~/.m2/settings.xml
<settings>
<mirrors>
<mirror>
<id>aliyun</id>
<name>aliyun</name>
<mirrorOf>central</mirrorOf>
<!-- 国内推荐阿里云的Maven镜像 -->
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
</mirrors>
</settings>
maven依赖引入
groupId:属于组织的名称,类似Java的包名;
artifactId:该jar包自身的名称,类似Java的类名;
version:该jar包的版本。
pom.xml 文件中commons-logging依赖示例:
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
IDEA引入依赖生效
maven生命周期(lifecycle)和阶段(phase)
内置的default生命周期包含以下阶段(phase)
validate
initialize
generate-sources
process-sources
generate-resources
process-resources
compile
process-classes
generate-test-sources
process-test-sources
generate-test-resources
process-test-resources
test-compile
process-test-classes
test
prepare-package
package
pre-integration-test
integration-test
post-integration-test
verify
install
deploy
运行mvn package,Maven就会执行default生命周期,它会从开始顺序执行一直运行到package这个phase。
clean生命周期,它会执行以下3个阶段(phase)
pre-clean
clean (注意这个clean不是lifecycle而是phase)
post-clean
常用maven组合命令
mvn clean:清理所有生成的class和jar;
mvn clean compile:先清理,再执行到编译compile;
mvn clean test:先清理,再执行到test,因为执行test前必须执行compile,所以这里不必指定compile;
mvn clean package:先清理,再执行到打包package。
使用自定义插件
常用插件
maven-shade-plugin:打包所有依赖包并生成可执行jar;
cobertura-maven-plugin:生成单元测试覆盖率报告;
findbugs-maven-plugin:对Java源码进行静态分析以找出潜在问题。
pom.xml 中引入maven-shade-plugin插件如下:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<!--指定Java程序的入口-->
<mainClass>Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Maven模块管理
一个大项目可以拆成几个模块:
- 通过继承在parent的pom.xml统一定义重复配置
- 通过编译多个模块
JDBC(mysql)
mysql数据
-- 创建数据库learjdbc:
DROP DATABASE IF EXISTS learnjdbc;
CREATE DATABASE learnjdbc;
-- 创建登录用户learn/口令learnpassword
CREATE USER IF NOT EXISTS learn@'%' IDENTIFIED BY 'learnpassword';
GRANT ALL PRIVILEGES ON learnjdbc.* TO learn@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;
-- 创建表students:
USE learnjdbc;
CREATE TABLE students (
id BIGINT AUTO_INCREMENT NOT NULL,
name VARCHAR(50) NOT NULL,
gender TINYINT(1) NOT NULL,
grade INT NOT NULL,
score INT NOT NULL,
PRIMARY KEY(id)
) Engine=INNODB DEFAULT CHARSET=UTF8;
-- 插入初始数据:
INSERT INTO students (name, gender, grade, score) VALUES ('小明', 1, 1, 88);
INSERT INTO students (name, gender, grade, score) VALUES ('小红', 1, 1, 95);
INSERT INTO students (name, gender, grade, score) VALUES ('小军', 0, 1, 93);
INSERT INTO students (name, gender, grade, score) VALUES ('小白', 0, 1, 100);
INSERT INTO students (name, gender, grade, score) VALUES ('小牛', 1, 2, 96);
INSERT INTO students (name, gender, grade, score) VALUES ('小兵', 1, 2, 99);
INSERT INTO students (name, gender, grade, score) VALUES ('小强', 0, 2, 86);
INSERT INTO students (name, gender, grade, score) VALUES ('小乔', 0, 2, 79);
INSERT INTO students (name, gender, grade, score) VALUES ('小青', 1, 3, 85);
INSERT INTO students (name, gender, grade, score) VALUES ('小王', 1, 3, 90);
INSERT INTO students (name, gender, grade, score) VALUES ('小林', 0, 3, 91);
INSERT INTO students (name, gender, grade, score) VALUES ('小贝', 0, 3, 77);
安装驱动
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope>
</dependency>
使用PreparedStatement 增删改查,避免sql注入
import java.sql.*;
public class Main {
public static void main(String[] args) throws SQLException {
// JDBC连接的URL, 不同数据库有不同的格式:
String JDBC_URL = "jdbc:mysql://localhost:3306/learnjdbc";
String JDBC_USER = "learn";
String JDBC_PASSWORD = "learnpassword";
// Connection建立JDBC连接
try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
/**
* PreparedStatement 进行查询,可以避免sql注入
* 查询
* */
try (PreparedStatement ps = conn.prepareStatement("SELECT id, grade, name, gender FROM students WHERE gender=? AND grade=?")) {
// ps.setObject(参数索引上述sql中?的位置, 参数的值)
ps.setObject(1, 1); // 参数索引?的位置是1,gender=1
ps.setObject(2, 3); // 参数索引?的位置是2,grade=3
// 查询结果 ResultSet
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
long id = rs.getLong("id");
long grade = rs.getLong("grade");
String name = rs.getString("name");
String gender = rs.getString("gender");
System.out.printf("id: "+ id + "grade: "+ grade + "name: " + name + "gender" + gender + "\n");
}
}
}
/**
* 插入
* */
try (PreparedStatement ps = conn.prepareStatement(
"INSERT INTO students (id, grade, name, gender, score) VALUES (?,?,?,?,?)")) {
ps.setObject(1, 999); // 注意:索引从1开始
ps.setObject(2, 1); // grade
ps.setObject(3, "Bob"); // name
ps.setObject(4, 0); // gender
ps.setObject(5, 100);
int n = ps.executeUpdate(); // 1
}
/**
* 更新
* */
try (PreparedStatement ps = conn.prepareStatement("UPDATE students SET name=? WHERE id=?")) {
ps.setObject(1, "zzd"); // 注意:索引从1开始
ps.setObject(2, 999);
int n = ps.executeUpdate(); // 返回更新的行数
}
/**
* 删除
* */
try (PreparedStatement ps = conn.prepareStatement("DELETE FROM students WHERE id=?")) {
ps.setObject(1, 999); // 注意:索引从1开始
int n = ps.executeUpdate(); // 删除的行数
}
}
}
}
JDBC事务
事务中的所有SQL要么全部执行成功,要么全部不执行,即数据库事务具有ACID特性:
- Atomicity:原子性
- Consistency:一致性
- Isolation:隔离性
- Durability:持久性
try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
try {
// 关闭自动提交:
conn.setAutoCommit(false);
// 执行多条SQL语句:
insert(); update(); delete();
// 提交事务:
conn.commit();
} catch (SQLException e) {
// 回滚事务:
conn.rollback();
} finally {
conn.setAutoCommit(true);
conn.close();
}
}
Batch批量高效执行SQL
对内容相同,参数不同的SQL,要优先考虑batch操作,性能很高。
/**
* Batch批量执行
* */
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO students (name, gender, grade, score) VALUES (?, ?, ?, ?)")) {
// 对同一个PreparedStatement反复设置参数并调用addBatch():
for (int i = 0; i < 200; i++) {
ps.setString(1, "zz" + i);
ps.setBoolean(2, false);
ps.setInt(3, 5);
ps.setInt(4, 90);
ps.addBatch(); // 添加到batch
}
// 执行batch:
int[] ns = ps.executeBatch();
for (int n : ns) {
System.out.println(n + " inserted."); // batch中每个SQL执行的结果数量
}
}
JDBC连接池
数据库连接池是一种复用Connection的组件,它可以避免反复创建新连接,提高JDBC代码的运行效率;
JDBC连接池有一个标准的接口javax.sql.DataSource,注意这个类位于Java标准库中,但仅仅是接口。要使用JDBC连接池,我们必须选择一个JDBC连接池的实现。这里使用HikariCP实现
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.7.1</version>
</dependency>
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.*;
import javax.sql.DataSource;
public class Main {
public static void main(String[] args) throws SQLException {
// 数据库连接池是一种复用Connection的组件,它可以避免反复创建新连接,提高JDBC代码的运行效率;
// 使用 HikariCP 配置 JDBC 连接池
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/learnjdbc");
config.setUsername("learn");
config.setPassword("learnpassword");
config.addDataSourceProperty("connectionTimeout", "1000"); // 连接超时:1秒
config.addDataSourceProperty("idleTimeout", "60000"); // 空闲超时:60秒
config.addDataSourceProperty("maximumPoolSize", "10"); // 最大连接数:10
DataSource ds = new HikariDataSource(config);
// 使用连接池就使用ds.getConnection()替换DriverManager.getConnection()
// 创建DataSource是一个非常昂贵的操作,所以通常DataSource实例总是作为一个全局变量存储
try (Connection conn = ds.getConnection()) { // 在此获取连接
/**
* Batch批量执行
* */
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO students (name, gender, grade, score) VALUES (?, ?, ?, ?)")) {
// 对同一个PreparedStatement反复设置参数并调用addBatch():
for (int i = 0; i < 200; i++) {
ps.setString(1, "zz" + i);
ps.setBoolean(2, false);
ps.setInt(3, 5);
ps.setInt(4, 90);
ps.addBatch(); // 添加到batch
}
// 执行batch:
int[] ns = ps.executeBatch();
for (int n : ns) {
System.out.println(n + " inserted."); // batch中每个SQL执行的结果数量
}
}
} // 在此“关闭”连接
}
}