如何外贸网站推广,wordpress 不用主题,找销售的网站,铭讯网站建设4. 泛型
4.1 泛型概述
4.1.1 生活中的例子 举例1#xff1a;中药店#xff0c;每个抽屉外面贴着标签 举例2#xff1a;超市购物架上很多瓶子#xff0c;每个瓶子装的是什么#xff0c;有标签 举例3#xff1a;家庭厨房中#xff1a; Java中的泛型#xff0c;就…4. 泛型
4.1 泛型概述
4.1.1 生活中的例子 举例1中药店每个抽屉外面贴着标签 举例2超市购物架上很多瓶子每个瓶子装的是什么有标签 举例3家庭厨房中 Java中的泛型就类似于上述场景中的标签。 4.1.2 泛型的引入
在Java中我们在声明方法时当在完成方法功能时如果有未知的数据需要参与这些未知的数据需要在调用方法时才能确定那么我们把这样的数据通过形参表示。在方法体中用这个形参名来代表那个未知的数据而调用者在调用时对应的传入实参就可以了。 受以上启发JDK1.5设计了泛型的概念。泛型即为“类型参数”这个类型参数在声明它的类、接口或方法中代表未知的某种通用类型。
举例1
集合类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象所以在JDK5.0之前只能把元素类型设计为ObjectJDK5.0时Java引入了“参数化类型Parameterized type”的概念允许我们在创建集合时指定集合元素的类型。比如ListString这表明该List只能保存字符串类型的对象。
使用集合存储数据时除了元素的类型不确定其他部分是确定的例如关于这个元素如何保存如何管理等。
举例2
java.lang.Comparable接口和java.util.Comparator接口是用于比较对象大小的接口。这两个接口只是限定了当一个对象大于另一个对象时返回正整数小于返回负整数等于返回0但是并不确定是什么类型的对象比较大小。JDK5.0之前只能用Object类型表示使用时既麻烦又不安全因此 JDK5.0 给它们增加了泛型。 其中T就是类型参数即泛型。 所谓泛型就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值或参数的类型。这个类型参数将在使用时例如继承或实现这个接口、创建对象或调用方法时确定即传入实际的类型参数也称为类型实参。 4.2 使用泛型举例
自从JDK5.0引入泛型的概念之后对之前核心类库中的API做了很大的修改例如JDK5.0改写了集合框架中的全部接口和类、java.lang.Comparable接口、java.util.Comparator接口、Class类等。为这些接口、类增加了泛型支持从而可以在声明变量、创建对象时传入类型实参。
4.2.1 集合中使用泛型ArrayListHashMap
集合中没有使用泛型时 集合中使用泛型时 Java泛型可以保证如果程序在编译时没有发出警告运行时就不会产生ClassCastException异常。即把不安全的因素在编译期间就排除了而不是运行期既然通过了编译那么类型一定是符合要求的就避免了类型转换。 同时代码更加简洁、健壮。 把一个集合中的内容限制为一个特定的数据类型这就是generic背后的核心思想。 package com.atguigu.java;import org.junit.Test;import java.util.*;/**** 泛型的使用* 1.jdk 5.0新增的特性** 2.在集合中使用泛型* 总结* ① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。* ② 集合框架在声明接口和其实现类时使用了泛型jdk5.0在实例化集合对象时* 情况1如果没有使用泛型则认为操作的是Object类型的数据。 * 情况2如果使用了泛型则需要指明泛型的具体类型。一旦指明了泛型的具体类型则在集合的相关的方法中* 凡是使用类的泛型的位置比如方法、构造器、属性等都替换为具体的泛型类型。* 比如add(E e) ---实例化以后add(Integer e)* * ③ 注意点泛型的类型必须是类不能是基本数据类型。需要用到基本数据类型的位置拿包装类替换* ④ 在集合类或接口中凡是定义类或接口时如果实例化时没有指明泛型的类型在eclipse中会有警告* 信息 在idea中没有警告。警告就是不强制 你不用也不报错。 ** 3.如何自定义泛型结构泛型类、泛型接口泛型方法。见 4.4** author shkstart* create 2019 上午 9:59*/
public class GenericTest {//在集合中使用泛型之前的情况Testpublic void test1(){ArrayList list new ArrayList();//需求存放学生的成绩list.add(78);list.add(76);list.add(89);list.add(88);//问题一类型不安全,没有限制可以存放任何类型。
// list.add(Tom);for(Object score : list){//问题二强转时可能出现ClassCastException。因为强转要满足父子类关系Tom是String类型 不满足条件。int stuScore (Integer) score;//强转自动拆箱System.out.println(stuScore);//78,76,89,88}}//在集合中使用泛型的情况以ArrayList为例Testpublic void test2(){//泛型都是引用数据类型基本数据类型用它的包装类。/** 说明* ArrayList能指定泛型的前提是,底层源码定义这个类时声明了泛型 如public class ArrayListE extends AbstractListE* 创建对象时相当于是给这个泛型赋值了个具体的类型 如ArrayListInteger list new ArrayListInteger(); 为Integer类型* 那么凡是在这个底层源码的类ArrayList中用到泛型的地方 如add()方法使用了泛型 public boolean add(E e) {}则在实例化对象时* 通过对象调用这个方法 如list.add(87);中元素的类型也应该和声明的泛型类型保持一致即实例化的类型是Integer add方法添加元素类型也是Integer。** 注意如果定义类时没有指定泛型那么创建对象时不能直接使用泛型。* */ArrayListInteger list new ArrayListInteger();list.add(78);//public boolean add(E e) {} 方法声明用的泛型和定义时一致创建对象时有指定泛型为Integer,所以这里只能用Integer类型数据添加。list.add(87);list.add(99);list.add(65);//编译时就会进行类型检查保证数据的安全
// list.add(Tom);//方式一
// for(Integer score : list){//直接就可以使用Integer类型来接收
// //避免了强转操作
// int stuScore score;
//
// System.out.println(stuScore);
//
// }//方式二/* Iterator在定义这个类时就使用了泛型所以在这个地方就可以使用泛型了。public interface IteratorE {}* 问题为什么这个地方IteratorInteger iterator调用方法的返回值 直接是Integer类型呢???* 原因在ArrayList中这个iterator()方法 public IteratorE iterator() {return new Itr();}使用了泛型* 在实例化ArrayList时已经指定了泛型为Integer,所以这个方法中的泛型也是Integer。** */IteratorInteger iterator list.iterator();while(iterator.hasNext()){int stuScore iterator.next();System.out.println(stuScore);}}//在集合中使用泛型的情况以HashMap为例。Map集合有2值所以k v都要指定泛型。Testpublic void test3(){//定义Map接口时泛型就写了2个所以实例化对象也要写2个。public interface MapK,V {}//jdk7新特性类型推断。 原来的写法MapString,Integer map new HashMapString,Integer();MapString,Integer map new HashMap();map.put(Tom,87);map.put(Jerry,87);map.put(Jack,67);// map.put(123,ABC);//解释1泛型的嵌套map.entrySet()返回的数据放在Set集合中// 第一层嵌套Set集合里面是一个个的Entry对象所以泛型是Entry类型// 第二层嵌套而Entry里面又有自己的泛型k v 所以在进一步指定泛型。//解释2Map.Entry为什么是通过Map.来调用// Entry为Map集合接口的一个内部接口所以想要使用内部接口就需要Map.Entry一下才能使用。// 如果想要省写为SetEntryString,Integer entry map.entrySet();那么在上面需要进行导包import java.util.Map.*;// 因为Map导了import java.util.*;,如果连Map都没导还是需要写全。SetMap.EntryString,Integer entry map.entrySet();IteratorMap.EntryString, Integer iterator entry.iterator();while(iterator.hasNext()){Map.EntryString, Integer e iterator.next();String key e.getKey();Integer value e.getValue();System.out.println(key ---- value);}}}
4.2.2 比较器中使用泛型
题目需求 定义一个Employee类。 该类包含private成员变量name,age,birthday其中 birthday 为 MyDate 类的对象 并为每一个属性定义 getter, setter 方法 并重写 toString 方法输出 name, age, birthday MyDate类包含: private成员变量year,month,day并为每一个属性定义 getter, setter 方法 创建该类的 5 个对象并把这些对象放入 TreeSet 集合中TreeSet 需使用泛型来定义 分别按以下两种方式对集合中的元素进行排序并遍历输出 1). 使Employee 实现 Comparable 接口并按 name 排序 2). 创建 TreeSet 时传入 Comparator对象按生日日期的先后排序。
Employee
package com.atguigu01.use.exer1;/*** ClassName: Employee* Description:* 定义一个Employee类。* 该类包含private成员变量name,age,birthday其中 birthday 为 MyDate 类的对象* 并为每一个属性定义 getter, setter 方法* 并重写 toString 方法输出 name, age, birthday* Author 尚硅谷-宋红康* Create 17:03* Version 1.0*//*** Comparable源码* public interface ComparableT {* public int compareTo(T o);* }*/
public class Employee implements ComparableEmployee{ //原先没有写泛型说明是Object类型 想比较谁就写谁private String name;private int age;private MyDate birthday;public Employee(String name, int age, MyDate birthday) {this.name name;this.age age;this.birthday birthday;}public Employee() {}public String getName() {return name;}public void setName(String name) {this.name name;}public int getAge() {return age;}public void setAge(int age) {this.age age;}public MyDate getBirthday() {return birthday;}public void setBirthday(MyDate birthday) {this.birthday birthday;}Overridepublic String toString() {return Employee{ name name \ , age age , birthday birthday };}//指明泛型时的写法Overridepublic int compareTo(Employee o) {return this.name.compareTo(o.name);}//没有指明泛型时的写法//按 name 排序
// Override
// public int compareTo(Object o) {
// if(o instanceof Employee){
// Employee e (Employee)o;
// return this.name.compareTo(e.name);
// }return 0;
// throw new RuntimeException(传入的数据类型不一致);
// }
}
MyDate
package com.atguigu01.use.exer1;/*** MyDate类包含:private成员变量year,month,day并为每一个属性定义 getter, setter 方法* author shkstart* create 2019 上午 10:21*/
public class MyDate implements ComparableMyDate{//想要让MyDate排序写的肯定是MyDate类型private int year;private int month;private int day;public MyDate(int year, int month, int day) {this.year year;this.month month;this.day day;}public MyDate() {}public int getYear() {return year;}public void setYear(int year) {this.year year;}public int getMonth() {return month;}public void setMonth(int month) {this.month month;}public int getDay() {return day;}public void setDay(int day) {this.day day;}Overridepublic String toString() {return MyDate{ year year , month month , day day };}//没有使用泛型的写法
// Override
// public int compareTo(Object o) {
// if(o instanceof MyDate){
// MyDate m (MyDate)o;
//
// //比较年
// int minusYear this.getYear() - m.getYear();
// if(minusYear ! 0){
// return minusYear;
// }
// //比较月
// int minusMonth this.getMonth() - m.getMonth();
// if(minusMonth ! 0){
// return minusMonth;
// }
// //比较日
// return this.getDay() - m.getDay();
// }
//
// throw new RuntimeException(传入的数据类型不一致);
//
// }//使用泛型的写法Overridepublic int compareTo(MyDate m) {//比较年int minusYear this.getYear() - m.getYear();if(minusYear ! 0){return minusYear;}//比较月int minusMonth this.getMonth() - m.getMonth();if(minusMonth ! 0){return minusMonth;}//比较日return this.getDay() - m.getDay();}
}
EmployeeTest
package com.atguigu01.use.exer1;import org.junit.Test;import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;/*** 创建该类的 5 个对象并把这些对象放入 TreeSet 集合中下一章TreeSet 需使用泛型来定义分别按以下两种方式对集合中的元素进行排序并遍历输出1). 使Employee 实现 Comparable 接口并按 name 排序2). 创建 TreeSet 时传入 Comparator对象按生日日期的先后排序。** author shkstart* create 2019 上午 10:23*/
public class EmployeeTest {//问题二创建 TreeSet 时传入 Comparator对象按生日日期的先后排序 (定制排序 自定义类)Testpublic void test2(){//泛型往集合里面添谁就写谁比较的是2个对象只不过是按照对象中的属性进行排序ComparatorEmployee comparator new ComparatorEmployee() { //匿名实现类的非匿名对象//使用泛型以后的写法Overridepublic int compare(Employee o1, Employee o2) {MyDate b1 o1.getBirthday();MyDate b2 o2.getBirthday();//方式一自定义类的自然排序不符合或者就没有写自然排序那么可以使用定制排序// //比较年// int minusYear b1.getYear() - b2.getYear();// if(minusYear ! 0){// return minusYear;// }// //比较月// int minusMonth b1.getMonth() - b2.getMonth();// if(minusMonth ! 0){// return minusMonth;// }// //比较日// return b1.getDay() - b2.getDay();//方式二自然排序就是按照日期进行排序那么为什么不直接使用自然排序反而使用定制排序呢// 如果直接使用MyData类的自然排序那么集合中添加的就应该是MyData类型。// 现在集合中添加的是Employee类型MyData类是Employee类中的一个属性private MyDate birthday// 你没有办法直接使用自然排序只能是在定制排序中先调用birthday对象之后在按照日期排序return b1.compareTo(b2);//调用的是自定义类MyDate重写后的方法}/*//使用泛型之前的写法Overridepublic int compare(Object o1, Object o2) {if(o1 instanceof Employee o2 instanceof Employee){Employee e1 (Employee)o1;Employee e2 (Employee)o2;MyDate b1 e1.getBirthday();MyDate b2 e2.getBirthday();//方式一// //比较年// int minusYear b1.getYear() - b2.getYear();// if(minusYear ! 0){// return minusYear;// }// //比较月// int minusMonth b1.getMonth() - b2.getMonth();// if(minusMonth ! 0){// return minusMonth;// }// //比较日// return b1.getDay() - b2.getDay();//方式二return b1.compareTo(b2);}// return 0;throw new RuntimeException(传入的数据类型不一致);}*/};TreeSetEmployee set new TreeSet(comparator);Employee e1 new Employee(liudehua,55,new MyDate(1965,5,4));Employee e2 new Employee(zhangxueyou,43,new MyDate(1987,5,4));Employee e3 new Employee(guofucheng,44,new MyDate(1987,5,9));Employee e4 new Employee(liming,51,new MyDate(1954,8,12));Employee e5 new Employee(liangzhaowei,21,new MyDate(1978,12,4));set.add(e1);set.add(e2);set.add(e3);set.add(e4);set.add(e5);IteratorEmployee iterator set.iterator();while (iterator.hasNext()){System.out.println(iterator.next());}}//问题一使Employee 实现 Comparable 接口并按 name 排序自然排序 自定义类Testpublic void test1(){TreeSetEmployee set new TreeSetEmployee();Employee e1 new Employee(liudehua,55,new MyDate(1965,5,4));Employee e2 new Employee(zhangxueyou,43,new MyDate(1987,5,4));Employee e3 new Employee(guofucheng,44,new MyDate(1987,5,9));Employee e4 new Employee(liming,51,new MyDate(1954,8,12));Employee e5 new Employee(liangzhaowei,21,new MyDate(1978,12,4));set.add(e1);set.add(e2);set.add(e3);set.add(e4);set.add(e5);IteratorEmployee iterator set.iterator();while (iterator.hasNext()){Employee employee iterator.next();System.out.println(employee);}}
}4.3 自定义泛型结构
4.3.1 泛型的基础说明
1、类型这种语法形式就叫泛型。 类型的形式我们称为类型参数这里的类型习惯上使用T表示是Type的缩写。即T。 T代表未知的数据类型我们可以指定为StringIntegerCircle等。 类比方法的参数的概念我们把T称为类型形参将Circle称为类型实参有助于我们理解泛型 这里的T可以替换成KV等任意字母。
2、在哪里可以声明类型变量T
声明类或接口时在类名或接口名后面声明泛型类型我们把这样的类或接口称为泛型类或泛型接口。
【修饰符】 class 类名类型变量列表 【extends 父类】 【implements 接口们】{}
【修饰符】 interface 接口名类型变量列表 【implements 接口们】{}//例如
public class ArrayListE
public interface MapK,V{....
} 声明方法时在【修饰符】与返回值类型之间声明类型变量我们把声明了类型变量的方法称为泛型方法。
[修饰符] 类型变量列表 返回值类型 方法名([形参列表])[throws 异常列表]{//...
}//例如java.util.Arrays类中的
public static T ListT asList(T... a){....
}4.3.2 自定义泛型类或泛型接口
当我们在类或接口中定义某个成员时该成员的相关类型是不确定的而这个类型需要在使用这个类或接口时才可以确定那么我们可以使用泛型类、泛型接口。
说明自定义泛型类和接口的用法相同只是接口和类的区别
格式
class AT{}interface BT1,T2{ //多个泛型参数用逗号隔开
}1) 说明
① 我们在声明完自定义泛型类以后可以在类的内部比如属性、方法、构造器中使用类的泛型。
② 我们在创建自定义泛型类的对象时可以指明泛型参数类型。一旦指明内部凡是使用类的泛型参数的位置都具体化为指定的类的泛型类型。
③ 如果在创建自定义泛型类的对象时没有指明泛型参数类型那么泛型将被擦除泛型对应的类型均按照Object处理但不等价于Object。 即Order order new Order(); 不等价于 OrderObject order3 new Order();
经验泛型要使用一路都用。要不用一路都不要用。
④ 泛型的指定中必须使用引用数据类型。不能使用基本数据类型此时只能使用包装类替换。
⑤ 除创建泛型类对象外子类继承泛型类时、实现类实现泛型接口时也可以确定泛型结构中的泛型参数。比如SubOrder2
如果我们在给泛型类提供子类时子类也不确定泛型的类型则可以继续使用泛型参数。比如SubOrder3
我们还可以在现有的父类的泛型参数的基础上新增泛型参数。比如SubOrder4SubOrder5
2) 注意
① 泛型类可能有多个参数此时应将多个参数一起放在尖括号内。比如E1,E2,E3 ② 泛型类的构造器如下: public GenericClass(){} 而下面是错误的: public GenericClassE(){}
③ 泛型不同的引用不能相互赋值
④ JDK7.0 开始泛型的简化操作ArrayListFruit flist new ArrayList();
⑤ 如果泛型结构是一个接口或抽象类则不可创建泛型类的对象。
⑥ 不能使用new E[]。但是可以E[] elements (E[])new Object[capacity];
参考ArrayList源码中声明Object[] elementData而非泛型参数类型数组。
⑦ 在类/接口上声明的泛型在本类或本接口中即代表某种类型但不可以在静态方法中使用类的泛型。因为类的泛型是实例化创建对象时指定静态资源是类加载时创建。
⑧ 异常类不能是带泛型的。
⑨ 泛型类的普通方法中不能写try-catch 3) 举例
用到的资源类结构
Person 普通类
package com.atguigu02.selfdefine;//定义普通类
public class Person {
}
Order 泛型父类
package com.atguigu02.selfdefine;import java.util.ArrayList;//定义泛型类
public class OrderT{//声明了类的泛型参数以后就可以在类的内部使用此泛型参数。// 小技巧可以把T看成是一个类型T t;int orderId;// static T t1;public Order() {}public Order(T t, int orderId) {this.t t;this.orderId orderId;}//如下的两个方法不是泛型方法public T getT() { //不是return t;}public void setT(T t) { //不是this.t t;}public int getOrderId() {return orderId;}public void setOrderId(int orderId) {this.orderId orderId;}Overridepublic String toString() {return Order{ t t , orderId orderId };}//不可以在静态方法中使用类的泛型 因为类的泛型是实例化创建对象时指定静态资源是类加载时创建。
// public static void method1(){
// System.out.println(t : t);
// }}
SubOrder1 子类
package com.atguigu02.selfdefine;//SubOrder1不是泛型类
public class SubOrder1 extends Order{
}
SubOrder2 子类
package com.atguigu02.selfdefine;//SubOrder2不是泛型类
//是不是泛型类取决于有没有一个不确定的类型
public class SubOrder2 extends OrderInteger{}
SubOrder3 子类
package com.atguigu02.selfdefine;//SubOrder3是泛型类
public class SubOrder3T extends OrderT{public void show(T t){System.out.println(t);}
}
SubOrder4 子类
package com.atguigu02.selfdefine;//SubOrder4是泛型类
public class SubOrder4E extends OrderInteger{//父类里面确定了泛型子类自己又多了个不确定的类型E e;public SubOrder4() {}public SubOrder4(E e) {this.e e;}public SubOrder4(Integer integer, int orderId, E e) {super(integer, orderId);this.e e;}public E getE() {return e;}public void setE(E e) {this.e e;}
}
SubOrder5 子类
package com.atguigu02.selfdefine;//SubOrder5是泛型类
public class SubOrder5T,E extends OrderT{//父类不确定的类型到子类中还不确定并且子类又多了个不确定的类型E e;public SubOrder5() {}public SubOrder5(T t, int orderId, E e) {super(t, orderId);this.e e;}public E getE() {return e;}public void setE(E e) {this.e e;}
}
测试类GenericTest
package com.atguigu02.selfdefine;import org.junit.Test;import java.util.ArrayList;public class GenericTest {/*** 1.测试普通类* Person普通类结构public class Person { }*/Testpublic void test1(){//1.1 普通类在声明的时候没有定义泛型所以实例化时也不能使用泛型Person p1 new Person(); //对
// PersonString p2 new Person(); //错//1.2 系统提供的类在jdk1.5时都增加了泛型所以实例化时就可以指明类的泛型参数的类型ArrayListString list new ArrayList();list.add(AA); //对
// list.add(123); //错//1.3 系统提供的类定义了泛型在实例化时可以使用泛型 也可以不用没有使用的情况下认为是Object类型ArrayList list1 new ArrayList(); //向下兼容。jdk5.0之前集合是没有声明为泛型的。list1.add(123);list1.add(AA); //认为是Object类型所以都可以添加}/*** 2.测试自定义的泛型类* Order泛型类的结构public class OrderT{ }*/Testpublic void test2(){//2.1 同样自定义的类即便在定义的时候声明了泛型参数在实例化的时候任然可以不使用此时类型为Object类型Order order new Order(); //不等价于 OrderObject order3 new Order();Object obj order.getT();//2.2 泛型参数在指明时是不可以使用基本数据类型的但是可以使用包装类替代基本数据类型。
// Orderint order1 new Order(); 错误//在实例化时可以指明类的泛型参数的具体类型一旦指明了泛型的类型则在泛型类中凡是使用泛型// 参数的位置都替换为指定的类型。即泛型类中有泛型变量、泛型方法则在实例化对象 通过对象调属性方法的时候都需要替换为对应的泛型OrderInteger order2 new OrderInteger();Integer t order2.getT();OrderString order3 new Order();order3.setT(AA);}//3.测试子类继承泛型父类时的各种情况Testpublic void test3(){/** 3.1 泛型父类结构public class OrderT{* }** 子类继承结构public class SubOrder1 extends Order{* }* SubOrder1不是泛型类* */SubOrder1 sub1 new SubOrder1();Object t sub1.getT(); //此时为Object类型// SubOrder1Integer sub2 new SubOrder1();//因为SubOrder1不是泛型类此处编译错误}Testpublic void test4(){/** 3.2 泛型父类结构public class OrderT{* }** 子类继承结构public class SubOrder2 extends OrderInteger{* }* SubOrder2不是泛型类是不是泛型类取决于有没有一个不确定的类型** 对于Order父类什么时候可以指明类型呢* 一造对象的时候 OrderInteger order2 new OrderInteger();* 二子类在继承Order父类的时候 public class SubOrder2 extends OrderInteger{ }* 一旦指定后子类继承过来的属性和方法不再是不确定的类型而是这个指明的类型。* */SubOrder2 sub2 new SubOrder2();Integer t sub2.getT();//此时是Integer类型/** 3.3 泛型父类结构public class OrderT{* }** 子类继承结构public class SubOrder3T extends OrderT{* }* SubOrder3是泛型类* */SubOrder3String sub3 new SubOrder3();String t1 sub3.getT();sub3.show(AA);/** 3.4 泛型父类结构public class OrderT{* }** 子类继承结构public class SubOrder4E extends OrderInteger{ 父类里面确定了泛型子类自己又多了个不确定的类型* }* SubOrder4是泛型类* */SubOrder4String sub4 new SubOrder4();Integer t2 sub4.getT(); //使用T的时候是个确定的类型String e sub4.getE(); //使用E的时候是自己指明的类型/** 3.5 泛型父类结构public class OrderT{* }** 子类继承结构public class SubOrder5T,E extends OrderT{//父类不确定的类型到子类中还不确定并且子类又多了个不确定的类型* }* SubOrder5是泛型类* */SubOrder5String,Integer sub5 new SubOrder5(); //泛型的顺序要一致String t3 sub5.getT(); //T是指定的String类型Integer e1 sub5.getE(); //E是指定的Integer类型}}
4.3.3 自定义泛型方法
如果我们定义类、接口时没有使用泛型参数但是某个方法形参类型不确定时这个方法可以单独定义泛型参数。
1) 说明
泛型方法的格式
//通常在形参列表或返回值类型的位置会出现泛型参数T
// 泛型只是为了标识此方法为泛型方法
[访问权限] 泛型 返回值类型 方法名([泛型标识 参数名称]) [抛出的异常]{}eg:public E E get(int id, E e) {E result null;return result;}声明泛型方法时一定要添加泛型参数方法也可以被泛型化与其所在的类是否是泛型类没有关系。泛型方法中的泛型参数在方法被调用时确定。泛型方法可以根据需要声明为static的。 泛型方法可以声明为静态的。泛型类中的方法不可以声明为静态的。原因泛型参数是在调用方法时确定的。并非在实例化类时确定。
2) 举例
举例1泛型方法所属的类是否是一个泛型类都可以
public class DAO {public E E get(int id, E e) {E result null;return result;}
}
举例2
Order泛型类
package com.atguigu02.selfdefine;import java.util.ArrayList;//定义泛型类
public class OrderT{//声明了类的泛型参数以后就可以在类的内部使用此泛型参数。// 小技巧可以把T看成是一个类型T t;int orderId;public Order() {}public Order(T t, int orderId) {this.t t;this.orderId orderId;}//如下的两个方法不是泛型方法//在泛型类的方法中使用了类的泛型参数。此方法不一定是泛型方法public T getT() { //不是return t;}public void setT(T t) { //不是this.t t;}public int getOrderId() {return orderId;}public void setOrderId(int orderId) {this.orderId orderId;}Overridepublic String toString() {return Order{ t t , orderId orderId };}//自定义泛型方法public E E method(E e){return null;}//定义泛型方法将E[]数组元素添加到对应类型的ArrayList中并返回//注意数组元素是E类型那么返回值也应该是ArrayListE,但是这样写还不对 编译器会误认为这个E是类如String一个具体的类型,// 而不会认为是一个参数 我们想要的是当作参数在调用的时候进行赋值解决需要在方法的返回值类型前面加上E才会当做是参数来看。public E ArrayListE copyFromArrayToList(E[] arr){ArrayListE list new ArrayList();for(E e : arr){list.add(e);}return list;}//定义泛型方法将E[]数组元素添加到对应类型的ArrayList中并返回 (泛型方法可以定义为静态的通过类名调用)//泛型方法可以声明为静态的。泛型类中的方法不可以声明为静态的。原因泛型参数是在调用方法时确定的。并非在实例化类时确定。public static E ArrayListE copyFromArrayToList1(E[] arr){ArrayListE list new ArrayList();for(E e : arr){list.add(e);}return list;}
}
GenericTest
package com.atguigu02.selfdefine;import org.junit.Test;import java.util.ArrayList;public class GenericTest {//测试泛型方法的使用Testpublic void test5(){OrderString order1 new Order();Integer[] arr new Integer[]{1,2,3,4,5};//泛型参数在方法调用时指明其具体的类型ArrayListInteger list order1.copyFromArrayToList(arr);for(Integer i :list){System.out.println(i);}}//测试泛型方法的使用Testpublic void test6(){OrderString order1 new Order();Integer[] arr new Integer[]{1,2,3,4,5};//泛型参数在方法调用时指明其具体的类型ArrayListInteger list Order.copyFromArrayToList1(arr);//通过类名调用静态的泛型方法for(Integer i :list){System.out.println(i);}}}
3) 练习
练习1: 泛型方法
编写一个泛型方法实现任意引用类型数组指定位置元素交换。
public static void method1( E[] e,int a,int b)
/*** author 尚硅谷-宋红康* create 9:11*/
public class Exer01 {//编写一个泛型方法实现任意引用类型数组指定位置元素交换。public static E void method( E[] arr,int a,int b){E temp arr[a];arr[a] arr[b];arr[b] temp;}Testpublic void testMethod(){Integer[] arr new Integer[]{10,20,30,40};method(arr,2,3);for(Integer i : arr){System.out.println(i);}}
}4.3.4 泛型类和泛型方法的使用场景同样的Dao
说明
通常一张数据库中的表对应一个实体类 一个Dao层
场景分析此时有2张表(customers、order)对应2个实体类(Customer、Order)对应2个Dao层(CustomerDAO、OrderDAO)此时每张表都要进行crud操作。
原先 每个Dao层都要写一遍crud的代码2个dao层的代码几乎相同只不过是用到的类不同。缺点这样造成每个Dao层都有很多相似的代码代码冗余即编程时不必要的代码段。解决 使用泛型定义一个通用的Dao层在里面编写这些通用的代码凡是用到类的地方都替换为泛型参数之后使用子类继承这个泛型父类同时给这个泛型参数赋值为一个具体的对象类型 好处这样只需要在父类Dao中编写一次crud的代码子类dao继承泛型父类并指定具体的对象类型赋值给泛型参数简写了代码。每个子类的dao还可以编写每张表特有的功能。
Customer实体类 对应数据库中的customer表
package com.atguigu02.selfdefine.apply;import java.sql.Date;/*** ClassName: Customer* Description:** ORM思想(object relational mapping)* 数据库中的一个表 与 Java中的一个类对应* 表中的一条记录 与 Java类的一个对象对应* 表中的一个字段或列 与 Java类的一个属性或字段对应** Author 尚硅谷-宋红康* Create 10:04* Version 1.0*/
public class Customer {int id;String name;String email;Date birth;
}
Order实体类 对应数据库中的order表
package com.atguigu02.selfdefine.apply;import java.sql.Date;/*** ClassName: Order* Description:** Author 尚硅谷-宋红康* Create 10:07* Version 1.0*/
public class Order {int orderId;String orderName;Date orderDate;
}
DAO通用数据层
package com.atguigu02.selfdefine.apply;import java.util.List;/*** DAO:data(base) access object。内部封装了操作数据库相关表的增删改查操作。(CRUD)**/
public class DAOT { //表的共性操作的DAO//增public void insert(T bean){//通过相应的sql语句将bean对象的属性值写入到数据表中。}//删public T deleteById(int id){//略return null;}//改public void update(int id,T bean){//略}//查//查询一条记录public T queryForInstance(int id){//略return null;}//查询多条记录构成的集合public ListT queryForList(int id){return null;}//定义泛型方法//比如查询表中的记录数。ELong类型//比如查询表中最大的生日。E:Date类型public E E getValue(String sql){return null;}}
CustomerDAOCustomer特有的数据层
package com.atguigu02.selfdefine.apply;/*** ClassName: CustomerDAO* Description:** Author 尚硅谷-宋红康* Create 10:12* Version 1.0*/
public class CustomerDAO extends DAOCustomer{//只能操作某一个表的DAO}
OrderDAOOrder特有的数据层
package com.atguigu02.selfdefine.apply;/*** ClassName: OrderDAO* Description:** Author 尚硅谷-宋红康* Create 10:13* Version 1.0*/
public class OrderDAO extends DAOOrder{//只能操作某一个表的DAO
}
DAOTest测试类
package com.atguigu02.selfdefine.apply;import org.junit.Test;import java.util.List;/*** ClassName: DAOTest* Description:** Author 尚硅谷-宋红康* Create 10:12* Version 1.0*/
public class DAOTest {Testpublic void test1(){/** public class DAOT { } 父类 泛型类* public class CustomerDAO extends DAOCustomer{ } 子类 不是泛型类* 子类在继承时实例化了泛型参数的类型为Customer* 所以此时实例化对象调用方法时用到泛型的地方都限定泛型的类型为Customer。* */CustomerDAO dao1 new CustomerDAO();//new对象的时候能不能用泛型取决于此类是不是泛型类dao1.insert(new Customer());Customer customer dao1.queryForInstance(1);}Testpublic void test2(){OrderDAO dao1 new OrderDAO();dao1.insert(new Order());ListOrder list dao1.queryForList(1);}
}
4.4 泛型在继承上的体现
如果B是A的一个子类型子类或者子接口而G是具有泛型声明的类或接口GB并不是GA的子类型
比如String是Object的子类但是ListString并不是ListObject的子类。 package com.atguigu01.use;import org.junit.Test;import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;/*** 泛型在继承方面的体现:* 情况一虽然类A是类B的父类但是GA 和GB二者不具备子父类关系二者是并列关系。* 情况二类A是类B的父类AG 是 BG 的父类*/
public class GenericTest {//情况一类SuperA是类A的父类则GSuperA 与 GA的关系GSuperA 和 GA是并列的两个类没有任何子父类的关系。// 比如ArrayListObject 、ArrayListString没有关系Testpublic void test1(){//父子类子类类型可以赋值给父类。Object obj null;String str null;obj str; //基于继承性的多态的使用Object[] arr1 null;String[] arr2 null;arr1 arr2; //基于继承性的多态的使用//编译不通过01
// Date date new Date();
// str date;ListObject list1 null;ListString list2 new ArrayListString();//此时的list1和list2的类型不具有子父类关系//编译不通过02类似于01的写法list1和list2里面的元素确实是父子类关系但是list1和list2本身不是父子类关系 顶多是并列关系。
// list1 list2;/*反证法假设list1 list2;list1.add(123);导致混入非String的数据出错。解释反证法list1 list2都是引用类型list1 list2相当于是 list1和list2都指向堆空间中的对象newArrayList那么此时list1调用时也不会报空指针异常在list1.add(123)添加数据时由于list1是Object类型所以任何数据都能添加 list1添加integer类型的数据123 就相当于像lis2中添加123它们都指向ArrayList而ArrayList又要求是String类型的所以报错。*/show(list1);//只能是Object类型show1(list2);//只能是String类型}public void show1(ListString list){}public void show(ListObject list){}//情况二类SuperA是类A的父类或接口SuperAG 与 AG的关系SuperAG 与AG 有继承或实现的关系。// 即AG的实例可以赋值给SuperAG类型的引用或变量// 比如ListString 与 ArrayListStringTestpublic void test2(){AbstractListString list1 null;ListString list2 null;ArrayListString list3 null;list1 list3;list2 list3;ListString list4 new ArrayList();}}
4.5 通配符的使用
当我们声明一个变量/形参时这个变量/形参的类型是一个泛型类或泛型接口例如ComparatorT类型但是我们仍然无法确定这个泛型类或泛型接口的类型变量T的具体类型此时我们考虑使用类型通配符 ? 。
4.5.1 通配符的理解
使用类型通配符
比如List?Map?,?
List?是ListString、ListObject等各种泛型List的父类。
4.5.2 使用注意点
注意点1编译错误不能用在泛型方法声明上返回值类型前面不能使用?
public static ? void test(ArrayList? list){
}注意点2编译错误不能用在泛型类的声明上
class GenericTypeClass?{
}注意点3编译错误不能用在创建对象上右边属于创建集合对象
ArrayList? list2 new ArrayList?();4.5.3 测试
Person 父类
package com.atguigu.java1;public class Person {
}
Student 子类
package com.atguigu.java1;public class Student extends Person{
}
测试类
package com.atguigu.java1;import org.junit.Test;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class GenericTest {/*通配符的使用通配符?类A是类B的父类GA和GB是没有关系的二者共同的父类是G?*/Testpublic void test3(){//ListObject,ListString是同级关系List?是它们2个的共同父类ListObject list1 null;ListString list2 null;List? list null;list list1;list list2;//编译通过
// print(list1);
// print(list2);//通配符的读入和写出操作要求ListString list3 new ArrayList();list3.add(AA);list3.add(BB);list3.add(CC);list list3;//添加(写入)对于List?就不能向其内部添加数据。 不可以往不确定的类型中添加数据。//除了添加null之外。 集合中添加的是引用类型不管是什么样的引用类型都可以赋值为null 默认值
// list.add(DD);
// list.add(?);list.add(null);//获取(读取)允许读取数据读取的数据类型为Object。// 因为不管获取到的是什么类型的元素都可以复值给它的父类ObjectObject o list.get(0);System.out.println(o);}public void print(List? list){Iterator? iterator list.iterator();while(iterator.hasNext()){//List? 里面的数据如何接收呢//答不管传入的是啥类型都可以使用Object来接收Object obj iterator.next();System.out.println(obj);}}/*3.有限制条件的通配符的使用。情况1 ? extends Number (无穷小 , Number] 即 只允许泛型为Number及Number子类的引用调用情况2? super Number [Number , 无穷大) 即 只允许泛型为Number及Number父类的引用调用情况3? extends Comparable 可以看成是情况1只允许泛型为实现Comparable接口的实现类的引用调用*/Testpublic void test4(){List? extends Person list1 null;// PersonList? super Person list2 null;// PersonListStudent list3 new ArrayListStudent();ListPerson list4 new ArrayListPerson();ListObject list5 new ArrayListObject();//测试extendslist1可以被 list3 list4赋值成功list5不能赋值成功。必须是Person的子类或者它本身类型(小于等于list1中的person)list1 list3;list1 list4;
// list1 list5;//测试superlist2可以被list4 list5赋值成功list3不能赋值成功。必须是Person的父类或者它本身类型(大于等于list2中的person)
// list2 list3;list2 list4;list2 list5;//读取数据可以读取只不过要注意接收的类型list1 list3;Person p list1.get(0);//list1是Person所以最小可以使用Person来接收 因为接收是用的它的父类// 编译不通过//Student s list1.get(0);//不能用Student类型接收 Student比Person还小不能接收list2 list4;Object obj list2.get(0);//list2是PersonPerson是最小的比Person还大的数据只能使用Object来接收//编译不通过
// Person obj list2.get(0);//写入数据 同样null肯定可以写入对应非null的数据只要理解2种方式父子类的关系就明白了//编译不通过 list1代表[-∞,Person] 此时Person是最大的类list1可以是比Person小很多的类小到比Student还小此时把大类型Student赋值给小类型当然报错。
// list1.add(new Student());//编译通过 list2代表[Person,∞],此时Person是最小的类list2别的数据肯定比Person更大我new的是比Person还小的类这个小的类当然可以赋值给大的类型list2了。list2.add(new Person());list2.add(new Student());}}
4.5.4 泛型应用举例实体类中通用的对象属性
举例1泛型嵌套
public static void main(String[] args) {HashMapString, ArrayListCitizen map new HashMapString, ArrayListCitizen();ArrayListCitizen list new ArrayListCitizen();list.add(new Citizen(赵又廷));list.add(new Citizen(高圆圆));list.add(new Citizen(瑞亚));map.put(赵又廷, list);SetEntryString, ArrayListCitizen entrySet map.entrySet();IteratorEntryString, ArrayListCitizen iterator entrySet.iterator();while (iterator.hasNext()) {EntryString, ArrayListCitizen entry iterator.next();String key entry.getKey();ArrayListCitizen value entry.getValue();System.out.println(户主 key);System.out.println(家庭成员 value);}
}
举例2个人信息设计
用户在设计类的时候往往会使用类的关联关系例如一个人中可以定义一个信息的属性但是一个人可能有各种各样的信息如联系方式、基本信息等所以此信息属性的类型就可以通过泛型进行声明然后只要设计相应的信息类即可。 注意
原来的写法class Person extends Info{ } 即Person 这里实体类Person的写法class PersonT extends Info{ } 这里写的是T不是通配符所以不是父类的情况而是一个具体的类型表示 new Person对象的时候T指明的类型必须是Info的子类 这样在new不同的子类对象时就可以根据泛型动态的设置不同人的个人信息。
interface Info{ // 只有此接口的子类才是表示人的信息
}
class Contact implements Info{ // 表示联系方式private String address ; // 联系地址private String telephone ; // 联系方式private String zipcode ; // 邮政编码public Contact(String address,String telephone,String zipcode){this.address address;this.telephone telephone;this.zipcode zipcode;}public void setAddress(String address){this.address address ;}public void setTelephone(String telephone){this.telephone telephone ;}public void setZipcode(String zipcode){this.zipcode zipcode;}public String getAddress(){return this.address ;}public String getTelephone(){return this.telephone ;}public String getZipcode(){return this.zipcode;}Overridepublic String toString() {return Contact [address address , telephone telephone , zipcode zipcode ];}
}
class Introduction implements Info{private String name ; // 姓名private String sex ; // 性别private int age ; // 年龄public Introduction(String name,String sex,int age){this.name name;this.sex sex;this.age age;}public void setName(String name){this.name name ;}public void setSex(String sex){this.sex sex ;}public void setAge(int age){this.age age ;}public String getName(){return this.name ;}public String getSex(){return this.sex ;}public int getAge(){return this.age ;}Overridepublic String toString() {return Introduction [name name , sex sex , age age ];}
}
class PersonT extends Info{private T info ;public Person(T info){ // 通过构造器设置信息属性内容this.info info;}public void setInfo(T info){this.info info ;}public T getInfo(){return info ;}Overridepublic String toString() {return Person [info info ];}}
public class GenericPerson{public static void main(String args[]){PersonContact per null ; // 声明Person对象per new PersonContact(new Contact(北京市,01088888888,102206)) ;System.out.println(per);PersonIntroduction per2 null ; // 声明Person对象per2 new PersonIntroduction(new Introduction(李雷,男,24));System.out.println(per2) ;}
}5. 集合—01
5.1 集合相关说明
5.1.1 出现背景 集合、数组都是对多个数据进行存储操作的结构简称Java容器。 说明此时的存储主要指的是内存层面的存储不涉及到持久化的存储.txt,.jpg,.avi数据库中 数组在存储多个数据方面的特点 2.1 一旦初始化以后其长度就确定了。 2.2 数组一旦定义好其元素的类型也就确定了。我们也就只能操作指定类型的数据了。如果想要数组里面可以写任意类的数据 则可以定义为Object类型。比如String[ ] arr;int[ ] arr1;Object[ ] arr2; 数组在存储多个数据方面的缺点 3.1 一旦初始化以后其长度就不可修改。 3.2 数组中提供的方法非常有限对于添加、删除、插入数据等操作非常不便同时效率不高。 3.3 获取数组中实际元素的个数的需求数组没有现成的属性或方法可用。(length只是获取数组能存几个数而不是实际存的个数) 3.4 数组存储数据的特点有序、可重复。对于无序、不可重复的需求不能满足。 以上缺点集合都能解决。
5.1.2 使用场景 5.1.3 java集合的分类 集合框架:都在util包中。|----Collection接口单列集合用来存储一个一个的对象|----List接口存储有序的、可重复的数据。 --“动态”数组|----ArrayList 实现类|----LinkedList|----Vector|----Set接口存储无序的、不可重复的数据 --高中讲的“集合”|----HashSet|----LinkedHashSet|----TreeSet|----Map接口双列集合用来存储一对(key - value)一对的数据 --高中函数y f(x)|----HashMap|----LinkedHashMap|----Hashtable|----Properties|----TreeMap5.2 Collection接口
5.2.1 概述
说明
英文名称Collection是用来存放Object对象类型的数据结构其中长度可变并提供了一组操作成批对象的方法。集合里面存的是Object对象类型的数据结构 即存放的都是任意引用类型的数据存放基本类型是因为自动装箱功能。集合可以存放各种类型数据这点不好所以在jdk1.5引用了泛型用来控制类型统一。注意集合不像数组那样不能直接输出如果集合中存的数据是系统提供的类里面自动重写了toString方法所以可以直接输出。
5.2.2 特点
单列集合用来存储一个一个的对象。集合中的数据类型都为 Object类型的引用数据类型对于基本数据类型能够储存是因为有自动装箱功能变为包装类。遍历方式2种迭代器增强for循环(又叫作foreach循环)。也可以直接调用foreach方法进行遍历详情查看jdk1.8新特性
5.2.3 Collection接口的继承结构
|----Collection接口单列集合用来存储一个一个的对象|----List接口存储有序的、可重复的数据。 --“动态”数组|----ArrayList 实现类|----LinkedList|----Vector|----Set接口存储无序的、不可重复的数据 --高中讲的“集合”|----HashSet|----LinkedHashSet|----TreeSet5.2.4 Collection接口的常用方法(集合数组互转)
说明暂时学习jdk8之前的常用方法(一共15个)其余的到jdk新特性时讲解。
CollectionTest 类
package com.atguigu.java2;import org.junit.Test;import java.util.*;/*** Collection接口中的方法的使用:** 结论* 向 Collection接口的实现类的对象中添加数据obj时要求obj所在类要重写equals().* * author shkstart* create 2019 下午 4:08*/
public class CollectionTest {Testpublic void test1(){Collection coll new ArrayList();//因为是接口测试里面的方法只能是多态形式//1. boolean add(Object e):将元素e添加到集合coll中coll.add(AA);coll.add(BB);coll.add(123);//自动装箱Object指的是任何引用对象类型这里填基本数据类型是因为自动装箱为包装类。coll.add(new Date());//2. int size():获取添加的元素的个数System.out.println(coll.size());//4//3. boolean addAll(Collection coll1):将coll1集合中的元素添加到当前的集合中Collection coll1 new ArrayList();coll1.add(456);coll1.add(CC);coll.addAll(coll1);System.out.println(coll.size());//6System.out.println(coll);//[AA, BB, 123, Fri Jan 28 21:08:38 CST 2022, 456, CC]//4. void clear():清空集合元素,但对象还存在coll.clear();//5. boolean isEmpty():判断当前集合是否为空System.out.println(coll.isEmpty());//true}Testpublic void test2(){Collection coll new ArrayList();coll.add(123);coll.add(456);
// Person p new Person(Jerry,20);
// coll.add(p);coll.add(new Person(Jerry,20));coll.add(new String(Tom));coll.add(false);//6.boolean contains(Object obj):判断当前集合中是否包含obj//我们在判断时会调用obj对象所在类的equals()。boolean contains coll.contains(123);System.out.println(contains);//trueSystem.out.println(coll.contains(new String(Tom)));
// System.out.println(coll.contains(p));//true,String默认重写过了equals方法此时比较的是对象里面的内容所以为true//解释自己定义的类如果没重写比较的是地址所以为false,开发中一般要求向Collection接口的实现类的对象中添加数据obj时要求obj所在类要重写equals().System.out.println(coll.contains(new Person(Jerry,20)));//false --true//7.boolean containsAll(Collection coll1):判断形参coll1中的所有元素是否都存在于当前集合中。Collection coll1 Arrays.asList(123,4567);//创建集合的返回值为List,此时用Collection接收System.out.println(coll.containsAll(coll1));}Testpublic void test3(){//8.boolean remove(Object obj):从当前集合中移除obj元素。Collection coll new ArrayList();coll.add(123);coll.add(456);coll.add(new Person(Jerry,20));coll.add(new String(Tom));coll.add(false);coll.remove(1234);//仍然会调用equals方法因为数据为包装类型底层重写了equals方法所以会移除成功返回true。System.out.println(coll);coll.remove(new Person(Jerry,20));//自己写的类重写了equals方法所以也能移除成功。返回true.System.out.println(coll);//9. boolean removeAll(Collection coll1):差集从当前集合中移除coll1中所有的元素。Collection coll1 Arrays.asList(123,456);coll.removeAll(coll1);System.out.println(coll);}Testpublic void test4(){Collection coll new ArrayList();coll.add(123);coll.add(456);coll.add(new Person(Jerry,20));coll.add(new String(Tom));coll.add(false);//10.boolean retainAll(Collection coll1):交集获取当前集合和coll1集合的交集并返回给当前集合(不会新创建集合和数组不同)
// Collection coll1 Arrays.asList(123,456,789);
// coll.retainAll(coll1);
// System.out.println(coll);//11.boolean equals(Object obj):比较2个对象是否相等要想返回true需要当前集合和形参集合的元素都相同。Collection coll1 new ArrayList();coll1.add(456);//考虑顺序问题如果顺序不一致为false.因为list是有序的。如果是set集合在这个地方就不需要考虑顺序了 为true。coll1.add(123);coll1.add(new Person(Jerry,20));coll1.add(new String(Tom));coll1.add(false);System.out.println(coll.equals(coll1));}Testpublic void test5(){Collection coll new ArrayList();coll.add(123);coll.add(456);coll.add(new Person(Jerry,20));coll.add(new String(Tom));coll.add(false);//12.int hashCode():返回当前对象的哈希值System.out.println(coll.hashCode());//13.集合 ---数组Object[] toArray()Object[] arr coll.toArray();for(int i 0;i arr.length;i){System.out.println(arr[i]);}//拓展数组 ---集合:调用Arrays类的静态方法static T ListT asList(T... a) 只考虑和List集合互转因为List本质上就是动态数组(长度可以改变)。ListString list Arrays.asList(new String[]{AA, BB, CC});System.out.println(list);List arr1 Arrays.asList(new int[]{123, 456});//此方法还提供了一种方便的方式来创建一个初始化为包含几个元素的固定大小的列表// List arr1 Arrays.asList(123, 456);System.out.println(arr1.size());//1,这种写法认为数组只有一个元素List arr2 Arrays.asList(new Integer[]{123, 456});System.out.println(arr2.size());//2写为包装类型才认为是2个元素//14.iIteratorE terator():返回Iterator接口的实例用于遍历集合元素。---放在IteratorTest.java中测试//15. T T[] toArray(T[] a) 返回包含此集合中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型。---泛型时讲}}
Person 类
package com.atguigu.java2;import java.util.Objects;/*** author shkstart* create 2019 上午 10:06*/
public class Person {private String name;private int age;public Person() {}public Person(String name, int age) {this.name name;this.age age;}public String getName() {return name;}public void setName(String name) {this.name name;}public int getAge() {return age;}public void setAge(int age) {this.age age;}Overridepublic String toString() {return Person{ name name \ , age age };}Overridepublic boolean equals(Object o) {System.out.println(Person equals()....);if (this o) return true;if (o null || getClass() ! o.getClass()) return false;Person person (Person) o;return age person.age Objects.equals(name, person.name);}Overridepublic int hashCode() {return Objects.hash(name, age);}
}
5.2.5 使用迭代器Iterator接口 迭代器的执行原理:
package com.atguigu.java2;import org.junit.Test;import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;/*** 集合元素的遍历操作使用迭代器Iterator接口。迭代器只适合于collection,不适用于Map.* 1.内部的方法hasNext() 和 next()* 2.集合对象每次调用iterator()方法都得到一个全新的迭代器对象* 默认游标都在集合的第一个元素之前。* 3.内部定义了remove(),可以在遍历的时候删除集合中的元素。此方法不同于集合直接调用remove()为迭代器提供的方法。** author shkstart* create 2019 上午 10:44*/
public class IteratorTest {Testpublic void test1(){Collection coll new ArrayList();coll.add(123);coll.add(456);coll.add(new Person(Jerry,20));coll.add(new String(Tom));coll.add(false);Iterator iterator coll.iterator();//方式一开发中不会这么写。 Object next() 返回迭代中的下一个元素。
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// //如果下条元素不存在则报异常NoSuchElementException
// System.out.println(iterator.next());//方式二不推荐因为推荐使用hsahNext()方法
// for(int i 0;i coll.size();i){
// System.out.println(iterator.next());
// }//方式三推荐boolean hasNext():判断是否还有下一个元素while(iterator.hasNext()){//next():①指针下移 ②将下移以后集合位置上的元素返回System.out.println(iterator.next());}}Testpublic void test2(){Collection coll new ArrayList();coll.add(123);coll.add(456);coll.add(new Person(Jerry,20));coll.add(new String(Tom));coll.add(false);//错误方式一会跳着输出并且报异常
// Iterator iterator coll.iterator();
// while((iterator.next()) ! null){
// System.out.println(iterator.next());
// }//错误方式二死循环一直输出 123//集合对象每次调用iterator()方法都得到一个全新的迭代器对象默认游标都在集合的第一个元素之前。while (coll.iterator().hasNext()){System.out.println(coll.iterator().next());}}//测试Iterator中的remove()//如果还未调用next() (指针问题)或在上一次调用 next 方法之后已经调用了 remove 方法(不能重复删除)// 再调用remove都会报IllegalStateException。原因未调用next方法 此时指针在最上方没有数据不能移除调用了 remove 方法再次调用没有数据无法在移除Testpublic void test3(){Collection coll new ArrayList();coll.add(123);coll.add(456);coll.add(new Person(Jerry,20));coll.add(new String(Tom));coll.add(false);//删除集合中TomIterator iterator coll.iterator();while (iterator.hasNext()){
// iterator.remove();Object obj iterator.next();if(Tom.equals(obj)){iterator.remove();
// iterator.remove();}}//遍历集合iterator coll.iterator();//因为上面遍历集合指针已经到最后一位了。重写遍历需要一个新的迭代器对象 默认游标都在集合的第一个元素之前。while (iterator.hasNext()){System.out.println(iterator.next());}}
}
5.2.6 新特性foreach(增强for循环)遍历 集合数组 package com.atguigu.java2;import org.junit.Test;import java.util.ArrayList;
import java.util.Collection;/*** jdk 5.0 新增了foreach循环用于遍历集合、数组** author shkstart* create 2019 上午 11:24*/
public class ForTest {Testpublic void test1(){Collection coll new ArrayList();coll.add(123);coll.add(456);coll.add(new Person(Jerry,20));coll.add(new String(Tom));coll.add(false);//for(集合元素的类型 局部变量 : 集合对象)//内部仍然调用了迭代器。//执行流程取第一个元素赋给obj,之后打印obj.取第二个......for(Object obj : coll){System.out.println(obj);}}Testpublic void test2(){int[] arr new int[]{1,2,3,4,5,6};//for(数组元素的类型 局部变量 : 数组对象)for(int i : arr){System.out.println(i);}}//练习题Testpublic void test3(){String[] arr new String[]{MM,MM,MM};// //方式一普通for赋值 输出为GG,因为改变的是数组本身的一个个元素。
// for(int i 0;i arr.length;i){
// arr[i] GG;
// }//方式二增强for循环 输出为GG因为把值赋给了s,s把值改变了.for(String s : arr){s GG;}for(int i 0;i arr.length;i){System.out.println(arr[i]);}}
}
5.3 Collection子接口之一List接口
5.3.1 概述
有序存储的数据在底层数组中按照数组索引的顺序添加
有序可重复。以存储多个null值。元素都有索引。遍历方式4种迭代器子接口迭代器增强for循环普通for循环。
5.3.2 继承结构 List接口框架|----Collection接口单列集合用来存储一个一个的对象|----List接口存储有序的、可重复的数据。 --“动态”数组,替换原有的数组|----ArrayList作为List接口的主要实现类线程不安全的效率高底层使用Object[] elementData;数组存储|----LinkedList对于频繁的插入、删除操作使用此类效率比ArrayList高底层使用双向链表存储|----Vector作为List接口的古老实现类线程安全的效率低底层使用Object[] elementData;数组存储5.3.3 测试常用方法。
说明List接口产生了特有方法都是按照索引操作的方法可以存储多个null。
package com.atguigu.java2;import org.junit.Test;import java.util.*;/*** 1. ArrayList的源码分析* 1.1 jdk 7情况下* ArrayList list new ArrayList();//底层创建了长度是10的Object[]数组elementData* list.add(123);//elementData[0] new Integer(123);* ...* list.add(11);//如果此次的添加导致底层elementData数组容量不够则扩容。* 默认情况下扩容为原来的容量的1.5倍同时需要将原有数组中的数据复制到新的数组中。** 结论建议开发中使用带参的构造器ArrayList list new ArrayList(int capacity),创建时指定大小 避免扩容影响效率。** 1.2 jdk 8中ArrayList的变化* ArrayList list new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组** list.add(123);//第一次调用add()时底层才创建了长度10的数组并将数据123添加到elementData[0]* ...* 后续的添加和扩容操作与jdk 7 无异。* 1.3 小结jdk7中的ArrayList的对象的创建类似于单例的饿汉式而jdk8中的ArrayList的对象* 的创建类似于单例的懒汉式延迟了数组的创建节省内存。** 2. LinkedList的源码分析* LinkedList list new LinkedList(); 内部声明了Node类型的first和last属性默认值为null* list.add(123);//将123封装到Node中创建了Node对象。** 其中Node定义为体现了LinkedList的双向链表的说法* private static class NodeE {E item;NodeE next;NodeE prev;Node(NodeE prev, E element, NodeE next) {this.item element;this.next next;this.prev prev;}}** 3. Vector的源码分析jdk7和jdk8中通过Vector()构造器创建对象时底层都创建了长度为10的数组。* 在扩容方面默认扩容为原来的数组长度的2倍。** 面试题ArrayList、LinkedList、Vector三者的异同* 同三个类都是实现了List接口存储数据的特点相同存储有序的、可重复的数据* 不同见上**** 4. List接口中的特有方法都是按照索引操作可以存储多个null。** author shkstart* create 2019 上午 11:39*/
public class ListTest {/*
void add(int index, Object ele):在index位置插入ele元素
boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
Object get(int index):获取指定index位置的元素
int indexOf(Object obj):返回obj在集合中首次出现的位置
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素并返回此元素
Object set(int index, Object ele):设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合总结常用方法包括collection的方法。
增add(Object obj)
删remove(int index) / remove(Object obj)
改set(int index, Object ele)
查get(int index)
插add(int index, Object ele)
长度size()
遍历① Iterator迭代器方式② 增强for循环③ 普通的循环因为有索引所以可以使用普通for循环*/Testpublic void test3(){ArrayList list new ArrayList();list.add(123);list.add(456);list.add(AA);//方式一Iterator迭代器方式Iterator iterator list.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}System.out.println(***************);//方式二ListIterator为Iterator接口的子接口可以逆向遍历但几乎不咋用ListIterator it2 list.listIterator() ;while( it2.hasNext() ) {//判断是否有 后一个 元素System.out.println( it2.next());//获取后一个 元素}//方式三增强for循环for(Object obj : list){System.out.println(obj);}System.out.println(***************);//方式四普通for循环for(int i 0;i list.size();i){System.out.println(list.get(i));}}Testpublic void test2(){ArrayList list new ArrayList();list.add(123);list.add(456);list.add(AA);list.add(new Person(Tom,12));list.add(456);//4.int indexOf(Object obj):返回obj在集合中首次出现的位置。如果不存在返回-1. 类似于String中的indexof的方法int index list.indexOf(4567);System.out.println(index);//5.int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置。如果不存在返回-1.System.out.println(list.lastIndexOf(456));//6.Object remove(int index):移除指定index位置的元素并返回此元素(相当于重载的collection中的remove方法之前是根据元素删除这是根据下标删除)Object obj list.remove(0);System.out.println(obj);System.out.println(list);//7.Object set(int index, Object ele):设置指定index位置的元素为elelist.set(1,CC);System.out.println(list);//8.List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的左闭右开区间的子集合List subList list.subList(2, 4);System.out.println(subList);System.out.println(list);}Testpublic void test1(){ArrayList list new ArrayList();list.add(123);list.add(456);list.add(AA);list.add(new Person(Tom,12));list.add(456);System.out.println(list);//1.void add(int index, Object ele):在index位置插入ele元素list.add(1,BB);System.out.println(list);//2.boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来List list1 Arrays.asList(1, 2, 3);list.addAll(list1); //有几个是几个
// list.add(list1); 看成一个元素System.out.println(list.size());//9//3.Object get(int index):获取指定index位置的元素System.out.println(list.get(0));}}
5.3.4 面试题
package com.atguigu.exer;import org.junit.Test;import java.util.ArrayList;
import java.util.List;/*** author shkstart* create 2019 下午 3:33*/
public class ListExer {/*区分List中remove(int index)和remove(Object obj)*/Testpublic void testListRemove() {List list new ArrayList();list.add(1);list.add(2);list.add(3);updateList(list);System.out.println(list);//[1,2]}private void updateList(List list) {
// list.remove(2); 直接写用的是根据下标进行删除list.remove(new Integer(2));//手动装箱后才是根据元素进行删除}}
5.4 List接口实现类之一ArrayList
5.4.1 概述
是List接口的实现类可以使用父接口List的功能也可以使用父父接口Collection的功能 没有产生特有方法。底层封装了Object类型的数组存放数据封装了数组的操作每个对象都有下标。内部数组默认初始容量是10。如果不够会以1.5倍容量增长。List接口的大小由可变数组的实现。每个 ArrayList 实例都有一个容量。该容量是指用来存储列表元素的数组的大小。查询快增删数据效率会降低。遍历方式4种迭代器子接口迭代器增强for循环普通for循环。凡是用数组的地方都可以替换为ArrayList。 5.4.2 创建对象
new ArrayList()//初始容量是10的空列表5.5 List接口实现类之二LinkedList 5.5.1 概述
双向链表两端效率高。底层就是数组和链表实现的。增加了特有方法一般都是和首位相关的方法。 有下标但在空间散乱排布所以查询慢。 双向链表下标遍历效率低迭代器遍历效率高4种遍历方式除了用下标的普通for循环速度慢另外3种都可以。
5.5.2 特点
底层维护了一个链表结构.空间不连续.也就造成查询业务效率低.链表结构适用于新增或者删除业务.链表上有两个高效节点:首元素和尾元素创建对象 LinkedList() 构造一个空列表。
5.5.3 特有方法 void addFirst(E e) 将指定元素插入此列表的开头。 void addLast(E e) 将指定元素添加到此列表的结尾。 E getFirst() 返回此列表的第一个元素。 E getLast() 返回此列表的最后一个元素。 E removeFirst() 移除并返回此列表的第一个元素 E removeLast() 移除并返回此列表的最后一个元素。 //用上面那套方法add就行2种都可以。 boolean offerFirst(E e) 在此列表的开头插入指定的元素。 boolean offer(E e) 将指定元素添加到此列表的末尾最后一个元素。 boolean offerLast(E e) 在此列表末尾插入指定的元素。 E peek() 获取但不移除此列表的头第一个元素。 E peekFirst() 获取但不移除此列表的第一个元素如果此列表为空则返回 null。 E peekLast() 获取但不移除此列表的最后一个元素如果此列表为空则返回 null。 E poll() 获取并移除此列表的头第一个元素 E pollFirst() 获取并移除此列表的第一个元素如果此列表为空则返回 null。 E pollLast() 获取并移除此列表的最后一个元素如果此列表为空则返回 null。 E pop() 从此列表所表示的堆栈处弹出一个元素。 5.5.4 练习1测试特有方法
package cn.tedu.collectiondemo;import java.util.LinkedList;//测试LinkedList实现类 public class Test4_LinkedList {public static void main(String[] args) {//1,创建对象//--LinkedList 底层维护了一个链表结构,方便增删,不方便查询.而且整个链表上的节点中,只存在两个高效节点就是首尾元素.LinkedListString list new LinkedList();//2,常用方法//TODO 继承自Collection和List接口的方法们list.add(1);list.add(2);list.add(3);list.add(1);list.add(2);System.out.println(list);//有序 可重复 [1, 2, 3, 1, 2]//--LinkedList的特有方法们list.addFirst(100); //添加首元素list.addLast(200); //添加尾元素System.out.println(list);//[100, 1, 2, 3, 1, 2, 200]System.out.println( list.getFirst() ); //获取首元素System.out.println( list.getLast() ); //获取尾元素System.out.println( list.removeFirst() );//移除首元素System.out.println( list.removeLast() );//移除尾元素System.out.println(list);//[1, 2, 3, 1, 2]}}5.6 List接口实现类之三Vector
基本不用略。
6 集合—02
6.1 Collection子接口之二Set接口
6.1.1 概述
一个不包含重复元素的 collection。数据无序(因为set集合没有下标)。由于集合中的元素不可以重复常用于给数据去重。最多包含一个null元素。使用的都是collection里面的方法没有产生特有的方法
6.1.2 继承结构
|----Collection接口单列集合用来存储一个一个的对象|----Set接口存储无序的、不可重复的数据 --高中讲的“集合”|----HashSet作为Set接口的主要实现类线程不安全的可以存储null值|----LinkedHashSet作为HashSet的子类遍历其内部数据时可以按照添加的顺序遍历对于频繁的遍历操作(看似有序实际上还是无序的要理解无序的概念)LinkedHashSet效率高于HashSet.|----TreeSet可以按照添加对象的指定属性进行排序。要求添加的元素是同一个类new的对象6.2 Set实现类之一HashSet
6.2.1 概述
此类实现 Set 接口由哈希表实际上是一个 HashMap 实例支持。它不保证 set 的迭代顺序特别是它不保证该顺序恒久不变。此类允许使用 null 元素。 没有特有方法
6.2.2 特点
底层是hashMap。像hasSet里面添加数据实际上是向hsahMap中添加数据。 数据储存是数组链表。
注意面试的时候一般不会问HashSet的底层原理实现因为HashSet的底层是HashMap直接说HashMap的底层原理就可以了。
6.2.3 测试
SetTest类
package com.atguigu.java2;import org.junit.Test;import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;/*** 1. Set接口的框架** |----Collection接口单列集合用来存储一个一个的对象* |----Set接口存储无序的、不可重复的数据 --高中讲的“集合”* |----HashSet作为Set接口的主要实现类线程不安全的可以存储null值* |----LinkedHashSet作为HashSet的子类遍历其内部数据时可以按照添加的顺序遍历* 对于频繁的遍历操作LinkedHashSet效率高于HashSet.* |----TreeSet可以按照添加对象的指定属性进行排序。要求添加的元素是同一个类new的对象*** 1. Set接口中没有额外定义新的方法使用的都是Collection中声明过的方法。** 2. 要求向Set(主要指HashSet、LinkedHashSet)中添加的数据其所在的类一定要重写hashCode()和equals()* * 要求重写的hashCode()和equals()尽可能保持一致性相等的对象(equals为true)必须具有相等的散列码(散列码即hash值相同对象的属性 计算的hash值也应该相同)* 重写两个方法的小技巧对象中用作 equals() 方法比较的 Field属性都应该用来计算 hashCode 值。一般自动生成都能保证重写的equals* 方法和hashCode方法用到的属性一致。*** author shkstart* create 2019 下午 3:40*/
public class SetTest {/*一、Set存储无序的、不可重复的数据以HashSet为例说明1. 无序性不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加而是根据数据的哈希值决定的。输出结果没有按照添加储存的顺序排列而是根据哈希值排列的所以每次输出的结果和第一次输出结果相同。如果是随机则每次输出的结果都不同。2. 添加元素时会自动调用equals方法保证添加的元素按照调用equals()判断时 不能返回true.即相同的元素只能添加一个。比较对象的内容需要重写equals()方法和hashcode()方法。如果是只比较equals()方法那么添加第1000条数据需要和前999条数据进行比较效率太低。所以要先进行hash值比较 具体过程如下。二、添加元素的过程以HashSet为例我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法计算元素a的哈希值此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置即为索引位置判断数组此位置上是否已经有元素如果此位置上没有其他元素则元素a添加成功。 ---情况1如果此位置上有其他元素b(或以链表形式存在的多个元素则比较元素a与元素b的hash值如果hash值不相同则元素a添加成功。---情况2如果hash值相同进而需要调用元素a所在类的equals()方法equals()返回true,元素a添加失败equals()返回false,则元素a添加成功。---情况2对于添加成功的情况2和情况3而言元素a 与已经存在指定索引位置上数据以链表的方式存储。jdk 7 :元素a放到数组中指向原来的元素。(新元素放在数组中原来的元素放在外面链表中)jdk 8 :原来的元素在数组中指向元素a (新元素放在链表中原来的元素放在数组中)总结七上八下HashSet底层数组链表的结构。*/Testpublic void test1(){Set set new HashSet();set.add(456);set.add(123);set.add(123);set.add(AA);set.add(CC);set.add(new User(Tom,12));set.add(new User(Tom,12));set.add(129);System.out.println(set);Iterator iterator set.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}}}
User类
package com.atguigu.java2;/*** author shkstart* create 2019 下午 3:56*/
public class User {private String name;private int age;public User() {}public User(String name, int age) {this.name name;this.age age;}public String getName() {return name;}public void setName(String name) {this.name name;}public int getAge() {return age;}public void setAge(int age) {this.age age;}Overridepublic String toString() {return User{ name name \ , age age };}//自动生成的equalsOverridepublic boolean equals(Object o) {System.out.println(User equals()....);if (this o) return true;if (o null || getClass() ! o.getClass()) return false;User user (User) o;if (age ! user.age) return false;return name ! null ? name.equals(user.name) : user.name null;}//自动生成的hashCodeOverride//没有重写调用的是object提供的hashCode()方法 是随机算一个数 不用比就能成功添加元素可重复。public int hashCode() {int result name ! null ? name.hashCode() : 0;//一般自动生成就行*31是因为扩大膨胀系数 减少出错概率。result 31 * result age;return result;}/*自己写的方法hashCode比较粗糙但是也能用因为有可能不同的属性出现相同的hash值。解释一般要求是2个对象的属性不同 算出来的hash值也不同2个对象的属性相同 算出来的hsah值相同。我们这种写法有可能出现2个对象的属性不同 算出来的hash值相同。如对象1name 20 age 24 . 对象2name 24 age 20相加都等于44在存的时候 hash值相同 在调用equals比较值不同虽然也能存入成功但是是上下存的一个存入数组 一个存入链表 用到了指针本来不同直接是在同一个数组不同位置进行储存这种效率更高 用到了指针这种上下存的效率较低。*/
// Override
// public int hashCode() {
// return name.hashCode() age;
// }}
6.2.4 解释膨胀系数为什么是*31 6.3 HashSet的子类LinkedHashSet
6.3.1 概述
说明相当于hashSet在添加数据的同时 加上了一层双向链表。可以保证添加顺序和输出结果顺序保持一致实际上还是无序的。
6.3.2 测试
package com.atguigu.java2;import org.junit.Test;import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;public class SetTest {//LinkedHashSet的使用//LinkedHashSet作为HashSet的子类在添加数据的同时每个数据还维护了两个引用记录此数据前一个//数据和后一个数据。//优点对于频繁的遍历操作LinkedHashSet效率高于HashSet因为记录的有数据位置 不用在一个一个的找了。Testpublic void test2(){Set set new LinkedHashSet();set.add(456);set.add(123);set.add(123);set.add(AA);set.add(CC);set.add(new User(Tom,12));set.add(new User(Tom,12));set.add(129);Iterator iterator set.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}}
}
6.3.3 测试在List内去除重复数字值 6.4 Set实现类之二TreeSet
6.4.1 概述
说明可以按照添加对象的指定属性进行排序。要求添加的元素是同一个类new的对象
总结
List集合需要重写equals方法里面的一些方法需要调用contains() /remove()/retainsAll() ….hashSet,LinkedHashSet需要重写equalshashCode方法放数据时避免重复TreeSet不需要重写equalshsahCode方法用的是ComparablecompareTo,Comparatorcompare(Object o1,Object o2)
6.4.2 测试TreeSet的自然排序和定制排序
TreeSetTest 类
package com.atguigu.java2;import org.junit.Test;import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;/*** author shkstart* create 2019 下午 4:59*/
public class TreeSetTest {/*1.向TreeSet中添加的数据要求是相同类的对象。2.两种排序方式自然排序实现Comparable接口 和 定制排序Comparator 其实就是java中的比较器只不过之前是Arrays.sort或者集合工具类collentions.sort调用的这个是TreeSet添加数据时调用的。3.自然排序中比较两个对象是否相同的标准为compareTo()返回0.不再是equals().是否添加数据成功取决于你的compare方法如果2个对象有2个属性 name:aa age:20name:aa age:30,不再是equals方法认为是不同的对象可以添加成功compareTo方法如果只比较了一个属性 则代表是相同的对象添加不成功想要认为是不同的对象 需要重写compareTo方法时2个属性都进行比较。4.定制排序中比较两个对象是否相同的标准为compare()返回0.不再是equals(). 同上......*///自然排序Testpublic void test1(){TreeSet set new TreeSet();//失败不能添加不同类的对象
// set.add(123);
// set.add(456);
// set.add(AA);
// set.add(new User(Tom,12));//举例一
// set.add(34); //系统提供的类重写compareTo方法默认是从小到大排序的自然排序
// set.add(-34);
// set.add(43);
// set.add(11);
// set.add(8);//举例二自定义类set.add(new User(Tom,12));set.add(new User(Jerry,32));set.add(new User(Jim,2));set.add(new User(Mike,65));set.add(new User(Jack,33));set.add(new User(Jack,56));Iterator iterator set.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}}//定制排序Testpublic void test2(){Comparator com new Comparator() {//按照年龄从小到大排列Overridepublic int compare(Object o1, Object o2) {if(o1 instanceof User o2 instanceof User){User u1 (User)o1;User u2 (User)o2;return Integer.compare(u1.getAge(),u2.getAge());}else{throw new RuntimeException(输入的数据类型不匹配);}}};TreeSet set new TreeSet(com);//不加参数默认按照自然排序排列set.add(new User(Tom,12));set.add(new User(Jerry,32));set.add(new User(Jim,2));set.add(new User(Mike,65));set.add(new User(Mary,33));//存放相同的对象属性name,因为是按照compare方法比较的里面只写了age的比较 认为年龄相同是一个对象,只能存放一个对象 谁在前存谁。set.add(new User(Jack,33));set.add(new User(Jack,56));Iterator iterator set.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}}}
Person类
package com.atguigu.java2;/*** author shkstart* create 2019 下午 3:56*/
public class User implements Comparable{private String name;private int age;public User() {}public User(String name, int age) {this.name name;this.age age;}public String getName() {return name;}public void setName(String name) {this.name name;}public int getAge() {return age;}public void setAge(int age) {this.age age;}Overridepublic String toString() {return User{ name name \ , age age };}//按照姓名从大到小排列,年龄从小到大排列Overridepublic int compareTo(Object o) {if(o instanceof User){User user (User)o;
// return -this.name.compareTo(user.name);int compare -this.name.compareTo(user.name);if(compare ! 0){return compare;}else{return Integer.compare(this.age,user.age);}}else{throw new RuntimeException(输入的类型不匹配);}}
}
7. 集合—03
7.1 Map接口
7.1.1 概述 和collection没啥关系顶多是平级关系 7.1.2 特点
可以根据键 提取对应的值键不允许重复 值可以重复如果键重复值会被覆盖即如果Key重复不会报错会把value值覆盖存放的都是无序数据初始容量是16默认的加载因子是0.75 数据都是键值对K V同时指定
7.1.3 继承结构
一、Map的实现类的结构|----Map:双列数据存储key-value对的数据 ---类似于高中的函数y f(x)|----HashMap:作为Map的主要实现类线程不安全的效率高存储null的key和value|----LinkedHashMap:保证在遍历map元素时可以按照添加的顺序实现遍历。原因在原有的HashMap底层结构基础上添加了一对指针指向前一个和后一个元素。对于频繁的遍历操作此类执行效率高于HashMap。|----TreeMap:保证按照添加的key-value对进行排序实现排序遍历。此时考虑key的自然排序或定制排序底层使用红黑树|----Hashtable(注意table这个t为小写):作为古老的实现类线程安全的效率低不能存储null的key和value|----Properties:常用来处理配置文件。key和value都是String类型HashMap的底层数组链表 jdk7及之前数组链表红黑树 jdk 8
与set的对应关系HashSet底层是HashMap…
HashSet LinkedHashSet TreeSet
HashMap LinkedHashMap TreeMap7.1.4 常用方法包括遍历方式 遍历方式图解
package com.atguigu.java;import org.junit.Test;import java.util.*;/*** 一、Map的实现类的结构* |----Map:双列数据存储key-value对的数据 ---类似于高中的函数y f(x)* |----HashMap:作为Map的主要实现类线程不安全的效率高存储null的key和value* 底层数组链表 jdk7及之前* 数组链表红黑树 jdk 8* |----LinkedHashMap:保证在遍历map元素时可以按照添加的顺序实现遍历。* 原因在原有的HashMap底层结构基础上添加了一对指针指向前一个和后一个元素。* 对于频繁的遍历操作此类执行效率高于HashMap。* |----TreeMap:保证按照添加的key-value对进行排序实现排序遍历。此时考虑key的自然排序或定制排序* 底层使用红黑树* |----Hashtable:作为古老的实现类线程安全的效率低不能存储null的key和value* |----Properties:常用来处理配置文件。key和value都是String类型****** 面试题* 1. HashMap的底层实现原理* 2. HashMap 和 Hashtable的异同* 3. CurrentHashMap 与 Hashtable的异同暂时不讲** 二、Map结构的理解* Map中的key:无序的、不可重复的使用Set存储所有的key --- key所在的类要重写equals()和hashCode() 可以保证添加数据时无序不可重复* 以HashMap为例TreeMap有涉及到compare,compareTo方法了* Map中的value:无序的、可重复的使用Collection存储所有的value ---value所在的类要重写equals()* 一个键值对key-value构成了一个Entry对象。* Map中的entry:无序的、不可重复的使用Set存储所有的entry** 三、HashMap的底层实现原理以jdk7为例说明* HashMap map new HashMap():* 在实例化以后底层创建了长度是16的一维数组Entry[] table。* ...可能已经执行过多次put...* map.put(key1,value1):* 首先调用key1所在类的hashCode()计算key1哈希值此哈希值经过某种算法计算以后得到在Entry数组中的存放位置。* 如果此位置上的数据为空此时的key1-value1添加成功。 ----情况1* 如果此位置上的数据不为空(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据* 的哈希值* 如果key1的哈希值与已经存在的数据的哈希值都不相同此时key1-value1添加成功。----情况2* 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同继续比较调用key1所在类的equals(key2)方法比较* 如果equals()返回false:此时key1-value1添加成功。----情况3* 如果equals()返回true:使用value1替换value2。** 补充关于情况2和情况3此时key1-value1和原来的数据以链表的方式存储。** 在不断的添加过程中会涉及到扩容问题当超出临界值(且要存放的位置非空)时扩容。默认的扩容方式扩容为原来容量的2倍并将原有的数据复制过来。** jdk8 相较于jdk7在底层实现方面的不同* 1. new HashMap():底层没有创建一个长度为16的数组* 2. jdk 8底层的数组是Node[],而非Entry[]* 3. 首次调用put()方法时底层创建长度为16的数组* 4. jdk7底层结构只有数组链表。jdk8中底层结构数组链表红黑树。* 4.1 形成链表时七上八下jdk7:新的元素指向旧的元素。jdk8旧的元素指向新的元素4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 8 且当前数组的长度 64时此时此索引位置上的所数据改为使用红黑树存储。** DEFAULT_INITIAL_CAPACITY : HashMap的默认容量16* DEFAULT_LOAD_FACTORHashMap的默认加载因子0.75* threshold扩容的临界值容量*填充因子16 * 0.75 12* TREEIFY_THRESHOLDBucket中链表长度大于该默认值转化为红黑树:8* MIN_TREEIFY_CAPACITY桶中的Node被树化时最小的hash表容量:64** 四、HashMap底层源码略详情查看551集* LinkedHashMap的底层实现原理了解* 源码中* static class EntryK,V extends HashMap.NodeK,V {EntryK,V before, after;//能够记录添加的元素的先后顺序Entry(int hash, K key, V value, NodeK,V next) {super(hash, key, value, next);}}*** 五、Map中定义的方法添加、删除、修改操作Object put(Object key,Object value)将指定key-value添加到(或修改)当前map对象中void putAll(Map m):将m中的所有key-value对存放到当前map中Object remove(Object key)移除指定key的key-value对并返回valuevoid clear()清空当前map中的所有数据元素查询的操作Object get(Object key)获取指定key对应的valueboolean containsKey(Object key)是否包含指定的keyboolean containsValue(Object value)是否包含指定的valueint 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()* 注意因为是无序的没有插入功能你无序的往哪插啊。*** author shkstart* create 2019 上午 11:15*/
public class MapTest {/*元视图操作的方法Set keySet()返回所有key构成的Set集合Collection values()返回所有value构成的Collection集合Set entrySet()返回所有key-value对构成的Set集合*//*解释迭代器只适合于Collection集合Map集合没有提供迭代器iterator方法那么该如何遍历迭代器呢????答前面讲到Map由k--v结构构成所有的key放在set集合中 只要拿到所有的key 在通过Set.iterator()便利即可。所有的value放在Collection集合中 只要拿到所有的value 在通过Collection.iterator()便利即可。key--value键值对又构成了一个Entry对象所有的Entry对象放在set集合中 只要拿到所有的Entry对象 在通过Set.iterator()便利即可。以上需要拿到的数据恰好对应着3个方法。*/Testpublic void test5(){Map map new HashMap();//没有学泛型之前可以放任何类型map.put(AA,123);map.put(45,1234);map.put(BB,56);//10.遍历所有的key集keySet()Set set map.keySet();Iterator iterator set.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}System.out.println();//AA BB 45//11.遍历所有的value集values()Collection values map.values();for(Object obj : values){System.out.println(obj);}System.out.println();//123 56 1234//12.遍历所有的key-value//方式一entrySet()Set entrySet map.entrySet();Iterator iterator1 entrySet.iterator();while (iterator1.hasNext()){Object obj iterator1.next();//1.直接输出时查看里面是什么值开发中是拿到里面的值做运算才有意义System.out.println(obj);/*直接输出:AA123BB56451234*///2.分别拿到里面具体的k和v的值/*entrySet集合中的元素都是entry对象,所以需要强转把object类型转为Entry类型学了泛型后不用在强转。*static interface Map.EntryK,V Entry为Map接口里面的内部接口(类似于内部类接口也有内部接口),静态内部接口创建对象方式为外部接口.内部接口*/Map.Entry entry (Map.Entry) obj;/* 此时调用的是Map接口中的内部接口Entry提供的方法 K getKey()和 V getValue()分别获取k和v*/System.out.println(entry.getKey() ---- entry.getValue());/*输出结果AA----123BB----5645----1234 */}System.out.println();/* 方式二方式一对比方式二方式一是拿到整个entry对象在通过这个对象分别调用k和v得值。方式二是拿到所有的key,在遍历所有的key的同时 map集合提供的方法通过key获取value值*/Set keySet map.keySet();Iterator iterator2 keySet.iterator();while(iterator2.hasNext()){Object key iterator2.next();Object value map.get(key);System.out.println(key value);/*输出结果AA123BB56451234 */}}/*元素查询的操作Object get(Object key)获取指定key对应的valueboolean containsKey(Object key)是否包含指定的keyboolean containsValue(Object value)是否包含指定的valueint size()返回map中key-value对的个数boolean isEmpty()判断当前map是否为空boolean equals(Object obj)判断当前map和参数对象obj是否相等*/Testpublic void test4(){Map map new HashMap();map.put(AA,123);map.put(45,123);map.put(BB,56);//5. Object get(Object key)System.out.println(map.get(45));//123,如果获取的key不存在则为Null//6.containsKey(Object key) 也会调用 hashCode和equals方法boolean isExist map.containsKey(BB);System.out.println(isExist);//true//7.size()System.out.println(map.size());//3isExist map.containsValue(123);//一旦找到一个就不会再向下找了System.out.println(isExist);//true//8.isEmptymap.clear();System.out.println(map.isEmpty());//true//9.equals(Object obj) 略}/*添加、删除、修改操作Object put(Object key,Object value)将指定key-value添加到(或修改)当前map对象中void putAll(Map m):将m中的所有key-value对存放到当前map中Object remove(Object key)移除指定key的key-value对并返回valuevoid clear()清空当前map中的所有数据*/Testpublic void test3(){Map map new HashMap();//多态形式//1.添加map.put(AA,123);map.put(45,123);map.put(BB,56);//修改map.put(AA,87);//如果key相同value会变为替换功能System.out.println(map);//{AA87, BB56, 45123}//2.Map map1 new HashMap();map1.put(CC,123);map1.put(DD,123);map.putAll(map1);System.out.println(map);//{AA87, BB56, CC123, DD123, 45123}//3.remove(Object key)Object value map.remove(CC);System.out.println(value);//123System.out.println(map);//{AA87, BB56, DD123, 45123}//4.clear()map.clear();//与map null操作不同,对象还存在 只不过里面没有值System.out.println(map.size());//0System.out.println(map);//{}}Testpublic void test2(){Map map new HashMap();//无序的map new LinkedHashMap();//和数据添加的顺序保持一致map.put(123,AA);map.put(345,BB);map.put(12,CC);System.out.println(map);}//测试Hashtable不能储存null值Testpublic void test1(){Map map new HashMap();
// map new Hashtable();map.put(null,123);}
}
7.2 HashMapMap实现类之一没有参生特有方法
说明
HashMap的键要同时重写hashCode()和equals()hashCode()用来判断确定hash值是否相同equals()用来判断属性的值是否相同 3.1 equals()判断数据如果相等hashCode()必须相同 3.2 equals()判断数据如果不等hashCode()尽量不同
7.2.1 概述
1. 基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作并允许使用 null 值和 null 键。
2. HashMap底层是一个Entry数组当存放数据时会根据hash算法计算数据的存放位置。算法hash(key)%nn就是数组的长度。
3. 当计算的位置没有数据时就直接存放当计算的位置有数据时也就是发生hash冲突的时候/hash碰撞时采用链表的方式来解决的在对应的数组位置存放链表的头结点。对链表而言新加入的节点会从头结点加入。HashMap类的实现则不保证顺序
4. HashMap 的实例有两个参数影响其性能初始容量 和加载因子。容量 是哈希表中桶的数量初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时则要对该哈希表进行 rehash 操作即重建内部数据结构从而哈希表将具有大约两倍的桶数。 将元素适当地分布在各桶之间.
特点 数据无序 底层是一个哈希表/散列表
创建对象
HashMap()
构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空HashMap。7.2.2 存储结构
存储结构 7.2.3 测试1读取HashMap的数据
package seday12;import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;public class Test0_Map {public static void main(String[] args) {HashMap map new HashMap ();map.put(100, 刘德华);map.put(101, 梁朝伟);map.put(102, 古天乐);map.put(103, 周润发);//遍历方式1keySet ()Set set m.keySet();Iterator it set.iterator();while(it.hasNext()) {String key (String) it.next();String val (String) m.get(key);System.out.println(keyval);}//遍历方式2entrySet ()Set set2 m.entrySet();Iterator it2 set2.iterator();while(it2.hasNext()) {Entry en (Entry) it2.next();String key (String) en.getKey();String value (String) en.getValue();System.out.println(keyvalue);}}
}7.2.4 测试2字符串中的字符统计
说明接收用户输入的一串字符串统计出现的每个字符的个数
package cn.tedu.collection;import java.util.HashMap;import java.util.Map;import java.util.Scanner;//测试 HashMappublic class Test5_HashMap2 {public static void main(String[] args) {//1, 获取用户输入的字符串String input new Scanner(System.in).nextLine() ;//声明map,存数据,格式: {a3,b1,c2}MapCharacter,Integer map new HashMap() ;//2,获取到每个字符并统计出现的次数for (int i 0; i input.length() ; i) {char key input.charAt(i) ;//根据下标获取字符--作为key存入map//value呢???Integer value map.get(key) ;//看看value是默认值null呢?还是已经存过数字了呢?if(valuenull){//如果是null,就是以前没存过,没统计过,这是第一次出现map.put(key,1) ;}else{//如果不是null,就是以前存过,在原有数字上1map.put(key,value1) ;}}System.out.println(map);}}7.3 LinkedHashMapHashMap的实现类 7.4 TreeMapMap实现类之二
7.4.1 概述 7.4.2 测试
TreeMapTest
package com.atguigu.java;import org.junit.Test;import java.util.*;/*** author shkstart* create 2019 下午 3:46*/
public class TreeMapTest {//向TreeMap中添加key-value要求key必须是由同一个类创建的对象//因为要按照key进行排序自然排序 、定制排序//自然排序Testpublic void test1(){TreeMap map new TreeMap();User u1 new User(Tom,23);User u2 new User(Jerry,32);User u3 new User(Jack,20);User u4 new User(Rose,18);map.put(u1,98);map.put(u2,89);map.put(u3,76);map.put(u4,100);Set entrySet map.entrySet();Iterator iterator1 entrySet.iterator();while (iterator1.hasNext()){Object obj iterator1.next();Map.Entry entry (Map.Entry) obj;System.out.println(entry.getKey() ---- entry.getValue());/*输出结果User{nameTom, age23}----98User{nameRose, age18}----100User{nameJerry, age32}----89User{nameJack, age20}----76*/}}//定制排序Testpublic void test2(){TreeMap map new TreeMap(new Comparator() {Overridepublic int compare(Object o1, Object o2) {if(o1 instanceof User o2 instanceof User){User u1 (User)o1;User u2 (User)o2;//按照年龄进行排序return Integer.compare(u1.getAge(),u2.getAge());}throw new RuntimeException(输入的类型不匹配);}});User u1 new User(Tom,23);User u2 new User(Jerry,32);User u3 new User(Jack,20);User u4 new User(Rose,18);map.put(u1,98);map.put(u2,89);map.put(u3,76);map.put(u4,100);Set entrySet map.entrySet();Iterator iterator1 entrySet.iterator();while (iterator1.hasNext()){Object obj iterator1.next();Map.Entry entry (Map.Entry) obj;System.out.println(entry.getKey() ---- entry.getValue());/* 输出结果User{nameRose, age18}----100User{nameJack, age20}----76User{nameTom, age23}----98User{nameJerry, age32}----89*/}}}
User
package com.atguigu.java;/*** author shkstart* create 2019 下午 3:56*/
public class User implements Comparable{private String name;private int age;public User() {}public User(String name, int age) {this.name name;this.age age;}public String getName() {return name;}public void setName(String name) {this.name name;}public int getAge() {return age;}public void setAge(int age) {this.age age;}Overridepublic String toString() {return User{ name name \ , age age };}Overridepublic boolean equals(Object o) {System.out.println(User equals()....);if (this o) return true;if (o null || getClass() ! o.getClass()) return false;User user (User) o;if (age ! user.age) return false;return name ! null ? name.equals(user.name) : user.name null;}Overridepublic int hashCode() { //return name.hashCode() age;int result name ! null ? name.hashCode() : 0;result 31 * result age;return result;}//自然排序按照姓名从大到小排列,年龄从小到大排列Overridepublic int compareTo(Object o) {if(o instanceof User){User user (User)o;
// return -this.name.compareTo(user.name);int compare -this.name.compareTo(user.name);if(compare ! 0){return compare;}else{return Integer.compare(this.age,user.age);}}else{throw new RuntimeException(输入的类型不匹配);}}
}
7.5 HashtableMap实现类之三
说明太古老了不用只关心它的子类。
注意他这个t是小写太古老了 连命名规范都没有遵循。 7.6 PropertiesHashtable的实现类 配置文件创造方式一不需要指定后缀。 配置文件创造方式二以new File方式需要指定后缀。 7.6.1 测试 PropertiesTest:
package com.atguigu.java;import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;/*** author shkstart* create 2019 下午 4:07*/
public class PropertiesTest {//Properties:常用来处理配置文件。key和value都是String类型public static void main(String[] args) {FileInputStream fis null;try {Properties pros new Properties();fis new FileInputStream(jdbc1.properties);pros.load(fis);//加载流对应的文件String name pros.getProperty(name);String password pros.getProperty(password);//name tomå¸, password abc123 如果配置文件有中文在设置编码时没有打钩那么输出的中文是乱码//打钩后输出需要先把原来生的properties文件删除重新写一个properties文件在输出。 name tom帅, password abc123System.out.println(name name , password password);} catch (IOException e) {e.printStackTrace();} finally {if(fis ! null){try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}
}
PropertiesTest配置文件
nametom帅
passwordabc123
#注意不要写空格如name tom它会认为你的值是 空格tom8 Collections工具类
注意ArrayList和HashMap都是线程不安全的如果程序要求线程安全我们可以将ArrayList和HashMap转换为线程安全的。使用synchronizedList(List list)和synchronizedMap(MapK,V m)
8.1 概念 8.2 常用方法测试
8.2.1 测试1达内
package cn.tedu.collection;import java.util.ArrayList;import java.util.Collections;import java.util.List;//测试 集合工具类Collectionspublic class Test6_Collections {public static void main(String[] args) {//1,调用Collections常用方法ListInteger list new ArrayList();// list.add(1);//不用工具类,只能一次一次的加// list.add(2);// list.add(3);Collections.addAll(list,1,2,3,4,5) ;//向指定集合list里添加很多元素System.out.println(list);//[1, 2, 3, 4, 5]System.out.println( Collections.max(list) ); //获取集合里的最大值5System.out.println( Collections.min(list) ); //获取集合里的最小值1Collections.reverse(list);//翻转指定集合里的所有元素System.out.println(list);//[5, 4, 3, 2, 1]Collections.swap(list,2,3);//把集合中,指定的两个下标对应的元素交换位置System.out.println(list);//[5, 4, 2, 3, 1]Collections.sort(list);//给list里的数据排序System.out.println(list);//[1, 2, 3, 4, 5]ListInteger list2 new ArrayList();//解决方法把集合list2的长度变为大于等于list.Collections.addAll(list2,…elements:55,88,74,55,88,888);Collections.copy(list2,list);//TODO 把集合list中的内容复制到List2中但此时list2集合的长度为0下标越界异常。System.out.println(list2);}}8.2.2 测试2尚硅谷
package com.atguigu.java;import org.junit.Test;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;/*** Collections:操作Collection、Map的工具类*** 面试题Collection 和 Collections的区别*** author shkstart* create 2019 下午 4:19*/
public class CollectionsTest {/*1.reverse(List)反转 List 中元素的顺序。注意不适用set无序的反转也没啥意义。2.shuffle(List)对 List 集合元素进行随机排序3.sort(List)根据元素的自然顺序对指定 List 集合元素按升序排序4.sort(ListComparator)根据指定的 Comparator 产生的顺序对 List 集合元素进行排序5.swap(Listint int)将指定 list 集合中的 i 处元素和 j 处元素进行交换6.Object max(Collection)根据元素的自然顺序返回给定集合中的最大元素7.Object max(CollectionComparator)根据 Comparator 指定的顺序返回给定集合中的最大元素8.Object min(Collection)9.Object min(CollectionComparator)10.int frequency(CollectionObject)返回指定集合中指定元素的出现次数11.void copy(List dest,List src)将src中的内容复制到dest中12.boolean replaceAll(List listObject oldValObject newVal)使用新值替换 List 对象的所有旧值*/Testpublic void test1(){List list new ArrayList();list.add(123);list.add(43);list.add(765);list.add(765);list.add(765);list.add(-97);list.add(0);System.out.println(list);// Collections.reverse(list);1.反转
// Collections.shuffle(list);2.真正的随机打乱输出不是无序输出。
// Collections.sort(list); 3.自然排序默认调用 因为存的是integer的包装类 默认使用的里面的comPareTo方法 从小到大排序。
// Collections.swap(list,1,2); 5.交换2个索引处的元素。int frequency Collections.frequency(list, 123);System.out.println(list);System.out.println(frequency);//10.}Testpublic void test2(){List list new ArrayList();list.add(123);list.add(43);list.add(765);list.add(-97);list.add(0);//11. 错误写法/* 报异常IndexOutOfBoundsException(Source does not fit in dest)List dest new ArrayList();Collections.copy(dest,list); 把list原集合 的数据复制到 新集合dest中。源码中要求原集合.size小于新集合.size 即元素的个数少于新集合这个新集合新创建没有元素 size为0原集合的size当然大于新集合的size,所以添加失败。*///正确的把数组转化为list集合这个数组的长度为原集合的长度每个值都为null进行占位。List dest Arrays.asList(new Object[list.size()]);System.out.println(dest.size());//list.size();5, [null,null,null,null,null]Collections.copy(dest,list);//此时在进行复制就可以了。System.out.println(dest);//[123, 43, 765, -97, 0]/*Collections 类中提供了多个 synchronizedXxx() 方法该方法可使将指定集合包装成线程同步的集合从而可以解决多线程并发访问集合时的线程安全问题。包括collection和Map*///返回的list1即为线程安全的List把原来线程不安全的集合转化为线程安全的。List list1 Collections.synchronizedList(list);}}