一、泛型
1、泛型的概念
定义
广泛的类型,在定义一个类的时候,类型中有些方法参数、返回值类型不确定,就使用一个符号,来表示那些尚未确定的类型,这个符号,就称为泛型。
使用
对于有泛型的类型,在这些类型后面跟上尖括号,尖括号里面写上泛型的确定类型(在使用某个类创建对象时,已经可以确定这个具体的类型了,那么就直接写出具体类型)
例如:List al = new ArrayList();
泛型的好处
1、提高了数据的安全性,将运行时的问题,提前暴露在编译时期
2、避免了强转的麻烦
注意事项
1、前后一致:在创建对象时,赋值符号前面和后面的类型的泛型,必须一致。
2、泛型推断:如果前面的引用所属的类型已经写好了泛型,后面创建对象的类型就可以只写一个尖括号,尖括号中可以不写任何内容。<>特别像菱形,称为“菱形泛型”,jdk1.7特性。
(1)不使用泛型
示例代码
public class Demo01_不使用泛型 {
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
list.add("aaa");
list.add(new Date());
for(int i=0;i<list.size();i++){
Integer x = (Integer)list.get(i);
}
}
}
(2)使用泛型
示例代码
public class Demo02_使用泛型 {
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("aaa");
list.add("bbb");
//list.add(111);
for(String str:list){
}
}
}
2、泛型类
格式
class 类名<泛型类型1,泛型类型2,…>{
}
说明
1、类名后面跟着的泛型类型,是泛型的声明,一旦泛型声明出来,就相当于这个类型成为了已知类型,这个类型就可以在整个类中使用。
2、泛型的声明名称,只需要是一个合法的标识符即可,但是通常我们使用单个大写字母来表示,常用字母:T、W、Q、K、V、E。
3、泛型确定的时机:将来在使用这个类和创建对象的时候。
需求:模拟ArrayList定义MyArrayList 完成add、get和size方法
示例代码
public class MyArrayList<T> {
private Object[] objs; //承装数据的数组
private int size; // 定义集合中元素的个数
public MyArrayList(){
objs = new Object[10]; // 数组初始化10个长度
}
public void add(T t){
if(size>=objs.length){ // 集合中元素个数大于等于数组长度时,需要对数组进行扩容
Object[] oldObjs = objs; // 先把承装数据的数组赋给 oldObjs这个引用,用来保留原有数据,因为objs要指向新的数组
objs = new Object[oldObjs.length+10];// 扩容10个长度
System.arraycopy(oldObjs,0,objs,0,oldObjs.length); // 把原有数据拷贝回去
}
objs[size]=t;
size++;
}
public int size(){
return size;
}
public T get(int index){
if(index>=size){
throw new RuntimeException("没这么多元素");
}
return (T)objs[index];
}
}
3、泛型方法
格式
修饰符 <泛型声明1, 泛型声明2,…> 返回值类型 方法名称(参数列表) {
}
说明
1、在方法上声明的泛型,可以在整个方法中,当做已知类型来使用。
2、如果【非静态】方法上没有任何泛型的声明,那么可以使用类中定义的泛型
3、如果【静态】方法上没有任何的泛型声明,那么就不能使用泛型,连类中定义的泛型,也不能使用,因为类中的泛型需要在创建对象的时候才能确定。所以【静态】方法想使用泛型,就必须在自己的方法上单独声明。
需求:写一个方法,调用者传递什么类型的变量,该方法就返回什么类型的变量?
实现一:
由于无法确定具体传递什么类型的数据,那么方法的形参就定义为Object类型,返回值也就是Object类型。但是使用该方法时需要强制类型转换。
示例代码
private Object getDate(Object obj) {
return obj;
}
当不进行强制类型转换能否写出该功能?
目前所学的知识无法解决该问题。
就需要使用泛型类解决。
使用的泛型的自定义来解决以上问题。
示例代码
public static void main(String[] args) {
int x = new Demo().getData(5);
String str = new Demo().getData(“aaa”);
}
public <T> T getData(T data) {
return data;
}
4、泛型接口
格式
interface 接口名称<泛型类型1, 泛型类型2,…> {
}
说明
1、在接口声明上,定义好的泛型,可以在整个接口中当做已知类型来使用。
2、泛型接口被其他类实现的时候,有两种实现方式:
(1)声明的类不再是一个泛型类,而是一个确定了泛型的类,格式如下: class 实现类类名 implements 接口名<具体类型> {
所有的泛型类型都已经被确定
}
(2)声明的类还是一个泛型类,泛型的类型和接口的泛型一致,格式如下:
class 实现类类名<泛型标识符1> implements 接口名<泛型标识符1> {
所有的方法还都是可以使用泛型标识符的方法
}
集合体系中,大量的接口和实现类都存在实现和继承的关系。
Collection…继承关系…List…实现关系…ArrayList
示例代码
interface Inter<T> {
void print(T t);
}
// 实现不知为何类型时可以这样定义
class MyInter<T> implements Inter<T> {
public void print(T t) {
System.out.println("myprint:" + t);
}
}
//使用接口时明确具体类型
class MyInter2 implements Inter<String> {
@Override
public void print(String t) {
System.out.println("myprint:" + t);
}
}
public static void main(String[] args) {
MyInter<String> my = new MyInter<String>();
my.print("泛型");
MyInter2 my2 = new MyInter2();
my.print("只能传字符串");
}
}
5、泛型通配符
1、使用泛型的时候,没有使用具体的泛型声明T,而是使用了和声明过的某个泛型T有关的一类类型,就称为泛型的通配符。三种形式:
第一种形式,使用?来表示可以是任意类型,例如:
Collection接口中的removeAll(Collection<?> c),表示可以接收任意泛型类型的集合,作为该方法的实际参数。参数集合的泛型,可以是与E没有任何关系
第二种形式,使用? extends E来表示必须是某个泛型类型或是该泛型类型的子类,例如:
Collection接口中的addAll(Collection<? extends E> c),表示可以接收泛型类型是调用者泛型类型或者其子类的集合,作为该方法的实际参数。参数的泛型和调用者的泛型,必须有关(相同或者是子父类)。确定了泛型的上边界。
第三种形式,使用? super E来表示必须是某个泛型类型或者是该泛型类型的父类,例如:
TreeSet集合中,存储的都是E类型的元素,构造方法TreeSet(Comparator<? super E> com),表示可以接收泛型类型是集合元素类型或者是元素类型的父类的比较器,作为构造方法的参数。参数的泛型和集合的泛型必须有关(相同或者是子父类)。确定了泛型的下边界。
示例代码
public class Demo06_泛型通配符 {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
coll.add("xxxx");
Collection<Integer> c = new ArrayList<>();
coll.removeAll(c); // removeAll(Collection<?> c) 表示传入的Collection对象的泛型是什么类型都可以
// 如果 addAll(Collection<? extends E> c) 表示传入的Collection对象c的泛型只能是E或是E的子类(比如本例中E是String)
// 如果 xxx(Collection<? super E> c) 表示传入的Collection对象c的泛型只能是E或是E的父类(比如本例中E是String)
}
}
? extends E: 接收E类型或者E的子类型。
? super E: 接收E类型或者E的父类型。
正确:Vector<? extends Number> x = new Vector();
错误:Vector<? extends Number> x = new Vector();
二、Map(映射)
1、 Map的含义
Map:双列集合的顶层接口。
Map:单词含义,地图。地图上的每个点,都表示了生活中的一个具体位置。地图的点和生活中的位置,有一个一一对应的关系,这种关系是通过穷举的方式来描述的。
Map是一个以键(key)值(value)对形式存储数据的容器。
2、Map的特点
Map 是一个接口,但不是Collection的子接口。
Map以键值对的形式存储数据,每个键(key)对应一个值(value)。
Map 中的键不能重复(唯一)。
Map 中的值可以重复。
主要实现类 HashMap Hashtable TreeMap …
3、Map结构图
4、Map常用方法
put(K key, V value) 向Map中添加数据
putAll(Map<? extends K,? extends V> m) 向Map中添加另一个Map集合。
isEmpty() Map中是否包含数据
size() Map中包含键值对的个数
get(key) 根据key获取value
clear() 清空Map
containsKey(key) 判断Map中是否包含key
containsValue(value) 判断Map中是否包含value
keySet() 返回Map中所有key 组成的Set集合
values() 返回Map中所有value组成的集合 (返回值Collection类型)
entrySet() 返回此映射中包含的映射关系的 Set 视图
5、HashMap
HashMap是Map接口的最常用实现类,底层是哈希表形式存储的,非线程安全的,允许有null键值对。
(1)使用HashMap演示以上方法
示例代码
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
map.put("aaa","111"); // key不能重复,value 可以重复
map.put("bbb","222");
map.put("ccc", "333");
map.put("ddd", "111"); // value可以重复
map.put(null,null);
System.out.println(map.put("aaa", "100")); // key不能重复,会覆盖原有的value
// put方法的返回值 是存入这个key时,之前这个key值在map中对应的value值
System.out.println(map.size()); // 获得Map中包含多少元素
String str = map.get("aaa"); // 根据key获取value
System.out.println(str);
String str2 = map.get("xxx"); // 当key不存在时返回null
System.out.println(str2);
System.out.println(map.containsKey("aaa")); // 判断是否包含某个key
System.out.println(map.containsKey("xxx"));
System.out.println(map.containsValue("222")); //判断是否包含某个value
System.out.println(map.containsValue("555"));
map.remove("aaa"); // 根据key删除数据
System.out.println(map.get("aaa"));
System.out.println(map.isEmpty()); //判断map中是否包含数据
map.clear(); // 清空map
System.out.println(map.isEmpty());
}
(2)Map遍历
方式一:使用keySet()方法
1、获取Map集合中的所有键,放到一个Set集合中,遍历该Set集合,获取到每一个键,根据键再来获取对应的值。【根据键获取值】
2、获取Map集合中的所有键
Set keySet()
图示
示例代码
Set<String> keys = map.keySet();
for(String key:keys){
System.out.println(key+"--->"+map.get(key));
}
方式二:使用entrySet()方法
获取Map集合中的所有键值对对象(Entry),到Set集合中,遍历Set集合,拿到的是每个键值对对象(Entry),从这个对象中分别获取键和值。【根据键值对对象获取键和值】
根据Map集合获取所有的键值对对象,到一个Set集合中
Set<Map.Entry<K, V>> entrySet()
Entry是Map接口中的内部接口,访问的方式:Map.Entry
Entry的常用方法:
getKey()获取当前键值对对象的键
getValue()获取当前键值对对象的值
图示
示例代码
Set<Map.Entry<String,String>> es = map.entrySet();
for(Map.Entry<String,String> entry:es){
//System.out.println(entry);
System.out.println(entry.getKey()+"--->"+entry.getValue());
}
(3)案例
案例1:编写一个方法,完成英译汉翻译功能。
dog—>狗
cat—>猫
pig—>猪
monkey—>猴
cow—> 牛
示例代码
public class Demo03_Map练习_英译汉 {
public static void main(String[] args) {
String result = translate("lion");
if(result!=null){
System.out.println(result);
}else{
System.out.println("查无此词");
}
}
public static String translate(String word){
// 词典
Map<String,String> map = new HashMap<String, String>();
map.put("cat", "猫");
map.put("dog", "狗");
map.put("monkey", "猴子");
map.put("pig", "猪");
map.put("elephant", "大象");
map.put("tiger", "老虎");
map.put("fox", "狐狸");
map.put("lion", "狮子");
String result = map.get(word);
return result;
}
}
案例2:键盘录入一个字符串,统计每个字符出现的次数
例如,录入aaaabbccddd!@#@#KaTeX parse error: Expected 'EOF', got '#' at position 2: @#̲%cc66ff
打印出来:a有4个,b有2个,c有4个,d有3个,!有1个,@有3个,$有2个,%有1个,6有2个,f有2个
public class Demo04_Map练习_统计字符出现的次数 {
/*
键盘录入一个字符串,统计每个字符出现的次数
例如,录入aaaabbccddd!@#@#$@#$%cc66ff
打印出来:a有4个,b有2个,c有4个,d有3个,!有1个,@有3个,$有2个,%有1个,6有2个,f有2个
思路:
1 使用Map key-->字符 value-->次数
2 遍历字符串取到字符
3 判断取到的字符在map的key中是否存在 存在,根据key取出次数 再+1 存回去。 不存在,把取到字符当成key存入map并存入次数(value) 1
* */
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str = sc.nextLine();
Map<String,Integer> map = new HashMap<String, Integer>();
for(int i=0;i<str.length();i++){
char c = str.charAt(i);
String key = c+"";
//Integer count = map.get(c+""); // 为null 说明map中没有这个字符
boolean flag = map.containsKey(key);
int count = 0;
if(flag){
count = map.get(key);
}
map.put(key, ++count);
}
Set<Map.Entry<String,Integer>> set = map.entrySet();
for(Map.Entry<String,Integer> entry:set){
System.out.println(entry.getKey()+" : "+entry.getValue());
}
}
}
6、Hashtable
Hashtable是一个比较古老的类(从JDK1.2开始),特点是线程安全的,不允许有null键值对。用法和HashMap相同,基本被HashMap替代。
(1)面试常见问题HashMap和Hashtable的区别?
HashMap: 线程不安全,允许null键值对
Hashtable: 线程安全,不允许null键值对
7、TreeMap
特点:会根据key值进行升序排列。
示例代码
public class Demo06_TreeMap {
/*
* 底层是由红黑数(自平衡的二叉树)来实现的 会根据key升序排序
* key需要有排序的能力 或传入比较器
* */
public static void main(String[] args) {
Map<String,String> map = new TreeMap<String,String>();
map.put("bbb","222");
map.put("ddd","444");
map.put("eee","555");
map.put("ccc","333");
map.put("aaa","111");
Set<Map.Entry<String,String>> set = map.entrySet();
for(Map.Entry<String,String> entry:set){
System.out.println(entry.getKey()+"---"+entry.getValue());
}
}
}
8、LinkedHashMap
是HashMap的一个子类。
和HashMap的不同之处在于,具有可预知的迭代顺序,存储键值对的顺序和遍历集合时取出键值对的顺序一致。
示例代码
public class Demo07_LinkedHashMap {
public static void main(String[] args) {
/*
LinkedHashMap 是 HashMap的子类 不同在于 LinkedHashMap有序(和添加顺序一致)
*/
Map<String,String> map = new LinkedHashMap<String,String>();
map.put("aaa","111");
map.put("bbb","222");
map.put("ccc","333");
map.put("ddd","444");
Set<Map.Entry<String,String>> set = map.entrySet();
for(Map.Entry<String,String> entry:set){
System.out.println(entry.getKey()+" "+entry.getValue());
}
}
}
9、斗地主练习
斗地主的制牌、洗牌和发牌
思路
1、制牌:1~K一共13个数字,四个花色,4 * 13张牌,小王、大王
2、洗牌:shuffle将集合中的字符串随机置换
3、发牌:将一个集合中的元素,分发到3个集合中
问题
发牌之后,每个人的牌都没有顺序,无法洗牌,字符串的字典顺序,和扑克牌中的大小顺序不同,不能使用字典顺序来直接排序。
解决
1、通过穷举,手动将所有牌面的大小,全都定义出来,每个牌面上的字符串,都可以对应一个其在扑克牌中的大小的数字(字符串和数字的对应关系)
2、可以给牌面的大小排序,对应到某个牌面
图示
发牌后不带自动排序的实现
示例代码
public class Demo02_斗地主_制牌_洗牌_发牌 {
public static void main(String[] args) {
String[] nums = {"A","2","3","4","5","6","7","8","9","10","J","Q","K"};
String[] colors = {"红桃","黑桃","梅花","方块"};
List<String> poker = new ArrayList()<String>();
//制牌
for(String color:colors){
for(String num:nums){
poker.add(color+num);
}
}
poker.add("小王");
poker.add("大王");
//洗牌
Collections.shuffle(poker);
//发牌
List<String> xiaodao = new ArrayList<String>();
List<String> longwu = new ArrayList<String>();
List<String> me = new ArrayList<String>();
List<String> dipai = new ArrayList<String>();
for(int i=1;i<=3;i++){
dipai.add(poker.remove(0));
}
while(true){
if(!poker.isEmpty()){
xiaodao.add(poker.remove(0));
}
if(!poker.isEmpty()){
longwu.add(poker.remove(0));
}
if(!poker.isEmpty()){
me.add(poker.remove(0));
}else{
break;
}
}
System.out.println(dipai);
System.out.println(xiaodao);
System.out.println(longwu);
System.out.println(me);
}
}
发牌后带自动排序的实现
示例代码
public class Demo03_斗地主_制牌_洗牌_发牌_有序 {
public static void main(String[] args) {
String[] nums = { "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2" };
String[] colors = { "红桃", "黑桃", "梅花", "方块" };
// 要制作一个表格,表格中存放的是 序号和牌的对应
Map<Integer, String> table = new HashMap<Integer, String>();
// Poker中只存序号就可以了,以后要用牌的时候,就到table中根据序号取到对应的牌即可
List<Integer> poker = new ArrayList<Integer>();
int count = 0;
for (String num : nums) {
for (String color : colors) {
table.put(count, color + num); // 把序号和生成的牌装入table表格中
poker.add(count);
count++;
}
}
table.put(count, "小王");
poker.add(count);
count++;
table.put(count, "大王");
poker.add(count);
// 洗牌
Collections.shuffle(poker);
// 发牌
List<Integer> dipai = new ArrayList<Integer>();
List<Integer> xiaodao = new ArrayList<Integer>();
List<Integer> longwu = new ArrayList<Integer>();
List<Integer> me = new ArrayList<Integer>();
for (int i = 1; i <= 3; i++) {
dipai.add(poker.remove(0));
}
while (true) {
if (!poker.isEmpty()) {
xiaodao.add(poker.remove(0));
}
if (!poker.isEmpty()) {
longwu.add(poker.remove(0));
}
if (!poker.isEmpty()) {
me.add(poker.remove(0));
}else{
break;
}
}
Collections.sort(xiaodao);
Collections.sort(longwu);
Collections.sort(me);
System.out.println(kanpai(dipai,table));
System.out.println(kanpai(xiaodao,table));
System.out.println(kanpai(longwu,table));
System.out.println(kanpai(me,table));
}
public static String kanpai(List<Integer> list,Map<Integer,String> map){
StringBuilder sb = new StringBuilder("[");
for(Integer x:list){
sb.append(map.get(x)).append(",");
}
return sb.replace(sb.length()-1,sb.length(),"]").toString();
}
}