基础API
API(Application Programming Interface,应用程序接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于 某软件可以访问的一些功能集,但又无需访问源码或理解内部工作机制的细节。也就是公司制定自己的系统接口。
学习方法
这里的每一个API方法都是独立的,一个没有记住不影响另外的API的使用
官方API手册
当一个构造方法嵌套层次较多时,可以从外往内分析,先查API手册看最外层的类的构造方法所需要的参数
API手册查询方法
- 查API手册
- 在IDEA中点击两次
Shift
打开IDEA工具的搜索框,选中右上角的include non-project items
- 在IDEA左侧
External Libraries
里面找到jdk1.8
->rt.jar
->java.lang.Object
Object 对象 顶级父类
hashCode()
哈希码值
哈希算法的原理非常简单,用一句话就能概括:将任意长度的二进制值串映射为固定长度的二进制值串,这个映射的规则就称为“哈希算法”。原始数据映射之后得到的二进制值串就称为“哈希值”。
引自《数据结构与算法之美》–王争
本方法的作用是返回对应对象的int类型的哈希值,力求不同的对象返回不同的哈希值,这样就可以根据哈希值区分不同的对象。
toString()
对象默认的输出内容,在各种ide中都可以自动生成,比如IDEA中Generate
->toString()
使用System.out.println()
方法时,会调用对象的toString()方法,默认会打印对象的getClass().getName() + "@" + Integer.toHexString(hashCode());
equals()
与hashCode
要保持一致,要重写都重写,要不重写都不重写。
如果不重写:
- hashCode()根据地址值生成
- equals()底层使用==
如果重写:
3. hashCode()根据重写后各属性值生成
4. equals()比较的是重写后类型+所有属性值
String
- 不可变、不可继承官方文档
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
- 创建
String
对象的方式
char[] values = {'a','b','c'};String s = new String(values);
使用此种方式每次都会在堆中创建一个新的对象String s = "abc";
推荐使用此种方式,写法简单,且字符串会入池。效果:第一次创建会正常放入常量池,但第二次创建时就不会再次创建了,使用的是之前创建好的值。
关于常量池相关的知识
深入解析String#intern
What is Java String interning?
String s = new String("abc");
使用此种方式每次都会在堆中创建一个新的对象
equals()
String类已经重写了此方法,比较的是两者的字符的值,即相同内容的String将会返回truehashCode()
返回哈希值。String已重写了此方法,根据字符串的具体内容生成哈然值。所以只要字符串的内容相同,则其生成的哈希值也相同。int length()
返回此字符串的长度,返回的是字符的个数,中英文都是按一个字符算returns the number of characters contained in the string object
toUpperCase()
返回所有小写字符转成大写后的字符串toLowerCase()
返回所有大写字符转成小写后的字符串boolean startsWith(String prefix)
判断字符串是否以prefix
开头boolean endsWith(String suffix)
判断字符串是否以prefix
结尾char charAt(int index)
返回char
格式的指定索引处的字符int indexOf(String str[, int fromIndex])
返回当前字符串中str第一次出现的位置,fromIndex
参数是可选的,意思是从fromIndex索引开始查找str第一次出现的位置,可用于查找第二次等出现的位置。int lastIndexOf(String str[, int fromIndex])
返回当前字符串中最后一次出现str的位置,fromIndex
参数是可选的,指在[0,fromIndex]范围内查找最后一次出现str的位置String concat(String str)
返回当前字符串与str拼接后的新串String[] split(String regex)
按指定的regex
将当前字符串拆分成一个字符串数组
regex
为""
时,返回逐个字符拆开后的数组,即"abc".split("");// ['a','b','c']
regex
出现在末尾时,也不会出现空元素,即"abcb".split("b");// ['a','c']
regex
一次也没有出现时,会返回只有一个当前字符串的数组,即"abc".split("d");// ['abc']
regex
支持转义字符\t \n
等都可以使用。正则表达式
String trim()
返回去掉了开头和结尾处的ASCII值<=\u0020,也就是char值小于' '
空格的char值的字符之后的新串,通过观察可以发现常遇到的如’\n’:10、\t
:9、\r
:13这些还有一些其他的不可见与控制字符都是在空格之前的,因此一般使用trim()方法后基本可以确定剩下的就是可见字符开头和结尾的了。byte[] getBytes()
把字符串存储到一个新的 byte 数组中
StringBuilder
和 StringBuffer
- 特点:
- 封装了char[]数组
- 是可变的字符序列
- 提供了一组可以对字符内容修改的方法
- 常用append()来代替字符串做字符串连接”+”
- 内部字符数组默认初始容量是16:super(str.length() + 16);
- 如果大于16会尝试将扩容,新数组大小原来的变成2倍+2,容量如果还不够,直接扩充到需要的容量大小。int newCapacity = value.length * 2 + 2;
- StringBuffer 1.0出道线程安全,StringBuilder1.5出道线程不安全
正则表达式
- 用途:主要用于校验数据
String str = "a";a.matches(regex);
包装类
- 基本类型没有任何功能,只是记录值,把基本类型进行包装,可以提供更加丰富的功能。
Integer
构造函数
new Integer(int i)
创建新对象Integer.valueOf(int i)
使用此种方式创建对象数据在 -128 ~ 127 范围内可以让引用变量直接指向缓存池内的对象,因此两个引用变量都使用此种方式赋值为相同的值时,两者使用==
比较时结果为true
static int parseInt(String s) throws NumberFormatException
将字符串参数作为有符号的十进制整数进行解析。除了第一个字符可以是用来表示负值的 ASCII 减号 ‘-’ (’\u002D’) 外,字符串中的字符都必须是十进制数字。返回得到的整数值,就好像将该参数和基数 10 作为参数赋予 parseInt(java.lang.String, int) 方法一样。
Double
构造方法摘要
Double(double value) 构造一个新分配的 Double 对象,它表示基本的 double 参数。
Double(String s) 构造一个新分配的 Double 对象,表示用字符串表示的 double 类型的浮点值。
- Double 没有缓存池,因此没有所谓高效的效果
static double parseDouble(String s)
返回一个新的 double 值,该值被初始化为用指定 String 表示的值,这与 Double 类的 valueOf 方法一样。
自动拆、装箱
- 自动装箱:
Integer i = 127;
编译器会自动把基本类型int 127
包装成包装类型Integer
,然后交给Integer
的类型的变量来保存,底层发生的代码实际是:Integer.valueOf(127);
自动装箱的方向int
【基本类型】 =>Integer
【包装类型】 - 自动拆箱:
int i1 = i;
编译器会自动把包装类型i拆掉“箱子”,变回基本类型的数据127
,然后交给基本类型的变量i1
来保存,底层代码:int i1 = i.intValue();
,自动拆箱的方向:Integer
【包装类型】 =>int
【基本类型】
BigDecimal
本类常用来解决精确的浮点数运算不精确的问题
- 构造函数
public BigDecimal(String val)
比较精确,推荐使用此种方式
public BigDecimal(double val)
此种方式不推荐,在转换过程中可能会出现问题,如果确实需要传入double
,可以使用new BigDecimal(doubleValue+"");
的方式来避免不精确的问题
此构造方法的结果有一定的不可预知性。有人可能认为在 Java 中写入 new BigDecimal(0.1) 所创建的 BigDecimal 正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于 0.1000000000000000055511151231257827021181583404541015625。这是因为 0.1 无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入 到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。
- 常用的四则运算
public BigDecimal add(BigDecimal augend)
public BigDecimal subtract(BigDecimal subtrahend)
public BigDecimal multiply(BigDecimal multiplicand)
public BigDecimal divide(BigDecimal divisor)
此方法在除不尽时会抛出算术异常,因为没有办法精确表示结果,解决方案:需要额外设置除不尽时的保留位数和舍入方式。divide(a,b,c);
public BigDecimal divide(BigDecimal divisor,
int scale,
RoundingMode roundingMode)返回一个 BigDecimal,其值为 (this / divisor),其标度为指定标度。如果必须执行舍入,以生成具有指定标度的结果,则应用指定的舍入模式。
参数:
divisor - 此 BigDecimal 要除以的值。
scale - 要返回的 BigDecimal 商的标度,就是保留位数。
roundingMode - 要应用的舍入模式。
舍入方式解析
ROUND_HALF_UP 四舍五入,五入 如:4.4结果是4; 4.5结果是5
ROUND_HALF_DOWN 五舍六入,五不入 如:4.5结果是4; 4.6结果是5
ROUND_HALF_EVEN 公平舍入(银行常用)参考银行家舍
比如:在5和6之间,靠近5就舍弃成5,靠近6就进位成6,如果是5.5,就找偶数,变成6
ROUND_UP 直接进位,不算0.1还是0.9,都进位
ROUND_DOWN 直接舍弃,不算0.1还是0.9,都舍弃
ROUND_CEILING(天花板) 向上取整,取实际值的大值
朝正无穷方向round 如果为正数,行为和round_up一样,如果为负数,行为和round_down一样
ROUND_FLOOR(地板) 向下取整,取实际值的小值
朝负无穷方向round 如果为正数,行为和round_down一样,如果为负数,行为和round_up一样
Scanner sc = new Scanner(System.in);
System.out.println("请输入两个小数:");
double a = sc.nextDouble();
double b = sc.nextDouble();
BigDecimal bd1 = new BigDecimal(a+"");
BigDecimal bd2 = new BigDecimal(b+"");
System.out.println("+"+bd1.add(bd2));
System.out.println("-"+bd1.subtract(bd2));
System.out.println(bd1.multiply(bd2));
System.out.println("/:"+bd1.divide(bd2,3,BigDecimal.ROUND_HALF_UP));
高级API
IO
学习方式:学习父类的公共方法,学习子类对象的创建方式
Stream
File
new File(String path);
只会在内存中创建一个File类型的对象,并不会在对应路径下创建一个真实的文件
path 为 \ 时路径为当前磁盘的根目录 如D:
path 为 “” 时,路径为当前Project所在目录 ,如D:\Personal\CGB210901
-
isDirectory()
是否是文件夹,当exists()
方法返回false时,isDirectory()
和isFile()
都会返回false
-
length()
返回文件的大小,与windows系统中右键查看显示的字节数一致 -
isFile()
是否是文件 -
getName()
获取文件名字或文件夹的名字 -
getParent()
file是文件时获取到的是D:\a
,如果是文件夹,获取到的是上一级目录D:\a
,反正获取到的都是没有最后面的斜线的 -
getAbsolutePath()
获取绝对路径,最后面是没有斜线的 -
exists()
是否存在 ,实际工作过程中请先使用此方法进行存在性判断,再做后续的处理。 -
boolean createNewFile()
返回:如果指定的文件不存在并成功地创建,则返回 true;如果指定的文件已经存在,则返回 false .。会有IOException
检查异常,需要处理掉才可以通过编译,否则报错。如果写的路径格式不正确,可能会报这个IOException异常 -
mkdirs()
创建多层文件夹 -
mkdir()
创建文件夹 -
delete()
删除文件或空文件夹 -
list()
文件及文件夹名字的数组 -
listFiles()
返回当前文件夹下的所有文件的数组,如果当前文件夹不存在或当前并非文件夹,则该方法返回的是Null
Stream
InputStream
FileInputStream
new FileInputStream(File fiile);
new FileInputStream(String path);
read();
从输入流读取数据的下一个字节。值字节作为0到255范围内的整数返回。如果由于到达流的结尾而没有字节可用,则返回值-1。此方法会一直阻塞,直到输入数据可用、检测到流结束或引发异常为止。
- File
字节流:针对二进制文件
- InputStream
- FileInputStream
- BufferedInputStream
- ObjectInputStream
- OutputStream
- FileOutputStream
- BufferedOutputStream
- ObjectOutputStream
字符流:针对文本文件
- Reader
- FileReader
- BufferedReader
- InputStreamReader
- Writer
- FileWriter
- BufferedWriter
- OutputStreamWriter
- PrintWriter一行行写出
InputStream in = null;
try {
//创建流对象,注意 InputStream是抽象父类,不可以实例化
//InputStream fin = new FileInputStream(new File("d:\\a\\1.txt"));//这种方式也可以,但是不常用,因为会创建一个File对象,浪费空间
in = new FileInputStream("d:\\a\\1.txt");
int read;
//循环读取数据,只要读到的数据不等-1,说明还有数据,符合循环条件,继承循环
while ((read = in.read()) != -1) {
System.out.println(read + " " + Integer.toBinaryString(read));
}
in.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();// 关流操作也有可能会抛出异常,因此也需要捕获
} catch (IOException e) {
e.printStackTrace();
}
}
BufferedInputStream
InputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream("D:\\a\\1.txt"));
int b;
while ((b = in.read()) != -1) {
System.out.println(b);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Reader
FileReader
Reader reader = null;
try {
reader = new FileReader("D:\\a\\1.txt");
int read;
while ((read = reader.read()) != -1) {
char c = (char)read;
System.out.print(c);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
BufferedReader
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("D:\\a\\4.txt"));
String line;
while ((line = br.readLine()) != null) {
System.out.print(line);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
OutputStream
FileOutputStream、 BufferedOutputStream
FileOutputStream
的重载构造方法里面有一个boolean append的参数,如果 true则使用追加写入。
OutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream("D:\\a\\3.txt",true));// boolean append
out.write(97);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter("D:\\a\\3.txt", true));
writer.write("中国");
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
序列化
- 定义:序列化是将对象的状态信息转换为可存储或传输的形式的过程。一般是以字节码或XML格式传输。而字节码或XML编码格式可以还原为完全相等的对象。这个相反的过程称为反序列化。
IDEA实现序列化接口Serializable自动生成serialVersionUID
注意:需要被序列化和被反序列化的对象需要implements Serializable
- 推荐大家:一次序列化操作对应一次反序列化操作
- JVM会给每个要序列化输出的类分配一个唯一的UID,只要更改了这个类的定义,自动生成的默认UID就会改变。
private static final long serialVersionUID = 8300075891166864588L;
这条语句不需要写,JVM会自动计算,且在类定义发生改变时自动更新,强制进行版本管理。
ObjectInputStream
- ObjectInputStream把之前序列化输出的数据读到内存程序中,相当于对象读取到程序
- 序列化输出的数据我们是看不懂的,我们需要后期通过反序列化技术把之前输出的数据重新恢复成对象,才能使用。
ObjectOutputStream
ObjectOutputStream 将对象的基本数据类型写入OutputStream,通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。
public static void main(String[] args) {
Student s = new Student("张三丰",18,"山上",'男');
Student s1 = new Student("思思",18,"山下",'女');
Student[] students = new Student[2];
students[0] = s;
students[1] = s1;
serializableToFile(students);
serializableFromFile();
}
public static void serializableToFile(Object obj) {
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(new FileOutputStream("stu.obj"));
out.writeObject(obj);
System.out.println("对象序列化完成!");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void serializableFromFile(){
ObjectInputStream in = null;
try {
in = new ObjectInputStream(new FileInputStream("stu.obj"));
Student[] stu = (Student[])in.readObject();
System.out.println("反序列化完成,将输出内容");
for (Student student : stu) {
System.out.println(student);
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
泛型
- 泛型通常与集合一起使用,用来约束集合中元素的类型,泛型的好处:可以约束集合中的数据类型,把报错的时机提前,而不是运行时才报错。在向集合中添加元素时,会先检查元素的数据类型,不是要求的类型就编译失败。
- 泛型不支持基本类型,可以使用其对应的包装类。即只可以是引用类型
- 泛型可以实现通用代码的编写,使用E表示元素的类型是ELement元素
- 泛型的语法要求:如果在方法上使用泛型 ,必须两处同时出现,一个是传入参数的类型为泛型,一个是返回值类型前的泛型类型,表示这是一个泛型方法。
public static <E> void print(E[] es) {
for (E e : es) {
System.out.print(e + ",");
}
System.out.println();
}
集合Collection
集合的迭代
- 迭代步骤
- 获取集合的迭代器
- 判断集合中是否仍有下一个元素可以迭代
- 获取当前迭代到的元素
Iterator<Integer> iterator = c.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
//逆序遍历
ListIterator<String> listIterator = list.listIterator();
while (listIterator.hasNext()) {
listIterator.next();
}
while(listIterator.hasPrevious()){
System.out.print(listIterator.previous()+" ");
}
List
- 元素都有下标
- 数据是有序的
- 允许存放重复数据
ArrayList
- list接口的实现类
- 底层结构是数组,内存空间连续
- 元素有下标,有序,允许存放重复的数据
- 通常进行的是根据下标进行的操作
- 增删操作慢,查询操作比较快【数据量比较大时】
- 默认大小10,扩容默认为原大小的1.5倍,若仍不够则直接扩容到需要的大小,扩容使用Arrays.copyOf得到新的、容量符合的数组
LinkedList
- list接口的实现类
- 底层结构是链表,内存不连续
- 元素有下标,有序,允许 存放重复的数据
- 通常进行的是首尾节点相关的操作
- 增删操作快,查询操作比较慢【数据量比较大时】
- 注意:LinkedLIst查询也不都是慢,首尾操作还是比较快的
LinkedList<String> list = new LinkedList<>();
list.element();//
list.peek();//
list.peekFirst();//
list.peekLast();//
list.offer(E e);//
list.offerFirst(E e);//
list.offerLast(E e);//
list.poll();//删除并返回首元素
list.pollFirst();//删除并返回首元素
list.pop();//删除并返回首元素
Map<K,V>
- 类型参数:K- 表示此映射所维护的键 V-表示此映射所
特点
- Map可以根据键来提取对应的值
- Map的键不允许重复,如果重复则对应的值会被覆盖
- Map存放的都是无序的数据
- Map的初始容量是16,默认的加载因子是0.75
Map原源解读 https://blog.csdn.net/woshimaxiao1/article/details/83661464
HashMap<K,V>
Modifier and Type | Method and Description | Description |
---|---|---|
void | clear() | Removes all of the mappings from this map (optional operation). |
boolean | containsKey(Object key) | Returns true if this map contains a mapping for the specified key. |
boolean | containsValue(Object value) | Returns true if this map maps one or more keys to the specified value. |
Set<Map.Entry<K,V>> | entrySet() | Returns a Set view of the mappings contained in this map. |
boolean | equals(Object o) | Compares the specified object with this map for equality. |
V | get(Object key) | Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key. |
default V | getOrDefault(Object key, V defaultValue) | Returns the value to which the specified key is mapped, or defaultValue if this map contains no mapping for the key. |
int | hashCode() | Returns the hash code value for this map. |
boolean | isEmpty() | Returns true if this map contains no key-value mappings. |
Set | keySet() | Returns a Set view of the keys contained in this map. |
V | put(K key, V value) | Associates the specified value with the specified key in this map (optional operation). |
void | putAll(Map<? extends K,? extends V> m) | Copies all of the mappings from the specified map to this map (optional operation). |
V | remove(Object key) | Removes the mapping for a key from this map if it is present (optional operation). |
default boolean | remove(Object key, Object value) | Removes the entry for the specified key only if it is currently mapped to the specified value. |
int | size() | Returns the number of key-value mappings in this map. |
Collection | values() | Returns a Collection view of the values contained in this map. |
特点
- HashMap底层是Entry[],初始容量16,加载因子是0.75f,扩容按约为2倍扩容、
Set
特点
- 没有下标
- 不能重复,常用来去重
- Set集合中的元素都是没有顺序的,即输出时的顺序与输入的顺序不一致。因为元素存储的位置与其hashCode相关
- 可以存
null
,但是只能存一个 - 自定义类如果也要使用Set的去重功能,需要重写
hashCode()
和equals()
方法
进程
- 进程: 正在运行当中的程序,会占用 对应的内存区域。
- CPU:Central Processing Unit;
- 特点
- 独立性:系统中独立存在的实体,它可以拥有有自己独立的资源,每个进程都拥有自己私有的地址空间在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。
- 动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令的集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的。
- 并发性:多个进程可以在单个处理器CPU上并发执行,多个进程之间互不影响。
线程
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。(进程是资源分配的最小单位)
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)
- 概念
- 线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.
- 一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程。
- 我们看到的进程的切换,切换的也是不同进程的主线程
- 多线程可以让同一个进程同时并发处理多个任务,相当于扩展了进程的功能。
- 串并行
- CPU:电脑的核心处理器,类似于“大脑”
- 串行:是指同一时刻一个CPU只能处理一件事,类似于单车道
- 并行:相对来说资源比较充足,多个CPU可以同时处理不同的多件事,类似于多车道
- 并发:相对来说资源比较紧缺,多个进程同时抢占公共资源,比如多个进程抢占一个CPU
- 特性
-
随机性:一个CPU【单核】同一时间只能执行一个进程中的一个线程,执行多线程时,CPU以纳秒级别甚至是更快的速度高效的在不同的线程间切换着,也就是说,宏观层面上,所有的进程看似并行【同时运行】,但是微观层面上是串行的【同一时刻,一个CPU只能处理一件事】。
-
CPU分时调度:时间片,即CPU分配给各个线程的一个时间段,称作它的时间片,即该线程被允许运行的时间,如果在时间片用完时线程还在执行,那CPU将被剥夺并分配给另一个线程,将当前线程挂起,如果线程在时间片用完之前阻塞或结束,则CPU当即进行切换,从而避免CPU资源浪费,当再次切换到之前挂起的线程,恢复现场,继续执行。
- 注意:我们无法控制OS选择执行哪些线程,OS底层有自己规则,如:
- FCFS(First Come First Service 先来先服务算法)
- SJS(Short Job Service短服务算法)
- 注意:我们无法控制OS选择执行哪些线程,OS底层有自己规则,如:
-
线程的状态
- 就绪(可运行)状态:线程已经准备好运行,只要获得CPU,就可立即执行
- 执行(运行)状态:线程已经获得CPU,其程序正在运行的状态
- 阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的状态,即线程执行阻塞
-
- 多线程实现方案1:继承Thread类
- 自定义一个类MyThread,继承Thread类
- 在MyThread类中重写父类的run()方法,这个run()里面写业务代码
- 创建MyThread类的对象-- 对应的是线程的新建状态
- 调用start()方法以多线程的方式将多个线程对象加入到就绪队列中,等待OS选中
- 该方案缺点:占用掉了继承的位置
public class Hello {
public static void main(String[] args) {
MyThread t1 = new MyThread("线程1");
MyThread t2 = new MyThread("线程2");
MyThread t3 = new MyThread("线程3");
MyThread t4 = new MyThread("线程4");
MyThread t5 = new MyThread("线程5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
class MyThread extends Thread{
public MyThread(){}
public MyThread(String name){super(name);}
@Override
public void run(){
for (int i = 0;i < 10; i++) {
System.out.println(getName()+":"+i);
}
}
}
run()
与start()
区别run()
:主要用来封装我们自定义的业务代码,直接调用本方法,相当于普通方法的调用start()
:主要用来以多线程的方式启动线程,然后由JVM调用本线程的run()
执行业务- 注意:这里的启动指的是将线程对象加入到就绪队列中,具体什么时候执行还得看OS
- 多线程实现方案2:实现
Runnable
接口- 自定义
RunnableThread
类,实现Runnable
接口 - 重写接口声明的
run()
方法,该方法里面为业务代码 - 创建
Runnable
对象,也就是业务对象 - 创建线程对象,并将业务对象交给线程对象来完成,就是说可以创建一个业务对象,但是交给多个线程对象使用。
- 调用创建的
Thread
对象的start()
以多线程的方式启动线程 - 如果需要使用与当前线程相关的信息,如获取线程名字,可以通过
Thread.currentThread()
来获取当前线程对象的引用,并调用其对应的方法
- 自定义
public class TestThread2 {
public static void main(String[] args) {
RunableThread rt = new RunableThread();
Thread t1 = new Thread(rt,"线程1");
t1.start();
}
}
class RunableThread implements Runnable{
@Override
public void run() {
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
System.out.println(threadName +":"+i);
}
}
}
同步锁
- 多线程会出现共享资源安全问题,由于线程的随机性+线程的阻塞,如以下方案
public class Hello {
public static void main(String[] args) {
Runnable ticketsRunnable = new TicketsRunnable();
for(int i = 0;i<4;i++){
Thread t = new Thread(ticketsRunnable,"窗口"+(i+1));
t.start();
}
}
}
class TicketsRunnable implements Runnable{
int num = 100;
@Override
public void run(){
String name = Thread.currentThread().getName();
while(true){
try{
Thread.sleep(1);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(name + ":售出:"+num-- +"====" +LocalDateTime.now());
if(num<=0){
System.out.println(name+":票已卖光,窗口关闭");
break;
}
}
}
}
- 满足以下所有条件则可能存在线程安全问题
- 多线程程序
- 有共享数据
- 多条语句操作共享数据,因为没法立即完成数据的修改 操作
- 概念:
- 同步:体现了排队的效果,同一时间只能有一个线程独占资源,其他需要此资源的线程排队。优点是保证了线程的安全,缺点是效率会降低。
- 异步:体现了多线程抢占资源的效果,线程间不互相不等待,互相抢占资源。优点是效率高,缺点是有安全隐患。
synchronized
关键字- 格式
synchronized (需要上锁的对象){可能会出现安全隐患的代码(也就是可能出现问题的操作共享数据的多条语句);}
- 相当于给容易出现问题的代码加了一把锁,包裹了所有可能会出现数据安全问题的代码,加锁之后,就有了同步(排队)的效果,但是加锁的范围需要考虑,不能太大,太大的话干啥都要排队,效率低。也不能太小,太小了的话锁不住,还是有安全隐患
- 注意:同步代码块必须保证所有线程对象使用同一把唯一的锁,锁对象必须唯一!!!锁对象的类型不作限制 ,唯一 就行。
- 写法
- 锁类的成员变量时,可以在类内部再创建一个Object对象,然后对该Object对象加锁
- 锁类的静态成员变量时,可以在类内部再创建一个静态的Object对象,对该静态Object对象加锁
- synchronized(class){},锁住需要锁的对象的类文件,则此类所有对象都可以看得到这个锁
- 注意:一般不使用线程消耗的对象作为锁对象。可以创建一个非消耗的对象,或者直接用当前对象作为锁对象。失败案例
扩展:线程锁
线程池(扩展)
Executorys
用来辅助创建线程池工具类
newFixedThreadPool(int);可以创建指定数目线程数的线程池对象
创建出来的对象就是ExecutorService,负责新建、启动、销毁线程
反射
通过class文件获取其相关属性及方法
- 通过使用含有
Declared
的方法进行操作的反射被称为暴力反射。 - 获得class对象
Class c = Class.forName("cn.tedu.review.TestThread");//该方法会抛出ClassNotFoundException,所以需要先捕获处理
Class c = 类名.class
Class c = 对象.getClass();
- 构造方法的反射
//获取构造方法,创建对象
try {
Object obj = c.newInstance();// 需要该类提供无参构造
System.out.println(obj);
// 获取指定的含参构造方法
Constructor<?> constructor = c.getConstructor(String.class, int.class);
//通过指定含参构造方法创建对象
Object obj1 = constructor.newInstance("张三", 18);
System.out.println(obj1);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
- 成员变量的反射
//获得该类自己声明的所有成员变量
Field nameField = c.getDeclaredField("name");//获取指定的成员变量
//设置访问权限,设置为true后可以访问私有变量、跨包访问,suppress Java language access checking when it is used
nameField.setAccessible(true);
nameField.set(obj,"张三丰");//通过属性的反射对象,来设置指定地象相对应的属性的值
System.out.println(nameField.get(obj));//通过属性的反射对象,来获取指定地象相就属性的值
Class<?> typeClass = field.getType();//获得该成员变量对应数据类型的class对象
Field[] fields = c.getFields();//获得该类中所有使用public修饰的成员变量,包含继承的public变量
Field[] fields = c.getDeclaredFields();//只获得该类中声明的成员变量,无视权限,不包含继承的
//遍历所有声明的成员变量,并根据类型进行赋值
for (Field field : fields) {
//设置访问权限,设置为true后可以访问私有变量、跨包访问
field.setAccessible(true);
//拿到成员变量名称
String fieldName = field.getName();
//如果给的配置Map中有对应成员变量的值
if (map.containsKey(fieldName)) {
//根据成员变量类型名称的不同,来对map里面的字符串做转换
switch (field.getType().getName()) {
case "java.lang.String":
// 通过反射设置对象中的属性的值
field.set(obj, map.get(fieldName));
break;
case "int":
field.set(obj, Integer.parseInt(map.get(fieldName)));
break;
case "double":
field.set(obj, Double.parseDouble(map.get(fieldName)));
break;
case "char":
//如果是char类型,则直接取字符串的第一个字符
if (map.get(fieldName).length() > 0) {
field.set(obj, map.get(fieldName).charAt(0));
}
break;
default:
break;
}
}
}
- 成员方法的反射
// 获取方法
Method[] methods = c.getMethods();//获得所有public的方法
Method[] methods = c.getDeclaredMethods();//获得所有本类自己声明的方法,无论权限
//通过参数类型列表来获取指定方法,getDeclaredMethod(String name, Class<?>... parameterTypes)后面是可变参数
Method saveMethod = c.getDeclaredMethod("save", int.class, String.class);
Object obj = c.newInstance();
saveMethod.setAccessible(true);
saveMethod.invoke(obj, 18,"张三丰");//使用方法的反射对象代理指定对象调用对应方法,并传入参数
for (Method method : methods) {
//获得权限修饰符的名字,getModifiers()返回的是一个整数,Modifier类可以把它转成文字
String modifyName = Modifier.toString(method.getModifiers());
//获得返回值类型的名称,getReturnType()返回的是字节码对象,可以做进一步的处理,这里只取其名字
String returnTypeName = method.getReturnType().getSimpleName();
//获得参数列表中的各类型的class文件
Class<?>[] parameterTypeClasses = method.getParameterTypes();
System.out.print(modifyName + " " + returnTypeName + " " + method.getName() + "(");
int argCount = 0;
//遍历各个参数类型文件
for (Class<?> parameterTypeClass : parameterTypeClasses) {
//输出参数类型的名字
System.out.printf("%s a%s",parameterTypeClass.getSimpleName(),argCount);
if (argCount++ < parameterTypeClasses.length - 1){
System.out.print(",");
}
}
System.out.println(")");
}
Java中的测试方法
@Test
使用该注解标记下面的方法为测试 方法public
权限修饰符必须是publicvoid
返回值类型必须是void()
参数列表必须为空JUnit4
目前使用的是这个
@Test
public void test(){
}
注解Annotation
- 分类
- JDK自带注解
- 元注解:定义注解的注解
- 自定义注解
- 常见注解:单独收集贴
- 元注解:用来描述注解的 注解,就5个
@Target
注解用在哪里:类上、方法上、属性上等等
@Retention
注解的生命周期:源文件中、字节码文件中、运行中
@Inherited
允许子注解继承
@Documented
生成javadoc的时候会包含注解
@Repeatable
注解为可重复类型注解,可以在同一个地方多次使用,不常用
- @Target ElementType,其中ElementType有以下几种值
ElementType.TYPE 应用于类的元素
ElementType.METHOD 应用于方法级
ElementType.FIELD 应用于字段或属性(成员变量)
ElementType.ANNOTATION_TYPE 应用于注解类型
ElementType.CONSTRUCTOR 应用于构造函数
ElementType.LOCAL_VARIABLE 应用于局部变量
ElementType.PACKAGE 应用于包声明
ElementType.PARAMETER 应用于方法的参数
-@Retention RetetionPlicy
RetentionPolicy.SOURCE;//只会存在于源码里面,编译后被擦除
RetentionPolicy.CLASS;//默认生命周期,一直到编译后,但是运行时会被擦除
RetentionPolicy.RUNTIME;//一直活动运行结束,可以通过反射取得
//1.定义自定义注解
/*1.1.首先注意注解定义的语法与java不同 */
/*1.2.定义自定义注解的格式:@interface 注解名{} */
//2.通过@Target注解标记自定义注解Rice可以使用的位置,通过ElementType.常量名来指定,可以写多个@Target({ele1,ele2})
/*@Target(ElementType.TYPE)//只设置单个位置的时候*/
@Target({ElementType.TYPE, ElementType.METHOD})//需要设置多种位置时
/*3.通过Retention注解标注生命周期*/
@Retention(RetentionPolicy.RUNTIME)//时效,(SOURCE,CLASS,RUNTIME)3选一
@interface Rice {
//5.添加注解的属性
/*6.注意此处int age();不是方法的定义,而是给自定义的注解添加了一个age属性*/
//int age();//给自定义注解添加了一个普通属性age,类型是int
int age() default 0;//给自定义注解添加了一个普通属性age,默认值是
/*还可以添加特殊属性value,要求:名字必须是value,类型不限.赋值时如果不需要同时给其他属性赋值则可以不写"属性名=",如@Rice("apple")*/
//String value();
String value() default "banana";//定义特殊属性,并赋予默认值
}
/*注解使用时,在规定位置,格式:@注解名 即可*/
@Rice(value = "", age = 1)
class TestAno {
String name;
@Rice("lemon")//当属性有设置默认值后,则该属性的赋值就是可选的了
public void eat() {
System.out.println("eat()");
Date date = new Date();
}
}