Collection接口:
--| List 接口: 元素是有序的,可以重复的;
--| ArrayList: 是由数组实现的(底层是一个数组),是由一个初始长度为10的数组组成的
特点: 增删慢,查找快
初始容量可以自定义:
new ArrayList(100); 这样100就是当前容量
List集合是存储在数组中的,而数组是存储在JVM中的,JVM存储在内存中
JVM在运行时默认分配内存空间是215M,一般建议修改内存的容量。
//1. 什么时候扩容??
扩容方法在add方法中,如果新增一个元素,长度大于当前数组的长度,就开始扩容!
//2. 扩大多少?
每次扩大为原来的1.5倍
--| LinkedList: (用的不多,面试) 底层是一个链表
特点: 增删快,查找慢
--| Set 接口: 元素是无序的,不可以重复!!
--| HashSet 底层存储结构是一个hash表,存储效率极高
Set<String> set =new HashSet<String>();
-------------HashSet的去重原理:
HashSet在存储值的时候,会先获取该对象的HashCode值,经过Hash算法,算出该对象在内 存中的位置;
如果该位置上有值,那么会进行equals比较
如果比较结果为相同,不能存储;
如果不同,可以存储
如果该位置没值,直接存储
--| treeSet 底层存储结构是一个树形结构,存储需要进行判断比较
特点: 有序。 compareTo 方法如果是0,说明该元素在集合中已经存在,不添加
如果返回正数或者负数是排序规则,正数按照降序排列,负数按照正序排列
Set<String> set3 =new TreeSet<String>();
set3.add("zhang");
set3.add("wang");
set3.add("li");
set3.add("zhao");
System.out.println(set3);
//TreeSet 是有顺序的,默认是按照字典顺序
/*
* 此处编写排序规则:
* 返回值是int类型
* 返回整数说明
* 如果返回正数: 倒序排列
* 负数 :正序排列
* 返回0说明两个元素是相同的
*/
/*@Override
public int compareTo(Student stu) {
return this.id - stu.getId();
}*/
MAP: 类似于数据库,号称"本地缓存"----------------->DAY02
HashMap-->是以键值对的形式存放数据的 key = value
Iterable接口 提供了一个方法,可以获取Iterator
Iterator接口 提供的方法:
hasNext 判断是否有下一个元素
next 获取当前元素,并指向下一个元素
remove 删除当前元素 //建议尽量不要在循环中删除元素,这样会导致长度改变造成异常
实现了这个接口,允许对象成为 "foreach" 语句的目标,也就是说,只要一个类是Iterable的子类,都可以使用foreach循环!!!
UUID 是 通用唯一识别码(Universally Unique Identifier)的缩写,是一种软件建构的标准,亦为开放软件基金会组织在分布式计算环境领域的一部分。其目的,是让分布式系统中的所有元素,都能有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。如此一来,每个人都可以创建不与其它人冲突的UUID。在这样的情况下,就不需考虑数据库创建时的名称重复问题。
UUID由以下几部分的组合:
(1) 当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。
(2) 时钟序列。
(3) 全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。
UUID是由一组32位数的16进制数字所构成,是故UUID理论上的总数为16^32=2^128,约等于3.4 x 10^38。也就是说若每纳秒产生1兆个UUID,要花100亿年才会将所有UUID用完。
UUID的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的32个字符。示例:
550e8400-e29b-41d4-a716-446655440000
代码示例:
String uuid = UUID.randomUUID().toString(); //其中一个结果为6462cc46-53af-47cf-ad7f-ffcc0ae31d23
uuid = uuid.replace("-", ""); //为了将‘-’去掉
System.out.println(uuid);
#####关键词❓:\s、\s+、\d、\d+
\s 匹配空格
+ 匹配至少一个 eg: \s+ 表示匹配至少一个空格
\d 匹配数字
//注意:在字符串里,用到了反斜杠 "\" ,那么就需要在加上一个反斜杠表示转移字符,即 "\\s+" 这种形式
//接下来这个例子展示了用法
//在一串数字中,有很多空格,我们要匹配多个空格提取数字:
Scanner scanner =new Scanner(System.in);//扫描器
System.out.println("请输入您要相加的数字,用空格隔开:");
String str = scanner.nextLine().trim();//1 5 10 23 12 34,输入这几个数字,随意加空格
String[] arr = str.split("\\s+"); // \s 匹配空格, + 配置至少一个空格
int sum =0;
for(String s : arr) {
if(s.matches("\\d+")) { //????????????????
int a = Integer.parseInt(s); //????????????????
sum += a;
}
}
System.out.println("sum= "+sum);//
/*
* 1、编写一个equalsIngoreCase方法
* 2、trim()方法
* 3、四位数的验证码生成
* 4、发牌完
*/
//纯手工制作equalsIngoreCase
public static boolean equalsIngoreCase(String str01,String str02) {
if(str01 == null || str02 == null) {
return false;
}
if(str01.length() != str02.length()) {
return false;
}
char[] array01 = str01.toLowerCase().toCharArray();
char[] array02 = str02.toLowerCase().toCharArray();
for(int i=0;i<array01.length;i++) {
if(array01[i] != array02[i]) {
return false;
}
}
return true;
}
//2 trim()方法
public static String trim(String oldStr) {
// " nihao tom "
char[] charArray = oldStr.toCharArray();//''
int begin = 0;
int end = oldStr.length() -1;
for(int i =0;i< charArray.length ;i++) {
if(charArray[i] != ' ') {
begin = i;
break;
}
}
for(int i =end;i >= 0 ;i--) {
if(charArray[i] != ' ') {
end = i;
break;
}
}
// 前包后不包 [)
return oldStr.substring(begin, end+1);
}
####?1.MAP——号称“本地缓存”
所有的容器:集合、数组,都是放在内存中的。MAP是一个单独的接口,注意它并不属于Colection接口
MAP是通过Key-value键值对的形式存储数据一种容器。
/* 本地缓存:Map
* 硬盘:机械硬盘、固态硬盘
* 内存:读写速度贼快,但是内存是有限的资源。
*
* Collection: List Set
* Map :
* 是通过Key-value键值对的形式存储的一种容器
* HashMap
*/
HashMap<String,String> map =new HashMap<String,String>();
map.put("张三", "Java");
map.put("李四", "Python");
map.put("王五", "H5");
map.put("赵六", "Big Data");
// map集合中key是由set存储的,如果有相同的key,后者会顶替前者
map.put("张三", "Java2");
System.out.println(map.size());
System.out.println(map.get("张三"));
// 先获取key值的set集合,通过key值获取value
Set<String> keySet = map.keySet();//key值是一个set集合
Iterator<String> iterator = keySet.iterator();
while(iterator.hasNext()) {
String key = iterator.next();
System.out.println("key="+key+",value="+map.get(key));
}
// 第一种方法的第二个写法 foreach
for (String key : keySet) {
System.out.println("key="+key+",value="+map.get(key));
}
/*
* 第二种方法就是:通过map获取Entry对象的集合
* 然后通过迭代器或者foreach循环这个集合
*/
Set<Entry<String, String>> entrySet = map.entrySet();
Iterator<Entry<String, String>> iterator2 = entrySet.iterator();
while(iterator2.hasNext()) {
Entry<String, String> entry = iterator2.next();
System.out.println(entry.getKey()+","+entry.getValue());
}
for (Entry<String, String> entry : entrySet) {
System.out.println(entry.getKey()+","+entry.getValue());
}
//map集合可以获取所有的value值
Collection<String> values = map.values();
for (String value : values) {
System.out.println("value="+value);
}
序号 | 返回值类型 | 方法 | 说明 |
---|---|---|---|
1 | void | clear( ) | 移除所有映射关系 |
2 | set |
entrySet( ) | 返回此映射中包含的映射关系的Set视图 |
3 | value | get(object key) | 返回指定键所映射的值 |
4 | int | hashCode( ) | 返回此映射的hash码值 |
5 | set | keySet( ) | 返回此映射中包含的所有键的set视图 |
6 | put(key,value) | 放入指定键和值 | |
7 | putAll(Map extends K, ? extends V> m) | 类似集合,将一组映射添加到另一组中 | |
8 | value | remove(object key) | 移除指定键的映射关系 |
9 | collection | value( ) | 返回此映射中值得集合 |
10 | int | size( ) | 返回键值对数,注意同key则值覆盖 |
11 | boolean | containsKey(object key) | 若此映射包含指定键的映射关系,返回true |
12 | boolean | containsValue(object value) | 指定值被一个或多个键映射,则返回true |
13 | boolean | equals(object obj) | 比较指定对象与此映射是否相等 |
14 | boolean | isEmpty( ) | 该映射没有映射关系,则返回true。注意:若映射 == null,则空指针异常 |
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
*
* 商品数据 商品类别
* 一个商品类别可以有多件商品
*
*/
public class Demo02 {
public static void main(String[] args) {
Map<String,List<Goods>> map =new HashMap<String,List<Goods>>();
List<Goods> list01=new ArrayList<Goods>();
list01.add(new Goods(1,"T Shirt",100));
list01.add(new Goods(2,"夏威夷风格大裤衩",50));
map.put("服装", list01);
//精日
List<Goods> list02=new ArrayList<Goods>();
list02.add(new Goods(1,"华伪P30 Pro",100));
list02.add(new Goods(2,"锤子",150));
map.put("手机", list02);
/**
* 此处省略10000行代码
*/
Set<String> keySet = map.keySet();
for (String key : keySet) {
System.out.println("此处的商品类型是:"+key);
System.out.println("有以下商品");
List<Goods> list = map.get(key);
for (Goods goods : list) {
System.out.println(goods);
}
}
}
}
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;
/**
*
* 在控制台输出一串字符串,打印每个字符出现的次数
*
*/
public class Demo03 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一串随意的字符串:");
String line = scanner.nextLine();
char[] array = line.toCharArray();
Map<Character,Integer> map =new HashMap<Character,Integer>();
for (char c : array) {
if(map.containsKey(c)) {
int count = map.get(c);
map.put(c, count+1);
}else {
map.put(c, 1);
}
}
Set<Entry<Character, Integer>> entrySet = map.entrySet();
for (Entry<Character, Integer> entry : entrySet) {
System.out.println(entry.getKey()+"出现了"+entry.getValue()+"次");
}
}
}
#####关键词❓: @Test、@Before 、@After
@Test 导包用Junit4,不要用5
单元测试用法示例:
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.qfedu.zuoye.StringUtils;
/**
*
* 单元测试Junit:
* 1、需要导入相应的包 2个
* 2、编写测试方法的时候只需要在测试方法上方添加@Test即可
* 3、方法的修饰符必须是public ,返回值必须是void ,方法名建议以test开头
* 4、初步体会:可以替代main方法(注意类里面没有main方法的)
*/
public class JunitTest {
/*
* 在before注解修饰的方法中经常写一些初始化类的代码
*/
@Before
public void init() {
System.out.println("该方法每一个单元测试方法执行前都必须先执行我!");
}
@Test
public void testTrim() {
//int i= 10/0;
String str= StringUtils.trim(" abc bcd ddd ");
assert (str.equals("abc bcd ddd"));
}
@Test
public void testEquals() {
System.out.println(StringUtils.equalsIngoreCase("Abc","abc"));
}
@After
public void destory() {
System.out.println("每个单元测试方法执行后,都要执行我");
}
}
断言,表示预判,若结果与assert表达的一致,则通过
@Test
public void testAdd() {
int a = computer.add(10, 4);
assert (a == 10);
}
匿名 --> 没有名字
内部类 --> 类中类,一个类中编写或声明了另一个类
作用:如果一个接口或者一个抽象类的实现类、实例化对象只需要使用一次,不想编写实现类,可以使用匿名内部类来简化这个问题
注意: 匿名内部类只能创建一个对象
单例设计模式:一个类只需要一个实例,不能出现多个实例,就叫单例;
单例模式: 该类只能有一个实例化对象
1. 私有化构造方法
2. 编写一个公共方法,供外部调用
3. 在公共方法中实例化对象
懒汉与饿汉:
* 懒汉是:创建一个方法,有人使用这个对象的时候再创建,优点是节约内存,缺点是太慢 线程不安全的
* 饿汉是:先创建对象,别人调用方法直接返回该对象。优点是效率高,缺点是占内存 线程安全
单例模式之 | 解释 | 优点 | 缺点 |
---|---|---|---|
懒汉模式 | 创建个方法,有人使用这个对象的时候再创建 | 节约内存 | 效率低,线程不安全 |
饿汉模式 | 先创建对象,别人调用方法直接返回该对象 | 效率高,线程安全 | 占内存 |
懒汉之"懒":别的类调用该方法的时候采取创建对象
public class Boss {
private String name;
private static Boss boss =null;
private Boss(String name ) {
this.name = name;
}
public static Boss instanceBoss() {
if(boss == null) {
boss =new Boss("王总"); //因为每个类调用的时候要new一个Boss,有可能被其他劫持,不安全
}
return boss;
}
}
饿汉之"饿": 先吃饱再说,先创建了再说,不管以后有没有人用!
public class Boss2 {
private String name;
private static Boss2 boss =new Boss2("王总");
private Boss2(String name ) {
this.name = name;
}
public static Boss2 instanceBoss() {
return boss;
}
}
collections.sort(list); 排序
collections.reserve(list); 反转元素
collections.shuffle(list); 洗牌
collections.max(list); 最大值
collections.min(list); 最小值
index collections.binarySearch(list, element); 二分法查找指定元素的下标 注意:先排序!!
collections.replaceAll(list, oldElement, newElement); 将原来指定元素替换为指定元素
插播一条arrays工具类: arrays.aslist(arr); 可以将数组转为list集合,但是该集合是/*只读*/的!!!
1、递归练习
2 + 22 + 222 + 2222
通过递归计算该值是多少?
2222 = 222*10 + 2;
222 = 22*10 +2;
####?1. File 工具类?
File: 就是一个工具类,该类主要用于操作文件及文件夹本身,不对其中的内容及逆行操作
IO流: 对文件中的内容进行写入和读取
eg: 创建一个文件
File file =new File("C:\\Users\\98023\\Desktop\\a.txt");//这里是我们想要创建路径的地方
if(!file.exists()) { //file.exist()是用于判断一个文件或文件夹是否存在的
file.createNewFile();
}
eg: 创建一个文件夹
File file2 =new File("\\\\Mac\\Home\\Desktop\\ab\\b");
//mkdir 和 mkdirs 都可以创建多级目录,如果创建多级目录的话,建议使用mkdirs
file2.mkdir();
1 | boolean | canExecute() 测试应用程序是否可以执行此抽象路径名表示的文件。 |
---|---|---|
2 | boolean | canRead() 测试应用程序是否可以读取此抽象路径名表示的文件。 |
3 | boolean | canWrite() 测试应用程序是否可以修改此抽象路径名表示的文件。 |
4 | boolean | createNewFile() 当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。 |
5 | boolean | delete() 删除此抽象路径名表示的文件或目录。 |
6 | boolean | deleteOnExit() 在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。 |
7 | boolean | exists() 测试此抽象路径名表示的文件或目录是否存在。 |
8 | File | getAbsoluteFile() 返回此抽象路径名的绝对路径名形式。 |
9 | String | getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。 |
10 | String | getPath() 将此抽象路径名转换为一个路径名字符串。 |
11 | String | getName() 返回由此抽象路径名表示的文件或目录的名称。 |
12 | String | getParent() 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null 。 |
13 | File | getParentFile() 返回此抽象路径名父目录的抽象路径名;如果此路径名没有指定父目录,则返回 null 。 |
14 | boolean | isDirectory() 测试此抽象路径名表示的文件是否是一个目录。 |
15 | boolean | isFile() 测试此抽象路径名表示的文件是否是一个标准文件。 |
16 | File[] | listFiles() 返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。 |
17 | boolean | mkdir() 创建此抽象路径名指定的目录。 |
18 | boolean | mkdirs() 创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。 |
19 | boolean | renameTo(file dest) 重新命名此抽象路径名表示的文件。 |
20 | boolean | setReadOnly() 标记此抽象路径名指定的文件或目录,从而只能对其进行读操作。 |
21 | boolean | setWriteOnly() 标记此抽象路径名指定的文件或目录,从而只能对其进行写操作。 |
22 | String | toString() 返回此抽象路径名的路径名字符串。 |
IO流:
向文件中写入数据,读取数据
MySQL等数据库本身也就是IO流的操作
IO流:Input、OutPut 输入输出流
-->输入,输出 指的是以内存为标志来说
/*输入流*/: 数据从其他地方流向内存就叫做输入流 读取文件,就是输入流
/*输出流*/: 从内存中将输入流向其他地方 ,就叫输出流 向文件中写入数据,叫输出流
-->字节流和字符流:
/*字节流*/,是按照字节byte进行读取或者写入的,一般用于非文本数据的处理,图片,音乐,视频等文件
//字节输入/输出流:FileInputStream / FileOutputStream
/*字符流*/:是按照字符char进行读取和写入的,一般用于文本数据 txt,excel,md,处理汉字比较方便
//字符输入/输出流: FileReader / FileWriter FileWriter参数加true表示不覆盖
public static void main(String[] args) {
try {
//字节输出流
OutputStream outPutStream = new FileOutputStream(new File("\\\\Mac\\Home\\Desktop\\a.txt"),true);
//outPutStream.write("hello world".getBytes());
outPutStream.write(97);// 97 在ASCII表中表示 a
byte[] arr = "hello world".getBytes();
outPutStream.write(arr, 0, 5);
outPutStream.flush();//从内存中清除要写的数据到磁盘
outPutStream.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
字节流不是不能读取汉字,而是一个汉字由 2个或者3个组成
gbk 是由两个字节表示一个汉字
utf-8 是由三个字节表示一个汉字
用字节流去读取汉字,有可能出问题,所以为了保险起见,不能使用
一个char 可以表示一个汉字
public static void main(String[] args) {
try {
// 字节输入流 ,每次读取一个字节
InputStream inputStream = new FileInputStream(new File("\\\\Mac\\Home\\Desktop\\a.txt"));
//int a = inputStream.read();//每次从文件中读取一个字节,如果遇到汉字,无法读取
//System.out.println((char)a);
byte[] arr = new byte[10];//定义了一个小车
int length = 0; //每次从文件中读取的长度
// [a b c d e f g x x e]
// [b x e d e f g g x e]
// [c x e d e f g g x e]
/*
* inputStream.read(arr) 每次读取arr.length个数据到 arr中
* 如果读取不到数据,返回-1,如果读取到数据返回值是读取的数据长度
* 每次读取完数据之后,都要将数据组装成一个新的字符串 new String(arr,0,length)
*/
while((length = inputStream.read(arr)) != -1) {
System.out.print(new String(arr,0,length));
}
inputStream.close();
// char a = '张';
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
字符输入流:
可以完美读取汉字,妈妈再也不用担心有?号了
public static void main(String[] args) {
try {
Reader reader = new FileReader(new File("\\\\Mac\\Home\\Desktop\\a.txt"));
//int a = reader.read();
//System.out.println((char)a);
char[] arr =new char[10];
int length = 0;
while((length = reader.read(arr)) != -1) {
System.out.print(new String(arr,0,length));
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
Writer writer =null;
try {
writer = new FileWriter(new File("\\\\Mac\\Home\\Desktop\\a.txt"),true);
for(int i =0;i< 10 ;i++) {
writer.write("Java 1903班,真棒!!! \r\n" );
}
writer.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if(writer != null) {
writer.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//复制一张图片到另一个地方
public static void main(String[] args) {
InputStream inputStream =null;
OutputStream outputStream =null;
try {
inputStream = new FileInputStream(new File("\\\\Mac\\Home\\Desktop\\a.jpg"));
outputStream = new FileOutputStream(new File("\\\\Mac\\Home\\Desktop\\ab\\aaa.jpg"));
byte[] arr =new byte[1024*20];// 1024 byte = 1kb
int length = 0;
while((length= inputStream.read(arr)) != -1) {
outputStream.write(arr, 0, length);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
outputStream.close();
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
– 扑克牌例子–>参见H:\qian_phone\课件存放_第二阶段\Day03_IO流\代码\Java03\src\com\qfedu\poker
#####1.1 转换流的作用
转换流 --> InputStreamReader
'转换流' 可以将'字节流'转换为'字符流'
//字节流
FileInputStream fileInputStream = new FileInputStream(new File("\\\\Mac\\Home\\Desktop\\a.txt"));
//如果出现读取的内容中文乱码,可以指定字符集,要不然,不要指定,
// 如何查看文件的字符集 记事本 另存为
//通过转换流将字节流转换为字符流
InputStreamReader reader = new InputStreamReader(fileInputStream);
char[] arr =new char[50];
int len = 0;
while((len = reader.read(arr)) != -1) {
System.out.print(new String(arr,0,len));
}
FileOutputStream fileOutputStream = new FileOutputStream(new File("\\\\Mac\\Home\\Desktop\\b.txt"));
//此处指定字符集:指定该文件以什么样的字符集创建,并以该字符集写入内容
//OutputStreamWriter writer = new OutputStreamWriter(fileOutputStream,"UTF-8");
//如果不指定,走默认 默认是以代码所在环境的字符集为默认字符集
OutputStreamWriter writer = new OutputStreamWriter(fileOutputStream);
for(int i=0;i<10;i++) {
writer.write("这个杀手不太冷"+i+"\r\n");
}
//一定要记得flush --> 强制将内存中数据刷入到文件中
writer.flush();
// OutputStreamWriter 中的close方法,其实就是 调用 fileOutputStream 的close
writer.close();
#####2.1 缓冲流的作用
--> 缓冲流:缓冲流就是套在普通流的外面,起到提升性能的作用,并且可以拓展一些功能
--> BufferedReader 拓展的功能: readLine(),详见2.3
BufferedWriter 拓展的功能: newLine(),详见2.4
FileInputStream fileInputStream = new FileInputStream(new File("\\\\Mac\\Home\\Desktop\\a.txt"));
BufferedInputStream inputStream = new BufferedInputStream(fileInputStream);
byte[] arr =new byte[10];// 緋豺
int length = 0;
while((length = inputStream.read(arr)) != -1) {
System.out.print(new String(arr,0,length));
}
#####2.3 BufferedReader
'BufferedReader': 缓冲字符输入流,主要作用是提升读取速度
还要一个重要的作用:可以每次读取一行 readLine()
readLine 只读取文字不读取回车或者换行符
readLine 返回值为null,说明读取到了文件的末尾
BufferedReader reader = new BufferedReader(new FileReader(new File("\\\\Mac\\Home\\Desktop\\a.txt")));
String str = null;
while((str = reader.readLine()) != null) {
System.out.println(str);
}
BufferedWriter writer =null;
try {
writer = new BufferedWriter(new FileWriter(new File("\\\\Mac\\Home\\Desktop\\aaa.txt")));
for(int i = 0;i < 10;i++) {
writer.write("你好!"+i);
writer.newLine();//新起一行
}
// writer.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
//BufferedWriter 是套用了 FileWriter ,close方法中包含了数据的写入操作,所以即使writer不flush,数据也会写入磁盘
//BufferedWriter的close方法,包含了FileWriter的关闭操作
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
static PrintStream in 标准输入流;
static PrintStream out 标准输出流;
public static void main(String[] args) throws Exception {
// PrintStream out 输出流
/*
* System 类中常用方法
* exit(int status) status = 0 说明虚拟机正常退出,否则都是异常退出
* gc() 垃圾回收
*/
//System.exit(0);//虚拟机正常退出
System.gc();//给保洁阿姨打电话而已
long time = System.currentTimeMillis();//可以获取当前时间的一个毫秒值
// yyyy-MM-dd HH-mm-ss
Date date = new Date(time);
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss-SSS");
//System.out.println(format.format(date));
//毫微秒 1秒= 1000毫秒 1毫秒=1000微秒 1微秒= 1000纳秒
// 毫微秒nanoTime 值
//System.out.println(System.currentTimeMillis());
//System.out.println(System.nanoTime());
// System 是一个工具类
//System.out.println("Hello World");
//毁三观之一,syso 不一定是输出在控制台的,只是默认输出在控制台而已
PrintStream printStream = new PrintStream(new File("\\\\Mac\\Home\\Desktop\\console.txt"));
//System.setOut(printStream);
//System.out.println("Hello World!");
//毁三观之二,Scanner 数据,不一定都来自于键盘
InputStream inputStream = new FileInputStream(new File("\\\\Mac\\Home\\Desktop\\scanner.txt"));
System.setIn(inputStream);
Scanner scanner =new Scanner(System.in);
while(scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
}
#####3.3 system工具类?
1 | static long | currentTimeMillis() 返回以毫秒为单位的当前时间。 |
---|---|---|
2 | static void | exit(int status) 终止当前正在运行的 Java 虚拟机。 |
3 | static void | gc() 运行垃圾回收器。 |
4 | static long | nanoTime() 返回最准确的可用系统计时器的当前值,以毫微秒为单位。 |
5 | static void | setIn(InputStream in) 重新分配“标准”输入流。 |
6 | static void | setOut(PrintStream out) 重新分配“标准”输出流。 |
#####4.1 对象流的简单叙述
public static void main(String[] args) throws Exception {
Student stu =new Student("张三",18);
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File("\\\\Mac\\Home\\Desktop\\a.txt")));
outputStream.writeObject(stu);
//对象流只能写一个对象进入文件
//如果想在一个文件中写入多个对象,可以使用容器
//outputStream.writeObject(new Student("lisi",20));
outputStream.flush();
outputStream.close();
}
private static final long serialVersionUID = 1L;
这句话的意思是,序列化的时候如果没有加上面这句话,也会产生一个serialVersionUID
serialVersionUID 是经过一定的算法算出来的,根据类名,构造方法,参数以及个数等因素计算出来的
如果在此期间修改了某些字段【新增,修改或者删除】,那么UID值就会发生改变
从该文件中读取对象的时候就会对比当前的uid值是否和存入到文件中的uid值是否一样,不一样直接报错
解决办法就是:
在普通pojo对象中,生成serialVersionUID该值,建议写1L
#####5.1 装饰者模式定义
装饰者模式:
其实就是对原有的类进行功能的增强而已
1、要修饰的类和本身的类要实现同样的接口或者父类
2、装饰类中一定要传递参数,该参数是你要增强的对象
在什么地方使用了装饰者模式?
IO流中大量使用了装饰者,比如缓存流,转换流,对象流
abstract class AbstractBread {
public abstract void huoMian();
public abstract void faJiao();
public abstract void shangLong();
public void make() {
huoMian();
faJiao();
shangLong();
}
}
class ColorBread extends AbstractBread {
AbstractBread bread;
public ColorBread(AbstractBread bread) {
this.bread = bread;
}
public void printColor() {
System.out.println("老刘闲赚钱慢,加了一些色素和增白剂!");
}
@Override
public void huoMian() {
printColor();
bread.huoMian();
}
@Override
public void faJiao() {
bread.faJiao();
}
@Override
public void shangLong() {
bread.shangLong();
}
}
class NomalBread extends AbstractBread {
@Override
public void huoMian() {
System.out.println("老闫和面,汗流浃背,不用放盐!");
}
@Override
public void faJiao() {
System.out.println("老刘添加一些安琪牌酵母菌进行发酵");
}
@Override
public void shangLong() {
System.out.println("凯云拿着大锅,上笼");
}
}
public class Demo {
public static void main(String[] args) {
AbstractBread nomalBread = new NomalBread();
nomalBread = new ColorBread(nomalBread);
nomalBread.make();
}
}
#####6.1 properties
Properties 是Hashtable的子类,而hashtable又实现了Map接口
Properties中数据存储是按照Key - Value键值对存储的
Properties 一般用来写入数据的不多
properties所用的方法:
setProperties(key,value);
store(输入流, 注释); //注释会在文件的开头给出
load(流); //加载,将流中的数据加载到properties中
public static void main(String[] args) throws Exception {
Properties properties =new Properties();
properties.setProperty("url", "jdbc://mysql://localhost:3306/xiaomi");
properties.setProperty("driver", "com.mysql.jdbc.Driver");
FileWriter writer = new FileWriter("C:\\Users\\98023\\Desktop\\db.properties");
properties.store(writer, "这是一个数据库连接文件");
writer.close();
}
---------------------------------------------------------------------------------------------
/*
http://localhost:8080/虚拟路径名【不是项目名】/资源
如何修改虚拟路径名呢?
1、MyEclipse MyEclipse -->Web Deploy Assemxxxx
2、JavaEE tomcat -->Modules --> 修改path 【类似于idea】
*/
public void testDb() throws Exception {
Properties properties =new Properties();
// 淘汰,测试时没问题,运行时 压根就没有src目录 只要是resource folder 编译完都没有
//FileInputStream inputStream = new FileInputStream("src/db.properties");
//从编译之后的classes文件夹下找该文件
// db.properties 如果在javaee工具下,要加个/
InputStream inputStream = Demo02.class.getClassLoader().getResourceAsStream("db.properties");
System.out.println(inputStream);
properties.load(inputStream);
System.out.println(properties.getProperty("name"));
System.out.println(properties.getProperty("password"));
}
public static void main(String[] args) throws Exception {
Properties properties =new Properties();
/*Reader reader =new FileReader("\\\\Mac\\Home\\Desktop\\db.properties");
properties.load(reader);
System.out.println(properties.getProperty("url"));
System.out.println(properties.getProperty("driver"));*/
FileInputStream inputStream = new FileInputStream("src/db.properties");
properties.load(inputStream);
System.out.println(properties.getProperty("name"));
System.out.println(properties.getProperty("password"));
}
枚举,其实是一个类; 枚举类里面可以写普通的方法
也可以写其他的成员变量,'但是一定要写在枚举值的后面',否则会报错
例如在 1.2 的例子中,'SPRING' 等数据,其实都是 'Season类' 的实例化对象
枚举磊,其实就是常量,而且有范围
一般将一些'常用的数据'写成枚举类,以防止开发人员自己定义变量或者常量,如季节、星期、男女等一些常识性的变量
枚举类一般在开发中经常出现在'switch语句'中
public enum Season {
// public static final Season SPRING=new Season();
// public static final Season SPRING=new Season("春天");
SPRING("春天"), //枚举的每一项用逗号','隔开,末尾用分号 ';'
SUMMER, //此处相当于"public static final Season SUMMER = new Season" 注意,是相当于
AUTUMN,
WINTER;
int a = 10;
public static final double PI = 3.14;
private String name; //枚举类里可以写其他的成员变量
Season(){
//也可以写普通的成员方法
}
Season(String name) { //此处是一个有参构造方法,若是没有上面的无参构造,那么前面的枚举会报错
//此时,可以给枚举加参数,或者在类中添加无参构造
this.name= name;
}
public void print() {
System.out.println("我是在枚举类中的,我是为了证明某些东西得存在而诞生的");
}
}
-----------枚举的使用-----------------------------------------------------------------
public class Demo {
public static void main(String[] args) {
//switch 后面的内容能跟哪些数据类型 byte,short,int,char,String(jdk1.7之后才支持的),枚举【jdk1.5之后】
//switch 后面不可跟的数据类型:double,float,boolean,long,
Season season = Season.SUMMER;
System.out.println(season);
switch(season) {
case SPRING:
System.out.println("春天来了,万物复苏,又到了XX的季节!");
break;
case SUMMER:
System.out.println("夏天到了,终于可以撸串了!");
break;
case AUTUMN:
System.out.println("秋天来了,果实晓磊!");
break;
case WINTER:
System.out.println("冬天到了,可以冬眠!");
break;
default:
System.out.println("哥们,数据传递的不对吧!");
break;
}
Season.AUTUMN.print(); //能用这种方法调用,证明了枚举是实例化对象!!!
}
}
* 'class'类:表示类的类,里面放置了一些 类中共有的一些属性或方法,任何一个普通的类都可以找到该类对应的class类
* 类 和 对象
类是对象的一种抽象表示
一句话!!!--> 反射就是不实例化对象的情况下,拿到一个类中所有内容的一种手段!!
(反射,是所有框架的灵魂!)
反射的缺点:
执行效率慢,破坏了我们面向对象的思想
直接通过类名.class
Class<?> clazz1 = Demo1.class;
根据类的全路径名称得到class
Class<?> clazz2 = Class.forName("完整包名+类名");
通过一个类的实例化对象获取该类的class类对象
Demo1 demo = new Demo1();
Class<? extends Demo1> clazz3 = demo.getClass();
public class Demo02 {
public static void main(String[] args) throws Exception {
Student stu =new Student();
System.out.println(stu.phone);
stu.getAge();
//第一步:不new就可以获取一个类的实例化对象
Class<?> clazz = Student.class;
//获取类中所有的构造方法
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor.getName());
}
//获取Student类中无参数的构造器,得到一个学生对象
Constructor<?> constructor = clazz.getConstructor();
Object object = constructor.newInstance();
Student stu2 = (Student) object;
System.out.println(stu2);
//通过有参数的构造器得到一个Student对象
Constructor<?> constructor2 = clazz.getConstructor(int.class,String.class,String.class);
Object object2 = constructor2.newInstance(12,"张三","181xxxx9903");
Student stu3 = (Student) object2;
System.out.println(stu3.getName());
//第二步:可以获取所有字段,包括私有字段
// 通过getFields 只能获取public字段
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
// Declared 公开的
//可以获取所有字段,包含私有的
Field[] fields2 = clazz.getDeclaredFields(); //getDeclaredFields
for (Field field : fields2) {
System.out.println(field.getName());
}
//第三步:可以调用类中方法,不需要new
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
//调用的是无参数的方法
Method method = clazz.getDeclaredMethod("getName");
System.out.println(method.invoke(stu3));
//如果有参数如何调用呢?
Method method2 = clazz.getDeclaredMethod("setName",String.class);
method2.invoke(stu2, "李四");
System.out.println(stu2.getName());
}
}
public class Student {
private int age;
private String name;
public String phone;
public Student() {
}
public Student(int age, String name, String phone) {
this.age = age;
this.name = name;
this.phone = phone;
}
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;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
获取类中的所有的构造方法:
获取类中所有字段
获取类中的方法
获取注解
一个方法中,
public void XX(int age, String...args) {
}
想获取所有的弹性参数,用args.length,做for循环
注意args是数组,args[i]
//注意:一个方法只允许出现一个弹性参数,单独的参数也可以放,但是必须放到最前面,也就是说,弹性参数必须放到最后面!!!
public class Demo03 {
/*该方法中添加了一个弹性的参数,该参数可以传递多个 0到多个
该弹性参数,一个方法只允许出现一个,而且必须放在所有参数的后面
String... args 本身就是一个数组,数组如何用,它就怎么用
*/
/*public void testParas(String... args,int age) {
}*/
public void a(String name) {
}
public void a(String name,String password) {
}
public void testParas(int age,String... args) {
for (String string : args) {
System.out.println(string);
}
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
public static void main(String[] args) {
Demo03 demo03 = new Demo03();
//demo03.testParas(10);
demo03.testParas(11,"zhangsan","李四","王五");
}
}
public @interface 注解名 {
}
这是一个自定义注解
一般没有方法体,知识用来标识。
注解:
1、标识的作用 ,用于让自己或者别的开发人员查看的
2、注解可以携带信息
注解虽然一般没有方法体,但是也可以加入属性
/*
ElementType.METHOD : 可以作用在方法上
TYPE : 可以作用在类上
FIELD : 全局变量
LOCAL_VARIABLE :局部变量
RetentionPolicy.RUNTIME 定义注解的生命周期
SOURCE :该注解只在源码期有用,编译成class之后就没有了
CLASS : 编译之后,该注解还存在,但是运行的时候没有了
RUNTIME:该注解在什么时候都有
*/
@Target({ElementType.METHOD,ElementType.TYPE}) // 意思是限制该方法只允许出现在哪些地方
@Retention(RetentionPolicy.RUNTIME)
public @interface Author {
public String name() default "zhangsan"; //如果该字段没有默认值,必须在使用该注解时为其赋值
public int age() default 10;//如果有默认值,使用的时候就可以不指定该值。
//public String value();//value这个名字很特别,如果该注解中只需要给value赋值,value= 可以省略的,叫其他名字不好使!
public String[] value();
}
进程 :正在运行的程序 QQ,百度音乐,eclipse
线程:线程是运行在进程中,一个进程可以包含多个线程 飞Q 收文件,聊天
Java是一个支持多线程的开发语言。
线程的生命周期:
线程调用 start() 方法,该线程不一定立即执行,只是进入到了一个就绪状态
可以抢占资源了。
//main方法是主线程
1. 编写一个类,继承Thread类
2. 重写run方法 //线程中的所有逻辑,都要写在run方法中
3. 启动子线程,用 start();
public class TalkThread extends Thread{
public TalkThread() {}
//我们先将名字传递给我们自己写的线程,在线程中,通过父类的构造方法,传递给父类
public TalkThread(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<100;i++) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.err.println("我是子线程!"+i);
}
}
public static void main(String[] args) {
TalkThread thread =new TalkThread();
thread.setPriority(Thread.MIN_PRIORITY); //设置优先级
//thread.run();
thread.start();//启动子线程的正确姿势
TalkThread thread2 =new TalkThread();
thread2.setPriority(Thread.MAX_PRIORITY);
thread2.start();
for(int i=0;i<100;i++) {
System.out.println("我是 主线程:"+i);
}
}
}
1、编写一个类实现Runnable,实现run方法
2、实例化该对象,并将该对象,存入Thread的构造方法中
3、thread.start() 启动子线程
class DemoRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++) {
System.out.println("我是子线程"+i);
}
}
}
public class Demo {
public static void main(String[] args) {
DemoRunnable runnable =new DemoRunnable();
Thread thread =new Thread(runnable);
thread.start();
for(int i=0;i<100;i++) {
System.err.println("我是主线程"+i);
}
}
}
Thread类也是实现了Runnable的,所谓的重写run方法,其实就是重写Runnable的方法
两种写法本质是一样的,最后 start() 调用的都是run方法
建议使用第二种写法:
1、解耦 各干各的事情,相互不纠缠
Runnable里面的run方法就干具体代码实现的事儿
Thread 就干线程启动,关闭,休眠等线程的事儿
2、java是单继承的,一个类继承了 Thread就不可能继承其他的
同一个CPU中,在同一个时刻,只可能有一个程序在运行
我们感知到的电脑可以干很多事情,或者我们的程序可以干很多事情,都是假象!
他们时刻交叉运行,其实就是看谁抢到了资源,谁就运行,存在随机性
####?4. 线程中的常见API
多线程中常见API:
// 1、设置线程优先级 1 5 10
thread.setPriority(Thread.MIN_PRIORITY);
优先级高,是在多次运行后才能看到的效果,不要用这个东西替代逻辑
// 2、线程的名字
默认名字:每个线程都有默认的名字
主线程:main
子线程: Thread-i
起名字:
1、在构造方法中起名字
2、通过setName起名字
System.out.println(Thread.currentThread().getName());//main
TalkThread th01 =new TalkThread("线程一");
System.out.println(th01.getName());
TalkThread th02 =new TalkThread();
th02.setName("线程二");
System.out.println(th02.getName());
// 3、Thread.currentThread() 获取当前线程 这个对象
// 4、Thread.sleep(毫秒数) 将当前线程进入休眠状态 毫秒值到了就会醒
#####5.1 火车站售票举例?
这里举个例子,火车站窗口售票的问题
线程安全问题:多个线程抢占同一资源,有可能引发线程安全问题
多个线程--> 四个窗口
同一资源--> num
现象:同一张票被卖出去多次
public class Demo {
public static void main(String[] args) {
/*Object obj =new Object();
TicketTread t1 = new TicketTread("窗口一",obj);
TicketTread t2 = new TicketTread("窗口二",obj);
TicketTread t3 = new TicketTread("窗口三",obj);
TicketTread t4 = new TicketTread("窗口四",obj);
t1.start();
t2.start();
t3.start();
t4.start();*/
TicketTread2 t1 = new TicketTread2("窗口一");
TicketTread2 t2 = new TicketTread2("窗口二");
TicketTread2 t3 = new TicketTread2("窗口三");
TicketTread2 t4 = new TicketTread2("窗口四");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class TicketTread extends Thread{
static int num = 100;//num属于类的,TicketTread ,该类对象只有一个
//static Object obj =new Object();
Object obj = null;
public TicketTread(String name,Object obj) {
super(name);//调用的是父类的有参数的构造方法,而且必须写在第一句
this.obj = obj;
}
@Override
public void run() {
//同步代码块
while(num > 0) {
//synchronized (obj) {
synchronized ("锁") {// “锁” 将来存放到常量池中
if(num == 0) {
System.out.println("票卖完了!!!");
break;
}
System.out.println(getName() + "窗口卖出了第"+num+"张票");
num--;
}
}
}
}
解决线程安全问题:(1、2、)
//1、加同步代码块锁
synchronized(mutex) {
}
'锁':什么东西可以充当锁呢?
任何对象都可以充当锁mutex,建议使用类的老父亲 Object obj =new Object();
'注意':obj可以放在类里,加static,这样?就唯一了
也可以在main方法中new一个,把obj作为参数,用this传参
或者用字符串常量"锁"等等,也能起到唯一的作用
//2、同步方法,就是在方法上直接加synchronized
public synchronized void run() {
}
//谁是锁??
如果在普通方法上加锁,锁对象是就是该类的实例化对象 this
如果方法是静态方法,锁对象就是该类的Class对象 Class clazz
//两种方案建议使用第一种:
1、灵活 想在哪个地方加都可以
2、高效 一个方法上加了锁,这个方法中所有代码必须都执行完才能释放锁,效率太低了
//这里说明:
Servlet 线程不安全:
Servlet对象是由tomcat【web容器 tomcat\webLogic\Jboss...】创建的
在一个web容器中只有一个serlvet对象
@WebSerlvet("/hello")
public class HelloServlet extends HttpServlet{
int num = 100; //num如果再加加或者减减等数字还会变动
}
//一定不要在servlet里定义全局变量,不安全
1、stop方法可以停止一个线程,但是该方法已经废弃,不再使用
2、建议使用定义变量的方法,如果需要停止该线程,修改变量值
class VNCRunnable implements Runnable{
private String name;
public boolean isFalg = true; //这里定义了一个boolean类型的变量
public VNCRunnable(String name) {
this.name = name;
}
@Override
public void run() {
while(isFalg) {
for(int i=0;i<10;i++) {
System.out.println(name+":"+i);
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
VNCRunnable runnable =new VNCRunnable("VNC线程");
Thread thread =new Thread(runnable);
thread.start();
for(int i=0;i<100;i++) {
System.err.println("我是主线程"+i);
if(i==80) {
//thread.stop();//不建议这样用,暴力执法,不够温柔
runnable.isFalg = false;//建议使用这种,好处是可以完整执行最后一遍代码
}
}
}
}
线程的通信:
两个线程,一个线程给另一个线程发送消息,感觉跟通信一样
一个线程负责生产蛋糕,一个线程负责购买蛋糕,而且还要做到生产一个,购买一个,交替往复
wait() 等待
将当前线程放入到等待的线程池中
notify 唤醒
从当前的线程池中随机唤醒一个等待的线程
cake.notifyAll() 去cake线程池中,唤醒所有等待的线程,而不是去dog,stu等线程池
使用wait() 和 notify() 方法,必须在同步代码块中调用
同步代码块儿的锁必须和wait,notify调用的对象一致
public class Cake {
public String name;
public float price;
//此标识用于判断此时是生产还是购买
public boolean isMake = true;
}
######7.2.2 制作、购买——线程?
class MakeCakeRunnable implements Runnable{
private Cake cake ;
private String name;
public MakeCakeRunnable(String name,Cake cake ) {
this.name = name;
this.cake = cake;
}
// json
@Override
public void run() {
while(true) {
// wait方法只能在同步代码块或者方法中运行
synchronized (cake) {
if(cake.isMake) {
cake.name = "小猪佩奇蛋糕";
cake.price = 88;
System.out.println(name+",生产成功了一个"+cake.name);
cake.isMake = false;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
cake.wait();//进入等待状态,线程阻塞
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
class BuyCakeRunnable implements Runnable{
private String name;
Cake cake = null;
public BuyCakeRunnable(String name,Cake cake) {
this.cake = cake;
this.name = name;
}
@Override
public void run() {
while(true) {
if(cake.isMake==false) {
System.out.println(name+"吃掉了"+cake.name+",该蛋糕是"+cake.price);
cake.name="";
cake.price = 0;
cake.isMake = true;
synchronized (cake) {
cake.notify();//唤醒阻塞的线程
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public class DemoMain {
public static void main(String[] args) {
Cake cake =new Cake();
MakeCakeRunnable make =new MakeCakeRunnable("锦鲤坊",cake);
BuyCakeRunnable buy =new BuyCakeRunnable("小明",cake);
new Thread(make).start();
new Thread(buy).start();
}
}
//1、什么是进程,什么是线程?
每个正在运行的程序就是一个进程;
线程包含在进程中,一个进程可以有多个线程
//2、创建多线程的两种方式?
① 编写一个类继承Thread类,重写run方法(线程中的所有逻辑都要写在run方法中),然后用start启动子线程
② 编写一个类实现Runnable接口,重写run方法,实例化该对象,并将该对象,存入Thread的构造方法中,thread.start() 启动子线程
Thread类也是实现了Runnable的,所谓的重写run方法,其实就是重写Runnable的方法
两种写法本质是一样的,最后 start() 调用的都是run方法
//3、Thread类有哪几种构造方法?
//4、Thread类常用方法有哪些?
setPriority() 优先级设置
getName() 获取名称
setName() 设置名称
currentThread() 获取当前线程
Thread.sleep() 将当前线程进入休眠
//5、什么是线程不安全?如何解决,并指出锁对象指的是谁?
多个线程抢占同一资源,就有可能引发线程安全问题。
加锁
对于普通方法,锁的对象是该类的实例化对象
对于静态方法,锁的对象是该类的class类对象(唯一)
//6、如何停止一个线程?
定义一个变量,通过改变这个变量的值终止一个循环,从而温柔地停止一个线程
//7、说说线程的等待唤醒机制,使用时需要注意哪些问题?
wait(); 进入等待状态,线程阻塞
notify(); 随机唤醒进入等待池的线程中的一个
需要注意两个方法要放在同步代码块中,且锁为两方法前面的变量名
//8、什么是反射?哪些地方用到了反射?
反射就是在不实例化对象的情况下,拿到一个类中所有内容的一种手段
框架中大量用到反射
//9、获取一个类的Class对象的三种方式
1. 直接通过类名
2. 通过类的全路径获取.class
3. 通过类的实例化对象获取
//10、Java中常用注解有哪些?注解有何作用?如何自定义注解?