java0-基础操作

1,java输入

  • Scanner
    • Scanner scan = new Scanner(System.in);
      String *** = scan.nextLine();
      byte *** = scan.nextByte();
      short *** = scan.nextShort();
      int *** = scan.nextInt();
      long *** = scan.nextLong();
      float *** = scan.nextFloat();
      double *** = scan.nextDouble();
      
    • 单行/多行输入
    • 输入字符
      char flag = sc.nextLine().charAt(0);  //本质是String的charAt
      
    • 判断输入是数字还是字符串
      if(!sc.hasNextInt()){
      //此时为字符串
      }
      
  • next与nextLine的区别
    • 用Scanner实现字符串的输入有两种方法,一种是next(),一种nextLine ()
      • next () 一定要读取到有效字符后才可以结束输入,对输入有效字符之前遇到的空格键、Tab键或Enter键等结束符,next () 方法会自动将其去掉,只有在输入有效字符之后,next ()方法才将其后输入的空格键、Tab键或Enter键等视为分隔符或结束符。
      • nextLine ()方法的结束符只是Enter键。
    • 正是因为以上特性导致nextInt()后紧跟nextLine(),nextLine()读取到一个换行符后就直接跳过的问题,解决方法:
      • 换用next(忽略最初的换行符)
      • 或者在nextInt()与nextLine()之间再加一个nextLine()
      import java.util.Scanner;
      
      public class test{
        public static void main(String[] args){
          Scanner sc = new Scanner(System.in);
          int q = sc.nextInt();
          System.out.println(q);
          String t = sc.nextLine();  //读取到一个 \n 就跳过了
          System.out.println(t);
      
          q = sc.nextInt();
          System.out.println(q);
          t = sc.next();
          System.out.println(t);
      
          q = sc.nextInt();
          System.out.println(q);
          sc.nextLine();  //读取 \n
          t = sc.nextLine();
          System.out.println(t);
        }
      }
      
  • 总结:
    • 输入字符串中有空格时,用nextLine
    • 紧跟nextInt等时,首选next;如果一定要用nextLine,在中间插入一个nextLine读取遗留的换行符

2,权限修饰符

同一个类 同一个包 子类 所有类
private *
default/缺省/不写 * *
protected * * *
public * * * *

3,重载与重写

英文 位置 修饰符 返回值 方法名 参数列表 抛出异常 方法体
重载 overload 同一个类中 无关 无关 必须相同 必须不同 无关 不同
重写 override 子类父类中 父类修饰符范围小于子类(如:父类private,子类public) 必须相同 必须相同 必须相同 小于等于 不同

4,代码执行顺序

https://www.cnblogs.com/dolphin0520/p/3799052.html
类只加载一次
加载过程中执行1:在执行开始前,先要寻找到main方法;执行main方法之前,必须加载main方法所在的类;如果main方法所在的类继承自其他类,会转去先加载父类;
加载过程中执行2:加载类的过程中发现有static块,便执行static块;
实例化时执行1:加载完所有类后,执行main方法;
实例化时执行2:main方法中有new进行实例化的时候,先执行父类的构造器(同名函数)并初始化父类的成员变量(类中的new等),再执行子类的构造器并初始化子类的成员变量;

5,java.util下常用类

https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/package-summary.html

5-1-常用数据结构

  • java集合框架
  • java.util.ArrayList
    • 特点及使用场合:
      • 特点:常规数组,查找的时间复杂度为O(n)
        • 注意不能一边遍历,一边改变对象,也就是类似for(int num: test){test.add(10);}的代码是错的。
        • while(test.size()!=0){int a = test.remove(0);System.out.println(a);test.add(10);}此类代码是正确的。
      • 使用场合:不定长度数组,不会频繁插入删除数据;保持相对次序的去重
  • java.util.LinkedList
    • 特点及使用场合:
      • 特点:使用链表实现的数组,插入删除数据的时间复杂度都是O(1)
      • 使用场合:不定长度数组,并且会频繁插入删除数据(比如二叉树的 层次/广度优先/BFS 遍历)
  • java.util.PriorityQueue
    • 特点及使用场合:
      • 特点:优先级队列(本质默认是小根堆),插入和删除对象会根据比较器自动排序,并且时间复杂度是O(logN)
      • 使用场合:不定长度数组,并且插入和删除对象需要频繁对数组排序(例子:6022. 将数组和减半的最少操作次数
    • 创建:PriorityQueue<C> test = new PriorityQueue<C>(lambda)
    • 增:add(object), offer(object)
    • 删:remove(object)-删除指定元素,poll()-删除并返回队首元素,clear()-清空
    • 查:contains(object)-包含元素返回true,peek()-获取队首元素,size()-查看队列大小
    • 其他帖子:
//例子-自定义数据类型
import java.util.Collections;
import java.util.PriorityQueue;

class hashMapKeyValue{
    int key;
    int value;
    public hashMapKeyValue(){};
    public hashMapKeyValue(int key, int value){this.key=key; this.value=value;}
}

public class testForSort {
    public static void main(String[] args) {
        //PriorityQueue<hashMapKeyValue> test = new PriorityQueue<hashMapKeyValue>((hashMapKeyValue num1, hashMapKeyValue num2)->(num1.key) - num2.key);
        PriorityQueue<hashMapKeyValue> test = new PriorityQueue<hashMapKeyValue>((hashMapKeyValue num1, hashMapKeyValue num2)->(num2.key) - num1.key);
        for(int i=0; i<10; i++){
            test.add(new hashMapKeyValue(i, 9-i));
        }
        for(hashMapKeyValue num: test){
            System.out.printf("%d-%d ", num.key, num.value);
        }
    }
}
//使用comparator匿名类初始化
import java.util.PriorityQueue;
import java.util.Comparator;

class State{
    int num;
    String name;
    public State(int num, String name){
        this.num = num;
        this.name = name;
    }
}

PriorityQueue<State> cache2 = new PriorityQueue<>(new Comparator<State>(){
    @Override
    public int compare(State s1, State s2){
        if(s1.num<s2.num){
            return 1;  //数值逆序-1
        }else if(s1.num>s2.num){
            return -1;  //数值逆序-2
        }else{
            return s2.name.compareTo(s1.name);  //字符串字典序逆序
        }
    }
});
  • java.util.HashSet
    • 特点及使用场合:
      • 特点:无重复元素集合,查找元素的时间复杂度为0(1)
      • 使用场合:不保持相对次序的去重,数据量不能太大;需要频繁查找元素是否存在;求交、并、差集
//转载自 https://blog.csdn.net/qq_26929957/article/details/89152491
import java.util.HashSet;
import java.util.Set;
 
public class TestSet {
 
    public static void main(String[] args) {
 
        Set<String> result = new HashSet<String>();
        Set<String> set1 = new HashSet<String>() {
            {
                add("王者荣耀");
                add("英雄联盟");
                add("穿越火线");
                add("地下城与勇士");
            }   
        };
 
        Set<String> set2 = new HashSet<String>() {
            {
                add("王者荣耀");
                add("地下城与勇士");
                add("魔兽世界");
            }
        };
 
        result.clear();
        result.addAll(set1);
        result.retainAll(set2);
        System.out.println("交集:" + result);
 
        result.clear();
        result.addAll(set1);
        result.removeAll(set2);
        System.out.println("差集:" + result);
 
        result.clear();
        result.addAll(set1);
        result.addAll(set2);
        System.out.println("并集:" + result);
 
    }
 
}
  • java.util.BitSet
    • 特点及使用场合:
      • 特点:boolean数组,使用索引存储值,以此确定某个值的存在与否
      • 使用场合:对海量数据进行一些统计工作,比如20亿电话号码去重等
      • BitSet的输入只能是int,如果超过int,可以配合HashMap进行分组使用
  • java.util.HashMap
    • 特点及使用场合:
      • 特点:存储元素之间映射关系,通过散列速度很快
      • 使用场合:记录映射关系,支持频繁插入和修改
  • java.util.LinkedHashMap
    • LinkedHashMap 通常提供的是遍历顺序符合插入顺序,它的实现是通过为条目(键值对)维护一个双向链表。
    • 可以使用匿名类重写removeEldestEntry方法,实现最旧条目的自动删除
  • java.util.TreeMap
    • 对于 TreeMap,它的整体顺序是由键的顺序关系决定的,通过 Comparator 或 Comparable(自然顺序)来决定。
    • 使用场合:在HashMap的基础上需要保证键有序

5-2-java.util.Collections

  • java.util.Collections.sort():对继承自java.util.List的类进行排序,如ArrayList, LinkedList,排序时可以使用lambda表达式。
//例子1-基本数据类型
import java.util.Collections;
import java.util.ArrayList;
import java.util.LinkedList;

public class testForSort {
    public static void main(String[] args) {
        ArrayList<Integer> test = new ArrayList<Integer>();
        //LinkedList<Integer> test = new LinkedList<Integer>();
        for(int i=0; i<10; i++){
            test.add(i);
        }
        //Collections.sort(test);  //正序
        //Collections.sort(test, Collections.reverseOrder());  //倒序
        //Collections.sort(test, (Integer num1, Integer num2)->(num1-num2));  //正序
        Collections.sort(test, (Integer num1, Integer num2)->(num2-num1));  //倒序
        for(int num: test){
            System.out.printf("%d ", num);
        }
    }
}
//例子2-自定义数据类型-比如间接实现HashMap依照key或者value排序
import java.util.Collections;
import java.util.ArrayList;
import java.util.LinkedList;

class hashMapKeyValue{
    int key;
    int value;
    public hashMapKeyValue(){};
    public hashMapKeyValue(int key, int value){this.key=key; this.value=value;}
}

public class testForSort {
    public static void main(String[] args) {
        ArrayList<hashMapKeyValue> test = new ArrayList<hashMapKeyValue>();
        //LinkedList<Integer> test = new LinkedList<Integer>();
        for(int i=0; i<10; i++){
            test.add(new hashMapKeyValue(i, 9-i));
        }
        //Collections.sort(test, (hashMapKeyValue num1, hashMapKeyValue num2)->(num1.key-num2.key));  //key正序
        Collections.sort(test, (hashMapKeyValue num1, hashMapKeyValue num2)->(num2.key-num1.key));  //key倒序
        for(hashMapKeyValue num: test){
            System.out.printf("%d-%d ", num.key, num.value);
        }
    }
}

5-3-java.util.Arrays

  • 使用方法:
Arrays.sort(数组)  //对数组排序 
Arrays.sort(数组, lambda)  //根据lambda函数对数组排序,数组必须是包装器类型
//例子
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[][] test = new int[5][2];
        for(int i=0; i<5; i++){
            test[i]=new int[]{i, 5-i};
        }
        Arrays.sort(test, (a,b)->(a[1]-b[1]));
        for(int i=0; i<5; i++){
            System.out.printf("%d %d \n", test[i][0], test[i][1]);
        }
    }
}

6,String类的常用方法

  • split函数:返回值为String[],分割符会被替换为"",替换规律为:最左侧""数量与分割符数目相同,一个不少;中间""少一个;最右侧""少完。
    • 或者split中写正则表达式?+*
public class Main {
    public static void main(String[] args) {
        String test = "RRLRRSLLRR";
        // String test = "LLRR";
        // String test = "LLRLRLLSLRLLSLSSSS";
        // String test = "SSRSSRLLRSLLRSRSSRLRRRRLLRRLSSRR";
        // String test = "LSSSLLSSSSLRRSLLLSLSLRRLLLLLRSSRSRRSLLLSSS";
        String[] testArray = test.split("R");
        for(String array: testArray){
            System.out.println(array + "@");
            //System.out.println(array.length());
        }
    }
}
  • substring函数:字符串切片,同样顾头不顾尾
  • toCharArray函数:返回字符数组
  • indexOf(int index):返回索引index处字符
  • lastIndexOf():查找字符或者子串是后一次出现的地方
  • A.contains(String B):判断字符串B是否为字符串A的子串
  • A.indexOf(String B):如果B为A的子串,返回B在A中首次出现时,B的首字符在A中的索引,否则返回-1
  • replace,replaceAll,replaceFirst
    • replace和replaceAll
      • public String replace(char oldChar, char newChar)在字符串中用newChar字符替代oldChar字符,返回一个新的字符串。(即可以支持字符的替换,也支持字符串的替换)
      • public String replaceAll(String regex,String replacement)使用给定的 replacement 字符串替换此字符串匹配给定的正则表达式的每个子字符串。(是基于正则表达式的替换,比如,可以通过replaceAll("\\d", "*")把一个字符串所有的数字字符都换成星号)
      • replace和replaceAll都是全部替换
    • replaceFirst()只替换第一次出现的字符串
    • 可以用于删除,如.replace("???", "")就是将字符串中的???删除。
  • length():字符串的长度
  • charAt():截取一个字符
  • getChars():截取多个字符
  • trim() 去掉起始和结尾的空格
  • toLowerCase() 转换为小写
  • toUpperCase() 转换为小写

7,instanceof的用法

  • 父类的引用可以指向子类对象(引用类是对象类的父类),所以声明为父类的变量可以强制类型转换为子类(将引用类转换为与子类一致)
  • 转换的前提是,对象类到底是不是转换后的类(父类->子类,转换后引用类和对象类一致)或转换后的类的子类(父类->父类,转换后引用类依旧是对象类的父类)的实例,这个就可用instanceof进行判断(以上两种情形都是true)。
  • 语法
if(object instanceof Class){
  //object的类与Class一致,或者是Class的子类,则返回为true,此时可以强转
  //object如果是null,则肯定返回false,所以instanceof 还可以用来判断对象是否为空(只要返回true肯定不为空)
  Class newObject = (Class)object;
}
  • 这样做的意义是,newObject扩大了可以使用的方法/属性,因为可以使用什么是由引用类决定的。

8,final修饰符

  • final修饰类:不可被继承。
  • final修饰方法(不可修饰构造方法):不可被子类覆盖。
  • final修饰变量:赋值后不可被修改。
    • final修饰成员变量:该变量必须被赋值,并且要么在声明的时候赋值,要么在构造方法中赋值,其他方法中不可被赋值。
    • final修饰类变量:要么在声明的时候赋值,要么在static块中赋值。
    • final修饰形参:不可在方法体中被赋值,只能在调用的时候通过传递实参赋值。
    • final修饰引用:必须被赋值,但是所指向的堆里面的数据是可以修改的。(final修饰的数组里的元素也是可以修改的)

9,object中的常用方法

  • object类中没有成员变量,只有方法
toString()  //返回值:getClass().getName() + "@" + Integer.toHexString(hashCode());
getClass()  //得到一个Class类的对象
* Class clazz = 对象.getClass() / 类.class
* Class类中的方法:
  * getName
  * getSimpleName
  * getField  //Field *** = clazz.getField(???);
    * 这个获取的Field也有方法:getName, getType
  * getMethod // Method *** = clazz.getMethod();
hashCode()  //得到对象的哈希码/特征码
equals()    //判断两个对象逻辑上是否相等(并不是判断两个对象是否为同一个对象,那是==的功能)
* hashCode和equals是最常覆盖的两个方法,覆盖原则是equals返回为true,hashCode返回值就应该相等。(equals为true->hashCode返回值相等)
* String也应该用equals比较,因为很长的String对象还是会创建两个
* equals和hashCode可以用IDE自动生成的
toString()  //IDE也可以自动生成,StringBuilder的append也是自动调的toString方法

10,equals和==

11,StringBuilder的用法

StringBuilder sb = new StringBuilder();
sb.append('*' / "***");      //可以添加字符,也可以添加字符串
sb.deleteCharAt(sb.length()-1);    //删除最后一个字符
sb.toString();                  //将存储的内容转化为字符串

12,通过char[]和String的相互转换

String test = "123";
char[] ch = test.toCharArray();
String test1 = new String(ch);

13,java取范围内随机数

  • java.util.Random
Random r = new Random();
r.nextInt(int n);  //生成一个随机的 int 值,该值介于 [0,n),包含 0 而不包含 n。
  • java.Math.Random方法 默认会返回大于等于 0.0、小于 1.0 的 double 类型随机数。
int res = (int)min + (int)(Math.Random()*(max-min));  //生成一个随机的 int 值,该值介于 [min, max),包含 min 而不包含 max。

14,java反射/reflection

  • 反射-反过来让对象告诉我们他是什么,他有什么
  • 反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。
  • 反射最大的作用之一就在于我们可以不在编译时知道某个对象的类型,而在运行时通过提供完整的”包名+类名.class”得到。注意:不是在编译时,而是在运行时。
    • 在运行时能判断任意一个对象所属的类。
    • 在运行时能构造任意一个类的对象。
    • 在运行时判断任意一个类所具有的成员变量和方法。
    • 在运行时调用任意一个对象的方法。
    • 说大白话就是,利用Java反射机制我们可以加载一个运行时才得知名称的class,获悉其构造方法,并生成其对象实体,能对其fields设值并唤起其methods。
  • 由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

14-1,反射的基本概念和语法

  • 作用:可以使用反射动态得访问属性,方法,静态属性,静态方法,private修饰的属性,private修饰的方法
  • 代码模板
Class clazz = 对象.getClass() / 类.class;

/*
属性反射,使用时IDEA可以通过在Field按住alt+enter->import class引入Field类,
在getField按住alt+enter->Add exception to method signature补齐需要抛出的异常
在get按住alt+enter->Add exception to method signature补齐需要抛出的异常
*/
Field 属性名Field = clazz.getField("属性名");  //只能获取具有访问权限的属性
-----------------------------------------------------------
Field 属性名Field = clazz.getDeclaredField("属性名");  //可以获取任何声明了的属性,包括private修饰的属性
属性名Field.setAccessible(true);
-----------------------------------------------------------
属性名Field.get(对象);  //相当于得到 对象.属性名 ,必须传入对象
属性名Field.get(null);  //此时属性名对应的是静态属性,可以直接传入null
属性名Field.set(对象, 传入值);  //相当于 对象.属性名=传入值
-----------------------------------------------------------
System.out.println(clazz.getName()+"里的field")
for(Field field: clazz.getFields()){
  System.out.println(field.getType()+" "+field.getName());
}

/*
方法反射
*/
Method 方法名Method = clazz.getMethod("方法名", 形参类型.class);  //只能获取具有访问权限的方法
-----------------------------------------------------------
Method 方法名Method = clazz.getDeclaredMethod("方法名", 形参类型.class);  //可以获取任何声明了的方法,包括private修饰的方法
方法名Field.setAccessible(true);
-----------------------------------------------------------
方法名Method.invoke(对象, 方法需要传递的实参);  //相当于 对象.方法名(实参),如果是静态方法则对象为null

14-2,反射的效率

示例代码
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

class simpleClass{
    //反射目标类
    int i = 1;
    int test() {
        return 0;
    }
}

public class testForReflection {
    public static void main(String[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchFieldException {
        //直接创建对象
        long start = System.currentTimeMillis();
        for(int i=0; i<(int)1e7; i++){
            simpleClass obj = new simpleClass();
        }
        System.out.println("直接创建耗时:"+(System.currentTimeMillis()-start));

        //反射创建对象1-每次都获取构造器
        start = System.currentTimeMillis();
        for(int i=0; i<(int)1e7; i++){
            simpleClass.class.getDeclaredConstructor().newInstance();
        }
        System.out.println("反射创建对象1耗时:"+(System.currentTimeMillis()-start));

        //反射创建对象2-将构造器缓存下来
        start = System.currentTimeMillis();
        Constructor constructor = simpleClass.class.getDeclaredConstructor();
        for(int i=0; i<(int)1e7; i++){
            constructor.newInstance();
        }
        System.out.println("反射创建对象2耗时:"+(System.currentTimeMillis()-start));

        //反射创建对象3-将构造器缓存下来,并关闭java语言的访问检查(可以直接使用private属性和方法)
        start = System.currentTimeMillis();
        constructor = simpleClass.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        for(int i=0; i<(int)1e7; i++){
            constructor.newInstance();
        }
        System.out.println("反射创建对象3耗时:"+(System.currentTimeMillis()-start));

        //直接调用方法
        start = System.currentTimeMillis();
        simpleClass obj = new simpleClass();
        for(int i=0; i<(int)1e7; i++){
            obj.test();
        }
        System.out.println("直接调用方法耗时:"+(System.currentTimeMillis()-start));

        //反射调用方法1-每次都获取方法
        start = System.currentTimeMillis();
        obj = new simpleClass();
        for(int i=0; i<(int)1e7; i++){
            simpleClass.class.getDeclaredMethod("test").invoke(obj);
        }
        System.out.println("反射调用方法1耗时:"+(System.currentTimeMillis()-start));

        //反射调用方法2-将方法缓存下来
        start = System.currentTimeMillis();
        obj = new simpleClass();
        Method method = simpleClass.class.getDeclaredMethod("test");
        for(int i=0; i<(int)1e7; i++){
            method.invoke(obj);
        }
        System.out.println("反射调用方法2耗时:"+(System.currentTimeMillis()-start));

        //反射调用方法3-将方法缓存下来,并关闭java语言的访问检查(可以直接使用private属性和方法)
        start = System.currentTimeMillis();
        obj = new simpleClass();
        method = simpleClass.class.getDeclaredMethod("test");
        method.setAccessible(true);
        for(int i=0; i<(int)1e7; i++){
            method.invoke(obj);
        }
        System.out.println("反射调用方法3耗时:"+(System.currentTimeMillis()-start));

        //直接调用属性
        start = System.currentTimeMillis();
        obj = new simpleClass();
        for(int i=0; i<(int)1e7; i++){
            int b = obj.i;
        }
        System.out.println("直接调用属性耗时:"+(System.currentTimeMillis()-start));

        //反射调用属性1-每次都获取属性
        start = System.currentTimeMillis();
        obj = new simpleClass();
        for(int i=0; i<(int)1e7; i++){
            int b = (int)simpleClass.class.getDeclaredField("i").get(obj);
        }
        System.out.println("反射调用属性1耗时:"+(System.currentTimeMillis()-start));

        //反射调用属性2-将属性缓存下来
        start = System.currentTimeMillis();
        obj = new simpleClass();
        Field field = simpleClass.class.getDeclaredField("i");
        for(int i=0; i<(int)1e7; i++){
            int b = (int)field.get(obj);
        }
        System.out.println("反射调用属性2耗时:"+(System.currentTimeMillis()-start));

        //反射调用属性3-将属性缓存下来,并关闭java语言的访问检查(可以直接使用private属性和方法)
        start = System.currentTimeMillis();
        obj = new simpleClass();
        field = simpleClass.class.getDeclaredField("i");
        field.setAccessible(true);
        for(int i=0; i<(int)1e7; i++){
            int b = (int)field.get(obj);
        }
        System.out.println("反射调用属性3耗时:"+(System.currentTimeMillis()-start));
    }
}

14-3,使用反射获取ArrayList的容量

  • 如果运行时报错module java.base does not “opens java.util“ to unnamed module,因为java9之后反射对private属性的访问机制发生变化,可以在VM option添加--add-opens java.base/java.util=ALL-UNNAMED参考
示例代码
public class test3 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        ArrayList<Integer> test = new ArrayList<>();
        for(int i=0; i<100; i++){
            test.add(i);
            System.out.println(i+" "+getCapcity(test));
        }
    }

    static int getCapcity(ArrayList<Integer> arrayList) throws NoSuchFieldException, IllegalAccessException {
        Class clazz = ArrayList.class;
        Field field= clazz.getDeclaredField("elementData");
        field.setAccessible(true);
        Object[] res = (Object[]) field.get(arrayList);
        return res.length;
    }
}

14-4,Unable to make protected final java.lang.Class

  • JDK8以上导致的非法反射访问警告的,这种问题不会在JDK8出现,也是模块化的问题,因此解决方式类似,开放模块即可。
  • 需要添加如下两个启动参数:
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED
上面的无效,则改为
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED

14-5,Method包含形参

  • 形参只有一个
    • 获取阶段:在getMethod时除了方法名的字符串,要额外传入形参的类(否则会NoSuchMethodException
    • 执行阶段:在invoke时除了对象,传入额外的实参
  • 形参有多个
    • 获取阶段:在getMethod时除了方法名的字符串,要额外传入形参的类的数组(否则会NoSuchMethodException
    • 执行阶段:在invoke时除了对象,依次传入额外的实参
  • 注意:Integer.classint.class不同
示例代码
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

class Node{
    int val;
    Node prev;
    Node next;
    public Node(){}
    public Node(int val){
        this.val = val;
    }
    public Node(int val, Node prev, Node next){
        this.val = val;
        this.prev = prev;
        this.next = next;
    }
}

class MyLinkedList {

    Node prevHead;
    Node nextTail;
    int length;

    public MyLinkedList() {
        prevHead = new Node();
        nextTail = new Node();
        length=0;
        prevHead.next = nextTail;
        nextTail.prev = prevHead;
    }

    public Node getNode(int index){
        Node head = prevHead;
        for(int i=0; i<=index; i++){
            head = head.next;
        }
        return head;
    }

    public void addNodeBetweenNodes(Node prevNode, Node node, Node nextNode){
        prevNode.next = node;
        node.prev = prevNode;
        node.next = nextNode;
        nextNode.prev = node;
    }

    public int get(int index) {
        // System.out.println(this);
        if(index<0 || index>=length){
            return -1;
        }
        return getNode(index).val;
    }

    public void addAtHead(int val) {
        // System.out.println(this);
        addNodeBetweenNodes(prevHead, new Node(val), prevHead.next);
        length++;
        // System.out.println(this);
    }

    public void addAtTail(int val) {
        // System.out.println(this);
        addNodeBetweenNodes(nextTail.prev, new Node(val), nextTail);
        length++;
    }

    public void addAtIndex(int index, int val) {
        // System.out.println(this);
        if(index<=0){
            addAtHead(val);
            length++;
        }else if(index==length){
            addAtTail(val);
            length++;
        }else if(index>length){
            return;
        }else{
            Node presNode = getNode(index);
            addNodeBetweenNodes(presNode.prev, new Node(val), presNode);
            length++;
        }
    }

    public void deleteAtIndex(int index) {
        // System.out.println(this);
        if(index<0 || index>=length){
            return;
        }
        Node presNode = getNode(index);
        presNode.prev.next = presNode.next;
        presNode.next.prev = presNode.prev;
        length--;
    }

    @Override()
    public String toString(){
        StringBuilder sb = new StringBuilder();
        Node head = prevHead.next;
        while(head!=nextTail){
            sb.append(head.val+"-");
            head = head.next;
        }
        return sb.toString()+"    "+length;
    }

}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList();
 * int param_1 = obj.get(index);
 * obj.addAtHead(val);
 * obj.addAtTail(val);
 * obj.addAtIndex(index,val);
 * obj.deleteAtIndex(index);
 */


public class test1 {
    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        MyLinkedList list = new MyLinkedList();

        String[] funcs = {"MyLinkedList","addAtHead","addAtIndex","get","addAtHead","addAtTail","get","addAtTail","get","addAtHead","get","addAtHead"};
        int[][] params = {{}, {5},{1,2},{1},{6},{2},{3},{1},{5},{2},{2},{6}};
        Class[] methodArguments = {int.class, int.class};

        System.out.println(Integer.class.getName());
        System.out.println(int.class.getName());

        System.out.println(funcs.length==params.length);
        for(int i=1; i< funcs.length; i++){
            System.out.println(funcs[i]);
            if(!funcs[i].equals("addAtIndex")){
                Method method = MyLinkedList.class.getDeclaredMethod(funcs[i], methodArguments[0]);
                method.invoke(list, params[i][0]);
            }else{
                Method method = MyLinkedList.class.getDeclaredMethod(funcs[i], methodArguments);
                method.invoke(list, params[i][0], params[i][1]);
            }
            System.out.println(funcs[i]+" "+ Arrays.toString(params[i]));
            System.out.println(list);
        }
    }
}

15,java8 steam

  • 参考:https://blog.csdn.net/qq_31821675/article/details/104405884
  • java.util.Stream 表示能应用在一组元素上一次执行的操作序列。(Stream本身并不存储元素,只是做计算)
  • Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样就可以将多个操作依次串起来。
    • 中间操作:例如peek()方法提供Consumer(消费)函数,但执行peek()方法时不会执行Consumer函数,而是等到流真正被消费时(终端操作时才进行消费)才会执行,这种操作为中间操作;
    • 终端操作:例如forEach()、collect()、count()等方法会对流中的元素进行消费,并执行指定的消费函数(peek方法提供的消费函数在此时执行),这种操作为终端操作。
  • Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行stream()执行或者并行parallelStream()执行,其中并行操作能发挥多核处理器的优势。
    • 转为stream
      • 对于数组:Arrays.stream(***)
      • 对于Collections子类对象:***.stream() 或 ***.parallelStream()
    • stream转数组:var *** = ***Stream.toArray()
  • 常用方法:
    • filter方法,count方法
      • 使用filter()方法对数组进行过滤
      • 使用count()方法对数组进行大小统计
      • long count = ***.stream().filter(lambda表达式 比如 i -> i>2).count()
    • forEach方法
      • forEach()方法的参数为一个Consumer(消费函数,一个函数式接口)对象,forEach()方法用来迭代流中的每一个数据,迭代过后不会返回stream(一遍就结束了)
      • ***.stream().forEach(n -> System.out.println(n))
    • map方法
      • 将流中的所有元素用Function对象进行运算,生成新的流对象(流的元素类型可能改变),会返回stream对象
      • ***.stream().map(n -> n+1)
    • mapToInt方法 mapToLong方法 mapToDouble方法
      • 使用map方法之后.toArray()得到的都是包装类数组,如果要得到基本数据类型数组需要使用mapToInt方法 mapToLong方法 mapToDouble方法等。
    • reduce方法
      • reduce操作又称为折叠操作,用于将流中的所有值合成一个。
      • 源码:Optional<T> reduce(BinaryOperator<T> accumulator);
      • reduce()方法参数为BinaryOperator类型的累加器(它接受两个类型相同的参数,返回值类型跟参数类型相同),返回一个Optional对象。
      • ***.stream().reduce((i1, i2)->i1+i2).getAsInt();
    • collect方法
      • collect()方法的参数为一个java.util.stream.Collector类型对象,可以用java.util.stream.Collectors工具类提供的静态方法来生成,Collectors类实现很多的归约操作,如Collectors.toList()、Collectors.toSet()、Collectors.joining()(joining适用于字符串流)等。
      • List<***> *** = ***.stream().collect(Collectors.toList());
    • distinct() 去重
    • sorted() 对流进行排序
    • peek()   生成一个包含原Stream的所有元素的新Stream,并指定消费函数
    • min 获取最小值
    • max 获取最大值
    • getAsInt() 结果转为int
  • 使用range生成数组
    • Integer[] test1 = IntStream.range(0, 10).boxed().toArray(Integer[]::new);
    • int[] test2 = IntStream.range(0, 10).toArray();
  • list, int数组, Integer数组 相互转化
    • int[] 转 list Arrays.stream(***).boxed().collect(Collectors.toList());
    • int[] 转 Integer[] Arrays.stream(***).boxed().toArray(Integer[]::new);
    • Integer[] 转 list Arrays.asList(***);
    • Integer[] 转 int[] Arrays.stream(***).mapToInt(Integer::valueOf).toArray();
    • list 转 int[] ***.stream().mapToInt(Integer::valueOf).toArray();
    • list 转 Integer[] ***.toArray(new Integer[0]);
  • Integer数组转string
    • Arrays.toString(***)
  • 例子
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;


public class test3 {
    public static void main(String[] args) {
        int[] test = {1,2,3,4,5};

        //filter count
        System.out.println(Arrays.stream(test).filter(i -> i>2).count());

        //forEach
        //Arrays.stream(test).forEach(i-> System.out.println(i));
        Arrays.stream(test).forEach(System.out::println);

        //map
        var test1 = Arrays.stream(test).map(i -> (int)Math.pow(i,2)).toArray();

        //reduce
        System.out.println(Arrays.stream(test).reduce((i,j)->i+j).getAsInt());

        //collect
        List<Integer> test2 = Arrays.asList(1,2,3,4,5);
        var test3 = test2.stream().map(n->n+1).collect(Collectors.toList());

        //min
        System.out.println(Arrays.stream(test).min().getAsInt());
    }
}

16,java8 lambda表达式

  • 参考:https://blog.csdn.net/qq_31821675/article/details/103168355
  • 概念:Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
  • 语法格式:(parameters) -> expression 或 (parameters) ->{ statements; }
  • 特征:
    • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
    • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
    • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
    • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指明表达式返回了一个数值。
  • lambda表达式与匿名类
    • 一个关键的不同点就是关键字 this。匿名类的 this 关键字指向匿名类,而lambda表达式的 this 关键字指向包围lambda表达式的类。
    • 另一个不同点是二者的编译方式。Java编译器将lambda表达式编译成类的私有方法。
    • lambda相当于jvm帮我们生成了一个类来实现接口,并调用我们提供的方法。
例子1-匿名内部类和lambda表达式对比的例子
//匿名内部类
public class test {

    public static void main(String[] args) {
        List<String> test1 = new ArrayList<>();
        for(int i=0; i<10; i++){
            test1.add(i+"");
        }
        System.out.println(test1.toString());

        //匿名内部类
        test1.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s+ " Hellow!");
            }
        });
    }
}
//lambda表达式的例子-lambda形式1
public class test {

    public static void main(String[] args) {
        List<String> test1 = new ArrayList<>();
        for(int i=0; i<10; i++){
            test1.add(i+"");
        }
        System.out.println(test1.toString());
        //lambda形式1
        test1.forEach(s-> System.out.println(s+ " Hellow!"));
    }
}
//lambda表达式的例子-lambda形式2--外部变量不要,只要声明要运行哪个方法(静态方法类调用)
public class test {

    public static void main(String[] args) {
        List<String> test1 = new ArrayList<>();
        for(int i=0; i<10; i++){
            test1.add(i+"");
        }
        System.out.println(test1.toString());
        //lambda形式2
        test1.forEach(test::printTest);
    }

    static void printTest(String s){
        System.out.println(s+ " Hellow!");
    }
}
//lambda表达式的例子-lambda形式3--外部变量不要,只要声明要运行哪个方法(非静态方法实例调用)
public class test {

    public static void main(String[] args) {
        List<String> test1 = new ArrayList<>();
        for(int i=0; i<10; i++){
            test1.add(i+"");
        }
        System.out.println(test1.toString());

        test1.forEach(new test()::printTest);
    }

    void printTest(String s){
        System.out.println(s+ " Hellow!");
    }
}
//lambda表达式的例子-lambda形式4--允许指定一个参数类型里的一个方法作为方法的引用
public class test {

    public static void main(String[] args) {
        List<String> test1 = new ArrayList<>();
        for(int i=0; i<10; i++){
            test1.add(i+"");
        }
        System.out.println(test1.toString());

        test1.forEach(String::hashCode);
    }
}
例子2
//例子2
public class test {

    public static void main(String[] args) {
        Map<String, String> test1 = new HashMap<>();
        for(int i=0; i<=3; i++){
            test1.put(i+"", 3-i+"");
        }
        test1.forEach((k,v)->{
            System.out.println(k+" 天啊!");
            System.out.println(v+" 牛逼!");
        });
    }
}

17,泛型

  • https://www.runoob.com/java/java-generics.html
  • 泛型(generics),是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
  • 可以在方法中定义泛型,generic method
    • 方法泛型的确认依据:
      • 如果方法返回值定义为E,根据方法调用时准备赋值变量的类型确定。
      • 如果方法返回值定义不是E,根据方法调用时,对应的传入数据的类型确定。
    • 定义:static public <E> void myMethod(Object input){ ...; return (E) output;} E可以作为方法返回类型(比如代替这里的void),方法传入数据类型(比如代替这里的Object),以及方法体局部变量的类型
    • 调用:String test = myMethod(...),这里E就是test的类型String(这样做的用处之一就是可以进行强制类型转换)
  • 可以在类/接口中定义泛型,generic types
    • 类的泛型通过<>进行传递,泛型可以一次定义多个
    • 定义:public myClass<E1, E2> {private E1 val1; private E2 val2; public myClass(E1 val1, E2 val2){this.val1=val1; this.val2=val2;}}
    • 调用:myClass<String, Integer> test = new myClass<>("123", 123);
  • 泛型在定义的时候可以指定泛型的边界:public myClass<E1 extends testClass, E2>{}
    • 这样E1局部变量就可以使用testClass的方法,传入的数据也必须是testClass类或其子类
  • Java泛型中的类型擦除
  • 泛型的协变和逆变
    • 协变和逆变是针对引用类型而言的,可以用在返回值类型、形参类型等引用类型上。在创建对象的时候,不能使用协变和逆变(用了就无法传入参数)。
    • 意义:让参数和返回值等引用类型的泛型类型更加灵活。
    • 写入形参使用逆变,读取形参使用协变。????
    • 一组例子
import java.util.ArrayList;

public class test {
    public static void main(String[] args) {
        grandParent myGrandParent = new grandParent(20);
        parent myParent = new parent(18, "123");

        //test1
        ArrayList<parent> myList = new ArrayList<>();
        myList.add(myParent);
        // myList.add(myGrandParent);   //myGrandParent是myParent的父类对象,但是不能add

        //test2
        ArrayList<grandParent> myList1 = new ArrayList<>();
        myList1.add(myGrandParent);
        testFunc(myList1);
        //testFunc(myList); //myList中元素是myList1的子类,但是传入函数依旧报错
        //这么传入参数是不行的,泛型类型不管继承关系,只管严格匹配
        //parent是grandParent的子类,但是 ArrayList<parent> 不是 ArrayList<grandParent> 的子类。

        //test3
        //如何解决test2中 testFunc(myList); 这样的问题呢?这里需要将方法中的形参声明为协变泛型
        //声明方法就是 testFunc1(ArrayList<? extends grandParent> input)
        //协变-协同变化:实参泛型的类型可以是形参泛型类型的同类或子类
        testFunc1(myList1);
        testFunc1(myList);

        //test4
        ArrayList<? extends grandParent> myList2 = new ArrayList<>();
        //myList2.add(new parent());    //使用协变泛型声明变量后,竟然无法传入参数!
        //myList2.add(new grandParent());   //使用协变泛型声明变量后,竟然无法传入参数!
        //原因很复杂,如下:
        myList2 = myList;   //这是允许的
        //myList2.add(new grandParent());   //如果这句允许的话,使用parent类型遍历myList时会出现一个grandParent对象,也就是声明类是对象类的子类了(子类指向了父类),这是不允许的
        //那么为什么 myList2.add(new parent()); 也不行呢?

        //test5
        //除了协变,java还有逆变
        //逆变刚好相反,实参泛型的类型可以是形参泛型类型的同类或父类
        //同样的,声明变量则无法add具体的对象
        testFunc2(myList);
        testFunc2(myList1);
    }

    static public void testFunc(ArrayList<grandParent> input){
        System.out.println("done");
    }

    static public void testFunc1(ArrayList<? extends grandParent> input){
        System.out.println("done");
    }

    static public void testFunc2(ArrayList<? super parent> input){
        System.out.println("done");
    }
}

  • 要用泛型可想好了代码的扩展,如果没那个能力将类设计好,就不要用了。。。

18,Iterator 和 Iterable

19,注解

  • 注解的英文名是annotation,是给类、方法以及成员变量等元素增加元数据(metadata:描述或修饰元素的,比如权限修饰符、final等)的方法。
  • 和注释不同,注解的描述会被java编译器处理而非跳过。
  • 常见的注解
@Override  //重写,实现或覆盖了父类的方法
@Deprecated  //标明方法已弃用,但是要用也可以
  • 自定义注解
    • 注解只是一种metadata传递的渠道,本身没有实际功能
    • 注解背后的具体的功能,还要用代码读取注解,然后根据注解实现相应的功能(注解只是传递数据)。注解在Spring、测试等框架中被广泛使用。
    • 注解是通过反射后 getAnnotations() 读取的
  • 自定义注解及使用的例子
//==============================1-自定义注解==============================
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

// TODO 定义注解可以用在什么元素上?(属性、方法)
//@Target(ElementType.METHOD)
@Target({ElementType.FIELD, ElementType.METHOD})    //多个要用{}括起来

// TODO 定义注解会被保留到哪个阶段(SOURCE, CLASS, RUNTIME),SOURCE只保留在源代码编译时用一下,CLASS会保留到class文件中,RUNTIME则在运行阶段也可以取到值
@Retention(RetentionPolicy.RUNTIME)

// TODO 自定义注解,使用 @interface 标注(此类接口默认继承 Annotation 接口)
public @interface myAnnotation {
    // TODO Annotation 支持的类型有基本数据类型、Class、String、枚举、其他枚举、基于以上类型的数组
    // TODO 可以指定缺省值
    String defaultValue() default "N/A";

    Class targetClass();

    int abc();

    String[] defaultValues();

    // TODO 注解类型的缺省值
    Override is() default @Override;
}

//==============================2-使用自定义注解的类==============================
public class testForMyAnnotation {

    @myAnnotation(defaultValue = "abc", targetClass = testForMyAnnotation.class,
            abc = 1, defaultValues = {"123", "456"})    //注解中没有默认的都要传值
    public void testFunc(){
        System.out.println("test");
    }
}

//==============================3-使用2中创建的类读取注解中的数据==============================
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;

public class useMyAnnotation {
    public static void main(String[] args) throws NoSuchMethodException {
        Class clazz = testForMyAnnotation.class;
        Method method = clazz.getDeclaredMethod("testFunc");

        // todo 遍历所有的注解
        for(Annotation annotation: method.getAnnotations()){
            System.out.println(annotation.annotationType());
        }

        // todo 读取注解的内容,可以根据注解的内容,作为相应功能的配置,以实现相应的功能
        myAnnotation anno1 = method.getAnnotation(myAnnotation.class);
        System.out.println(anno1.defaultValue());
        System.out.println(anno1.targetClass());
        System.out.println(anno1.abc());
        System.out.println(Arrays.asList(anno1.defaultValues()).toString());
        System.out.println(anno1.is());
    }
}

20,文件操作

20-1,文件和文件夹的创建、重命名、删除

  • 使用File类可以判断一个路径File *** = new File(***, ***);
    • 是不是文件***.isFile()
    • 是不是文件夹***.isDirectory()
    • 存不存在***.exists()
    • 创建文件夹***.mkdir()
    • 重命名文件夹***.renameTo(***)
    • 删除文件夹***.delete()
    • 创建文件***.createNewFile()
    • 重命名文件***.renameTo(***)
    • 删除文件***.delete()
  • 例子
// linux和windows上的路径分割符,和环境变量分割符不同,如果要写跨平台的程序,可以用下面两个变量
File.seperator
File.pathSeperator

//创建文件夹
public class test {

    private static final String ROOT = "." + File.separator;
    private static Scanner sc = new Scanner(System.in);

    public static void main(String[] args) throws IllegalAccessException {
        File dir = new File(ROOT, "test1");   //1
        if(dir.isDirectory()){
            System.out.println("文件夹已存在!");
            return;
        }else{
            boolean createSuccess = dir.mkdir();    //2
            if(createSuccess){
                System.out.println("创建成功!");
            }else{
                throw new IllegalAccessException("无法在 " + ROOT + " 下创建目标文件夹");
            }
        }
    }
}

//重命名文件夹
public class test {

    private static final String ROOT = "." + File.separator;

    public static void main(String[] args) throws IllegalAccessException {
        File dir = new File(ROOT, "test1");   //1
        File dir2 = new File(dir.getParent(), "test2");  //2
        dir.renameTo(dir2);  //3
    }
}

//删除文件夹
public class test {

    private static final String ROOT = "." + File.separator;

    public static void main(String[] args){
        File dir1 = new File(ROOT, "test1"); //1
        dir1.delete();  //2
    }
}

//创建文件
public class test {

    private static final String ROOT = "." + File.separator;

    public static void main(String[] args) throws IOException {
        File dir1 = new File(ROOT, "test1"); //1
        File file1 = new File(dir1, "file1.txt");  //2
        file1.createNewFile();  //3
    }
}

//重命名文件
public class test {

    private static final String ROOT = "." + File.separator;

    public static void main(String[] args) throws IOException {
        File dir1 = new File(ROOT, "test1"); //1
        File file1 = new File(dir1, "file1.txt");   //2
        File file2 = new File(dir1, "file2.txt");   //3
        file1.renameTo(file2);  //4
    }
}

//删除文件
public class test {

    private static final String ROOT = "." + File.separator;

    public static void main(String[] args){
        File dir1 = new File(ROOT, "test1"); //1
        File file2 = new File(dir1, "file2.txt");   //2
        file2.delete();  //3
    }
}

20-2,java IO-文件读写

  • java中有三代IO框架,分别是:
    • 第一代流式阻塞IO(Blocking IO)
    • 第二代NIO(New IO),是非阻塞的
    • 第三代NIO2(或者叫AIO,async IO),又进一步支持了异步IO
  • 如果是高吞吐量的生产环境下,建议使用更加优秀的netty作为IO框架
  • 四个重要的接口
    • 输入流(直接连接数据源头,与Byte数组进行数据交换,因为文件存储到计算机上都是Byte数组的形式(字符在硬盘上对应特定编码格式的字节))
      • InputStream <- FileInputStream
      • InputStream <- FilterInputStream <- BufferedInputStream
      • InputStream <- ByteArrayInputStream
    • 输出流(直接连接数据目的地,与Byte数组进行数据交换)
      • OutputStream <- FileOutputStream
      • OutputStream <- FilterOutputStream <- BufferedOutputStream
      • OutputStream <- ByteArrayOutputStream
    • 输入字符流(与字符进行数据交换,通过InputStream输入流 连接到数据源头)
      • Reader <- BufferedReader
      • Reader <- InputStreamReader
    • 输出字符流(与字符进行数据交换,通过OutputStreamWriter输处流 连接到数据目的地)
      • Writer <- PrintWriter
      • Writer <- BufferedWriter
      • Writer <- OutputStreamWriter
  • 例子
//最简略的写法
public class test3 {
    public static void main(String[] args) throws IOException {
        PrintWriter pw = new PrintWriter("myfile.txt", "UTF-8");
        pw.println("你好");
        pw.flush();

        Scanner sc = new Scanner(Paths.get("myfile.txt"), "UTF-8");
        System.out.println(sc.nextLine());
    }
}
//写入文件
public class test {

    private static final String ROOT = "." + File.separator;

    public static void main(String[] args) throws IOException {
        File file = new File(new File(ROOT, "test1"), "test1.txt");
        try(//使用try说明以下类都继承了Closeable接口
            //创建一个outputstream,建立一个程序到文件的byte数据传输流(可以写入byte数组)
            FileOutputStream fos = new FileOutputStream(file);
            //创建一个可以消费outputstream的writer,并指定字符集(可以写入字符,将字符转为UTF_8得byte的格式)
            OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
            //使用PrinterWriter可以方便得写入一行字符(提供了一些很便利的方法)
            PrintWriter pw = new PrintWriter(osw);
        ){
            pw.println("Hello world!");
            pw.flush();
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

//读取文件-经典方式
public class test {

    private static final String ROOT = "." + File.separator;

    public static void main(String[] args) throws IOException {
        File file = new File(new File(ROOT, "test1"), "test1.txt");
        try(//使用try说明以下类都继承了Closeable接口
            //创建一个InputStream,建立一个文件到程序的byte数据传输流(可以读取byte数组)
            FileInputStream fis = new FileInputStream(file);
            //创建一个可以消费InputStream的reader,并指定字符集(可以读取byte数组,将UTF_8格式的byte数组转为字符)(装饰)
            InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
            //使用BufferedReader增加缓存功能,效率更高,并且可以一次读取一行
            BufferedReader reader = new BufferedReader(isr);
        ){
            String line = null;
            while ((line = reader.readLine()) != null){
                System.out.println(line);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

//读取文件-stream流 + lambda
public class test {

    private static final String ROOT = "." + File.separator;

    public static void main(String[] args) throws IOException {
        File file = new File(new File(ROOT, "test1"), "test1.txt");
        try(//使用try说明以下类都继承了Closeable接口
            //创建一个InputStream,建立一个文件到程序的byte数据传输流(可以读取byte数组)
            FileInputStream fis = new FileInputStream(file);
            //创建一个可以消费InputStream的reader,并指定字符集(可以读取byte数组,将UTF_8格式的byte数组转为字符)(装饰)
            InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
            //使用BufferedReader增加缓存功能,效率更高,并且可以一次读取一行
            BufferedReader reader = new BufferedReader(isr);
        ){
            reader.lines().forEach(System.out::println);    //stream流
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

21,java开发调试程序相关的工具

  • javac:将源代码编程成class字节码文件。

  • java:运行编译后的class字节码文件。

  • javap:JDK自带的反汇编器,可以查看java编译器生成的字节码。

  • jar:JDK提供的一个用于生成JAR包的命令。JAR包是Java中特有的一种压缩文档,可以把它理解为.zip包,只是JAR包中有一个META-INF\MANIFEST.MF文件,当生成JAR包时,该文件会自动生成。可以通过java命令指定jar包路径及其内字节码文件进行运行。

  • jps:JDK提供的一个显示当前所有java进程pid的命令。

  • jmap:JDK提供的一个将内存(堆)使用的详细情况输出到文件的命令。

  • jhat:jhat也是jdk内置的工具之一,主要是用来分析java堆的命令,可以将堆中的对象以html的形式显示出来,包括对象的数量,大小等等,并支持对象查询语言。 使用jmap等方法生成java的堆文件后,使用其进行分析。

  • jstack:java虚拟机自带的一种栈跟踪工具,用于打印出给定的java进程ID或core file或远程调试服务的Java栈信息。

  • jstat:JDK自带的一个轻量级小工具,利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。jstat工具特别强大,有众多的可选项,详细查看堆内各个部分的使用量,以及加载类的数量。

  • Jvisualvm:JVisualVM是一个Java虚拟机的监控工具,功能非常强大,可以对堆内存进行dump、快照以及性能可视化分析,也可以安装插件对堆外内存进行分析。

  • JDK自带工具文档地址:

22,日志-logger

  • 工作环境中没有控制台,exception等异常信息就需要保存到日志文件中进行分析。
  • Java Logger使用
//初始化流程
Logger logger = new Logger("com");
logger.setLevel(Level.SEVERE);  //设置日志级别,从高到低:SEVERE,WARNING,INFO,CONFIG,FINE,FINER,FINEST,ALL,OFF

ConsoleHandler consoleHandler =new ConsoleHandler();  //输出到控制台,Logger的Handler有:Handler MemoryHandler StreamHandler ConsoleHandler FileHandler SocketHandler
consoleHandler.setLevel(Level.ALL);
consoleHandler.setFormatter(new SimpleFormatter());  //默认为xml格式,Logger的Formatter有:Formatter SimpleFormatter XMLFormatter
logger.addHandler(consoleHandler);

FileHandler fileHandler = new FileHandler("test.log");  //输出到文件
fileHandler.setLevel(Level.ALL);
fileHander.setFormatter(new SimpleFormatter());
logger.addHandler(fileHandler);
//异常与日志相结合
public class test3 {
    public static void main(String[] args) throws IOException {
        Logger logger = Logger.getLogger("test.log");
        logger.setLevel(Level.INFO);
        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setLevel(Level.ALL);
        consoleHandler.setFormatter(new SimpleFormatter());
        logger.addHandler(consoleHandler);
        FileHandler fileHandler = new FileHandler("test.log");
        fileHandler.setFormatter(new SimpleFormatter());
        logger.addHandler(fileHandler);
        try{
            throw new NullPointerException("测试:nullpointer异常");
        }catch (NullPointerException ex){
            logger.warning(ex.getMessage());
        }
    }
}
//自定义Formatter
package com.xiya.test;
 
import java.io.IOException;
import java.util.Date;
import java.util.logging.*;
 
/**
 * Created by N3verL4nd on 2017/4/24.
 */
 
class LoggerFormatter extends Formatter {
 
    @Override
    public String format(LogRecord record) {
        return "[" + new Date() + "]" + " [" + record.getLevel() + "] "
                + record.getClass() + record.getMessage() + "\n";
    }
}
 
public class LogDemo {
    public static void main(String[] args) throws IOException {
        Logger logger = Logger.getLogger("com.xiya.test.LogDemo");
        logger.setLevel(Level.ALL);
        ConsoleHandler consoleHandler = new ConsoleHandler();
        logger.addHandler(consoleHandler);
        FileHandler fileHandler = new FileHandler("testLog.log");
        fileHandler.setFormatter(new LoggerFormatter());
        logger.addHandler(fileHandler);
        logger.info("hi");
    }
}

23,强引用、软引用、弱引用、虚/幻象引用、引用队列

  • V1
  • V2
    • 在Java语言中,除了基本数据类型外,其他的都是指向各类对象的对象引用;Java中根据其生命周期的长短,将引用分为4类。
    • 强引用:我们平常典型编码Object obj = new Object()中的obj就是强引用。通过关键字new创建的对象所关联的引用就是强引用。 当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。
    • 软引用:软引用通过SoftReference类实现。 软引用的生命周期比强引用短一些。只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象:即JVM 会确保在抛出OutOfMemoryError之前,清理软引用指向的对象。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueuepoll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。
      • 应用场景:软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
    • 弱引用:弱引用通过WeakReference类实现。 弱引用的生命周期比软引用短。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
      • 应用场景:弱应用同样可用于内存敏感的缓存。
    • 虚引用:虚引用也叫幻象引用,通过PhantomReference类来实现。无法通过虚引用访问对象的任何属性或函数。幻象引用仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取一些程序行动。
      • 应用场景:可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收之前会收到一条系统通知。
  • 一个有关reachability的例子,也是reachabilityFence的一个使用,清楚说明对方法的调用并不能保证对象存活。
//例子1
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;

public class test4 {
    public static void main(String[] args) {
        myClass myclass = new myClass(1);
        ReferenceQueue referenceQueue = new ReferenceQueue<>();
        PhantomReference<myClass> p = new PhantomReference<>(myclass, referenceQueue);
        myclass = null; //引用被清理
        System.gc();    //垃圾回收
        try{
            Reference<myClass> ref = referenceQueue.remove(1000L);  //remove是阻塞方法,1000L是指定的timeout
            if(ref!=null){
                myclass = ref.get();
                System.out.println(myclass);
            }
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
}

class myClass{
    int a;

    public myClass(){}
    public myClass(int a){
        this.a=a;
    }
}
//例子2
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

public class test5 {
    public static void main(String[] args) throws InterruptedException {
        SavePoint savePoint = new SavePoint("Random"); // 创建一个强引用
        ReferenceQueue<SavePoint> savepointQ = new ReferenceQueue<SavePoint>();//引用队列
        WeakReference<SavePoint> savePointWRefernce = new WeakReference<SavePoint>(savePoint, savepointQ);
        System.out.println("SavePoint 被作为一个弱引用来创建" + savePointWRefernce);
        Runtime.getRuntime().gc();
        System.out.println("在引用队列中存在引用型对象吗 ? " + (savepointQ.poll() != null));
        savePoint = null; // 唯一的强引用被删除掉,在堆中的对象现在只具有弱可达性
        System.out.println("现在调用GC");
        Runtime.getRuntime().gc(); // 对象会在这里被回收掉,finalize方法会被调用
        System.out.println("在引用队列中有任何弱引用吗? " + (savepointQ.remove() != null));
        System.out.println("弱引用型对象还引用着堆中的对象吗?" + (savePointWRefernce.get() != null));   //有弱引用但是已经不引用堆中的对象了
        System.out.println("弱引用型对象被添加到引用队列中了吗?" + (savePointWRefernce.isEnqueued()));
    }
}

class SavePoint {
    String a;

    public SavePoint() {
    }
    public SavePoint(String a) {
        this.a = a;
    }
}
//例子3
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

public class test6 {
    public static void main(String[] args) throws InterruptedException {
        SavePoint savePoint = new SavePoint("Random"); // 创建一个强引用
        ReferenceQueue<SavePoint> savepointQ = new ReferenceQueue<SavePoint>();// 引用队列
        WeakReference<SavePoint> savePointWRefernce = new WeakReference<SavePoint>(savePoint, savepointQ);
        System.out.println("在队列中有任何弱引用吗? " + (savepointQ.poll() != null));
        savePoint = null;
        System.out.println("现在调用GC...");
        Runtime.getRuntime().gc(); // 对象在这里会被清除掉 - finalize方法会被调用
        Reference<? extends SavePoint> reCreatedSavePoint = savepointQ.remove();
        System.out.println("在队列中存在任何弱引用吗 ? " + (reCreatedSavePoint != null));
        System.out.println("这个引用和原先的弱引是同一个对象吗 ? " + (reCreatedSavePoint == savePointWRefernce));
        System.out.println("堆中对象是: " + reCreatedSavePoint.get());
    }
}

24,动态代理

  • 动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类似机制做到的,比如用来包装 RPC 调用、面向切面的编程(AOP)。
  • 实现动态代理的方式很多,比如 JDK 自身提供的动态代理,就是主要利用了反射机制。还有其他的实现方式,比如利用传说中更高性能的字节码操作机制,类似 ASM、cglib(基于 ASM)、Javassist 等。
  • 代理: 为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在两者之间起到中介的作用;动态代理,就是实现阶段不用关心代理谁,而是在运行阶段才指定代理哪个一个对象(不确定性)。如果是自己写代理类的方式就是静态代理(确定性)。
  • (动态)代理模式主要涉及三个要素:
    • 其一:抽象类接口
    • 其二:被代理类(具体实现抽象接口的类)
    • 其三:动态代理类:实际调用被代理类的方法和属性的类
  • 例子
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyDynamicProxy {
    public static  void main (String[] args) {
        HelloImpl hello = new HelloImpl();
        MyInvocationHandler handler = new MyInvocationHandler(hello);
        // 构造代码实例
        Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), handler);
        // 调用代理方法
        proxyHello.sayHello();
    }
}
interface Hello {
    void sayHello();
}
class HelloImpl implements  Hello {
    @Override
    public void sayHello() {
        System.out.println("Hello World");
    }
}
class MyInvocationHandler implements InvocationHandler {
    private Object target;
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("Invoking sayHello");
        Object result = method.invoke(target, args);
        return result;
    }
}

25,回调

  • 回调函数,简称回调,是指通过将函数参数传递到其他代码的某一块可执行代码的引用来调用该块可执行代码,这一设计允许了底层代码调用在高层定义的子程序。 简单来说:A调用了B的b ()方法,那b方法如果包含对A中实现的逻辑的调用,这个过程就被称作回调。
    *对于回调,不同的语言有不同的回调形式,例如:
    • C、C++ 允许将函数指针作为参数传递;
    • JavaScript、Python 允许将函数名作为参数传递;

25-1,Java 实现回调的四种写法

  • 反射:Java 的反射机制允许我们获取类的信息,其中包括类的方法。我们将以 Method 类型去获取回调函数,然后传递给请求函数。
import java.lang.reflect.Method;

public class test8 {
    public static void main(String[] args) throws Exception {
        Request request = new Request();
        System.out.println("[Main]:我开个线程去异步发请求");
        new Thread(() -> {
            try {
                request.send(CallBack.class, CallBack.class.getMethod("processResponse"));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
        System.out.println("[Main]:请求发完了,我去干点别的");
        Thread.sleep(1000);
    }
}

class Request{
    public void send(Class clazz, Method method) throws Exception {
        // 模拟等待响应
        Thread.sleep(300);
        System.out.println("[Request]:收到响应");
        method.invoke(clazz.newInstance());
    }
}

class CallBack {
    public void processResponse() {
        System.out.println("[CallBack]:处理响应");
    }
}
  • 直接调用:不使用反射,改为直接通过对象来调用方法。
    • 这种实现方式十分简单,但是存在的问题是不符合修改封闭原则。也就是说当我们想要换一种“处理响应”的方法时,将必须去修改 CallBack 类的 processRequest()方法。而如果将 CallBack 类改为接口,我们就可以仅更换 CallBack 的实现了。
public class test8 {
    public static void main(String[] args) throws Exception {
        Request request = new Request();
        System.out.println("[Main]:我开个线程去异步发请求");
        CallBack callBack = new CallBack();
        new Thread(() -> {
            try {
                request.send(callBack);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
        System.out.println("[Main]:请求发完了,我去干点别的");
        Thread.sleep(1000);
    }
}

class Request{
    public void send(CallBack callBack) throws Exception {
        // 模拟等待响应
        Thread.sleep(300);
        System.out.println("[Request]:收到响应");
        callBack.processResponse();
    }
}

class CallBack {
    public void processResponse() {
        System.out.println("[CallBack]:处理响应");
    }
}

  • 接口调用:更加符合修改封闭原则。
public class test8 {
    public static void main(String[] args) throws Exception {
        Request request = new Request();
        System.out.println("[Main]:我开个线程去异步发请求");
        CallBack callBack = new CallBackImpl(); //传入具体实现类的对象
        new Thread(() -> {
            try {
                request.send(callBack);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
        System.out.println("[Main]:请求发完了,我去干点别的");
        Thread.sleep(1000);
    }
}

class Request{
    public void send(CallBack callBack) throws Exception {
        // 形参声明接口
        // 模拟等待响应
        Thread.sleep(300);
        System.out.println("[Request]:收到响应");
        callBack.processResponse();
    }
}

interface CallBack {
    public void processResponse();
}

class CallBackImpl implements CallBack{
    @Override
    public void processResponse(){
        System.out.println("[CallBack]:处理响应");
    }
}
  • Lambda表达式:既不用去新增实现类,也不用去实例化,只需要定义接口并传递 Lambda 表达式就可以完成回调。
public class test8 {
    public static void main(String[] args) throws Exception {
        Request request = new Request();
        System.out.println("[Main]:我开个线程去异步发请求");
        new Thread(() -> {
            try {
                request.send(()->{
                    System.out.println("[CallBack]:处理响应");
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
        System.out.println("[Main]:请求发完了,我去干点别的");
        Thread.sleep(1000);
    }
}

class Request{
    public void send(CallBack callBack) throws Exception {
        // 形参声明接口
        // 模拟等待响应
        Thread.sleep(300);
        System.out.println("[Request]:收到响应");
        callBack.processResponse();
    }
}

interface CallBack {
    public void processResponse();
}

26,借助LinkedHashMap删除最旧的条目

import java.util.LinkedHashMap;
import java.util.Map;

public class test {
    public static void main(String[] args) {
        LinkedHashMap<String, String> accessOrderedMap = new LinkedHashMap<String, String>(16, 0.75F, true){
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, String> eldest) { // 实现自定义删除策略,否则行为就和普遍Map没有区别
                return size() > 3;  //最旧条目就是链表首(按存入时间来算),这里只是触发删除时应满足的条件
            }
        };
        accessOrderedMap.put("Project1", "Valhalla");
        accessOrderedMap.put("Project2", "Panama");
        accessOrderedMap.put("Project3", "Loom");
        accessOrderedMap.forEach( (k,v) -> {
            System.out.println(k +":" + v);
        });
        // 模拟访问
        accessOrderedMap.get("Project2");
        accessOrderedMap.get("Project2");
        accessOrderedMap.get("Project3");
        System.out.println("Iterate over should be not affected:");
        accessOrderedMap.forEach( (k,v) -> {
            System.out.println(k +":" + v);
        });
        // 触发删除
        accessOrderedMap.put("Project4", "Mission Control");
        System.out.println("Oldest entry should be removed:");
        accessOrderedMap.forEach( (k,v) -> {// 遍历顺序不变
            System.out.println(k +":" + v);
        });
    }
}

27,以.为split分割符

String s = "a.b.c";
String[] sSplit = s.split("\\.");
System.out.println(Arrays.toString(sSplit));
posted @ 2021-08-28 14:09  tensor_zhang  阅读(140)  评论(0)    收藏  举报