十四、面向对象
万物皆对象,客观存在的事物皆是对象
1、类的实例化 一个简单的类
public class Dog { /** * 尺寸 */ int size; /** * 颜色 */ String coulour; /** * 爱吃啥 */ public static void eat() { System.out.println("爱吃"); } } 实例化这个类
public class DogTest { @Test public void test01() { //实例化Dog这个类 //dog 是 Dog类实例化的对象 Dog dog = new Dog(); }
@Test public void test02() { //实例化Dog这个类 //dog01和dog02 是 Dog类实例化的对象 //但是两个对象是不同的 Dog dog01 = new Dog(); Dog dog02 = new Dog(); //输出false System.out.println(dog01 == dog02); }
} 2、成员变量与局部变量 成员变量 在类中定义,用来描述对象将来要有什么 在类中定义,在方法中临时保存 没有赋值,为初始值 与局部变量的区别 a)作用域不同 -局部变量的作用域仅限于定义它的方法 -成员变量的作用域在整个类内部都是可见的 b)初始值不同 -成员变量有默认的初始值 -局部变量没有默认的初始值,必须自行设定初始值 c)同名变量 -在同一个方法中不允许有同名的局部变量 -在不同的方法中可以有同名的而局部变量 d)存储位置不同 -成员变量在对象创建后存储在堆中,对象回收后消失 -局部变量是在方法被调用时存在于栈中,方法调用结束,从栈中消失 e)生命周期不同 -随着对象的创建而创建,对象回收后消失 -随着方法的调用而创建,随着方法执行结束而消失
静态成员变量实现了多个方法之间数值的共享
public int x; //静态成员变量实现了多个方法之间数值的共享 public static int y; public void fun01() { System.out.println("非静态x" + ++x); System.out.println("静态y" + ++y); } @Test public void test01() { Person03 person01 = new Person03() ; Person03 person02 = new Person03() ; //x-1 //y-1 person01.fun01(); //x-1 //y-2 person02.fun01(); //x-2 //y-3 person01.fun01(); //x-2 //y-4 person02.fun01(); }
3、方法的调用 /** * 静态方法 */ public static void sleep() { System.out.println("我在睡觉"); } public static void eat() { System.out.println("吃薯片"); } public void drink() { System.out.println("喝可乐"); } public class Person01Test { @Test public void test01() { //静态方法可以用类名.方法名的方式调用 //但是不能通过该方式调用非静态方法 Person01.sleep(); }
@Test public void test02() { //静态方法可以用类名.方法名的方式调用 //但是不能通过该方式调用非静态方法 Person01.sleep(); //可以通过类的实例化对象.方法名的方式调用静态方法 //也可以通过类的实例化对象.方法名的方式调用非静态方法 Person01 person01 = new Person01(); person01.drink(); person01.eat(); //但没必要,一般通过类名.方法调用静态方法 }
} 4、匿名对象与引用对象 匿名对象
没有指向,用完之后立刻消失
引用对象
person04 引用类型变量 引用了内存中的一个对象
可能会多次调用,不会立即删除
三种变量的区别
普通变量 直接赋值 引用对象 引用类型变量 引用了内存中的一个对象 引用了一个内存地址 匿名对象 没有引用类型变量指向的对象 -优点 使用之后直接从内存中消失,不会长期占用内存,适用于仅仅偶尔使用的场景下 -缺点 因为使用之后直接消失,所以当需要频繁使用该对象时,会频繁的创建及销毁,创建和回收过程都需要占用系统资源 频繁使用的对象建议使用引用类型变量接受 一个简单的例子
public class Person04 { public void laugh() { System.out.println("仰天长笑"); } } @Test public void fun01() { /** * 引用对象 * person04 引用类型变量 引用了内存中的一个对象 * 可能会多次调用,不会立即删除 */ Person04 person01 = new Person04(); person01.laugh(); /** * 匿名对象 * 没有指向,用完之后立刻消失 */ new Person04().laugh(); } 5、对象的生成、赋值与读取 一个简单的类
public class Person01 { /** * 姓名 */ String nickname; /** * 性别,1→男 0→女 * 默认值为0,女 */ int gender; /** * 年龄 */ int age; } 生成它的对象
@Test /** * 生成对象 */ public void test01() { //实例化该类,生成对象 //类的指向 对象名 = new(初始化)实例化的类 Person01 person01 = new Person01(); Person01 person02; person02 = new Person01(); }
生成对象的赋值与读取
@Test /** * 生成对象的赋值与读取 */ public void test02() { Person01 person01 = new Person01(); Person01 person02 = new Person01(); //给生成对象赋值 person01.nickname ="张三"; person02.nickname ="李四"; System.out.println(person01.nickname); System.out.println(person02.nickname); //person01.gender = 0; 0是默认值,无需再次赋值 person02.gender = 1; System.out.println(person01.gender == 1 ? "男" : "女"); System.out.println(person02.gender == 1 ? "男" : "女"); person01.age = 20; person02.age = 22; System.out.println(person01.age); System.out.println(person02.age); }
6、访问修饰符 访问修饰符 public protected default private
public : 对所有类可见 使用对象:类、接口、变量、方法
protected : 对同一个包内的类和所有子类可见。使用对象:变量、方法。 注意:不能用来修饰类(外部类)
default(即默认,什么也不写,这个不能写) : 在同一个包内可见,不适用任何修饰符,使用对象:类、接口、变量、方法
private :只在同一个类内可见。使用对象:方法、变量。 注意:不能用来修饰类(外部类)
7、封装 良好的封装能够减少耦合。
类内部的结构可以自由修改。
可以对成员变量进行更精确的控制。
使用私有修饰符,隐藏信息,实现细节。
封装一个类
public class Person02 { /** * 姓名 */ private String nickname; /** * 性别,1→男 0→女 * 默认值为0,女 */ private int gender; /** * 年龄 */ private int age; //private修饰了变量,需要用的set和get来进行赋值和调用 //右键→source(Alt + Shift + S)→Generate getters and setters //开发工具自动生成get和set public void setAge(int a) { if(a<=0 || a>200) { System.out.println("年龄错误"); }else { age = a; } } public void setNickName(String a) { //注意使用String nickname参数列表(即使用原变量名)时 //不能直接nickname = nickname; //因为都是成员变量,没有赋值,此时实例化后对象参数都是默认值 //需要this.nickname=nickname; //this.调用成员变量 //this 方法的调用者谁调用我 我就是谁 nickname=a; } public void setGender(int a) { gender = a; } public int getGender() { return gender; } public int getAge() { return age; } public String getNickName() { return nickname; } } 调用该封装好的类
public class Person02Test { @Test /** * 生成对象 */ public void test01() { //实例化 Person02 person01 = new Person02(); //赋值 person01.setNickName("法外狂徒张三"); person01.setGender(1); person01.setAge(20); //读取 System.out.println("姓名是:" + person01.getNickName()); System.out.println("性别是:" + (person01.getGender()== 1 ? "男" : "女")); System.out.println("年龄是:" + person01.getAge()); } } 8、构造方法 方法名与类名相同,方法体没有返回值,但是在声明的时候却不需要使用void修饰的方法称之为构造方法
作用:用于初始化对象,对象的属性赋值
没有参数的方法称之为无参构造方法
代码中没有编写过构造方法,jvm在执行对象初始化的帮助程序追加无参构造方法
一个类中,既没有无参构造方法也没有有参构造方法的时候jvm会追加一个无参构造方法
一个类中,存在有参构造方法没有编写无参构造方法的时候jvm不会追加无参构造方法
不论任何情况下一定要手写一个无参构造方法
在类的实例化过程中被调用,实例化几次调用几次
无参构造方法
public class Person01 { public Person01() { } } 全参构造方法
public class Person02 { //昵称 private String nickname; //性别 1男0女 private int gender; //年龄 private int age; public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public int getGender() { return gender; } public void setGender(int gender) { this.gender = gender; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } /** * 全参构造方法 * @param nickname 昵称 * @param gender 性别 * @param age 年龄 */ //右键→source→Generate Constructor using Fields... //直接生成有参构造方法 public Person02(String nickname,int gender,int age) { this.nickname = nickname; this.gender = gender; this.age = age; } } 全参构造方法的调用
public class Person02Test { @Test public void test01() { //初始化对象 Person02 person01 = new Person02("法外狂徒张三",1,20); System.out.println("昵称是:" + person01.getNickname()); System.out.println("性别是:" + (person01.getGender()== 1 ? "男" : "女")); System.out.println("年龄是:" + person01.getAge()); } } 构造方法调用时机
public class Person02Test { @Test public void test01() { //没有无参构造方法 //因为有了有参构造方法,所以jvm不会追加无参构造方法了 //因此无论任何情况下,一定要手写一个无参构造方法 //Person02 person01 = new Person02(); }
@Test public void test02() { Person02 person01 = new Person02("法外狂徒张三",1,20); }
}
lombox工具简化书写
//自动生成setter @Setter
//自动生成getter @Getter
//自动生成无参构造方法 @NoArgsConstructor
//自动生成全参构造方法 @AllArgsConstructor
9、构造代码块 花括号包裹起来的我们称之为代码块或代码段
只有一对花括号包裹起来的我们称之为构造代码块或代码段
构造代码块与构造方法一样都是在类被实例化的过程中被调用的,并且构造代码块在构造方法之前先执行
类每次被实例化的过程中都会调用构造代码块
{ System.out.println("构造代码块"); }
10、静态代码块 使用static修饰的构造代码块我们称之为静态代码块
当类加载的时候执行静态代码块而且的静态代码块仅执行一次不可重复执行
而构造代码块是在类的实例化过程中执行的
执行优先级:静态代码块>构造代码块>构造方法
static{ System.out.println("静态代码块"); }
11、this调用 public class Person08 { private final static int DefaultCapacity = 10; private int[] capacity;
public Person08() { //capacity = new int [DefaultCapacity]; //this在构造方法中调用本类的其他构造方法只能放在第一行,也就是说只能调用一个 //注意使用this调用构造方法的时候不允许相互调用造成死循环 this(new int[DefaultCapacity]); } public Person08(int[] capacity) { super(); this.capacity = capacity; } public int[] getCapacity() { return capacity; } public void setCapacity(int[] capacity) { this.capacity = capacity; }
} 12、包装类 jdk1.0提出了包装类的概念
包装类对象---->基本数据类型:拆箱
基本数据类型---->包装类对象:装箱
自动装拆箱jdk1.5
public class Wraper { public static void main(String[] args) { //八种数据类型对应的类 //Byte byte //Short short //Integer int //Long long //Double double //Float float //Character char //Boolean boolean Integer a = 10; int b = a; int c = 20; Integer d = c; Integer e = 127; Integer f = 127; Integer g = 128; Integer h = 128; int aa = 127; int bb = 127; int cc = 128; int dd = 128; //t,数据类型,相等 System.out.println(aa == bb); //t,数据类型,相等 System.out.println(cc == dd); //t,缓存在-128到127,没有超出缓存,不生成新的对象 System.out.println(e == f); //f,因为缓存超过了-128到127之间,生成了新的对象 System.out.println(g == h); System.out.println(dd == h); } }
十五、类的继承
1、继承 extends
写出一个父类
public class Person { /** * 昵称 */ private String nickname; /** * 性别,1→男 0→女 * 默认值为0,女 */ private int gender; /** * 年龄 */ private int age; public Person() { super();
} public Person(String nickname, int gender, int age) { super(); this.nickname = nickname; this.gender = gender; this.age = age; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public int getGender() { return gender; } public void setGender(int gender) { this.gender = gender; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }
} 写出一个继承父类的子类
//父类Person子类Male public class Male extends Person{
} 子类实例化
public class PersonTest { @Test public void test01() { Male male = new Male(); male.setNickname("张三"); male.setAge(20); male.setGender(1); System.out.println("昵称是:" + male.getNickname()); System.out.println("性别是:" + (male.getGender()== 1 ? "男" : "女")); System.out.println("年龄是:" + male.getAge()); } @Test public void test02() { Male male = new Male("张三",1,20); Female female = new Female(); System.out.println("昵称是:" + male.getNickname()); System.out.println("性别是:" + (male.getGender()== 1 ? "男" : "女")); System.out.println("年龄是:" + male.getAge());
}
}
2、继承后的实例化过程
父类
public class Person { public Person() { System.out.println("Person无参构造方法"); } } 子类
//父类Person子类Male
public class Male extends Person {
public Male() { System.out.println("Male无参构造方法"); }
} 实例化子类过程中先实例化父类
@Test public void test01() { //子类实例化过程中先实例化父类 Male male = new Male(); } //输出结果 //Person无参构造方法 //Male无参构造方法
3、子父类关系
父类
public class Person { public void eat() { System.out.println("吃"); } } 子类
//父类Person子类Male
public class Male extends Person { public void work() { System.out.println("work"); } } 子类可以调用父类的方法,也可以拥有独有的方法
@Test public void test01() { Male male = new Male(); //子类可以调用父类的方法 male.eat(); //子类可以拥有独有的方法 male.work(); } public Male(String nickname, int gender, int age) { //引用父类 //super调用父类的构造方法时只能放在第一行 super(nickname, gender, age); System.out.println("Male有参"); }
4、方法重写
当父类的方法满足不了子类的需求是,子类可以重写父类的方法
父类
public class Person { public void eat() { System.out.println("吃"); } } 子类
//父类Person子类Male
public class Male extends Person { //重写父类 /** * 方法的重写 * 当父类的方法满足不了子类的需求是,子类可以重写父类的方法 */ @Override public void eat() { System.out.println("吃核桃"); } } 子类可以调用父类的方法,若方法已经重写,则调用的是重写之后的方法
@Test public void test01() { Male male = new Male(); //子类可以调用父类的方法,若方法已经重写,则调用的是重写之后的方法 male.eat(); //输出吃核桃 }
final修饰的方法不能重写
父类
public class Person { public void eat() { System.out.println("eat SCP-377绝对准确幸运饼干"); } public final void drink() { System.out.println("drink SCP-207超人可乐"); } } 子类
public class Male extends Person { public void eat() { System.out.println("eat SCP-643巧克力糖"); } //Cannot override the final method from Person //final修饰的方法不能重写 //public void drink() {} } 重写与重载
/**
-
方法重载与方法重写
-
在同一个类中,参数列表不同的同名方法我们称之为方法重载
-
父类的方法满足不了子类需求,子类 重写父类的方法我们称之为方法重写
-
方法重载在同一个类中
-
方法重写必须存在子父类继承关系
-
@author 张世翔
-
上午11:31:43 2022年3月24日
-
@version 1.0.0 */ public class Inheritance { public static void main(String[] args) { Super s = new Sub(); Goo goo = new Goo(); goo.g(s); //输出结果 //重载看类型 //调用的是super的obj //g.(Super).. //重写看类 //实例化指向的是子类 //Sub.f() } } class Super { public void f() { System.out.println("Super.f()"); } } class Sub extends Super { @Override public void f() { System.out.println("Sub.f()"); } } class Goo { public void g(Super obj) { System.out.println("g.(Super).."); obj.f(); }
public void g(Sub obj) { System.out.println("g.(Sub).."); obj.f(); } } 输出 g.(Super).. Sub.f() 5、向上转型与向下转型 父类
public class Person { public void eat() { System.out.println("吃"); } } 子类
//父类Person子类Male
public class Male extends Person { @Override public void eat() { System.out.println("吃核桃"); } public void work() { System.out.println("work"); } } 向上转型,随便转
@Test public void test01() { //父类对象的引用指向了子类对象实例化的的对象 //类型是父类,指向的是子类对象 //调用的是子类的方法 //向上转型,随便转 Person male = new Male(); //父类有的,子类重写过了,使用的是子类 male.eat(); //吃核桃 //父类没有的子类的方法,无法调用,因为栈内存是父类,没有指向 //male.work(); } 向下转型,强制转换
@Test public void test02() { //子类不能指向父类对象 //向下转型,强制转换 //父对象=new 子类(); //子对象=(子类)父对象; //只能转换实例化对象,类不能改 Person person01 = new Male(); Male male = (Male) person01; //实例化的是父类对象 //但是强制转换成了子类对象 //父类有的,子类重写过了,使用的是子类的方法 person01.eat(); //吃核桃 male.eat(); //吃核桃 male.work(); //work }
十六、内部类与抽象类
1、内部类 调用内部类中的静态方法
public class Outer01 { public static class Inner01{ public static void run() { System.out.println("run"); } } } 直接调用
@Test //直接调用静态内部类中的静态方法 public void outer01() { Outer01.Inner01.run(); }
调用非静态内部类中的静态方法
public class Outer02 { public class Inner01{ public static void run() { System.out.println("run"); } } } 直接调用
@Test //调用非静态内部类中的静态方法,可以直接调用 public void outer02() { Outer02.Inner01.run(); }
调用非静态内部类中的非静态方法
public class Outer03 { public class Inner01{ public void run() { System.out.println("run"); } } } 先实例化
@Test //调用非静态内部类中的非静态方法,需要先实例化 public void outer03() { //Outer03.Inner01.run(); new Outer03().new Inner01().run(); }
调用非静态内部类中的非静态方法
public class Outer04 { public class Inner01{ public void run() { System.out.println("run"); } } public void innerRun() { new Inner01().run(); } } 在外部类写一个非静态方法调用内部类,然后在实例化该方法
@Test //也可以通过在外部类写一个非静态方法调用内部类,然后在实例化该方法 public void outer04() { //Outer03.Inner01.run(); new Outer04().innerRun(); }
2、局部内部类 1>
public class Outer05 { // 局部内部类,静态 public static void innerRun() { class Inner01 { public static void run() { System.out.println("run"); } } Inner01.run(); } } 直接调用
@Test public void outer05() { Outer05.innerRun(); }
2>
public class Outer06 { //局部内部类,静态 public static void innerRun() { class Inner01{ public void run() { System.out.println("run"); } } new Inner01().run(); } } 直接调用
@Test public void outer06() { Outer06.innerRun(); }
3>
public class Outer07 { //局部内部类,非静态 public void innerRun() { class Inner01{ public void run() { System.out.println("run"); } } new Inner01().run(); } } 实例化
@Test public void outer07() { new Outer07().innerRun(); }
4>局部内部类匿名对象
public class Outer08 { // 局部内部类 public void innerRun() { class Inner01 { } new Inner01() { public void run() { System.out.println("run"); } }.run(); } } 实例化
@Test public void outer08() { new Outer08().innerRun(); }
3、匿名内部类 public class Outer09 { public void innerRun() { //Object() new Object() { public void run() { System.out.println("run"); } }.run(); } } 4、抽象类 使用abstract修饰的类称之为抽象类
使用abstract修饰没有方法体的方法我们称之为抽象方法
具备抽象方法的类必须是抽象类
public abstract class Person { //具备抽象方法的类必须是抽象类 public abstract void eat() ; }
抽象类不一定要有抽象方法
public abstract class Person { // 抽象类不一定要有抽象方法 public void eat() { System.out.println("吃"); } } 5、抽象类的继承 子类继承抽象类
抽象父类
public abstract class Person { public abstract void eat() ; } 继承-1>重写抽象父类中的抽象方法
public class Male extends Person { public void eat() { System.out.println("000"); } } 继承-2>子类也是抽象类
public abstract class Female extends Person { } 注意:使用final修饰的类不能被继承,不能同时使用abstract和final
类一
public final class Person { public final void drink() { System.out.println("drink SCP-207"); } } 类二(此时无法继承)
//使用final修饰的类不能被继承 //public class Male extends Person { public class Male { public void eat() { System.out.println("000"); } }
十七、接口
java接口的概念:
接口作为一种特殊的抽象类,指定一个类去做什么,而不是规定他如何去做。 1
接口的使用:
接口的使用与类的使用有些不同。在需要使用类的地方,会直接使用new关键字来构建一个类的实例,但接口不可以这样使用,因为接口不能直接使用 new 关键字来构建实例。 1
接口必须通过类来实现(implements)它的抽象方法,然后再实例化类。类实现接口的关键字为implements。
如果一个类不能实现该接口的所有抽象方法,那么这个类必须被定义为抽象方法。
不允许创建接口的实例,但允许定义接口类型的引用变量,该变量指向了实现接口的类的实例。
一个类只能继承一个父类,但却可以实现多个接口。
接口可以多继承。
接口是没有静态代码块或者构造方法的。
接口只能声明常量,而且是静态常量。
实现接口的格式如下:
修饰符 class 类名 extends 父类 implements 多个接口 { 实现方法 }
在 JDK1.8以后
// 在接口中使用default修饰的方法允许有方法体 // 在接口中使用default修饰的方法允许被实现类重写,使用实现类的实例化对象调用,执行的是实现类中重写的方法 // 在接口中使用default修饰的方法可以被子接口继承并且子接口可以重写继承的default修饰的方法 // 在接口中使用static修饰的方法允许有方法体 // 在接口中使用static修饰的方法允许有方法体,使用接口名点方法名调用,且不能被实现类重写 // 在接口中使用static修饰的方法不可以被子接口继承
接口的本质:
接口是抽象类的延伸,可以将它看做是纯粹的抽象类,接口中的所有方法都没有方法体。
在Java程序设计语言中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。继承是一个“是不是”的关系,接口实现则是能不能的关系。接口的本质是契约,标准,规范。
1.接口不是类,不能使用new运算符实例化一个接口
2.类可以实现多个接口,弥补Java单继承的局限性
3.接口和类是并列的两个结构
4.接口的方法都是抽象方法
接口的语法:
public interface pain{ void draw(); }
定义接口方法可省略public abstract 关键字
一个类继承一个父类的同事再实现一个接口,可以写成如下形式
public class Parallelogram extends Quardrangle implements Paintable{ }
接口的多继承:
InterfaceA
public interface InterfaceA { public abstract void methodA(); }
InterfaceB
public interface InterfaceB { public abstract void methodB(); }
InterfaceC
public interface InterfaceC extends InterfaceA,InterfaceB{ }
函数式接口:
@FunctionalInterface public interface Fa { void fun(); }
十八、Object类:
Java Object 类是所有类的父类,也就是说 Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法。
1、重写Object类里面的toString方法
代码如下:
public class Person01 { /** *定义一个名字 */ private String nickname; /** *定义一个年龄 */ private int age; /** *定义一个性别 */ private int gender; public Person01() { } public Person01(String nickname, int age, int gender) { this.nickname = nickname; this.age = age; this.gender = gender; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getGender() { return gender; } public void setGender(int gender) { this.gender = gender; } //重写toString方法 @Override public String toString() { return "Person02( nickname = " + this.nickname + ", age = " +this.age + ", gender = " + this.gender +" )"; } }
2、Object类的hashCode方法
什么是hashCode方法?
-
hashCode 表示对象在 hash 表中的位置,对于同一个对象来说,多次调用,返回相同的 hashCode。
-
如果 Object.equal () 相等,Object.hashCode () 也必然相等。重写时也建议保证此特性。
-
如果 Object.equal () 相等,这并不要求 Object.hashCode () 也返回不同值。如果真出现这种情况,最好优化代码,充分利用 hash 表的性能。
3、重写Object类中的equals方法
public class Person01 { /** *定义一个名字 */ private String nickname; /** *定义一个年龄 */ private int age; /** *定义一个性别 */ private int gender; public Person01() { } public Person01(String nickname, int age, int gender) { this.nickname = nickname; this.age = age; this.gender = gender; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getGender() { return gender; } public void setGender(int gender) { this.gender = gender; } @Override public boolean equals(Object obj) { // 传进来的比较的对象 Person05 person04 = (Person05) obj; if (this.age != person04.getAge()) { return false; } else if (this.gender != person04.getGender()) { return false; } else if (!Objects.equals(this.nickname, person04.getNickname())) { return false; } else { return true; } }
4、重写Object类中的hashCode方法
public class Person01 { /** *定义一个名字 */ private String nickname; /** *定义一个年龄 */ private int age; /** *定义一个性别 */ private int gender; public Person01() { } public Person01(String nickname, int age, int gender) { this.nickname = nickname; this.age = age; this.gender = gender; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getGender() { return gender; } public void setGender(int gender) { this.gender = gender; } @Override public int hashCode() { int reference = 31; return this.age * (this.gender + reference) + this.nickname.hashCode(); }
19、Java集合
大体框架
一、单例集合Collection中的List
1、初始ArrayList
以下代码证明
集合相比数组 是可以添加多个数据类型的元素。 而一个数组里面只能存在同一种数据类型的元素。
我们添加了十个元素 但其中有两个元素相同 都是a ,输出的集合大小依然为10 而非9,由此可见在ArrayList中存储元素的值是可以重复的。
public class ArrayListTest { @Test public void test01() { List list = new ArrayList(); byte a = 1; short b = 2; int c = 3; long d = 4L; double e = 5.0d; float f = 6.0f; char g = 'a'; boolean h = false; Person person = new Person(); list.add(a); list.add(b); list.add(c); list.add(d); list.add(e); list.add(f); list.add(g); list.add(h); list.add(person); list.add(a); System.out.println(list.size()); }
输出结果为10
2、依据下标 index获取对应位置上元素的值
@Test public void test01() { List list = new ArrayList(); list.add("XXX"); System.out.println(list.get(0)); // java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1 我们只存储了一个元素 所以报下标越界异常 // System.out.println(list.get(1)); }
输出结果为XXX
3、使用for循环遍历出ArrayList
@Test public void test01() { List list = new ArrayList(); for (int i = 0; i < 4; i++) { list.add("XXX" + i); } //输出集合数量为4 System.out.println(list.size()); for(int i = 0 ;i < list.size();i++) System.out.println(list.get(i)); }
输出结果为XXX0
XXX1
XXX2
XXX3
4、ArrayList插入数据
@Test public void test01() { List list = new ArrayList(); for (int i = 0; i < 4; i++) { list.add("XXX" + i); } System.out.println(list); //选择插入add 在下标位置2的位置上插入一个XXX list.add(2, "XXX"); System.out.println(list); }
输出结果为XXX0
XXX1
XXX
XXX2
XXX3
5、ArrayList删除数据
@Test public void test01() { List list = new ArrayList(); for (int i = 0; i < 4; i++) { list.add("XXX" + i); } System.out.println(list); list.remove(3); System.out.println(list); }
输出结果为XXX0
XXX1
XXX2
6、ArrayList修改数据
@Test public void test01() { List list = new ArrayList(); for (int i = 0; i < 4; i++) { list.add("XXX" + i); } System.out.println(list); list.set(2, "XXX"); System.out.println(list); }
输出结果为XXX0
XXX1
XXX
XXX3
7、使用foreach循环遍历ArrayList
@Test public void test01() { List list = new ArrayList(); for (int i = 0; i < 4; i++) { list.add("XXX" + i); } // Object 集合中元素的数据类型 // name 遍历后结合中每一个元素的变量 // list 被遍历的集合 for (Object name : list) { System.out.print(name + "\t"); } }
输出结果为XXX0 XXX1 XXX2 XXX3
8、简单理解泛型
@Test public void test01() { // 泛型 可以简单理解为参数的数据类型 <数据类型> List<String> list = new ArrayList(); for (int i = 0; i < 4; i++) { list.add("XXX" + i); } for (String name : list) { System.out.print(name + "\t"); }
输出结果为XXX0 XXX1 XXX2 XXX3
9、使用迭代器遍历ArrayList
迭代器遍历一遍后数据 不能重新迭代 相对安全 速度也快
@Test public void test01() { List<String> list = new ArrayList(); for (int i = 0; i < 4; i++) { list.add("XXX" + i); } // 获取该集合的迭代器 Iterator<String> it = list.listIterator(); while (it.hasNext()){ String next = it.next(); System.out.print(next + "\t"); } System.out.println(); System.out.println("+++++++++++++++++"); while (it.hasNext()){ String next = it.next(); System.out.print(next + "\t"); } System.out.println("+++++++++++++++++"); }
输出结果为XXX0 XXX1 XXX2 XXX3
10、ArrayList和LinkedList区别
ArrayList和LinkedList是 List 接口的两种不同实现,并且两者都不是线程安全的。
ArrayList 内部使用的动态数组来存储元素,LinkedList 内部使用的双向链表来存储元素,这也是 ArrayList 和 LinkedList 最本质的区别。
ArrayList 使用下标来访问和遍历元素的效率较高,插入删除效率低(适用于频繁访问和遍历的数据,不适用于频繁变动的大量数据)for循环遍历效率高
LinkedList使用指针来访问元素,插入和删除效率高,随机访问效率低(适用于数据量很大或者操作很频繁的数据)使用迭代器访问效率最高
二、单例集合Collection中的Set
1、set和list的区别
List:有序可重复 有序指的是按照新增的顺序存储 可重复指的是存储元素的值可以重复
Set:无序不可重复 无序指的不会按照新增的顺序存储 不可重复指的是存储元素的值不可以重复(真正的无序不可重复其实是hashSet 而linkedHashSet 是有序不可重复 但linkedHashSet是按照新增存储顺序 排序的)
@Test public void test01() { List list = new ArrayList(); byte a = 1; short b = 2; int c = 3; long d = 4L; double e = 5.0d; float f = 6.0f; char g = 'a'; boolean h = false; list.add(a); list.add(b); list.add(c); list.add(d); list.add(e); list.add(f); list.add(g); list.add(h); list.add(a); System.out.println(list); } @Test public void test02() { Set set = new HashSet(); byte a = 1; short b = 2; int c = 3; long d = 4L; double e = 5.0d; float f = 6.0f; char g = 'a'; boolean h = false; set.add(a); set.add(b); set.add(c); set.add(d); set.add(e); set.add(f); set.add(g); set.add(h); set.add(a); System.out.println(set); }
list输出结果[1, 2, 3, 4, 5.0, 6.0, a, false, 1]
set输出结果为[6.0, 1, a, 2, 3, 4, 5.0, false]
2、使用foreach循环遍历HashSet
@Test public void test01() { Set<String> set = new HashSet(); for (int i = 0; i < 4; i++) { set.add("XXX" + i); } System.out.println(set); for (String name : set) { System.out.println(name); } }
输出结果为[XXX1, XXX0, XXX3, XXX2] XXX1 XXX0 XXX3 XXX2
3、使用迭代器遍历HashSet
@Test public void test01() { Set<String> set = new HashSet(); for (int i = 0; i < 4; i++) { set.add("XXX" + i); } System.out.println(set); Iterator<String> it = set.iterator(); while (it.hasNext()){ String name = it.next(); System.out.println(name); } }
输出结果为[XXX1, XXX0, XXX3, XXX2] XXX1 XXX0 XXX3 XXX2
4、LinkedHashSet(有序不可重复)
@Test public void test01(){ Set set = new LinkedHashSet(); byte a = 1; short b = 2; int c = 3; long d = 4L; double e = 5.0d; float f = 6.0f; char g = 'a'; boolean h = false; set.add(a); set.add(b); set.add(c); set.add(d); set.add(e); set.add(f); set.add(g); set.add(h); set.add(a); System.out.println(set); }
输出结果为[1, 2, 3, 4, 5.0, 6.0, a, false]
三、双例集合Map
1、hashMap
(1)存值取值
@Test public void test01() { Map<String, String> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); System.out.println(map.get("key1")); System.out.println(map.get("key2")); System.out.println(map.get("key3")); }
输出结果为value1
value2
value3
(2)修改和删除value值
put 方法key如果不存在就是新增 存在就是修改🔺
Map 的key无序不可重复 Value可以重复🔺
@Test public void test01() { Map<String, String> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); System.out.println("key2 >>> " + map.get("key2")); map.put("key2", "v2"); System.out.println("key2 >>> " + map.get("key2")); map.remove("key2"); System.out.println("key2 >>> " + map.get("key2")); }
输出结果为key2 >>> value2 key2 >>> v2 key2 >>> null
(3)判断key是否存在 存在就删除
@Test public void test01() { Map<String, String> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); System.out.println("key2 >>> " + map.get("key2")); if (map.containsKey("key2")) { map.remove("key2"); System.out.println("删除key2"); } System.out.println("key2 >>> " + map.get("key2")); }
输出结果为key2 >>> value2 删除key2 key2 >>> null
(4)hashSet获取的值不存在的话 就返回默认值的小方法
@Test public void test01() { Map<String, String> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); System.out.println("key2 >>> " + map.getOrDefault("key2", "XXX")); System.out.println("key5 >>> " + map.getOrDefault("key5", "XXX")); }
输出结果为key2 >>>value2
key5 >>> XXX
(5)根据key遍历hashMap
@Test public void test01() { Map<String, String> map = new HashMap<>(); for (int i = 0; i < 10; i++) { map.put("key" + i, "value" + i); } System.out.println(map); }
输出结果为{key1=value1, key2=value2, key0=value0, key5=value5, key6=value6, key3=value3, key4=value4, key9=value9, key7=value7, key8=value8}
(6)用迭代器遍历hashMap
@Test public void test01() { Map<String, String> map = new HashMap<>(); for (int i = 0; i < 10; i++) { map.put("key" + i, "value" + i); } Set<String> keySet = map.keySet(); Iterator<String> it = keySet.iterator(); while (it.hasNext()) { String key = it.next(); System.out.println("key >>> " + key + "\tvalue >>> " + map.get(key)); } }
输出结果为key >>> key1 value >>> value1 key >>> key2 value >>> value2 key >>> key0 value >>> value0 key >>> key5 value >>> value5 key >>> key6 value >>> value6 key >>> key3 value >>> value3 key >>> key4 value >>> value4 key >>> key9 value >>> value9 key >>> key7 value >>> value7 key >>> key8 value >>> value8
(7)用迭代器遍历hashMap中的value值
@Test public void test01() { Map<String, String> map = new HashMap<>(); for (int i = 0; i < 10; i++) { map.put("key" + i, "value" + i); } Collection<String> values = map.values(); Iterator<String> it = values.iterator(); while (it.hasNext()) { String value = it.next(); System.out.println(value); } }
输出结果为value1 value2 value0 value5 value6 value3 value4 value9 value7 value8
(8)使用entrySet遍历hashMap
@Test public void test01() { Map<String, String> map = new HashMap<>(); for (int i = 0; i < 10; i++) { map.put("key" + i, "value" + i); } Set<Map.Entry<String, String>> entrySet = map.entrySet(); Iterator<Map.Entry<String, String>> it = entrySet.iterator(); while (it.hasNext()){ Map.Entry<String, String> entry = it.next(); System.out.println("key >>> " + entry.getKey() + "\tvalue >>> " + entry.getValue()); } }
输出结果为key >>> key1 value >>> value1 key >>> key2 value >>> value2 key >>> key0 value >>> value0 key >>> key5 value >>> value5 key >>> key6 value >>> value6 key >>> key3 value >>> value3 key >>> key4 value >>> value4 key >>> key9 value >>> value9 key >>> key7 value >>> value7 key >>> key8 value >>> value8
2、LinkedHashMap
相比HashMap 就是 有序 类似于hashSet和LinkedHashSet之间的区别🔺
@Test public void test01(){ Map<String, String> map = new LinkedHashMap(); for (int i = 0; i < 10; i++) { map.put("key" + i, "value" + i); } Set<Map.Entry<String, String>> entrySet = map.entrySet(); Iterator<Map.Entry<String, String>> it = entrySet.iterator(); while (it.hasNext()){ Map.Entry<String, String> entry = it.next(); System.out.println("key >>> " + entry.getKey() + "\tvalue >>> " + entry.getValue()); } }
输出结果为key >>> key0 value >>> value0 key >>> key1 value >>> value1 key >>> key2 value >>> value2 key >>> key3 value >>> value3 key >>> key4 value >>> value4 key >>> key5 value >>> value5 key >>> key6 value >>> value6 key >>> key7 value >>> value7 key >>> key8 value >>> value8 key >>> key9 value >>> value9
3、ConcurrentHashMap和HashTable
都是线程安全的 但是现在HashTable用的少了 建议使用ConcurrentHashMap。