Java知识复习五
相关导航
Java剑开天门(一)
Java剑开天门(二)
Java剑开天门(三)
Java剑开天门(四)
Java剑开天门(五)
前言
本博文重在夯实Java基础的知识点,回归书本,夯实基础,学深学精
近期需要巩固项目和找实习,计划打算将Java基础从头复习一遍,夯实基础,可能对Java的一些机制不了解或者一知半解,正好借此机会深入学习,也算记录自己的一个学习总结。
Java基础参考书籍:《Java 开发实战经典 名师讲坛 第2版》与《Java核心技术 卷I》
本博文主要归纳整理Java应用程序设计方面的相关知识,如类集
、泛型
、 IO
、GUI
相关知识点。
一、Java类集框架
1、直观框架
Java集合框架主要包括两种类型的容器
,一种是集合(Collection)
,另一种是图(Map)
。Collection
接口又有3种子类型,List
、Set
和Queue
,再下面是一些抽象类
,最后是具体实现类
,常用的有ArrayList、LinkedList、HashSet、LinkedHashSet等等。Map
常用的有HashMap
,LinkedHashMap
等。
注意
接口 引用名称=new 具体实现类();//实例化一个实现类对象
是正确
的,利用了面向对象
的继承
和多态
思想。
2、Collection接口
2.1 List接口
List接口扩展自Collection,它可以定义一个允许重复
的有序集合
,从List接口中的方法来看,List接口主要是增加了面向位置的
操作,允许在指定位置上操作元素
,同时增加了一个能够双向遍历线性表
的新列表迭代器ListIterator
。AbstractList类提供了List接口的部分实现,AbstractSequentialList扩展自AbstractList,主要是提供对链表的支持。
- 常用方法
-
add(Object element): 向列表的尾部添加指定的元素。
-
size(): 返回列表中的元素个数。
-
get(int index): 返回列表中指定位置的元素,index从0开始。
-
add(int index, Object element): 在列表的指定位置插入指定元素。
-
set(int i, Object element): 将索引i位置元素替换为元素element并返回被替换的元素。
-
clear(): 从列表中移除所有元素。
-
isEmpty(): 判断列表是否包含元素,不包含元素则返回 true,否则返回false。
-
contains(Object o): 如果列表包含指定的元素,则返回 true。
-
remove(int index): 移除列表中指定位置的元素,并返回被删元素。
-
remove(Object o): 移除集合中第一次出现的指定元素,移除成功返回true,否则返回false。
-
11、iterator(): 返回按适当顺序在列表的元素上进行迭代的迭代器。
下面介绍List接口的两个重要的具体实现类,也是我们可能最常用的类,ArrayList
和LinkedList
。
- 如果
除了
在末尾
外不能在其他位置插入或者删除元素
,那么ArrayList效率更高
,如果需要经常插入或者删除元素
,就选择LinkedList
。
2.1.1 ArrayList
它是用数组存储元素
的,这个数组可以动态创建
,如果元素个数超过了数组的容量,那么就创建一个更大的新数组,并将当前数组中的所有元素都复制到新数组
中。
1、找到add()实现方法。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
2、此方法主要是确定将要创建的数组大小。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
3、最后是创建数组,可以明显的看到先是确定了添加元素后的大小之后将元素复制到新数组中。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
- 实例化
ArrayList<String> list = new ArrayList<String>(); //<String>泛型表示集合中存的是字符串形式的元素。
list.add("徐冰"); //add()用于向List集合容器中添加元素。
list.add("萤火");
--------------------
List<String> list = new ArrayList<String>(){{
add("string1");
add("string2");
}};
- 四种遍历方式
import java.util.*;
public class Test{
public static void main(String[] args){
List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("World");
list.add("Hello");
//第一种:withOutSize-for遍历。遍历时,内部不锁定, **效率**最高,
//但是当写多线程时要考虑并发操作的问题。
int size=list.size();
for(int i = 0;i < size; i ++){
System.out.println(list.get(i));
}
//第二种:withSize-for遍历。遍历时,内部不锁定,集合长度在循环内部,
// **效率**次于withOutSize-for的方法。
for(int i = 0;i < list.size(); i ++){
System.out.println(list.get(i));
}
//第三种方法: 使用迭代器
//第三种方法不用担心在遍历过程中超过集合的长度
//效率次于withSize-for方法
Iterator<String> ite = list.iterator();
while(ite.hasNext()){
System.out.println(ite.next());
}
//第四种方法: for-each遍历 list
// 内部调用第三种, 换汤不换药,
// 因此比Iterator 慢,这种循环方式还有其他限制, 不建议使用它。
//效率最慢
for(String str : list){
System.out.println(str);
}
}
}
For each其实也是用了迭代器来实现,因此当数据量变大时,两者的效率基本一致。也因为用了迭代器,所以速度上受了影响。不如直接get(index)快
。
-
那为何get(index)会比较快呢?
-
因为ArrayList是通过动态数组来实现的,支持随机访问,所以get(index)是很快的。迭代器,其实也是通过数组名+下标来获取,而且增加了逻辑,自然会比get(index)慢。
2.1.2 LinkedList
LinkedList
是在一个链表
中存储元素。
链表和数组
的最大区别在于它们对元素的存储方式的不同
导致它们在对数据进行不同操作时的效率不同
。
实例化方法同AraayList。
- LinkedList的四种遍历方式
LinkedList是通过双向链表实现的,无法支持随机访问。当你要向一个链表取第index个元素时,它需要二分后从某一端开始找,一个一个地数才能找到该元素。这样一想,就能明白为何get(index)如此费时了。
import java.util.*;
public class Test{
public static void main(String[] args){
List<String> list = new LinkedList<String>();
list.add("Hello");
list.add("World");
list.add("Hello");
//第一种:withOutSize-for遍历。遍历时,内部不锁定, **效率**最低,
int size=list.size();
for(int i = 0;i < size; i ++){
System.out.println(list.get(i));
}
//第二种:withSize-for遍历。遍历时,内部不锁定,集合长度在循环内部,
// **效率**高于withOutSize-for的方法。
for(int i = 0;i < list.size(); i ++){
System.out.println(list.get(i));
}
//第三种方法: 使用迭代器
//第三种方法不用担心在遍历过程中超过集合的长度
//效率最高
Iterator<String> ite = list.iterator();
while(ite.hasNext()){
System.out.println(ite.next());
}
//第四种方法: for-each遍历 list
// 内部调用第三种, 换汤不换药,
// 因此比Iterator 慢,这种循环方式还有其他限制, 不建议使用它。
//效率次于迭代器的方法
for(String str : list){
System.out.println(str);
}
}
}
2.1.3 CopyOnWriteArrayList
CopyOnWriteArrayList,是一个线程安全
的List接口的实现,它使用了ReentrantLock锁
来保证在并发情况
下提供高性能的并发
读取。
2.1.4 ArrayList和LinkedList四种遍历方式的总结
-
对于ArrayList和LinkedList,在
size小于1000
时,每种方式的差距都在几ms之间,差别不大,选择哪个方式都可以。 -
对于ArrayList,无论size是多大,
差距都不大
,选择哪个方式都可以
。 -
对于LinkedList,
当size较大时
,建议使用迭代器
或for-each
的方式进行遍历,否则效率会有较明显的差距。
所以,综合来看,建议使用for-each
,代码简洁,性能也不差。
2.2 Set接口
Set接口扩展自Collection,它与List
的不同之处在于,规定Set的实例不包含重复
的元素。
在一个规则集内,一定不存在两个相等的元素
。AbstractSet是一个实现Set接口的抽象类,Set接口有三个具体实现类,分别是散列集HashSet
、链式散列集LinkedHashSet
和树形集TreeSet
。
2.2.1 HashSet
散列集HashSet
是一个用于实现Set接口的具体类,可以使用它的无参构造
方法来创建空的散列集
,也可以由一个现有的集合
创建散列集
。- 在散列集中,有两个名词需要关注,
初始容量
和客座率
。
客座率是确定在增加
规则集之前
,该规则集的饱满程度
,当元素个数
超过了容量与客座率
的乘积时,容量就会自动翻倍
。 - 集合元素值可以为
null
; 不保证元素的排列顺序
,有可能排列顺序与添加顺序不同
;- 非同步集合,
多线程访问HashSet时,是不安全的
,需要通过同步代码保证同步。 - 元素
不可重复相同
,通过equals()和hashCode()方法
一起判断是否相同
。 - 当我们查询元素时,也是通过该元素的hashCode值快速定位到该元素在集合中的位置,其实HashSet底层是通过HashMap实现的。
实例化与List相同 - 两种遍历方式
在HashSet中同样不能使用for, 基本相同,for each(内置迭代器)较不稳定
import java.util.*;
public class Test{
public static void main(String[] args){
HashSet<String> set = new HashSet<String>();
set.add("Hello");
set.add("World");
set.add("Hello");
//第一种方法: 使用迭代器
//第一种方法不用担心在遍历过程中超过集合的长度
//效率相当,但稳定
Iterator<String> ite = set.iterator();
while(ite.hasNext()){
System.out.println(ite.next());
}
//第二种方法: for-each遍历 list
//效率相当,但不稳定
for(String str : set){
System.out.println(str);
}
}
}
2.2.2 LinkedHashSet
LinkedHashSet是用一个链表
实现来扩展HashSet类
,它支持对规则集内的元素排序
【有序】【不可重复】。HashSet中的元素是没有被排序的
,而LinkedHashSet
中的元素可以按照它们插入规则集的顺序提取
。
实例化与List相同
LinkedHashSet<String> linkedHashSet =new LinkedHashSet<String>();
linkedHashSet.add("a");
linkedHashSet.add("a");
linkedHashSet.add("a");
linkedHashSet.add("b");
linkedHashSet.add("b");
linkedHashSet.add("c");
linkedHashSet.add("d");
System.out.println(linkedHashSet);
----------------------------------------
输出
[a,b,c,d]
遍历方式及效率与HashSet相同
2.2.3 TreeSet
TreeSet扩展自AbstractSet,并实现了NavigableSet,AbstractSet扩展自AbstractCollection,树形集是一个有序
的Set,其底层是一颗树,这样就能从Set里面提取一个有序序列了
。
【简而言之,TresSet本身是可以自动排序的】
在实例化
TreeSet时,我们可以给TreeSet指定一个比较器Comparator来指定
树形集中的元素顺序
。树形集中提供了很多便捷的方法。
使用Comparator的三种方法
//第一种方法
//类实现Comparable接口,并重写compareTo方法
public class Person implements Comparable{
private int age;
private String name;
public Person(){}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public int compareTo(Object o) {
if (!(o instanceof Person))
throw new RuntimeException("不是人对象");
Person p = (Person) o;
if (this.age > p.age)
return 1;
if (this.age == p.age){
return this.name.compareTo(p.name);
}
return -1;
}
}
----------------------------------------
//第二种方法
//写个类实现Comparartor接口,重写compare方法
class myComparator implements Comparator{
@Override
public int compare(Object o1, Object o2) {
Person p1 = (Person) o1;
Person p2 = (Person) o2;
int num = p1.getName().compareTo(p2.getName());
// 0的话是两个相同,进行下一个属性比较
if (num == 0){
return new Integer(p1.getAge()).compareTo(new Integer(p2.getAge()));
}
return num;
}
}
//在new Set的时候放进去
TreeSet ts = new TreeSet(new myComparator());
----------------------------------------
//第三种方法
//写匿名内部类,重写compare方法
TreeSet ts = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Person p1 = (Person) o1;
Person p2 = (Person) o2;
int num = p1.getName().compareTo(p2.getName());
if (num == 0){
return new Integer(p1.getAge()).compareTo(new Integer(p2.getAge()));
}
return num;
}
});
实例化和遍历方式同HashSet
2.2.4 Queue
队列是一种先进先出
的数据结构,元素在队列末尾添加
,在队列头部删除
。Queue接口扩展自Collection,并提供插入、提取、检验等操作。
主要方法
//非阻塞队列
add(E e) : 将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则会抛出异常;
remove() :移除队首元素,若移除成功,则返回true;如果移除失败(队列为空),则会抛出异常;
offer(E e) :将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则返回false;
poll() :移除并获取队首元素,若成功,则返回队首元素;否则返回null;
peek() :获取队首元素,若成功,则返回队首元素;否则返回null
对于非阻塞队列,一般情况下建议使用**offer、poll和peek**三个方法,
不建议使用add和remove方法。
因为使用offer、poll和peek三个方法可以通过返回值判断操作成功与否,
而使用add和remove方法却不能达到这样的效果。
注意,非阻塞队列中的方法都没有进行同步措施。
//阻塞队列(ArrayBlockingQueue、LinkedBlockingQueue、
//PriorityBlockingQueue、DelayQueue)
阻塞队列包括了非阻塞队列中的大部分方法,
上面列举的5个方法在阻塞队列中都存在,
但是要注意这5个方法在阻塞队列中都进行了同步措施。
特有的方法
put(E e) : 用来向队尾存入元素,如果队列满,则等待;
take() : 用来从队首取元素,如果队列为空,则等待;
offer(E e,long timeout, TimeUnit unit) : 用来向队尾存入元素,如果队列满,则等待一定的时间,当时间期限达到时,如果还没有插入成功,则返回false;否则返回true;
poll(long timeout, TimeUnit unit) : 用来从队首取元素,如果队列空,则等待一定的时间,当时间期限达到时,如果取到,则返回null;否则返回取得的元素;
添加元素和以上三个Set不同
Queue<Integer> q = new LinkedBlockingQueue<Integer>();
//初始化队列
for (int i = 0; i < 5; i++) {
q.offer(i);
}
//使用offer()方法实现数据添加
实例化和遍历
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 队列的遍历
*
* @author leizhimin 2009-7-22 15:05:14
*/
public class TestQueue {
public static void main(String[] args) {
Queue<Integer> q = new LinkedBlockingQueue<Integer>();
//初始化队列
for ( int i = 0; i < 5; i++) {
q.offer(i);
}
System.out.println( "-------1-----");
//集合方式遍历,元素不会被移除
for (Integer x : q) {
System.out.println(x);
}
System.out.println( "-------2-----");
//迭代器遍历
Iterator it = queue.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
System.out.println( "-------3-----");
//队列方式遍历,元素逐个被移除
while (q.peek() != null) {
System.out.println(q.poll());
}
System.out.println( "-------4-----");
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
}
}
总结,与以上三种的遍历方式基本相同,但增加了两种特有的遍历方式
2.2.5 遍历方式总结
建议使用for each
遍历,效率和显式迭代器遍历差不多,但是可以和List对应起来
,便于记忆。
2.3 Map接口
Map
操作的是一对
对象,就是我们常说的"键值对"
,Map中每个元素都用key-value
的形式存储。
常用方法
添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元素视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
总结
添加:put(Object key,Object value)
删除:remove(Object key)
修改:put(Object key,Object value)
查询:get(Object key)
长度:size()
遍历:keySet() / values() / entrySet()
2.3.1 HashMap
HashMap是基于哈希表
的Map接口的非同步
实现,继承自AbstractMap,AbstractMap是部分实现Map接口的抽象类。元素是无序
的。
JDK1.8
之后,(之前是数组+链表
)HashMap采用数组+链表+红黑树
实现,当链表长度超过阈值(8)
时,将链表
转换为红黑树
,这样大大减少了查找时间。
- 五种遍历方法
1.使用 Iterator 遍历 HashMap EntrySet
2.使用 Iterator 遍历 HashMap KeySet
3.使用 For-each 循环迭代 HashMap
4.使用 Lambda 表达式遍历 HashMap
5.使用 Stream API 遍历 HashMap
----------------------------------------
1. 使用 Iterator 遍历 HashMap EntrySet
package com.java.tutorials.iterations;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
/**
* 在 Java 中遍历 HashMap 的5种最佳方法
*
*/
public class IterateHashMapExample {
public static void main(String[] args) {
// 1. 使用 Iterator 遍历 HashMap EntrySet
Map < Integer, String > coursesMap = new HashMap < Integer, String > ();
coursesMap.put(1, "C");
coursesMap.put(2, "C++");
coursesMap.put(3, "Java");
coursesMap.put(4, "Spring Framework");
coursesMap.put(5, "Hibernate ORM framework");
Iterator < Entry < Integer, String >> iterator = coursesMap.entrySet().iterator();
while (iterator.hasNext()) {
Entry < Integer, String > entry = iterator.next();
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
}
2. 使用 Iterator 遍历 HashMap KeySet
package com.java.tutorials.iterations;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* 在 Java 中遍历 HashMap 的5种最佳方法
*
*/
public class IterateHashMapExample {
public static void main(String[] args) {
Map < Integer, String > coursesMap = new HashMap < Integer, String > ();
coursesMap.put(1, "C");
coursesMap.put(2, "C++");
coursesMap.put(3, "Java");
coursesMap.put(4, "Spring Framework");
coursesMap.put(5, "Hibernate ORM framework");
// 2. 使用 Iterator 遍历 HashMap KeySet
Iterator < Integer > iterator = coursesMap.keySet().iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
System.out.println(key);
System.out.println(coursesMap.get(key));
}
}
}
3. 使用 For-each 循环遍历 HashMap
package com.java.tutorials.iterations;
import java.util.HashMap;
import java.util.Map;
/**
* 在 Java 中遍历 HashMap 的5种最佳方法
*
*/
public class IterateHashMapExample {
public static void main(String[] args) {
Map < Integer, String > coursesMap = new HashMap < Integer, String > ();
coursesMap.put(1, "C");
coursesMap.put(2, "C++");
coursesMap.put(3, "Java");
coursesMap.put(4, "Spring Framework");
coursesMap.put(5, "Hibernate ORM framework");
// 3. 使用 For-each 循环遍历 HashMap
for (Map.Entry < Integer, String > entry: coursesMap.entrySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
}
4. 使用 Lambda 表达式遍历 HashMap
package com.java.tutorials.iterations;
import java.util.HashMap;
import java.util.Map;
/**
* 在 Java 中遍历 HashMap 的5种最佳方法
*
*/
public class IterateHashMapExample {
public static void main(String[] args) {
Map < Integer, String > coursesMap = new HashMap < Integer, String > ();
coursesMap.put(1, "C");
coursesMap.put(2, "C++");
coursesMap.put(3, "Java");
coursesMap.put(4, "Spring Framework");
coursesMap.put(5, "Hibernate ORM framework");
// 4. 使用 Lambda 表达式遍历 HashMap
coursesMap.forEach((key, value) -> {
System.out.println(key);
System.out.println(value);
});
}
}
5. 使用 Stream API 遍历 HashMap
package com.java.tutorials.iterations;
import java.util.HashMap;
import java.util.Map;
/**
* 在 Java 中遍历 HashMap 的5种最佳方法
*
*/
public class IterateHashMapExample {
public static void main(String[] args) {
Map < Integer, String > coursesMap = new HashMap < Integer, String > ();
coursesMap.put(1, "C");
coursesMap.put(2, "C++");
coursesMap.put(3, "Java");
coursesMap.put(4, "Spring Framework");
coursesMap.put(5, "Hibernate ORM framework");
// 5. 使用 Stream API 遍历 HashMap
coursesMap.entrySet().stream().forEach((entry) - > {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
});
}
}
//两个 entrySet 的性能相近,并且执行速度最快,
//接下来是 stream ,然后是两个 keySet,性能最差的是 KeySet
// entrySet 的性能比 keySet 的性能高出了一倍之多,
//因此我们应该尽量使用 entrySet 来实现 Map 集合的遍历。
2.3.2 LinkedHashMap
LinkedHashMap继承自HashMap,它主要是用链表
实现来扩展HashMap类
,HashMap中条目是没有顺序的
,但是在LinkedHashMap中元素既可以按照它们插入图的顺序排序
,也可以按它们最后一次被访问的顺序排序
。
LinkedHashMap是通过比HashMap多了一个双向链表实现的有序
。
遍历方式基本上与HashMap相同。但是有按照何种顺序遍历的分类。
- 按照
插入顺序
遍历 - 按照
访问顺序
遍历
是否使用访问顺序遍历,是通过LinkedHashMap 的accessOrder参数
控制的,true
为访问顺序
遍历,false
为插入顺序
遍历。
设置该值只能在创建LinkedHashMap 时通过构造方法
设置的。
/**
* 实例化一个LinkedHashMap;
*
* LinkedHashMap的插入顺序和访问顺序;
* LinkedHashMap(int initialCapacity,
* float loadFactor, boolean accessOrder);
* 说明:
* 当accessOrder为true时表示当前数据的插入读取顺序为访问顺序;
* 当accessOrder为false时表示当前数据的插入读取顺序为插入顺序;
*/
Map<String,String> linkedHashMap = new
LinkedHashMap<String,String>(0,1.6f,true); // 访问顺序;
// Map<String,String> linkedHashMap = new
//LinkedHashMap<String,String>(0,1.6f,false); // 插入顺序;
2.3.3 TreeMap
TreeMap基于红黑树
数据结构的实现,键值可以使用Comparable或Comparator接口
来排序
。TreeMap继承自AbstractMap,同时实现了接口NavigableMap,而接口NavigableMap则继承自SortedMap。
SortedMap是Map的子接口,使用它可以确保图中的条目
是排好序
的。
TreeMap 默认排序规则
:按照key的字典顺序
来排序(升序
)
当然,也可以自定义排序规则
:要实现Comparator接口
。
在实际使用中,如果更新图时不需要
保持图中元素的顺序
,就使用HashMap
,如果需要
保持图中元素的插入顺序或者访问顺序
,就使用LinkedHashMap
,如果需要使图按照键值排序
,就使用TreeMap
。
2.3.4 ConcurrentHashMap
Concurrent,并发
,从名字就可以看出来ConcurrentHashMap是HashMap的线程安全版
。同HashMap相比,ConcurrentHashMap不仅保证了访问的线程安全性
,而且在效率
上与HashTable相比,也有较大的提高
。
2.3.5 遍历方式总结
对照其它集合,建议使用 for each 循环迭代 HashMap的entrySet进行Map的遍历。
for (Map.Entry < Integer, String > entry: coursesMap.entrySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
2.4 集合遍历方式总结
从熟练和效率考虑,Collection和Map集合都建议利用for each
内置迭代器进行集合遍历
,其中,Map系列利用for each
遍历entrySet
。
二、泛型
泛型的本质是参数化类型
,也就是说所操作的数据类型被指定为一个参数。这种参数类型
可以用在类、接口和方法的创建
中,分别称为泛型类、泛型接口、泛型方法。
Java语言引入泛型的好处是安全简单
。主要针对类型转换
。
对于常见的泛型模式,推荐的名称是:
K ——键,比如映射的键。
V ——值,比如 List 和 Set 的内容,或者 Map 中的值。
E ——异常类。
T ——泛型。
1、泛型类
泛型类中的类型参数几乎可以用于任何可以使用接口名、类名的地方。
public interface Map<K, V> {
public void put(K key, V value);
public V get(K key);
}
2、泛型方法
泛型方法使得该方法能独立于类
而产生变化
。
以下是一个基本的指导原则:无论何时,只要你能做到,你就应该尽量使用泛型方法
。也就是说,如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法,
因为它可以使事情更清楚明白
。另外,对于一个static的方法而言,无法访问泛型类的类型参数。所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。
要定义
泛型方法,只需将泛型参数列表
置于返回值之前
。
示例
package Generics;
public class GenericMethods {
//当方法操作的引用数据类型不确定的时候,可以将泛型定义在方法上
public <T> void f(T x){
System.out.println(x.getClass().getName());
}
public static void main(String[] args) {
GenericMethods gm = new GenericMethods();
gm.f(99);
gm.f("掌上洪城");
gm.f(new Integer(99));
gm.f(18.88);
gm.f('a');
gm.f(gm);
}
}
/* 输出结果:
java.lang.Integer
java.lang.String
java.lang.Integer
java.lang.Double
java.lang.Character
Generics.GenericMethods
*/
泛型方法与可变参数列表能很好地共存
package Generics;
import java.util.ArrayList;
import java.util.List;
public class GenericVarargs {
public static <T> List<T> makeList(T... args){
List<T> result = new ArrayList<T>();
for(T item:args)
result.add(item);
return result;
}
public static void main(String[] args) {
List ls = makeList("A");
System.out.println(ls);
ls = makeList("A","B","C");
System.out.println(ls);
ls = makeList("ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""));
System.out.println(ls);
}
}
/*
[A]
[A, B, C]
[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]
*/
静态方法上的泛型
静态方法无法访问类上定义的泛型。如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
public static <Q> void function(Q t) {
System.out.println("function:"+t);
}
3、泛型通配符 ?
可以解决当具体类型不确定
的时候,这个通配符就是?
;
当操作类型时,不需要使用类型的具体功能
时,只使用Object类
中的功能。那么可以用 ?
通配符来表未知类型
。
Class<?>classType = Class.forName("java.lang.String");
4、泛型限定表达式
- 上限:?extends E:可以接收E类型或者E的子类型对象。
- ?super E:可以接收E类型或者E的父类型对象。
上限什么时候用:往集合
中添加元素时,既可以添加E类型对象
,又可以添加E的子类型
对象。为什么?因为取的时候,E类型既可以接收E类对象
,又可以接收E的子类型对象
。
下限什么时候用:当从集合中获取元素进行操作的时候,可以用当前元素的类型接收
,也可以用当前元素的父类型接收
。
5、应用场景
当接口、类及方法中的操作的引用数据类型不确定
的时候,以前用的Object来进行扩展的,现在可以用泛型
来表示。这样可以避免强转
的麻烦。
泛型的细节
-
泛型到底
代表什么类型
取决于调用者传入的类型
,如果没传
,默认是Object类型
; -
使用
带泛型的类
创建对象
时,等式两边
指定的泛型
必须一致;原因:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了;
-
等式
两边可以在任意一边
使用泛型
,在另一边不使用
(考虑向后
兼容)。
三、IO操作
1、直观图
2、字节流和字符流
- 字节流可以处理
所有类型的数据
,如MP3、图片、文字、视频等。在读取时,读到一个字节就返回一个字节。
在Java中对应的类都以“Stream”结尾
。 - 字符流仅能够处理
纯文本数据,如txt文本等
。在读取时,读到一个或多个字节,先查找指定的编码表,然后将查到的字符返回。
在Java中对应的类都以“Reader”或“Writer”结尾
。
3、读取文件示例
public class ReadFromFile {
/**
* 以字节为单位读取文件,常用于读**二进制文件**,如图片、声音、影像等文件。
*/
public static void readFileByBytes(String fileName) {
File file = new File(fileName);
InputStream in = null;
try {
System.out.println("以字节为单位读取文件内容,一次读一个字节:");
// 一次读一个字节
in = new FileInputStream(file);
int tempbyte;
while ((tempbyte = in.read()) != -1) {
System.out.write(tempbyte);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
return;
}
try {
System.out.println("以字节为单位读取文件内容,一次读多个字节:");
// 一次读多个字节
byte[] tempbytes = new byte[100];
int byteread = 0;
in = new FileInputStream(fileName);
ReadFromFile.showAvailableBytes(in);
// 读入多个字节到字节数组中,byteread为一次读入的字节数
while ((byteread = in.read(tempbytes)) != -1) {
System.out.write(tempbytes, 0, byteread);
}
} catch (Exception e1) {
e1.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e1) {
}
}
}
}
/**
* 以字符为单位读取文件,常用于读文本,数字等类型的文件
*/
public static void readFileByChars(String fileName) {
File file = new File(fileName);
Reader reader = null;
try {
System.out.println("以字符为单位读取文件内容,一次读一个字节:");
// 一次读一个字符
reader = new InputStreamReader(new FileInputStream(file));
int tempchar;
while ((tempchar = reader.read()) != -1) {
// 对于windows下,\r\n这两个字符在一起时,表示一个换行。
// 但如果这两个字符分开显示时,会换两次行。
// 因此,屏蔽掉\r,或者屏蔽\n。否则,将会多出很多空行。
if (((char) tempchar) != '\r') {
System.out.print((char) tempchar);
}
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
System.out.println("以字符为单位读取文件内容,一次读多个字节:");
// 一次读多个字符
char[] tempchars = new char[30];
int charread = 0;
reader = new InputStreamReader(new FileInputStream(fileName));
// 读入多个字符到字符数组中,charread为一次读取字符数
while ((charread = reader.read(tempchars)) != -1) {
// 同样屏蔽掉\r不显示
if ((charread == tempchars.length)
&& (tempchars[tempchars.length - 1] != '\r')) {
System.out.print(tempchars);
} else {
for (int i = 0; i < charread; i++) {
if (tempchars[i] == '\r') {
continue;
} else {
System.out.print(tempchars[i]);
}
}
}
}
} catch (Exception e1) {
e1.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
}
}
}
}
/**
* 以行为单位读取文件,常用于读面向行的格式化文件
*/
public static void readFileByLines(String fileName) {
File file = new File(fileName);
BufferedReader reader = null;
try {
System.out.println("以行为单位读取文件内容,一次读一整行:");
reader = new BufferedReader(new FileReader(file));
String tempString = null;
int line = 1;
// 一次读入一行,直到读入null为文件结束
while ((tempString = reader.readLine()) != null) {
// 显示行号
System.out.println("line " + line + ": " + tempString);
line++;
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
}
}
}
}
/**
* 随机读取文件内容
*/
public static void readFileByRandomAccess(String fileName) {
RandomAccessFile randomFile = null;
try {
System.out.println("随机读取一段文件内容:");
// 打开一个随机访问文件流,按只读方式
randomFile = new RandomAccessFile(fileName, "r");
// 文件长度,字节数
long fileLength = randomFile.length();
// 读文件的起始位置
int beginIndex = (fileLength > 4) ? 4 : 0;
// 将读文件的开始位置移到beginIndex位置。
randomFile.seek(beginIndex);
byte[] bytes = new byte[10];
int byteread = 0;
// 一次读10个字节,如果文件内容不足10个字节,则读剩下的字节。
// 将一次读取的字节数赋给byteread
while ((byteread = randomFile.read(bytes)) != -1) {
System.out.write(bytes, 0, byteread);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (randomFile != null) {
try {
randomFile.close();
} catch (IOException e1) {
}
}
}
}
/**
* 显示输入流中还剩的字节数
*/
private static void showAvailableBytes(InputStream in) {
try {
System.out.println("当前字节输入流中的字节数为:" + in.available());
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
String fileName = "C:/temp/newTemp.txt";
ReadFromFile.readFileByBytes(fileName);
ReadFromFile.readFileByChars(fileName);
ReadFromFile.readFileByLines(fileName);
ReadFromFile.readFileByRandomAccess(fileName);
}
}
五、GUI
图形化的用户界面(Graphical User Interface,简称GUI),Java提供了Swing组件和JavaFx进行GUI开发。
前者是通过代码进行GUI绘制与事件注册;
后者是通过拖拽进行布局。
重点介绍JavaFx
这一组件。
5.1 Swing组件
-
java.awt 包 – 主要提供字体/布局管理器
-
javax.swing 包[商业开发常用] – 主要提供各种组件(窗口/按钮/文本框)
-
java.awt.event 包 – 事件处理,后台功能的实现。
5.2JavaFx配置
简单来说,配置主要分为以下几步
- 下JDK(7以上)
- 装JavaFx Scence Builder 下载链接
- IDEA装JavaFx插件
- IDEA指定JavaFx Scence Builder的可执行文件的路径
- 使用MVC模式进行开发
5.3 JavaFx项目开发
- Main.java
包括一个start()方法
和main()方法
,start()方法
是所有JavaFX应用程序的入口
,是必需的;
而当JavaFX应用程序是通过JavaFX Packager工具打包
时,main()方法就不是必需的的了
,因为JavaFX Package工具会将JavaFX Launcher嵌入到JAR文件中。该文件使用FXMLLoader类,该类负责加载FXML源文件sample.fxml并返回结果对象图。 - sample.fxml
fxml源文件,引用样式表
和Controller
、定义JavaFX组
件等,即跟用户界面相关的定义
都在这里 - Controller.java
定义sample.fxml中的组件id
并实现sample.fxml
中事件引发的方法
,主要用于键盘
、鼠标
事件的处理
。 - 本博主开发的话,会自定义一个
Model
文件夹,存放业务逻辑与数据读取
方法。
六、总结
关于JDBC的一些内容会在后续的Spring系列结合Mybatis-plus进行归纳,敬请期待。