Mrli
别装作很努力,
因为结局不会陪你演戏。
Contacts:
QQ博客园

重拾Java笔记

2020/05/18 java
Word count: 12,186 | Reading time: 49min

重拾Java笔记

工作主要用Java, 因此开始准备Java基础再补补。根据小猴子1024-JAVA基础整理笔记

命名规范:

没怎么写, 所以一直忘, 这次写在最前面, 便于翻阅。

大驼峰命名(UpperCamelCase):每个单词的第一个字母大写,其他字母小写。e.g.MyException

小驼峰命名(lowerCamelCase):如果仅有一个单词,那么所有字母全部小写,如果是两个及以上的单词组成的名称,那么除了第一个单词是全部小写外,其他都是的首字母大写,其他字母小写。e.g.getMyName


1.1 包的命名

包的命名由全部小写的单词组成。一般使用公司的域名的作为自己程序包的唯一前缀,使用倒域名规则,例如:com.baidu.项目名,然后针对每个具体的模块在区分每个模块包名,例如:论坛模块的整体包名:com.baidu.项目名.tribune(域名倒写)

1.2 类的命名

类的命名遵循大驼峰命名的规则

1.3 接口的命名

接口命名遵循大驼峰命名的规则,以大写的I开头,表示这是一个接口,以able或ible截尾。

1.4 变量命名

变量的命名遵循小驼峰命名的规则,其中控件的变量建议使用控件缩写+逻辑名称的格式,例如:

1.5 常量的命名

常量名称的每个单词都大写,并且每个单词之间通过下划线(_)连接,例如:

1.6 方法的命名

方法的命名遵循小驼峰命名的规则,以动词+名词的方式组成,例如初始化view:initView()。

1.7 资源文件命名

全部小写,并通过下划线连接。

1.7.1 布局文件的命名

作者:summer_七七
链接:https://www.jianshu.com/p/511cc270400f
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


class与文件名:

在一个Java文件里面,可以声明多个class,但是只能声明一个public class

  • 如果使用class来声明类,文件名可以是任何合法的文件名称,文件名不需要和Class类一致
  • 如果采用public class来声明class,那么文件名必须和类名一致

结论: 使用javac命令所编译出的class文件的名称跟java的文件名没有关系,而是跟类名一致

函数传参:

  • 如果参数是基本数据类型, 是会生成一个新的形参
  • 如果参数是引用数据类型(不包括封装数据类型), 那么会生成该对象的引用(类、 接口类型、 数组类型、 枚举类型、 注解类型、 字符串型)==>引用数据类型变量,调用方法时作为参数是按引用传递

参看: Java中的基本数据类型和引用数据类型的区别

代码块的分类

使用{}括起来的代码被称为代码块,根据其位置和声明的不同可以分为下面4种:

  • 局部代码块,在方法中出现,限定变量生命周期,及早释放,提高内存利用率
  • 构造代码块,在类中方法外出现;多个构造方法方法中相同的代码存放到一起,每次调用构造都执行,并且在构造方法前执行
  • 静态代码块, 在类中方法外出现,并加上static修饰;用于给类进行初始化,在加载的时候就执行,并且只执行一次。一般用于加载驱动。
  • 同步代码块(后面多线程部分会讲解)

执行顺序:

1.静态代码块,随着类加载而加载,且只执行一次
2.构造代码块,每创建一个对象就会执行一次,优先于构造方法执行
3.构造方法,每创建一个对象就会执行一次

this指针

▲注意点: 在构造函数中调用该对象的另一个构造方法时, this(实参)必须写在最前面

1
2
3
4
5
6
7
8
//构造方法
//需求:在创建日期对象的时候,默认的日期是:1970-1-1
MyDate(){
//通过this调用有参的构造方法
this(1970,1,1);//必须出现在第一行,否则将编译报错
//构造方法不能这样调用
//MyDate(1970,1,1);Error
}

继承问题:

静态代码块Fu
静态代码块Zi
构造代码块Fu
构造方法Fu
构造代码块Zi
构造方法Zi

分析:
1.系统将Fu.class和Zi.class分别加载到方法区的内存里面,静态代码会随着.class文件一块加载到方法区里面,所以先打印出了静态代码块中的内容。
2.构造代码块优先于构造方法执行,父类初始化之前,所以打印出父类中的构造代码块和构造方法中的内容。

多态:

在工作当中尽量面向抽象编程,不要面向具体编程,即合理利用多态——SOLID原则中依赖倒置:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程。

多态的优点

  • 提高程序的扩展性
  • 降低代码之间的耦合

用法

  • 向上转型:上面代码中子类向父类型进行转换,是自动类型转换
  • 向下转型: 父类向子类型转换,是强制类型转换

重写

重写,也叫做覆盖,当父类中的方法无法满足子类需求时,子类可以将父类的方法进行重写编写来满足需求。比如孩子继承了父亲的房子,可以将房子重新装修。
方法重写的条件:

  • 两个类必须是继承关系
  • 必须具有相同的方法名,相同的返回值类型,相同的参数列表.
  • 重写的方法不能比被重写的方法拥有更低的访问权限。
  • 重写的方法不能比被重写的方法抛出更宽泛的异常。(关于异常后面的章节再讲。)
  • 私有的方法不能被重写。
  • 构造方法无法被重写,因为构造方法无法被继承。
  • 静态的方法不存在重写。
  • 重写指的是成员方法,和成员变量无关。

Super关键字:

什么时候使用super?

  • 子类和父类中都有某个数据,例如,子类和父类中都有name这个属性。如果要再子类中访问父类中的name属性,需要使用super。例1
  • 子类重写了父类的某个方法(假设这个方法名叫m1),如果在子类中需要调用父类中的m1方法时,需要使用super。例1
  • 子类调用父类中的构造方法时,需要使用super。

Object类之finalize方法

java对象如果没有更多的引用指向它(引用技术),则该java对象成为垃圾数据,等待垃圾回收器的回收,垃圾回收器在回收这个java对象之前会自动调用该对象的finalize方法==>可以理解为解析函数

访问控制权限

方法访问控制权限

修饰词 本类 同一个包的类 子类 任何地方
private × × ×
default(默认) × ×
protected ×
public

方法访问控制权限

▲。注意以上对类的修饰只有:public和default,内部类除外

priavte和public都比较好理解和记忆,这里就不演示了,主要演示一下不同包下的两个具有父子关系的类里面使用protected和default的区别。

构造函数的权限问题:

public是一个访问权限(访问修复饰符)。一般构造函数可加可不加public。

  • 如果加上制public,就代表此类可以对外开放,其他的类可以继承它,外部也可以实例化该对象。
  • 如果不加public,则默认的修饰词是default,表示可以被这个类的子类或者和这个类同包的类调用。

除了这两个,你还可以添加private和default

Final关键字:

特点为确定不可变

  • final修饰的类无法被继承。
  • final修饰的方法无法被重写。
  • final修饰的局部变量,一旦赋值,不可再改变。
  • final修饰的成员变量必须初始化值。

static关键字

static的作用

  • static可以修饰变量,被static修饰的变量叫做静态变量,静态变量在类加载阶段赋值,并且只赋值一次。请看例1
  • static可以修饰方法,被static修饰的方法叫做静态方法,不用创建对象就能能直接访问该方法,即使用类名.静态方法名的方式。静态方法不能访问非静态的数据,静态方法不能使用this。请看例2
  • static可以定义静态语句块,静态语句块在类加载阶段执行,并且只执行一次,并且是自上而下的顺序执行,在构造方法之前执行。请看例3

static修饰的变量、方法、代码块都是隶属于**类(class)**级别的,跟对象无关。某一类物体如果可以被多个其他物体所共享,那么可以将这类物体使用static修饰。
比如wifi,多个人可以共同使用同一个wifi,所以wifi可以使用static修饰。手机是每人使用自己的,就不能用static修饰。

抽象类的特点

  • 抽象类无法被实例化,无法创建抽象类的对象。
  • 虽然抽象类没有办法实例化,但是抽象类也有构造方法,该构造方法是给子类创建对象用的。这也算是多态的一种。
  • 抽象类中不一定有抽象方法,但抽象方法必须出现在抽象类中。
  • 抽象类中的子类可以是抽象类,如果不是抽象类的话必须对抽象类中的抽象方法进行重写。
  • 抽象类和抽象方法不能被final修饰

接口:

  • 接口中只能出现常量和抽象方法(jdk8之后可以有default方法)
  • 接口里面没有构造方法,无法创建接口的对象
  • 接口和接口之间支持多继承,即一个接口可以有多个父接口
  • 一个类可以实现多个接口,即一个类可以有多个父接口
  • 一个类如果实现了接口,那么这个类需要重写接口中所有的抽象方法(建议),如果不重写则这个类需要声明为抽象类(不建议)

equals

== 两边如果是引用类型,则比较内存地址,地址相同则是true,反之则false.

  • Object中的equals方法比较的是两个引用的内存地址。
  • 但是在现实的业务逻辑当中,不应该比较内存地址,应该比较地址里面的内容,所以需要对equals方法进行重写。

==>▲注意:在使用自己创建的类进行equals比较时,一定要先重写equals方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//根据需求规定重写equals方法
//s1.equals(s2);
public boolean equals(Object obj){

if(this==obj){
return true;
}

if(obj instanceof Star){
Star s = (Star)obj;
if(s.id == id && s.name.equals(name)){
return true;
}
}

return false;
}

内部类的分类

内部类,顾名思义就是在一个类的内部声明一个类。内部类主要分为:

  • 静态内部类
  • 匿名内部类
  • 成员内部类
  • 局部内部类

异常的分类

异常: 指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止——JVM处理异常的方式是中断处理。

异常主要分为:Error、一般性异常、RuntimeException

  • Error(强制中断错误):如果程序出现了Error,那么将无法恢复,只能重新启动程序,最典型的Error的异常是:OutOfMemoryError
  • Exception(一般性异常(编译时异常):出现了这种异常必须在程序里面显示的处理,否则程序无法编译通过
  • RuntimeException(运行时异常):此种异常可以不用显示的处理,例如被0除异常,java没有要求我们一定要处理。

异常继承结构图

JVM是如何处理异常的

  • main方法自己将该问题处理,然后继续运行
  • 自己没有针对的处理方式,只有交给调用main的jvm来处理,jvm有一个默认的异常处理机制。例如上面出现的ArithmeticException,jvm在控制台里面打印出来了异常信息。

大致流程: native method自己解决->交给Main解决->交给JVM解决

更好的讲解: B站视频

throw和throws

throws

  • 用在方法声明后面,跟的是异常类名 public void m1() throws Exception
  • 可以跟多个异常类名,用逗号隔开
  • 表示抛出异常,由该方法的调用者来处理 (向上抛出指定异常)

throw

  • 用在方法体内,跟的是异常对象名 ==> throw new Exception()
  • 只能抛出一个异常对象名
  • 表示抛出异常,由方法体内的语句处理,需要直接在此处解决异常(在当前语句抛出指定异常)

自定义异常

1.自定义异常类一般都是以Exception结尾,说明该类是一个异常类
2.自定义异常类,必须的继承Exception或者RuntimeException
- 继承Exception:那么自定义的异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try…catch
- 继承RuntimeException:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyException extends Exception{
/**
* 添加一个空参数的构造方法
*/
public MyException(){
super();
}

/**
* 添加一个带异常信息的构造方法
* 查看源码发现,所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常信息
*/
public MyException(String s){
super(s);
}
}

catch

1
2
3
4
5
catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {
// 出现多个异常,采取同样的处理措施
// 多个异常见用 | 隔开
// 多个异常必须是平级关系
}

字符串的不可变性

String类不能被继承

通过源码可以看到String类前面加了final修饰,因此String类是不能够被继承的。将其设置为不能被继承的原因是为了减少歧义。

字符串(String)的不可变性

String创建好之后值是不可以被改变的,这里指的是在堆中的字符串的值是不可以被改变。

String不可变的主要原因是其底层使用了一个final修饰的byte数组(jdk9之后版本中),final修饰的变量是不能被改变的。在jdk8版本中,String底层使用的是final修饰的char数组。这个版本之间的变化。

字符串常量池

我们声明的字符串会放到一个叫做字符串常量池的地方,这样可以减少内存的使用,字符串常量池是堆的一部分。

如果用new String("monkey")会在字符串常量池中再建一个monkey, 其实是浪费了内存。所以开发中建议使用String s = “monkey1024”;这种方式创建字符串对象,可以减少堆内存的使用。==>比较两个字符串是否一致最好使用equals方法(看引用的内存地址是否一致)

详细请看: http://www.monkey1024.com/javase/481

JVM内存图

String、StringBuffer、StringBuilder

  • 如果需要对字符串进行频繁拼接的话,建议使用StringBuffer或者StringBuilder

StringBuffer

  • StringBuffer是一个字符串缓冲区,如果需要频繁的对字符串进行拼接时,建议使用StringBuffer。
  • StringBuffer的底层是byte数组(jdk9之后),jdk8中底层是char数组,如果没有明确设定,则系统会默认创建一个长度为16的byte类型数组,在使用时如果数组容量不够了,则会通过数组的拷贝对数组进行扩容,所以在使用StringBuffer时最好预测并手动初始化长度,这样能够减少数组的拷贝,从而提高效率。

StringBuilder和StringBuffer的区别

通过API可以看到StringBuilder和StringBuffer里面的方法是一样的,那他们有什么区别呢?
StringBuffer是jdk1.0版本中加入的,是线程安全的,效率低
StringBuilder是jdk5版本加入的,是线程不安全的,效率高

什么是自动拆箱和自动装箱?

  • 自动装箱:把基本类型转换为包装类类型
  • 自动拆箱:把包装类类型转换为基本类型

集合

接口Collection: 由三个接口组成——List / Set / Queue

集合的由来

数组长度是固定,如果要改变数组的长度需要创建新的数组将旧数组里面的元素拷贝过去,使用起来不方便。
java给开发者提供了一些集合类,能够存储任意长度的对象,长度可以随着元素的增加而增加,随着元素的减少而减少,使用起来方便一些。

集合类的一些特点

  • List:里面存放的数据是有顺序的,可以存放重复的数据。
  • Set:里面存放的数据是没有顺序的,不能存放重复的数据。
  • Queue:是一个队列,里面的数据是先进先出,可以存放重复的数据。

区别

  • 区别1:
    • 数组既可以存储基本数据类型,又可以存储引用数据类型,基本数据类型存储的是值,引用数据类型存储的是地址值
    • 集合只能存储引用数据类型(对象),如果存储基本数据类型时,会自动装箱变成相应的包装类
  • 区别2:
    • 数组长度是固定的,不能自动增长
    • 集合的长度的是可变的,可以根据元素的增加而自动增长

List两个子类的特点

ArrayList:

  • 底层数据结构是数组,查询快,增删慢。

LinkedList:

  • 底层数据结构是链表,查询慢,增删快。

ArrayList和LinkedList的区别

  • ArrayList底层是数组结果,查询和修改快
  • LinkedList底层是链表结构的,增和删比较快,查询和修改比较慢
  • 共同点:都是线程不安全的

ArrayList线程安全的方案

如果使用ArrayList需要考虑线程安全的问题,有两种方案:

  • 可以使用Collections工具类中的synchronizedList方法可以将ArrayList变成线程安全的

    1
    List list = Collections.synchronizedList(new ArrayList());
  • 使用java.util.concurrent包下面的CopyOnWriteArrayList,使用方式跟ArrayList一样

集合数组的互转

集合转数组:

1
2
3
4
5
6
7
8
9
10
11
12
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");

//当集合转换数组时,数组长度如果是<=集合的size时,转换后的数组长度等于集合的size
//如果数组的长度大于了size,分配的数组长度就和你指定的长度一样
String[] array = list.toArray(new String[3]);
for(String s : array){
System.out.println(s);
}

数组转集合

1
2
3
4
5
6
7
8
9
10
11
12
13
// 注意转换后的集合不能调用其add方法向里面添加数据,否则会报出UnsupportedOperationException

//数组转集合
String[] arr = {"a","b","c"};
//将数组转换成集合
List<String> listArray = Arrays.asList(arr);
//不能添加
//listArray.add("d");
System.out.println(listArray);
//通过这种方式将listArray转换成真正的ArrayList
ArrayList<String> arrayList = new ArrayList<String>(listArray);

//通过Arrays.asList((T… a))的源码可以看到,这里面返回的ArrayList是在Arrays类里面定义的一个内部类,并非java.util包下的ArrayList。

基本数据类型的数组转换成集合,会将整个数组当作一个对象转换,下面程序将会打印出list的对象地址

1
2
3
4
int[] arr = {1,2,3,4,5};            
List<int[]> list = Arrays.asList(arr);
System.out.println(list);
// ==>[[I@282ba1e]

==>▲.将数组转换成集合,数组中的数据必须是引用数据类型

1
2
3
4
Integer[] arr = {11,22,33,44,55};                    
List<Integer> list = Arrays.asList(arr);
System.out.println(list);
// ==>[11, 22, 33, 44, 55]

Collection集合

Set的特点

Set里面存储的元素不能重复,没有索引,存取顺序不一致。

▲.这里需要注意:在向HashSet中存放自定义类型对象时,一定要重写hashCode和equals方法,原因是无重复的话需要比较, 所以得。

TreeSet简介

TreeSet的特点是可以对存放进去的元素进行排序

∴ 使用TreeSet存储自定义类型。这里还是存储之前定义的Person对象,需要实现Comparable接口并且重写compareTo方法,先根据name的字典顺序排序,然后再根据年龄进行排序。

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package top.nymrli.day07;

import javafx.scene.PerspectiveCamera;
import javafx.util.converter.PercentageStringConverter;

import java.util.*;

/**
* @Program: testIDEA
* @Description: Set测试
* @Author: MrLi
* @Create: 2020-05-16 09:09
**/

public class day07 {
public static void main(String[] args) {
// HashSet<String> hs = new HashSet<>();
// boolean b1 = hs.add("a");
// System.out.println(hs);
// boolean c1 = hs.add("b");
// System.out.println(hs);
// for (String s : hs) {
// System.out.println(s);
// }

// Q1:
// HashSet<Integer> hashSet = new HashSet<>();
// Random random = new Random();
// while (hashSet.size() < 10) {
// int num = random.nextInt(20);
// hashSet.add(num);
// }
//
// for (Integer i : hashSet) {
// System.out.println(i);
// }

// Q2;Treeset
TreeSet<Person> ts = new TreeSet<>();
ts.add(new Person("cl", 30));
ts.add(new Person("qsy", 25));
ts.add(new Person("sxh", 30));
ts.add(new Person("ll", 15));
for (Person p : ts) {
System.out.println(p);
}


}
}


class Person implements Comparable<Person> {
private String name;
private int age;

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public int getAge() {
return age;
}

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

public String getName() {
return name;
}

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


@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(getName(), person.getName());
}

@Override
public int hashCode() {
return Objects.hash(getName(), age);
}


@Override
public int compareTo(Person o) {
int nameSame = this.name.compareTo(o.name);
if (nameSame != 0){ // 如果name不相等
// 当compareTo方法返回正数的时候,系统将元素存储到右边,所以集合存取顺序一致
return nameSame;
}else{
return this.age - o.age;
}
}

}

Map接口概述

map中的元素是以键-值的方式存在的,通过键可以获取到值,键是不可以重复的,跟地图比较像,通过一个坐标就可以找到具体的位置。该接口由三个类实现: HashMap / HashTable / AbstractMap

▲与前两个相比,添加元素的函数由add => put

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 day08_Map {
public static void main(String[] args) {
HashMap<String, Integer> hashmap = new HashMap<>();
hashmap.put("Cl", 18);
hashmap.put("GJQ", 15);
hashmap.put("SXH", 10);
hashmap.put("HSR", 12);
System.out.println(hashmap);
System.out.println(hashmap.containsValue("CL"));
System.out.println(hashmap.containsKey("GJQ"));
System.out.println("------");
Collection<Integer> values = hashmap.values();
System.out.println(values);
System.out.println("----------");
Integer res = hashmap.remove("SXH");
System.out.println(res);



// Map的遍历
// 方法一
Set<String> keySet = hashmap.keySet();
Iterator<String> iterator = keySet.iterator();
System.out.println(keySet);

while (iterator.hasNext()) {
String key = iterator.next();
System.out.println("Key: " + key + " value: " + hashmap.get(key));
}
System.out.println("---------");
// 上述的代替写法
// for (String key : keySet) {
// System.out.println("Key:" + key + "value: " + hashmap.get(key));
// }

// 方法二
// Map中的键和值被封装成了Entry对象,并存储在Set集合中,通过entrySet()可以获取到这个Set集合。
Set<Map.Entry<String, Integer>> entries = hashmap.entrySet();
// Iterator<Map.Entry<String, Integer>> entriesIterator = entries.iterator();
for (Map.Entry<String, Integer> en : entries) {
System.out.println("Key:" + en.getKey() + " value: " + en.getValue());
}
System.out.println("_______________");
}
}

LinkedHashMap

  • LinkedHashMap的特点:存取顺序一致

TreeMap

  • TreeMap的特点:可以对存储的元素进行排序

HashMap和Hashtable的区别

  • Hashtable是JDK1.0版本出现的,是线程安全的,效率低,不可以存储null键和null值
  • HashMap是JDK1.2版本出现的,是线程不安全,效率高,可以存储null键和null值

Collection工具

1
2
3
4
Collections.sort(list);
Collections.shuffle(list);
Collections.reverse(list);
Collections.binarySearch(list, 6);

Collection总结

  • List(存取有序,有索引,可以重复)

    • ArrayList
      底层是数组实现的,线程不安全,查找和修改快,增和删比较慢
    • LinkedList
      底层是链表实现的,线程不安全,增和删比较快,查找和修改比较慢
    • Vector
      底层是数组实现的,线程安全的,无论增删改查都慢

如果查找和修改多,用ArrayList
如果增和删多,用LinkedList
如果都多,用ArrayList

  • Set(存取无序,无索引,不可以重复)

    • HashSet
      底层是哈希算法实现
    • LinkedHashSet
      底层是链表实现,可以保证元素唯一,存取顺序一致
    • TreeSet
      底层是二叉树算法实现,可以排序,存储自定义类型时需要注意实现Comparable接口并重写compareTo方法

    一般在开发的时候不需要对存储的元素排序,所以在开发的时候大多用HashSet,HashSet的效率比较高.TreeSet在面试的时候比较多

  • Map

    • HashMap
      底层是哈希算法
    • LinkedHashMap
      底层是链表,存取顺序一致
    • TreeMap
      底层是二叉树算法,可以排序

    开发中用HashMap比较多

HashCode

HashCode方法的作用

在HashSet中的元素是不能重复的,jvm可以通过equals方法来判断两个对象是否相同,假设自定义一个Person类里面有10个成员变量,每调用一次equals方法需要做10次if判断分别比较这10个成员变量是否相等,如果想HashSet中存放100个对象,那就会做1000次if判断,数据量大的话会严重影响性能。
要解决这个问题的话可以这样做,将一些特征相似或相近的对象归类放到一起给他们一个编号,在做equals判断时,先比较这些编号,编号相同的话再去比较equals,这样可以减少一些比较次数。这个编号可以通过HashCode方法获得。HashCode方法的作用就是将对象进行分类,然后获取到编号值。
举个例子,图书馆里面的书都是分好类的,想找《java编程思想》这本书,先找到计算机类的书架,然后再去找就行,倘若图书馆里面的书籍没有分类,那找起来就如大海捞针。

如何重写HashCode

HashCode算法决定了对象的归类,如果算法编写的不好可能不会对性能有所提升。在编写时最好可以让对象均匀的散列开,这里假设可以将对象分为10个种类,那么每个种类中存放的对象的数量最好不要相差太多。

将Person的name和age属性都加上了,可以将Person进行细分,开发中建议使用:

1
2
3
4
5
6
7
8
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}

为什么上面的prime的值是31?其实这个值改成别的也可以,只不过定义为31之后有一些好处:

  • 31是一个质数,质数是能被1和自己本身整除的数,并且这个数不大也不小
  • 31这个数好算,2的五次方-1,2向左移动5位

关于重写HashCode方法的一些说明

  • 任何时候对同一对象多次调用 hashCode 方法,都必须一直返回同样的整数。
  • 如果两个对象通过 equals(Object) 方法来比较相等,那么这两个对象的 hashCode的值必须相等。
  • 如果两个对象通过 equals(Object) 方法比较结果不等,可以相等也可以不相等。

泛型的概念

在编写集合相关代码时在eclipse里面总有一些黄色警告,在不使用注解的情况下,使用泛型之后,就不会有这些黄色警告了。
通过API可以看到Collection,List,ArrayList,这几个类里面都有,这个就是泛型,里面的E可以是任何引用数据类型,使用泛型指明了数据类型之后,这个集合里面只能存储这种数据类型的对象。

不使用泛型时,要进行多次类型强制转换。如List list = new ArrayList();

使用泛型: List<Person> list = new ArrayList<Person>();

泛型的优点

  • 可以统一集合中的数据类型,提高安全性
  • 可以减少强制类型转换

自定义泛型

通过JDK的源码可以看到,泛型一般写的都是或者,里面的T和E就是表示使用者指定的类型。可以自己定义一个使用泛型的类

泛型通配符

在实际工作当中,有可能通过调用某个方法来接受一个返回值List的数据,这样就不太好确定返回值中的数据类型,这样可以使用泛型通配符<?>

1
List<?> list = new ArrayList<Integer>();//=号右边可能是通过调用某个方法返回的List

使用泛型通配符限定子类或者父类

  • ? extends E
    向下限定,E及其子类,可以存储当前类型的子类
  • ? super E
    向上限定,E及其父类,可以存储当前类型的父类
1
2
3
4
5
6
7
8
9
10
11
12
/**
* ? extends E 向下限定,E及其子类,可以存储当前类型的子类
* ? super E 向上限定,E及其父类,可以存储当前类型的父类
*/
public class GenericTest03 {
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
List<Student> studentList = new ArrayList<>();
//因为studentList中存放的Student是Person类的子类,所以可以将studentList放入personList中
personList.addAll(studentList);
}
}

集合框架中的三种迭代方式删除数据

  • 普通for循环,可以删除,注意让索引做自减运算

    1
    2
    3
    4
    5
    6
    7
    //1,普通for循环删除,索引做自减运算
    for(int i = 0; i < list.size(); i++) {
    if("b".equals(list.get(i))) {
    list.remove(i);
    i--;
    }
    }
  • 迭代器,可以删除,但是必须使用迭代器自身的remove方法,否则会出现并发修改异常

  • 增强for循环不能删除

可变参数

注意:如果一个方法有可变参数,并且有多个参数,那么,可变参数肯定是最后一个。

1
2
3
4
5
6
7
// 修饰符 返回值类型 方法名(数据类型…  变量名){}
//可变参数其实是一个数组
public static void print(int ... arr) {
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}

注解:

之前也看过一些Java-Spring Boot的视频, 有些讲的详细的会去讲源码,然后当时就是看到有很多注解, 就不懂是什么意思, 形成了理解障碍。 所以这次特地又去了解了一下

Spring-Boot的入口函数是由@SpringBootApplication注解的main,无疑@SpringBootApplication这个注解是非常重要的。

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

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// ---
@SpringBootConfiguration
@EnableAutoConfiguration
// ---
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...
}

上述一共七个注解,一共可以分成三类: 元注解、配置注解、Component组件注解。之前学的时候就是被这么多注解给吓到了,而且课上对注解的讲解也特别小, 所以一直感觉注解是个高深莫测的东西。

  • 元注解讲解: Java 元注解

    • @Inherited使用该注解的注解父类的子类可以继承父类的注解。请注意,如果使用注释类型来注释除类之外的任何内容,则此元注释类型不起作用。 还要注意,这个元注释只会导致从超类继承注释; 已实现的接口上的注释无效。
  • 配置注解源码讲解: 尚硅谷SpringBoot顶尖教程(springboot之idea版spring boot)

    • @AutoConfigurationPackage@Import({Registrar.class})注解,其中Registrar

      1
      2
      3
      public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])); // 后面第二个参数的结果是metadata中的所有包==>即获得主配置类所在包及以下子包
      }

      将主配置类(@SpringBootApplication标注的类)所在包及下面子包里面的所有组件扫描到Spring容器中

注解(Annontion)是Java5开始引入的新特征。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。==>类似注释,但能将内容传递给程序,对修饰对象有约束作用。

IO流

1
2
3
4
5
6
//每次调用的时候会读取一个字节的数据,如果read返回结果是-1,则说明读取完毕
int temp;//保存当前读取的字节数据
//将读取的数据赋值给temp,然后再判断
while ((temp = f.read()) != -1) {
System.out.print((char)temp);
}

使用File.separator解决不同系统的路径问题

在windos中的文件路径是以"“来分隔
在linux中的文件路径是以”/"来分隔
如果将上面代码部署到linux中会读取不到文件,为了保证编写的代码跨平台需要使用java.io包下的File.separator来替代文件路径的分隔符,如下:fis = new FileInputStream("file" + File.separator + "monkey.txt");

文件IO读写FileInputStream

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

public class copy_io {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("C:\\Users\\10630\\Desktop\\TODO\\ok.yml");
File file = new File("good.txt");
if (!file.exists()) {
boolean newFile = file.createNewFile();
System.out.println("Yes, Create it~");
}
fos = new FileOutputStream("good.txt");


byte[] arr= new byte[1024];
int length;
while ((length = fis.read(arr)) != -1) {
// f.read(arr)和f.read()不一样,
// f.read(arr)会一次性读取arr大小的数据, 然后长度用length来记录读取了多少字符
// write写的时候, 将数组arr中length写入文件
// 在while中输出了length的大小, 为1024, 842
fos.write(arr, 0, length);
System.out.println(length);
}

} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
} finally{
try {
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}

}
}
}

使用缓冲流进行文件拷贝BufferedInputStream

Java中提供了BufferedInputStreamBufferedOutputStream缓冲流用来读取和写出, BufferedInputStream读取时会创建一个长度为8192的byte类型数组,程序一次读取8192个字节数据到数组中 使用缓冲流之后就不用再自定义byte类型数组了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

public class bufferStream_io {
public static void main(String[] args) throws IOException {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;

try {
bis = new BufferedInputStream(new FileInputStream("C:\\Users\\10630\\Desktop\\TODO\\ok.yml"));
bos = new BufferedOutputStream(new FileOutputStream("test.txt"));
// 其实不存在会自动创建, 不需要下面的代码
File f = new File("text.txt");
if (!f.exists()) {
boolean newFile = f.createNewFile();
System.out.println("创建成功~");
}
int tmp;
while ((tmp = bis.read()) != -1) {
bos.write(tmp);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bis != null) {
bis.close();
}
if (bos != null) {
bos.close();
}
}
}
}

使用自定义数组和buffer的图解

jdk7的新写法

在jdk7中新加入了AutoCloseable接口,IO流中的类都实现了这个接口,这样在读取或者写出操作结束之后,系统会自动close相关资源,开发者不需要再手动close了

1
2
3
4
5
6
7
8
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\Users\\10630\\Desktop\\TODO\\ok.yml")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("auto.txt"));) {
int tmp;
while ((tmp = bis.read()) != -1) {
bos.write(tmp);
}
} catch (IOException e) {
e.printStackTrace();
}

使用字符流解决乱码问题FileReader

字符流FileReader主要用来读取字符的IO流,使用字符流读取文本文件可以解决乱码问题。

1
2
3
4
5
6
7
8
9
10
11
12
13

public class FileReader_io {
public static void main(String[] args) {
try (FileReader fileReader = new FileReader("G:\\C与C++、\\java\\testIDEA\\src\\testForChinests.txt")) {
int c;
while ((c = fileReader.read()) != -1) {
System.out.print((char) c);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

使用缓冲流BufferedReader可以一次读取一行的文字:

1
2
3
4
5
6
7
8
9
10

// 读取
try (BufferedReader bufferedReader = new BufferedReader(new FileReader("G:\\C与C++、\\java\\testIDEA\\src\\testForChinests.txt"))) {
String s;
while ((s = bufferedReader.readLine()) != null){
System.out.print(s);
}
} catch (IOException e) {
e.printStackTrace();
}

写入

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

// 写入
try(FileWriter fw = new FileWriter("newword.txt");){
fw.write("我喜欢学习java");
fw.write(32); // 空格
fw.write(97);
} catch (IOException e) {
e.printStackTrace();
}


try(BufferedWriter bw = new BufferedWriter(new FileWriter("newword.txt"));){
bw.write("我喜欢打篮球");
bw.newLine();//换行
bw.write("我喜欢踢足球");
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}

BufferedWriter、BufferedInputStream内的参数都是原有的FileWrite、FileInputStream,实际上使用了装饰模式(设计模式)

装饰者设计模式的优点:
不用修改被装饰对象的源码,装饰者与被装饰者耦合度不高。

转换流——编码格式转换InputStreamReader

如果要解决上面问题,需要使用InputStreamReader和OutputStreamWriter指明文本文件的编码,这两个类都属于字符流,可以将字节流输出为字符流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
// 使用FileInputStream读取文本内容,然后通过InputStreamReader和指定的编码将字符转换为字节
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream("utf-8.txt"), "utf-8"));
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("gbk.txt"), "gbk"));) {
String msg;
while((msg = br.readLine()) != null){
bw.write(msg);
}
bw.flush();
} catch (UnsupportedEncodingException | FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
}

上面的代码中再FileInputStream对象上使用了InputStreamReader装饰,从而将字节转换为字符,之后再InputStreamReader对象上又使用了BufferedReader将字符进行缓冲,从而提高。==>都有reader

输出指定目录下的所有文件名称

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

public class OutputAllFiles {
private static int cnt = 0;

public static void main(String[] args) {
final File file = getFile();
getListFiles(file);
}


public static void getListFiles(File f) {
final File[] files = f.listFiles();
for (int i = 0; i < files.length; i++) {
for (int j = 0; j < cnt; j++) {
System.out.print('\t');
}

System.out.println(files[i]);

if (files[i].isDirectory()) {
cnt++;
getListFiles(files[i]);
cnt--;
}
}

}

public static File getFile() {
System.out.print("请输入要遍历的目录: ");
final Scanner scanner = new Scanner(System.in);
while (true) {
final String next = scanner.nextLine();
// next()不会吸取字符前/后的空格/Tab键,只吸取字符,开始吸取字符(字符前后不算)直到遇到空格/Tab键/回车截止吸取;
// nextLine()吸取字符前后的空格/Tab键,回车键截止。
final File file = new File(next);
if (!file.exists()) {
System.out.println("输出的路径错误, 请重新输入");
} else if (file.isFile()) {
System.out.println("请输入一个文件夹路径");
} else {
return file;
}
}
}
}

序列化和反序列化

  • 在工作中有可能遇到多台机器远程通信的情况,如果要将机器A中的某个java对象传输到机器B上面,需要将这个java对象转换为字节序列然后进行传输。将对象转换为字节序列的过程叫做序列化,反之叫做反序列化。
  • 使用序列化还可以将一个对象保存到硬盘中,然后再通过反序列化将该对象读取到内存里面。

一个对象如果支持序列化,需要实现Serializable的接口,这个接口中没有任何方法,实现该接口后,JVM会给这个对象做特殊待遇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SerializableTest {
public static void main(String[] args) {
final Student s = new Student("张三");
try (ObjectOutputStream zhangsan = new ObjectOutputStream(new FileOutputStream("zhangsan"))) {
// try()括号中,如果是多句, 则加;, 单句不需要加;
// ObjectOutputStream也是一个装饰模式
zhangsan.writeObject(s);
zhangsan.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

在一个类实现Serializable接口后,系统会给每个对象一个序列化版本号,当这个类的源码被修改后,系统会重新分配一个新的序列化版本号,这样做的好处就是保证序列化和反序列化的对象内容一致。例如将一个对象序列化到硬盘之后,修改这个对象所对应类的源码,在进行反序列化是就会报出InvalidClassException异常。如果手动编写序列化版本号之后,就不会出现这个异常了。

1
2
3
4
/**
* 自动生成序列化版本号
*/
private static final long serialVersionUID = -716323668524282676L;

transient关键字

如果不希望将Student类中的age属性序列化,可以使用transient声明该属性,在序列化时将忽略这个属性。transient private int age;

多线程

三种创建方式

  • 继承Thread类, 重写run方法

    • 优点:可以直接使用Thread类中的方法,代码简单
    • 缺点:继承Thread类之后就不能继承其他的类
  • 实现runnable接口, 重写run方法

    • 优点:即时自定义类已经有父类了也不受影响,因为可以实现多个接口
    • 缺点:在run方法内部需要获取到当前线程的Thread对象后才能使用Thread中的方法
  • 实现Callable接口创建线程

    • 优点:可以获取返回值,可以抛出异常

    • 缺点:代码编写较为复杂

      1.自定义一个类实现java.util.concurrent包下的Callable接口
      2.重写call方法
      3.将要在线程中执行的代码编写在call方法中
      4.创建ExecutorService线程池
      5.将自定义类的对象放入线程池里面
      6.获取线程的返回结果
      7.关闭线程池,不再接收新的线程,未执行完的线程不会被关闭

    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
    //1.定义一个类实现Callable<V>接口
    class MyCallable implements Callable<Integer> {
    // 2.重写call方法
    @Override
    public Integer call() throws Exception {
    // 3.将要执行的代码写在call方法中
    //返回一个随机数
    Random r = new Random();
    int num = r.nextInt(100);

    return num;
    }
    }

    public class MultiThread_callable {
    public static void main(String[] args) {
    //4.创建ExecutorService线程池
    ExecutorService exec = Executors.newCachedThreadPool();

    //5.将自定义类的对象放入线程池里面
    //开启两个线程
    Future<Integer> result1 = exec.submit(new MyCallable());
    Future<Integer> result2 = exec.submit(new MyCallable());


    //判断线程是否计算完毕
    while (!result1.isDone() && !result2.isDone()) {
    System.out.println("等待线程计算完毕");
    }

    //6.获取线程的返回结果
    Integer i1 = null;
    try {
    i1 = result1.get();
    } catch (InterruptedException e) {
    e.printStackTrace();
    } catch (ExecutionException e) {
    e.printStackTrace();
    }
    Integer i2 = null;
    try {
    i2 = result2.get();
    } catch (InterruptedException e) {
    e.printStackTrace();
    } catch (ExecutionException e) {
    e.printStackTrace();
    }

    System.out.println(i1);
    System.out.println(i2);

    //7.关闭线程池,不再接收新的线程,未执行完的线程不会被关闭
    exec.shutdown();
    }
    }

线程池

线程池是初始化一个多线程应用程序过程中创建一个线程集合,即一次创建多个线程,然后在需要执行新的任务时直接去这个线程集合中获取,而不是重新创建一个线程。任务执行结束后,线程放回到池子中等待下一次的分配。

线程池的作用

解决创建单个线程耗费时间和资源的问题。

创建线程池

上面代码中演示了两种方式创建线程池

  • Executors.newFixedThreadPool(int nThreads);
    通过传入的int类型参数来指定创建线程池中的线程数,如果任务数量大于线程数量,则任务会进行等待。
  • Executors.newCachedThreadPool();
    会根据需要创建新线程的线程池,如果线程池中的线程数量小于任务数时,会创建新的线程,线程池中的线程最大数量是Integer.MAX_VALUE,int类型的最大值。如果线程的处理速度小于任务的提交速度时,会不断创建新的线程来执行任务,这样有可能会因为创建过多线程而耗尽CPU 和内存资源。

匿名内部类、labmbda表达式创建

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

public class NewWayCreateThread {
/**
* @Description: 匿名内部类
* @Author: MrLi
* @Param: [args]
* @Return: void
* @Date: 2020/5/18
*/
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println( Thread.currentThread().getName() + " " + i);
System.out.println( getClass() + " " + i);
}
}
}).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main" + i);
}
}

/**
* @Description: labmbda表达式
* @Author: MrLi
* @Param: [args]
* @Return: void
* @Date: 2020/5/18
*/
public static void main(String[] args) {
new Thread(() ->{
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}).start();

for (int i = 0; i < 1000; i++) {
System.out.println("main " + i);
}
}
}

synchronized同步方法与同步代码块

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
69
70
71
72
73
74
75
76
77
78
79
80
81
public class SleepThread {

public static long begin1;
public static long end1;
public static long begin2;
public static long end2;

public static void main(String[] args) {
LongTask ts = new LongTask();
Thread t1 =new Thread(() -> {
begin1 = System.currentTimeMillis();
ts.add();
end1 = System.currentTimeMillis();
});

Thread t2 = new Thread(() -> {
// new Thread(syncValue::add).start();
begin2 = System.currentTimeMillis();
ts.add();
end2 = System.currentTimeMillis();
});

t1.start();
t2.start();


try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}

long begin = 0;
long end = 0;

if(begin1 > begin2){
begin = begin2;
}else{
begin = begin1;
}

if(end1 > end2){
end = end1;
}else{
end = end2;
}

System.out.println("两个线程总共耗时:" + (end -begin) + "ms");
}
}

class LongTask {
private static int num = 0;
Object obj = new Object();

// public synchronized void add() {
// try {
// Thread.sleep(3000L);
// System.out.println("执行耗时任务");
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// num++;
// System.out.println(num);
// }
//两个线程总共耗时:6001ms

public void add() {
try {
Thread.sleep(3000L);
System.out.println("执行耗时任务");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
num++;
System.out.println(num);
}
}
//两个线程总共耗时:3001ms
}

修改后将需要同步的代码放到synchronized代码块中,再次运行SynchronizedTest02类,打印结果是3秒,因为那段耗时较长的代码是在异步情况下运行,所以节省了一些时间。

注意:多个线程在执行synchronized同步代码块时,代码块括号里面可以传入任意对象,但一定要保证多个线程访问的是同一个对象。(这里代码只有一个实例, 这个实例的obj是相同的)

单例模式

饿汉式

  • 构造方法私有化
  • 创建当前类对象
  • 对外提供公共的访问方法将SingletonHungary对象暴露给外部

懒汉式

  • 构造方法私有化
  • 创建当前类的引用
  • 对外提供公共的访问方法将SingletonHungary对象暴露给外部

单例模式的案例Runtime

java.lang包下的Runtime类使用了单例模式,使用该类可以执行windows系统里面的一些命令,例如:mspaint(打开画图软件),shutdown(关机)等等。

1
2
3
4
public static void main(String[] args) throws IOException {
Runtime rt = Runtime.getRuntime();
rt.exec("mspaint");
}

使用Timer类来实现定时任务

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

public class TimerTest {
public static void main(String[] args) throws ParseException {
final Timer t = new Timer();
// t.schedule(new TimerTask() {
// @Override
// public void run() {
// final Date date = new Date();
// System.out.println(date);
// }
// }, new SimpleDateFormat("yyyy-MM-dd hh:mm:ss SSS").parse("2017-07-03 18:09:00 000"), 5000);
//第一个参数接收TimerTask对象,即上面创建的MyTimerTask
//第二参数的Date类型是定时任务执行的开始时间
//第三个参数指定定时任务每隔多少毫秒执行一次


t.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(new Date());
}
}, 0, 1000);
}
}

附录

Q:java 定义long和float为什么要加L和F?

A:整形默认值为int,如果定义long 必须要加L来区分,浮点型默认值为double双精度,定义单精度float要加F来区分。

Java中@SuppressWarnings的作用

A:作用:告诉编译器忽略指定的警告,不用在编译完成后出现警告信息。如@SuppressWarnings("unchecked", "deprecation")等同于@SuppressWarnings(“unchecked”, “deprecation”)

输出变量类型——Python中type关键字

1
2
3
4
public static String getType(Object o){  //通过反射来获取变量类型方法
return o.getClass().toString(); //使用int类型的getClass()方法
// return o.getClass().getName(); //使用int类型的getClass()方法
}

▲. 基本数据类型无效, 如int, 但可以查看包装数据类型。

Author: Mrli

Link: https://nymrli.top/2020/05/15/重拾Java笔记/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
IDEA配置——自定义快捷键、生成注释
NextPost >
华为春招4.29笔试题
CATALOG
  1. 1. 重拾Java笔记
    1. 1.1. 命名规范:
    2. 1.2. class与文件名:
    3. 1.3. 函数传参:
    4. 1.4. 代码块的分类
      1. 1.4.1. 执行顺序:
    5. 1.5. this指针
    6. 1.6. 继承问题:
    7. 1.7. 多态:
    8. 1.8. 重写
    9. 1.9. Super关键字:
    10. 1.10. Object类之finalize方法
    11. 1.11. 访问控制权限
      1. 1.11.1. 构造函数的权限问题:
    12. 1.12. Final关键字:
    13. 1.13. static关键字
      1. 1.13.1. static的作用
    14. 1.14. 抽象类的特点
    15. 1.15. 接口:
    16. 1.16. equals
    17. 1.17. 内部类的分类
    18. 1.18. 异常的分类
      1. 1.18.1. JVM是如何处理异常的
      2. 1.18.2. throw和throws
      3. 1.18.3. 自定义异常
      4. 1.18.4. catch
    19. 1.19. 字符串的不可变性
      1. 1.19.1. String类不能被继承
      2. 1.19.2. 字符串(String)的不可变性
      3. 1.19.3. 字符串常量池
    20. 1.20. String、StringBuffer、StringBuilder
      1. 1.20.1. StringBuffer
      2. 1.20.2. StringBuilder和StringBuffer的区别
    21. 1.21. 什么是自动拆箱和自动装箱?
    22. 1.22. 集合
      1. 1.22.1. 集合的由来
      2. 1.22.2. 集合类的一些特点
      3. 1.22.3. 区别
    23. 1.23. List两个子类的特点
      1. 1.23.1. ArrayList线程安全的方案
    24. 1.24. 集合数组的互转
    25. 1.25. Collection集合
      1. 1.25.1. Set的特点
      2. 1.25.2. TreeSet简介
      3. 1.25.3. Map接口概述
        1. 1.25.3.1. HashMap和Hashtable的区别
      4. 1.25.4. Collection工具
      5. 1.25.5. Collection总结
    26. 1.26. HashCode
      1. 1.26.1. HashCode方法的作用
      2. 1.26.2. 如何重写HashCode
      3. 1.26.3. 关于重写HashCode方法的一些说明
    27. 1.27. 泛型的概念
      1. 1.27.1. 泛型的优点
      2. 1.27.2. 自定义泛型
      3. 1.27.3. 泛型通配符
    28. 1.28. 集合框架中的三种迭代方式删除数据
    29. 1.29. 可变参数
    30. 1.30. 注解:
    31. 1.31. IO流
      1. 1.31.1. 使用File.separator解决不同系统的路径问题
      2. 1.31.2. 文件IO读写FileInputStream
      3. 1.31.3. 使用缓冲流进行文件拷贝BufferedInputStream
      4. 1.31.4. jdk7的新写法
    32. 1.32. 使用字符流解决乱码问题FileReader
      1. 1.32.1. 使用缓冲流BufferedReader可以一次读取一行的文字:
      2. 1.32.2. 转换流——编码格式转换InputStreamReader
    33. 1.33. 输出指定目录下的所有文件名称
    34. 1.34. 序列化和反序列化
      1. 1.34.1. transient关键字
    35. 1.35. 多线程
      1. 1.35.1. 三种创建方式
      2. 1.35.2. 线程池
    36. 1.36. synchronized同步方法与同步代码块
    37. 1.37. 单例模式
      1. 1.37.1. 饿汉式
      2. 1.37.2. 懒汉式
      3. 1.37.3. 单例模式的案例Runtime
    38. 1.38. 使用Timer类来实现定时任务
  2. 2. 附录
    1. 2.1. Q:java 定义long和float为什么要加L和F?
    2. 2.2. Java中@SuppressWarnings的作用
    3. 2.3. 输出变量类型——Python中type关键字