Java常用API及基础语法

一、Object类中的几个常用方法:

1、toString

①作用:将对象转化成字符串,一般都会重写该方法(默认输出:类名@对象内存地址);
②直接输出类会自动调用toString方法;
System.out.println(cat); //输出com.test.javase.Cat@1590164

2、equals

①作用:判断两个对象是否相等(默认使用==判断,所以要重写该方法);

3、finalize

①作用:如果希望在对象销毁时执行一段代码,可以将其写到finalize()方法当中;
②源码:protected void finalize() throws Throwable { };
③这个方法不需要程序员手动调用,JVM垃圾回收器负责调用这个方法;
④执行时机:当一个java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用finalize()方法

4、hashCode

①作用:返回一个对象的哈希代码值,通过将该对象的内存地址转换成一个整数,函数返回一个int型数据

二、数组

1、一维数组初始化

静态初始化:int[] a = {100, 200, 102};
动态初始化:int[] a = new int[5]; //默认值为0
或者[]放在后面:int a[] = new int[10];

2、数组拷贝

System.arraycopy(原数组,拷贝开始下标,目标数组,拷贝到目标数组的下标,拷贝长度)

3、二维数组初始化

静态初始化:int[][] a = {{1,2,3},{4,5,6},{7,8,9}};
动态初始化:int[][] a = new int[5][5];

4、Arrays工具类

排序:Arrays.sort
二分查找:Arrays.binarySearch

三、String类

1、字符串都是直接存储在方法区的字符串常量池当中的

视频链接:https://www.bilibili.com/video/BV1Rx411876f?p=587&spm_id_from=pageDriver&vd_source=451d825d8e1ffcbfd124804db3810f11
"hello":java双引号引起来都是一个字符串对象,都会在字符串常量池中创建一个新对象(相同的字符串只会创建一个),当用new创建String对象时,堆里面的String对象里面存放的是字符串常量池里面的内存地址【两个是两种不同的对象】
①第一种初始化方式:String s1 = "hello";

String s1 = "hello";
String s2 = "hello";
System.out.println(b == c); 这里打印true

image.png

②第二种初始化方式:String x = new String("xyz");

String x = new String("xyz");
String y = new String("xyz");
System.out.println(b == c); 这里打印false
image.png
2、字符串比较时避免空指针异常

将username.equals("admin")修改为:
"admin".equals(username)

3、String类构造函数

①将byte数组全部转化为字符串:String s = new String(bytes);

byte[] bytes = {97, 98, 99};    97是a,98是b,99是c
String s = new String(bytes);
System.out.println(s); 输出abc

②将char数组全部转换为字符串:

char[] c = {'a', 'b', 'c'};
String s = new String(c);
System.out.println(s);  输出abc
4、String各类方法:

①equalsIgnoreCase()
"Abc".equalsIgnoreCase("abc"); true,【忽略大小写比较是否相等】
②getBytes()
byte[] b = "abcd".getBytes(); [97, 98, 99, 100] 【将字符串转换为byte数组】
③String.valueOf()
String s1 = String.valueOf(true); 【静态方法,可以将非字符串转换成字符串,true->"true"】
System.out.println(s1);

5、StringBuffer(线程安全的)

作用:当需要频繁的拼接字符串时,为了避免不断的在字符串常量池当中创建对象,可以使用StringBuffer。StringBuffer底层实际是一个byte[]数组,初始化容量是16;

StringBuffer s = new StringBuffer();
s.append("a");
s.append("b");
s.append("c");
s.append(3.14);
s.append(8);
s.append(true);
System.out.println(s);   结果:abc3.148true

优化:在创建StringBuffer的时候尽可能给定一个合适的初始化容量,减少底层数组的扩容次数。

6、StringBuilder(非线程安全的)

StringBuilder跟StringBuffer区别:StringBuffer的方法都有synchronized关键字修饰,StringBuffer在多线程环境下运行是安全的,而StringBuilder在多线程下是不安全的。

四、八种包装类

1、八种类

Byte、Short、Integer、Long、Float、Double、Boolean、Character

2、装箱、拆箱

装箱:基本数据类型转换为包装类
Integer i = new Integer(123);
插箱:包装类转换为基本数据类型
int relValue = i.intValue();

自动装箱:基本数据类型自动转换为包装类
Integer i = 123; 【i中保存的还是指向对象的内存地址,并非123】
自动插箱:包装类自动转换为基本数据类型
int j = i;

3、注意点

java为了提高程序的执行效率,将[-128到127]之间所有的包装对象提前创建好,放到一个方法区的“整数型常量池”当中,目的是只要用这个区间的数据不需要再new了,直接从整数型常量池当中取出来。
例如:

Integer i = 127;
Integer j = 127;
System.out.println(i == j); true
        
Integer x = 128;
Integer y = 128;
System.out.println(x == y); false

图解:


image.png

五、日期处理

1、格式化时间 SimpleDateFormat
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
System.out.println(dateFormat.format(date));  结果:2022-07-01 14:27:12 400
2、字符串转换成Date类型 SimpleDateFormat

【字符串要保持与创建的SimpleDateFormat 格式一致】

String s = "2022-07-01 14:27:12 400";
Date date1 = dateFormat.parse(s);
System.out.println(date1);
3、System.currentTimeMillis() 获取时间戳

六、数字处理

1、格式化数字
DecimalFormat df = new DecimalFormat("###,###.##");
System.out.println(df.format(3245.62)); 结果3,245.62

# 代表任意数字
, 代表千分位
. 代表小数点
0 代表不够时补0

2、高精度数字BigDecimal

专门用于财务软件之中

BigDecimal d1 = new BigDecimal(100);
BigDecimal d2 = new BigDecimal(200);
System.out.println(d1.add(d2));  结果:300
3、随机数
Random r = new Random();
System.out.println(r.nextInt(101));   结果:产生0-100(不包含101)的随机整数
4、枚举enum

语法:

enum Color{
  YELLOW, RED, BLACK, PINK, GREEN
}

七、异常

1、java异常处理机制

①异常在java中以类和对象的形式存在,所有异常都是发生在运行阶段
②异常的继承结构:
Object下面有Throwable
Throwable下面有两个分支:Error(不可处理)和Exception(可处理)
Exception下面有两个分支:
ExceptionSubClass:编译时异常,要求在编写程序阶段必须先对这些异常进行处理,如果不处理编译器会报错
RuntimeException:运行时异常,编写程序的时候可以不处理
③编译时异常 和 运行时异常的区别:
编译时异常发生概率高

2、异常处理

第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级(抛给调用者,调用者如果没有处理就继续上抛,直到抛给main函数,main函数抛给JVM,JVM检查到异常则停止程序运行)throw是手动抛出异常;【异常上抛】
第二种方式:使用try...catch语句进行异常捕捉 。【异常捕捉】

3、try...catch

①catch后面小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型
②catch可以写多个,建议catch的时候,精确的一个个处理,这样有利于程序调试
③catch写多个的时候,从上到下,必须遵守从小到大。

4、异常对象的常用方法

e.printStackTrace(); 打印异常堆栈信息
String msg = e.getMessage(); 获取异常简单描述信息

5、finally

在finally字句中的代码是最后执行的,且一定会执行,所以一般在finally语句块中完成资源的释放/关闭
①语法:

try{

}catch (Exception e){

}finally{

}

②面试问题
第一种:

try {
    System.out.println("try");
    return;     
} finally{
    System.out.println("finally");  会执行
}

第二种:

try {
    System.out.println("try");
    System.exit(0);  退出JVM
} finally{
    System.out.println("finally"); 不会执行
}

第三种:

int i = 100;
try {
    return i;  i最后返回100
} finally{
    i++;
}
6、final、finally、finalize的区别

final是一个关键字,表示最终的、不变的,无法继承、无法覆盖、无法重新赋值;
finally也是一个关键字,与try联合使用,使用在异常处理机制中,在finally语句块中的代码是一定会执行的;
finalize()是Object类中的一个方法,作为方法名出现,当一个java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用finalize()方法。

7、自定义异常

第一步:编写一个类继承 Exception(编译时异常) 或者 RuntimeException(运行时异常);
第二步:提供两个构造方法,一个无参数的,一个带有String参数的。

八、集合

1、集合中任何时候存储的都是“引用”

集合不能直接存储基本数据类型

2、List、Set接口(Map键值对)

Collection接口下:
List
Set
①List、Set区别
List:有序可重复,有下标
Set:无序不可重复,无下标
②List接口的实现
ArrayList集合底层采用了数组(非线程安全)
LinkedList集合底层采用了双向链表
Vector集合底层采用了数组(线程安全)
③Set接口的实现
HashSet集合底层采用了HashMap集合
TreeSet集合底层采用了TreeMap集合(TreeSet继承自SortedSet)
HashMap底层是哈希表
TreeMap底层是二叉树

3、Iterator迭代器

集合结构只要发生改变,迭代器必须重新获取
所以在迭代的过程中不能直接使用集合对象进行删除(改变了结构),而应该使用迭代器提供的remove()方法,该方法删除的是迭代器指向的当前元素(迭代器迭代时是使用快照,如果直接在对象上调用remove方法删除,没有通知迭代器,那么迭代器还是使用的原集合,就会报异常)

循环的三种方式:

List<String> arg = new ArrayList<String>();
arg.add("hello");
arg.add("Tong");
arg.add("Panda");
        
迭代器:
Iterator<String> it = arg.iterator();
while(it.hasNext()){
    System.out.println(it.next());
}
        
下标方式:
for(int i = 0; i < arg.size(); i++){
    System.out.println(arg.get(i));
}
        
增强for循环:
for(String str : arg){
    System.out.println(str);
}
4、ArrayList
  • ArrayList集合初始化容量是10
  • ArrayList集合底层是Object类型的数组Object[]
  • 扩容1.5倍
  • 建议给定一个预估初始容量,减少数组的扩容操作
5、泛型 ArrayList<obj>
  • 泛型这种语法机制只在程序编译阶段起作用,只是给编译器参考的(运行阶段泛型没用)
  • 使用了泛型之后集合中储存的元素类型统一了
  • 自定义泛型:
class Test<E> {
  public void add(E a){
    ...
  }
}
6、增强for循环:foreach(没有下标)
int[] arr = {1,2,3,4,5};
for (int item : arr) {
    System.out.println(item);
}
7、Map键值对
  • 注意点:
    ①Map与Collection没有继承关系
    ②Map集合以key、value键值对的形式存储数据
    ③key、value都是引用数据类型
  • 常用方法:
    containsKey、
    containsValue、
    get("key")、
    put("id", 123)、
    remove("key")、
    Set<K> keySet() 获取Map集合所有的key,返回一个Set
    Collection<V> values() 获取Map集合所有的value,返回一个Collection
    Set<Map.Entry<K, V>> b = a.entrySet(); 将Map集合转换成Set集合:变为id=123、age=19
  • Map集合的遍历:
    ①先用keySet()获取到所有的key,然后通过key遍历
Map<String, Integer> map = new HashMap<String, Integer>();
Set<String> keys = map.keySet();
for(String k : keys){
    System.out.println(k + ":" + map.get(k));
}

②【效率高】先用entrySet()将Map转成Set,再遍历Set获取到单个的Map.Entry<K, V>对象,调用对象的getKey()、getValue()方法获取key、value值,例:

Set<Map.Entry<String, Integer>> set = map.entrySet();
for(Map.Entry<String, Integer> item : set){
    System.out.println(item.getKey()+ '=' + item.getValue());
}
  • Properties
    Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型;
    Properties被称为属性类对象;
    Properties是线程安全的;
Properties pro = new Properties();
pro.setProperty("url", "jdbc:mysql://localhost:3306");
pro.setProperty("username", "root");
pro.setProperty("password", "123456");
System.out.println(pro.getProperty("url"));

搭配FileReader 使用
//读取xml配置文件
FileReader reader = new FileReader("xml");
Properties pro = new Properties(); //key value都是String
//将xml文件键值对加载到pro集合中
pro.load(reader);
//关闭流
reader.close();
//通过key获取value
System.out.println(pro.getProperty("ClassName"));
8、TreeSet

TreeSet集合中的元素是可排序的
视频链接:https://www.bilibili.com/video/BV1Rx411876f?p=709&vd_source=451d825d8e1ffcbfd124804db3810f11

九、IO流

1、IO流的分类:

输出流
输入流
字节流:可以读任意类型的文件
字符流:只能读纯文本文件

2、流的四大家族:

java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
【注意】:

  • 【类名以Stream结尾的都是字节流,以“Reader/Writer”结尾的都是字符流】
  • 所有的输出流都实现了java.io.Flushable接口,都是可刷新的,都有flush()方法,输出流在输出了之后一定要flush()刷新一下,将通道/管道中剩余未输出的数据强行输出(清空管道);
    如果没有flush()可能会导致丢失数据。
3、常用流
  • 文件流:
    FileInputStream【重点】
    FileOutputStream【重点】
    FileReader
    FileWriter
  • 转换流:(将字节流转换成字符流)
    InputStreamReader
    OutputStreamWriter
  • 缓冲流:
    BufferedReader
    BufferedWriter
    BufferedInputStream
    BufferedOutputStream
  • 数据流:
    DataInputStream
    DataOutputStream
  • 标准输出流:
    PrintWriter
    PrintStream【重点】
  • 对象流:
    ObjectInputStream【重点】
    ObjectOutputStream【重点】
4、FileInputStream的常用方法:

①read():【不传参默认读一个字节并返回该字节;传入byte数组则将字节存入byte数组中并返回成功读取的字节数】

    public static void main(String[] args){
        FileInputStream fs = null;
        try {
            fs = new FileInputStream("C:\\Users\\Tong\\Desktop\\新建 文本文档.txt");
            byte[] bytes = new byte[4];
            int readCount = 0; //初始化读到的字节数
            while((readCount = fs.read(bytes)) != -1){
                System.out.print(new String(bytes, 0, readCount));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            if(fs != null){
                try {
                    fs.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

②available():【返回流当中剩余没有读到的字节数量】

fs = new FileInputStream("C:\\Users\\Tong\\Desktop\\新建 文本文档.txt");
byte[] bytes = new byte[fs.available()];  //不适合大文件
fs.read(bytes);
System.out.print(new String(bytes));

③skip():【跳过几个字节不读】

5、FileOutputStream的常用方法

①构造方法:

fs = new FileOutputStream("文本文档");
fs = new FileOutputStream("文本文档", true); 带上true表示以追加的方式在文件末尾写入,不会覆盖原文件内容

②write():

fs = new FileOutputStream("文本文档", true);
String str = "你好啊";
byte[] bytes = str.getBytes(); 将字符串转换成byte数组
fs.write(bytes);
fs.flush();

③输入输出配合拷贝文件:

//创建一个输入流对象
fis = new FileInputStream("C:\\Users\\Tong\\Desktop\\新建 文本文档 (2).txt");
//创建一个输出流对象
fos = new FileOutputStream("文本文档", true);
            
byte[] bytes = new byte[1024*1024]; //一次最多拷贝1MB
int count = 0;
while((count = fis.read(bytes)) != -1){
    fos.write(bytes, 0, count);
}
fos.flush();
6、FileReader

①文件字符输入流,只能读取普通文本
②read方法传入的是char数组
③搭配Properties使用

//读取xml配置文件
FileReader reader = new FileReader("xml");
Properties pro = new Properties(); //key value都是String
//将xml文件键值对加载到pro集合中
pro.load(reader);
//关闭流
reader.close();
//通过key获取value
System.out.println(pro.getProperty("ClassName"));
7、BufferedReader

①缓冲流自带缓冲区,不需要借用byte或者char数组
②构造函数:必须要传入一个Reader对象

FileReader reader = new FileReader("C:\\Users\\Tong\\Desktop\\新建 文本文档 (2).txt");
BufferedReader buf = new BufferedReader(reader);

③readLine()方法:读取一行不带换行符

8、节点流和包装流

①概念:
节点流:当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流
包装流:外部负责包装的这个流叫做:包装流/处理流
上例中FileReader 就是一个节点流;BufferedReader 就是包装流(处理流)
处理结束之后只用关闭处理流(处理流中的close方法会关闭节点流)
②字节流转换成字符流:
使用InputStreamReader,构造方法传入FileInputStream字节流对象,返回一个Reader字符流对象

FileInputStream in = new FileInputStream("C:\\Users\\Tong\\Desktop\\新建 文本文档 (2).txt");
buf = new BufferedReader(new InputStreamReader(in));
9、数据流

DataOutputStream数据专属流:可以将数据连同数据类型一并写入文件
DataInputStream:DataOutputStream写的文件只能使用DataInputStream去读,并且读的顺序需要与写的顺序保持一致,才可以正常取出数据。

10、标准输出流PrintStream 【日志工具的实现】

①PrintStream对象:
PrintStream pr = System.out; 【System.out是一个PrintStream对象】
标准输出流不需要手动关闭
②修改流的输出方向:
不再打印到控制台,而是指向“log”文件

PrintStream pr = new PrintStream(new FileOutputStream("log.txt"));
System.setOut(pr);  【修改输出方向,将输出方向修改到log文件】
11、对象流

①序列化、反序列化
序列化:serialize,将java对象存储到文件中,将java对象的状态保存下来的过程;
反序列化:deserialize,将硬盘上的数据重新恢复到内存中,恢复成java对象;
②参与序列化、反序列化的对象,必须实现Serializable接口
③Serializable接口只是一个标志接口,接口里面什么代码都没有
④标志接口:里面都是空的,没有代码,作用是给类添加一个标志,java虚拟机在看到标志后会分别作出不同处理

十、File类

File f = new File("C:\Users\Tong\Desktop\新建 文本文档 (2).txt");
f.exists() 文件是否存在
f.createNewFile(); 创建文件
f.mkdir(); 创建目录
f.mkdirs(); 创建多重目录
f.getParent(); 获取文件的父路径String
f.getParentFile(); 获取文件的父亲File对象
f.getAbsolutePath(); 获取文件的绝对路径
f.getName(); 获取文件名
f.isDirectory(); 判断是否是目录
f.isFile(); 判断是否是文件
f.lastModified(); 获取最后一次修改的时间戳
f.length(); 获取文件大小(单位是字节)
f.listFiles(); 获取当前目录下所有的子文件

十一、多线程

视频链接:https://www.bilibili.com/video/BV1Rx411876f?p=796&vd_source=451d825d8e1ffcbfd124804db3810f11

1、同一进程的线程共享【堆内存】、【方法区内存】;【栈内存】独立
2、实现多线程的三种方法:

①编写一个类,直接继承java.lang.Tread,重写run方法:

public class MyThread extends Thread{
    @Override
    public void run(){
        ...
    }
}

【如何使用】:在主函数中new出线程对象——>调用线程对象的start()方法

MyThread thread = new MyThread(); //创建线程对象
thread.start(); //启动线程

【start()方法的作用】:启动一个分支线程,在JVM中开辟一个新的栈空间,只要栈空间开出来,start()方法就结束了,线程就启动成功了。启动成功的线程会自动调用run方法,并且将run方法压入栈底部。
run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的。

②(使用较多)编写一个类,实现java.lang.Runnable接口,实现run方法:

public class MyThread implements Runnable{
    @Override
    public void run(){
        ...
    }
}

【如何使用】:在主函数中new出Thread对象,构造函数中传入一个Runnable对象——>调用Thread对象的start()方法

Thread s = new Thread(new MyThread());  //创建线程对象
s.start();

③实现Callable接口:【可以获取到线程的返回值】

package com.test.javase;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class Test {
    public static void main(String[] args) throws Exception{
        //使用匿名内部类的方式实现Callable接口
        FutureTask task = new FutureTask(new Callable() {
            //call()方法相当于run()方法
            public Object call() throws Exception {
                Thread.sleep(1000);
                int a = 3;
                int b = 4;
                return a + b;
            }
            
        });
        
        //创建线程对象
        Thread t = new Thread(task);
        t.start();
        
        //获取到t线程的返回值
        System.out.println(task.get());  //会阻塞当前线程,因为只有当t线程执行完才能够拿到返回值
        
        System.out.println(Thread.currentThread().getName());
    }
}
3、线程生命周期:

start方法执行完毕之后,线程进入就绪态,开始争夺CPU时间片
JVM调度分配时间片
当run方法使用完一个时间片后,线程又进入就绪态,等待JVM调度
当run方法结束后进入死亡状态

4、线程对象的方法:
  • setName("Thread-0") 设置线程名;
  • getName() 获取线程名;
  • Thread.currentThread() 获取当前线程对象,这个方法出现在哪个线程的代码中,返回的就是哪个线程,类似于this;
  • Thread.sleep(1000L) 接收long参数,表示毫秒数;让当前线程进入休眠(阻塞态),出现在哪个线程中,哪个线程就进入休眠;
  • interrupt() 中断线程的休眠,唤醒线程(这种终断睡眠的方式依靠了java的异常处理机制),会进入sleep的catch语句块中
  • stop() 强制终止线程执行,可能会导致丢失数据,不建议使用
  • 合理结束线程:
package com.test.javase;

public class Test {
    public static void main(String[] args) throws Exception{
        MyThread t = new MyThread();
        Thread s = new Thread(t);
        s.start();
        
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
        
        t.run = false; 手动结束线程
    }
}

class MyThread implements Runnable{
    boolean run = true;
    
    @Override
    public void run(){
        for(int i = 0; i < 10; i++){
            if(run){
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }else{
                  return就结束了,在结束前可以保存未保存的值
                //终止当前线程
                return;
            }
        }
    }
}

  • setPriority() 设置线程优先级,最高10,最低1,默认5;
  • getPriority() 获取线程优先级;
  • Thread.yield() 暂停当前正在执行的线程对象,并执行其他线程,会让当前进程从运行态进入就绪态;
  • t.join() 合并线程,当前线程进入阻塞,t线程执行,直到t线程结束,当前线程才可以继续执行;
  • Thread.currentThread().getContextClassLoader().getResource("xml.txt").getPath() 获取类路径下xml.txt文件的绝对路径
5、java线程调度

java线程调度策略采用的是【优先级抢占式调度】

6、线程安全

①java中保证线程安全的关键是使用【线程同步】

②局部变量永远不会存在线程安全的问题,因为局部变量在栈中不共享,一个线程一个栈;实例变量(在堆中)和静态变量(在方法区)可能存在线程安全

③synchronized:

synchronized的用法:

synchronized (XX) {
   ...
}

假设有[t1、t2、t3、t4、t5]几个线程,此时希望t1、t2、t5同步进行某项操作,这时XX传入的就是t1、t2、t5共享的一个对象,希望同步执行的代码块写入synchronized中,执行时java会给该对象加对象锁(只支持独占式访问)

  • 同步的代码块越少,效率越高
  • synchronized出现在实例方法上作为修饰关键字时,一定锁的是this(括号中传入的对象是this),不能修改,所以这种方式不灵活。同时整个方法体都需要同步,可能会无故扩大同步范围,导致程序的执行效率降低。如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这种方式。

④synchronized的写法:

  • 同步代码块
public void test(){
    ...
    synchronized (XX) {
        需要同步的代码块...
    }
    ...
}
  • 作为修饰关键字
public synchronized void test(){
    需要同步的代码块...
}
  • 在静态方法上使用synchronized:
    表示找类锁,类锁永远只有一把,就算创建多个对象,类锁也只有一把。
    对象锁:1个对象1把锁
    类锁:类锁只有一把,用于保证静态变量的安全
public synchronized static void test(){
    需要同步的代码块...
}

⑤死锁:synchronized最好不要嵌套使用,可能会引起死锁
死锁例子:

public class Test {
    public static void main(String[] args) throws Exception{
        Object o1 = new Object();
        Object o2 = new Object();
        
        MyThread1 t1 = new MyThread1(o1, o2);
        MyThread2 t2 = new MyThread2(o1, o2);
        
        t1.start();
        t2.start();
    }
}

class MyThread1 extends Thread{
    Object o1;
    Object o2;
    public MyThread1(Object o1, Object o2) {
        super();
        this.o1 = o1;
        this.o2 = o2;
    }
    
    public void run(){
        synchronized (o1) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            synchronized (o2) {
                
            }
        }
    }
}

class MyThread2 extends Thread{
    Object o1;
    Object o2;
    public MyThread2(Object o1, Object o2) {
        super();
        this.o1 = o1;
        this.o2 = o2;
    }
    
    public void run(){
        synchronized (o2) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            synchronized (o1) {
                
            }
        }
    }
}
7、守护线程

①概念:

  • java中线程分为两大类:用户线程、守护线程(后台线程)
  • 垃圾回收线程就是一个守护线程,main方法是一个用户线程;
  • 特点:守护线程一般是一个死循环,所有的用户线程结束,守护线程会自动结束
  • t.setDaemon(true):将一个用户线程设置为守护线程

②定时器:
实现方式:

  • 可以使用sleep方法,设置睡眠时间,每到这个时间点醒来,执行任务。这种方法是最原始的定时器;
  • 在java类库中已经写好了一个定时器,java.util.Timer,可以直接使用;
  • 实际开发中使用较多的是Spring框架中提供的SpringTask框架,这个框架只需要简单的配置就可以完成定时器的任务。
8、wait、notify方法
  • wait、notify方法不是线程对象的方法,是java对象都有的方法;
  • wait、notify方法都建立在线程同步的基础上,因为多线程要同时操作一个仓库,存在线程安全问题;
  • o.wait()方法让正在o对象上活动的线程t进入等待状态,并且释放t线程之前占有的o对象的锁;
  • 生产者、消费者模式:
package com.test.javase;

import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) throws Exception{
        List list = new ArrayList<Object>();
        
        Thread t1 = new Thread(new Producer(list));
        Thread t2 = new Thread(new Customer(list));
        
        t1.start();
        t2.start();
    }
}

class Producer implements Runnable{
    List list;

    public Producer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        while(true){
            synchronized (list) {
                if(list.size() > 0){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                list.add(new Object());
                System.out.println("生产者生产了:"+list.get(0));
                list.notify();
            }
        }
    }
}

class Customer implements Runnable{
    List list;

    public Customer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        while(true){
            synchronized (list) {
                if(list.size() == 0){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Object obj = list.remove(0);
                System.out.println("消费者消费了:"+obj);
                list.notify();
            }
        }
    }
}

十二、反射机制

视频链接:https://www.bilibili.com/video/BV1Rx411876f?p=810

1、基础概念
  • 通过反射机制可以操作字节码文件
  • 通过使用反射机制可以直接用类名创建对象,使程序变得更加灵活
  • 反射机制的相关类在 java.lang.reflect.* 包下
  • 反射机制相关的重要类:
    java.lang.Class; 代表整个字节码,整个类
    java.lang.reflect.Method; 代表字节码中的方法字节码。代表类中的方法
    java.lang.reflect.Constructor; 代表字节码中的构造方法字节码。代表类中的构造函数
    java.lang.reflect.Field; 代表字节码中的属性字节码。代表类中的成员变量
  • 如果只希望一个类的静态代码块执行,其他代码一律不执行,可以使用:
    Class.forName("完整类名");
    这个方法的执行会导致类加载,类加载时,静态代码块执行。
2、获取Class的三种方式
  • Class.forName("完整类名");
    Class c = Class.forName("java.lang.String");
  • 对象.getClass();
    String s = "abc";
    Class c = s.getClass();
  • 类.class;
    Class c = String.class;
3、通过读属性文件实例化对象
//获取绝对路径的方式
//String path = Thread.currentThread().getContextClassLoader().getResource("xml").getPath()
//FileReader reader = new FileReader(path);
        
//直接以流的形式返回
InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("xml");
Properties pro = new Properties(); //key value都是String
//将xml文件键值对加载到pro集合中
pro.load(reader);
//关闭流
reader.close();
//通过key获取value
Class a = Class.forName(pro.getProperty("ClassName"));
//实例化对象     
Object obj = a.newInstance();
4、资源绑定器
  • java.util包下提供了一个资源绑定器,便于获取【属性配置文件】中的内容;
  • 资源绑定器只能绑定xxx.properties文件,且该文件必须在类路径下(bin或者src文件夹中);
  • 在写路径的时候,文件名不需要带扩展名;
  • 例如:
ResourceBundle bundle = ResourceBundle.getBundle("xml");
String className = bundle.getString("ClassName");
System.out.println(className);
5、反射属性Field
  • ①获取类的属性Field:
Class studentClass = Student.class;
//只能获取public修饰的field
Field[] fields = studentClass.getFields();
for(Field f : fields){
    System.out.println(f.getName());
}
        
//获取所有的field
Field[] allFields = studentClass.getDeclaredFields();
for(Field f : allFields){
    System.out.println("属性名:"+f.getName());
    System.out.println("类型:"+f.getType().getSimpleName());
    System.out.println("修饰符:"+Modifier.toString(f.getModifiers()));
    System.out.println("---------");
}
  • ②反编译:
    可以通过class文件反编译出它含有的属性、方法等
  • ③通过反射机制访问对象属性(赋值、取值):
Class studentClass = Student.class;
Object obj = studentClass.newInstance();
Field id = studentClass.getDeclaredField("id");
id.set(obj, "123k"); //给obj对象的id属性赋值
System.out.println(id.get(obj)); //取出obj对象的id属性值
  • ④访问private私有变量:age.setAccessible(true);打破封装
Field age = studentClass.getDeclaredField("age");
age.setAccessible(true);
age.set(obj, 12);
System.out.println(age.get(obj));
6、反射Method

①反编译Method:

public static void main(String[] args) throws Exception{
    Class studentClass = Student.class;
        
    Method[] methods = studentClass.getDeclaredMethods();
        
    for(Method m : methods){
        System.out.println("修饰符:" + Modifier.toString(m.getModifiers()));
            
        System.out.println("返回类型:" + m.getReturnType().getSimpleName());
            
        System.out.println("方法名:" + m.getName());
            
        StringBuffer s = new StringBuffer();

        Class[] parameterTypes = m.getParameterTypes();
        for(Class parameterType : parameterTypes){
            s.append(parameterType.getSimpleName() + " ");
        }
        System.out.println("参数类型列表:" + s);
        
        System.out.println();
    }
}

②反射机制调用方法:【*******五颗星重点*******】
视频链接:https://www.bilibili.com/video/BV1Rx411876f?p=830&vd_source=451d825d8e1ffcbfd124804db3810f11

原类中的方法:

public int test2(int i, Boolean b){
    System.out.println("测试方法2");
    if(b) return 1;
    return 0;
}

反射机制调用test2方法:

//获得class
Class studentClass = Student.class;

//实例化对象
Object obj = studentClass.newInstance(); 

//反射Method:传入方法名、方法参数类型,用以区分【方法重载】
Method method = studentClass.getDeclaredMethod("test2", int.class, Boolean.class);

反射机制调用方法
/*四要素:
method方法、
obj对象、
1, true 实参
res返回值
*/
Object res = method.invoke(obj, 1, true);
        
System.out.println(res);
7、反射Constructor

反射机制调用构造方法:
先获取到有参数的构造方法,调用构造方法new对象

public static void main(String[] args) throws Exception{
    Class studentClass = Student.class;
        
    //无参数构造函数
    Object obj = studentClass.newInstance();
    
    //获取到带参数构造方法
    Constructor con = studentClass.getDeclaredConstructor(String.class, String.class, int.class);
    //使用该构造方法new对象
    Object newObj = con.newInstance("1120abc", "laowang", 23);
    System.out.println(newObj.toString());
}
8、获取父类和父接口
public static void main(String[] args) throws Exception{
    Class studentClass = Student.class;
        
    //获取继承的父接口
    Class[] interfaces = studentClass.getInterfaces();
    for(Class i : interfaces){
        System.out.println(i.getSimpleName());
    }
        
    //获取父类
    Class superClass = studentClass.getSuperclass();
    
    System.out.println(superClass.getSimpleName());
}

十三、注解

①基础概念:
注解Annotation是一种引用数据类型,编译之后也是生成xxx.class文件;

②自定义注解:
[修饰符列表] @interface 注解类型名{
}
例如:

public @interface MyAnnotation {

}

③使用注解:
@注解名
注解可以出现在类上、属性上、方法上、变量上、注解上......

④JDK内置的注解:

  • @Deprecated:表示标注的这个类、方法等已过时
  • @Override:只能注解方法。是给编译器参考的,跟运行阶段没关系;凡是java方法中带有这个注释的,编译器都会进行编译检查,如果这个方法不是重写父类的方法,编译器会报错。

⑤元注解:
修饰注解的注解称为元注解,例如:

  • Target:用来标注“被标注的注解”可以出现在哪些位置上;
    @Target(ElementType.METHOD):表示该注解只能出现在方法上;
  • Retention:用来标注“被标注的注解”最终保存在哪里;
    @Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中;
    @Retention(RetentionPolicy.CLASS):表示该注解被保存在calss文件中;
    @Retention(RetentionPolicy.RUNTIME):表示该注解被保存在calss文件中,并且可以被反射机制读取。

⑥注解中定义属性:

  • 语法:
public @interface MyAnnotation {
    //name属性
    String name();
    int age() default 25; //指定默认值
}
  • 如果一个注解当中有属性,那么使用该注解时一定要给属性赋值,除非有默认值:
    @MyAnnotation(属性名=属性值)

  • 如果属性名是“value”并且只有一个属性,使用注解时,属性名可以省略:
    定义:String value();
    使用:@MyAnnotation("hello")

  • 注解中属性允许的类型:
    byte short int long float double boolean char String Class 枚举
    以及以上每一种的数组形式

⑦反射注解:

  • 被反射的注解必须要被保存在class文件中,要能被反射机制读取,即:
    @Retention(RetentionPolicy.RUNTIME)

  • 反射机制获取到注解对象的属性:

Class stuClass = Student.class;
if(stuClass.isAnnotationPresent(MyAnnotation.class)){ //判断是否存在该注解
    MyAnnotation myAnnotation = (MyAnnotation)stuClass.getAnnotation(MyAnnotation.class);
    System.out.println(myAnnotation.age());
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容