【动力节点】javaSE-advance

1. IDEA常用快捷键

  • 快速生成main方法:psvm
  • 快速生成System.out.println():sout
  • 删除一行:ctrl+y
  • 任何新增/新建/添加的快捷键:alt+insert
  • Java程序切换:alt+右箭头/左箭头
  • 窗口切换:alt+数字标号(打开/关闭)
  • 提示方法参数:ctrl+p
  • 注释
    • 单行注释:选中+ctrl+/
    • 多行注释:选中+ctrl+shift+/
  • 定位方法/属性/变量:ctrl+光标点击
  • 纠正错误:光标点中+alt+回车

2. 面向对象

2.1. final关键字

2.1.1. 修饰类

final修饰的类无法被继承

final class A{} //A类不能被其他类继承
2.1.2. 修饰方法

final修饰的方法无法被覆盖和重写

class A{
	public final a(){} //方法a不能被覆盖和重写
}
2.1.3. 修饰变量

final修饰的变量只能赋一次值

final int k;
k = 100;
k = 200; //×,只能赋一次值
  • 修饰引用(引用也属于变量)

    final修饰的引用只能指向一个对象,不能更改,并且也不会被GC回收,直到方法执行结束

    但是该引用指向的对象内的数据可以被修改

    class Person{
        int age;
    	public Person(){}
    	public Person(int age){
            this.age = age;
        }
    }
    final Person p = nwe Person(30);
    p = new Person(30); //×,引用p只能指向一个对象
    p = null //×,也无法将对象p指向空。只有在当前方法执行结束后才会被GC回收
    p.age = 40; //√
    

    002-final修饰的引用

  • 修饰实例变量

    • final修饰的实例变量,系统不会赋默认值,必须手动赋值。

    • 实例变量在什么时候赋默认值(初始化)?

      ​ 在构造方法执行时赋默认值(new的时候)

      final修饰的实例变量要赶在系统赋默认值之前手动赋值。

      class User{
      	// 第一种赋值方法
      	fianl double height = 1.8;
      	
      	// 第二种赋值方法
      	fianl double weight;
      	public User(){
      		this.weight = 80;
      	}
      }
      
  • 常量

    • final修饰的实例变量一般加static修饰。

    • static和final联合修饰的变量称为“常量”,常量名全部大写,每个单词之间下划线衔接。

      public static final String COUNTRY = "China"
      
    • 常量和静态变量

      都是存储在方法区,并且都是在类加载时初始化;

      区别是常量的值不能变

    • 常量一般都是公开的。

2.2. 抽象类和接口

2.2.1. 抽象类
  • 概念

    类和类之间的共同特征提取出来形成的就是抽象类。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZRcHpKRN-1644842724107)(…/001-JavaSE课堂笔记+思维导图/09-JavaSE进阶每章课堂画图/01-面向对象/001-抽象类的理解.png)]

  • 语法

    [修饰符列表] abstract class 类名{
    	类体;
    }
    
  • 性质

    • 抽象类属于引用数据类型。

    • 抽象类是无法实例化、无法创建对象的,是用来被子类继承的。

    • final和abstract是对立的,不能联合使用。

    • 抽象类的子类可以是抽象类。

    • 抽象类可以有构造方法,用于供子类使用。

  • 抽象方法:没有实现的方法。

    • 语法:没有方法体,以分号结尾;用abstract修饰

      public abstract void dosome();
      
    • 抽象类中可以有或没有抽象方法、非抽象方法。

      但抽象方法必须出现在抽象类中。

    • 非抽象类继承抽象类,必须将抽象方法实现(覆盖/重写)

  • Java语言中没有方法体的方法都是抽象方法?

    错,Object类中很多方法都没有方法体,例如:

    // 该方法调用了c++写的动态链接库程序
    // 修饰列表中没有abstract,有一个native,表示调用JVM本地程序。
    public native int hashcode();
    
2.2.2. 接口
  • 语法

    [修饰符列表] interface 接口名 {
        常量或抽象方法; // 接口内只能有这两部分内容
        //接口中抽象方法定义时public abstract可以省略;
        //接口中常量定义时Public static final可以省路
    }
    
  • 性质

    • 接口属于引用数据类型

    • 接口是完全抽象的(抽象类是半抽象),特殊的抽象类

    • 接口中所有元素都是public修饰

    • 类和类之间叫做继承,类和接口之间叫做实现,用implements关键字完成

    • 接口支持多继承,一个接口可以继承多个接口;一个类也可以实现多个接口

      interface A{}
      interface B{}
      interface C extends A,B{}
      class D extends A,B{}
      
      • 类与接口间的多态与类间的多态行为一致,参考多态的向下转型

      • 接口之间没有继承关系也可以进行强转(向下转型必须出现在类相互继承的情况),但运行时很可能会出现classCastException异常。

        (不用过多细想,记住每次强转时使用instanceof判断就行)

  • 在开发中的作用(类似多态在开发中的作用):解耦合

    面向接口编程,可以降低程序的耦合度,提高程序的扩展力,符合OCP原则。

    接口的使用离不开多态。

2.2.3. 抽象类和接口
  • 子类实现接口(继承抽象类)时,必须将其所有抽象方法都实现。

  • 继承和实现都存在时,extends关键字在前,implements关键字在后。

    class 子类名 extends 父类名 implements 接口名{}
    
  • 区别

    • 抽象类时半抽象的,接口时完全抽象的
    • 抽象类中有构造方法,接口中没有构造方法
    • 接口和接口之间支持多继承,类和类之间只能单继承
    • 一个类可以同时实现多个接口,只能继承一个抽象类(单继承)
    • 接口中值允许出现常量和抽象方法

2.3. package和import

2.3.1. package(包)
  • 作用

    • 1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
    • 2、如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
    • 3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

    Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。

  • 语法

    package 包名;
    

    package语句只能出现在Java源代码的第一行

  • 编译

    javac - d . xxx.java
    

    运行

    java 完整类名(带包名)
    
  • 包名的命名规范

    公司域名倒序+项目名+模块名+功能名

2.3.2. import
  • 什么时候使用

    A类中使用B类,若A类和B类不在同一包下,必须使用import

    【**Java.lang.***下的类不需要import导入】

  • 怎么使用

    import语句只能出现在package语句之下,class声明语句之上

2.4. 访问控制权限

  • private私有:可以修饰属性、方法

​ 只能在本类中访问

  • protected受保护:可以修饰属性、方法

​ 只能在本类、同包、子类中访问

  • public公开:可以修饰属性、方法、类、接口

​ 在任何位置都能访问

  • 默认:可以修饰属性、方法、类、接口

​ 只能在本类,以及同包下访问

访问控制修饰符本类同包子类任意位置
public
protected×
默认××
private×××

public > protected > 默认 > private

2.5. Object类

2.5.1. 常用方法
  • 什么是API
    应用程序编程接口。(Application Program Interface)
    整个JDK的类库就是一个javase的API。
    每一个API都会配置一套API帮助文档。
    SUN公司提前写好的这套类库就是API。(一般每一份API都对应一份API帮助文档。)

  • 常用方法

    protected Object clone() // 负责对象克隆的。
    int hashCode() // 获取对象哈希值的一个方法。
    boolean equals(Object obj) // 判断两个对象是否相等
    String toString() // 将对象转换成字符串形式
    protected void finalize() // 垃圾回收器负责调用的方法【JDK 9后过时】

2.5.2. String toString()方法
  • 作用:返回对象的字符串表示形式。
  • System.out.println(引用); 这里会自动调用“引用”的toString()方法。
  • 以后所有类的toString()方法是需要重写的。
    重写规则,越简单越明了就好。
2.5.3. boolean equals(Object obj)方法
  • 作用:判断两个对象是否相等

  • Object中的equals方法比较的是两个对象的内存地址(与“==”作用一样),我们应该比较内容,所以需要重写。

    重写规则:自己定,主要看是什么和什么相等时表示两个对象相等。

  • 基本数据类型判断是否相等用“==”;引用数据类型判断是否相等用equals()方法

  • 判断两个字符串是否相等,不能使用==,要调用字符串对象的equals方法。

    String类已经重写了equals()方法

2.5.4. protected void finalize()方法【JDK9后过时】
  • 作用: 垃圾回收器(GC)负责调用的方法,不需要手动调用

  • 源代码

    protected void finalize() throws Throwable { }
    
  • 执行时机:当一个Java对象即将被垃圾回收器回收的时候。

2.5.5. int hashCode()方法
  • 返回的哈希码是一个对象的内存地址经过哈希算法转换的值

  • 源代码

    public native int hashCode();
    

    不是抽象方法,带有native关键字,底层调用c++程序

2.6. 内部类

内部类的分类

  • 静态内部类:类似与静态变量
  • 实例内部类:类似于实例变量
  • 局部内部类:类似于局部变量
    • 匿名内部类属于局部内部类的一种
class A{
    //静态内部类
    static class B{}
    
    //实力内部类
    class C{}
    
    public void m{
        int i;
        
        //局部内部类
        class D{}
    }
    
}
2.6.1. 匿名内部类
interface Compute{
    int sum(int a, int b);
}

class MyMath{
    public void mySum(Compute c, int x, int y){
        int retValue = c.sum(x,y);
        System.out.prinln(x + "+" + y + "=" + retValue);
    }
}

public class test{
    public static void main(String[] args){
        MyMath mm = new MyMath();
        
        //mm.myMath(匿名内部类,200,300)
        //new Coumpute(){}中的{}表示对接口的实现
        mm.mySum(new Compute(){
            public int sum(int a, int b){
                return a+b;
            }
        }, 200, 300)  // 200+300=500
    }
}

匿名内部类没有名字,因此不能重复使用,且可读性差

3. 数组

  • 基本语法
    • Java语言中的数组属于引用数据类型,不属于基本数据类型
    • 数组长度不可变 ,所有数组对象都有length属性,用来获取数组中元素个数
    • 数组中可以存储基本数据类型,也可以存储引用数据类型,但数组中元素类型要统一
  • 内存结构
    • 数组不能直接存储Java对象,实际上存储的是对象的引用
    • 数组对象存储在堆中
    • 数组中元素内存地址是连续的数组中首元素的内存地址就是整个数组的内存地址
    • 数组中每个元素占用的空间大小相同

001-数组内存图002-数组的内存结构003-数组的内存结构

  • 优点:检索效率高。

    知道首元素的内存地址,通过一个数学表达式,就可以快速计算出某个下标位置上元素的内存地址,直接通过内存地址定位,效率非常高。

  • 缺点:

    • 随机增删效率较低,数组无法存储大数据量。

      数组最后一个元素的增删效率很高,不受影响

    • 不能存储大数据量,因为在内存空间中很难找到一块特别大的连续的存储空间

3.1. 一维数组

3.1.1. 初始化
  • 静态初始化:
int[] arr = {1,2,3,4};
Object[] objs = {new Object(), new Object(), new Object()};
  • 动态初始化:
int[] arr = new int[4]; // 4个长度,每个元素默认值0
Object[] objs = new Object[4]; // 4个长度,每个元素默认值null
3.1.2. 数组扩容
  • 原理:新建一个大容量数组,再将小容量数组中元素拷贝进去。

  • 涉及到数组拷贝,因此效率较低

3.1.3. 数组拷贝
  • 源代码

    // System.arraycopy(源数组,源数组下标,目标数组,目标数组下标,拷贝长度)
    System.arraycopy(Object src, int srcPos, Object dest, int destPos, int lenght)
    
  • 内存图

    004-数组拷贝内存图

3.2. 二维数组

二维数组是一个特殊的一位数组,其每个元素是一个一维数组

3.2.1. 初始化
  • 静态初始化

    int[][] arr = {
        {1,2,34},
        {54,4,34,3},
        {2,34,4,5}
    };
    
    Object[][] arr = {
        {new Object(),new Object()},
        {new Object(),new Object()},
        {new Object(),new Object(),new Object()}
    };
    
  • 动态初始化

    int[][] arr = new int[3][4];
    Object[][] arr = new Object[4][4];
    Animal[][] arr = new Animal[3][4];
    // Person类型数组,里面可以存储Person类型对象,以及Person类型的子类型都可以。
    Person[][] arr = new Person[2][2];
    
    
    3.3. 数组工具类

java.util.Arrays 是一个工具类。

4. 常用类

4.1. String类

4.1.1. 存储原理
  • 在Java中随便使用双引号(“”)括起来的都是String对象,并且字符串一旦创建是不可变的。
  • 在JDK中,由于字符串使用频繁,为了执行效率,字符串都直接存储在方法区的常量池中。GC不会释放常量。
  • 字符串的比较必须使用equals方法。
  • String已经重写了toString()和equals()方法。

举例1:

// 前两行代码中,底层实际创建了三个String对象,
// 分别是直接存储在方法区的常量池中的“abcdef”、“xy”和”abcdefxy“
String s1 = "abcdef";
String s2 = "abcdef" + "xy";

String s3 = new String("xy");

内存图: 001-String的内存图

举例2:

public class User{
    private int id;
    private String name;
    public User(int id, String name){
        this.id = id;
        this.name = name;
    }
    ······
}
User user = new User(110, "张三");

内存图:

002-String类型的引用中同样也是保存了对象的内存地址

举例3:

// "hello"是存储在方法去的字符串常量池中
// 所以这个"hello"不会新建,因为这个对象已经存在了
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); //true,内存地址相同

String x = new String("xyz");
String y = new String("xyz");
System.out.println(x == y)//false,内存地址不同
System.out.println(x.equals(y)); // true,String类中已经重写了equals方法

内存图:

003-String相关面试题

举例4:

// 一共创建了3对象
// 方法区字符串常量池中有1个:"hello"
// 堆内存中有2个String对象
String s1 = new String("hello");
String s2 = new String("hello");
4.1.2. 构造方法

String s = “abc”;
String s = new String(“abc”);
String s = new String(byte数组);
String s = new String(byte数组, 起始下标, 长度);
String s = new String(char数组);
String s = new String(char数组, 起始下标, 长度);

4.1.3. 常用方法
  • // 返回当前字符串长度
    int length();
    
  • // 判断当前字符串是否为空字符串,null会空指针异常
    boolean isEmpty();
    
  • //返回指定索引处char值
    char charArt(int index);
    
  • //从第一个索引的值开始比较,按字典顺序,前后相等为0,前小后大为-1.前大后小为1
    int compareTo(String anotherString);
    
  • //判断d字符串是否包含传入的字符串
    boolean contains(CharSequence s);
    
  • // 判断当前字符串是否以传入的字符串结尾
    boolean endsWith(String suffix);
    
    // 判断当前字符串是否以传入的字符串开始
    boolean startsWith(String suffix);
    
  • // 判断两个字符串是否相等,并忽略大小写
    boolean equalsIgnoreCase(String anotherString)
    
  • // 将当前字符串转换为byte数组
    byte[] getByte();
    
    // 将当前字符串转换为Char数组
    char[] toCharArray()
    
  • // 返回传入字符串在当前字符串中第一次出现处的索引
    int indexOf(String str);
    
    // 返回传入字符串在当前字符串中最后一次出现处的索引
    int lastIndexOf(String str);
    
  • // 用新的字符/文本替换字符串中所有匹配的旧字符/文本,并返回替换后的新字符串。
    String repalce(char oldChar, char newChar);
    String repalce(CharSequence oldText, CharSequence newText);
    
  • // 将当前字符串以regex为分隔符进行拆分,返回拆分后的字符串数组
    String[] split(String regex);
    
  • // 将当前字符串以beginIndex为起始下标截取
    String substring(int beginIndex);
    
    // 将当前字符串以beginIndex为起始下标,以endIndex-1为结束下标截取
    String substring(int beginIndex,int endIndex);
    
  • // 将当前字符串中所有字符转换为小写
    String toLowerCase();
    
    // 将当前字符串中所有字符转换为大写
    String toUperCase();
    
  • // 去除字符串中前后空白(中间空白不能去除)
    String trim();
    
  • // Sting中唯一的静态方法
    // 将c非字符串转换为字符串
    static String valueOf(Object o)
    

4.2. StringBuffer/StringBuilder

Java中每一次字符串拼接都会产生新的字符串,造成空间浪费

StringBuffer/StringBuilder类创建字符串缓冲区,用于大量进行字符串拼接的操作。

4.2.1. 基本语法
// 指定初始化容量的StringBuffer对象(字符串缓冲区对象)
StringBuffer sb = new StringBuffer(capacity:100);
sb.append(100);
sb.append(true);
sb.append("hello");
System.out.println(sb); //100truehello
4.2.2. StringBuffer和StringBuilder的区别

StringBuffer中的方法都有synchronized关键字修饰。表示stringBuffer在多线程环境下运行是安全的。

StringBuilder没有该关键字在多线程环境下运行是不安全

4.3. 包装类

Java为8种基本类型对应准备了8种包装类,这8种包装类属于引用数据类型。

八种包装类的类名是什么?
Byte
Short
Integer
Long
Float
Double
Boolean
Character

4.3.1.装箱和拆箱
  • 自动装箱和自动拆箱
//Java种将[-128,127]间所有包装对象提前创建好,放到一个方法区的“整数型常量池”中
// 目的是使用这个区间的数据不需要new了,直接从整数型常量池中取出来
Integer x = 100; // x里面并不是保存100,保存的是100这个对象的内存地址。
Integer y = 100;
System.out.println(x == y); // true,x中保存的内存地址与y中一样

Integer x = 128;
Integer y = 128;
System.out.println(x == y); // false
4.3.2. Integer类
  • 内存结构

    004-Integer的内存结构

  • 常用方法

    // 将字符串转换为int类型
    static int parseInt(String s);
    
    // 将十进制转换为二进制字符串
    static String toBinaryString(int i);
    
    // 将十进制转换为十六进制字符串
    static String toHexString(int i);    
    
    // 将十进制转换为八进制字符串
    static String toOctalString(int i); 
    
    // 将int类型转换为Integer
    static Integer valueOf(int i);
    
    // 将String类型转换为Integer
    static Integer valueOf(String s);
    
  • String int Integer类型互换

    005-String Integer int三种类型的互相转换

4.4. 日期类

java.util.Date类

4.1.1. 常用方法
  • 构造方法

    import java.util.Date;
    
    public class Demo02Date {
        public static void main(String[] args) {
            demo03();
        }
    
        /*
            long getTime() 把日期转换为毫秒值(相当于System.currentTimeMillis()方法)
              返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
         */
        private static void demo03() {
            Date date = new Date();
            long time = date.getTime();
            System.out.println(time);//3742777636267
        }
    
        /*
            Date类的带参数构造方法
            Date(long date) :传递毫秒值,把毫秒值转换为Date日期
         */
        private static void demo02() {
            Date date = new Date(0L);
            System.out.println(date);// Thu Jan 01 08:00:00 CST 1970
    
            date = new Date(3742767540068L);
            System.out.println(date);// Sun Aug 08 09:39:00 CST 2088
        }
    
        /*
            Date类的空参数构造方法
            Date() 获取当前系统的日期和时间
         */
        private static void demo01() {
            Date date = new Date();
            System.out.println(date);//Sun Aug 08 12:23:03 CST 2088
        }
    }
    
  • 日期格式化:Date --> String

    // 日期格式要准确
    SimpleDateFormat sdf = new SimpleDate("yyyy-MM-dd HH:mm:ss SSS");
    String s = sdf.format(new Date()); //yyyy-MM-dd HH:mm:ss SSS
    
  • String --> Date

    SimpleDateFormat sdf = new SimpleDate("yyyy-MM-dd HH:mm:ss");
    Date d = sdf.parse("2008-08-08 08:08:08");
    
  • 获取毫秒数

    // 获取去1970年1月1日到此时得总毫秒数数
    long begin = System.currentTimeMillis();
    // 获取昨天此时的时间
    Date d = new Date(begin - 1000 * 60 * 60 * 24); // //yyyy-MM-dd HH:mm:ss SSS
    

4.5. 数字类

4.5.1. 格式化

java.text.DecimalFormat类

//  表示加入千分位,保留两个小数。
DecimalFormat df = new DecimalFormat(pattern: "###,###.##");
// 表示加入千分位,保留4个小数,不够补0
DecimalFormat df = new DecimalFormat(pattern: "###,###.0000");                               
4.5.2. 高精度

BigDecimal类,常用于处理财务数据

4.6. 随机数

4.6.1. 产生一个int类型随机数
Random r = new Random();
int i = r.nextInt();
4.6.2. 产生某个范围之内的int类型随机数
Random r = new Random();
int i = r.nextInt(101); // 产生[0-100]的随机数。

4.7. 枚举

4.7.1. 基本语法
enum 枚举类型名{
	枚举值1,枚举值2,枚举值3;
}
4.7.2. 性质
  • 枚举是一种引用数据类型
  • 枚举编译之后也是class文件
  • 当一个方法执行结果超过两种情况,并且是一枚一枚可以列举出来的时候,建议返回值类型设计为枚举类型。

5. 异常处理

5.1. 异常的基本概念

  • 控制台出现的异常信息由JVM打印

  • java中异常的作用是:增强程序健壮性。

  • java中异常以类和对象的形式存在。

5.2. 异常的分类

image-20220119162017648

  • 异常主要分为:错误、编译时异常(受控异常)、运行期异常(非受控异常)
    • 错误:如果应用程序出现了Error,那么将无法恢复,只能重新启动应用程序,最典型的
      Error 的异常是:OutOfMemoryError
    • 受控异常:出现了这种异常必须显示的处理,不显示处理java 程序将无法编译通过
    • 非受控异常:此种异常可以不用显示的处理,例如被0 除异常,java 没有要求我们一定要
      处理
  • 编译时异常因为什么而得名?
    因为编译时异常必须**在编译(编写)阶段预先处理,**如果不处理编译器报错,因此得名。
    所有异常都是在运行阶段发生的。因为只有程序运行阶段才可以new对象。
    因为异常的发生就是new异常对象。
  • 编译时异常和运行时异常,都是发生在运行阶段。编译阶段异常是不会发生的。

5.3. 对异常的处理

  • 第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。
    谁调用我,我就抛给谁。抛给上一级。

    注意:Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果:终止java程序的执行。

  • 第二种方式:使用try…catch语句进行异常的捕捉。
    这件事发生了,谁也不知道,因为我给抓住了。

    • catch后面的小括号中的类型可以是具体的异常类型。也可以是该异常类型的父类型

    • catch可以写多个,多个catch必须遵从异常从小到大

    • JDK8以后catch括号中可以用或逻辑

      catch(FileNotFoundException | ArithmaticException | NullPointerException e)
      

5.4. 异常对象的常用方法

5.4.1. getMessage()方法

获取异常简单的描述信息

String msg = exception.getMessage();
5.4.2. printStackTrace()方法

打印异常追踪的堆栈信息

exception.printStackTrace();

5.5. finally子句

  • finally语句必须和try一起出现(可以没有catch),不能单独编写

  • finally子句中的代码是最后执行的,并且一定会执行,即使try语句块中代码出现了异常、即使try语句中正常且有return

    注意】如果退出JVM,finally就不会执行

  • 执行顺序

    先执行try,再执行finally,最后执行return

    public static void main(String[] args){
        try{
           System.out.println("try...");
            return;
        }
        finally{
           System.out.println("finally...");
    
        }
    }
    
  • 特例

    public static void main(String[] args){
        int result = m();
        System.out.println(result);
    }
    public static int m(){
        int i = 100;
        try{
            return i;
        }finally {
            i++;
        }
    }
    

    输出结果为100

    因为Java规则不能破坏:方法体中的代码必须遵循自上而下顺序依次逐行执行;

    return语句一旦执行,整个方法必须结束。

    上代码反编译结果:

    public static int m(){
        int i = 100;
        int j = i;
        i++;
        return j;
    }
    

5.6. 自定义异常

  • 第一步:编写一个类继承Exception或者RuntimeException

    第二步:提供两个构造方法,一个无参数,一个带有String参数

6. 集合

6.1. 集合概述

6.1.1. 什么是集合
  • 集合是一个容器,是一个载体,可以一次容纳多个对象。
  • 在实际开发中,假设连接数据库,数据库当中有10条记录,那么假设把这10条记录查询出来,在java程序中会将10条数据封装成10个java对象,然后将10个java对象放到某一个集合当中,将集合传到前端,然后遍历集合,将一个数据一个数据展现出来。
6.1.2. 集合的性质
  • 集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,
    集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用。)
list.add(100); //自动装箱Integer
  • 注意:
    集合在java中本身是一个容器,是一个对象。
    集合中任何时候存储的都是“引用”(对象的内存地址)。

001-集合中存储的是对象的内存地址

  • 在java中每一个不同的集合,底层会对应不同的数据结构。

    往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。

  • 所有的集合类和集合接口都在java.util包下。

6.1.3. 集合的继承结构
  • 在java中集合分为两大类:

    • 一类是单个方式存储元素:
      单个方式存储元素,这一类集合中超级父接口:java.util.Collection;

    • 一类是以键值对的方式存储元素
      以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map;

  • 集合的继承结构图–Collection部分

    image-20220122154615796

  • 集合的继承接口图–Map部分

    image-20220122163807370

6.2. Collection和Iterator

6.2.1. Collection中的常用方法
  • // 向集合中添加元素
    boolean add(Object e);
    
  • // 获取集合中元素个数
    int size();
    
  • // 清空集合
    void clear();
    
  • // 如果此集合包含指定的元素,则返回 true。
    boolean contains(Object o);
    

    方法原理:

    002-Collection的contains方法

  • // 删除集合中指定元素
    boolean remove(Object o);
    
  • // 判断集合是否为空
    boolean isEmpty();
    
  • // 返回包含此集合中所有元素的数组。
    Object[] toArray();
    
  • // 获取集合的迭代器对象
    Iterator iterator();
    

    当集合结构发生改变(如增删)时,迭代器必须重新获取

6.2.2. Iterator迭代器(在所有collection中通用)
  • 常用方法

    // 如果目标仍有元素可以迭代,则返回true
    boolean hasNext();
    
    // 返回迭代的下一个元素
    Object next();
    

    next()方法的返回类型必须是Object

  • 迭代原理

    003-迭代原理

  • 迭代集合的原理

    004-迭代集合的原理

6.3. List接口

6.3.1. 特有的常用方法
  • 初始化

    // 默认容量(10)
    List l1 = new ArrayList();
    
    // 初始化容量为20
    List l2 = new ArrayList(20);
    
    // 将HashSet集合转换为List集合
    Collection c = new HashSet();
    c.add(100);
    c.add(200);
    List l3 = new ArrayList(c);
    
  • // 在列表指定索引出插入元素
    // 效率比较低,使用较少
    void add(int index, Object element);
    
  • // 修改列表指定索引处元素
    Object set(int index, Object element);
    
  • // 根据列表索引获取元素
    Object get(int index);
    
  • // 获取指定对象在列表中第一次出现的索引
    int indexOf(Object o);
    
    //获取指定对象在列表中最后一次出现的索引
    int lastIndexOf(Object o);
    
  • // 删除列表中指定索引处的元素
    Object remove(int index);
    
6.3.2. ArrayList 实现类
  • ArrayList集合底层是Object类型的数组,初始化容量是10(底层先创建了一个长度为0的数组,当添加第一个元素时,初始化容量为10)
  • ArrayList元素超出容量时会自动扩容为原来的1.5倍,扩容效率较低
6.3.3. LinkedList 实现类
  • 链表

    • 单向链表

      005-链表(单向链表)

    • 双向链表

      006-双向链表

  • Linked List是双向链表结构

    LinkedList内存地址

    007-LinkedList内存图

  • 链表数据结构随即增删效率较高,检索效率较低。

    在空间内存上,内存地址不连续。

6.3.4. Vector 实现类
  • Vector底层是数组,初始化容量为10,扩容为原容量的2倍

  • Vector中所有方法都是线程同步的,都带有synchronized关键字,是线程安全的,效率比较低。

  • 将ArrayList转换为线程安全的方法:java.util.Collections集合工具类

    Collections.synchronizedList(list);
    

6.4. JDk新特性

6.4.1. 泛型(JDK5.0之后)
  • 使用泛型好处

    • 集合中存储的元素统一
    • 从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”。
  • 泛型的缺点

    • 集合中存储的元素缺乏多样性
  • 自定义泛型

    public class Genetic<E>{
        public E dosone(){}
    }
    Genetic<String> g1 = new Generic<>();
    String d1 = g1.dosome(); // 返回String类型
    
    Genetic<Integer> g2 = new Generic<>();
    Integer d2 = g2.dosome(); // 返回Integer类型
    
    Genetic g3 = new Generic();
    Object d3 = g3.dosome(); // 返回Object类型
    
6.4.2. 钻石表达式(JDK8之后)
List<String> list = new ArrayList<>();

类型自动推断!

6.4.3. foreach(JDK5.0以后)

foreach为增强for循环

  • 语法

    for(元素类型 变量名 : 数组或集合){}
    

6.5. Map 接口

6.5.1. 常用方法
  • // 向Map集合中添加键值对
    V put(K key, V value);
    
  • // 通过key获取value
    V get(Object key);
    
  • // 获取Map集合中键值对的个数
    int size();
    
  • // 清空Map集合
    void clear();
    
  • // 判断Map中是否包含某个key
    boolean containsKey(Object key);
    
    // 判断Map中是否包含某个value
    boolean containsValue(Object value);
    

    contains方法底层都是调用equals方法,所以自定义的类型要重写equals方法

  • // 获取Map集合所有的key,返回的是一个Set集合
    Set<K> keySet();
    
    // 获取Map集合中所有value,返回的是一个Collection集合
    Collection<V> values();
    
  • // 判断Mao集合是否为空
    boolean isEmpty();
    
  • // 通过key删除键值对
    V remove(Object key);
    
  • // 将Map集合转换成Set集合,返回的Set集合中每个元素为“key=value”
    // Map.Entry是一个Map类中的静态内部类Entry
    Set<Map.Entry<K,V>> entrySet();
    
6.5.2. 遍历方式
  • 第一种方法,拿到所有key值,通过遍历key值来遍历value

    Set<Integer> keys = map.keySet();
    
    // 迭代器方式
    Iterator<Integer> it = keys.iterator();
    while(it.hasNext()){
        Integer ket = it.next();
        String value = map.get(key);
    }
    
    // foreach 方式
    for(Integer key : keys){
        String value = map.get(key);
    }
    
  • 第二种方法,通过调用Set<Map.Entry<K,V>> entrySet()方法,把Map集合转换成Set集合(其元素类型为Map.Entry)

    遍历Set集合,每次取一个Node

    009-Map集合转换成Set集合entrySet()方法

    Set<Map.Entry<Integer,String>> set = map.entrySet();
    
    // 迭代器方式
    Iterator<Map.Entry<Integer,String>> it = set.iterator();
    while(it.hasNext()){
        Map.Entry<Integer,String> node = it.next();
        Integer key = node.getKey();
        String value = node.getValue();
    }
    
    // foreach方式
    for(Map.Entry<Integer,String> node : set){
        Integer key = node.getKey();
        String value = node.getValue();
    }
    
  • foreach 方式效率较高,适合大数据量

6.5.3. HashMap
  • 哈希表

    • 哈希表是一个数组和单向链表的结合体。

      哈希表是一个一维数组,每一个元素是一个单向链表。

    • 数据结构

      008-哈希表或者散列表数据结构

  • HashMap的默认初始化容量为16(自定义是必须为2的倍数),默认加载因子为0.75(容量达到75%时开始扩容),扩容后为原容量的2倍

  • 放在HashMap和HashSet的key中的元素需要同时重写HashCode()和equals()方法。

  • 在JDK8之后,为了提高检索效率

    如果HashMap的哈希表中单向链表中元素超过8时,会将单向链表数据结构变为红黑树数据结构。

    如果红黑树上的节点数量小于6,会重新把红黑树变成单向链表数据结构。

  • HashMap和Hashtable的区别

    • HashMap:
      初始化容量16,扩容2倍。
      非线程安全
      key和value可以为null。
    • Hashtable:
      初始化容量11,扩容2倍+1
      线程安全
      key和value都不能是null。
6.5.4. Properties 类
  • 性质

    • Properties继承Hashtable,其key和value值都是String类型。
    • 被称为属性类对象
    • 线程安全
  • 常用方法

    • // 调用Hashtable的方法put
      Object setProperties(String key, String value);
      
    • // 根据key值取value
      String getProperties(String key);
      
6.5.5. TreeMap集合
  • TreeSet集合底层是一个TreeMap;TreeMap底层是一个平衡二叉树

  • TreeSet集合中的元素无序不可重复,但可以按照元素的大小顺序自动排序。

  • TreeMap的key或者TreeSet集合中的元素要想排序,有两种实现方式:
    第一种:实现java.lang.Comparable接口。
    第二种:单独编写一个比较器Comparator接口,在构造TreeMap或TreeSet集合时传一个比较器对象 。

  • 自平衡二叉树

    010-自平衡二叉树

6.5.6. Collections 集合工具类
  • synchronizedList()方法,将非线程安全集合变为线程安全

    // ArrayList不是线程安全的。
    List<String> list new ArrayList<>();
    // 将其变成线程安全的
    Collecions.synchronizedList(list);
    
  • sort方法(要求集合中元素实现Comparable接口。),对集合中元素进行排序。

7. IO流

7.1. Java流概述

7.1.1. 流的分类
  • 按流的方向:输入流(读)和输出流(写)(以内存为参照物)
  • 按读取数据的方式:字节流(任何类型文件)和字符流(只能读取纯文本文件)
7.1.2. 四大家族
  • java.io.InputStream 字节输入流

​ java.io,OutputStream 字节输出流

​ java.io.Reader 字符输入流
​ java.io.Writer 字符输出流

​ 四大家族的首领都是抽象类。(abstract class)

  • 所有的流都实现了:java.io.Closeable接口,都是可关闭的,都有close()方法。

    流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费(占用)很多资源。

  • 所有的输出流都实现了:java.io.Flushable接口,都是可刷新的,都有flush()方法。
    输出流在最终输出之后,一定要记得flush()刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的作用就是清空管道
    注意:如果没有flush()可能会导致丢失数据。

  • java.io包下需要掌握的流有16个:

    文件专属:
    	java.io.FileInputStream(掌握)
    	java.io.FileOutputStream(掌握)
    	java.io.FileReader
    	java.io.FileWriter
    
    转换流:(将字节流转换成字符流)
    	java.io.InputStreamReader
    	java.io.OutputStreamWriter
    
    缓冲流专属:
    	java.io.BufferedReader
    	java.io.BufferedWriter
    	java.io.BufferedInputStream
    	java.io.BufferedOutputStream
    
    数据流专属:
    	java.io.DataInputStream
    	java.io.DataOutputStream
    
    标准输出流:
    	java.io.PrintWriter
    	java.io.PrintStream(掌握)
    
    对象专属流:
    	java.io.ObjectInputStream(掌握)
    	java.io.ObjectOutputStream(掌握)
    

7.2. 文件流

7.2.1. FileInputStream
  • 读取文件

    FileInputStream fis = null;
    try{
        fis = new FileInputStream("temptfile");
        byte[] bytes = new byte[fis.available()];
    	int readCount = 0;
    	while((readCount = fis.read(bytes)) != -1){
            System.out.println(new String(bytes, 0, readCount));
        }
    } catch(FileNotFoundException e){
        e.printStackTrace();
    } catch(IOException e){
        e.printStackTrace();
    } finally{
        if(fis != null){
            try{
                fis.close();
            } catch(IOException e){
        		e.printStackTrace();
    		} 
        }
    }
    
  • 其他方法

    // 返回流中剩余没有读取的字节数
    int available();
    
    // 跳过几个字节不读取
    long skip(long n);
    
7.2.2. FileOutputStream
  • 写入文件

    FileOutputStream fos = null;
    try{ 
        // 以追加的方式在文件末尾写入。不会清空原文件内容。
        // 不加boolean append参数会将原文件清空,再重新写入
        fos = new FileInputStream("temptfile", true);  
        String s = "abcdef";
        // 将字符串转换成byte数组
        byte[] bytes = s.getBytes();
    	fos.wrtite(bytes);
        // 将byte数组的一部分写入
        fos.wrtite(bytes, 0, 2); // ab
    	// 写完之后,最后一定要刷新
        fos.flush();
    } catch(FileNotFoundException e){
        e.printStackTrace();
    } catch(IOException e){
        e.printStackTrace();
    } finally{
        if(fos != null){
            try{
                fos.close();
            } catch(IOException e){
        		e.printStackTrace();
    		} 
        }
    }
    
7.2.3. FileReader

文件字符输入流,只能读取文件(char字符)。读取文本内容时比较方便。

读取文件方式与FileInputStream方法一样,只是读取为char数组

7.2.4. FileWriter

文件字符输出流,只能写入文件(char字符)

写入文件方式与FileOutputStream方法一样,只是写入为char数组或字符串

7.3. 缓冲流

7.3.1. BufferedReader

带有缓冲区的字符输入流,使用时不需要自定义char数组,自带缓冲

FileInputStream fis = null;
try{
    fis = new FileInputStream("temptfile");
    // 将字节流文件转换为字符流文件
    InputStreamReader reader = new InputStreamReader(fis);
	// FileReader是一个节点流,BufferedReader是包装流/处理流(负责外部包装的称为包装流)
	BufferedReader br = new BufferedReader(reader);
	String s = null;
	// 读一行
	while((s = br.readLine()) != null){
    	System.out.println(s);
	}
	// 对于包装流,只需要关闭最外层流就行,里面的节点流会自动关闭
	br.close();
} catch(FileNotFoundException e){
    e.printStackTrace();
} catch(IOException e){
    e.printStackTrace();
} finally{
    if(br != null){
        try{
            br.close();
        } catch(IOException e){
    		e.printStackTrace();
		} 
    }
}
7.3.2. BufferedWriter
FileOutputStream fos = null;
try{
	fos = new FileOutputStream("tempfile", true);
    OutStreamWriter writer = new OutStreamWriter(fos);
	BufferedWriter br = new BufferedWriter(writer);
	String s = "abcdef";
	br.writer(s);
	br.flush();
	br.close();
} catch(FileNotFoundException e){
    e.printStackTrace();
} catch(IOException e){
    e.printStackTrace();
} finally{
    if(br != null){
        try{
            br.close();
        } catch(IOException e){
    		e.printStackTrace();
		} 
    }
}

7.4. 标准输出流

7.4.1. PrintStream
  • 改变输出方向到指定文件

    // 指定一个文件
    PrintStream out = new PrintStream(new FileOutputStream("temptfile", true));
    // 改变输出方向
    System.setOut(out);
    System.out.println("此时输出不再打印到控制台,而是写入指定文件中");
    

7.3. java.io.File类

7.3.1. 特点
  • File类不能完成文件的读和写

  • File对象是文件和目录路径名的抽象表现形式

    如C:\Drivers和C:\Drivers\Readme.txt都是一个File对象

7.3.2. 常用方法
// 创建一个File对象
File f1 = new File("D:\\file");

// 判断是否存在
boolean f1.exists();

// 以文件形式创建D:\\file
f1.createNewFile();

// 以目录形式创建D:\\file
f1.mkdir();

// 构建多重目录
File f2 = new File("D:\\file\\a\\b");
f2.mkdirs();

// 获取父路径
String s1 = f2.getParent(); // D:\\file\\a

// 获取绝对路径
String s2 = f2.getAbsolutePath();

// 获取文件名
String s3 = f1.getName();

// 获取文件最后一次修改时间。结果是从1970年到现在的毫秒数
String s4 = f1。lastModified();

// 获取当前目录下所有的子目录
File[] files = f1.listFiles();

7.4. 对象流

7.4.1. 对象的序列化和反序列化

003-对象的序列化和反序列化

  • 参与序列化和反序列化的接口必须实现Serializable接口

    • Serializable接口中没有代码,起到标志作用,给Java虚拟机参考后自动生成序列化版本号

    • 如果自动生成序列化版本号,修改类的内容序列化版本号会改变,再次编译会出错

    • 建议对需要序列化和反序列化的类手动写序列化版本号

      private static final long serialVersionUID = 1L; 
      
  • 代码实列

    • 单个对象

      class Student implete Serializavle{
          
      }
      // 对象序列化
      Student s = new Student();
      ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("students"));
      oos.writeObject(s);
      oos.flush();
      oos.close();
      
      // 对象反序列化
      ObjectInputStream ois= new ObjectInputStream(new FileInputStream("students"));
      Object obj = ois.readObject();
      ois.close();
      
    • 多个对象

      class User implete Serializavle{
          
      }
      // 对象序列化
      List<User> userList = new ArrayList();
      userList.add(new User());
      userList.add(new User());
      ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users"));
      oos.writeObject(userList);
      oos.flush();
      oos.close();
      
      // 对象反序列化
      ObjectInputStream ois= new ObjectInputStream(new FileInputStream("users"));
      Object obj = ois.readObject(); 
      if(obj instanceof List)  // true;
      	List<User> userList = (List<User>)obj;
      ois.close();
      
7.4.2. transient 关键字

修饰对象的成员变量,表示该变量不参加序列化

8. 多线程

8.1. 概述

8.1.1. 进程和线程
  • 进程是一个应用程序(软件)
  • 线程是一个进程中的执行场景/执行单元
  • 一个进程可以启动多个线程
8.1.2. 线程中内存的关系
  • 一个线程一个栈,栈是独立的,堆和方法区是多线程共享的

    004-一个线程一个栈

8.2. 线程的创建和实现

8.2.1. start()和run()方法
  • start()方法:启动一个分支线程,在JVM中开辟一个新的栈空间

    启动成功的线程会自动调用run()方法,并且run()方法在分支栈的栈底部(压栈)

  • run()方法在分支栈的栈底部,main()方法在主栈的栈底部,run()和main()是平级的

  • 如果新建分支对象,没有调用start()方法,而是调用run()方法。不会启动线程,不会分配新的分支栈

8.2.2. 实现方法
  • 第一种方法

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

    public static void main(String[] args){
        // 新建一个分支线程对象
        MyThread t = new MyThread();
        // 启动线程
        t.start();
         // 这段程序运行在主线程中(主栈)
            for(int i=0; i<1000; i++){
                System.out.println(i);
    		}
    }
    
    //定义线程类
    class NyThread extends Thread{
        @Override
        public void run(){
            // 这段程序运行在分支线程中(分支栈)
            for(int i=0; i<1000; i++){
                System.out.println(i);
    		}
        }
    }
    
  • 第二种方法(更灵活)

    编写一个类,实现java.lang.Runnable接口,实现run方法

    public static void main(String[] args){
        // 创建一个可运行的对象
        MyRunnable r = new MyRunnable();
        // 将可运行的对象封装成一个线程对象
        Tread t = new Thread(r);
        // 启动线程
        t.start();
         // 这段程序运行在主线程中(主栈)
        for(int i=0; i<1000; i++){
            System.out.println(i);
        }
    }
    
    // 这并不是一个线程类,是一个可运行的类。它还不是一个线程
    class MyRunnable implements Runnable{
        @Override
        public void run(){
            for(int i=0; i<1000; i++){
                System.out.println(i);
    		}
    	}
    }
    
  • 第三种方式(JDK8新特性)

    实现Callable接口。

    优点:可以获取到线程的执行结果。
    这种方式的缺点:在获取t线程执行结果的时候,当前线程受阻塞,效率较低。

    // 第一步:创建一个“未来任务类”对象。
            // 参数非常重要,需要给一个Callable接口实现类对象。
            FutureTask task = new FutureTask(new Callable() {
                @Override
                public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
                    // 线程执行一个任务,执行之后可能会有一个执行结果
                    // 模拟执行
                    System.out.println("call method begin");
                    Thread.sleep(1000 * 10);
                    System.out.println("call method end!");
                    int a = 100;
                    int b = 200;
                    return a + b; //自动装箱(300结果变成Integer)
                }
            });
    
            // 创建线程对象
            Thread t = new Thread(task);
    
            // 启动线程
            t.start();
    
            // 这里是main方法,这是在主线程中。
            // 在主线程中,怎么获取t线程的返回结果?
            // get()方法的执行会导致“当前线程阻塞”
            Object obj = task.get();
            System.out.println("线程执行结果:" + obj);
    
            // main方法这里的程序要想执行必须等待get()方法的结束
            // 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
            // 另一个线程执行是需要时间的。
            System.out.println("hello world!");
    

8.3. 线程的生命周期

image-20220211121516061

8.4. 线程的方法

8.4.1. 常用方法
  • 获取当前线程对象

    // 在main方法中就是main
    Tread t = Tread.currentTread();
    
  • 获取线程名字

    // 默认为Tread-0、Tread-1·······
    String s = 线程对象.getName();
    
  • 修改线程名字

    线程对象.setName();
    
8.4.2. sleep方法

使当前线程进入“阻塞状态”,放弃占有CPU时间片,让给其他线程使用

// 参数为毫秒
static void sleep(long millis);
  • 中断sleep方法

    线程对象.interruput();
    

    中断线程的睡眠,依靠java的异常处理机制

8.4.3. 合理终止线程的执行
public static void main(String[] args){
    // 创建一个可运行的对象
    MyRunnable r = new MyRunnable();
    // 将可运行的对象封装成一个线程对象
    Tread t = new Thread(r);
    // 启动线程
    t.start();
     // 这段程序运行在主线程中(主栈)
    for(int i=0; i<1000; i++){
        System.out.println(i);
    }
    
    // 终止线程
    t.run = false;
}

// 这并不是一个线程类,是一个可运行的类。它还不是一个线程
class MyRunnable implements Runnable{
    @Override
    public void run(){
        // 做一个布尔标记
        boolean run = true;
        if(run){
            for(int i=0; i<1000; i++){
            System.out.println(i);
			}
        }
        else{
            // 中止当前线程
            return;
        }
	}
}

8.5. 线程安全

8.5.1. 存在线程安全的条件
  • 存在线程安全问题的条件
    • 多线程并发
    • 有共享数据
    • 共享数据有修改行为
  • 解决方法:线程排队执行(不能并发),称为线程同步机制
  • 线程同步会牺牲一部分效率
8.5.2. 线程同步
  • 异步编程模型:线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,

    ​ 多线程并发(效率较高。)

    同步编程模型:
    线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行
    结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,
    两个线程之间发生了等待关系,这就是同步编程模型。
    效率较低。线程排队执行。

  • 语法

    synchronized(多线程共享的对象){
        // 线程同步代码块
    }
    
  • 如果共享的对象就是this,并且需要同步的代码是整个方法体,可以将synchronized使用在实例方法上,表示锁this,如

    public synchronized void withDraw(double money){}
    
  • 在静态方法上使用synchronized,表示使用类锁(不管创建多少个对象,都共享一个类锁),用来保护静态变量的安全

8.5.3. Java中三大变量的线程安全问题
  • 实例变量:在堆中。

    静态变量:在方法区。

    局部变量:在栈中。

    以上三大变量中:

    • 局部变量永远都不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈。)
      局部变量在栈中。所以局部变量永远都不会共享。

    • 实例变量在堆中,堆只有1个。
      静态变量在方法区中,方法区只有1个。
      堆和方法区都是多线程共享的,所以可能存在线程安全问题。

​ 局部变量+常量:不会有线程安全问题。
​ 成员变量:可能会有线程安全问题。

8.5.4. 解决线程安全方法

是一上来就选择线程同步吗?synchronized
不是,synchronized会让程序的执行效率降低,用户体验不好。
系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择
线程同步机制。

第一种方案:尽量使用局部变量代替“实例变量和静态变量”。

第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样
实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,
对象不共享,就没有数据安全问题了。)

第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候
就只能选择synchronized了。线程同步机制。

8.6.守护线程

一类是:用户线程
一类是:守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)。

​ 守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。

注意:主线程main方法是一个用户线程。

守护线程用在什么地方呢?
每天00:00的时候系统数据自动备份。
这个需要使用到定时器,并且我们可以将定时器设置为守护线程。
一直在那里看着,没到00:00的时候就备份一次。所有的用户线程
如果结束了,守护线程自动退出,没有必要进行数据备份了。

设置守护线程的方法:

t.setDaemon(true);
t.start();

8.7. 定时器

定时器的作用:
间隔特定的时间,执行特定的程序。

代码实现

       // 创建定时器对象
        Timer timer = new Timer();
        //Timer timer = new Timer(true); //守护线程的方式

        // 指定定时任务
        //timer.schedule(定时任务, 第一次执行时间, 间隔多久执行一次);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime = sdf.parse("2020-03-14 09:34:30");
        //timer.schedule(new LogTimerTask() , firstTime, 1000 * 10);
        // 每年执行一次。
        //timer.schedule(new LogTimerTask() , firstTime, 1000 * 60 * 60 * 24 * 365);

        //匿名内部类方式
        timer.schedule(new TimerTask(){
            @Override
            public void run() {
                // code....
            }
        } , firstTime, 1000 * 10);


// 编写一个定时任务类
// 假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask {

    @Override
    public void run() {
        // 编写你需要执行的任务就行了。
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String strTime = sdf.format(new Date());
        System.out.println(strTime + ":成功完成了一次数据备份!");
    }
}

8.8. wait和notify方法

wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,这两个方式是Object类中自带的。

8.8.1. wait()方法
Object o = new Object();
o.wait();

表示:
让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。
o.wait();方法的调用,会让“当前线程(正在o对象上活动的线程)”进入等待状态,并且释放之前占有的o对象的锁。

8.8.2. notify()方法
Object o = new Object();
o.notify();

表示:
唤醒正在o对象上等待的线程。不会释放之前占有的o对象的锁。

还有一个notifyAll()方法:
这个方法是唤醒o对象上处于等待的所有线程。

008-wait和notify方法的理解
8.8.3. 生产者和消费者模式

007-生产者和消费者模式

9. 反射机制

什么是反射机制:可以操作字节码文件

作用:让程序更灵活

**相关类:*java.lang.reflect.

重要类:

java.lang.Class:代表整个字节码,代表一个类型,代表整个类。

java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。

java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法

java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。

java.lang.Classpublic class User{
    // Field
    int no;
	// Constructor
   	public User(){}
 	public User(int no){
     	his.no = no;
	}

	// Method
	public void setNo(int no){
		this.no = no;
		public int getNo(){
		return no;
    }
}

字节码内存图:

001-字节码内存图

9.1. 反射类Class

  • 获取java.lang.Class实例三种方式
    第一种:Class c = Class.forName(“完整类名带包名”);
    第二种:Class c = 对象.getClass();
    第三种:Class c = 任何类型.class;

  • 通过反射实例化对象

    Class c = Class.forName("完整类名带包名");
    // newInstance()调用无参构造方法,需要保证类的无参构造方法存在
    Object obj = c.newInstance();
    

    优点:在不改变Java源代码的基础上,做到对不同对象的实例化。符合OCP开闭原则

    只执行类中的静态代码块,可以使用Class.forName(“完整类名带包名”);

  • 获取类路径下文件的绝对路径

    // Thread.currentThread() 当前线程对象
    // getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象。
    // getResource() 【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。 
    String path = Thread.currentThread().getContextClassLoader()
                    .getResource("类路径下(src目录)开始").getPath(); // 这种方式获取文件绝对路径是通用的。
    
  • 文件以流的形式直接返回

    InputStream reader = Thread.currentThread().getContextClassLoader()
        					,getResourceAsStream("类路径下开始");
    
  • 资源绑定器

    资源绑定器只能绑定properties文件,并且这个文件必须在类路径下

    再写路径的时候,路径后面的扩展名不能写

    ResourceBundle bundle = ResourceBundle.getBundle("properties文件路径(扩展名不写)")
    

9.2. 反射属性Field

  • 获取类的属性Field的方法

    // 获取类的公共属性
    Field[] fields = 对象名.getFields();
    
    // 获取类的所有属性
    Field[] fs = 对象名.getDeclaredFields();
    
    // 根据类的属性名来获取属性
    Field field = 对象名.getDeclareField("属性名");
    
    // 获取属性名
    String name = fields[0].getName();
    
    // 获取属性的修饰符列表
    int i = fields[0].getModifiers(); // 返回的修饰符是一个数字,每个数字是修饰符的代号
    // 将“代号”转换成“字符串”
    String modifierString = Modifier.toString(i);
    
     // 获取属性的类型
    Class fieldType = fields[0].getType();
    // 获取属性的类型名
    String fName = fieldType.getName();
    // 获取属性的简单类型名
    String fn = fieldType.getSimpleName();
    
  • 通过反射对象访问Java对象的属性

    class Student{
        private int no = 1111;
    }
    
    Class studentClass = Class.forName("Student");
    Object obj = studentClass.newInstance(); // obj就是Student对象。(底层调用无参数构造方法)
    
    // 获取no属性(根据属性的名称来获取Field)
    Field noFiled = studentClass.getDeclaredField("no");
    
    // 打破封装
    // 这样设置完之后,在外部也是可以访问private的。
    nameField.setAccessible(true);
    
    // 给obj对象(Student对象)的no属性赋值
    /*
       虽然使用了反射机制,但是三要素还是缺一不可:
    		要素1:obj对象
            要素2:no属性
            要素3:2222值
    */
    noFiled.set(obj, 22222); // 给obj对象的no属性赋值2222
    
    // 读取属性的值
    // 两个要素:获取obj对象的no属性的值。
    System.out.println(noFiled.get(obj));
    

9.3. 反射方法Method

  • 可变长参数

    public static void m(int... args){}
    m();
    m(1);
    m(1,2);
    m([1,2,3]);
    
    • 可变长度参数参数个数是0~n个
    • 可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有一个
    • 可变长度参数可以当作一个数组来看待
  • 反射Method

    class UserService{
        public void a(){}
        private int b(){
            return 1;
        }
    }
    
    // 获取类
     Class userServiceClass = Class.forName("UserService");
    // 获取所有的Method(包括私有的!)
     Method[] methods = userServiceClass.getDeclaredMethods();
    
     // 遍历Method
    for(Method method : methods){
        // 获取修饰符列表
        System.out.println(Modifier.toString(method.getModifiers()));
        // 获取方法的返回值类型
        System.out.println(method.getReturnType().getSimpleName());
        // 获取方法名
        System.out.println(method.getName());
        // 方法的修饰符列表(一个方法的参数可能会有多个。)
        Class[] parameterTypes = method.getParameterTypes();
        for(Class parameterType : parameterTypes){
            System.out.println(parameterType.getSimpleName());
        }
    } 
    
  • 通过反射对象调用一个对象的方法

public class UserService {
    /**
     * 登录方法
     * @param name 用户名
     * @param password 密码
     * @return true表示登录成功,false表示登录失败!
     */
    public boolean login(String name,String password){
        if("admin".equals(name) && "123".equals(password)){
            return true;
        }
        return false;
    }
    // 可能还有一个同名login方法
    // java中怎么区分一个方法,依靠方法名和参数列表。
    public void login(int i){

    }
    //退出系统的方法
    public void logout(){
        System.out.println("系统已经安全退出!");
    }
}


Class userServiceClass = Class.forName("UserService");
// 创建对象
Object obj = userServiceClass.newInstance();
// 获取Method
Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
//Method loginMethod = userServiceClass.getDeclaredMethod("login", int.class);

// 调用方法
// 调用方法有几个要素? 也需要4要素。
// 反射机制中最最最最最重要的一个方法,必须记住。
/*
 四要素:
    loginMethod方法
    obj对象
     "admin","123" 实参
    retValue 返回值
*/
Object retValue = loginMethod.invoke(obj, "admin","123123");
System.out.println(retValue);

9.4. 反射构造方法Constructor

  • 使用反射机制创建对象

    // 使用反射机制怎么创建对象呢?
    Class c = Class.forName("完整类名带包名");
    // 调用无参数构造方法
    Object obj = c.newInstance();
    System.out.println(obj);
    
    // 调用有参数的构造方法
    // 第一步:先获取到这个有参数的构造方法
    Constructor con = c.getDeclaredConstructor(int.class, String.class, String.class,boolean.class);
    // 第二步:调用构造方法new对象
    Object newObj = con.newInstance(110, "jackson", "1990-10-11", true);
    System.out.println(newObj);
    
    // 获取无参数构造方法
    Constructor con2 = c.getDeclaredConstructor();
    Object newObj2 = con2.newInstance();
    System.out.println(newObj2);
    
  • 获取父类和父接口

    // String举例
    Class stringClass = Class.forName("java.lang.String");
    
    // 获取String的父类
    Class superClass = stringClass.getSuperclass();
    System.out.println(superClass.getName());
    
    // 获取String类实现的所有接口(一个类可以实现多个接口。)
    Class[] interfaces = stringClass.getInterfaces();
    for(Class in : interfaces){
        System.out.println(in.getName());
    }
    

10. 注解

10.1. 定义和使用

  • 注解Annotation是一种引用数据类型,编译后生成xxx.class文件

    注解只在编译阶段起作用,和运行阶段无关

  • 自定义语法格式

    [修饰符列表] @interface 注解类型名{
    
     }
    

    举例:

    public @interface MyAnnotation {
    
        /**
         * 我们通常在注解当中可以定义属性,以下这个是MyAnnotation的name属性。
         * 看着像1个方法,但实际上我们称之为属性name。
         * @return
         */
        String name();
    
        /*
        颜色属性
         */
        String color();
    
        /*
        年龄属性
         */
        int age() default 25; //属性指定默认值
    
    }
    
    // 如果一个注解当中有属性,那么必须给属性赋值。(除非该属性使用default指定了默认值。)
    //@MyAnnotation(属性名=属性值,属性名=属性值,属性名=属性值),
    //如果注解只有一个属性,并且属性名是value,属性名可以省略
    @MyAnnotation(name = "zhangsan", color = "红色")
    public void doSome(){
    
    }
    

    注解中属性的的类型可以是:

    ​ byte short int long float double boolean char String Class 枚举类型

    ​ 以及以上每种的数组形式

    ​ 如果数组只有一个元素,大括号可以省略

  • 使用

    第一:注解使用时的语法格式是:
    @注解类型名

    第二:注解可以出现在类上、属性上、方法上、变量上等…注解还可以出现在注解类型上。

  • JDK内置注解

    java.lang包下的注释类型:

    • 用 @Deprecated 注释的程序元素,表示标注的元素已过时
      不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。
    • @Override 表示一个方法声明打算重写超类中的另一个方法声明。

10.2. 元注解

  • 什么是元注解

    用来标注“注解类型”的“注解”,称为元注解。

  • 常见的元注解有哪些?
    Target
    Retention

  • 关于Target注解:
    这是一个元注解,用来标注“注解类型”的“注解”
    这个Target注解用来标注“被标注的注解”可以出现在哪些位置上。

    @Target(ElementType.METHOD):表示“被标注的注解”只能出现在方法上。
        
    @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
     表示该注解可以出现在:构造方法上,字段上,局部变量上,方法上,包上,类上...
    
  • 关于Retention注解:
    这是一个元注解,用来标注“注解类型”的“注解”
    这个Retention注解用来标注“被标注的注解”最终保存在哪里。

    @Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中。
    @Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中。
    @Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取。
    

    源代码:

    //元注解	
    public @interface Retention {
        //属性
        RetentionPolicy value();
    }
    
    public enum RetentionPolicy {
        SOURCE,
        CLASS,
        RUNTIME
    }
    
    //@Retention(value=RetentionPolicy.RUNTIME)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation{}
    

10.3. 反射注解

  • 反射获取注解对象及其属性

    // 获取这个类
    Class c = Class.forName("MyAnnotationTest");
    // 判断类上面是否有@MyAnnotation
    if(c.isAnnotationPresent(MyAnnotation.class)){
        // 获取该注解对象
        MyAnnotation myAnnotation = (MyAnnotation)c.getAnnotation(MyAnnotation.class);
        // 获取注解对象的属性
        String value = myAnnotation.value();
        System.out.println(value);
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值