java基础
java笔记
一. 静态成员
关键词static
修饰对象
- 静态常量(类常量)
- 静态变量(类变量)
- 静态方法(类方法)
- 静态类(对内部类的修饰)
应用场景: 多个类需要共享同一个数据 故用关键词static 修饰 使其方法/属性/常量 固定一个内存空间 让每一个类都可以使用
调用静态成员的格式 ——类名.静态类成员
代码演示
public class StaticTest{ static double PI=3.14;//静态常量 static int id;//静态变量 public static void method1(){//静态方法 //do Something } public void method (){ System.out.println(StaticTest.PI);//调用静态常量 System.out.println(StaticTest.id); StaticTest.method1(); } }
static作用
- 给各类提供共享的数据和方法
- 打破虽创建同一个类名但他们却各自独立的情况 改变静态变量/常量 同类调用的时候也是改变后的变量
- 总的来说就是static可以让类之间不用继承 也可以一起使用的变量和方法(当然是在类之间没有继承关系但又要共享同一内容的情况下)
public class StaticTest(){ static int i=47; public StaticTest(){} public static void main (String []args){ StaticTest staticTest1=new StaticTest(); StaticTest staticTest2=new StaticTest();//虽创建同一个类名taticTest 但是jvm分给他们的是不同的内存空间 staticTest1.i=60;//static给他们搭起了桥梁 联通了起来 可以使他们共享改变后的i值 System.out.println(staticTest2.i);//打印出来是i=60;//而不是i=47; } }
注意:
- 静态类成员依旧遵循权限问题 如:静态类方法的私有属性 不能被其他类所调用
- ==静态方法体中不能调用非静态类方法和变量==
- 静态方法体也不能用this关键词(因为不能调用非静态的属性和方法)
- 用static关键修饰的变量必须是 类的属性(就是类的全局变量而非成员方法中的变量[成员方法的变量称作为局部变量])
//错误例子 public class StaticTest(){ static int id; static double PI; public void Test1(){ //do something } public static Test2(){ //do something } public static void Test3(){ static int money;//必报错 不能修饰方法的变量 Test1();//编译器会报错!!!在静态方法中只能调用静态方法!!! return this;//不能用this 不过我不明白为什么要写return this 可能为例子才写的吧 } }
5.成员变量又称实例变量,因为new(实例对象);而静态变量不需要实例化对象,直接通过类名调用该变量或方法,因此称之为类变量。
二、常量
关键词 final
修饰对象
- 变量(局部变量、全局变量)
- 对象
- 类
- 方法
作用
对于变量——使其不能被更改 变量变成了常量
注意:fina关键词定义的变量必须在声明时对其进行赋值操作。
tip:表示常量的变量要用全大写
final double PI=3.14;//数学中的常量 不可更改!局部常量 static final double PI=3.14//全局常量 只能在定义时才能赋值 其余不改变!
- 对于对象 ——使其只能指向固定的一个对象
注意:但还是可以改对象的变量值 要想根本不能改变 就必须 用static final修饰
public final class java1 { int id=101; public static void main(String[] args) { //final 固定指向了java1 java1 java1 = new java2();//不能指向其他类 会报错 java1.id=11;//只固定指向java1 但没限制对该类的变量赋值变值操作 } } class java2{ ..... }
- 对于类——不能被继承 你想想final的作用就是不想被更改 如果能被继承 不就是会遇到子类更改所继承的变量值吗?
- 对于方法 ——不能被重写
三.、内部类
[内部类和外部类之间的相互调用 - 没有名字~ - 博客园 (cnblogs.com)](https://www.cnblogs.com/rgever/p/8902758.html#:~:text=外部类访问内部类:必须建立内部类的对象. 内部类访问外部类:内部类可以直接访问外部类的成员包括私有成员,因为外部类持有内部类的引用. 特例:内部类写在外部类的方法中 (即局部变量的位置) 1、内部来外部类均可定义变量%2F常量.,2、只能被final%2Fabstract修饰. 3、只能访问被final%2Fabstract修饰的变量. 4、可以直接访问外部类中的成员,因为还持有外部类的引用. 标签%3A javase复习.)
1. 定义
一个类的内部又完整嵌套了另一个类结构。被嵌套的类称之为内部类,嵌套其他类的类称之为外部类。
2.基本语法
class Outer{//外部类
class Inner{//内部类
}
}
3.特点
可以直接访问私有属性
4.分类
定义在外部类的局部位置上(比如在方法和代码块 )
4.1局部内部类
基本语法
class OuterClass{
void test(){
class InnerClass{
...
}
}
}
特点
可以访问外部类的所有成员(属性和方法) 私有成员也可以
不可以添加访问修饰符,但是可以用final修饰
==作用域==作用在定义的方法或代码块中
外部类在方法中可以创建内部类对象,然后就可以调用内部类的方法
外部其他类不能访问 局部内部类(因为局部内部类地位是属于一个局部变量)
外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类成员,则可以使用[==外部类.this.成员==]去访问
class OuterClass{ private int m1=100; void m2(){ System.out.println("外部类方法"); } void test(){ class InnerClass{ private int m1=800; void test1(){ System.out.println(m1);//就近原则 输出800 System.out.println("访问外部类的m1="+OuterClass.this.m1); } } } }
4.2匿名(局部)内部类(==重点==)
基本语法
new 类或接口(参数列表){//本身接口就是不能new(实例化) 但是呢这是匿名内部类就是可以使用new关键字
类体
};
特点
- 没有类名
- 运行类型是匿名内部类
- 基于抽象类的匿名内部类必须实现抽象类的方法
- 基于类的匿名内部类的形参列表会传入到类中的构造器中
class OuterClass{
void test(){
IA tiger=new IA(){//匿名内部类
//基于接口的匿名内部类
//0.接口是不能实例化的 所以
//1.底层jdk已经new好了一个类 Outer04$1
//2.然后implements接口IA
//3.最后赋值给类型为接口类型IA 变量名为tiger
public void cry(){
//重写接口的方法
}
};
tiger.cry();
Father father=new Father("jack"){
//基于类的匿名内部类
//0.创建一个匿名内部类Outer04$2
//1.然后extends类Father
//2.最后赋值给变量名为father类型为Father的变量
void test(){
//重写test方法
System.out.println("基于类的匿名内部类的方法test")
}
};
father.test();
Son son=new Son(){//必须实现抽象方法test
void test(){
...
}
};
son.test();
}
}
interface IA{//定义一个接口
public void cry();
}
class Father{
Father(String name){
System.out.println(name);
}
void test(){
}
}
abstract class Son{
abstract void test();
}
- 匿名内部类仍然遵循动态绑定和继承的规则(super)
class Outer{
private int m1=100;
void test(){
Person p=new Person(){
void teach(){
//重写
}
};
p.teach();
//另一种写法(直接调用)
new Person(){
void test(){
//重写
}
void ok(String str){
super.ok(str);//调用的是匿名内部类继承的Person类
}
}.ok("jack");
}
}
class Person{
void teach(){
....
}
void ok(String str){
System.out.println(str);
}
}
- 匿名内部类本身也是返回对象
- 匿名内部类可以访问外部类所有成员(包括私有成员)
class Outer{
private int m1=100;
void test(){
Person p=new Person(){
void teach(){
//重写
System.out.println(m1);//100
}
};
p.teach();
}
}
class Person{
void teach(){
...
}
}
- 外部其他类不能访问 匿名内部类(因为局部内部类地位是属于一个局部变量)
- 外部类和匿名内部类的成员重名时,默认遵循就近原则,如果想在匿名内部类中访问外部类成员,则可以使用[==外部类.this.成员==]去访问
class Outer{
private int m1=100;
void test(){
Person p=new Person(){
int m1=88;
void teach(){
//重写
System.out.println(m1);//就近原则88
System.out.println(Outer.this.m1);//100
}
};
p.teach();
}
}
class Person{
void teach(){
...
}
}
作用
为了简化开发
同时解决只用一次类而产生不必要的损耗
最佳实践
将匿名内部类当做实参直接传递到函数中
class Practice{
public static void main(String args[]){
test(new IA(){
//实现接口IA的show方法
show(){
System.out.println("....");
}
});
Picture picture =new Picture();
picture.show();
}//调用test方法 将匿名内部类当做实参传入test当中
public static void test(IA ia){
ia.show();
}
}
interface IA{
void show();
}
//传统写法
//单独写一个类去实现这个接口
class Picture implements IA{
void show(){
System.out.println("....");//灵活性不如匿名内部类传参 因为一旦改变 所有为Picture类型的对象的内容都要改变(对于只是使用一次类的情况来说)
}
}
定义在外部类的成员位置上
4.3成员内部类
基本语法
class OuterClass{
int m1;
int m2;
void f1(){
...
}
class InnerClass{//成员内部类
...
}
}
特点
成员内部类可以直接访问外部类的所有成员,包括私有
可以任意添加访问修饰符(public protected 默认 private) 因为它本质是一个成员
作用域在所处的整个类体当中
外部类访问成员内部类,需要创建对象,再调用访问
class OuterClass{ private int m1; private m2; void f1(){ ... } class InnerClass{//成员内部类 void say(){ System.out.println("这是成员内部类的方法say") } } //外部类使用成员内部类方法 //先创建 后调用 InnerClass in=new InnerClass(); in.say(); }
外部其它类访问成员内部类,需要先创建外部类,再创建内部类
//两种方式 class OtherOuterClass{ public static void main(String args[]){ OuterClass outClass=new OuterClass(); OuterClass.InnerClass in=outerClass.new InnerClass(); OuterClass.InnerClass getIn=outerClass.getInnerClass(); } } class OuterClass{ int m1; int m2; void f1(){ ... } class InnerClass{//成员内部类 ... } public InnerClass getInnerClass(){ return new InnerClass(); } }
外部类和成员内部类的成员重名时,默认遵循就近原则,如果想访问外部类成员,则可以使用[==外部类.this.成员==]去访问
外部其他类无法访问成员内部类的私有方法和私有的成员内部类
解决措施
//可以通过在外部类的非私有方法中创建内部类并调用其私有方法 public class OtherOutClass { public static void main(String[] args) { OutClass outClass = new OutClass(); outClass.use(); } } class OutClass{ private class InClass{ private void say(){ System.out.println("这是成员内部类私有方法say"); } } void use(){ InClass inClass = new InClass(); inClass.say();//外部类可以调用内部类私有成员 } }
4.4静态(成员)内部类
定义 对成员内部类进行static的修饰
特点
只能访问外部类静态成员,非静态成员不能访问
可以添加访问修饰符(public protected 默认 private)
作用域在整个类体
可以直接访问外部类的静态成员
外部类访问静态内部类,需要先创建后调用,调用可以直接通过类名访问(前提要遵守访问权限)
class OtherOuterClass{ public static void main(String args[]){ OuterClass.InnerClass in=new OuterClass.InnerClass(); in.say(); //下面会报错 外部其他类无法访问私有的静态内部类的成员 OuterClass.InnerClass1 getIn=new OuterClass.say(); } } class OuterClass{ int m1; static int m2; static class InnerClass{ void say(){ } } private static class InnerClass1{ void say(){ } } }
外部类和静态内部类的成员重名时,默认遵循就近原则,如果想访问外部类成员(该成员一定要是静态的),则可以使用[==外部类.成员==]去访问
class OuterClass{ private static int m1=100; static class InnerClass{ int m1=200; void say(){ System.out.println(m1);//100 System.out.println(OuterClass.m1);//200 } } }
四、枚举类
0.应用场景:
需要创建某一个类,这个类的值是固定不变且有限个,即只读不修改。
1.定义
枚举是一组常量的集合,枚举是一种特殊的类,只包含一组有限且特定的对象。
2.实现方式
2.1 自定义实现枚举
1.不需要setXXX方法,因为枚举对象值通常只读
2.对枚举对象/属性使用final+static共同修饰,实现底层优化
3.枚举对象名通常使用全部大写,常量的命名规范
4.枚举对象个根据需要,也可以有多个属性
5.构造器私有化,避免在其他类中创建并初始化!
public class enumCode {
public static void main(String[] args) {
}
}
class Season{
String name;
String desc;
public final static Season SPRING=new Season("春天","温暖");
public final static Season SUMMER=new Season("夏天","炎热");
public final static Season AUTUMN=new Season("秋天","凉爽");
public final static Season WINTER=new Season("冬天","寒冷");
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
}
2.2 使用关键字enum实现枚举
1.用关键字enum代替class
2.多个对象值用逗号隔开
3.构造器依旧私有化
4.在枚举类的第一行写上对象值
public class enumCode {
public static void main(String[] args) {
System.out.println(Season.SPRING);
}
}
enum Season{
SPRING("春天","温暖"),//其实就是代替public final static Season SPRING=new Season("春天","温暖");
SUMMER("夏天","炎热"),
AUTUMN("秋天","凉爽"),
WINTER("冬天","寒冷");
String name;
String desc;
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
3.特点
- 使用enum关键字开发一个枚举类时,会默认继承Enum类(通过反编译javap)
public static final Season SPRING=new Season("春天","温暖")
用关键词enum修饰的类,是继承java.lang.Enum类
public class EnumPr { public static void main(String[] args) { Gender boy1=Gender.BOY; Gender boy2=Gender.BOY;//本质就是 public static final Gender BOY=new Gender(); System.out.println(boy1);//先去调用Gender类的toString方法 本类没有 就调用父类Enum类的toString方法 System.out.println(boy1==boy2);//静态对象赋初值 当然是一样的啦 } } enum Gender{ BOY,Girl; }
如果使用枚举类的无参构造器时,实参列表的和小括号可以省略
enum Season{
SPRING("春天","温暖"),//其实就是代替public final static Season SPRING=new Season("春天","温暖");
SUMMER("夏天","炎热"),
AUTUMN("秋天","凉爽"),
WINTER("冬天","寒冷"),
other();//省略写成other 调用Season无参构造器;
String name;
String desc;
private Season(){
//无参构造器
}
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
}
4.Enum类常用的成员方法
name()
//以上面枚举类的Season为例 public class EnumMethod{ public static void main (String[] args){ Season autumn=Season.AUTUMN; System.out.println(autumn.name()); } }
ordinal()
//枚举的静态值的次序 从零开始编号 autumn.ordinal()
values()
//返回枚举类型的数组 取出枚举类中所有静态值(枚举对象) Season []values=Season.values() for(Season season:values){//增强for循环 System.out.println(season);//根据枚举类的toString方法打印 }
valueOf()
//用于查找枚举对象 Season autumn=Season.valueOf("AUTUMN")
compareTo()
//用于比较两个枚举对象的编号 作差法 System.out.println(Season.AUTUMN.compareTo(Season.SUMMER))
五、注解
1.分类
1.1Override
定义
@Target(ElementType.METHOD)//元注解 所用只用于修饰方法 @Retention(RetentionPolicy.SOURCE) public @interface Override { }
作用
用于说明在子类继承父类并且同时重写(override)了父类的方法 便于校验是否重写父类方法
特点
只能用于修饰方法,不能修饰其他类,包和属性
1.2Deprecated
定义
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { }
作用
表明某元素已经过时 但==不代表不能调用== 用于版本升级的注解说明
特点
可以修饰包,变量,方法,文件,字段,参数等
1.3SupressWarnings
定义
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }
作用
用于抑制警告
特点
作用域根据放置的位置相关 可以修饰变量,方法,文件,字段,参数等
2.元注解(了解)
2.1 定义
修饰注解的注解称之为元注解
2.2种类
Retention(保留)
作用:只能用于修饰一个元注解定义,用于指定元注解可以保留多长时间 @Retention中包含了一个成员RetentionPolicy类型的成员变量,使用@Retention是必须为该value成员变量指定值
RetentionPolicy.**SOURCE **:编译器使用后,直接丢弃这种策略的注解
RetentionPolicy.**CLASS **:编译器将把注释记录在class文件中,当运行Java程序时,JVM不会保留注解
RetentionPolicy.RUNTIME:编译器将把注释记录在class文件中,当运行Java程序时,JVM会保留注解,程序可以通过反射获取该注解
Target
指定注解可以修饰哪些元素
Documented
指定该注解是否会在Javadoc(Java文档document)体现
Inherited
子类会继承父类的注解
六、集合
0.数组与集合
数组
1)创建数组,长度必须指定,一旦指定无法修改
2)保存必须是同一类型的元素
3)使用数组进行增加元素代码较为麻烦
Person[] pers=new Person[3]; Person[]pers2=new Person[pers.length+1]; pers2[pers2.length-1]=new Person();
集合
1)可以==动态保存==任意多个对象,使用方便
2)提供add,remove,set,get方法便于操作对象
3)使用集合添加,删除元素代码 更加简洁
集合框架图
- 单列集合
2. 双列结合(键值对)
1. 分类
1.1单列集合
1.1.1Collection(接口)
- 常用方法
- add 添加单个元素
- remove 删除指定元素
- contains 查找元素是否存在
- size 获取元素个数
- isEmpty 判断是否为空
- clear 清空
- addAll 添加多个元素
- containsAll 查找多个元素是否都存在
- removeAll 删除多个元素
@SuppressWarnings("all")
public class CollectionMethod {
public static void main(String[] args) {
//接口回调 类似于向上转型 子类指向父类 实现类指向接口 接口调用实现类方法
List list=new ArrayList();
//add()
list.add(1);// boolean add(Object) 任意类型
list.add("粥粥");
list.add(true);
//删除
list.remove(0);
System.out.println(list);
//查找
System.out.println(list.contains(0));
//个数
System.out.println(list.size());
//清空
list.clear();
System.out.println(list);
//添加多个元素
List list1=new ArrayList();
list1.add("三体");
list1.add("悲伤逆流成河");
list.addAll(list1);
System.out.println(list);
//删除多个元素
list.removeAll(list1);
System.out.println(list);
//查找多个元素
System.out.println(list.containsAll(list1));
//判断是否为空
System.out.println(list.isEmpty());
}
}
遍历元素方式
使用迭代器
介绍
1)用于遍历Collection集合中的元素
2)所有实现接口Collection的集合类都有iterator()方法,用以返回一个实现了Iterator接口的对象,既可以返回一个迭代器
3)Iterator的结构
4)Iterator仅用于遍历集合,本身并不存放对象
原理
@SuppressWarnings("all") public class Iterator_ { public static void main(String[] args) { Collection col=new ArrayList(); col.add(new Book("三国演义","罗贯中")); col.add(new Book("红楼梦","曹雪芹")); col.add(new Book("西游记","吴承恩")); Iterator iterator = col.iterator(); while (iterator.hasNext()) {//判断下一个位置是否还有更多的元素 Object next = iterator.next(); System.out.println(next); } // iterator=col.iterator();//该为重置iterator迭代器 //快捷键itit 显示快捷键ctrl+j while (iterator.hasNext()) {//返回false 无法执行while语句 Object next = iterator.next(); System.out.println(next); } } } class Book{ //封装Book private String name; private String author; public Book(String name, String author) { this.name = name; this.author = author; } }
增强for循环
底层还是利用迭代器 是迭代器的优化形式
public class Iterator_ { public static void main(String[] args) { Collection col=new ArrayList(); col.add(new Book("三国演义","罗贯中")); col.add(new Book("红楼梦","曹雪芹")); col.add(new Book("西游记","吴承恩")); for(Object book:col){ System.out.println(book); } } }
1.1.2List(接口)
基本介绍
List接口是Collection接口的子接口(上述的体系图)
特点
1)List集合类中元素有序(即添加的顺序和取出顺序一致),且可重复
public class list_ { public static void main(String[] args) { List list = new ArrayList(); list.add(1); list.add(1); list.add(2); list.add(3); } }
2)List集合中的每个元素都有其对应的顺序索引,即支持索引(从零开始)
//索引 System.out.println(list.get(0));
3)List容器中的元素都对应一个整数型的序号记载其在容器中位置,可以根据序号存取容器中的元素
常用方法
- add(int Index ,Object obj) 插入单个元素(默认尾插)
- addAll(int index,Object obj) 插入多个元素
- get() 获取索引
- indexOf() 获取第一个出现元素的位置序号
- set(int index,Object obj) 替换某元素
- subList(int start,int end) 获取子list 是左闭右开
public class adv_6 { public static void main(String[] args) { List list = new ArrayList(); for (int i = 0; i < 11; i++) { System.out.println(list.add("hello"+i)); } list.add(1,2);//在序号为1的位置插入元素2 list.remove(5); list.set(6,"粥粥"); Iterator iterator = list.iterator();//获取迭代器 while (iterator.hasNext()) { Object next = iterator.next(); System.out.println(next); } } }
遍历元素方式
实现接口List的子类遍历方式都是一样的 (ArrayList Vector LinkedList)
- 迭代器
- 增强for
- 普通for循环
public class listFor { public static void main(String[] args) { List list = //List list = new Vector();OK //List list = new LinkedList();OK list.add(1); list.add(2); list.add(3); list.add(4); //第一种 迭代器 Iterator iterator = list.iterator(); while (iterator.hasNext()) { Object elem = iterator.next(); System.out.println(elem); } //第二种 增强for循环 for (Object o :list) { System.out.println(o); } //第三种 普通for循环 for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } } }
List常用的实现子类
ArrayList
特点
1)可以存放空值(null)
2)是由可变数组来实现数据存储
3)ArrayList基本等同于Vector,除了==ArrayList是线程不安全==(效率高),故此在多线程当中不建议使用ArrayList
源码分析(==重难点==)
1)ArrayList中含有一个Object类型的数组(elementData)用于存放各种类型的数据对象
2)当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加,则扩容elementData为10,如需要再次扩容,则扩容为1.5倍
// 调用无参构造器 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//初始化值为零 } private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//返回一个类型为Object的数组 长度为零0 public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! 每次确认是否能够增加一个元素 elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private void ensureExplicitCapacity(int minCapacity) { modCount++;//纪录修改次数 在下标位置修改元素值 将null值改为赋值后的元素值 // 当最小容量都大于当前数组长度时 申请扩容grow if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // 纪录当前数组长度 int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1);//第一次运行扩容newCapacity还是零 位运算向右移一位相当于数据除于2 (1+0.5=1.5倍) if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); //拷贝原先数据 然后在申请放置空值的空间 elementData = Arrays.copyOf(elementData, newCapacity); }
3)如果使用的是指定大小的构造器,则初始化的大小为规定的大小。如果需要扩容。则直接扩容为elemData数组的1.5倍
public ArrayList(int initialCapacity) { //三种情况 if (initialCapacity > 0) {//大于零 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) {//等于零 就按照之前调用无参构造器是一样的 this.elementData = EMPTY_ELEMENTDATA; } else {//小于零 负数报错 throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity); } } //其余的方法跟无参的Arraylist是一样的 这就不复制粘贴了(doge)
Vector
- 定义
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
特点
底层也是一个对象数组,protected Object[]elementData;
Vector是线程同步的 ,即线程安全(效率不高);Vector类的操作方法有关键词synchronized
public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
故在开发中,需要线程同步时,考虑使用Vector
源码分析
1)当创建Vector对象时,如果使用的是无参构造器,则初始elementData容量为10,满后2倍扩容
2)如果指定大小,每次直接扩容两倍
//无参构造器调用——默认不定值为长度10 public Vector() { this(10); } public synchronized boolean add(E e) { modCount++;//纪录填入对象数组的次数 ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; } private void ensureCapacityHelper(int minCapacity) { // 判断最小需要长度(minCapacity)有无超过当前数组长度(elementData.length) 是否需要扩容grow if (minCapacity - elementData.length > 0) grow(minCapacity); } public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement;//未调用该构造器则capacityIncrement=0 } private void grow(int minCapacity) { //扩容机制 //capacityIncrement 是自定义增量大小 默认为零 int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity) if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } //指定大小 public Vector(int initialCapacity) { this(initialCapacity, 0); }
LinkedList
特点
1)LinkedList底层实现了双向链表和双端队列
2)可以添加任意元素(可以重复) 包括null
3)线程不安全
4)效率高
底层操作机制
1)底层维护了一个双向链表
2)维护了两个属性first 和last分别指向首节点和尾结点
3)每个节点(Node对象),里面又维护了prev,next,item三个属性,通过prev指向前一个,通过next指向后一个,最终实现双向链表
模拟双向链表
- 创建 new LinkedList
- 删除 remove(删除第一个)
- 添加 add(尾插)
public class LinkedList_ { public static void main(String[] args) { Node hjy = new Node("hjy"); Node zz = new Node("zz"); Node ggh = new Node("ggh"); Node hmy = new Node("hmy"); Node hxw = new Node("hxw"); //连接顺序 hjy->zz->ggh->hmy->hxw //若添加一个新结点N /* Node N =new Node("new"); hjy.next=zz; zz.next=ggh; ggh.next=N; N.next=hmy; hmy.next=hxw; hxw.prev=hmy; N.prev=ggh; hmy.prev=N; ggh.prev=zz; zz.prev=hjy;*/ hjy.next=zz; zz.next=ggh; ggh.next=hmy; hmy.next=hxw; hxw.prev=hmy; hmy.prev=ggh; ggh.prev=zz; zz.prev=hjy; /*删除就重新指向原来的Node结点 N结点就被删除了*/ //头结点 尾结点 Node first=hjy; Node last=hxw; while(true){ if(first==null) break; System.out.println(first); first=first.next;//移动头指针 } while(true){ if(last==null) break; System.out.println(last); last=last.prev;//移动尾指针 } } } //双向链表=Node+first+last //结点=value+prev+next class Node{ Node prev; Node next; Object value; //数据域 public Node(Object value) { this.value = value; } @Override public String toString() { return "Node{" + "value=" + value + '}'; } }
源码分析
//尾部添加元素 public boolean add(E e) { linkLast(e);//尾部连接元素 return true; } //尾插法 void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; } //删除第一个元素 public E remove() { return removeFirst(); } public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); } private E unlinkFirst(Node<E> f) { // 纪录第一个元素的信息 item和next final E element = f.item; final Node<E> next = f.next; //第一个元素的数据置空 next指针置空 f.item = null; f.next = null; //头指针移动到下一个元素 first = next; if (next == null) last = null; else next.prev = null;//当头指针移动到的元素的数据不为空,则前驱指针置空 size--;//长度减一 modCount++;//修改次数加一 return element; }
ArrayList和LinkedList比较
底层结构 增删的效率 改查的效率 ArrayList 可变数组 较低 数组扩容 较高 LinkedList 双向链表 较高 通过链表追加 较低 如何选择ArrayList和LinkedList:
1)如果==改查==的操作多,选择ArrayList
2)如果==增删==的操作多,选择LinkedList
3)一般来说,在程序中,大多情况都是查询,故大部分情况会选择ArrayList
1.1.3Set(接口)
基本介绍
1)无序(添加和取出的顺序不一致),==没有索引==
2)不允许重复元素(数据),所以最多包含一个null
public class HashSet_ { public static void main(String[] args) { HashSet hashSet = new HashSet(); hashSet.add(1); hashSet.add(1);//加入不了 重复元素(数值) hashSet.add(new Dog("jack")); hashSet.add(new Dog("jack"));//可以加入 这一个新的对象只是名字相同罢了 hashSet.add(new String("tom")); hashSet.add(new String("tom"));//加入不了 在后面的源码分析会解释 System.out.println(hashSet); } }
3)JDK中API的Set接口的常用使用类
- HashSet
- TreeSet
常用方法
因为List和Set都是Collection的子接口 所以常用方法和Collection接口一样
遍历方式
- 可以使用迭代器
- 增强for
- 不能使用索引进行遍历
public class Set_ { public static void main(String[] args) { Set set = new HashSet(); set.add("d"); set.add(1); set.add(2); set.add("8"); System.out.println(set); //迭代器 Iterator iterator = set.iterator(); while (iterator.hasNext()) { Object next = iterator.next(); System.out.println(next); } //增强for for (Object o :set) { System.out.println(o); } } }
HashSet
底层就是HashMap,HashMap底层是==数组+链表==
添加一个元素,先得==hash值== 会转成 索引值
找到存储数据表table 看这个索引位置是否已经存放的元素
如果有 调用==equals==比较 如果相同就放弃添加 如果不相同 则添加到最后
在java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),==并且==table的大小>=**MIN_TREEIFY_CAPACITY(默认64)**,就会进行树化(红黑树),否则让采用下列扩容机制
- 第一次添加,table数组扩容到16*加载因子(0.75)=12(并不是说占用了十二个下标的位置 而是说有十二个节点 )
- 如果该hash值对应的数组使用到了临界值12,就会扩容到16乘2=32,新的临界值就是32*0.75=24,以此类推(每加入一次结点) 16(12)32(24)64(48)
//源码分析 public HashSet(){ //构造器创建一个HashMap map=new HashMap(); } //hashSet中add() public boolean add(E e) { return map.put(e, PRESENT)==null; } 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; //hash$Node 封装了key-value属性 int n, i; //检验table是否为空 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //通过与hash按位与计算 得出下标 并且需要判断该hash后的下标位置是否为空 if ((p = tab[i = (n - 1) & hash]) == null) //空则生成结点Node加入该hash下标的位置 tab[i] = newNode(hash, key, value, null); //与对应的位置元素发生冲突(hash值相同) //三种情况 //1. 值相同或者是equals比较返回true //2. 该节点是否是树节点 //3. 结点不相同 加入hash值对应下标的tab表 else { Node<K,V> e; K k; //如果key相同或者equals比较返回true 则不加入该位置的结点后面 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); //同时加入后 判断该hash值对应下标tab的链表的结点个数有无到达8个 是否进行树化 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } //若hash相同 key相同 或者equals返回true 则不加入链表中 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; //不同p指向下一个结点 p移动 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;//空表示该位置可以加入元素 }
TreeSet
package Collection_; import java.util.Comparator; import java.util.TreeSet; /** * @author LouisBrilliant * @version 1.0 */ public class TreeSet_ {//低层 其实就是TreeMap public static void main(String[] args) { //必须设定自己比较器方法 下面的add方法才能执行 //TreeSet的去重方式 TreeSet treeSet = new TreeSet(new Comparator() {//new 接口{} 匿名内部类 构造器的参数也是接口 其实就是面向编程 @Override public int compare(Object o1, Object o2) { return ((String)o1).length()-((String)o2).length();//这里需要返回一个表达式 因为底层会帮你进行比较(大于 小于 等于) } }); /* Comparator<? super K> cpr = comparator; if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } else {//其中没有比较器参数时 执行String 中的compareTo方法 if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key;//字符串了型的key已经实现了Comparable接口 也就是默认的比较器Compartor do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } */ treeSet.add("lucy"); treeSet.add("lucy2"); treeSet.add("lucy35"); treeSet.add("lucy467"); } }
LinkedHashSet
介绍
- LinkedHashSet是LinkedSet的子类
- 由数组+双向链表维护构成
- LinkedHashSet更加元素的hashCode安排数组下标位置,同时通过双向链表维护元素的次序,使得元素看起来是以顺序插入
- LinkedHashSet 是不允许添加重复元素
debug分析(双向链表)
//AA->1->2->3->4->5->NULL(after)
//NULL<-AA<-1<-2<-3<-4<-5(before)
public class LinkedHashMap_ {
public static void main(String[] args) {
Set set = new LinkedHashSet();
set.add(new String("AA"));//底层维护了Node类型 前后指针 使得数据放进取出有了顺序
set.add(1);
set.add(2);
set.add(3);
set.add(4);
set.add(5);
}
}
1.2双列集合
1.2.1Map(接口)
定义
用于保存具有映射关系的数据:key-value(双列元素)
特点
- Map中key不能重复,value值可以重复
- key和value可以任何的数据类型,会封装到HashMap$Node对象中
- key和value存在的是单向一对一关系,即通过指定的key可以找到对应的value值
源码解读
- key-value是被Node对象封装
- 同时Node implements Entry接口 实现getValue和getKey方法
- 因此Entry接口可以存放Node类型的结点
- 将类型为Entry的元素存放在EntrySet中 为了遍历方便
public class Map_ { public static void main(String[] args) { Map map = new HashMap(); map.put(1,"bzd1"); map.put(2,"bzd2"); map.put(3,"bzd3"); map.put(4,"bzd4"); Set set = map.entrySet();//将map中entry返回到set集合中 for (Object o :set) {//向上转型 Map.Entry entry=(Map.Entry) o;//向下转型(拥有了Node独有的方法getValue和getKey) 运行类型是Node类型 Node类型实现了接口Map.Entry System.out.println(entry.getKey()+"-"+entry.getValue()); } } }
常用方法
- put 添加
- remove 根据键删除映射关系
- get 根据键获取值
- size 获取元素个数
- isEmpty 判断个数是否为0
- clear 清除
- containsKey 查找键是否存在
遍历方法(基于HashMap)
package Collection_;
import javax.imageio.stream.IIOByteBuffer;
import java.util.*;
@SuppressWarnings("all")
public class Map_ {
public static void main(String[] args) {
Map map = new HashMap();
map.put(1,"bzd1");
map.put(2,"bzd2");
map.put(3,"bzd3");
map.put(4,"bzd4");
//遍历方式
//1. map.entrySet
Set set = map.entrySet();
//1.1 增强for循环
for (Object o :set) {//向上转型
Map.Entry entry=(Map.Entry) o;
System.out.println(entry.getKey()+"-"+entry.getValue());
}
//1.2 迭代器
System.out.println("entryset===================");
Iterator iterator2 = set.iterator();
System.out.println(iterator2.next().getClass());//返回类型是class java.util.HashMap$Node
while (iterator2.hasNext()) {
Object entry = iterator2.next();//父类指向子类Node
Map.Entry m=(Map.Entry)entry;//需要向下转型
System.out.println(m.getKey()+":"+m.getValue());
}
// 2. map.keySet 获取所有的key值 以集合类型返回
Set set1 = map.keySet();
//2.1 通过增强for循环
for (Object key :set1) {
System.out.println(key+":"+map.get(key));
}
//2.2 通过迭代器
Iterator iterator = set1.iterator();//获取迭代器
while (iterator.hasNext()) {//判断下一个是否为空?
Object key = iterator.next();//不为空 则向后移一位
System.out.println(key+":"+map.get(key));
}
//3. map.values 获取map中所有的values值
Collection values = map.values();//返回一个集合类Collection
//3.1 普通for循环
System.out.println("2.for循环================");
for (int i = 0; i < values.size(); i++) {
System.out.println(i+":"+map.get(i));
}
//3.2 增强for循环
System.out.println("2.增强for循环==================");
for (Object value :values) {
int i=0;
System.out.println(value);
}
//3.3 迭代器
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object value = iterator1.next();
System.out.println(value);
}
}
}
1.2.2HashMap
特点
原理分析
跟上述的HashSet一致,就不赘述了…(详情请看HashSet源码分析)
Set set = map.entrySet(); //返回的是Node类型对象类型 for (Object o :set) {//向上转型 Map.Entry entry=(Map.Entry) o; //向下转型 运行类型是Node类型 Node类型实现了接口Map.Entry //就是HashMap$Node实现了接口Map$Entry接口 数据也是存储在Node对象的 System.out.println(entry.getKey()+"-"+entry.getValue()); }
1.2.3HashTable
- 介绍
- 存放键值对
- hashtable中键和值都不能为null 否则抛出NullPointerException
- 使用方法和HashMap一样
- HashTable是线程安全
- 键无序
package Collection_;
import java.util.Hashtable;
/**
* @author LouisBrilliant
* @version 1.0
*/
//hashtable
public class Hashtable_ {
public static void main(String[] args) {
Hashtable table = new Hashtable();
table.put("key","value");//俩参数 key-value
// table.put("key",null);//空指针异常
table.put("key","VALUE");//替换
table.put("key2","VALUE");//替换
table.put("key3","VALUE");
table.put("key4","VALUE");
table.put("key5","VALUE");
table.put("key6","VALUE");
table.put("key7","VALUE");
//扩容机制 2x+1 临界值threshold=初始化Hashtable$entry[]为11 11*0.75=8
//addEntry(hash,key,value,index) 添加K-V封装到Entry
//当if(count>threshold)满足时 进行 扩容
//int newCapacity =(oldCapacity<<1)+1 所以第一次扩容 11*2+1=23
table.put("key8","VALUE");//替换
table.put("key9","VALUE");//替换
System.out.println(table);
}
}
1.2.4Properties
读取文件
package Collection_;
import java.util.Properties;
/**
* @author LouisBrilliant
* @version 1.0
*/
public class properties_ {
public static void main(String[] args) {
//properties继承了Hashtable类并且实现了Map接口 也是键值对存储数据
//Properties还可以用于从xxx.properties文件(配置文件)中加载数据到Properties类对象 进行读取和修改
//因为properties继承了hashtable 所以key和value都不能为空
//properties和hashtable类似
Properties properties = new Properties();
// properties.put(null,"wocao");//异常
// properties.put(1,null);//异常
//添加
properties.put("1","hjy");
//删除
properties.remove("1");
//查
properties.getProperty("1");
properties.get("1");
}
}
1.2.5TreeMap
package Collection_;
import java.util.Comparator;
import java.util.TreeMap;
/**
* @author LouisBrilliant
* @version 1.0
*/
public class TreeMap_ {
public static void main(String[] args) {
/*
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
*/
//跟TreeSet一样 都是可以面向接口编程
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length()-((String)o2).length();
}
});
}
}