2、set集合
2.1概述
不包含重复集合的集合。
没有带索引的方法,所以不能使用普通for循环遍历。
Set<String> set = new HashSet<String>(); set.add("huhfn"); set.add("my"); set.add("dear"); set.add("my"); for(String s:set){ System.out.println(s); } out: huhfn my dear
2.2哈希值
哈希值:时jdk根据对象的地址或者字符串或者数字算出来的int类型。
hashCode():返回对象的哈希值。(object类中)。
Student s1 = new Student("z",5); System.out.println(s1.hashCode());//1324119927 Student s2 = new Student("z",5); System.out.println(s2.hashCode());//990368553
同一个对象的哈希值时相同的。
不同对象的哈希值再默认情况下时不同的。
通过方法重写可以实现哈希值时相同的。
2.3 hashset概述
java.util包下。实现了set接口,对集合的顺序不做保证。底层数据结构时哈希表。没有带索引的方法,不包含重复元素的集合。
public static void main(String[] args) { HashSet<String> hs = new HashSet<String>(); hs.add("hello"); hs.add("world"); hs.add("java"); for(String s:hs){ System.out.println(s); } /* world java hello */ } }
2.4 为啥不重复呢?
Alt + 7打开structure窗口。
public boolean add(E e) { return map.put(e, PRESENT)==null; } static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
&&短路,前面如果false则后面都不会运行。
要保证元素唯一性,需要重写hashcode和equis那个。
2.5哈希表
hashset()构建一个新集合,默认初始容量为16.0-15
hello:99162322%16 =2. 首先将hello放到2的位置。
world:113318802%16 = 2 。 与hello同一个位置,与hello比较首先比较哈希值,world存储进来了。
java:3254818%16 =2 .也在2,同以前的比较哈希值不同,java存储进来了。
world:113318802%16 = 2 .与第一个比较哈希值不同,比较第二个world发现与第二个哈希值相同。哈希值相同情况下比较内容,内容相同,说明元素重复,不存储。
2.6实例,重写hashset和equals方法就保证了唯一性
创建一个学生对象,存储多个学生对象,使用程序实现控制台遍历改集合。
HashSet<Student> hs = new HashSet<Student>(); Student s1 = new Student("z",1); Student s2 = new Student("a",2); Student s3 = new Student("b",3); Student s4 = new Student("b",3); hs.add(s1); hs.add(s2); hs.add(s3); hs.add(s4); for(Student s :hs){ System.out.println(s.getAge()+","+s.getName()); } 当在student中没有重写hashset和equals方法时: 输出: 2,a 3,b 1,z 3,b 重写之后:直接 2,a 3,b 1,z
重写方法:制动生成就行。alt+insert,选择这两方法,直接next就行。
2.7 linkedhashset
这个可以保证元素的存储和取出顺序是一致的。
哈希表和链表实现的是set接口,具有可预测的迭代次序。
由哈希表保证元素的唯一,也就是说没有重复的元素。
联系:存储字符串并遍历
LinkedHashSet<String> ls = new LinkedHashSet<String>(); ls.add("sd"); ls.add("hj"); ls.add("df"); ls.add("df"); for (String s : ls){ System.out.println(s); } 输出: sd hj df
2.7 treeset集合的概述和特点
间接实现了set接口。
元素有序。(按照一定的规则进行排序)
可以实现自然排序,或者comparator可以根据比较器排序。
用哪一种排序,根据构造方法。无参构造根据自然排序进行排序。带参构造:TreeSet(Comparator)根据指定的比较器进行排序。
没有带索引的方法。不能使用普通的for循环遍历。
练习:存储整数并遍历。
public static void main(String[] args) { TreeSet<Integer> ts = new TreeSet<Integer>(); ts.add(1); ts.add(20); ts.add(90); ts.add(5); for (Integer i :ts){ System.out.println(i); } } 输出: 1 5 20 90 //自动排序
TreeSet<Integer> ts = new TreeSet<Integer>();
!!!!注意!!!《里面》需要写的是其包装类型,比如int不要直接写int,要写Integer。
2.8 自然排序Comparable的使用
存储学生对象并遍历,使用无参构造。
要求:按照年龄从小到大。年龄相同时,按照姓名的字母顺序排序。
!!!报错了!!!!
public static void main(String[] args) { TreeSet<Student> ts = new TreeSet<Student>(); Student s1 = new Student("z",1); Student s2 = new Student("a",2); Student s3 = new Student("b",3); ts.add(s1); ts.add(s2); ts.add(s3); for (Student s:ts){ System.out.println(s.getAge()+","+s.getName()); }// java.lang.ClassCastException: class com.zy2.Student cannot be cast to class java.lang.Comparable }
Student要实现接口Comparable《Student》,该程序认为大的应该放后面。根据返回值认为,返回1认为下一个比前一个大。-1认为下一个比前一个小,现在的放前面。
需要重写compareTo....。。因为返回的是0,所以它会认为s2和s1是一样的。
@Override public int compareTo(Student o) { return 0; } 如果这么写,只能存储一个元素。 输出: 1,z
!!!!!!!return 1.就是按照存储的顺序来输出的。因为返回的是正数,所以它认为s2比s1大。
@Override public int compareTo(Student o) { return 1; } } 1,z 2,a 3,b
负数,s2比s1小认为,
@Override public int compareTo(Student o) { return -1; } } 3,b 2,a 1,z
下面这个this代表s2,s代表s1。如果正数,s2》s1,正数往后放,负数往前放。
第一个输入的7,第二个2,return 负数,往前放,变成了2-7.
第三个输入3,3-2=正,所以往后放。
Student s1 = new Student("z",7); Student s2 = new Student("a",2); Student s3 = new Student("b",3); @Override public int compareTo(Student s) { int num = this.age-s.age; return num; } 2,a 3,b 7,z
String本身就继承Compara接口。
这是最终实现的代码。
Student s1 = new Student("z",7); Student s2 = new Student("a",2); Student s3 = new Student("b",3); Student s4 = new Student("h",3); Student s5 = new Student("h",3); ts.add(s1); ts.add(s2); ts.add(s3); ts.add(s4); ts.add(s5); @Override public int compareTo(Student s) { int num = this.age-s.age; int n = num==0?this.name.compareTo(s.name):num; return n; }
总结:
无参构造使用的是自然排序的方法对元素惊醒重构的。
自然排序就是让元素所属的类实现Comparable接口,重写comparaTo(E e)方法
重写方法的时候,看要求。
2.9 Comparator比较器排序
联系要求跟上面一样。
存储学生对象并遍历,使用无参构造。
要求:按照年龄从小到大。年龄相同时,按照姓名的字母顺序排序。
public int compare(Student s1, Student s2)里面的前面那个s1是当前输入的值,s2是之前输入的数值
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {//匿名啥啥类来着 @Override public int compare(Student s1, Student s2) { int num = s1.getAge()- s2.getAge(); int n = num==0?s1.getName().compareTo(s2.getName()):num; return n; } }); Student s1 = new Student("z",86); Student s2 = new Student("a",34); Student s3 = new Student("b",99); Student s4 = new Student("h",3); Student s5 = new Student("h",3); ts.add(s1); ts.add(s2); ts.add(s3); ts.add(s4); ts.add(s5); for (Student s:ts){ System.out.println(s.getAge()+","+s.getName());
成绩排序练习
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { int s1 = o1.getChinese()+ o1.getMath(); int s2 = o2.getChinese()+ o2.getMath(); int num = s2-s1; int n = num==0? o1.getChinese() - o2.getChinese():num; int n2 = n==0?o1.getName().compareTo(o2.getName()):n; return n2; } }); Student s1 = new Student("1",20,20); Student s2 = new Student("2",10,20); Student s3 = new Student("3",80,20); Student s4 = new Student("4y",5,20); Student s5 = new Student("5y",4,21); Student s6 = new Student("adf",80,20); ts.add(s1); ts.add(s2); ts.add(s3); ts.add(s4); ts.add(s5); ts.add(s6); for (Student s :ts){ System.out.println(s.getChinese()+","+s.getMath()+","+s.getName()); }
80,20,3 80,20,adf 20,20,1 10,20,2 4,21,5y 5,20,4y
2.10 不重复的随机数
练习:编写一个程序,获取1-20之间的随机数,要求随机数不能重复,并在控制台输出。
1、创建set集合对象
2、创建随机数对象
3、判断集合长度是不是小于10
是:产生一个随机数。
回到3继续。
4、遍历集合。
用hashset方法顺序随意。Treeset是输出是有顺序的。从小到大。
public class demo1 { public static void main(String[] args) { // Set<Integer> set = new HashSet<Integer>(); Set<Integer> set = new TreeSet<Integer>(); Random r = new Random(); while (set.size() < 10) { int n = r.nextInt(20) + 1; set.add(n); } for (Integer i : set) { System.out.println(i); } } }
3、泛型
3.1概述
泛型:是JDK5中引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译的时候检测到非法的类型。
他的本质是参数化类型,也就是所操作的数据类型被指定为一个参数
顾名思义,就是将类型由原来的具体的类型参数化,然后在使用/调用时传入具体的类型。
这种参数类型可以i用在类、方法和接口中。分别称为泛型类,泛型方法、泛型接口
定义格式:
《类型》:指定一种类型的格式,这里的类型可以堪称是形参
《类型1,类型2.。。》:指定多种类型,多种类型之间用逗号隔开,这里的类型可以堪称是形参
将来具体调用的时候给定的类型可以看成时实参,并实参的类型时引用数据类型。
Collection<,,,,,,> c = new ......这里面的<>的里面就是泛型。
Collection c = new ArrayList();//没有给泛型 c.add("dcdv"); c.add("bhb"); c.add("kj"); c.add(10);//java.lang.ClassCastException: for (Object s:c){ System.out.println((String)s); }
好处:
把问题给提前了,在编译的时候就一定要改。把运行时候的问题提前到了编译的时候。 不需要加强制类型转换了。
3.2泛型类
定义格式
修饰符class 类名<类型>{ }
public class Gen<T>{}
类型一般用T,E,K,V表示泛型。
Gen<String> g1 =new Gen<String>(); g1.setT("mhfgbhd"); System.out.println(g1.getT()); Gen<Integer> i = new Gen<Integer>(); i.setT(52); System.out.println(i.getT());
mhfgbhd 52
public class Gen<T> {//泛型类 private T t; public T getT() { return t; } public void setT(T t) { this.t = t; } }
3.3 泛型方法
格式:
修饰符<类型> 返回值
泛型类改进 public class fangfa<T> { public void show(T t){ System.out.println(t); } } 泛型方法改进 public class fangfa { public <T> void show(T t){ System.out.println(t); } }
fangfa<String> s = new fangfa<String>(); s.show("djfbh"); fangfa<Integer> i = new fangfa<Integer>(); i.show(52); fangfa<Boolean> b = new fangfa<Boolean>(); b.show(true); 泛型方法改进 fangfa s = new fangfa(); s.show("djfbh"); s.show(52); s.show(true);
djfbh 52 true
3.4 泛型接口
格式:
修饰符 interface 接口名<类型>{ }
jiekou<String> i = new jiekou<String>();//psvm i.show("df"); jiekou<Integer> g = new jiekou<Integer>(); g.show(52);
public class jiekou<T> implements Geni<T>{//接口实现类 @Override public void show(T t) { System.out.println(t); } }
public interface Geni<T> {//接口 void show(T t); }
3.5 类型通配符
为了表示各种泛型list的父类,可以使用类型通配符
类型通配符:<?>
List<?>:表示元素类型位置的List,他的元素可以匹配任何类型
这种带通配符的List仅仅表示它是任何泛型的父类,并不能把元素添加到其中。
如果不希望List<?>是任何泛型的父类,只希望它代表某一类泛型List的父类,可以使用类型通配符的上限。
类型通配符的上限:<?extends 类型>
List<?extends Number>:他表示的类型是Number或者其子类
类型通配符的下限:<?super 类型>
List<?super Number>:它表示的类型是Number或者其父类
List<?> l1 = new ArrayList<String>(); List<?> l2 = new ArrayList<Integer>(); // List<? extends Number> l3 = new ArrayList<Object>();//错误 List<? extends Number> l3 = new ArrayList<Number>(); List<? extends Number> l4 = new ArrayList<Integer>(); List<? super Number> l5 = new ArrayList<Number>(); List<? super Number> l6 = new ArrayList<Integer>();//错误
3.6 可变参数
格式:
修饰符 返回值类型 方法名(数据类型... 变量名){}
public static void main(String[] args) { System.out.println(sum(10, 20,20)); } public static int sum(int... a) {//a把方法调用中封装的参数封装到数组中 int sum = 0; for (int i : a) { sum += i; } return sum; } 50
public static void main(String[] args) { System.out.println(sum(10, 20,20)); } public static int sum(int b,int... a) { int sum = 0; for (int i : a) { sum += i; } return sum; } 40
使用:
Arrays工具类中有一个静态方法:
public static <T> List <T> asList(T... a):返回由指定数组支持的固定大小的列表。
返回的集合不能做增删操作,可以做修改操作。
List接口中有一个静态方法:
public static《E》 List《E》 of(E... elements):返回包含任意数量元素的不可变列表。
返回的集合不能做增删改操作
set接口中有一个静态方法:
public static<E>Set<E> of (E...elements):返回一个包含任意数量元素的不可变集合。
给元素不能给重复元素,返回的集合不能做增删,没有修改方法因为没有索引。
public static void main(String[] args) { List<String> l = Arrays.asList("hjdf","dkj","ooo");//返回由指定数组支持的固定大小的列表。 // l.add("pppppp");//错误。因为返回的是一个固定大小的列表。 // l.remove("df");//错误UnsupportedOperationException l.set(1,"pppppppppp");//可以 System.out.println(l); List<String> df = List.of("df", "hhh", "ooooo", "yyy","yyy");//不可变列表。 // df.add("sssss");//错误 // df.remove("yyy");//错误 // df.set(1,"jjjj");//错误UnsupportedOperationException System.out.println(df); // Set<String> s = Set.of("qqqq", "uuuuuu", "ppppp","ppppp");//错误,IllegalArgumentException非法或者不正确的参数 //因为set集合不能有重复元素 Set<String> s = Set.of("qqqq", "uuuuuu", "ppppp"); s.add("kk");//错误 s.remove("ppppp");//错误 System.out.println(s); }