文件/字符串/反射/泛型
文件
新的JDK提供了很多好用的文件处理API,在小文件场景下,不需要写繁琐的流文件处理过程了。
新API
主要集中在Files和Paths两个类的静态方法中(jdk大部分工具类方法都会在原类上加一个s,比如Strings、Collections)。
-
获取这个路径是否有Posix许可 Files.getPosixFilePermission(Path p);
Posix 可移植操作系统接口,是IEEE为unix上运行软件,定义的一系列api总称。 -
走查文件树,这个方法帮我们快速访问文件树。
可以定义从指定目录开始,文件访问选项,最大递归深度,文件访问方法。
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)
-
WatchService可以监听某个目录下的所有文件操作,仅限于指定目录,不包括其子目录。 -
PathMatcher查找目录,可以按照glob(linux的简化正则)或者 regex 来匹配查找指定文件和目录。 -
Files.readAllLines(Paths.get()),读整个文件到内存中。 -
Files.write(Paths.get(),content),将内容直接写入某个文件中。 -
Stream<> strem = Files.line()按行读取文件内容,直接生成流。 这个好用,且他还是按行读取的,性能好。
文件系统
1.文件或目录的路径
2.文件本身
字符串
-
使用javap -c xxx.class文件,来查看代码实际流程
这个命令可以来反编译class文件,获得jvm的汇编语言,查看代码原始行为。 -
也可以用idea的获取字节码文件功能。
-
StringBuffer和StringBuilder
-
String.format,其内部就是实例化一个Formatter(它是一个java.util中的格式化工具类),并将传入的参数直接给它。(要注意String.format性能是比较差的,但是可读性强)
反射
反射可以让我们在运行时发现和使用类的信息。
Class对象
在java中每个类都有个Class对象,每次编写并编译一个类时,都会生成一个Class对象(并被相应的存储在同名的.class文件中),为了生成这个对象,jvm会使用一个类加载器的子系统。
类在首次使用时才会被加载到JVM中,当程序第一次使用该类的静态变量时,就会触发这个类的加载。当我们new一个对象时,其实调用了其构造器,而构造器是类的一个隐式静态方法。
常用API
反射的大部分API都是基于Class实现的。
-
Class.forName("") 根据类绝对地址获取类信息,如果获取不到会抛出类找不到异常。
-
Class.isInterface()
-
Class.getSimpleName() 不带包名的类名
-
Class.getCanonicalName() 带包名完全限定类名
-
Class.getInterface() 获取接口的数组
泛型类的使用
泛型为我们日常编写通用型工具提供了可能,可以接收不同的对象类型来做相同的事情。
常见用法
-
<?>非具体类,表示允许任意类型的类使用。 -
<? 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行为。
<? 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
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>类型
}
-
T和?区别在于T可以将泛型变量化,进行实体调用,?只是一种声明式的限定。
-
cast()强转,Cat.class.cast(new Object())将Object强转为Cat;
-
几种类型判断方式和效果
如果 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
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
浙公网安备 33010602011771号