文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 2019-10-11   3.8k

Java基础学习之核心类

字符串

String是一个引用类型,也是一个类,它的一个重要特点就是不可变,这种不可变性是通过内部的private final char[]字段,以及没有任何修改char[]的方法实现的。

字符串比较

当我们想要比较两个字符串是否相同时,要特别注意,我们实际上是想比较字符串的内容是否相同。必须使用equals()方法而不能用==

1
2
3
4
5
6
7
8
public class Main() {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "HELLO".toLowerCase();
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
}

要忽略大小写比较,使用equalsIgnoreCase()方法。

搜索子串的例子:

1
2
3
4
"Hello".indexof("l"); // 2
"Hello".lastIndexof("l"); //3
"Hello".startsWith("He"); // true
"Hello".endsWith("lo"); //true

提取子串的例子:

1
2
"Hello".substring(2); //"llo"
"Hello".substring(2, 4); //"llo"

去除首尾空白字符

使用trim()方法可以移除字符串首尾空白字符。空白字符包括空格,\t\r\n

注意:trim()并没有改变字符串的内容,而是返回了一个新字符串。

另一个strip()方法也可以移除字符串首尾空白字符,包括中文的空格字符\u3000

String还提供了isEmpty()isBlank()来判断字符串是否为空和空白字符串。

替换子串

使用replace()方法可以根据字符或字符串替换:

1
2
3
String s = "hello";
s.replace('l', 'w'); // "hewwo"
s.replace('ll', "~~"); // "he~~o"

分割字符串

split()方法根据匹配给定的正则表达式来拆分字符串。注意: .|* 等转义字符,必须得加 \\

1
2
String s = "A,B,C,D";
String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}

拼接字符串

拼接字符串使用静态方法join(),它用指定的字符串连接字符串数组:

1
2
String[] arr = {"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C"

类型转换

静态方法valueOf()可以吧任意基本类型或引用类型转换为字符串。

1
2
3
4
String.valueOf(123); //"123"
String.valueOf(45.67); //"45.67"
String.valueOf(true); //"true"
String.valueOf(new Object()); //[email protected]

字符串转换为int类型:

1
2
int n1 = Integer.parseInt("123"); //123
int n2 = Integer.parseInt("ff", 16); //按十六进制转换,255

字符串转换为boolean类型:

1
2
3
boolean b1 = Boolean.parseBoolean("true");
boolean b2 = Boolean.parseBoolean("FALSE");

转换为char[]

Stringchar可以互相转换:

1
2
3
char[] cs = "Hello".toCharArray();
String s = new String(cs);

如果修改了char[]数组,String并不会改变:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {
public static void Main(String[] args) {
char[] cs = "Hello".toCharArray();
String s = new String(cs);
System.out.println(s);
cs[0] = 'X';
System.out.println(s);
}
}

// 输出
// Hello
// Hello

这是因为通过new String(char[])创建新的String实例时,它并不会直接引用传入的char[]数组,而是会复制一份,所以,修改外部的char[]数组不会影响String实例内部的char[]数组,因为这是两个不同的数组。

所以,如果传入的对象有可能改变,我们需要复制而不是直接引用,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] scores = new int[] {88, 99, 11, 22, 33};
Score s = new Score(scores);
s.printScores();
scores[2] = 55;
s.printScores;
}
}

class Score {
private int[] scores;
public Score(int[] scores) {
this.scores = scores;
}

public void printScores() {
System.out.println(Arrays.toString(scores));
}
}

// 输出
// 88, 99, 11, 22, 33
// 88, 99, 55, 22, 33

观察两次输出,由于Score内部直接引用了外部传入的int[]数组,这会造成外部代码对int[]数组的修改,影响到Score类的字段。正确的构造函数如下:

1
2
3
4
public Score(int[] scores) {
this.scores = scores.clone();
}

StringBuilder

StringBuilder是一个可变对象,能高效拼接字符串,往StringBuilder中新增字符时,不会创建新的临时对象。

1
2
3
4
5
6
7
8
9
10
11
StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
sb.append(',');
sb.append(i);
}
String s = sb.toString();
System.out.println(s);

// 输出
// 0,1,2,3...999

此外,StringBuilder还可以进行链式操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {
public static void main(String[] args) {
var sb = new StringBuilder(1024);
sb.append("Mr")
.append("Bob")
.append("!")
.insert(0, "Hello, ");
System.out.println(sb.toString());
}
}

// 输出
// Hello, Mr Bob!

支持链式操作的关键可以从StringBuilder源码看出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public StringBuilder append(String str) {
super.append(str);
return this;
}

public AbstractStringBuilder append(String str) {
if (str == null) {
return appendNull();
}
int len = str.length();
ensureCapacityInternal(count + len);
putStringAt(count, str);
count += len;
return this;
}

定义的append()方法会返回this,这样,就可以不断调用自身的其他方法。

模仿StringBuilder设计一个可以不断增加的计数器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Main {
public static void main(String[] args) {
Adder adder = new Adder();
adder.add(2)
.add(5)
.inc()
.add(10);
System.out.println(adder.value())
}
}

class Adder {
private int sum = 0;

public Adder add(int n) {
sum += n;
return this;
}

public Adder inc() {
sum ++;
return this;
}

public int value() {
return sum;
}
}

// 输出
// 18

练习

请使用StringBuilder构造一个INSERTSQL语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.itranswarp.learnjava;

/**
* Learn Java from https://www.liaoxuefeng.com/
*
* @author liaoxuefeng
*/
public class Main {

public static void main(String[] args) {
String[] fields = { "name", "position", "salary" };
String table = "employee";
String insert = buildInsertSql(table, fields);
System.out.println(insert);
System.out.println(
"INSERT INTO employee (name, position, salary) VALUES (?, ?, ?)".equals(insert) ? "测试成功" : "测试失败");
}

static String buildInsertSql(String table, String[] fields) {
// TODO:
StringBuilder sb = new StringBuilder("INSERT INTO ").append(table).append(" (");
for (String st: fields) {
sb.append(st).append(", ");
}

// 注意去掉最后的", ":
sb.delete(sb.length()-2, sb.length()).append(") VALUES (");

for (String st : fields) {
sb.append("?, ");
}
sb.delete(sb.length()-2, sb.length()).append(")");
return sb.toString();
}

}

StringJoiner

在上面的那个练习当中,我们需要, 来分割每个字段,可以直接使用StringJoiner对象来干这事。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.StringJoiner;

public class Main {
public static void main(String[] args) {
String[] names = {"Bob", "Alice", "Grace"};
var sj = new StringJoiner(", ");
for (String name: names) {
sj.add(name);
}
System.out.println(sj.toString())
}
}

// 输出
// Bob, Alice, Grace

还可以指定“开头”和“结尾”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.StringJoiner;

public class Main {
public static void main(String[] args) {
String[] names = {"Bob", "Alice", "Grace"};
var sj = new StringJoiner(", ", "Hello", "!");
for (String name: names) {
sj.add(name);
}
System.out.println(sj.toString())
}
}

// 输出
// Hello Bob, Alice, Grace!

其实,String还提供了一个静态方法join(),这个方法在内部使用了StringJoiner来拼接字符串,在不需要指定“开头”和“结尾”的时候,用String.join()更方便:

1
2
3
String[] names = {"Bob", "Alice", "Grace"};
var s = String.join(",", names);

练习

请使用StringJoiner构造一个SELECT语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.itranswarp.learnjava;
import java.util.StringJoiner;
/**
* Learn Java from https://www.liaoxuefeng.com/
*
* @author liaoxuefeng
*/
public class Main {

public static void main(String[] args) {
String[] fields = { "name", "position", "salary" };
String table = "employee";
String select = buildSelectSql(table, fields);
System.out.println(select);
System.out.println("SELECT name, position, salary FROM employee".equals(select) ? "测试成功" : "测试失败");
}

static String buildSelectSql(String table, String[] fields) {
// TODO:
var sj = new StringJoiner(", ", "SELECT ", " FROM "+table);
for (String field: fields) {
sj.add(field);
}
return sj.toString();
}

}

包装类型

Java为每种基本类型都提供了对应的包装类型:

基本类型 对应的引用类型
boolean java.lang.Boolean
byte java.lang.Byte
short java.lang.Short
int java.lang.Integer
long java.lang.Long
float java.lang.Float
double java.lang.Double
char java.lang.Character

使用静态方法valueOf()创建Integer实例:

1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) {
int i = 100;
Integer n1 = Integer.valueOf(i);
Integer n2 = Integer.valueOf("100");
}
}

自动装箱和自动拆箱

IntInteger可以相互转换:

1
2
3
4
int i = 100;
Integer n = Integer.valueOf(i);
int x = n.intValue();

而Java编译器可以帮助我们自动在intInteger直降相互转换:

1
2
3
Integer n = 100;
int x = n;

但是这样做可能会造成空指针异常的错误:

1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
Integer n = null;
int i = n;
}
}

不变性

由于包装类型都属于引用类型,所以一旦创建了包装类型的对象,该对象就是不可变的。

String类似,对于两个Integer实例进行比较必须使用equals(),而不能使用==

进制转换

最常用的静态方法parseInt()可以把字符串解析成一个整数:

1
2
3
int x1 = Integer.parseInt("100"); // 100
int x2 = Integer.parseInt("100", 16); // 255

Integer还可以把整数格式化为指定进制的字符串:

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
System.out.println(Integer.toString(100)); // "100"
System.out.println(Integer.toString(100, 36)); // "2s"
System.out.println(Integer.toHexString(100)); // "255"
System.out.println(Integer.toOctalString(100)); // "144"
System.out.println(Integer.toBinaryString(100)); // "1100100"
}
}

所有的整数和浮点数的包装类型都继承自Number,因此,可以非常方便地直接通过包装类型获取各种基本类型:

1
2
3
4
5
6
7
8
9
//向上转型为Number
Number num = new Integer(100);
// 获取byte, int, long, float, double;
byte b = num.byteValue();
int i = num.intValue();
long l = num.longValue();
float f = num.floatValue();
double d = num.doubleValue();

无符号整型

1
2
3
4
5
6
7
8
9
public class Main {
public static void main() {
byte x = -1;
byte x = 127;
System.out.println(Byte.toUnsignedInt(x)); // 255
System.out.println(Byte.toUnsignedInt(x)); // 127
}
}

JavaBean

定义

JavaBean是一种特殊但较为常见的class,它符合下列要求:

  • 有若干个private实例字段;

  • 通过public方法来读写实例字段;

  • 且读写方法符合如下命名规范:

    1
    2
    3
    4
    5
    //读方法
    public Type getXyz()
    //写方法
    public void Type setXyz()

作用

JavaBean主要用来传递数据,即把一组数据组合成一个JavaBean便于传输

枚举JavaBean属性

要枚举一个JavaBean的所有属性,可以直接使用Java核心库提供的Introspector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Main {
public static void main(String[] args) throws Exception {
BeanInfo info = Introspector.getBeanInfo(Person.class);
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
System.out.println(pd.getName());
System.out.println(" " + pd.getReadMethod());
System.out.println(" " + pd.getWriteMethod());
}
}
}

class Person {
private String name;
private int age;

public String getName() {
return name;
}

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

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

// 输出
/**
age
public int Person.getAge()
public void Person.setAge(int)
class
public final native java.lang.Class java.lang.Object.getClass()
null
name
public java.lang.String Person.getName()
public void Person.setName(java.lang.String)
**/

枚举类

直接上🌰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main() {
Weekday day = Weekday.SUN;
if (day == Weekday.SUN || day == Weekday.SAT) {
System.out.println("Work at home");
} else {
System.out.println("Work an office!");
}
}
}

enum Weekday {
SUN, MON, TUE, WED, THU, FRI, SAT;
}

int类型的常量相比,使用enum定义枚举的优势有:

  • enum常量本身带有类型信息,即Weekday.SUN的类型是Weekday
  • 不可能引用到非枚举的值;
  • 不同类型的枚举不能相互比较或赋值。

enum的比较

enum是一个引用类型,前面提到引用类型进行比较必须使用equal(),因为==比较的是两个引用类型的变量是否是同一个对象。但enum类型可以例外,因为enum类型的每个常量在JVM中只有一个唯一实例。所以:

1
2
3
day == Weekday.FRI //true
day.equals(Weekday.FRI) //true

enum的方法

name()

返回常量名,例如:

1
2
String s = Weekday.SUN.name() // "SUN"

ordinal()

返回定义常量的顺序,例如

1
2
int n = Weekday.SUN.ordinal() // 0

但是需要注意的是,改变枚举常量定义的顺序就会导致ordinal()返回值发生变化。

如果枚举类型要和int转换,处于程序的健壮性考虑,就不能依靠ordinal()的返回值。因为enum本身就是一个类,所以我们可以每个枚举常量增加一个字段,并定义private的构造方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {
public static void main(String[] args) {
Weekday day = Weekday.SUN;
if (day.dayValue == 6 || day.dayValue == 7){
System.out.println("Work at home!");
} else {
System.out.println("Work at office!");
}
}
}

enum Weekday {
MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6), SUN(0);

public final int dayValue;

private Weekday(int dayValue){
this.dayValue = dayValue;
}
}

toString()

默认情况下,对枚举常量调用toString()会返回和name()一样的字符串。但是,toString()可以被覆写,而name()则不行。我们可以给Weekday添加toString()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Main {
public static void main(String[] args) {
Weekday day = Weekday.SUN;
if (day.dayValue == 6 || day.dayValue == 7){
System.out.println("Today is " + day + ". Work at home!");
} else {
System.out.println("Today is " + day + ". Work at office!");
}
}
}

enum Weekday {
MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日");

public final int dayValue;
public final String chinese;

private Weekday(int dayValue, String chinese){
this.dayValue = dayValue;
this.chinese = chinese;
}

@Override
public String toString() {
return this.chinese;
}
}

Switch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Main {
public static void main(String[] args) {
Weekday day = Weekday.SUN;
switch(day) {
case MON:
case TUE:
case WED:
case THU:
case FRI:
System.out.println("Today is " + day + ". Work at office!");
break;
case SAT:
case SUN:
System.out.println("Today is " + day + ". Work at home!");
break;
default:
throw new RuntimeException("cannot process " + day);
}
}
}

enum Weekday {
MON, TUE, WED, THU, FRI, SAT, SUN;
}

BigInteger

BigDecimal

定义

1
2
3
BigDecimal bd = new BigDecimal("123.4567");
System.out.println(bd.mutiply(bd)); // 15241.55677489

获取小数位数

1
2
3
4
5
6
7
BigDecimal d1 = new BigDecimal("123.45");
BigDecimal d2 = new BigDecimal("123.4500");
BigDecimal d3 = new BigDecimal("1234500");
System.out.println(d1.scale()); // 2,两位小数
System.out.println(d2.scale()); // 4
System.out.println(d3.scale()); // 0

剔除小数末尾0

1
2
3
4
5
6
7
8
9
10
BigDecimal d1 = new BigDecimal("123.4500");
BigDecimal d2 = d1.stripTrailingZeros();
System.out.println(d1.scale()); // 4
System.out.println(d2.scale()); // 2,因为去掉了00

BigDecimal d3 = new BigDecimal("1234500");
BigDecimal d4 = d3.stripTrailingZeros();
System.out.println(d3.scale()); // 0
System.out.println(d4.scale()); // -2,表示这是一个整数,并且末尾有两个0

设置精度

1
2
3
4
5
6
7
8
9
10
11
import java.math.BigDecimal;
import java.math.RoundingMode;

public class Main {
public static void main(String[] args) {
BigDecimal d1 = new BigDecimal("123.456789");
BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP); // 123.4568 四舍五入保留4位小数
BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN); //123.4567 直接截断
}
}

除法运算

BigDecimal做加、减、乘时,精度不会丢失,但是做除法时,存在无法除尽的情况,这时,就必须指定精度以及如何进行截断:

1
2
3
4
BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("23.456789");
BigDecimal d3 = d1.divide(d2, 10, RoundingMode.HALF_UP); //保留10位小数并四舍五入

比较

在比较两个BigDecimal的值是否相等时,要特别注意,使用equals()方法不但要求两个BigDecimal的值相等,还要求它们的scale()相等:

1
2
3
4
5
6
BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("123.45600");
d1.equals(d2); // False
d1.equals(d2.stripTrailingZeros()); // True
d1.compareTo(d2); //0 根据两个值的大小分别返回负数、正数和0,分别表示小于、大于和等于。

总是使用compareTo()比较两个BigDecimal的值,不要使用equals()!

常用工具类

  • Math:数学计算

  • Random:生成伪随机数

    要生成一个随机数,可以使用nextInt()nextLong()nextFloat()nextDouble()

    1
    2
    3
    4
    5
    6
    7
    Random r = new Random();
    r.nextInt(); // 2071575453,每次都不一样
    r.nextInt(10); // 5,生成一个[0,10)之间的int
    r.nextLong(); // 8811649292570369305,每次都不一样
    r.nextFloat(); // 0.54335...生成一个[0,1)之间的float
    r.nextDouble(); // 0.3716...生成一个[0,1)之间的double

    如果我们在创建Random实例时指定一个种子,就会得到完全确定的随机数序列:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import java.util.Random;
    public class Main {
    public static void main(String[] args) {
    Random r = new Random(12345);
    for (int i = 0; i < 10; i++) {
    System.out.println(r.nextInt(100));
    }
    }
    }

  • SecureRandom:生成安全的随机数

Copyright © ca01h 2019-2020 | 本站总访问量