上帝视角学JAVA- 基础16-泛型、文件类【2021-08-30】

1、泛型

1.1 为什么要有泛型

泛型就是 标签的意思。看看泛型长什么样

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{}

上面的代码是 ArrayList 类去除类体部分。 <>里面的E就是 泛型的一种写法。泛型一定要放在<> 吗?

看下面的例子

public E get(int index) {
            synchronized (mutex) {return list.get(index);}
        }

上面这个方法是 Collections 类中SynchronizedList内部类的get方法。 这里面的 E 也是泛型,但是没有放在<>里面

泛型长什么样已经知道了,现在问题来了为什么要有泛型呢?它是为了解决什么问题。

ans: 我们发现,在集合类的设计阶段,比如我们定义 ArrayList 这个类时,并不知道 这个ArrayList到底要放什么类型的数据,但是我们知道所有类都是Object类的子类,因此可以使用Object来定义。 但是这样就无法限制只能放置同种类型的数据了。而且使用元素时,还存在类型强转的问题。当混入不是同一种类型的数据,强转会出错。在JDK5.0之前,只能使用Object来定义。

jdk5.0 以后,提供了泛型这个工具来解决这个问题。将元素的类型设计成一个参数,放置<>里面,这个参数就是泛型。泛型也是一个标签,代表某一类型。

在上面第二个例子中, E 没有放在<> 里,因为这个在类的内部使用泛型,而泛型的定义,必须放在<>里面。下面是SynchronizedList的定义

static class SynchronizedList<E>
        extends SynchronizedCollection<E>
        implements List<E> 

1.2 使用

// new ArrayList 对象时,在<>中指定 具体的类型。
List<Integer> list = new ArrayList<>();
list.add(123);
list.add(34);
list.add(7);
Integer i = list.get(2);
System.out.println(i); // 7

一旦泛型被指定,那么 ArrayList 可以添加的元素类型也就确定了。

泛型是可以定义多个的。最常见的是2个。比如 HashMap。先看 HashMap 类的定义去除类体部分。有2个泛型 K 和V 这也说明泛型不是都叫E的。可以自己取名字,一般用大写。

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {}

使用:

HashMap<String, Integer> map = new HashMap<>();
map.put("ddd", 2);
System.out.println(map); // {ddd=2}

这就结束了? 泛型还可以嵌套的 看HashMap类中的 entrySet方法,

public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }

Set<> 的<> 里面是 Map.Entry<K,V> 然后里面还有<>, 里面才是K,V

Map.Entry 实际上是指 Entry 接口,只不过Entry接口是定义在Map接口的内部接口。 因此,Set 里面需要放的是 Entry的实现类对象。

而Entry接口,自己也定义了泛型。 一起写在这里,就是泛型嵌套了。

当我们没有指定泛型时,泛型按照Object类型处理,但是不等同于Object

1.3 自定义泛型结构

1.3.1 泛型类、泛型接口

1、泛型类定义

// 类头定义了 E 这个泛型参数,类体就可以使用了
public class Order<E> {
​
    String name;
    int orderId;
    // 声明一个 E 类型的变量 e
    E e;
​
    public E getE() {
        return e;
    }
​
    public void setE(E e) {
        this.e = e;
    }
​
    public Order(String name, int orderId, E e) {
        this.name = name;
        this.orderId = orderId;
        this.e = e;
    }
    public Order() {
        
    }
    @Override
    public String toString() {
        return "Order{" +
                "name='" + name + '\'' +
                ", orderId=" + orderId +
                ", e=" + e +
                '}';
    }
}

上面定义的泛型类只有一个泛型参数E,其实可以定义多个,如果需要的话。

可以看到,泛型类的构造函数并没有<>,和普通类的构造函数的定义是一样的

//使用以下这个泛型类
Order<String> order = new Order<>();
order.setE("sss");
System.out.println(order); // Order{name='null', orderId=0, e=sss}
  • 尽管编译时 Order<String> order = new Order<>(); 与Order<Integer> order = new Order<>(); 不同,但是在运行时,只有1个Order类加载到内存中。

  • 异常类不能声明为泛型类。下面的写法是错误的,编译就通不过。

// 错误写法
public class MyException<E> extends RuntimeException{}
  • 当需要使用泛型创建数组时,怎么写呢?

// 错误写法
// E[] arr = new E[10];
// 正确写法
E[] arr = (E[]) new Object[10];

当某个类继承了 泛型类怎么处理?

  • 情况1:继承的时候指定了泛型,子类将不再有泛型

public class SubOrder1 extends Order<Integer>{
    
}
  • 情况2:继承的时候没有指定泛型,任然是一个泛型类

public class SubOrder2<E> extends Order<E>{
​
}
  • 情况3:继承的时候指定了部分泛型,还是一个泛型类。这是多个泛型参数的情况才会出现

public class TwoOrder<E,T> {
    int a;
    E e;
    T t;
​
    public TwoOrder(int a, E e, T t) {
        this.a = a;
        this.e = e;
        this.t = t;
    }
​
    public TwoOrder() {
    }
}
// 只指定了父类的 一个泛型。
public class SubTwoOrder<T> extends TwoOrder<String, T>{
    
}
  • 情况4 不管有没有指定父类的泛型,都可以定义自己的泛型

public class SubTwoOrder<A,B,T> extends TwoOrder<String, T>{
    A a;
}
public class SubTwoOrder<A,B,T> extends TwoOrder{
    A a;
}
// 注意,这里的 T 是子类自己的,不是父类的。父类的没有指定等价于
public class SubTwoOrder<A,B,T> extends TwoOrder<Object, Object>{
    A a;
}

2、泛型接口定义

接口与类定义泛型没有什么区别。

public interface OrderInterface<E> {

    E getE();
    
}

1.3.2 泛型方法

注意:泛型方法 与方法中使用泛型是不一样的。

下面是方法中使用泛型:

public E getE() {
        return e;
    }

注意:静态方法中不能使用泛型。因为泛型是造对象时指定的,而静态方法是类加载时创建的。

再看泛型方法的例子:

<T> T[] toArray(T[] a);

泛型方法中的 泛型与类的泛型无关。不要求类是一个泛型类。

当泛型类中有泛型方法时,泛型类定义泛型变量与泛型方法中定义泛型变量不是同一个,即使它们的名称相同。

看下面的代码:

public class Order<E> {
​
    String name;
    int orderId;
    // 声明一个 E 类型的变量 e
    E e;
    
    public <E> E getT(E e){
​
        return t;
    }
​
    public Order() {
        // 错误写法
        // E[] arr = new E[10];
        // 正确写法
        E[] arr = (E[]) new Object[10];
    }
​
    @Override
    public String toString() {
        return "Order{" +
                "name='" + name + '\'' +
                ", orderId=" + orderId +
                ", e=" + e +
                '}';
    }
}

使用

Order<Integer> order = new Order<>();
String ddd = order.getT("ddd");
System.out.println(ddd); // ddd

上面使用的代码中,指定了Order类的泛型是Integer,而调用泛型方法getT时,传入的泛型是String,得到的返回值也是String

虽然Order 类使用的是泛型E变量定义,而且方法getT也是使用的E变量定义,名称一样,但是是2个不同的东西。

1.3.3 泛型在继承的体现

Object obj = new Integer(333);
String str = "333";
// 正确
obj = str;
​
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
// 错误写法!
list1 = list2;

以上说明 泛型中的类型即使是子父类关系,泛型类也不具备子父类关系。list2 不能赋值给 list1,虽然List1中的泛型指定的是Object,是list2指定的String的父类。但是list1 与list2 仍然不具备子父类关系,不能互相赋值。

但是泛型类本身是具有继承关系,指定的泛型相同是可以进行赋值的。

// SubOrder2 是 Order 的子类。都指定了相同的Integer类型
Order<Integer> fa = new Order<>();
SubOrder2<Integer> sub = new SubOrder2<>();
fa = sub;

1.3.4 通配符 ?

通配符就是问号 ?

List<Integer> list1 = null;
List<String> list2 = null;
​
List<?> list = null;
list = list1;
list = list2;

通配符就是能匹配任意的类型。相等与是 所有的 具体泛型类的公共父类一样。

如:List<A> 和List<B> 是并列关系的,二者的公共父类是 List<?>

用在什么地方呢?

public void print(List<?> list){
    for (Object obj : list) {
        System.out.println(obj);
    }
}

我们写了一个遍历的方法,用上通配符之后,就可以接收指定任意类型的泛型类了。其实,你不写泛型也没事。

public void print(List list){
        for (Object obj : list) {
            System.out.println(obj);
        }
    }

以List为例,对于List<?> 就不能向其内部添加数据了,除了null之外。

List<Integer> list1 =  new ArrayList<>();
​
List<?> list = list1;
list.add(null) // 只能添加 null

不让写是为了维护不能随便添加数据这个原则,以前是现在只能添加泛型类型的数据。现在是通配符了,就不让写了。

但是是可以读的。

List<Integer> list1 = new ArrayList<>();
list1.add(555);
list1.add(123);
List<?> list = list1;
Object o = list.get(1);
System.out.println(o); // 123
  • 有限制条件的通配符

上限 extends :使用时指定的类型必须是继承某个类或者实现某个接口。

下线super:使用时指定的类型不能小于操作的类

<? extends Number>  泛型类型只允许Number以及子类
​
<? super Number> 泛型类型必须为 Number或者Number的父类

看例子:

// 有一个 Order 类
public class Order<E> {
    String name;
    int orderId;
}
// Order的子类 SubOrder2
public class SubOrder2<E> extends Order<E>{
​
}
// list1 能作为 泛型是Order或者Order的子类 的父类
List<? extends Order> list1 = new ArrayList<>();
// list2 能作为 泛型是Order或者Order的父类 的父类
List<? super Order> list2 = new ArrayList<>();
​
List<Order> list3 = new ArrayList<>();  // 泛型是 Order 的 List
List<SubOrder2> list4 = new ArrayList<>();// 泛型是 Order 的 子类 SubOrder2 的 List
List<Object> list5 = new ArrayList<>();// 泛型是 Order 的 父类 Objec  的 List
​
list1 = list3; // 正确
list1 = list4; // 正确
list1 = list5; // 错误 list1 不能接收泛型是 Order的父类的 List 对象
​
list2 = list3; // 正确
list2 = list5; // 正确
list2 = list4; // 错误 list2 不能接收泛型是 Order的子类的 List 对象

有条件的通配符的对象读写分析:

首先是读,都可以读。不管有没有条件。

写的话需要分情况,null都是可以写的。

list2.add(new Order()); // 正确

只有使用 super 限定时,只能放Order及其父类,那么添加进去的最小必须是 Order类。

只有是添加Order类本身时,才允许被添加。

如果是允许添加的是Order其他的父类,无法保证都能被Order接收。只有是添加Order类,一定能被Order接收。

2、File 类

File 类即可以表示一个文件,也可以表示一个文件夹。

File 类 在 java.io 包下

File类在JDK8.0 有 2241 行代码。简要看看

1、构造器

 

可以看到,File类共有 6个有参构造器,没有无参构造器,其中前2个是私有构造器。我们在外面能使用 的只有后4个构造器。

上图中第二列的 红色的锁代表是 private权限,绿色的锁且打开的是 public 权限。

最常用的是 第三个,参数是String 类型的 路径。可以是相对路径,也可以是绝对路径

// 相对路径   取的是当前路径。在idea中,是当前module的根路径 
File file = new File("hello.txt");
// 绝对路径
File file1 = new File("G:\\study\\leetCode\\JavaBaseStudy");

new了对象之后,只是内存中的 普通对象。并没有与硬盘中的文件进行对应

上述绝对路径的写法中,分隔符使用了 "\\" 主要是 "\" 在java中 表示转义的意思。把本来是转义字符再转义一下,变成普通字符"\"

在 linux 中 分隔符用 / 而在windows中 用的是 \

为了避免写错,File类提供了一个常量 separator 来动态根据操作系统获取分隔符。

File.separator

第4个构造器,第一个参数写 路径,第二个参数写 第一个参数路径下的文件夹或者文件。

 File file2 = new File("G:\\study\\leetCode\\JavaBaseStudy\\", "hello.txt");

第5个构造器, 第一个参数传File 类型的对象,第二个参数是这个对象的路径的子路径或文件

File file3 = new File("G:\\study\\leetCode\\JavaBaseStudy\\");
File file4 = new File(file3, "hello.txt");
System.out.println(file4);
//输出:  G:\study\leetCode\JavaBaseStudy\hello.txt

最后一个构造器: 接收的是一个URI 对象

public File(URI uri)

上面是 这个构造器的方法头。接收的是URI 类。后续再讲

2.1 File类的常用方法

  • File getAbsoluteFile() 获取文件的完整路径

 File file = new File("hello.txt");
 File file1 = new File("G:\\study\\leetCode\\JavaBaseStudy\\hello.txt");
​
System.out.println(file); // hello.txt
// 获取完整路径,返回值还是 File类型的对象
File absoluteFile = file.getAbsoluteFile();
System.out.println(absoluteFile); // G:\study\leetCode\JavaBaseStudy\hello.txt
  • getPath、getParent、length、lastModified方法

getPath 获取路径

getParent 获取父路径

length 长度

lastModified 最近一次修改时间戳

File file = new File("hello.txt");
​
String path = file.getPath();
String parent = file.getParent();
long length = file.length();
long l = file.lastModified();
System.out.println(path); // hello.txt
System.out.println(parent);// null
System.out.println(length);// 7
System.out.println(l);// 1630026081316
File file = new File("G:\\study\\leetCode\\JavaBaseStudy\\hello.txt");
​
String path = file.getPath();
String parent = file.getParent();
long length = file.length();
long l = file.lastModified();
System.out.println(path); // G:\study\leetCode\JavaBaseStudy\hello.txt
System.out.println(parent);//G:\study\leetCode\JavaBaseStudy
System.out.println(length);//7
System.out.println(l);// 1630026081316

可以看到,当是相对路径时,是没有parent的。而绝对路径可以获取上一级目录

此时还是内存级别的操作,不涉及到硬盘中是否有这个文件。即 new File对象时,这个路径可以是不存在的。

对于相对路径写法,想要获取上一级目录,可以先获取完整路径getAbsoluteFile,再调用getParent方法。

  • String[] list() 获取指定目录下的所有文件或者文件目录的名称数组。

    File[] listFiles() 获取指定目录下的所有文件或者文件目录的文件数组。

File file = new File("G:\\study\\leetCode\\JavaBaseStudy\\hello.txt");
​
String[] list = file.list();
File[] files = file.listFiles();
System.out.println(list); // null
System.out.println(files);  // null
// 之所以是null 是因为file是一个文件,而不是一个文件夹
File file = new File("G:\\study\\leetCode\\JavaBaseStudy\\");
​
String[] list = file.list();
File[] files = file.listFiles();
for (String s : list) {
    System.out.println(s);
}
for (File file1 : files) {
    System.out.println(file1);
}
// 输出
hello.txt
JavaBaseStudy.iml
pom.xml
src
target
G:\study\leetCode\JavaBaseStudy\hello.txt
G:\study\leetCode\JavaBaseStudy\JavaBaseStudy.iml
G:\study\leetCode\JavaBaseStudy\pom.xml
G:\study\leetCode\JavaBaseStudy\src
G:\study\leetCode\JavaBaseStudy\target

因此,这2个方法对文件目录才有意义。而且要求这个目录需要存在,不存在会报错

  • boolean renameTo(File dest) 文件重命名方法

    这个方法接收一个 File 对象。

    要想保证成功,需要保证 原文件是存在的,且目标文件对象是不存在的。

File file = new File("hello.txt");
​
// 硬盘中并没有这个he.txt文件
File he = new File("he.txt");
boolean b = file.renameTo(he);
System.out.println(b); // true
  • isDirectory 判断 File对象是否是 目录

    isFile 判断 File对象是否是 文件

    exists 判断 File对象是否在硬盘存在

    canWrite 判断文件是否可写、canRead 可读、canExecute 可执行

    isHidden File对象是否是隐藏的

boolean directory = file.isDirectory();
boolean file1 = file.isFile();
boolean exists = file.exists();
boolean b = file.canRead();
boolean b1 = file.canWrite();
boolean b2 = file.canExecute();
boolean hidden = file.isHidden();

使用时,应该先调用 exists 方法判断是否存在,再判断是否是 文件或者文件夹。

  • boolean createNewFile() 创建新文件,如果已经存在则不会创建。返回是否创建成功。

// 该文件已经存在
File file = new File("hello.txt");
boolean newFile = file.createNewFile();
System.out.println(newFile); // false
  • mkdir() 创建目录,如果不存在

    mkdirs 创建目录,如果这个目录的上层目录也不存在,会一并创建。

File file = new File("hello.txt");
boolean mkdir = file.mkdir();
boolean mkdirs = file.mkdirs();
// file对象已经存在了,就不会再创建
System.out.println(mkdir); // false
System.out.println(mkdirs); // false
  • delete() 移除文件 返回是否删除成功

    当删除文件时,需要文件存在才能删除成功

    当删除文件夹时,需要是空文件夹才能删除成功。 如果需要删除非空目录,需要遍历该目录,将里面的东西全部删除之后才能删除这个目录。

// he.txt 是不存在的  hello.txt是存在的
File file = new File("he.txt");
File file1 = new File("hello.txt");
​
boolean delete = file.delete();
boolean delete1 = file1.delete();
System.out.println(delete); // false
System.out.println(delete1); // true

File类 不能对文件进行读写操作,进行读写操作必须使用流来进行。

File对象通常是传递给流的构造器,告诉流对那个文件进行操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java泛型Java 5引入的新特性,可以提高代码的可读性和安全性,降低代码的耦合度。泛型是将型参数化,实现代码的通用性。 一、泛型的基本语法 在声明、接口、方法时可以使用泛型泛型的声明方式为在名、接口名、方法名后面加上尖括号<>,括号可以声明一个或多个型参数,多个型参数之间用逗号隔开。例如: ```java public class GenericClass<T> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public interface GenericInterface<T> { T getData(); void setData(T data); } public <T> void genericMethod(T data) { System.out.println(data); } ``` 其,`GenericClass`是一个泛型,`GenericInterface`是一个泛型接口,`genericMethod`是一个泛型方法。在这些声明,`<T>`就是型参数,可以用任何字母代替。 二、泛型的使用 1. 泛型的使用 在使用泛型时,需要在名后面加上尖括号<>,并在括号指定具体的型参数。例如: ```java GenericClass<String> gc = new GenericClass<>(); gc.setData("Hello World"); String data = gc.getData(); ``` 在这个例子,`GenericClass`被声明为一个泛型,`<String>`指定了具体的型参数,即`data`字段的型为`String`,`gc`对象被创建时没有指定型参数,因为编译器可以根据上下文自动推断出型参数为`String`。 2. 泛型接口的使用 在使用泛型接口时,也需要在接口名后面加上尖括号<>,并在括号指定具体的型参数。例如: ```java GenericInterface<String> gi = new GenericInterface<String>() { private String data; @Override public String getData() { return data; } @Override public void setData(String data) { this.data = data; } }; gi.setData("Hello World"); String data = gi.getData(); ``` 在这个例子,`GenericInterface`被声明为一个泛型接口,`<String>`指定了具体的型参数,匿名内部实现了该接口,并使用`String`作为型参数。 3. 泛型方法的使用 在使用泛型方法时,需要在方法名前面加上尖括号<>,并在括号指定具体的型参数。例如: ```java genericMethod("Hello World"); ``` 在这个例子,`genericMethod`被声明为一个泛型方法,`<T>`指定了型参数,`T data`表示一个型为`T`的参数,调用时可以传入任何型的参数。 三、泛型的通配符 有时候,我们不知道泛型的具体型,可以使用通配符`?`。通配符可以作为型参数出现在方法的参数型或返回,但不能用于声明泛型泛型接口。例如: ```java public void printList(List<?> list) { for (Object obj : list) { System.out.print(obj + " "); } } ``` 在这个例子,`printList`方法的参数型为`List<?>`,表示可以接受任何型的`List`,无论是`List<String>`还是`List<Integer>`都可以。在方法内部,使用`Object`型来遍历`List`的元素。 四、泛型的继承 泛型泛型接口可以继承或实现其他泛型泛型接口,可以使用子或实现型参数来替换父或接口的型参数。例如: ```java public class SubGenericClass<T> extends GenericClass<T> {} public class SubGenericInterface<T> implements GenericInterface<T> { private T data; @Override public T getData() { return data; } @Override public void setData(T data) { this.data = data; } } ``` 在这个例子,`SubGenericClass`继承了`GenericClass`,并使用了相同的型参数`T`,`SubGenericInterface`实现了`GenericInterface`,也使用了相同的型参数`T`。 五、泛型的限定 有时候,我们需要对泛型型参数进行限定,使其只能是某个或接口的子或实现。可以使用`extends`关键字来限定型参数的上限,或使用`super`关键字来限定型参数的下限。例如: ```java public class GenericClass<T extends Number> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public interface GenericInterface<T extends Comparable<T>> { T getData(); void setData(T data); } ``` 在这个例子,`GenericClass`的型参数`T`被限定为`Number`的子,`GenericInterface`的型参数`T`被限定为实现了`Comparable`接口的。 六、泛型的擦除 在Java泛型信息只存在于代码编译阶段,在编译后的字节码会被擦除。在运行时,无法获取泛型的具体型。例如: ```java public void genericMethod(List<String> list) { System.out.println(list.getClass()); } ``` 在这个例子,`list`的型为`List<String>`,但是在运行时,`getClass`返回的型为`java.util.ArrayList`,因为泛型信息已经被擦除了。 七、泛型型推断 在Java 7,引入了钻石操作符<>,可以使用它来省略型参数的声明。例如: ```java List<String> list = new ArrayList<>(); ``` 在这个例子,`ArrayList`的型参数可以被编译器自动推断为`String`。 八、总结 Java泛型是一个强大的特性,可以提高代码的可读性和安全性,降低代码的耦合度。在使用泛型时,需要注意它的基本语法、使用方法、通配符、继承、限定、擦除和型推断等问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值