并发修改异常
package
com.czz.test00;
import
java.util.ArrayList;
import
java.util.Iterator
;
import
java.util.ListIterator;
/*
*
并发修改异常
*
迭代器遍历集合时
,
却使用集合本身的方式修改集合元素
,
将会导致并发修改异常
* */
public
class
ConcurrentExcepDemo {
public
static
void
main(String[]
args
) {
ArrayList<String>
list
=
new
ArrayList<String>();
list
.add(
"hello"
);
list
.add(
"hello"
);
list
.add(
"niling"
);
list
.add(
"czz"
);
//
迭代器遍历
/* Iterator<String> it = list.iterator();
while (it.hasNext()) {
String next = it.next();
if (!next.startsWith("h")) {
// list.remove("hello");//java.util.ConcurrentModificationException
it.remove();
}
System.out.println(next);
}
*/
//
集合本身的方式遍历
,
集合本身方式修改
/* for (
int
i = 0; i < list.size(); i++) {
if((list.get(i)).equals("hello")) {
list.remove(i);
i--;
}
System.out.println(list);
}
*/
//ListedList
类底层使用的是链表保存数据
ListIterator<String>
li
=
list
.listIterator();
while
(
li
.hasNext()) {
String
str
=
li
.next();
if
(
str
.equals(
"hello"
)) {
li
.add(
"HELLO"
);
}
}
//
遍历
for
(String
str
:
list
) {
System.
out
.println(
str
);
}
}
}
练习:
使用LinkedList类模拟栈结构的集合.(FILO,LIFO)
注意是模拟栈结构,不是直接使用LinkedList,即:有一个集合,其中的元素是先进后出.
package
com.czz.test01;
import
java.util.LinkedList;
//
自定义栈
class
MyStack {
LinkedList
list
=
new
LinkedList
();
public
void
add(Object
obj
) {
list
.addFirst(
obj
)
;
}
public
Object get() {
Object
obj
=
list
.remove();
//remove
获取并移除此列表的头
(
第一个元素
)
return
obj
;
}
public
boolean
isEmpty() {
return
list
.isEmpty();
}
}
public
class
FILIDemo{
public
static
void
main(String[]
args
) {
MyStack
link
=
new
MyStack();
link
.add(
"hello"
);
link
.add(
"hello1"
);
link
.add(
"hello2"
);
link
.add(
"niling"
);
while
(!
link
.isEmpty()) {
System.
out
.println(
link
.get());
}
}
}
泛型
概述:
1.
在
JDK1.5
之前,把对象放入到集合中,集合不会记住元素的类型,取出时,全都变成
Object
类型
2.
集合接口,集合类中出现的
<>
就是泛型,即参数化类型
<>
中的字母代表的是类型
3.
提高了程序的安全性,不符合类型的元素不能添加
4.
将运行期遇到的问题转移到了编译期
5.
记住了元素的类型,取出元素时省去了类型强转的麻烦
泛型类的定义
在类上添加一个类型的定义,在使用类时指定一个类型,就可以对传入的数据进行类型检查;在取出时,不必进行类型转换了,这样的类就叫做泛型类.
泛型类的特点:
每次针对不同类型的参数,都必须创建不同类型的对象;
这使得方法依赖于创建对象时的类型,他们之间的耦合度太高了;
泛型方法的定义
为了降低这种耦合度,可以把泛型定义在方法上,这就是泛型方法
例:
package
com.czz.fanxing;
//
自定义泛型类
class
Genclass<T>{
private
T
num
;
//
自定义泛型方法
public
T getNum() {
return
num
;
}
public
void
setNum(T
num
) {
this
.
num
=
num
;
}
}
public
class
GenericDemo {
public
static
void
main(String[]
args
) {
Genclass<String>
gen1
=
new
Genclass<String> ();
gen1
.setNum(
"2392"
);
System.
out
.println(
gen1
.getNum());
}
}
或者自定义泛型方法(在返回值前面声明)
class
czz{
public
<T>
void
show(T
t
) { //一旦方法是泛型方法,请在返回值前需要做泛型声明
}
}
泛型接口的定义
实现类实现泛型接口时,有两种情况:
1.实现类还不知道接口上应该传递什么类型,那么此实现类依然是个泛型类;
2.实现类知道了接口上应该传递什么类型,那么此实现类就是一个普通类.
例:
package
com.czz.fanxing;
//
定义泛型接口
interface
InterA<T>{
public
abstract
void
show(T
t
);
}
//
接口实现类
//
实现类知道接口中的具体类型
,
此时实现类可以是非泛型类
class
A
implements
InterA<String>{
@Override
public
void
show(String
t
) {
//
TODO
Auto-generated method stub
System.
out
.println();
}
}
//
实现类在实现接口时
,
本身还是一个泛型类
class
B<T>
implements
InterA<Integer>{
@Override
public
void
show(Integer
t
) {
//
TODO
Auto-generated method stub
}
}
//
实现类也不知道接口中泛型的具体类型
,
那么子类也是一个泛型类
class
C<T>
implements
InterA<T>{
@Override
public
void
show(T
t
) {
//
TODO
Auto-generated method stub
}
}
public
class
GenericDemo2{
public
static
void
main(String[]
args
) {
}
}
演示子父类关系中,子类继承泛型父类时的三种情况:
1.子类知道父类的泛型类型
2.子类不知道父类的泛型类型
3.子类知道父类的泛型类型,同时子类又是一个泛型类
package
com.czz.fanxing;
//
抽象类
abstract
class
Father<T>{
public
abstract
void
show(T
t
);
}
//
子类知道父类的泛型类型
,
但子类不是泛型
class
Son1
extends
Father<String>{
@
Override
public
void
show(String
t
) {
//
TODO
Auto-generated method stub
}
}
//
子类知道父类的泛型类型
,
但子类是泛型
class
Son2<T>
extends
Father<String>{
@
Override
public
void
show(String
t
) {
//
TODO
Auto-generated method stub
}
}
//
子类不知道父类的泛型类型
,
同时子类也是一个泛型类
class
Son3<T>
extends
Father<T>{
@
Override
public
void
show(T
t
) {
//
TODO
Auto-generated method stub
}
}
public
class
GenericDemo6 {
public
static
void
main(String[]
args
) {
//
TODO
Auto-generated method stub
}
}
集合中泛型的完整使用
其实集合是泛型的最主要的使用场景,正常的集合在使用时,都应该指定其中元素的类型,
ArrayList<String> al = new ArrayList<String>();
带泛型版ArrayList的使用
练习:
1.存储基本数据类型的元素并遍历
2.存储字符串元素并遍历
3.存储自定义类型的对象并遍历
package com.onnez.code;
import java.util.ArrayList;
import java.util.Iterator;
class A{
private String a;
public String getA() {
return a;
}
public void setA(String a) {
this.a=a;
}
public A() {
super();
// TODO Auto-generated constructor stub
}
public A(String a) {
super();
this.a = a;
}
public String toString() {
return a;
}
}
public class GenerictyDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("-----------");
ArrayList<A> list1=new ArrayList<A>();
list1.add(new A("HELLO"));
list1.add(new A("WORLD"));
Iterator<A> it1 = list1.iterator();
while(it1.hasNext()) {
System.out.println(it1.next());
}
}
}
泛型通配符
通配符
:
在创建集合变量时
,
不知道具体的类型
,
但是知道其类型的范围
,
可以使用类型通配符
?
表示一个不确定的类型
和其配套使用的还有以下两种
:
? extends E:
表示
E
及其子类
? super E:
表示
E
及其父类
例:
? extends Animal : 上限限定,Animal和它的子类可以使用
? super Animal: 下限限定,Animal和它的父类都可以使用
例:
/*
* 泛型类是没有多态性
* 泛型如何实现类似于多态性?
* 通配符能实现
* ?
* ? extends Animal : 上限限定,Animal和它的子类可以使用
* ? super Animal: 下限限定,Animal和它的父类都可以使用
*
* 类型限定的使用
*
*/
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
class Demo<T> {
}
class DemoTest {
public void test6(Collection<? extends Animal> c){//
//由于不知道具体的类型,所以不能放东西
// c.add(null);
//由于元素的类型要么是Animal,要么是它的子类,都可以使用Animal去接
for (Animal animal : c) {
}
}
public void test7(Collection<? super Animal> c){//
//由于不知道具体的类型,但是我知道都是Animal或者是它的父类,Animal和它的子类可以自动转成Animal
c.add(new Animal());
c.add(new Dog());
c.add(new Cat());
// c.add(new Object());
//由于不知道类型的上限,所以不能往外拿元素
// for (Object object : c) {
//
// }
}
public void test(Demo<Animal> d) {// 多态?Demo<Dog>
}
public void test2(Demo<?> d) {// 相当于Object
}
public void test3(Demo<Object> d) {// 相当于Object
}
public void test4(Demo<? extends Animal> d){//Animal或者它的子类
}
public void test5(Demo<? super Animal> d){//Animal或者它的父类都可以使用
}
}
public class GenericDemo1 {
public static void main(String[] args) {
DemoTest dt = new DemoTest();
dt.test5(new Demo<Animal>());
dt.test5(new Demo<Object>());
// dt.test5(new Demo<Dog>());//NG
// dt.test4(new Demo<Animal>());
// dt.test4(new Demo<Dog>());
// dt.test4(new Demo<Cat>());
// dt.test4(new Demo<Object>());//NG
// dt.test2(new Demo<Integer>());
// Demo<Dog> d = new Demo<Dog>();
// Demo<Animal> d2 = new Demo<>();
// dt.test(d2);//Demo<Animal> Demo<Dog>
}
}
Set接口概述
Set接口:一个不包含
"重复元素"的集合
Set接口中并没有定义特殊的方法,其方法多数都和collection接口相同
哈希集合中对重复元素的理解:和通常意义上的理解是不一样的.两个元素(对象)的hashcode返回值相同,并且equals返回值为true时(或者地址相同时),才称这两个元素是相同的.
HashSet:哈希集
线程不安全,存取速度快
他的方法大多数和collection相同
它不保证元素的迭代顺序,也不保证该顺序恒久不变
当HashSet中的元素超过一定数量时,会发生元素的顺序重新分配
设计程序完成:对同一个对象可以多次添加到HashSet集合中
package com.czz.hashcode;
import java.util.HashSet;
class Student{
String name;
static
int a = 0 ;
public Student(String name) {
this.name = name;
}
@Override
public int hashCode() {
System.out.println(this.name + " hashCode()...");//toString
return a++;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
return true;
}
}
public class HashCodeDemo1 {
public static void main(String[] args) {
HashSet<Student> set = new HashSet<>();
Student s1 = new Student("s1");
Student s2 = new Student("s2");
Student s3 = new Student("s3");
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s3);
set.add(s3);
System.out.println(set.size());
}
}
存储自定义类对象,并遍历.当所有的成员变量都相同,则为相同对象
package com.czz.hashcode;
/*
* 存储自定义类对象,并遍历.当所有的成员变量都相同,则为相同对象
* */
import java.util.HashSet;
import java.util.Iterator;
class student1{
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public student1(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public student1(String name) {
super();
this.name = name;
}
public student1() {
super();
// TODO Auto-generated constructor stub
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
student1 other = (student1) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "student1 [name=" + name + ", age=" + age + "]";
}
}
public class HashCodeDemo2 {
public static void main(String[] args) {
HashSet<student1> set = new HashSet<>();
student1 s1 = new student1("czz",25);
student1 s2 = new student1("nl",23);
student1 s3 = new student1("czz",25);
set.add(s1);
set.add(s2);
set.add(s3);
for (student1 student1 : set) {
System.out.println(student1.getName() + "," +student1.getAge());
}
System.out.println(set.size());
}
}
产生10个1-20之间不同的随机int值,要求使用HashSet集合
package
com.czz.hashcode;
import
java.util.HashSet;
public
class
HashCodeDemo3 {
public
static
void
main(String[]
args
) {
HashSet<Integer>
set
=
new
HashSet<>();
while
(
set
.size() <= 10) {
int
i
= (
int
)(Math.
random
()*20 +1);
set
.add(
i
);
}
for
(Integer
integer
:
set
) {
System.
out
.
println
(
integer
);
}
}
}
HashSet如何保证元素的唯一
考查add(Object obj)方法的实现过程
1.先调用obj的hashCode方法,计算哈希值(槽位值)
2.根据哈希值确定存放的位置
3.若位置上没有元素,则这个元素就是第一个元素,直接添加
4.若此位置上已经有元素,说明还有元素的hashCode方法返回值与它相同,则调用它的equals方法与已经存在的元素进行比较
5.若返回值为true,表明两个元素是“相同”的元素,不能添加
6.若返回值为false,表明两个元素是“不同”的元素,新元素将以链表的形式添加到集合中
class Student{
String name;
public Student(String name) {
this.name = name;
}
@Override
public int hashCode() {
System.out.println(this.name + " hashCode()...");//toString
return 0;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
return false;
}
}
public class HashSetDemo {
public static void main(String[] args) {
HashSet<Student> set = new HashSet<>();
Student s1 = new Student("s1");
Student s2 = new Student("s2");
Student s3 = new Student("s3");
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s3);
set.add(s3);
System.out.println(set.size());
/*
HashSet<String> set = new HashSet<String>();
set.add("hello");
set.add("hello");
set.add("hello");
set.add("hello2");
set.add("hello2");
for (String string : set) {
System.out.println(string);
}
System.out.println(set.size());
*/
}
}
HashSet注意事项:
1.想要往HashSet中添加对象,需要在定义类时,重写hashCode和equals方法
2.由于HashSet使用的是散列方法,所以,轻易不要在迭代集合元素的时候改变集合中的元素
LinkedHashSet类
从后缀可以看出
:
其本质是
HashSet,
只不过在内部维护了一个链表
,
可以记住元素放入的顺序
,
这样就保证了存取的顺序
,
但是
,
正是由于多了链表
,
所以它的效率低些
.