JAVA开发学习-day06
集合是容器的一种,之前学过的数组也是一种容器,但数组有弊端,数组只能存储指定的数据类型,且数组的长度不能改变,所有我们在存储数据时可以使用集合,常用的集合有List,Set,Map
1. 列表 List
List接口继承自Collection接口,并添加了一些针对有序列表的操作。它允许元素的重复,并提供了根据索引访问、添加、删除和替换元素的方法。在Java中,List接口有几个常见的实现类,每个实现类都具有不同的性能和用途。
ArrayList
:基于动态数组实现,支持随机访问和快速遍历,适用于读取和修改操作较多的场景。LinkedList
:基于双向链表实现,支持高效的插入和删除操作,适用于频繁的插入和删除操作。Vector
:与ArrayList
类似,但是线程安全,适用于多线程环境。
1.1 ArrayList
ArrayList是List接口的一个常见实现类,它基于动态数组实现,可以根据需要自动扩展和收缩数组的大小。
ArrayList中的数组是Object类数组,所以可以存储任意类型的数据
下面是一些ArrayList类中常用的方法
add(E element): 在列表的末尾添加元素。
List list = new ArrayList();
//List中常用方法
//添加数据 List可以存储任意类型 List中存储数据的的是Object数组
list.add("A");
list.add(12);
list.add(null);
list.add(33.33);
System.out.println(list); //[A, 12, null, 33.33]
list.add(1,"44"); // 在下标为1的位置上位置上,该下标的元素以及后面的元素依次向后
System.out.println(list); // [A, 44, 12, null, 33.33]
//插入的位置之前必须都有元素
list.add(5,989);
System.out.println(list); // [A, 44, 12, null, 33.33, 989]
//list.add(10,123) 会造成下标越界
get(int index): 获取指定索引位置的元素。
List list = new ArrayList();
//List中常用方法
//添加数据 List可以存储任意类型 List中存储数据的的是Object数组
list.add("A");
list.add(12);
list.add(null);
list.add(33.33);
System.out.println(list); //[A, 12, null, 33.33]
//获取元素
Object obj = list.get(2); //传入下标
System.out.println(obj); // null
set(int index, E element):设置指定索引位置的元素
public static void main(String[] args) {
List list = new ArrayList();
list.add("A");
list.add(12);
list.add(null);
list.add(33.33);
System.out.println(list); //[A, 12, null, 33.33]
//设置元素
list.set(2,22);
System.out.println(list); // [A, 12, 22, 33.33]
}
boolean contains(Object o):判断元素是否存在列表中
List list = new ArrayList();
list.add("A");
list.add(12);
list.add(null);
list.add(33.33);
System.out.println(list); //[A, 12, null, 33.33]
//列表中是否包含元素判断
boolean bool = list.contains(12);
System.out.println(bool); //true
bool = (list.indexOf(12) != -1);
System.out.println(bool); //true
boolean containsAll(Collection<?> c):判断该集合是否包含传入的集合
List list = new ArrayList();
list.add("A");
list.add(12);
list.add(null);
list.add(33.33);
System.out.println(list); //[A, 12, null, 33.33]
//列表中是否包含元素判断
List listA = new ArrayList();
listA.add(33.33);
listA.add(null);
// list ⊇ listA
System.out.println(listA); // [33.33, null]
Boolean bool = list.containsAll(listA); // list是否包含listA
System.out.println(bool); //true
boolean addAll(Collection<? extends E> c):将传入的集合中的所有元素添加到集合中
List list = new ArrayList();
list.add("A");
list.add(12);
list.add(null);
list.add(33.33);
System.out.println(list); //[A, 12, null, 33.33]
//列表中是否包含元素判断
List listA = new ArrayList();
listA.add(33.33);
listA.add(null);
list.addAll(listA);
System.out.println(list); // [A, 12, null, 33.33, 33.33, null]
boolean remove():删除传入的列表元素或删除传入索引指向的元素
List list = new ArrayList();
list.add("A");
list.add(12);
list.add(null);
list.add(33.33);
System.out.println(list); //[A, 12, null, 33.33]
//删除元素
list.add("A");
list.add("A");
//删除找到的第一个元素
System.out.println(list.remove("A")); //true
System.out.println(list);// [12, null, 33.33, A, A]
//删除下标所在的元素
//参数传入对象 删除这个对象 返回 boolean
//参数传下标 删除下标位置对象 返回删除对象
//传入数字只能用于删除下标
Object obj = list.remove(2);
System.out.println(list); // [12, null, A, A]
System.out.println(obj); //33.33
//想要删除基本数据类型需要先装箱
Boolean bool = list.remove((Integer)12);
System.out.println(list); //[null, A, A]
System.out.println(bool); //true
Iterator iterator():返回一个迭代器对象
List list = new ArrayList();
list.add("A");
list.add(12);
list.add(null);
list.add(33.33);
System.out.println(list); //[A, 12, null, 33.33]
//遍历元素
list.add("A");
list.add("A");
for(int i = 0; i < list.size(); i++){
System.out.print(list.get(i) + " ");
}
System.out.println();
for(Object item : list){
System.out.print(item + " ");
}
System.out.println();
Iterator it = list.iterator(); //获取了一个迭代器
while(it.hasNext()){
System.out.println(it.next());
}
在Java中,for-each循环(也称为增强型for循环)不支持在迭代过程中直接修改底层集合的元素(除非是通过集合元素的内部可变状态),因为它不提供对元素索引的访问。
List listA = new ArrayList();
listA.add("A");
listA.add(12);
listA.add(null);
listA.add(33.33);
List list = new ArrayList();
list.add("A");
list.add(12);
list.add(null);
list.add(33.33);
list.add(listA);
System.out.println(list); //[A, 12, null, 33.33, [A, 12, null, 33.33]]
//在Java中,for-each循环(也称为增强型for循环)不支持在迭代过程中直接修改底层集合的元素(除非是通过集合元素的内部可变状态),因为它不提供对元素索引的访问。
for(Object t : list) {
//类型判断
if (t instanceof Double) {
if ((Double) t == 33.33) {
//此时t的指向由list中的33.33变为指向12.222,所以列表中的33.33并没有改变
t = 12.222;
}
}
if(t instanceof List){
List objs = (List)t;
//t指向的是列表中的列表,改变了它指向的列表中的值,但它的指向的列表的内存地址没变
objs.set(1,"abc");
}
}
System.out.println(list); //[A, 12, null, 33.33, [A, abc, null, 33.33]]
来看一下ArrayList的扩容方式
先看ArrayList中的add方法
public boolean add(E e) {
//与AbstractStringBuilder中的方法名一样,作用也是类似的,确保容量够用
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
再看ensureCapacityInternal方法,先通过calculateCapacity得出需要的容量为多大,再通过ensureExplicitCapacity来判断是否需要扩容
private void ensureCapacityInternal(int minCapacity) {
//通过calculateCapacity方法确定需要的容量是多少,再通过ensureExplicitCapacity来判断是否需要扩容
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
初始容量大小为空,如果需要的容量小于等于ArrayList中给出的默认容量(DEFAULT_CAPACITY),则容量大小就设置为默认容量(大小为10),如果大于默认容量则设置为需要的容量(size + 1),如此就确认了需要的容量大小
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//DEFAULT_CAPACITY值为10,最少给10的容量
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
来看ensureCapacityInternal中的ensureExplicitCapacity,来判断是否扩容,如果需要的容量大于当前数组的长度,则使用grow方法来扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果需要的容量大于当前数组的长度,则进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
在grow方法中先将容量扩容至原来的1.5倍,如果还是小于需要的容量则将容量设置为需要的容量,再与能够设置的最大容量值比较,如果大于最大容量再使用hugeCapacity()扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//扩容至原容量的1.5倍,原容量 + 原容量向右移一位
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 使用Arrays.copyOf扩容存储数据的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
hugeCapacity方法
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // 为负数则抛出异常
throw new OutOfMemoryError();
//Integer.MAX_VALUE为2^31-1, MAX_ARRAY_SIZE为2^31-9,多余的8是安全内存
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
ArrayList的扩容方式总的来说就是,默认初始容量 10
扩容时候,先扩容1.5倍(原容量 + 原容量向右移动一位),如果不够设置为需要的容量
2. 代码块
代码块也叫初始化块 属于类中的成员,类似于方法,将逻辑语句封装在方法体中,通过{ }连接起来。
简而言之,就是代码被放在一对大括号中
2.1 静态代码块
public class HomeWork {
//静态代码块
//static修饰,随着类加载而加载,只执行一次
//用于类加载时做一些初始化操作
static {
System.out.println("这是一个静态代码块");
}
public static void main(String[] args) {
//调用HomeWork类中的主函数方法时先加载
//第一次调用TestA时候第一次加载TestA类,执行一次静态代码块
new TestA();
//第二次就不会再执行静态代码块
new TestA();
//这是一个静态代码块
//这也是一个静态代码块
}
}
class TestA{
static {
System.out.println("这也是一个静态代码块");
}
}
2.2 成员代码块(实例代码块)
public class HomeWork {
public static void main(String[] args) {
new TestA();
new TestA();
//这也是一个静态代码块
//这是一个实例代码块
//这是一个实例代码块
//静态代码块在实例代码块前执行
}
}
class TestA{
//实例代码块
{
System.out.println("这是一个实例代码块");
}
static {
System.out.println("这也是一个静态代码块");
}
}
2.3 代码块和构造方法的执行顺序
块的执行顺序
1.父类静态代码块
2.子类静态代码块
3.父类的成员代码块
4.父类的构造构造方法
5.子类的成员代码块
6.子类的构造方法
public class EasyBlock {
static {
//静态代码块 一个类的静态代码块只会执行一次
//加载类对象时执行
System.out.println("这是一个静态代码块");
}
{
//成员代码块
System.out.println("这是成员代码块");
}
EasyBlock(){
System.out.println("构造方法");
}
public static void main(String[] args) {
//每次new都会执行成员代码块,成员代码块在构造方法前执行
new EasyChild();
//这是一个静态代码块
//这是子类的静态代码块
//这是成员代码块
//构造方法
//这是子类的成员代码块
//子类的构造方法
}
}
class EasyChild extends EasyBlock {
static {
//静态代码块
System.out.println("这是子类的静态代码块");
}
//成员代码块
{
System.out.println("这是子类的成员代码块");
}
EasyChild(){
System.out.println("子类的构造方法");
}
}
3. 内部类
汽车的发动机和汽车都可以看做是类,但是汽车的发动机类单独存在没什么意义,所以我们可以直接将其定义为内部类
class Car{
String carName;
int carAge;
String carColor;
class Engine{
String engineName;
int engineAge;
}
}
3.1 成员内部类
/*
* 成员内部类,属于外部类的成员
* 获取成员内部类的两种方式
* 方式一:
* 当成员内部类被private修饰时,外部类编写方法,对外提供内部类对象
* 方式二:
* 当成员内部类没有被private修饰时,直接创建对象
* 格式: outer.inner 对象名 = new outer(). new inner();
* */
方式一:
public class HomeWork {
public static void main(String[] args) {
Car car = new Car();
car.getEngine();
}
}
class Car{
String carName;
int carAge;
String carColor;
public Engine getEngine(){
return new Engine();
}
private class Engine{
String engineName;
int engineAge;
}
}
方式二:
public class HomeWork {
public static void main(String[] args) {
Car.Engine engine = new Car().new Engine();
}
}
class Car{
String carName;
int carAge;
String carColor;
class Engine{
String engineName;
int engineAge;
}
}
3.2 静态内部类
由static修饰的内部类,可以使用类名直接调用
public class HomeWork {
public static void main(String[] args) {
Car.Engine engine = new Car.Engine();
engine.engineName = "aa";
System.out.println(engine.engineName); //aa
}
}
class Car{
String carName;
int carAge;
String carColor;
//静态内部类
static class Engine{
String engineName;
int engineAge;
}
}
3.3 匿名内部类
在使用抽象类或接口的抽象方法时,我们可能只实现一个只在这里使用的方法,不想再写一个实体类,这个时候就可以使用匿名内部类来解决这个问题
class Car{
Interface1 interface1 = new Interface1() {
@Override
public void method() {
System.out.println("实现接口抽象方法");
}
};
Abstract anAbstract = new Abstract() {
@Override
public void method() {
System.out.println("实现抽象类的抽象方法");
}
};
String carName;
int carAge;
String carColor;
//静态内部类
static class Engine{
String engineName;
int engineAge;
}
}
@FunctionalInterface
interface Interface1{
void method();
}
abstract class Abstract{
public abstract void method();
}
lambda表达式
lambda表达式分为三部分,方法参数、lambda操作符和函数式接口方法的实现逻辑。
()里面参数的个数,根据函数式接口里面抽象方法的参数个数来决定。
当只有一个参数的时候,()可以省略
当expr逻辑非常简单的时候,{}和return可以省略
public class HomeWork{
public static void main(String[] args) {
TestClass testClass = new TestClass();
testClass.interface1.method();
int result = testClass.interface2.method(3,4);
Interface1 i1 = TestClass::fun1;
Interface2 i2 = TestClass::fun2;
i1.method();
i2.method(5,5);
}
}
class TestClass{
Interface1 interface1 = () -> {
System.out.println();
};
Interface2 interface2 = (int a, int b) -> {
return a + b;
};
public static void fun1(){
}
public static int fun2(int a,int b){
return 1;
}
int result = interface2.method(3,4);
}
@FunctionalInterface
interface Interface1{
void method();
}
interface Interface2{
int method(int a,int b);
}
abstract class Abstract{
public abstract void method();
}
3.4 局部内部类
定义在方法中的类,只在方法中使用
//局部内部类
public void inner(){
//只在方法中使用
class InnerC{
}
new InnerC();
}
() -> {
System.out.println();
};
Interface2 interface2 = (int a, int b) -> {
return a + b;
};
public static void fun1(){
}
public static int fun2(int a,int b){
return 1;
}
int result = interface2.method(3,4);
}
@FunctionalInterface
interface Interface1{
void method();
}
interface Interface2{
int method(int a,int b);
}
abstract class Abstract{
public abstract void method();
}
### 3.4 局部内部类
定义在方法中的类,只在方法中使用
```java
//局部内部类
public void inner(){
//只在方法中使用
class InnerC{
}
new InnerC();
}