集合体系结构

1.Collection

1.1、collection集合概述和使用

collection集合概述

Collection是单列集合的顶层接口,他表示一组对象,这些对象也称为Collection的元素

JDK不是提供此接口的任何直接实现,它提供更具体的子接口(Set和List)实现

创建Collection集合的对象

多态的方式

具体的实现类ArrayList

package com.itheima_01;import java.util.ArrayList;
import java.util.Collection;public class CollectionDemo {public static void main(String[] args) {
//      创建一个对象Collection c=new ArrayList();
//        添加元素boolean add(E e)c.add("hello");c.add("world");c.add("java");
//输出的内容是[hello, world, java]   说明ArrayList重写了toString()System.out.println(c);}
}

1.2、collection集合常用方法

方法名 说明
boolean add(E e) 添加元素
boolean remove(Object o) 从集合中移除元素
void clear() 清空集合中的元素
boolean contains(Object o) 判断集合中是否存在指定的元素
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中元素的个数
package com.itheima_02;import java.util.ArrayList;
import java.util.Collection;/*
boolean add(E e)添加元素
boolean remove(int idex,E e)从集合中移除元素
void clear()清空集合中的元素
boolean contains(Object o) 判断集合中是否存在指定的元素
boolean isEmpty()判断集合是否为空
int size()集合的长度,也就是集合中元素的个数
*
*
*
*
*
* */
public class CollectionDmeo {public static void main(String[] args) {
//       先创建一个对象Collection c=new ArrayList();
//        boolean add(E e)添加元素c.add("I");c.add("Love");c.add("My");c.add("life");System.out.println(c);
//        boolean remove(Object o)从集合中移除元素c.remove("My");System.out.println(c);
//        void clear()清空集合中的元素
//        c.clear();
//        System.out.println(c);
//        boolean contains(Object o) 判断集合中是否存在指定的元素boolean flag=c.contains("my");System.out.println(flag);
//        boolean isEmpty()判断集合是否为空boolean flag2=c.isEmpty();System.out.println(flag2);
//        int size()集合的长度,也就是集合中元素的个数System.out.println("该集合的长度为:"+c.size());}
}

1.3、collection集合遍历

Iterator:迭代器,集合的专用遍历方式      Iterator是一个(抽象)接口

Iterator<E>iterator()  返回此集合中元素的迭代器,通过集合的iterator()方法得到

迭代器是通过集合的Iterator()方法得到的,所以我们说他是依赖于集合而存在的

Iterator中常用的方法

E next() 返回迭代中的写一个元素

boolean hasNext()  如果迭代具有更多元素,则返回true

package com.itheima_03;import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;/*
Iterator:迭代器,集合的专用遍历方式Iterator<E>iterator() 返回此集合中元素的迭代器,通过集合的iterator()方法得到迭代器是通过集合的Iterator()方法得到的,所以我们说他是依赖于集合而存在的
Iterator中常用的方法E next() 返回迭代中的写一个元素boolean hasNext() 如果迭代具有更多元素,则返回true
* */
public class IteratorDemo {public static void main(String[] args) {Collection<String> c=new ArrayList<String>();
//        boolean add(E e)添加元素c.add("I");c.add("Love");c.add("My");c.add("life");
//Iterator<E>iterator() 返回此集合中元素的迭代器,通过集合的iterator()方法得到Iterator<String> it=c.iterator();
//        boolean hasNext()  判断集合中有没有元素,有的就为truewhile(it.hasNext()){
// E next() 返回迭代中的写一个元素String s=it.next();System.out.println(s);}}
}

1.4、集合使用步骤图解

1.5、collection集合存储学生对象并遍历

案例:collection集合存储学生对象并遍历

需求:创建一个存储学生对象的集合,存储3个学生对象,使用程序实现在控制台遍历该集合

分析,

先创建一个学生类,包括姓名、年龄

创建测试类,在测试类中导入学生信息

将导入的信息存储在Collection集合<String>中

使用Iterator<E> iterator()   遍历学生对象

package com.itheima_04;import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;public class StudentTest {public static void main(String[] args) {Collection<Student> c=new ArrayList<Student>();Student s1=new Student();s1.setName("张三丰");s1.setAge(120);Student s2=new Student("林青霞",30);Student s3=new Student("王祖贤",30);Student s4=new Student("刘亦菲",20);c.add(s1);c.add(s2);c.add(s3);c.add(s4);Iterator<Student> it=c.iterator();while(it.hasNext()){Student s=it.next();System.out.println(s.getName()+","+s.getAge());}}
}

2.List

2.1、List集合概述和特点

List集合概述

有序集合,用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引访问元素,并搜索列表中元素。

与set集合不同,列表通常允许重复的元素

List集合特点

有序、可重复

2.2、List集合概特有方法

方法名                 说明
void add(int index,E element) 在此集合指定索引添加元素
E remove (int index) 删除指定索引处元素,返回被删除元素
E set(int index,E element) 修改指定索引处的元素并返回被修改元素
E get(int index) 返回指定索引的元素
package com.itheima_01;import java.util.ArrayList;
import java.util.List;/*
void add(int index,E element)   在此集合指定索引添加元素
E remove (int index)    删除指定索引处元素,返回被删除元素
E set(int index,E element)  修改指定索引处的元素并返回被修改元素
E get(int index)    返回指定索引的元素
*/
public class LIstDemo {public static void main(String[] args) {
//        创建一个List集合List<String> list=new ArrayList<String>();
//        void add(int index,E element)   在此集合指定索引添加元素list.add("hello");list.add("world");list.add("java");
//        E remove (int index)    删除指定索引处元素,返回被删除元素
//        list.remove("world");//        System.out.println(list.remove(1));
//        E set(int index,E element)  修改指定索引处的元素并返回被修改元素
//        list.set(2,"javaweb");System.out.println(list.set(2,"javaweb"));
//        E get(int index)    返回指定索引的元素}
}

2.2.1、案例:List集合存储学生对象并遍历

package com.itheima_02;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class StudentDemo {public static void main(String[] args) {
//        传参Student s1=new Student("科比",38);Student s2=new Student("乔丹",50);Student s3=new Student("姚明",41);
//        将学生信息存储到List集合中List<Student> l=new ArrayList<Student>();
//        添加学生信息l.add(s1);l.add(s2);l.add(s3);System.out.println("遍历方式一:使用迭代器");Iterator<Student> it=l.iterator();while(it.hasNext()){Student s= it.next();System.out.println(s.getName()+","+s.getAge());}System.out.println("遍历方式二:用List集合自带的get()方法遍历");for (int i=0;i<l.size();i++){Student s=l.get(i);System.out.println(s.getName()+","+s.getAge());}}
}

2.3、并发修改异常

List集合是一个单独的参数,Iterator迭代器也是一个单独的参数,迭代器时依赖集合存在的,Iterator迭代器参数具体内容是通过List的方法,就跟赋值粘贴一样,你原来的内容都通过add增加了内容,所以会报错,所以说它是判断你生成迭代器后,原来的List集合的内容有没有被修改过,如果被修改就需要重新生成一次迭代器。

使用迭代器需要保证List集合的内容不再发生改变,集合内容发生改变,迭代器又没有重新使用List集合iterator()方法去更新内容,然后直接调用迭代器的next()方法就会出现异常(并发修改异常),如果使用hasNext()方法是不会出现异常,因为它没有加检测判断。

使用迭代器时不能修改元素,如果想修改
        方法①需要再add()后面加上break
        方法②或者用for循环get()遍历并修改

方法③将迭代器放在try...catch中

package com.itheima_03;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/*
*
* ConcurrentModificationException
* */
public class ListDemo {public static void main(String[] args) {List<String> list=new ArrayList<String>();list.add("hello");list.add("world");list.add("java");Iterator<String> it=list.iterator();/* while (it.hasNext()){String s= it.next();if (s.equals("world")){list.add("javaee");
//                使用迭代器时不能修改元素,如果想修改需要再add()后面加上break;,或者用for循环get()遍历并修改
//                break;}}*/for (int i=0;i< list.size();i++){String s= list.get(i);System.out.println(s);}}
}

2.4、ListIterator

ListIterator:列表迭代器

通过List集合的listIterator()方法得到,所以说它是List集合特有的迭代器

用于允许程序员沿着任意方向遍历列表,在迭代期间修改列表,并获取列表中迭代器的当前位置

ListIterator中常用的方法

E next()      返回迭代中下一个元素
boolean hasNext()   如果迭代具有更多元素,则返回true
E previous()    返回列表中的上一个元素
boolean hasPrevious()   如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回true
void add(E e)   将指定元素添加进列表

2.5、增强for循环

2.5.1、案例:List集合存储学生对象用三种方式遍历

package com.itheima_06;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;public class StudentDemo {public static void main(String[] args) {Student s1=new Student("Kobe",41);Student s2=new Student("kari",31);Student s3=new Student("Jams",37);
//        定义一个集合用来存储学生信息List<Student> list=new ArrayList<Student>();list.add(s1);list.add(s2);list.add(s3);Iterator<Student> it= list.listIterator();while (it.hasNext()){Student s=it.next();System.out.println(s.getName()+","+s.getAge());}for(int i=0;i< list.size();i++){Student ss=list.get(i);System.out.println(ss.getName()+","+ss.getAge());}System.out.println("增强for循环");ListIterator<Student> lit=list.listIterator();for(Student s:list){System.out.println(s.getName()+","+s.getAge());}}
}

2.6、数据结构

数据结构时计算机存储、组织数据的方式。是指相互之间存在一种或者多种特定关系的数据元素的集合

数据结构的意义:通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率

2.7、常见数据结构之栈

 先进后出:呕吐

2.8、常见数据结构之队列

队列:拉屎

先进先出

2.9、常见数据结构之数组

2.10、常见数据结构之链表

2.11、List集合子类特点

List集合常用的子类:

  • ArrayList        (数组型)查快增删慢
  • LinkedList        (链表型)查慢增删快

他们的功能List都有可以直接用

package com.itheima_07;import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;public class ListDemo {public static void main(String[] args) {ArrayList<String> array=new ArrayList<String>();array.add("科比");array.add("打球");array.add("很帅");for (String s:array){System.out.println(s);}System.out.println("使用链表型LinkedList");LinkedList<String> linkedList=new LinkedList<String>();linkedList.add("科比");linkedList.add("打球");linkedList.add("很帅");Iterator<String> it= linkedList.listIterator();while (it.hasNext()){String s1= it.next();System.out.println(s1);}}
}

2.11.1、案例:ArrayList集合存储学生对象用三种方式遍历

2.12、LinkedList集合的特有功能

void addFirst(E e)      在该链表开头添加指定元素
void addLast(E e)       在该链表末尾添加指定元素
public E getFirst()     获取该链表开头元素
public E getLast()      获取该链表末尾元素
public E removeFirst()  删除该链表开头元素,并返回被删除的元素
public E removeLast()   删除该链表末尾元素,并返回被删除的元素

3.Set   (Set、HashSet无序(linkedSet有序、TreeSet可排序2)、元素不重复)

set集合继承Collection的方法

方法名 说明
boolean add(E e) 添加元素
boolean remove(Object o) 从集合中移除元素
void clear() 清空集合中的元素
boolean contains(Object o) 判断集合中是否存在指定的元素
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中元素的个数

Set集合的特点

  • 不包括重复元素的集合
  • 没有带索引的方法,所以不能使用普通for循环遍历(用增强for循环)
package com.itheima_01;
/*
set集合的特点:不包括重复元素的集合没有带索引的方法,所以不能使用普通for循环遍历(用增强for循环)
HashSet对集合的顺序不做任何保证
set可以使用Collection的方法
boolean add(E e)                添加元素
boolean remove(Object 0)        移出集合中指定元素
void clear()                    清空元素
boolean contains(Object 0)      判断集合中是否存在指定元素
boolean isEmpty()               判断集合是否为空
int size()                      集合长度
*/import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;public class SetDemo {public static void main(String[] args) {
//        创建集合类,Set集合是一个接口,不能直接实现需要多态的方法实例化才行Set<String> set=new HashSet<>();set.add("hello");set.add("world");set.add("java");
//        不包括重复元素的集合set.add("world");
//两种遍历方式Iterator<String> it=set.iterator();while(it.hasNext()){String s=it.next();System.out.println(s);}for (String s:set){System.out.println(s);}}
}

3.2、哈希值

哈希值:是JDK根据对象的地址字符串数字算出来的int类型数值

Object类中有一个方法可以获得对象的哈希值

public int hashCode()返回对象的哈希码值

默认情况下:同一个对象,返回的哈希值是相同的,不同对象的哈希值不同

通过重写public int hashCode() 修改返回值可以实现哈希值相同

package com.itheima_09;import com.itheima_06.Student;public class HashDemo {public static void main(String[] args) {Student s1=new Student("科比",41);Student s2=new Student("乔丹",51);Student s4=new Student("乔丹",51);Student s3=new Student("詹姆斯",37);System.out.println(s1.hashCode());//1324119927System.out.println(s1.hashCode());//1324119927
//        不同对象、不同字符串哈希值不同System.out.println(s2.hashCode());//990368553System.out.println(s4.hashCode());//1096979270
//        当我们再Student类中去重写Hash()方法时,修改默认值,不同的对象哈希值就可以一样了/*@Overridepublic int hashCode() {return 100;}*/System.out.println("-----当我们使用直接输入字符串时------");System.out.println("hello".hashCode());//99162322System.out.println("world".hashCode());//113318802System.out.println("world".hashCode());//113318802System.out.println("-----------");System.out.println("狗日的".hashCode());//29091414System.out.println("他妈的".hashCode());//20135762System.out.println("重地".hashCode());//1179395System.out.println("通话".hashCode());//1179395}
}

3.4、HashSet集合特点

需要导包、序、元素不重复

//创建HashSet集合对象
HashSet<String> hs=new HashSet<String>();hs.add("hello");hs.add("world");hs.add("java");
//调用add方法  将字符串hello传过去
public boolean add(E e) {return map.put(e, PRESENT)==null;
}
//hash值是根据元素的.hashCode()计算得到的
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);
}//hash值是根据元素的.hashCode()计算得到的
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {// Node<K,V>[] 就是一个元素为节点的数组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;/*存入的元素和以前的元素比较哈希值如果哈希值不同 p.hash == hash,会继续向下执行,把元素添加到集合如果哈希值相同,会调用对象的equals()比较(k = p.key) == key || (key != null && key.equals(k)))如果返回true,说明元素重复,不存储(k = p.key) == key || (key != null && key.equals(k)))如果返回false,会继续向下执行,把元素添加到集合*/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 1sttreeifyBin(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 keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;
}

3.5常见数据结构之哈希表

 案例:HashSet集合存储学生对象并遍历

package com.itheima_03;import java.util.HashSet;public class HashSetDemo {public static void main(String[] args) {HashSet<Student> hs=new HashSet<Student>();Student s1=new Student("科比",41);Student s2=new Student("乔丹",51);Student s3=new Student("库里",36);Student s4=new Student("库里",36);hs.add(s1);hs.add(s2);hs.add(s3);
//        为了保证HashSet中集合元素的唯一性,我们需要重写 hashCode() 和equals()hs.add(s4);for (Student s:hs){System.out.println(s.getName()+","+s.getAge());}}
}

3.6、LinkedHashSet集合概述和特点

需要导包、有序、元素不重复

  • LinkedHashSet集合是哈希表和链表实现的Set接口,具有可预测的迭代次序,
  • 该实现与HashSet不同之处在于它保持双向链接列表的所有条目。
  • 由链表保证元素有序,也就是说元素的 存储和取出顺序是一致的
  • 由哈希表保证元素唯一,也就是说没有重复元素
package com.itheima_04;import com.itheima_03.Student;import java.util.LinkedHashSet;public class LinkedHashSetDemo {public static void main(String[] args){/*LinkedHashSet<Student> lhs=new LinkedHashSet<Student>();Student s1=new Student("科比",41);Student s2=new Student("乔丹",51);Student s3=new Student("库里",36);Student s4=new Student("库里",36);lhs.add(s1);lhs.add(s2);lhs.add(s3);lhs.add(s4);for (Student s:lhs){System.out.println(s.getName()+","+s.getAge());}*/LinkedHashSet<String> lhs=new LinkedHashSet<String>();lhs.add("hello");lhs.add("world");lhs.add("Java");lhs.add("Java");for (String s:lhs){System.out.println(s);}}
}

3.7、TreeSet集合概述和特点

元素有序:这里的顺序不是指存储和取出的顺序,而是按照一定顺序规则进行排序,具体方式取决于构造方法

无参构造方法TreeSet()        根据其元素的自然排序(按字母A--z,0--9)进行排序

带参构造        TreeSet(Comparator comparator)        根据指定的比较器进行排序

没有带索引的方法,所以不能使用普通for循环比遍历(使用Iterator<> it=O.iterator()  或者使用增强for)

由于是Set集合,所以没有重复元素

<引用类型>       错误写法:<int>   正确写法<Integer>

package com.itheima_05;import java.util.Iterator;
import java.util.TreeSet;public class TreeSetDemo {public static void main(String[] args) {
//        TreeSet<String> ts=new TreeSet<String>();/* ts.add("a");ts.add("l");ts.add("t");ts.add("f");*/TreeSet<Integer> ts=new TreeSet<Integer>();
//        装箱,直接可以将int类型的值装入Integer中ts.add(62);ts.add(32);ts.add(72);
//        还是不能有重复的元素ts.add(32);ts.add(42);Iterator<Integer> it=ts.iterator();while(it.hasNext()){int s=it.next();System.out.println(s);}}
}

3.8、自然排序Comparable的使用

  • 存储学生对象并遍历,创建TreeeSet集合使用无参构造方法
  • 要求:按年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序

结论

  • 用TreeSet集合存在自定义对象,无参构造方法使用的是自然排序对元素进行排序的
  • 自然排序,就是让元素所属的实现Comparable接口,重写compareTo(T o)方法
public class Student implements Comparable<Student>{...}
  • 重写方法时,一定要注意排序规则必须按照要求条件和次要条件来写
public class Student implements Comparable<Student>{public int compareTo(Student s) {
//        return 0; 认为每一个元素都和第一个元素相等
//        return 1;   正数 按照输入的顺序输出
//        return -1;    负数 输出顺序与输入顺序相反
//        按照年龄大小排序 num=this.age-s.age为正 从小到大排序int num=this.age-s.age;
//         年龄相同时按照名字的字母排序 年龄相同,比姓名,姓名相同返numint num2=num==0?this.name.compareTo(s.name):num;return num2;}

案例:按年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序

package com.itheima_05;import java.util.TreeSet;public class ComparableDemo {public static void main(String[] args) {TreeSet<Student> ts=new TreeSet<Student>();Student s1=new Student("xishi",28);Student s2=new Student("wangzhaojun",24);Student s3=new Student("diaochan",26);Student s4=new Student("yangguifei",32);Student s5=new Student("wangzuxian",32);Student s6=new Student("wangzuxian",32);ts.add(s1);ts.add(s2);ts.add(s3);ts.add(s4);ts.add(s5);
//        姓名 年龄都重复时 return 0;s所以重复元素不会输出ts.add(s6);for (Student s:ts){System.out.println(s.getName()+","+s.getAge());}}
}

案例二:存储学生对象并遍历,创建TreeSet集合对象使用带参构造方法

要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序

package com.itheima_06;import java.util.Comparator;
import java.util.TreeSet;/*
案例二:存储学生对象并遍历,创建TreeSet集合对象使用带参构造方法要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序* */
public class TreeSetDemo {public static void main(String[] args) {TreeSet<Student> ts=new TreeSet<Student>(new Comparator<Student>() {@Overridepublic int compare(Student s1, Student s2) {
//                return 0; 认为每一个元素都和第一个元素相等
//        return 1;   正数 按照输入的顺序输出
//        return -1;    负数 输出顺序与输入顺序相反
//        按照年龄大小排序 num=this.age-s.age为正 从小到大排序int num= s1.getAge()- s2.getAge();
//         年龄相同时按照名字的字母排序 年龄相同,比姓名,姓名相同返numint num2=num==0?s1.getName().compareTo(s2.getName()):num;return num2;}});Student s1=new Student("kebo",41);Student s2=new Student("kyrie",29);Student s3=new Student("Jams",37);Student s4=new Student("Curry",33);Student s5=new Student("Love",51);Student s6=new Student("curry",33);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.getName()+","+s.getAge());}}
}

案例:成绩排序

需求:用TreeSet集合存储多个学生信息(姓名、语文成绩、数学成绩),并遍历该集合

要求:按照总分从高到低出现

package com.itheima_07;import com.itheima_07.Student;
import com.itheima_07.Teacher;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;public class TreeSetDemo {public static void main(String[] args) {Student s1=new Student("lala",6,2);Student s2=new Student("wangmazi",3,2);Student s3=new Student("ankang",5,2);Student s4=new Student("anna",4,6);Student s5=new Student("weiwei",8,2);TreeSet<Student> ts=new TreeSet<Student>(new Comparator<Student>() {@Overridepublic int compare(Student s1, Student s2) {//                int num=s1.getyScore()+s1.getsScore()-(s2.getyScore()+s1.getsScore());
//              成绩高的排名在前面int num=-s1.sum()+s2.sum();int num2=num==0?s1.getName().compareTo(s2.getName()):num;return num2;}});ts.add(s1);ts.add(s2);ts.add(s3);ts.add(s4);ts.add(s5);for (Student s:ts){System.out.println(s.getName()+","+s.getyScore()+","+s.getsScore());}//   通过自然排序(在类实现comparable<>接口,然后重写compareTo() )的方式在来一遍System.out.println("案例二:新建一个老师类用老师的年龄和工龄之和排序,用自然排序实现");Teacher t1=new Teacher("wang",36,12);Teacher t2=new Teacher("li",37,12);Teacher t3=new Teacher("jiang",52,24);Teacher t4=new Teacher("li",36,12);Teacher t5=new Teacher("an",36,12);TreeSet<Teacher> t=new TreeSet<Teacher>();t.add(t1);t.add(t2);t.add(t3);t.add(t4);t.add(t5);Iterator<Teacher> it=t.iterator();while(it.hasNext()){Teacher tr=it.next();System.out.println(tr.getName()+","+tr.getAge()+","+tr.getWorkingYears());}}}

案例:不重复的随机数

需求:编写一个程序,获取10个1-20之间的随机数,要求随机数不能重复,并在控制台输出

思路:

package com.itheima_08;import java.util.*;/*
案例:不重复的随机数
需求:编写一个程序,获取10个1-20之间的随机数,要求随机数不能重复,并在控制台输出思路:①创建一个Set集合对象②创建一个随机数对象③判断集合的长度是不是小于10是:产生一个随机数,并添加到Set集合否:回到③继续④遍历集合产生随机数的方法回顾:
方法一:Math.random()   随机数范围[0,1]
方法二: Random r=new Random()  [0,bound]不带随机种子,每次执行都产生不一样的结果Random r=new Random(5)带有随机种子,随机种子相同时执行相同的次数,生成的随机数是一样的
方法三:System.currentTimeMillis() 生成一个当前时间毫秒数的随机数,所以循环生成可能只有一个值。* */
public class TreeSetDemo {public static void main(String[] args) {
//        HashSet不会自动排序
//        Set<Integer> set=new HashSet<Integer>();
//        TreeSet集合会自动排序Set<Integer> set=new TreeSet<Integer>();
//            int random= (int) (Math.random()*10);Random random=new Random();while(set.size()<10){int number=random.nextInt(20)+1;set.add(number);}Iterator<Integer> it= set.iterator();while (it.hasNext()){System.out.println(it.next());}
//                return break;}
}

4.泛型

4.1、泛型的概述

概述:时JDK中引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型。它的本质时参数化类型,也就是说所操作的数据类型被指定为一个参数

一提到参数,最熟悉的就是定义但方法时有形参,然后调用此方法时传参,那么参数化类型怎么理解?

顾名思义,就是将类型由原来的具体的类型参数化,然后再使用/调用时传入具体的类型

这种参数类型可以用在类、方法和接口中,分别被称为泛型类型、泛型方法、泛型接口

泛型定义格式:

<泛型>:指定一种类型的格式,这里的类型可以看成是形参

<泛型1、泛型2...>:指定多种类型的格式,多种类型之间用逗号隔开,这里的类型可以看成形参

将来具体调用时给定的类型可以看成实参,并且实参的类型只能是引用数据类型

泛型的好处:

  • 把运行时期的问题提前到了编译阶段。
  • 避免了强制类型转换

4.2、泛型类

package com.itheima_01;/*public class Generic {
//    分别创建String、Integer、Boolean类型的方法
//    缺点在于,需要几种类型,就得定义几个方法public void show(String s){System.out.println(s);}public void show(Integer i){System.out.println(i);}public void show(Boolean b){System.out.println(b);}
}*//*public class Generic<T> {public void show(T t){System.out.println(t);}
}*/
//泛型方法可以接受任意的类型
public class Generic{public<T> void show(T t){System.out.println(t);}
}

4.4、泛型接口

定义格式:

修饰符 interfacce 接口名<类型>{ }

public interface Generic<T> { }

接口不能直接使用,因为接口是抽象的,无法直接实现,需要给出实现类。然后用多态的方式创建对象。

4.5、类型通配符

package com.itheima_03;
import java.util.ArrayList;
import java.util.List;public class GenericDemo {public static void main(String[] args) {
//        类型通配符<?>表示任意类型List<?> list1=new ArrayList<Object>();List<?> list2=new ArrayList<Object>();List<?> list3=new ArrayList<Object>();
//        <?extends 上限类名> 此时List只能访问上限以下的类型
//        List<?extends Number> list=new ArrayList<Object>();//超过了上限List<?extends Number> list5=new ArrayList<Number>();
//        List<?extends Number> list6=new ArrayList<String>();//超过了上限List<?extends Number> list7=new ArrayList<Integer>();//        <?super 下限类名> 此时List只能访问下限以上的类型List<?super Number> list8=new ArrayList<Object>();List<?super Number> list9=new ArrayList<Number>();
//        List<?super Number> list10=new ArrayList<String>();//超过了底线
//        List<?super Number> list11=new ArrayList<Integer>();//超过了底线}
}

4.6、可变参数

4.7、可变参数的使用

Arrays工具类中有一个静态方法
public static <T> List<T>asList(T...a)  返回由指定数组支持的固定宽度大小列表
没有增删,可以set()List接口中有一个静态方法
public static <E>List<E>of(E...elements)    返回包含任意数量元素的不可变列表
返回的集合不能做增删改Set接口中有一个静态方法
public static <E>Set<E>of(E...elements)     返回一个包含任意数量元素的不可变集合

元素不可重复,不能增删没有修改的方法

package com.itheima_05;import java.util.Arrays;
import java.util.List;
import java.util.Set;/*
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 class GenericDemo {public static void main(String[] args) {
//        Arrays工具类中有一个静态方法
//        public static <T> List<T>asList(T...a)  返回由指定数组支持的固定宽度大小列表List<String> list= Arrays.asList("hello","world","java");
//        list.add("javaee");//UnsupportedOperationException
//        list.remove("java");//UnsupportedOperationExceptionlist.set(1,"javaee");System.out.println(list);System.out.println("----------------------");
//        List接口中有一个静态方法
//        public static <E>List<E>of(E...elements)    返回包含任意数量元素的不可变列表List<String> list1=List.of("I","love","you","you");
//        list.add("too");//UnsupportedOperationException
//        list1.remove("love");//UnsupportedOperationException
//        list1.set(1,"yl");//UnsupportedOperationExceptionSystem.out.println(list1);//        Set接口中有一个静态方法
//        public static <E>Set<E>of(E...elements)     返回一个包含任意数量元素的不可变集合System.out.println("----------------------");
//        元素不能重复Set<String > list2=Set.of("I","love","you");list2.add("too");//UnsupportedOperationExceptionlist2.remove("love");//UnsupportedOperationException
//        list2.set(1,"yl");//UnsupportedOperationExceptionSystem.out.println(list2);}
}

5.Map

5.1、Map集合概述和使用

Map集合概述

Interface Map<K,V>        K:键的类型 V:值的类型

将键映射到值得对象:不能包含重复的键:每个键可以映射到最多一个值

举例:学生的学号和姓名

itheima001        林青霞

itheima002        张曼玉

itheima003        王祖贤

创建Map集合对象

多态的方式

具体的实现类HashMap

package com.itheima01;import java.util.HashMap;
import java.util.Map;public class MapDemo01 {public static void main(String[] args) {
//        Interface Map<K,V>     K:键的类型 V:值的类型Map<String,Integer> map=new HashMap<String,Integer>();
//      Object put(Object k, Object v)将指定的值与此映射中的指定键关联(可选操作)。map.put("老张",20);map.put("老王",22);map.put("老杨",23);
//  当键位类型相同时,就是在替换之前键位的数据map.put("老杨",27);System.out.println(map);}
}

5.2、map集合常用方法

package com.itheima01;import java.util.HashMap;
import java.util.Map;public class MapDemo02 {public static void main(String[] args) {
//        Interface Map<K,V>     K:键的类型 V:值的类型Map<String,Integer> map=new HashMap<String,Integer>();
//      Object put(Object k, Object v)将指定的值与此映射中的指定键关联(可选操作)。map.put("老张",20);map.put("老王",22);map.put("老杨",23);
//  当键位类型相同时,就是在替换之前键位的数据map.put("老杨",27);map.remove("老张");
//        map.clear();System.out.println(map.containsKey("老王"));System.out.println(map.containsValue(27));System.out.println(map.isEmpty());System.out.println(map.size());System.out.println(map);}
}

5.3、map集合常用获取元素的方法

 V get(Object key)     根据键获取值Set<K>keySet()        获取所有键的集合Collection<V>values()  获取所有值的集合Set<Map.Entry<K,V>>entrySet() 获取所有键值对 对象的集合
package com.itheima01;import java.util.*;/*
* V get(Object key)     根据键获取值
* Set<K>keySet()        获取所有键的集合
* Collection<V>values()  获取所有值的集合
* Set<Map.Entry<K,V>>entrySet() 获取所有键值对 对象的集合
*
*
* */
public class MapDemo03 {public static void main(String[] args) {
//        Interface Map<K,V>     K:键的类型 V:值的类型Map<String,Integer> map=new HashMap<String,Integer>();
//      Object put(Object k, Object v)将指定的值与此映射中的指定键关联(可选操作)。map.put("老张",20);map.put("老王",22);map.put("老杨",23);map.put("科比",41);map.put("乔丹",51);map.put("詹姆斯",37);
//  当键位类型相同时,就是在替换之前键位的数据
//        map.put("老杨",27);
//        map.remove("老张");
//        map.clear();
//        System.out.println(map.containsKey("老王"));
//        System.out.println(map.containsValue(27));
//        System.out.println(map.isEmpty());
//        System.out.println(map.size());
//         V get(Object key)     根据键获取值int age=map.get("科比");
//        System.out.println(age);
//         Set<K>keySet()        获取所有键的集合Set<String> keySet=map.keySet();for (String s:keySet){System.out.println(s);}
//         Collection<V>values()  获取所有值的集合Collection<Integer> values =map.values();/*for (Integer i:values){System.out.println(values);}*/Iterator<Integer> it=values.iterator();while(it.hasNext()){int i=it.next();System.out.println(i);}
//         Set<Map.Entry<K,V>>entrySet() 获取所有键值对 对象的集合/**/System.out.println(map);}
}

5.4、map集合遍历方式1

Map集合的遍历方式获取所有键的集合,用keySet()遍历键的集合,获取到每一个键,用增强for实现根据每个键去找值,用get(Object key)
package com.itheima02;import java.util.HashMap;
import java.util.Map;
import java.util.Set;/*
Map集合的遍历方式获取所有键的集合,用keySet()遍历键的集合,获取到每一个键,用增强for实现根据每个键去找值,用get(Object key)
*/
public class mapDemo {public static void main(String[] args) {Map<String,String> map=new HashMap<String,String>();map.put("科比","湖人");map.put("乔丹","公牛");map.put("库里","勇士");map.put("哈登","篮网");Set<String> keySet=map.keySet();for (String key:keySet){String team=map.get(key);System.out.println(key+","+team);}}
}

5.5、map集合遍历方式1

将元素看成一对夫妻

  • 获取所有的结婚证
  • 遍历结婚证的集合,得到每一个结婚证
  • 根据结婚证获取丈夫和妻子

案例一:HashMap集合存储学生对象并遍历

String为键位,Student为键位对应的值,此时键位值类型需要自己创建类

然后调用Student值有两种方法

①先调取键位+值得整体,存入Set集合,然后分别调用具体得数据

②将键位值用map.keySet()方法存储在Set集合中,的然后在遍历Set集合的同时,

再用get(Object key)方法获取键位对应的值,将键位与值拼接起来。

HashMap<String, Student> map = new HashMap<String, Student>();

package com.itheima03;import java.util.HashMap;
import java.util.Map;
import java.util.Set;public class StudentDemo {public static void main(String[] args) {HashMap<String, Student> map = new HashMap<String, Student>();Student s1 = new Student("老杨", 18);Student s2 = new Student("老田", 19);Student s3 = new Student("老铁", 20);map.put("001", s1);map.put("002", s2);map.put("003", s3);
//        获取键位,将键位添加到集合Set<String> keySet =map.keySet();
//      遍历键位,然后用get(Object key)获取键位对应的值for (String key:keySet){Student student=map.get(key);System.out.println(key+","+student.getName()+","+student.getAge());}/*Set<Map.Entry<String,Student>> entrySet= map.entrySet();for (Map.Entry<String,Student> me:entrySet){String key=me.getKey();Student value=me.getValue();System.out.println(key+","+value.getName()+","+value.getAge());}*/}
}

案例二:当键位作为类的时候,想要保证键位的唯一性我们需要在类中重写equals(Object o) hashCode()方法

package com.itheima04;import java.util.HashMap;
import java.util.Set;public class StudentDemo {public static void main(String[] args) {HashMap<Student,String> map = new HashMap<Student,String>();Student s1 = new Student("杨磊", 18);Student s2 = new Student("田云", 19);Student s3 = new Student("老铁", 20);
//        创建一个信息相同的Student对象Student s4 = new Student("老铁", 20);map.put(s1,"西安");map.put(s2,"新疆");map.put(s3,"拉萨");
//       不同的键位值,对应了相同的键位,键位不唯一了,此时需要重写equals() HashCode()/*后台输出老铁,20,拉萨杨磊,18,西安田云,19,新疆老铁,20,重庆*/map.put(s4,"重庆");
//        获取键位,将键位添加到集合Set<Student> keySet =map.keySet();
//      遍历键位,然后用get(Object key)获取键位对应的值for (Student key:keySet){String value=map.get(key);System.out.println(key.getName()+","+key.getAge()+","+value);}/*Set<Map.Entry<String,Student>> entrySet= map.entrySet();for (Map.Entry<String,Student> me:entrySet){String key=me.getKey();Student value=me.getValue();System.out.println(key+","+value.getName()+","+value.getAge());}*/}
}

案例三:ArrayList集合存储HashMap元素并遍历

需求:创建一个ArrayList集合,存储三个元素,每个元素都是Hash Map,每个HashMap的键和值都是String,并遍历

  • 第一个HashMap元素

湖人  科比
    公牛  乔丹
    勇士  库里

  • 第二个HashMap元素

北京  书豪
    广东  阿联
    新疆  周琦

  • 第三个HashMap元素

美国  白人
    非洲  黑人
    日本  黄人

  • 建立一个ArrayList将Hash元素存储进去
  • 然后遍历ArrayLIst
  • 然后在遍历ArrayList的过程中再将遍历出来的HashMap元素在遍历一次
package com.itheima05;
/*
第一个HashMap元素湖人  科比公牛  乔丹勇士  库里
第二个HashMap元素北京  书豪广东  阿联新疆  周琦
第三个HashMap元素美国  白人非洲  黑人日本  黄人建立一个ArrayList将Hash元素存储进去*/import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;public class ArrayListDemo {public static void main(String[] args) {ArrayList<HashMap<String,String>> array=new ArrayList<HashMap<String,String>>();HashMap<String,String> hm1=new HashMap<String,String>();hm1.put("湖人","科比");hm1.put("公牛","乔丹");hm1.put("勇士","库里");HashMap<String,String> hm2=new HashMap<String,String>();hm2.put("北京","书豪");hm2.put("公牛","阿联");hm2.put("勇士","周琦");HashMap<String,String> hm3=new HashMap<String,String>();hm3.put("美国","白人");hm3.put("非洲","黑人");hm3.put("日本","黄人");
//       将所有的HashMap集合当成元素添加到,ArrayList中array.add(hm1);array.add(hm2);array.add(hm3);
//        System.out.println(array);for (HashMap<String,String> m:array){
//            System.out.println(2);
//            将ArrayLIst中存储的集合遍历出来
//            System.out.println(m);
//            因为ArrayList集合中元素也是集合,所以还可以在遍历一次
//          m表示的是一个HashMap集合,现在要做的是将HashMap集合中键位和键位值拼接在遍历出来Set<Map.Entry<String,String>> entrySet=m.entrySet();for (Map.Entry<String,String> entry:entrySet){String key=entry.getKey();String value = entry.getValue();System.out.println(key+","+value);}System.out.println("尝试一下通过拼接 调用键位+键位对应的值");Set<String> keySet=m.keySet();for (String key:keySet){String value=m.get(key);System.out.println(key+","+value);}}}
}

案例四:Has和Map集合存储ArrayList元素并遍历

需求:创建一个HashMap集合,存储三个键值对元素,每个键值对元素的键是String,值是ArrayList,每个ArrayList的元素是String,并遍历

案例五:统计字符串中每个字符出现的次数

需求:键盘录入一个字符串,要求统计字符串中每个字符串出现的次数。例如:aceacdbacbadbab      在控制台输出  a(5)b(4)c(3)d(2)e(1)思路:键盘录入一个字符串创建HashMap集合,键是Character 值是Integer遍历字符串,得到每一个字符串拿到的每一个字符串作为键到HashMap集合中去找对应的值,看他的返回值如果返回值是null,说明该字符在HashMap集合中不存在,就把该字符串作为键,1作为值存储如果返回值不是null,说明该字符在HashMap集合中存在,就把该值加1,然后重新存储该字符和对用的值
package com.itheima07;import java.util.*;/*
需求:键盘录入一个字符串,要求统计字符串中每个字符串出现的次数。例如:aceacdbacbadbab      在控制台输出  a(5)b(4)c(3)d(2)e(1)思路:键盘录入一个字符串创建HashMap集合,键是Character 值是Integer遍历字符串,得到每一个字符串拿到的每一个字符串作为键到HashMap集合中去找对应的值,看他的返回值如果返回值是null,说明该字符在HashMap集合中不存在,就把该字符串作为键,1作为值存储如果返回值不是null,说明该字符在HashMap集合中存在,就把该值加1,然后重新存储该字符和对用的值
*/
public class HashMapDemo {public static void main(String[] args) {
//        键盘录入一个字符串Scanner sc=new Scanner(System.in);System.out.println("请随机输入一个字符串");String line=sc.nextLine();
//        创建HashMap集合,键是Character 值是Integer/*HashMap<Character,Integer> hm=new HashMap<Character,Integer>();//        遍历字符串,得到每一个字符for (int i=0;i<line.length();i++){
//            获取到了对应的键位char key = line.charAt(i);
//            根据键位去找对应的值Integer value = hm.get(key);
//        如果返回值是null,说明该字符在HashMap集合中不存在,就把该字符串作为键,1作为值存储if (value == null) {
//           当键位值是空的时候我们给键位值赋1hm.put(key, 1);
//        如果返回值不是null,说明该字符在HashMap集合中存在,就把该值加1,然后重新存储该字符和对用的值} else {value++;hm.put(key, value);}}
//        拿到的每一个字符串作为键到HashMap集合中去找对应的值,看他的返回值Set<Map.Entry<Character, Integer>> entries = hm.entrySet();for (Map.Entry<Character, Integer> m:entries){Character key = m.getKey();Integer value = m.getValue();System.out.print(key+"("+value+")");}*/System.out.println("用TreeMap可以实现对键位进行排序");TreeMap<Character,Integer> hm=new TreeMap<Character,Integer>();//        遍历字符串,得到每一个字符for (int i=0;i<line.length();i++){
//            获取到了对应的键位char key = line.charAt(i);
//            根据键位去找对应的值Integer value=hm.get(key);
//        如果返回值是null,说明该字符在HashMap集合中不存在,就把该字符串作为键,1作为值存储if (value==null){
//           当键位值是空的时候我们给键位值赋1hm.put(key,1);
//        如果返回值不是null,说明该字符在HashMap集合中存在,就把该值加1,然后重新存储该字符和对用的值}else {value++;hm.put(key,value);}}
//        拿到的每一个字符串作为键到HashMap集合中去找对应的值,看他的返回值Set<Map.Entry<Character, Integer>> entries = hm.entrySet();for (Map.Entry<Character, Integer> m:entries){Character key = m.getKey();Integer value = m.getValue();System.out.print(key+"("+value+")");}}
}

6.Collections

6.1、Collections概述和使用

Collections类的概述

是针对集合操作的工具类

collections类的常用方法

package com.itheima01;/*
public static <T extends Comparable<?super T>>void sort(List<T>list) 将制定的列表按升序排序
public static void reverse(List<?>list)     反转制定列表中元素的顺序
public static void shuffle(list<?>list)     使用默认的随机排列指定的列表*/import java.util.ArrayList;
import java.util.Collections;
import java.util.List;public class CollectionsDemo {public static void main(String[] args) {List<Integer> list=new ArrayList<>();list.add(23);list.add(42);list.add(32);list.add(21);list.add(43);
//    public static <T extends Comparable<?super T>>void sort(List<T>list) 将制定的列表按升序排序
//        Collections.sort(list);
//    public static void reverse(List<?>list)     反转制定列表中元素的顺序Collections.reverse(list);
//    public static void shuffle(list<?>list)     使用默认的随机排列指定的列表Collections.shuffle(list);System.out.println(list);}
}

集合collection相关推荐

  1. Java 集合Collection常见知识点汇总~

    看了一些所谓大公司的JAVA面试问题,发现对于JAVA集合类的使用都比较看重似的,而自己在这方面还真的是所真甚少,抽空也学习学习吧. java.util包中包含了一系列重要的集合类,而对于集合类,主要 ...

  2. Java—一篇读懂java集合(Collection/Map)及Lambda表达式

    集合简介   在集合类之前,我们使用数组存储,数组既可以存储基本数据类型的值,也可以存储对象(对象的引用变量),但是集合只能存储对象.   Java集合类似于一种容器,将同类型的对象(实际为对象引用) ...

  3. 【再探backbone 02】集合-Collection

    前言 昨天我们一起学习了backbone的model,我个人对backbone的熟悉程度提高了,但是也发现一个严重的问题!!! 我平时压根没有用到model这块的东西,事实上我只用到了view,所以昨 ...

  4. 集合Collection总览

    一.集合(Collection)介绍 1.1为什么需要Collection Java是一门面向对象的语言,就免不了处理对象 为了方便操作多个对象,那么我们就得把这多个对象存储起来 想要存储多个对象(变 ...

  5. Java集合Collection接口中的常用方法演示

    Java集合Collection接口中的常用方法演示 添加 add(Objec tobj) 和 addAll(Collection coll) 获取有效元素的个数 int size() 清空集合 vo ...

  6. Java 集合 Collection、Iterator

    Java集合分为Set(无序.不可重复).List(有序.重复).Queue(队列)和Map(映射关系) Java集合概述 数组元素既可以是基本类型的值,也可以是对象(实际保存对象的引用变量) 集合只 ...

  7. 小白学习java集合框架(集合Collection)

    希望各位能够留下你们美丽的赞和评论谢谢,或者有好的资源帮帮小编提升实力一起努力,奥里给!! 拒绝垃圾视频:超级好的视频,建议从头开始看:https://www.bilibili.com/video/B ...

  8. 容器集合——Collection(单列)、Map(双列)

    集合体系结构 集合类的特点 提供一种存储空间可变的存储模型,存储的数据容量可以随时发生改变 集合类的体系图 Collection(单列) 概述     - 是单例集合的顶层接口,它表示一组对象,这些对 ...

  9. Day15 --框架集合 Collection集合 和 List 集合

    a.     对象数组 --自定义对象类         概述             * 创建一个自定义类用于存储学生信息,并且获取到每个学生信息 .         使用              ...

最新文章

  1. CRNN维度变换的解释这样你也可以自定义CRNN了
  2. ZYNQ 调试遇到的问题
  3. EMNLP2018论文解读 | 利用篇章信息提升机器翻译质量
  4. python获取系统时间函数_简单记录python的时间函数操作
  5. numpy reshape
  6. OS X 使用技巧——在Finder窗口标题栏上显示路径
  7. JavaScript怎么安装_WebStrom 2019安装教程
  8. daemontools的安装、简介
  9. 易语言解压服务器中压缩包,易语言取压缩包中的文件列表源码
  10. 惩罚函数法迭代过程的本质及其他最优化方法中的一些概念
  11. 用计算机编程解魔方,魔方程序 (详细的解释)
  12. docker-compose开机自启动设置
  13. python访问陌生人qq空间_使用Python+Selenium模拟登录QQ空间
  14. 广告投放管理平台 oython源码_【直播】全新腾讯广告投放管理平台如何帮助广告主乘风破浪...
  15. 50个Java精品源码免积分下载
  16. Taday——文件操纵(1)
  17. UNITY 开发日记/教程 俄罗斯方块 (五) 方块平移和旋转
  18. sql服务器查看版本信息,SQL Server 各种版本号的查看
  19. 区块链技术能否提高网络安全?
  20. JavaScript中linux时间戳与日期的转换

热门文章

  1. 映像文件工具srec
  2. quick-cocos 3.5 lua banding c++
  3. 1276. 不浪费原料的汉堡制作方案(Medium)
  4. 5G消息应用号推荐|官方种草清单第六期
  5. OSChina 周日乱弹 —— 这个野男人是谁
  6. 笔记本合上盖子就断网怎么办?
  7. linux下qt打印功能如何实现,Qt Graphics-View的打印功能实现
  8. Python——提取复数类型的数组的的实数部分和虚数部分
  9. 计算机显示屏知识,计算机配置知识之显示屏
  10. 开发者除了技术硬实力之外,还有哪些不可或缺的软实力