静态方法与非静态方法
静态方法与非静态方法最主要区别就是在类中使用static修饰,而非静态方法是不加static修饰方法的,在类中定义没有占内存,只有在类中被实例化成对象时,对象调用该方法才被分配内存。
其次,静态方法中只能调用静态成员或者方法,不能调用非静态方法或者非静态成员,而非静态方法既可以调用静态成员或者方法又可以调用其他的非静态成员或者方法。总的来说非静态方法啥都可以调用。
注意main方法一定是static方法!!因此main方法中要想调用非静态方法,就需要先进行实例化。
集合分类
在Java中,集合主要可分为两大类,分别是Collection(单列集合)与Map(双列集合)
Collection集合基础知识
我们首先对Collection集合进行介绍,Collection是一个接口,定义为Collection即支持泛型,而在Collection下面还要List与Set两个子接口。
Collection集合特点
-
List系列集合:添加的元素是有序、可重复、有索引。
ArrayList、LinekdList:有序、可重复、有索引。
-
Set系列集合:添加的元素是无序、不重复、无索引。
Hashset: 无序、不重复、无索引; LinkedHashset:有序、不重复、无索引。(对Hashset进行了改良) Treeset:按照大小默认升序排序、不重复、无索引。
下面是Collection的常用方法,由于Collection是最终接口,因此所有子接口都是可以使用的。
Collection的遍历
Collection的遍历方式主要可分为3种,分别是iterator(迭代器),for增强以及
注意这里之所以说是for增强而不是for循环,是因为要想使用for循环则必须要有索引,而并不是所有的Collection都是有索引的。
Collection<Integer> collection=new ArrayList<>();
collection.add(1);
collection.add(4);
collection.add(66);
System.out.println(collection);
//从集合对象中获取迭代器对象
Iterator<Integer> iterator=collection.iterator();
//获取的迭代器默认是指在第一个位置的
System.out.println(iterator.next());
//结合while循环输出迭代器结果
while(iterator.hasNext()){
System.out.println(iterator.next());
}
第二种方式则是for增强,该方法不但可以遍历集合,也可以遍历数组。事实上,for增强遍历集合是迭代器遍历的简化版本。
for (Integer idex : collection){
System.out.println(idex);
}
而迭代器则不能做到。
String[] strings={"李白","杜甫","白居易"};
for (String idex : strings){
System.out.println(idex);
}
Lambda表达式遍历集合
Lambda表达式是JDK8新增的,是一种更简单更直接的方式。该方法遍历是通过调用forEach实现的。事实上,该方法本质上就是for增强。下面是其简化过程。
注意:集合存储对象实际上是存储的地址,其存储思想如下图所示:
List集合
List集合是Collection集合的子接口,因此上面Collection所具有的特性List都具备。
同时,List集合的实现类包含ArrayList以及LinkList。
由于List是具有索引的,因此是可以使用for循环遍历的。
ArrayList的底层数据结构
ArrayList是基于数组
实现的。
LinkList的底层数据结构
由于LinkedList存储了首尾位置,因此其对首尾元素操作很快。
Java模拟队列
叫号系统,排队系统的设计
public static void main(String[] args) {
LinkedList<String> queue=new LinkedList<>();
queue.addLast("第一个");
queue.addLast("第二个");
queue.addLast("第三个");
System.out.println(queue.removeFirst());
System.out.println(queue);
}
Java模拟栈
LinkedList<String> stack=new LinkedList<>();
stack.addFirst("第一个");
stack.addFirst("第二个");
stack.addFirst("第三个");
System.out.println(stack.removeFirst());
System.out.println(stack);
Set集合
HashSet集合
首先我们来看第一个Set的实现,HashSet,其特点是无序、不重复、无索引
需要注意的是,set中的无序并非每次都无序,每当有元素变动时,原本的顺序可能会被打乱,而如果没有元素变动,则顺序只会无序一次。
public static void main(String[] args) {
Set<Integer> set=new HashSet<>();//很经典的一个代码
set.add(55);
set.add(67);
set.add(55);
set.add(55);
System.out.println(set);
}
LinkHashSet集合
与HashSet不同的是,LinkHashSet是可排序的,不重复,无索引
Set<Integer> set=new LinkedHashSet<>();
set.add(55);
set.add(67);
set.add(65);
set.add(55);
System.out.println(set);
TreeSet
Set<Integer> set=new TreeSet<>();
set.add(55);
set.add(67);
set.add(65);
set.add(55);
System.out.println(set);
可以看到,TreeSet默认升序排序
Set集合的功能几乎都是继承于其父亲Collection,因此其并没有什么其他的功能。
哈希值
实际上就是一个int类型的数值,每个Java对象都有一个hash值。
Java中的所有对象,都可以调用Object类的hashCode方法来获取其哈希值。
哈希值的特点:
同一个对象调用hashCode方法的返回值都是相同的,即无论调用多少次结果都是相同的。
不同的对象,其哈希值理论上是不同的,但也有可能相同,称为哈希碰撞
HashSet的底层数据结构
Hash表是一种增删改查都表现较好的数据结构
在JDK8之前,Hash表是由数组与链表组成的,在JDK8之后,Hash表是由数组、链表以及红黑树
组成的。
根据HashSet的底层原理,我们可以得知为何HashSt是一种不重复、无序、无索引的结构了。由于无序,所以便是无索引了。
但随着值变多,hashset中的链表变得越来越长,这也就意味着查询性能会下降。如何解决呢,与先前List相似,进行扩容即可。上面提到,默认创建的数组 会有一个加载因子,这个值默认是0.75
,这意味着16给元素,如果数组占用达到了75%
,即16*0.75=12
时便会进行扩容。
但这种情况下,依旧会存在数量过大 的情况,因此从JDK8开始,便规定如果链表长度大于8,并且数组长度>=64时,便会将上述这种数据结构(数组+链表)转换为红黑树的结构。
当然,这个值指的是hash
值。
问题:
我们发现,,相同的对象存入set中时是不能去重复的,这实际上是由HashSet的实现原理所决定的(对象的哈希值不同
),那么,如果我们想要让HashSet能够对相同的对象进行去除,我们该怎么做呢?
方法如下:我们需要做的是重写对象的hashCode和equals方法。
重写hashCode方法与equals方法其实很简单,我们可以让程序自动生成。
LinkHashSet的底层数据结构
LinkHashSet的底层依旧是通过哈希表(数组、链表以及红黑树)实现的,但其每个元素多了一个双链表机制来记录其前后元素的位置,即有了首地址,那么每个元素的位置便被确定了,因此是有序的。
TreeSet的底层数据结构
TreeSet是基于红黑树构建的,因此其是有序不重复的。
其特点是:不重复、无索引、可排序(默认 升序排序,即按照元素的大小,从小到大排序)
那么既然是可以排序的,那是依据什么排序呢?
- 对于数值类型,如Integer、Double等,其按照数值本身的大小排序
- 对于字符串类型,默认按照字符串首字符的编号进行升序排序
- 但对于自定义的引用型对象,TreeSet默认是无法进行排序的。
因此,一旦将引用型对象插入Set集合中,则会报错:
package Collection;
import javax.swing.plaf.synth.SynthSpinnerUI;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetTest {
private String name;
private int age;
public TreeSetTest(String name, int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
TreeSetTest t1= new TreeSetTest("李白",19);
TreeSetTest t2= new TreeSetTest("李白",19);
Set<TreeSetTest> s=new TreeSet<>();
s.add(t1);
System.out.println(s);
}
}
Exception in thread "main" java.lang.ClassCastException: Collection.TreeSetTest cannot be cast to java.lang.Comparable
at java.util.TreeMap.compare(TreeMap.java:1294)
at java.util.TreeMap.put(TreeMap.java:538)
at java.util.TreeSet.add(TreeSet.java:255)
at Collection.TreeSetTest.main(TreeSetTest.java:20)
那么,对于这个问题,我们该如何解决呢?我们可以自定义排序规则,如何做呢,有两种方法:
1.让自定义类型实现Comparable
接口,并重写里面的compareTo
方法。我们在compareTo方法中指定如何比较,比如按照年龄比较。
package Collection;
import javax.swing.plaf.synth.SynthSpinnerUI;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetTest implements Comparable<TreeSetTest>{
private String name;
private int age;
public TreeSetTest(String name, int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
TreeSetTest t1= new TreeSetTest("李白",19);
TreeSetTest t2= new TreeSetTest("李白",19);
Set<TreeSetTest> s=new TreeSet<>();
s.add(t1);
System.out.println(s);
}
@Override
public int compareTo(TreeSetTest o) {
return this.age-o.age;
}
}
其中,this
代表本身,形参o
是传过来的要比较的对象。但此时我们发现了一个问题,那就是我们明明将两个对象都加入了,但是输出时却只有第一个。
其实这也很容易解释,因为TreeSet是基于红黑树构造的,因此相同的就不会加入了。即不重复。
那么第二种方法是什么呢?
通过TreeSet
的有参构造器,可以设置Comparator
对象(比较器对象,用于指定比较规则)
package Collection;
import javax.swing.plaf.synth.SynthSpinnerUI;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetTest implements Comparable<TreeSetTest>{
private String name;
private int age;
public TreeSetTest(String name, int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
TreeSetTest t1= new TreeSetTest("李白",19);
TreeSetTest t2= new TreeSetTest("杜甫",18);
Set<TreeSetTest> s=new TreeSet<>(new Comparator<TreeSetTest>() {
@Override
public int compare(TreeSetTest o1, TreeSetTest o2) {
return o2.getAge()-o1.getAge();
}
});
s.add(t1);
s.add(t2);
System.out.println(s);
}
@Override
public int compareTo(TreeSetTest o) {
return this.age-o.age;
}
}
同时我们还故意将TreeSet构造器中的比较规则与自定义对象的比较规则写反,此时我们发现其最终遵循的是构造器中的比较规则。
当然,上面的代码我们还可以简化。
public static void main(String[] args) {
TreeSetTest t1= new TreeSetTest("李白",19);
TreeSetTest t2= new TreeSetTest("杜甫",18);
Set<TreeSetTest> s=new TreeSet<>((o1, o2) -> o2.getAge()-o1.getAge());
s.add(t1);
s.add(t2);
System.out.println(s);
}
Lambda表达式的核心就是实现的这个接口中的抽象方法中的形参列表 -> 抽象方法的处理,可以帮助我们简化代码。
需求场景总结
集合的并发修改异常问题
要求将数据中包含”李“字的全部删掉。我们首先使用迭代器来实现:
public static void main(String[] args) {
ArrayList arrayList=new ArrayList();
arrayList.add("李白");
arrayList.add("小李子");
arrayList.add("杜甫");
arrayList.add("李世民");
arrayList.add("李广");
arrayList.add("白起");
System.out.println(arrayList);
Iterator<String> iterator=arrayList.iterator();
while (iterator.hasNext()){
String name=iterator.next();
if (name.contains("李")){
arrayList.remove(name);
}
}
}
发生报错:
[李白, 小李子, 杜甫, 李世民, 李广, 白起]
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at Collection.ListException.main(ListException.java:18)
随后我们使用for循环来实现:
for (int i = 0; i < arrayList.size(); i++) {
String name=arrayList.get(i);
if (name.contains("李")){
arrayList.remove(i);
}
}
此时,则没有报错了,但我们却发现有些数据没有删除成功。
这是由于什么呢,因为我们删了一个元素后,后面的元素则会移上来占据位置,但这个索引却依旧递增,因此导致部分数据没有删除成功。
相比于使用for循环遍历删除,迭代器遍历删除则更贴心一些,告诉你发生了并发修改异常,而不需要像for循环一样让你去自己找bug。
那么,我们该如何解决呢?首先是for循环修改,我们分析只需要让其每次删除元素后索引位置后移一下即可,即执行i--
;
for (int i = 0; i < arrayList.size(); i++) {
String name=arrayList.get(i);
if (name.contains("李")){
arrayList.remove(i);
i--;
}
}
随后是迭代器修改,我们只需要不在List中直接删除元素就可以了,迭代器中为我们提供了一个remove方法,可以删除迭代器当前遍历的元素,并相当于在底层执行一次i--
操作。
Iterator<String> iterator=arrayList.iterator();
while (iterator.hasNext()){
String name=iterator.next();
if (name.contains("李")){
iterator.remove();
}
}
同时,我们要知道使用增强for循环是解决不了这个问题的。
可变参数
例子:
package Collection;
import java.util.Arrays;
public class ParamsTest {
public static void main(String[] args) {
test();
test(1);
test(1,2,3,5);
test(new int[] {1,2,5,76});
}
public static void test(int... num){
//获得的参数是当作数组来处理的
System.out.println(num.length);
System.out.println(Arrays.toString(num));
}
}
Collections工具类
//为集合(Collection)批量添加元素,传入的参数分别是要插入的集合对象以 及要添加的数据
ArrayList<String> names=new ArrayList<>();
Collections.addAll(names,"李白","杜甫","白居易");
System.out.println(names);
//打乱List集合中的元素顺序
Collections.shuffle(names);
System.out.println(names);
//对List集合中的元素按照升序进行排序
Collections.sort(names);
System.out.println(names);
以上是对一些基本数据类型的使用案例,那么对一些自定义数据类型呢?如自定义对象,我们发现在使用sort
方法时报错了。
随后我们进行修改,与先前TreeSst中排序一样,我们可以选择自定义对象中设置排序规则,也可以让sort
方法中设置比较器对象来实现。
ArrayList<Students> students=new ArrayList<>();
Collections.addAll(students,new Students("李白",28),new Students("杜甫",19),new Students("白居易",29));
System.out.println(students);
Collections.shuffle(students);
System.out.println(students);
Collections.sort(students);
System.out.println(students);
Collections.sort(students, new Comparator<Students>() {
@Override
public int compare(Students o1, Students o2) {
return o2.getAge()-o1.getAge();
}
});
System.out.println(students);
Map集合
Map为双列集合,又称为键值对集合