JAVA基础--JAVA 集合框架(泛型、file类)16
一、集合总结
集合:Collection体系、Map体系。
Collection体系:单列集合的共性操作规则。
List:列表,可以重复,有下标,拥有特有的迭代器ListIterator。
ArrayList:底层是可变数组。增删慢,查询快。不安全
LinkedList:底层是链表结构,增删快,查询慢。不安全。有头和尾,特有的头尾操作的方法。可以模
拟数据结构(堆栈、队列)。
Vector:底层是可变数组,增删,查询都慢。安全。被ArrayList代替。
Enumeration:它是古老的迭代器,被Iterator代替。
Set:不能存放重复元素。
HashSet:底层是哈希表。不保证存取的顺序。保证对象唯一需要依赖对象的hashCode和equals方法。
因此给HashSet中存放的对象需要重写Object类中的hashCode和equals方法。
TreeSet:底层是二叉树结构。保证对象唯一和排序需要依赖对象的比较方法(compareTo),对象要具
备比较方法,需要实现Comparable接口。
如果对象具备的比较功能不适合需求,或者对象本身不具备比较功能,这时我们可以自己指定比较器对象(Comparator)。然后将这个对象传递给TreeSet集合。
LinkedHashSet:它是HashSet的子类,底层是哈希表和链表组合。它可以保证存取的顺序。
Map体系:双列集合的共性操作规则。存放的是具有一定对应关系的数据。key值是不能重复的。
HashMap:底层是哈希表,不保证存取顺序。主要作用在key上。key是自定义对象,需要复写hashCode
和equals方法。
TreeMap:底层是二叉树,作用在key上。key是自定义对象,需要实现Comparable接口,或者给集合
传递Comparator对象。
Hashtable:它底层是哈希表,安全。被HashMap代替。
LinkedHashMap:它是HashMap的子类,可以保证存取的顺序。底层链表和哈希表。
Map集合的遍历,不能直接使用Iterator,而需要将Map集合转成单列集合。
keySet方法,将Map中的所有key取出存放在Set集合中。遍历Set集合,得到每个key值,然后调用Map中的get方法,进而获取到key对应的value值。
entrySet方法。将Map中的key和value组成Entry对象(key和value对应关系对象)。存储在set中,遍历set集合,得到Entry对象,然后调用getKey和getValue方法得到key和value。
values方法:获取到的是Map中的所有value值,存放在Collection集合中。
可以存放重复元素:
ArrayList:主要用在查询较多的时候。
LinkedList:主要是根据头尾操作时。
Vector:基本不用
不可以出现重复元素:
HashSet: 对数据的存取顺序没有要求
LinkedHashSet:保证存取顺序
TreeSet:对其中的数据排序
数据具有对应关系:
HashMap: 对数据的存取顺序没有要求
TreeMap: 对其中的数据排序
Hashtable: 基本不用
LinkedHashMap:保证存取顺序
二、泛型技术
1. 泛型的引入
在使用集合的时候,由于集合中存放的元素没有做任何的限制,因此可以将任何对象保存到集合中。这样会导致在从集合中取出对象的时候,对象都被提升为Object类型,如果需要使用对象的特有方法,这时需要向下转型,就有可能发生类型转换异常。
、
集合是容器,数组也是容器,但是数组容器一旦定义完成之后,其中能够存放的数据类型就一定确定,如果在给数组中存放元素的时候,类型和数组限定的类型不匹配,这时编译就直接报错
上述集合和数组的差异,是因为集合中的元素没有进行类型的限定。
我们可以想办法给集合中能够存储的元素进行类型的限定,这样如果在给集合中存储元素的时候,类型与限定的类型不一致,就不让程序编译通过。只要程序能够编译通过,肯定集合中保存的类型是限定的类型。这样在取出的时候,我们肯定知道取出的元素是什么类型。
解决上述的问题,需要使用JDK中提供的泛型技术。泛型其实是在限定数据类型的。
2.泛型技术介绍
泛型的书写格式:
<引用数据类型>
泛型在使用的时候,主要是限定数据类型,程序中加入泛型之后,操作数据的时候,类型一定要和泛型中指定的类型一致。泛型中不能书写基本数据类型(使用对应的包装类型)。
3.泛型技术的简单应用
1 * 2 * 泛型技术演示 3 */ 4 5 //自定义比较器 6 class MyComparator implements Comparator<String>{ 7 8 public int compare(String o1, String o2) { 9 int temp = o1.length() - o2.length(); 10 return temp == 0 ? o1.compareTo(o2) : temp; 11 } 12 } 13 public class GeneratorDemo { 14 public static void main(String[] args) { 15 16 //演示泛型在集合中的使用 泛型的菱形技术 17 TreeSet<String> set = new TreeSet<String>( new MyComparator() ); 18 19 set.add("aaaaa"); 20 set.add("aba"); 21 set.add("cb"); 22 set.add("ABC"); 23 set.add("CCB"); 24 set.add("ICBC"); 25 26 //遍历 27 for (Iterator<String> it = set.iterator(); it.hasNext();) { 28 //String s = it.next(); 29 System.out.println(it.next()); 30 } 31 } 32 }
a、 泛型的菱形技术:在定义类型的时候声明的泛型的类型,后面创建对象时需要指定的泛型类型可以省略。
b、 泛型的擦除技术:泛型技术属于编译时期的技术。当前程序编译完成之后,泛型全部被删除。
4. 自定义泛型
a. 泛型类
泛型技术在程序能够使用,原因是在使用的类或者接口上提供书写泛型的地方。如果类或者接口上没有提供书写泛型的地方,在实际代码中我们也不能书写。
我们自己来模拟JDK中可以书写泛型的类或者接口。
我们自己定义类或者接口的时候,声明<变量>泛型,当在使用这个类或者接口的时候就可以指定具体的类型。
1 /* 2 * 自定义泛型类: 3 * 定义的类上声明泛型。 4 */ 5 /* 6 * 需求:定义类,封装任意类型的数据 7 * 分析: 8 * 可以在类中定义Object类型的变量,接收任意类型的数据 9 * 但是给调用者返回这个数据的时候,数据就被提升成Object类型 10 * 调用者需要自己手动的再向下转型。 11 * 12 * 我们可以在定义类的时候,在类上定义泛型,让调用者自己类声明保存的数据类型。 13 * 14 * class Data<T> : 在定义类的时候,在类上定义一个泛型参数。 15 * 当使用者使用这类的时候,会指定当前这个参数的类型。 16 * 例如:Data<String> d = new Data<String>(); 17 * 我们定义的类上的泛型T就被String代替。 18 * 19 * 在类上定义的泛型参数,其实就是一个数据类型,只是还没有被使用的时候 20 * 我们不知道是什么类型,但是调用者只要创建对象,就会明确这个类型, 21 * 只要程序在运行,泛型类型肯定就已经明确。 22 * 因此在类上定义的泛型参数,在类中的任意地方都可以使用。 23 */ 24 class Data<T>{ 25 26 private T data; 27 28 public T getData() { 29 return data; 30 } 31 32 public void setData(T data) { 33 this.data = data; 34 } 35 } 36 37 38 public class Demo2 { 39 public static void main(String[] args) { 40 41 Data<Integer> d = new Data<Integer>(); 42 d.setData(123); 43 44 } 45 }
总结:
泛型类:在定义类的时候,定义泛型参数。在类上定义的泛型参数,在类中可以使用。
类上定义的泛型参数,需要在 创建这个类对象的时候明确,如果没有明确这个类型,默认是Object
类型。
b. 非静态方法泛型
方法的定义格式:
修饰符 返回值类型 方法名( 参数类型 变量名 , 参数类型 变量名 ...... ){
方法体
}
方法上定义的泛型格式:
修饰符 <泛型参数> 返回值类型 方法名( 参数类型 变量名 , 参数类型 变量名 ...... ){
方法体
}
1 /* 2 * 泛型方法: 3 * 类上可以定义泛型,在类中是能够直接使用类上定义的泛型。 4 * 5 * 在类中也可以不使用类上的泛型。 6 */ 7 //类上定义泛型 8 class Test2<W>{ 9 10 //在方法上使用泛型 11 public void show(W w){ 12 System.out.println(w); 13 } 14 15 //方法上也可以不使用类上的泛型 16 public void method(int a){ 17 18 } 19 /* 20 * 如果方法上接收的参数类型也不确定,但是我们知道方法上 21 * 接收的参数类型和类上定义的泛型的类型不一致, 22 * 这时可以在方法上单独去定义适合这个方法自己的泛型 23 * 24 */ 25 public <Q> void function( Q q ){ 26 System.out.println(q); 27 } 28 29 } 30 public class Demo3 { 31 public static void main(String[] args) { 32 33 Test2<Double> t = new Test2<Double>(); 34 //使用拥有泛型的方法 35 t.function('a'); 36 37 } 38 }
c. 静态方法定义泛型
静态方法不能使用类上定义的泛型。因为类上的泛型类型需要在创建这个类的对象时明确。而静态方法运行时候可以没有对象。
d. 泛型接口
在定义接口的时候,可以在接口上定义泛型参数。
1 /* 2 * 演示接口上定义泛型 3 */ 4 //在接口上定义了泛型参数 5 interface Inter<P>{ 6 public void show(P p); 7 } 8 /* 9 * 接口的实现类,在实现接口的时候,明确接口上泛型的具体数据类型 10 */ 11 class InterImpl implements Inter<String>{ 12 13 public void show(String p) { 14 15 } 16 }
接口上的泛型:在实现接口的时候明确具体的数据类型。
e. 泛型传递
interface Collection<E>{}
interface List<E> extends Collection<E>{}
class ArrayList<E> implements List<E>{}
开发者自己的类:ArrayList<String> list = new ArrayList<String>();
5. 泛型通配符
1 /* 2 * 演示泛型的通配符技术: 3 * 通配符:统统都匹配的符号。使用一个符号去匹配其他的数据。这个符号被称为通配符 4 * 5 * 泛型的通配符符号:?号 ;当接收的数据的时候,如果需要使用泛型,却不知道当前应该 6 * 书写什么数据类型的时候,这时可以使用?表示。 7 */ 8 public class GenericTest { 9 public static void main(String[] args) { 10 11 List<String> list = new ArrayList<String>(); 12 Collections.addAll(list, "aa", "bb", "cc", "dd"); 13 printCollection(list); 14 15 Set<Integer> set = new HashSet<Integer>(); 16 Collections.addAll(set, 11, 22, 33, 44, 55, 66, 77); 17 // 打印集合 18 printCollection(set); 19 } 20 21 //泛型的通配符 22 public static void printCollection( Collection<?> list ) { 23 // 打印集合 24 for (Iterator it = list.iterator(); it.hasNext();) { 25 System.out.println(it.next()); 26 } 27 } 28 29 }
6. 泛型上下限
1 /* 2 * 演示泛型的通配符技术: 3 * 通配符:统统都匹配的符号。使用一个符号去匹配其他的数据。这个符号被称为通配符 4 * 5 * 泛型的通配符符号:?号 ;当接收的数据的时候,如果需要使用泛型,却不知道当前应该 6 * 书写什么数据类型的时候,这时可以使用?表示。 7 */ 8 public class GenericTest2 { 9 public static void main(String[] args) { 10 11 List<Teacher> list = new ArrayList<Teacher>(); 12 list.add(new Teacher("老唐",18)); 13 list.add(new Teacher("老张",28)); 14 list.add(new Teacher("花和尚",38)); 15 list.add(new Teacher("圣手书生",28)); 16 //printCollection(list); 17 18 19 Set<Student> set = new HashSet<Student>(); 20 set.add(new Student("班长",38)); 21 set.add(new Student("班花",18)); 22 set.add(new Student("班草",28)); 23 set.add(new Student("班导",48)); 24 printCollection(set); 25 26 27 Set<Person> set2 = new HashSet<Person>(); 28 set2.add(new Person("班长",38)); 29 set2.add(new Person("班花",18)); 30 set2.add(new Person("班草",28)); 31 set2.add(new Person("班导",48)); 32 printCollection(set2); 33 34 } 35 /* printCollection( Collection<?> coll ) 这个方法可以接收任何的Collection下的集合容器 36 * 并且可以将其中的说打印出来。 37 * 38 * printCollection 只打印集合中保存的数据是Person或者Person的子类数据 39 * 如果传递的集合中给出的数据不是Person或者Person的子类,就不接受这样的数据。 40 * 41 * 泛型的限定: 42 * 上限限定: ? extends E 当前的?表示的数据类型可以是E类型,也可以是E的子类类型 43 * 例如:? extends Person 当前的?可以是Person类型或者Person的子类类型 44 * 45 * 下限限定:? super E 当前的?表示的数据类型可以是E本身类型,或者E的父类类型 46 * 例如:? super Student 当前的?可以是Student类型或者Student的父类类型, 47 * 但不能是Student的子类或者兄弟类型 48 * 49 * TreeSet(Comparator<? super E> comparator) 50 * TreeSet(Collection<? > c) 51 * 52 * public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) 53 */ 54 public static void printCollection( Collection<? super Student> coll ) { 55 56 for (Iterator it = coll.iterator(); it.hasNext();) { 57 System.out.println(it.next()); 58 } 59 } 60 61 }
7. 解释API中泛型的限定
class TreeSet<E>{
public TreeSet( Collection<? exntends E> c ){
}
}
当在程序中我们创建TreeSet集合对象的时候,需要明确TreeSet上定义的泛型的具体类型。如果已经明确的了具体的类型,那么构造方法上使用的E,就可以明确的类型一致。
创建TreeSet对象:
TreeSet<Person> set = new TreeSet<Person>( 传递集合数据 );
就意味着TreeSet集合中所有使用E的地方都会变成Person类型。
构造方法上的Collection<? extends Person> ,限定当前可以给集合中保存的数据是Person或者Person的子类。
上面是TreeSet的构造方法,它的目的是在创建TreeSet集合对象的时候,将Collection集合中的数据添加到TreeSet集合中。
而创建TreeSet集合时我们设定集合中添加的数据类型应该Person类型,那么就说明Collection中的元素能够添加到TreeSet中,Collection中的数据要么是Person类型,要么是Person子类类型。
创建TreeSet对象的时候,会明确E的类型,同时还可以给TreeSet传递一个比较器对象。
传递的比较器是用来比较正要给集合中保存的数据和已经在集合中的数据的大小。
TreeSet<Student> set = new TreeSet< Student >(); 声明TreeSet上的泛型E为Student类型。
set.add(new Student(“zs”,23));
set.add(new Student(“ls”,33));
相当于要把第二个Student和第一个Student对象传递给Comparator中的compare方法进行比较大小。
compare方法需要接收当前这两个Student对象。
public int compare( Person o1 , Person o2 ){
}
Comparator接口中的compare方法主要任务的接收给集合中存储的元素和已经在集合中的元素数据,然后对其进行比较。
compare方法上负责接收2个对象的那个类型可以和集合中的数据类型一致,或者是集合中数据类型的父类型也可以。
因此得出:TreeSet构造方法上传递的Comparator接口可以接收的数据类型可以和集合一致,或者集合中的数据类型的父类类型。
三、IO技术
1.IO介绍
我们的数据全部存储在计算机的内存中。当程序运行结束之后,数据就全部消失。下次程序启动的时候,如果需要上次运行的结果数据,这时是无法获取到的。
我们需要在 程序中加入其它的技术,将程序中的有效的数据长久的保存起来,以后程序启动的时候可以读取这些数据,接着进行处理。
需要将数据长久保存,就需要使用Java中提供的IO技术。
IO技术:
I:Input 输入,读取操作
O:Output 输出,写操作。
数据从其他的设备上被加载到内存中的这个过程被称为输入(读取)操作。
数据从内存中输出到其他的设备上的这个过程被称为输出(写)操作。
2. IO学习路线
1、 学习如何操作硬盘上的文件或者文件夹
2、 学习如何读写文件中的数据。
注意:IO操作中的数据方向,然后确定最后需要使用的类或接口中的方法。
四、File类
1. File类介绍
数据需要保存在文件中,而文件多了,就需要文件夹进行管理。也就是说文件或者文件夹是我们长久保存数据的设备上存在的一类事物。
Java肯定会有一个类多这个事物进行描述。
Java中使用File类描述文件或者文件夹这个事物。
只要在java中需要操作硬盘上的文件或者文件夹就应该使用File类。
2. File类构造方法
1 /* 2 * 演示File类中的构造方法 3 */ 4 public class FileConstructorDemo { 5 public static void main(String[] args) { 6 method3(); 7 } 8 /* 9 * File(File parent, String child) 10 * File parent 已经将父目录封装成File对象, 11 * String child 文件或者文件夹 12 */ 13 public static void method3() { 14 15 //封装的父目录 16 File dir = new File("d:/abc/bbb/ccc"); 17 18 //将父目录下的文件或文件夹封装成File对象 19 File file = new File(dir , "1.txt"); 20 21 System.out.println(file); 22 } 23 /* 24 * File(String parent, String child) 25 * String parent 当前文件或者文件夹所在的父目录 26 * String child 是当前的文件或者文件夹 27 * 注意: 28 * 在windows中,目录(文件夹)之间的使用的是\ 作为默认分隔符 29 * 我们可以可以使用 / 作为分隔符 30 */ 31 public static void method2() { 32 33 //创建File对象 34 File file = new File("d:/abc","1.txt"); 35 36 System.out.println(file); 37 } 38 /* 39 * File(String pathname) 40 * new File("d:/1.txt") 将指定的字符串中的数据封装成File对象 41 * 但是这个字符串中表示的文件或者文件夹到底是否真实存在, 42 * 创建File对象的时候不会去验证 43 */ 44 public static void method() { 45 46 //创建File对象 47 File file = new File("d:/1.txt"); 48 49 System.out.println(file); 50 } 51 }
3. 获取方法
1 /* 2 * 演示File类中的 获取 方法 3 * 4 * 举例说明: 5 * 绝对路径:上海市闵行区浦江镇三鲁公路3279号明浦广场3号楼1楼125室 6 * 相对路径:三鲁公路3279号明浦广场3号楼1楼125室 7 * 8 * 绝对路径: 9 * 文件或文件所在的全路径。 10 * 相对路径: 11 * 文件或文件夹相对其他文件而言的路径。 12 * 13 * 例如: 14 * "1.txt" 这时并没有说明文件具体在哪个目录中, 15 * 这时JVM会认为当前的这个文件是相对当前程序所在的项目而言。 16 * "d:/1.txt" 全路径。这时JVM就已经可以识别这个目录的具体位置 17 * /1.txt 它相对的是当前项目所在的文件系统的根目录而言。 18 * 19 */ 20 public class FileGetMethodDemo { 21 public static void main(String[] args) throws IOException { 22 23 File file = new File("abc/1.txt"); 24 25 //getAbsolutePath 获取到的是File对象保存的文件或者文件所在的全路径 26 System.out.println("getAbsolutePath="+file.getAbsolutePath()); 27 System.out.println("getAbsoluteFile="+file.getAbsoluteFile()); 28 // getCanonicalPath 获取到的全路径中的真实目录 29 System.out.println("getCanonicalPath="+file.getCanonicalPath()); 30 //getName 获取到的是File对象中封装的最后一级的名称 31 System.out.println("getName="+file.getName()); 32 // getParent 获取到的最后一级目录之前的所有目录 33 System.out.println("getParent="+file.getParent()); 34 System.out.println("getParentFile="+file.getParentFile()); 35 36 System.out.println("getFreeSpace="+file.getFreeSpace()); 37 System.out.println("getTotalSpace="+file.getTotalSpace()); 38 System.out.println("getUsableSpace="+file.getUsableSpace()); 39 40 //获取操作系统所有的根目录,windows 所有的盘符 41 File[] roots = File.listRoots(); 42 for (File root : roots) { 43 System.out.println(root); 44 } 45 } 46 }
4. 创建方法
a. 创建文件
b. 创建文件夹
5. 删除方法
注意:delete方法不走回收站,慎用
6. 判断方法
1 /* 2 * 演示File类中的判断方法 3 */ 4 public class FileDemo3 { 5 public static void main(String[] args) throws IOException { 6 7 File file = new File("d:/1234.txt"); 8 //file.createNewFile(); 9 file.mkdir(); 10 //isDirectory 判断是否是目录(文件夹) 11 System.out.println(file.isDirectory()); 12 //isFile 判断是否是文件 13 System.out.println(file.isFile()); 14 //isHidden 判断是否是隐藏文件或文件夹 15 System.out.println(file.isHidden()); 16 //exists 判断文件或者文件夹是否存在 17 System.out.println(file.exists()); 18 19 } 20 }
7. 列举方法
上述的方法是可以根据指定的目录(文件夹),获取到这个目录(文件夹)下的所有文件和文件夹数据。
String[] list() 它是获取到指定文件夹下的所有文件和文件夹的名称,将这些名称保存到字符串数组中。
File[] listFiles() 它获取到的是指定的文件夹下的所有文件和文件夹对象,并将这些对象存储在File数组中。
1 /* 2 * 演示获取指定目录下的文件或文件夹名称 3 */ 4 public class ListDemo { 5 public static void main(String[] args) { 6 7 //指定目录 8 File dir = new File("d:/"); 9 10 //列出当前目录下的文件和文件夹的名称保存在字符串数组中 11 String[] list = dir.list(); 12 13 for (String s : list) { 14 System.out.println(s); 15 } 16 17 } 18 } 19 20 /* 21 * 获取指定目录下的文件和文件夹,会将这些文件和文件夹再次封装成File对象 22 * 存储在File数组中 23 */ 24 public class ListFilesDemo { 25 public static void main(String[] args) { 26 //创建File对象 27 File dir = new File("d:/"); 28 29 //列出指定目录下的所有文件和文件夹对象 30 File[] files = dir.listFiles(); 31 32 for (File file : files) { 33 System.out.println(file.isDirectory()); 34 } 35 } 36 }
注意:使用list方法和listFiles方法获取指定目录下的文件和文件夹的时候,如果指定的目录Java没有权限,这时list或listFiles方法返回的结果是null。
1 /* 2 * 获取指定目录下,以及子目录,子子目录(多级目录)下的所有内容 3 */ 4 public class ListFilesTest { 5 public static void main(String[] args) { 6 //获取系统的所有盘符 7 File[] roots = File.listRoots(); 8 for(File root : roots){ 9 //列出每个盘符下的所有文件 10 getFiles(root); 11 } 12 File dir = new File("d:/"); 13 getFiles(dir); 14 } 15 //获取指定目录下的文件和文件夹 16 public static void getFiles(File dir) { 17 System.out.println("......"+dir); 18 //列出当前这个目录下的文件和文件夹对象 19 File[] files = dir.listFiles(); 20 if( files != null ){ 21 //遍历数组 22 for (File file : files) { 23 //判断当前的file是否是文件夹,是文件夹就需要继续列出其中的文件或文件夹 24 if( file.isDirectory() ){ 25 //判断成立说明当前的file对象一定是文件夹(目录) 26 getFiles(file); 27 }else{ 28 //判断不成立,说明当前的file对象一定是文件 29 System.out.println(file); 30 //file.delete(); 31 } 32 } 33 } 34 } 35 }