容器、集合:List、Set、Map
数组的弊端:只能存指定的数据类型,数组的长度不可变
List
List 接口的实现类:ArrayList、LinkedList、vector
ArrayList 数组列表
线性表是指由一系列元素组成的数据结构,元素之间具有顺序关系。ArrayList是一种线性表的实现方式,它是基于数组的数据结构。它的特点是可以通过索引直接访问元素,插入和删除元素时需要移动其他元素。
List list = new ArrayList();
List中的常用方法
1.添加元素 add( 添加的内容 )
没有额外声明则可以存储任意类型,也可以往里面存放一个数组或链表作为元素
//添加元素
list.add("A");
list.add(12);
list.add(null);
list.add(33.33);
2.在指定位置插入元素 add( 插入位置的下标 , 插入的内容 )
插入后,之后的元素会自动向后移
如果插入位置不连贯会出现下标越界异常
//在指定位置插入元素
list.add(1,44);//放到下标为1的地方,其他的元素向后移
//list.add(6,"B");//插入下标不连贯会出现下标越界异常
3.获取指定下标的元素 get( 指定下标 )
//获取元素
Object obj = list.get(2);
System.out.println(obj);
4.设置指定位置的元素 set( 指定下标 , 设置的值 )
//设置指定位置元素的值
list.set(2,22);
System.out.println(list);
5.是否包含某一个对象 contains( 对象 )
也可以使用 indexOf( 对象 ) != -1 来判断
//是否包含某一个对象
boolean bool = list.contains(22);
System.out.println(bool);
bool = list.indexOf(22)!=-1;
System.out.println(bool);
6.是否包含另一个集合的所有元素 containsAll( 另一个集合 )
有一个元素不包含就返回false
List listA = new ArrayList();
listA.add(33.33);
listA.add(null);
listA.add(2);
//是否包含另一个集合的所有元素
bool = list.containsAll(listA);
7.把另一个集合的所有元素依次添加到集合中 addAll( 另一个集合 )
注意,如果直接 add( 另一个集合 ) 是把整个集合作为一个元素添加到其中
//把另一个集合的所有值依次添加到集合中
list.addAll(listA);
//list.add(listA); //是将集合作为一个元素添加到集合中
System.out.println(list);
8.删除元素 remove( 要删除的下标或内容 )
删除找到的第一个元素,分两种情况:
参数传对象,删除这个对象,返回boolean值
参数传数值,删除下标位置的对象,然后返回被删除的对象
//删除元素 删除掉找到的第一个元素
list.remove(null);
System.out.println(list);
//参数传对象 删除这个对象 返回boolean
//参数传下标 删除下标位置的对象 返回被删除的对象
obj = list.remove(2);//传数字,下标越界会报异常
bool = list.remove((Integer)2);//强转成封装类删除指定元素
System.out.println(bool);
System.out.println(list);
传入下标越界会报异常,如果希望删除指定数值内容的元素需要强转成对应包装类
遍历
使用 size() 获取当前集合容量,然后使用循环遍历
for (int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
while (!list.isEmpty()){//非空,循环删除
list.remove(0);//删除第一个元素会自动补齐
}
当不知道里面有多少元素时,可以使用迭代器
Iterator 迭代器:只知道是否有下一个元素,和下一个元素是什么,不知道下标是多少
Iterator it = list.iterator();//获取一个迭代器
while (it.hasNext()){//是否有下一个
System.out.println(it.next());//获取下一个元素
//一开始指向空,下一个指向第一个
}
增强for循环是一种语法糖,它在底层使用迭代器来遍历集合中的元素,而不是直接操作集合中的元素。迭代器是一种访问集合中元素的方式,它提供了一种顺序访问集合中元素的方法,但它并不允许修改集合中的元素。
在增强for循环中,迭代器会在每一次迭代中返回集合中的下一个元素,并且迭代器本身并不知道集合中元素的索引位置。因此,增强for循环无法直接通过迭代器来修改集合中的元素。
System.out.println(list);// [A, 44, 33.33, [33.33, null, 2]]
for(Object item : list){
//System.out.println(item);//不能进行修改值的操作
if(item instanceof Double){
if((Double) item ==33.33) {
//ClassCastException 类型转换异常(不安全)
System.out.println("item赋值" + item);
item = 44.44;//不能直接赋值
}
}
//if(item.getClass()==item.getClass()){}//比较是不是同一个类
if(item instanceof List){
List list1 = (List) item;
list1.set(1,"ABC");
}
}
System.out.println(list);// [A, 44, 33.33, [33.33, ABC, 2]]
在上面的例子中,通俗来讲,item只是一个指向集合元素地址的变量,当对item直接赋值时,改变的只是变量指向的地址,而不改变原集合内存空间中的元素;但当通过item定位到一个List类型的元素时,通过对应的set方法直接设置了对象内存空间的内容,实现了修改。
扩容
ArrayList扩容方式
默认初始容量10,也可以通过构造方法传入一个值手动设置初始容量
创建时还没有容量,还是一个空数组,往里添加元素时才会初始化一个默认容量10
扩容1.5倍,新容量 = 原容量 + (原容量 >> 1)除2取整
扩容过程:10 +5、 15 +7、 22 +11、 33...
最大容量为 Integer.MAX_VALUE - 8 的安全容量,如果还不够,最多只能 Integer.MAX_VALUE
LinkedList 链表
链表也是一种线性表的实现方式,它是由一系列节点组成的。每个节点包含一个数据元素和指向下一个节点的指针。链表的特点是插入和删除元素时不需要移动其他元素,只需要改变节点之间的指针。
LinkedList 链表和 ArrayList 的使用方法一样,存储数据的方式(结构)不一样。
LinkedList 是双向链表,同时记录着前一个元素和后一个元素的地址,实现双向检索。
使用链表存储理论上无限扩容,但由于size属性是int类型存储,实际上最大值只能是 Integer.MAX_VALUE,超过这个值将无法查询。
ArrayList 和 LinkedList 性能比较
ArrayList:通过数组存储数据,通过下标索引查找数据非常快;
插入和删除慢,插入和删除元素时需要移动其他元素。
LinkedList:通过节点指针从头遍历,查找速度慢;
插入和删除快,插入和删除时只需要改变前后指向。
可以通过双向检索,将指定位置和(size>>1)比较,选择从前或从后。
ArrayList适合频繁访问元素的场景,而LinkedList适合频繁插入和删除元素的场景。
当处理超大数据量时,二者的性能几乎一样。
内部类
独立的类叫外部类,定义在类中的类叫内部类
外部类只能用public和default(默认)来修饰
静态内部类:当前类中需要某些对象来支持他的业务,但又不希望外部访问和修改,用static修饰
成员内部类:对象的类,需要通过对象调用,可以使用任意访问权限修饰符
局部内部类:在方法中声明,只能在方法中调用,方法结束后消亡,不能使用其他修饰符
public class EasyInnerClass {
//静态内部类
//当前类中需要某些对象来支持他的业务,但又不希望外部访问和修改
public static class InnerA{
}
//成员内部类(对象的类)
public class InnerB{//访问权限修饰符都行
}
public void inner(){
//局部内部类(在方法中声明,只能在方法中调用,方法结束后消亡)
class InnerC{ //不能使用其他修饰符
}
new InnerC();
}
public static void test(){
InnerA innerA = new InnerA();
new EasyInnerClass().new InnerB();//成员内部类需要通过对象去调用
}
}
class Test{
public static void method(){
//通过类名在外部调用某个类的内部类
EasyInnerClass.InnerA a = new EasyInnerClass.InnerA();
}
}
匿名内部类,对象
匿名内部类是没有名字的类,它是在创建时同时定义和实例化的。它通常用于创建只需要简单实现的接口或抽象类的实例。
//抽象类
abstract class AbstractClass{
public abstract void method();//抽象方法
}
//函数式接口
@FunctionalInterface
interface EasyInter{
void method();
}
interface EasyInterA{
int method();
}
使用匿名内部类的语法是在创建对象时,直接在对象创建表达式的花括号内定义类的实现。
//匿名内部类对象,接口也可以声明匿名内部类
AbstractClass ac = new AbstractClass() {
@Override
public void method() {//实现抽象方法
}
};
System.out.println(ac.getClass());//class com.easy719.EasyInnerClass$1
AbstractClass acac = new AbstractClass() {
@Override
public void method() {//实现抽象方法可以不同
}
};
System.out.println(ac.getClass()==acac.getClass());//false,不是同一个匿名内部类
EasyInter ei = new EasyInter() {
@Override
public void method() {
}
};
在上面的例子中,创建了一个匿名内部类来实现AbstractClass抽象类和EasyInter接口,并重写了各自的抽象方法。使用getClass方法获取类全名,其中的$1表示这是外部类中的第一个匿名内部类。
Lambda表达式是一种更简洁的方式来声明函数式接口的实例,它可以作为参数传递给方法或者赋值给变量。Lambda表达式可以看作是一种匿名函数,是简化匿名内部类的一种写法,它可以被当作一个对象来使用。
使用Lambda表达式的语法是在参数列表和方法体之间使用箭头符号(->
)来定义函数的实现。
public static void test(){
//lambda表达式,简化匿名内部类的一种写法
EasyInter eia =()-> System.out.println("1111");
EasyInter eib =()->{int aa=12;
System.out.println("2222");
};
EasyInterA ea =()->12;//直接用12作为抽象方法的返回值
EasyInterA eaa =()-> {
return 12;//更多实现写在花括号中
};
EasyInter ee = EasyInnerClass::fun;//直接将静态方法作为抽象方法的实现
ee.method();
}
public static void fun(){
System.out.println("function");
}
代码块
一对大括号 { } 就是代码块
成员代码块:直接在类中写一对大括号 { } 就是成员代码块,每一次new对象时执行;
成员代码块在构造方法之前执行。
静态代码块:大括号前面用static修饰,在加载类对象时执行;
一个类的静态代码块在程序运行期间只会执行一次。
public class EasyBlock {
static {
System.out.println("父类---静态代码块");
}
{
System.out.println("父类---成员代码块");
}
EasyBlock(){
System.out.println("父类---构造方法");
}
public static void main(String[] args) {
new EasySon();
}
}
class EasySon extends EasyBlock {
static {
System.out.println("子类---静态代码块");
}
{
System.out.println("子类---成员代码块");
}
EasySon(){
System.out.println("子类---构造方法");
}
}
执行顺序:先有类再有对象,先执行static静态代码块;
先有父类再有子类,如果子类和父类都有静态代码块,先执行父类的
1.父类静态代码块
2.子类静态代码块
3.父类的成员代码块
4.父类的构造方法
5.子类的成员代码块
6.子类的构造方法