Fork me on GitHub

Java代码规范和知识点

记录编写Java代码时的一些规范和知识点。

规范

编写类名时

类名首字母最好是大写字母。如:class School{}

编写类中属性时

属性名最好是小写字母。如:String name

编写类中方法时

一般是开头是动词,若是多个词连接而成,则除第一个词外其他单词首字母大写。
如:void addItems()

知识点

Java八种基本类型

其中六种数字类型(四个整数型,两个浮点型),一种字符型,一种布尔型。
注意:String不是基本类型之一。

六种数字类型:

  • byte(有符号八位)
  • short(有符号16位)
  • int(有符号32位)
  • long(有符号64位)
  • float(单精度32位,默认值0.0f,声明float类型必须加上f后缀)
  • double(双精度64位,默认值0.0d,其中后缀d可以不加)

一种字符型:

  • char(一个单一的16位Unicode字符)

一种布尔型:

  • boolean(表示一位的信息,只有true和false两个取值)

Java面向对象三要素

封装,继承和多态。

运算符优先级和结合顺序

参考https://www.cnblogs.com/zjfjava/p/5996666.html
运算符

其中&为长路与,&&为短路与(长路或|和短路或||用法类似)
无论长路与还是短路与
两边的运算单元都是布尔值
都为真时,才为真
任意为假,就为假
区别
长路与 两侧,都会被运算
短路与 只要第一个是false,第二个就不进行运算了

自增和自减运算符

Java中的i=i+ ++i结果为3,而c++运算结果为4。原因如下图:
自增自减

自增与自减运算符还遵循以下规律:

  • 可以用于整数类型byte、short、int、long,浮点类型float、double,以及字符串类型char。
  • 在Java5.0及以上版本中,它们可以用于基本类型对应的包装器类Byte、Short、Integer、Long、Float、Double、Character。
  • 它们的运算结果的类型与被运算的变量的类型相同。

使用System.arraycopy函数复制数组

把一个数组的值,复制到另一个数组中
System.arraycopy(src, srcPos, dest, destPos, length)

src: 源数组
srcPos: 从源数组复制数据的起始位置
dest: 目标数组
destPos: 复制到目标数组的起始位置
length: 复制的长度

程序如下:

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
public class TestJava {
public static void main(String[] args){
int aLen=(int)(5+Math.random()*10);
int bLen=(int)(5+Math.random()*10);
int[] a=new int[aLen];
int[] b=new int[bLen];
int i;
for(i=0;i<a.length;i++)
a[i]=(int)(Math.random()*100);

for(i=0;i<b.length;i++)
b[i]=(int)(Math.random()*100);

int[] c=new int[aLen+bLen];
System.arraycopy(a,0,c,0,aLen);
System.arraycopy(b,0,c,aLen,bLen);

for(i=0;i<a.length;i++){
System.out.print(a[i]+" ");
}
System.out.println();
for(i=0;i<b.length;i++){
System.out.print(b[i]+" ");
}
System.out.println();
for(i=0;i<c.length;i++){
System.out.print(c[i]+" ");
}
}
}

Arrays类操作

Arrays是针对数组的工具类,可以进行 排序,查找,复制填充等功能。 大大提高了开发人员的工作效率。
Arrays
详细介绍见how2j

方法中的参数为可变数量的参数

  • 要声明可变长度参数,在方法参数的数据类型之后添加一个省略号…。
  • 方法最多可以有一个可变长度参数。可变长度参数方法的可变长度参数必须是参数列表中的最后一个参数。

示例程序如下:

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
// 这里为了编写方便,把几个类都写在一起,实际编写时一个程序一个类才符合规范
class NvWuShen{
String name;
double hp; //血量
double atk; //攻击力
}

class NiShang{
String name;
double hp; //血量
double defense; //防御力
}

class YaYi extends NvWuShen{
public void attack(){
System.out.println(name+"攻击了谁呢?");
}

// 下面的attack方法Nishang对象是可变数量的参数
// 使用操作数组的方式处理niShangs对象即可
public void attack(NiShang... niShangs){
System.out.print(name+"攻击了");
for(int i=0;i<niShangs.length;i++){
System.out.print(niShangs[i].name+",");
}
}
}

public class TestJava {
public static void main(String[] args){
YaYi yaYi=new YaYi();
yaYi.name="绯红";
yaYi.attack();

NiShang taiTang=new NiShang();
taiTang.name="泰坦";
NiShang jiaoFu=new NiShang();
jiaoFu.name="教父";

yaYi.attack(taiTang);
yaYi.attack(jiaoFu,taiTang);
}
}

this关键字

通过this调用其他构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class NvWuShen {
String name;
double hp; //血量
double atk; //攻击力

// 无参构造方法
NvWuShen(){ }
// 重载构造方法
NvWuShen(String name){
this.name=name;
System.out.println("女武神名字:"+this.name);
}
NvWuShen(String name,double hp){
this(name); // 调用了上面的只有一个参数的构造方法
System.out.println(name+":时空断裂");
this.hp=hp;
}
}

关于修饰符

  • 属性通常使用private封装起来
  • 方法一般使用public用于被调用
  • 会被子类继承的方法,通常使用protected
    再就是作用范围最小原则
    简单说,能用private就用private,不行就放大一级,不用修饰符,再不行就用protected,最后用public。 这样就能把数据尽量的封装起来,没有必要露出来的,就不用露出来了

static关键字

类属性

由static修饰的属性叫做类属性(静态属性)(所有对象共享一个值)
类属性可以通过对象.类属性类.类属性访问,最好使用类.类属性来访问,这样更符合语意。

类方法

由static修饰的属性叫做类方法(静态方法)
访问一个类方法时,不需要创建对象,就能直接访问
如果一个方法,没有调用任何对象属性,那么就可以考虑设计为类方法,比如

1
2
3
public static void printGameDuration(){
System.out.println("已经玩了10分50秒");
}

注意:不能在一个类方法中调用对象方法

对象属性初始化

对象属性的初始化有三种方式
故意把初始化块,放在构造方法下面,问题:

这三种方式,谁先执行?谁后执行?
可以参考下面这个程序:(面试可能会问到)

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
/*
* 静态代码块;构造代码块;构造代码区别
* */
class Code{
static{
prt("1---显示表明在没有对象的时候初始化了类,所以静态代码块可以初始类,在静态代码区第一个被进栈");
}

Code(){
prt("3---无参;构造函数开始执行,这里表明构造代码只适合初始化特定对象,表明构造函数在对象建立以后第三个进栈");
}
Code(String a){//构造函数,有参
prt(a);
}

static {prt("2---显示表明构造代码块初始化了所有的对象。在对象的建立后进栈");
}

static void prt(String a){
System.out.println(a);
}


}
public class 静态构造代码块构造函数进栈过程 {
public static void main (String[] age){
Code c=new Code();
Code c1=new Code("3.1---构造函数开始执行;初始化了第二个对象c1.表明构造函数是针对特有的对象初始化");

}

}

输出结果为:

1
2
3
4
1---显示表明在没有对象的时候初始化了类,所以静态代码块可以初始类,在静态代码区第一个被进栈
2---显示表明构造代码块初始化了所有的对象。在对象的建立后进栈
3---无参;构造函数开始执行,这里表明构造代码只适合初始化特定对象,表明构造函数在对象建立以后第三个进栈
3.1---构造函数开始执行;初始化了第二个对象c1.表明构造函数是针对特有的对象初始化

可以看出,初始化块最先执行,而构造函数最后执行

所以说,使用下面这个程序新建一个对象时,对象的名字会是”泰坦”

1
2
3
4
5
6
7
8
9
10
11
class NiShang{
String name="逆熵";
double hp; //血量
double defense; //防御力
public NiShang(){
name="泰坦";
}
{
name="教父";
}
}

向上转型和向下转型

向上转型

就是子类转父类(父类对象可以是接口),那怎么判断是否转换成功,一个简单的识别方法是:将右边的当做左边的来用。比如说:

父类nvwushen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Nvwushen {
String name;
double hp; //血量
double atk; //攻击力

// 无参构造方法
Nvwushen(){ }

public void attack(){
System.out.println("女武神攻击");
}

public void defense(){
System.out.println("防御");
}
}

子类Yayi

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
class Yayi extends Nvwushen{
@Override
public void attack(){
System.out.println("yayi攻击了谁呢?");
}

public void cook(){
System.out.println("yayi会做饭");
}

}

public class BengHuai3 {
public static void main(String[] args){
Nvwushen nvwushen =new Nvwushen();
nvwushen.attack();
Yayi yayi=new Yayi();
// 上转型
// 把yayi当做普通nvwushen,肯定可以
nvwushen =yayi;
// 此时引用的是子类yayi的attack方法
nvwushen.attack();

}
}

Yayi继承了Nvwushen,而且yayi和nvwushen都有attack方法,那么将yayi向上转型为nvwushen类型是说得通的,因为可以把yayi当做普通的nvwushen。因此所有的子类转为父类都是可行的。同样道理,可以把猫当做普通动物看待,把热水当做普通的水看待。

向上转型需要注意的问题:

  • 向上转型时,子类单独定义的方法会丢失。比如上面的例子中nvwushen引用指向了子类Yayi,但是访问不到Yayi类单独定义的cook方法。
  • 子类引用不能指向父类对象。Yayi yayi=(Yayi)new Nvwushen();是不可以的。

向上转型的好处在多态中再说。

向下转型

就是父类转子类,不一定能转换成功。例如:

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
class Yayi extends Nvwushen{
@Override
public void attack(){
System.out.println("yayi攻击了谁呢?");
}

public void cook(){
System.out.println("yayi会做饭");
}

}

class Kiyana extends Nvwushen{
@Override
public void attack(){
System.out.println("kiyana攻击了谁呢");
}

}

public class BengHuai3 {
public static void main(String[] args){
Nvwushen nvwushen =new Nvwushen();
Yayi yayi=new Yayi();
// 子类转父类,肯定成功
nvwushen =yayi;
// 将nvwushen下转型为yayi,由于nvwushen引用所指向的是Yayi对象,所以把Yayi对象转换为Yayi对象是可行的。
yayi=(Yayi)nvwushen;


Kiyana kiyana=new Kiyana();
// 将nvwushen下转型为kiyana,由于nvwushen引用所指向的是Yayi对象,要把Yayi对象转换为Kiyana对象,是行不通的。
kiyana=(Kiyana) nvwushen;

}
}

我们可以总结出:

  • 向下转型的前提是父类对象指向的是子类对象(也就是说,在向下转型之前,它得先向上转型)
  • 向下转型只能转型为本类对象(yayi是不能变为kiyana的)

那么,我们为什么要先上转型,再下转型呢,可以参考:向下转型,里面也有介绍向下转型在多态中的运用,值得一看。

多态

多态可以理解为:同一个行为具有多个不同表现形式或形态的能力就是多态。

多态需要具备以下条件:

  • 继承:在多态中必须存在有继承关系的子类和父类。(也可以是实现接口)
  • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
  • 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

请看下面这个演示多态的例子:

首先,编写一个Die接口

1
2
3
4
public interface Die {
// 女武神死亡时,提供一个说不同台词的接口
public void die();
}

接着,女武神yayi和kiyana通过实现接口Die来说不同台词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Yayi extends Nvwushen implements Die{
@Override
public void attack(){
System.out.println("yayi攻击了谁呢?");
}

public void cook(){
System.out.println("yayi会做饭");
}

// 重写Die接口方法
@Override
public void die(){
System.out.println("舰长,不能再和你一起战斗了");
}
}

1
2
3
4
5
6
7
8
9
10
11
class Kiyana extends Nvwushen implements Die{
@Override
public void attack(){
System.out.println("kiyana攻击了谁呢");
}

@Override
public void die(){
System.out.println("这不可能");
}
}

编写nishang杀死nvwushen的方法kill(Die d),直接将Die接口作为参数传入

1
2
3
4
5
6
7
8
9
10
11
class Nishang{
String name="逆熵";
double hp; //血量
double defense; //防御力
public Nishang(){
name="泰坦";
}
public void kill(Die d){
d.die();
}
}

当我们想使用kill方法杀死不同女武神时,就不需要在Nishang类中重载多个kill方法,直接通过上转型将想要kill的对象传入即可。

1
2
3
Nishang nishang=new Nishang();
nishang.kill(yayi); //会显示出yayi死亡时的台词
nishang.kill(kiyana); //会显示出kiyana死亡时的台词

这样,通过kill的不同表现形式,我们实现了多态。

那么,如果我们不使用上转型,就要重载多个kill方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Nishang{
String name="逆熵";
double hp; //血量
double defense; //防御力
public Nishang(){
name="泰坦";
}
// 这样写会多出很多重复代码,而且要添加新对象时,又要进行重载,非常不方便
public void kill(Yayi yayi){
yayi.die();
}

public void kill(Kiyana kiyana){
kiyana.die();
}

......
}

对比能够得出上转型的好处:

  • 减少重复代码,使代码变得简洁。
  • 提高系统扩展性。

隐藏(与重写类似)

与重写类似,方法的重写是子类覆盖父类的对象方法 。而隐藏,就是子类覆盖父类的类方法。
针对类方法,可以理解为父子不同,子调用子,父调用父。父子相同,都调用父。
如下面例子:

1
2
3
4
5
6
Nvwushen nvwushen=new Nvwushen();
nvwushen.win(); // 父调用父
Yayi yayi=new Yayi();
yayi.win(); // 子调用子
Nvwushen nvwushen2 =new Yayi();
nvwushen2.win(); // 调用父

输出结果:

1
2
3
战斗胜利
芽衣胜利
战斗胜利

super关键字

super是子类调用父类构造方法,属性时使用。

  1. 若是父类中没有任何构造方法,则系统默认给父类加上一个无参构造方法,子类通过这个无参构造方法来构造。
  2. 若父类中只有有参构造方法,而不显式声明无参构造方法,则需要在子类构造方法中使用super声明父类有参的构造方法,否则会报错。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    class Yayi extends Nvwushen{
    Yayi(){
    super("影舞"); //在构造方法里必须是第一句,声明使用父类有参的构造方法
    System.out.println("芽衣:"+name);
    }
    }

    public class Nvwushen {
    // 无参构造方法
    //Nvwushen(){ }
    // 重载构造方法
    Nvwushen(String name){
    this.name=name;
    System.out.println("女武神名字:"+this.name);
    }
    }

super和this的异同:
super关键字

上图引自某博客

Object类中的方法

  • toString()
  • equals()
  • hashCode()
  • wait()
  • notify()
  • notifyAll()
  • getClass()
  • finalize()(用于垃圾回收,在java9及以上版本已弃用)
    其中toString,equals和hashCode三个方法比较常用,而wait,notify,notifyAll为线程同步方法,getClass会返回一个对象的类对象(在java反射机制中再详细了解)

下面主要介绍toString,equals和hashCode三个方法。

  1. toString()
    如果没有重写toString方法,则输出来的是类名+哈希编码
    1
    2
    Yayi yayi=new Yayi();
    System.out.println(yayi.toString());

输出:Yayi@1540e19d

重写Yayi类中的toString方法,让它返回一串有意义的字符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Yayi extends Nvwushen{
Yayi(){
name="影舞";
hp=1800;
atk=256;
}

@Override
public String toString(){
return "女武神名字:"+name
+",血量:"+hp
+",攻击力:"+atk;
}
}

public class BengHuai3 {
public static void main(String[] args){
Yayi yayi=new Yayi();
System.out.println(yayi.toString());

}
}

输出:女武神名字:影舞,血量:1800.0,攻击力:256.0
强烈建议每编写一个自定义的类,重写toString方法。用想要表达的字符串替代默认字符串更好。

  1. equals()
    默认的equals方法仅仅只是判断两个对象是否具有相同的引用,如果有相同的引用,则这两个对象一定相等。但是,根据实际情况,我们判断两个对象是否相等,一般都是看两个对象的name,sex之类的是否相等,而不是单单判断引用是否相等。

这里我们重写equals方法。假设两个Yayi对象名字相同时,就认为两个对象相等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Yayi extends Nvwushen{
@Override
public boolean equals(Object o){
if(o instanceof Yayi){
Yayi yayi=(Yayi)o;
return this.name == yayi.name;
}
return false;
}
}

public class BengHuai3 {
public static void main(String[] args){
Yayi y1=new Yayi();
y1.setName("绯红");
Yayi y2=new Yayi();
y2.setName("绯红");
Yayi y3=new Yayi();
y3.setName("影舞");

System.out.println("y1 equals y2:"+y1.equals(y2));
System.out.println("y1 equals y3:"+y1.equals(y3));
}
}

输出结果为:

1
2
y1 equals y2:true
y1 equals y3:false

  1. hashCode()
    一般来说,重写了equals方法后,也要重写hashCode编码,否则有可能出现equal方法判断出两个对象相等,但是输出的hashCode却不相等。

重写hashCode方法:

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
class Yayi extends Nvwushen{
@Override
public boolean equals(Object o){
if(o instanceof Yayi){
Yayi yayi=(Yayi)o;
return this.name == yayi.name;
}
return false;
}

@Override
public int hashCode(){
return name.hashCode();
}
}

public class BengHuai3 {
public static void main(String[] args){
Yayi y1=new Yayi();
y1.setName("绯红");
Yayi y2=new Yayi();
y2.setName("绯红");
Yayi y3=new Yayi();
y3.setName("影舞");

System.out.println("y1 equals y2:"+y1.equals(y2));
System.out.println("y1 equals y3:"+y1.equals(y3));

System.out.println("y1的哈希编码:"+y1.hashCode());
System.out.println("y2的哈希编码:"+y2.hashCode());
System.out.println("y3的哈希编码:"+y3.hashCode());
}
}

输出结果为:

1
2
3
4
5
y1 equals y2:true
y1 equals y3:false
y1的哈希编码:1039763
y2的哈希编码:1039763
y3的哈希编码:790733

可以看到两个相同的对象y1和y2哈希编码相同。

final关键字

final可以用来修饰类,方法,引用和基本类型变量。

  • final修饰类时,该类不能被继承。(如String类是public final class String,不能被继承)
  • final修饰方法时,该方法不能被重写。
  • final修饰引用,该引用只有一次指向对象的机会。
  • final修饰基本类型变量时,该变量只有一次赋值机会。
    在方法中用final修饰形参变量时,该变量不可再被另外赋值。
    而当final修饰简单pojo对象时,该对象中的属性可以被另外赋值,如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
       void addUser(final String name){
    name="小明"; //编译不通过
    }

    void addUser(final User user){
    user.name="小明"; //编译通过

    user=new User(); //编译不通过
    }

抽象类

在类中声明一个方法,这个方法没有实现体,是一个“空”方法。
这样的方法就叫抽象方法,使用修饰符“abstract" 。
当一个类有抽象方法的时候,该类必须被声明为抽象类。

抽象类也可以没有抽象方法,使用abstract修饰符修饰类时,该类便成为抽象类。
一旦一个类被声明为抽象类,就不能够被直接实例化。

那么,子类可以做成抽象类吗?
答案是肯定的,无论父类是不是抽象类,子类都可以作为抽象类。

抽象类中也可以有主方法,只是不常用。

抽象类和接口的区别:

  1. 子类只能继承一个抽象类,不能继承多个
    子类可以实现多个接口

  2. 抽象类可以定义
    public,protected,package,private
    静态和非静态属性
    final和非final属性
    但是接口中声明的属性,只能是
    public
    静态
    final的
    即便没有显式的声明

注:抽象类和接口中都可以有实体方法。
参考how2j java抽象类讲解

内部类

参考内部类

默认方法

在jdk8以上版本,可以在接口中定义默认方法,即使用default声明的有方法体的方法。
假如没有这种机制,那么每次在接口中新增一个方法时,其他实现了该接口的类也要重写方法。
而通过这种默认方法的机制,可以不重写默认方法,会直接得到这个方法。这样能够更好地进行拓展。

而当一个类实现多个接口,而接口中存在相同的默认方法时,使用接口名.super.默认方法来声明使用哪个接口的默认方法。

编写分支攻击接口:

1
2
3
4
5
6
7
8
public interface BranchAttack {
// 分支攻击的接口

// jdk8新特性,可以在接口中定义默认方法
default public void attack(){
System.out.println("这是分支攻击");
}
}

编写蓄力攻击接口:

1
2
3
4
5
6
7
8
public interface XuliAttack {
// 蓄力攻击的接口

// jdk8新特性,可以在接口中定义默认方法
default public void attack(){
System.out.println("这是蓄力攻击");
}
}

YaYi既会分支攻击,也会蓄力攻击,需要实现这两个接口

1
2
3
4
5
6
7
class Yayi extends Nvwushen implements BranchAttack,XuliAttack{
@Override
public void attack(){
BranchAttack.super.attack(); //通过这种方式声明使用哪个接口中的默认方法
XuliAttack.super.attack();
}
}

封装类

所有基本类型,都有对应的封装类。如:int类型对应Integer类。
数字封装类有:Byte,Short,Integer,Long,Float,Double, 这些类都是抽象类Number的子类。

自动装箱和自动拆箱:

  • 不需要调用构造方法,通过=符号自动把 基本类型 转换为 类类型 就叫装箱。
  • 不需要调用Integer的intValue方法,通过=就自动转换成int类型,就叫拆箱。

注意:自动装箱必须一一匹配对应类型,而自动拆箱只要拆出来的基本类型,其范围小于左侧基本类型的取值范围,就可以实现。

1
2
3
4
5
//这是可以实现的
Byte b=5;
Integer i=6;
long l=b;
l=i;

char对应的封装类为Character类,其常用方法如下:

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

public class TestChar {

public static void main(String[] args) {

System.out.println(Character.isLetter('a'));//判断是否为字母
System.out.println(Character.isDigit('a')); //判断是否为数字
System.out.println(Character.isWhitespace(' ')); //是否是空白
System.out.println(Character.isUpperCase('a')); //是否是大写
System.out.println(Character.isLowerCase('a')); //是否是小写

System.out.println(Character.toUpperCase('a')); //转换为大写
System.out.println(Character.toLowerCase('A')); //转换为小写

String a = 'a'; //不能够直接把一个字符转换成字符串
String a2 = Character.toString('a'); //转换为字符串

}
}

字符串转数字以及数字转字符串

  1. 数字转字符串的两种方法:
    方法1: 使用String类的静态方法valueOf
    方法2: 先把基本类型装箱为对象,然后调用对象的toString

  2. 字符串转数字:
    调用对应封装类的parse方法(例如:调用Integer的静态方法parseInt)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 数字转字符串
double d=6.66;
// 使用String的valueOf方法直接转换
String s=String.valueOf(d);

// 将基本类型double先装箱,再调用toString方法
Double d1=d;
String s1=d1.toString();

System.out.println(d);
System.out.println(d1);

// 字符串转数字
String str="3.1e5"; //除了字母e和E之外,使用其他字母会报错
double num=Double.parseDouble(str);
System.out.println(num);

字符串常用方法

  • charAt(int index):获取指定位置的字符
  • trim():去除字符串左右两边的空格
  • subString():截取子字符串(边界值是左闭右开)

    参考字符串常用方法

日期格式转换

日期转换为规定格式的字符串

使用SimpleDateFormat来规定日期格式,使用format方法转换日期格式。

1
2
3
4
5
6
7
// 日期转字符串
Date d=new Date();
// 设计日期格式为“年-月-日 时:分:秒”
SimpleDateFormat f=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 将日期转换为指定格式的字符串形式
String day=f.format(d);
System.out.println(day);

规定格式的字符串转换为日期

使用SimpleDateFormat来规定日期格式,使用parse方法转换日期格式。
注意:字符串格式必须与规定的格式一致,否则会抛出异常。

1
2
3
4
5
6
7
8
9
10
// 字符串转日期
String str="2015-09-12 08:00:00";
// 指定日期格式
SimpleDateFormat f1=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try{
Date d1=f1.parse(str);
System.out.println(d1);
}catch (ParseException e){
e.printStackTrace();
}

准备一个长度是9的日期数组
使用2000年-2018年之间的随机日期初始化该数组
按照这些日期的时间进行升序排序
比如 2015-1-21 12:33:22 就会排在 2003-4-21 19:07:23 前面,因为它的时间更小,虽然日期更大
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

/**
* 准备一个长度是9的日期数组
* 使用2000年-2018年之间的随机日期初始化该数组
* 按照这些日期的时间进行升序排序
* 比如 2015-1-21 12:33:22 就会排在 2003-4-21 19:07:23 前面,因为它的时间更小,虽然日期更大
*/
public class test {
public static void main(String[] args){
int i,j;
SimpleDateFormat f=new SimpleDateFormat("yyyy");
String startYear="2000"; //开始时间
String endYear="2019"; //结束时间
try{
long start=(f.parse(startYear)).getTime(); //获取开始时间,也就是2000-01-01 00:00:00的毫秒数
long end=(f.parse(endYear)).getTime()-1; //获取结束时间,也就是2019-01-01 00:00:00的毫秒数减1

//获取随机日期
String[] day=new String[9];
SimpleDateFormat fm=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for(i=0;i<day.length;i++){
long millsec=(long)(Math.random()*(end-start)+start); //获取符合时间范围的随机毫秒数
day[i]=fm.format(new Date(millsec)); // 日期转字符串
}

// 分为3行3列输出
System.out.println("未排序的日期顺序为:");
for(i=0;i<day.length;i++){
if( i%3==0 && i!=0)
System.out.println();
System.out.print(day[i]+" ");
}

// 按照格式取出“HHmmss”,然后使用parseInt化为整数,比较大小即可
SimpleDateFormat fmt=new SimpleDateFormat("HHmmss");

// 冒泡排序
for(j=0;j<day.length;j++){
for (i=0;i<day.length-j-1;i++){
String s0=fmt.format(fm.parse(day[i])); // 将字符串转为Date格式后,再转为只有“时分秒”的字符串格式
int i0=Integer.parseInt(s0);
String s1=fmt.format(fm.parse(day[i+1]));
int i1=Integer.parseInt(s1);
if(i0>i1){
String temp=day[i];
day[i]=day[i+1];
day[i+1]=temp;
}
}
}

// 分为3行3列输出
System.out.println("\n\n排序后的日期顺序为:");
for(i=0;i<day.length;i++){
if( i%3==0 && i!=0)
System.out.println();
System.out.print(day[i]+" ");
}

}catch(ParseException e){
e.printStackTrace();
}
}
}

输出结果:
日期格式

使用Calendar类操作日期

java推荐使用Calendar类操作日期。

1
2
3
4
5
6
7
8
//采用单例模式获取日历对象Calendar.getInstance();
Calendar c = Calendar.getInstance();

//通过日历对象得到日期对象
Date d = c.getTime();

Date d2 = new Date(0);
c.setTime(d2); //把这个日历,调成日期 : 1970.1.1 08:00:00

两个方法:

  • add方法:在原日期上增加年/月/日
  • set方法:直接设置年/月/日
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Calendar c=Calendar.getInstance();
    Date now=new Date();

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    // 下个月后的倒数第三天
    c.setTime(now);
    c.add(Calendar.MONTH,2); // 两个月后的今天
    c.set(Calendar.DATE,1); // 设置月初第一天
    //c.add(Calendar.DATE,-3); //下个月倒数第三天
    System.out.println("下个月后的倒数第三天是:"+sdf.format(c.getTime()));

异常

java中的异常Exception和Error都继承Throwable类。所以说,捕获异常时,也可以抛出Throwable。
Exception里又分为运行时异常(RuntimeException)和可查异常(CheckedException)

  • 可查异常:是必须处理的异常,要么使用try,catch捕获再进行处理,要么使用throw或throws往外抛
  • 运行时异常:不是必须要try,catch的异常。
    常见的运行时异常有:
    1. 除数不能为0异常:ArithmeticException 
    2. 下标越界异常:ArrayIndexOutOfBoundsException 
    3. 空指针异常:NullPointerException 
    

文件操作

创建带路径的文件

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

public class FileOperation {
public static void main(String[] args) throws Exception{
// 创建带路径的文件
// 如果给定的路径是根路径,则可以直接使用createNewFile()方法创建
// 如果要创建的文件存在目录,则无法进行创建
// 合理的做法应该是创建文件前判断父路径是否存在
// 若不存在,应该先创建目录(mkdir创建多级目录),再创建文件
File file=new File("D:\\xje\\java测试文件操作\\testFile\\test.txt");
if(!file.getParentFile().exists()){ //如果父路径不存在
file.getParentFile().mkdirs(); // 创建父路径
}
System.out.println(file.createNewFile()); // 创建新文件
}
}

按照上面的程序就能在指定路径下创建新文件了,在实际开发中也会用到。

使用文件流来操作文件内容

基本操作参考how2j文件流操作

这里写一个文件拆分程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileStreamOperation {
public static void main(String[] args){

// 拆分文件,将pkqIcon.JPG拆分为每10KB一份

try{
File file=new File("D:\\xje\\java测试文件操作\\testFile\\pkqIcon.JPG");
// if(!file.getParentFile().exists()){
// // 父目录不存在,则创建父目录
// file.getParentFile().mkdirs(); // 使用mkdirs可以创建多层目录
// }
// file.createNewFile(); // 创建新文件


// 文件输出流
FileInputStream fis=new FileInputStream(file);

// 创建字节数组
byte[] all=new byte[(int)file.length()];

int filePart=(int)(Math.ceil(file.length()/(1024.0*10))); //向上取整
int remainder=1024*10;
if((file.length()%(1024*10))!=0){ // 当不能被10kb整除时,remainder就是除下来的余数
remainder=(int)(file.length()%(1024*10));
}
for(int i=0;i<filePart;i++){
File fout=new File(file.getParent(),i+".part");
FileOutputStream fos=new FileOutputStream(fout);
if(i<filePart-1){ // 前面部分都是以10KB作为单位
fos.write(all,i*1024*10,1024*10);
}else{ // 以余数remainder作为最后一部分的容量
fos.write(all,i*1024*10,remainder);
}
// 关闭输出流
fos.close();
}
// 关闭输入流
fis.close();
}catch(IOException e){
e.printStackTrace();
}

}
}

一个使用文件输入输出流来进行简单加密和解密的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import java.io.*;
import java.nio.charset.Charset;

/**
* 使用FileReader和FileWriter对文件中的内容进行加密解密
* 加密算法:
* 数字:
如果不是9的数字,在原来的基础上加1,比如5变成6, 3变成4
如果是9的数字,变成0
字母字符:
如果是非z字符,向右移动一个,比如d变成e, G变成H
如果是z,z->a, Z-A。
字符需要保留大小写
非字母字符
比如',&^ 保留不变,中文也保留不变
*
* 解密算法是加密算法的逆过程
*/

public class FileReaderWriter {
public static void main(String[] args){
File file=new File("D:\\xje\\java测试文件操作\\testFile\\test.txt"); //从test.txt中获取要加密的密文
if(!file.getParentFile().exists()){
// 父路径不存在,则创建父路径
file.getParentFile().mkdirs();
}

// 由于使用FileReader会使用默认编码格式,如果是中文系统,就是GBK编码
// 这里我们使用InputStreamReader来设置编码格式,以避免中文乱码
try(FileInputStream fis=new FileInputStream(file);
InputStreamReader isr=new InputStreamReader(fis, Charset.forName("GBK")); //记事本中是GBK编码保存的
OutputStreamWriter osr=new OutputStreamWriter(new FileOutputStream("D:\\xje\\java测试文件操作\\testFile\\encodedFile.txt"))){
// 创建字符数组
char[] all=new char[(int)file.length()];
// 将文件内容对应的ASCII码读到all数组中
isr.read(all);
for(int i=0;i<all.length;i++){ //加密,算法写在程序最上面
// 对数字加密
if(all[i]>='0' && all[i]<'9'){
all[i]=(char)(all[i]+1);
}else if(all[i]=='9'){
all[i]='0';
}else if((all[i]>='a' && all[i]<'z') || (all[i]>='A' && all[i]<'Z')){
all[i]=(char)(all[i]+1);
}else if(all[i]=='z' || all[i]=='Z'){
all[i]=(char)(all[i]-25);
}
}

// 将加密后的字符数组all写入encodedFile.txt文件中
osr.write(all);

}catch (IOException e){
e.printStackTrace();
}

// 解密是加密的逆过程,这里就不编写了

}
}

重要:使用文件输入输出流复制文件

使用FileInputStream和FileOutputStream进行复制操作

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
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyFile {
public static void main(String[] args){
// 设置开始时间
long startTime=System.currentTimeMillis();

File inFile=new File("D:\\xje\\java测试文件操作\\testFile\\pkqIcon.JPG"); //这是要复制的文件
if(!inFile.exists()){ // 若文件不存在,则输出错误信息,退出程序
System.out.println("源文件不存在,请输出正确的文件路径");
System.exit(1);
}

File outFile=new File("D:\\xje\\java测试文件操作\\out.JPG");
if(!outFile.getParentFile().exists()){// 如果输出文件的父目录不存在,则创建父目录
outFile.getParentFile().mkdirs();
}

// 实现文件复制操作
try(FileInputStream fis=new FileInputStream(inFile);
FileOutputStream fos=new FileOutputStream(outFile)){
int temp=0; // 保存每次读取的数据长度
byte[] data=new byte[1024]; // 每次读取1024,也就是1k字节
// 将每次读取进来的数据保存在字节数组里面,并且返回读取的个数
while((temp=fis.read(data))!=-1){ // 循环读取数据
fos.write(data,0,temp); // 将数据输出到指定文件中
}

// 设置结束时间(毫秒数)
long endTime=System.currentTimeMillis();
System.out.println("复制文件所花时间为:"+(endTime-startTime));

}catch(IOException e){
e.printStackTrace();
}
}
}

缓存流

主要是BufferedReader和PrintWriter(能够一次读取或写入较多数据,方便用户操作)。
BufferedReader程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class test {
public static void main(String[] args){
File file=new File("D:\\xje\\java测试文件操作\\testFile\\test.txt");
// 使用文件输入流实例化BufferedReader类对象
try(BufferedReader buf=new BufferedReader(new FileReader(file))){
String str=null; // 接收输入的数据
while((str=buf.readLine())!=null){
System.out.println(str); // 循环读取文件中的内容
}
}catch(IOException e){
e.printStackTrace();
}

}
}

PrintWriter程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.*;

public class test {
public static void main(String[] args){
File file=new File("D:\\xje\\java测试文件操作\\testFile\\outPrint.txt");
if(!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
// 使用文件输入流实例化BufferedReader类对象
try(PrintWriter pw=new PrintWriter(new FileWriter(file))){
pw.print("hello"); // 不换行写入
pw.println(" how are you?"); // 换行写入
pw.println("追梦少年"); // 换行写入
String str="蓝翔"; // 支持类似于c语言的格式
pw.printf("挖掘机技术哪家强?中国山东找%s",str);
}catch(IOException e){
e.printStackTrace();
}

}
}

对象序列化

java创建的对象都只保存在内存中,所以这些对象生命周期不会超过JVM进程。
如果我们希望在JVM进程结束后对象仍然可以保存下来,那就需要将对象序列化。

要将对象序列化必须使对象实现Serializable接口,这个接口没有任何操作方法存在,因为它是一个标识接口,表示一种能力。

编写一个实现Serializable接口的类ObjectSerializable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.Serializable;

public class ObjectSerializable implements Serializable {
String name; //名称
String description; //描述

public ObjectSerializable(String name,String description){
this.name=name;
this.description=description;
}

@Override
public String toString(){
return "类名:"+name
+"\n描述:"+description;
}

}

使用ObjectOutputStream将对象序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.*;

public class WriteSerializable {
public static void main(String[] args){

// 输出的文件
File outFile=new File("D:\\xje\\java测试文件操作\\testFile\\outFile.txt");
if(!outFile.getParentFile().exists()){
outFile.getParentFile().mkdirs();
}
// 使用ObjectOutputStream写入对象
try(ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(outFile))){
oos.writeObject(new ObjectSerializable("序列化类","我是实现了Serializable接口的类"));
}catch(IOException e){
e.printStackTrace();
}

}
}

使用ObjectInputStream实现反序列化操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.*;

public class ReadSerializable {
public static void main(String[] arge){
// 输入的进行反序列化的文件
File file=new File("D:\\xje\\java测试文件操作\\testFile\\outFile.txt");
if(!file.exists()){
System.out.println("请输入正确的文件路径");
System.exit(1);
}
// 使用ObjectInputStream实现反序列化操作
try(ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file))){
Object obj=ois.readObject(); // 反序列化对象
ObjectSerializable os=(ObjectSerializable) obj; // 下转型(存在安全隐患,最好使用反射机制进行操作)
System.out.println(os);
}catch(IOException e){
e.printStackTrace();
}catch (ClassNotFoundException e){
e.printStackTrace();
}
}
}

输出结果为:
Serializable