文件/字符串/反射/泛型

文件

新的JDK提供了很多好用的文件处理API,在小文件场景下,不需要写繁琐的流文件处理过程了。

新API

主要集中在Files和Paths两个类的静态方法中(jdk大部分工具类方法都会在原类上加一个s,比如Strings、Collections)。

  1. 获取这个路径是否有Posix许可 Files.getPosixFilePermission(Path p);
    Posix 可移植操作系统接口,是IEEE为unix上运行软件,定义的一系列api总称。

  2. 走查文件树,这个方法帮我们快速访问文件树。
    可以定义从指定目录开始,文件访问选项,最大递归深度,文件访问方法。

Params:
start – the starting file
options – options to configure the traversal
maxDepth – the maximum number of directory levels to visit
visitor – the file visitor to invoke for each file
Returns:
the starting file

    public static Path walkFileTree(Path start,
                                    Set<FileVisitOption> options,
                                    int maxDepth,
                                    FileVisitor<? super Path> visitor)
  1. WatchService 可以监听某个目录下的所有文件操作,仅限于指定目录,不包括其子目录。

  2. PathMatcher 查找目录,可以按照glob(linux的简化正则)或者 regex 来匹配查找指定文件和目录。

  3. Files.readAllLines(Paths.get()),读整个文件到内存中。

  4. Files.write(Paths.get(),content),将内容直接写入某个文件中。

  5. Stream<> strem = Files.line() 按行读取文件内容,直接生成流。 这个好用,且他还是按行读取的,性能好。

文件系统

1.文件或目录的路径
2.文件本身

字符串

  1. 使用javap -c xxx.class文件,来查看代码实际流程
    这个命令可以来反编译class文件,获得jvm的汇编语言,查看代码原始行为。

  2. 也可以用idea的获取字节码文件功能。

  3. StringBuffer和StringBuilder

  4. String.format,其内部就是实例化一个Formatter(它是一个java.util中的格式化工具类),并将传入的参数直接给它。(要注意String.format性能是比较差的,但是可读性强)

反射

反射可以让我们在运行时发现和使用类的信息。

Class对象

在java中每个类都有个Class对象,每次编写并编译一个类时,都会生成一个Class对象(并被相应的存储在同名的.class文件中),为了生成这个对象,jvm会使用一个类加载器的子系统。

类在首次使用时才会被加载到JVM中,当程序第一次使用该类的静态变量时,就会触发这个类的加载。当我们new一个对象时,其实调用了其构造器,而构造器是类的一个隐式静态方法。

常用API

反射的大部分API都是基于Class实现的。

  1. Class.forName("") 根据类绝对地址获取类信息,如果获取不到会抛出类找不到异常。

  2. Class.isInterface()

  3. Class.getSimpleName() 不带包名的类名

  4. Class.getCanonicalName() 带包名完全限定类名

  5. Class.getInterface() 获取接口的数组

泛型类的使用

泛型为我们日常编写通用型工具提供了可能,可以接收不同的对象类型来做相同的事情。

常见用法

  1. <?> 非具体类,表示允许任意类型的类使用。

  2. <? extends Number> <T extends Number> 上界限泛型,表示使用的类限定是Number的子类或者Number。
    解释:为了让类必须具有父类的行为时才会用,限定了该类是Number的子类,编译器会为其提供Number的行为和属性。
    使用场景:实现某种公共方法,或者声明在集合中,用来确保其公共方法可以调用。
    适用:泛型容器声明、静态泛型方法实现、泛型类实现。

        List<? extends Number> test1 = new ArrayList<>();
        test1.add(300);//编译错误,extends只规定了该类是Number子类,并不确定是不是Integer。
        test1.get(0).byteValue();//编译正确,可以确保这个子类一定有Number的byteValue方法。

        List<? super Number> test2 = new ArrayList<>();
        test2.add(300);//编译正确,super规定了该类是Number的父类,那Integer作为Number的子类,当然可以放到这个集合中。
        test2.get(0).byteValue();//编译错误,不能确定这个类是否有Number的byteValue行为。
  1. <? super Number> <T super Number> 下界限泛型类,表示该类限定是Number的超类或者Number。
    解释:这种用法不能获取类的使用方法,限定了该类型是Number的超类后,编译器允许其利用多态接收Number或者的子类。
    使用场景:这个使用场景比较复杂,它目的是让代码写法更灵活,且让编译器能够检查框架代码,避免可能出现的错误。
    适用:只适用于泛型容器声明,或者结合 extends一起使用。

super能够使代码写法更加灵活,以下几个Demo说明了这一点::
Demo1,Cat希望能够与任何Animal的子类进行比较,Cat和Dog也可以比较年龄大小。
这个有super的正常用法,compareTo编译无告警,因为compareTo本身就可以接收T或T的子类。

     @Data
    public static class Animal{
        protected Integer age;

    }

    @Data
    public static class Cat extends Animal implements Comparable<Animal> {
        public Cat(Integer age) {
            this.age  = age;
        }

        @Override
        public int compareTo(Animal animal) {
            return this.age-animal.age;
        }
    }

    /**
     * 比较两个元素的大小
     * @param t1 元素1
     * @param t2 元素2
     * @param <T> T必须实现Comparable,可以比较;且Comparable必须可以接收T或T的子类。
     */
    public static <T extends Comparable<? super T>> void compare(T t1,T t2){
        int i = t1.compareTo(t2);//无告警,compareTo的调用合法
        System.out.println(i);
    }


    public static void main(String[] args) {
        compare(new Cat(20),new Cat(21));//运行无误
    }

Demo2,假设我们没法用super,意味着我们只有两个选择:
a. 使用无类型的Comparable,这意味着类型没有检查,框架编写者可以用t1.compareTo(new Object()),编译没问题,运行中强转对象就会出错,不可取。
b. 在Demo1的基础上我们将Comparable改为仅支持T(如Demo2),此时编译器报错,我们只能将Cat改为实现Comparable,此时Cat只能与Cat进行比较,无法与同为Animal的子类进行比较,丧失一部分灵活性。
Demo2

@Data
    public static class Animal{
        protected Integer age;

    }

    @Data
    public static class Cat extends Animal implements Comparable<Animal> {
        public Cat(Integer age) {
            this.age  = age;
        }

        @Override
        public int compareTo(Animal animal) {
            return this.age-animal.age;
        }
    }

    /**
     * 比较两个元素的大小
     * @param t1 元素1
     * @param t2 元素2
     * @param <T> T必须实现Comparable,可以比较;且Comparable必须可以接收T和T的子类。
     */
    public static <T extends Comparable<T>> void compare(T t1,T t2){
        int i = t1.compareTo(t2);
        System.out.println(i);
    }


    public static void main(String[] args) {
        compare(new Cat(20),new Cat(21));//编译器报错,Comparable<Animal>不合法,只能是Comparable<Cat>类型
    }
  1. T和?区别在于T可以将泛型变量化,进行实体调用,?只是一种声明式的限定。

  2. cast()强转,Cat.class.cast(new Object())将Object强转为Cat;

  3. 几种类型判断方式和效果
    如果 Cat extends Animal
    cat instanceof Animal = true //cat实例是否属于Animal类
    Animal.isInstance(cat) = true //Animal是否认为cat是自己类型的实例
    cat.getClass()==Animal.class = false //Cat类和Animal类是否等价
    cat.getClass().equals(Animal.class) = false //Cat类和Animal类是否等价

动态代理

静态代理=>动态代理=>cglib(字节码技术通过继承实现)和jdk动态代理(反射实现一定要配置一个接口)

java泛型和C++的对比

对比C++模板的初衷

对C++模板(泛型的主要灵感以及基本语法的来源)某些方面的理解有助于掌握泛型概念的基础,以及非常重要的一点——理解java泛型的限制和这些限制存在的原因

简单泛型

泛型最重要的初衷之一,就是用于创建集合。

元组库:java方法只能返回一个对象,你可以定一个数据传输对象,包括多个泛型,用该对象做容器传输多个返回值。

Set工具类:set.addAll(set) 并集、set.retainAll(set) 交集、set.removeAll(set) 差集。

类型擦除

下面案例说明了Java对泛型的处理方式:
ArrayList<Integer>.class = ArrayList<String>.class 两个类是完全相同的。
实际上,在泛型内部,从未定义任何与具体类相关的内容,你只能得到一个T K V这种泛型字符,所以List<String>List<Integer>对JVM来说是完全一样的。

而在C++中,可以写这种代码:

tempalte<class T> class Manipulator{
  T obj;
public:
  Manipulator(T x){obj = x;}
  void manipulate(){ obj.f();}
}

class Hasf{
public:
  void f(){cout << "HasF::f()" << endl;}
}

int main(){
  HasF hf;
  Manipulator<Hasf> manipulator(hf);
  manipulator.manipulate();
}

C++在编译时会推断Manipulator中对象实例化后实际是HasF类型,就可以调用其方法f(),从而编译不会报错。
既然C++可以得到对象具体类型,当然也能根据类型走不同方法模板:

// 模板方法
template <typename T>
bool Equal(T a, T b) {
    return a == b;
}
// 特化版本
template <>
bool Equal<char*>(char* a, char* b) {
    return (strcmp(a, b) == 0);
}

这个代码表示当T为char类型时,是无法用==判断相等的,所以走下面的特化版本方法来判断。而编译器在静态编译时期就会检查程序正确性。(当然有些只有运行时才能检查的错误状态需要额外的代码来识别)

而在Java一般泛型中,我们无法知道任何对象相关的内容,所以只有Object自带的方法可以调用,类似toString这种。

在java里可以使用<Son extends Father>来使其具有某些方法和特性,而编译器也会使用类型擦除技术,将Son类型擦除为Father父类类型,我们就可以认为它就是Father类型。

为什么要用类型擦除

很明显,C++的具体化泛型可以对泛型对象的方法和参数做检查,用法更灵活,而Java的泛型限制十足。

Java这么设计是为了保证向后兼容性,在java5之前,并没有泛型存在,所以很多程序和库都是直接用的Object obj来接受参数,然后使用强转使用,编译器也不会检查类型。
如果Java设计的泛型和C++一致,编译器就可以对类型做检查,每个对象都应该可以明确类型,强转方式就不能使用,很多库会直接报错,为了保证历史库的兼容性,java决定将新的泛型设计为无类型的类,也就是Object,而新泛型实现的代码效果必须强转效果完全一致,才能保证兼容性。

我们可以尝试看下以下两个类的字节码文件:
泛型类:

public class GenericHolder <T>{
    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    public static void main(String[] args) {
        GenericHolder<String> genericHolder = new GenericHolder<>();
        genericHolder.setT("泛型");
        String t = genericHolder.getT();

    }
}

字节码文件:

// class version 52.0 (52)
// access flags 0x21
// signature <T:Ljava/lang/Object;>Ljava/lang/Object;
// declaration: com/example/mydemo/multiple/data/config/T/GenericHolder<T>
public class com/example/mydemo/multiple/data/config/T/GenericHolder {

  // compiled from: GenericHolder.java

  // access flags 0x2
  // signature TT;
  // declaration: t extends T
  private Ljava/lang/Object; t

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 9 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/example/mydemo/multiple/data/config/T/GenericHolder; L0 L1 0
    // signature Lcom/example/mydemo/multiple/data/config/T/GenericHolder<TT;>;
    // declaration: this extends com.example.mydemo.multiple.data.config.T.GenericHolder<T>
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  // signature ()TT;
  // declaration: T getT()
  public getT()Ljava/lang/Object;
   L0
    LINENUMBER 13 L0
    ALOAD 0
    GETFIELD com/example/mydemo/multiple/data/config/T/GenericHolder.t : Ljava/lang/Object;
    ARETURN
   L1
    LOCALVARIABLE this Lcom/example/mydemo/multiple/data/config/T/GenericHolder; L0 L1 0
    // signature Lcom/example/mydemo/multiple/data/config/T/GenericHolder<TT;>;
    // declaration: this extends com.example.mydemo.multiple.data.config.T.GenericHolder<T>
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  // signature (TT;)V
  // declaration: void setT(T)
  public setT(Ljava/lang/Object;)V
    // parameter  t
   L0
    LINENUMBER 17 L0
    ALOAD 0
    ALOAD 1
    PUTFIELD com/example/mydemo/multiple/data/config/T/GenericHolder.t : Ljava/lang/Object;
   L1
    LINENUMBER 18 L1
    RETURN
   L2
    LOCALVARIABLE this Lcom/example/mydemo/multiple/data/config/T/GenericHolder; L0 L2 0
    // signature Lcom/example/mydemo/multiple/data/config/T/GenericHolder<TT;>;
    // declaration: this extends com.example.mydemo.multiple.data.config.T.GenericHolder<T>
    LOCALVARIABLE t Ljava/lang/Object; L0 L2 1
    // signature TT;
    // declaration: t extends T
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x9
  public static main([Ljava/lang/String;)V
    // parameter  args
   L0
    LINENUMBER 21 L0
    NEW com/example/mydemo/multiple/data/config/T/GenericHolder
    DUP
    INVOKESPECIAL com/example/mydemo/multiple/data/config/T/GenericHolder.<init> ()V
    ASTORE 1
   L1
    LINENUMBER 22 L1
    ALOAD 1
    LDC "\u6cdb\u578b"
    INVOKEVIRTUAL com/example/mydemo/multiple/data/config/T/GenericHolder.setT (Ljava/lang/Object;)V
   L2
    LINENUMBER 23 L2
    ALOAD 1
    INVOKEVIRTUAL com/example/mydemo/multiple/data/config/T/GenericHolder.getT ()Ljava/lang/Object; ====>>这里
    CHECKCAST java/lang/String ====>>这里
    ASTORE 2
   L3
    LINENUMBER 25 L3
    RETURN
   L4
    LOCALVARIABLE args [Ljava/lang/String; L0 L4 0
    LOCALVARIABLE genericHolder Lcom/example/mydemo/multiple/data/config/T/GenericHolder; L1 L4 1
    // signature Lcom/example/mydemo/multiple/data/config/T/GenericHolder<Ljava/lang/String;>;
    // declaration: genericHolder extends com.example.mydemo.multiple.data.config.T.GenericHolder<java.lang.String>
    LOCALVARIABLE t Ljava/lang/String; L3 L4 2
    MAXSTACK = 2
    MAXLOCALS = 3
}

强转类:

/**
 * @program: mydemo
 * @description:
 * @author: Mr.LaiHM
 * @create: 2022-11-13 18:17
 **/
public class SimpleHolder {
    private Object t;

    public Object getT() {
        return t;
    }

    public void setT(Object t) {
        this.t = t;
    }

    public static void main(String[] args) {
        SimpleHolder simpleHolder = new SimpleHolder();
        simpleHolder.setT("泛型");
        String t = (String)simpleHolder.getT();

    }
}

字节码文件:

// class version 52.0 (52)
// access flags 0x21
public class com/example/mydemo/multiple/data/config/T/SimpleHolder {

  // compiled from: SimpleHolder.java

  // access flags 0x2
  private Ljava/lang/Object; t

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 9 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/example/mydemo/multiple/data/config/T/SimpleHolder; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public getT()Ljava/lang/Object;
   L0
    LINENUMBER 13 L0
    ALOAD 0
    GETFIELD com/example/mydemo/multiple/data/config/T/SimpleHolder.t : Ljava/lang/Object;
    ARETURN
   L1
    LOCALVARIABLE this Lcom/example/mydemo/multiple/data/config/T/SimpleHolder; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public setT(Ljava/lang/Object;)V
    // parameter  t
   L0
    LINENUMBER 17 L0
    ALOAD 0
    ALOAD 1
    PUTFIELD com/example/mydemo/multiple/data/config/T/SimpleHolder.t : Ljava/lang/Object;
   L1
    LINENUMBER 18 L1
    RETURN
   L2
    LOCALVARIABLE this Lcom/example/mydemo/multiple/data/config/T/SimpleHolder; L0 L2 0
    LOCALVARIABLE t Ljava/lang/Object; L0 L2 1
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x9
  public static main([Ljava/lang/String;)V
    // parameter  args
   L0
    LINENUMBER 21 L0
    NEW com/example/mydemo/multiple/data/config/T/SimpleHolder
    DUP
    INVOKESPECIAL com/example/mydemo/multiple/data/config/T/SimpleHolder.<init> ()V
    ASTORE 1
   L1
    LINENUMBER 22 L1
    ALOAD 1
    LDC "\u6cdb\u578b"
    INVOKEVIRTUAL com/example/mydemo/multiple/data/config/T/SimpleHolder.setT (Ljava/lang/Object;)V
   L2
    LINENUMBER 23 L2
    ALOAD 1
  INVOKEVIRTUAL com/example/mydemo/multiple/data/config/T/SimpleHolder.getT ()Ljava/lang/Object; ====>>这里
  CHECKCAST java/lang/String  ====>>这里
    ASTORE 2
   L3
    LINENUMBER 25 L3
    RETURN
   L4
    LOCALVARIABLE args [Ljava/lang/String; L0 L4 0
    LOCALVARIABLE simpleHolder Lcom/example/mydemo/multiple/data/config/T/SimpleHolder; L1 L4 1
    LOCALVARIABLE t Ljava/lang/String; L3 L4 2
    MAXSTACK = 2
    MAXLOCALS = 3
}

两者的字节码文件完全一致,他们都在边界(泛型对象变成具体对象的时候)对类型做了检查并强转。
所以到这就可以理解Java泛型的实际行为了。

类型擦除的补偿

为了补偿JAVA泛型的不灵活(使用泛型时,你无法使用任何实例化T的操作,或者反射操作),但你可以用Class t,此时t可以进行实例化,也可以使用反射获取其方法和参数,这也算补偿了一部分能力,保持了和C++ go python等有潜在类型机制的语言一样的用法。

public class GenericHolder <T>{
    private T t;
    private Class<T> t2;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    public T newT() throws InstantiationException, IllegalAccessException {
        return t2.newInstance();
    }

    //无法编译
//    public T newT(){
//        return t.newInstance();
//    }

}

当然,并不能确定T是否有无参构造,可能运行时期会报错,所以Java设计者推荐使用Supplier 让调用者传入构造方法,而不是直接用newInstance。

posted @ 2023-03-15 10:02  来焕明  阅读(21)  评论(0)    收藏  举报