------------恢复内容开始------------
# java高级应用1.补充
当方法中不存在与对象相关的方法时 比如 直接的数字计算 输出 等 都可以写成静态方法 集合成一个工具类
1.类变量与类方法(静态变量 / 静态方法)
也就是记录变量和方法的使用次数 , 每次随着类的生成而生成 随着类的消失而消失?
static 和 final ? 静态变量 (打点计时器) final 定义常量.
类变量 又叫静态属性, 是该类所有的对象共享的变量,任何一个该类的对象请访问它时,取出来的值都是一样的,同样 任何变量去修改时,也是修改的同一个值.
特点:-
根据版本的不同
-
static 在java7以前 在方法区的静态域中
-
static 在java7及以后在堆中 随着类的生成 生成一个class对象,
内存区域:
- 不管static在哪里 静态变量在同类中所有对象共享
- 在类加载的时候就生成了.
语法:
访问修饰符 static 数据类型 变量名:[推荐]
static 访问修饰符 数据类型 变量名:
访问方式:
类名.类变量名 [推荐]
对象名.类变量名
注意: 访问修饰符无异
类变量细节问题
1.使用时, 匿名对象也可以 , 用于统计同一变量时
2.类变量该类全部对象共享, 实例变量是对象独享
3.必须加上static. 别称:如下
4.访问方式, 类名.类变量名, 对象名.类变量名
5.实例变量不能类名.类变量名访问.
6.类加载时就初始化了.
7,其生命周期随类的加载开始而生成, 随类的消亡而销毁.
package static_;
/**
* @Auther: qianl
* @Date: 2022/11/2 18:42
* @Description:
*/
public class VisitStatic {
public static void main(String[] args) {
//类名.类变量名
//随着类的加载而创建,即使没有实例化也可以访问.
System.out.println(Student.fee);
//0.0
//匿名对象,记录在构造器中 一样有用.
new Student("1",13);
new Student("2",13);
new Student("3",13);
System.out.println(Student.fee);
//39.0
}
}
class Student{
private String name;
public static double fee;
public Student(String name, double fee) {
this.name = name;
this.fee += fee;
}
}
类方法问题:
使用情况:
当需要不创建实例, 也可以调用方法(当工具类使用)
或者一些通用的方法 ,就可以直接设计成静态方法,注意就不用创建对象就能使用了. 比如 打印数组,冒泡排序等.
创建自己的工具类时.
这时将方法写成静态方法.
1,方法修饰static 变成静态方法
2.静态方法就可以访问静态属性/变量
3.不能使用this引用
4.对象可以使用 但idea里无法"点出"
package static_;
/**
* @Auther: qianl
* @Date: 2022/11/2 18:42
* @Description:
*/
public class VisitStatic {
public static void main(String[] args) {
Student.payFee(100);
Student.getFee();
Student student = new Student("2", 100);
student.payFee(100);
student.getFee();
}
}
class Student{
private String name;
private static double fee;
public Student(String name, double fee) {
this.name = name;
this.fee += fee;
}
//1,方法修饰static 变成静态方法
//2.静态方法就可以访问静态属性/变量
public static void payFee(double fee){
//this.fee += fee; 静态变量不能使用this引出.
Student.fee += fee;
}
public static void getFee(){
System.out.println(Student.fee);
}
}
总结细节
1.类方法和普通方法 都是随着类的架子啊而加载的, 将结构信息存储在方法区.
2.类方法中无法使用this参数.普通方法中隐含this参数
3.类方法可以通过类名调用,也能通过对象名调用.
4.普通方法 和对象有关.需要通过对象名调用, 比如对象名.方法名(参数).不能通过类名调用.
5.类方法不能使用 this 和 super 关键字 静态方法 只能访问静态的方法和静态的变量.
(静态方法只能访问静态成员)
6.普通成员方法 可以访问 非静态成员 ,也能访问 静态成员
练习:
输出结果:
1.运行类中的静态方法 : 输出 count = 9 (后++ 类中 count = 10)
2.再次运行静态方法 输出 count = 10 (后++ 类中 count = 11)
3.打印此时的count值 为11
1: 看有没有错误 , 修改 并输出
问题1 getTotalPerson方法中 使用了非静态属性 id
修改id为静态..
然后 id+1 =1; 输出 total =0;
生成一个对象,
走构造器 total = 0 total+1;
id = 1;
再输出 id = 1+1;
total =1; 输出 1
0 1
第三题:
1: 看有没有错误 , 修改 并输出total
问题1 静态方法内 不能使用this. 注销.
第一句: total = 3;
第二句: total = 3+1
id = total; id =4 total=4
练习小结:
- 静态方法只能访问静态成员,
- 非静态方法,可以访问所有成员
- 编写代码时,依然遵循访问权限规则
2.main方法 static(静态)
main方法语言
- main方法是虚拟机调用的
- java虚拟机调用类的main()方法 必须是public
- java在运行main()方法时 不必创建对象,所以必须是static
- 该方法接受String类型的数组参数, 数组中保存了执行java命令时给所允许的类的参数.(类的参数)
- java执行的程序 参数1 参数2 参数3
- main() 方法具有一个字符串数组参数,用来接收执行 Java 程序的命令行参数。命令行参数作为字符串,按照顺序依次对应字符串数组中的元素。
- 一个类只能有一个main方法.
//main方法 可以接受一个字符串数组 但是没有返回值
public static void mian(String[] args){
}
D:\myJava>java TestMain apple banana
一共有 2 个参数
apple
banana
main() 方法可以以字符串的形式接收命令行参数,然后在方法体内进行处理。
IDEA传入main方法 String参
和普通类的区别:
1.可以直接调用main所在类的静态方法或静态属性.(相同)
2.不能直接访问非静态 (相同 ) 必须创建一个实例对象后, 才能去访问类中的非静态方法(相同) 其他类里的静态方法也可以.
3.代码块
代码化块 又称初始化块 属于类中的成员 [类的一部分 ]
类似于方法, 将逻辑语句封装在方法体内 使用{} 包围
和方法不同, 没有方法名, 没有返回值 没有参数 只有方法体. 不能通过显性调用, 只能在加载类时, 和创建对象时 隐形调用.
基本结构:
[修饰符]{
代码
};
1.修饰符 就是默认 作用范围: 本类 本包
2.能写修饰符 只能写static 称为静态代码块
3.逻辑语句无要求,可以是任何逻辑语句(输入/输出/方法调用/循环/判断)
- ;可以写 可以省略.
代码块
1.相当于另一种形式的 构造器, (对构造器的补充) 可以做初始化的操作.
2.如果多个构造器中重复的语句,就可以抽取到初始化代码块中,提高代码重用性.
package static_;
/**
* @Auther: qianl
* @Date: 2022/11/3 02:49
* @Description:
*/
public class CodeBlock01 {
public static void main(String[] args) {
Movie movie = new Movie();
//名字hhn
}
}
class Movie{
private String name;
{
name = "hhn";
System.out.println("名字"+name);
}
}
代码块细节:
理解: 普通代码块是构造器的补充
静态代码块 是类的初始化的补充
-
代码块语句 在生成对象的时候 随着 类的加载而执行.并且没创建一个对象就执行一次
-
随着类的加载 静态代码块 只会执行一次.
-
类加载的情况
- 创建对象实例 new
- 创建在子类对象实例,父类被加载时
- 使用类的静态成员时(静态属性和静态方法.)
-
普通代码块,创建对象实例,会被隐形的调用. 被创建一次就会被调用一次.
-
普通代码块 如果只是使用类的静态成员时, 不会执行.
1.使用静态成员 时 会调用静态代码块 不会使用普通代码块
2.new / 子类加载父类 都会调用普通代码块(一个对象一次). 静态代码块(一次)
静态代码块从类生成以后 只会被调用一次 后续多少个对象 静态方法的引用都不会在调用,
也就是 静态代码块 默认 public static int count = 1; 只有一次共用使用机会
package static_;
/**
* @Auther: qianl
* @Date: 2022/11/3 02:49
* @Description:
*/
public class CodeBlock01 {
public static void main(String[] args) {
Movie movie01 = new Movie();
Movie movie02 = new Movie();
//名字hhn
System.out.println(Movie.i);
System.out.println(movie01.i);
Movie.love();
movie02.love();
/*我是静态代码块
我是普通代码块
我是普通代码块
0
0
静态方法
静态方法*/
}
}
class Movie{
private String name;
public static int i=0;
static{
System.out.println("我是静态代码块");
}
{
name = "hhn";
System.out.println("我是普通代码块");
}
public Movie(){
}
public Movie(String name) {
this.name = name;
}
public static void love(){
System.out.println("静态方法");
}
}
执行顺序:
static优先级比普通成员 优先级高
同static成员(属性,代码块,方法) 按顺序来
例子: 优先加载属性, 任何是静态代码块. (按顺序)
static比普通高一级
加入普通代码块和普通属性.
同普通成员 优先级一致 按 "顺序"**执行
在static代码块 后被执行.
加入构造器
构造器最后才被使用
在所有代码块后执行.
代码块的最前面 隐含了 super() 和调用普通代码块.
第一: super必须在第一行. 调用父类!
然后调用普通代码块
输出顺序:
先加载父类 与子类的静态成员(顺序)
父类代码块
父类构造器
子类代码块
子类构造器
注意!!!!
静态代码块和静态属性总是被优先执行的!
比父类的普通代码块都优先.
- 先类加载 然后再进行生成对象(调用构造器)
package static_;
/**
* @Auther: qianl
* @Date: 2022/11/3 13:05
* @Description:
*/
public class CodeBlockDetail {
//测试代码块 构造器 初始属性 在子类与父类的调用顺序
public static void main(String[] args) {
//先分析一波
//优先级, 静态比非静态高 同优先级按顺序进行.
B b = new B();
//1.新建该类 先加载B()类信息 然后发现继承A() 加载A()类信息
//2.加载A()中的静态属性 静态代码块(顺序进行)
//3.加载B()中的静态属性 静态代码块 理由: 类的静态成员随着类生成而生成
//开始走构造器 super();
//4.然后加载父类普通代码块
//5.加载父类构造器 理由: 先进行了父类的初始化(代码块是构造器的补充)
// 代码块随着构造器而产生 先代码块 后构造器
//6.super()结束 开始子类构造器 子类代码块
//7.子类构造器
/*这是父类静态属性
这是父类的静态代码块
这是子类静态属性
这是子类的静态代码块
这是父类普通属性
这是父类的普通代码块
这是父类的空构造器
这是子类普通属性
这是子类的普通代码块
这是子类的空构造器
*/
}
}
class A{
public static int aStaticInt=aShowStatic();
public int aInt=aShow();
static {
System.out.println("这是父类的静态代码块");
}
{
System.out.println("这是父类的普通代码块");
}
public A() {
System.out.println("这是父类的空构造器");
}
public static int aShowStatic(){
System.out.println("这是父类静态属性");
return 100;
}
public static int aShow(){
System.out.println("这是父类普通属性");
return 100;
}
}
class B extends A{
public static int bStaticInt=bShowStatic();
public int bInt=bShow();
static {
System.out.println("这是子类的静态代码块");
}
{
System.out.println("这是子类的普通代码块");
}
public B() {
System.out.println("这是子类的空构造器");
}
public static int bShowStatic(){
System.out.println("这是子类静态属性");
return 100;
}
public static int bShow(){
System.out.println("这是子类普通属性");
return 100;
}
}
注意:
静态代码块只能调用静态成员. 普通代码块可以调用任意成员 静态代码块类似于静态方法.
练习:
以下代码输出什么:
主方法 调用了静态属性
加载类 加载静态成员 然后同时触发了静态代码块 输出: in static block!;
第一句话 时 total = 100;
第二句话 时 total = 100;
第二题:
看输出什么
主方法: 创建了Test() 的无参构造器;
第一步 Test静态资源: 输出: 静态成员sam初始化;
Test静态资源: 静态代码块 输出: static块执行;
第二步: 加载普通代码块 和普通属性的初始化 所以 sam1 初始化 输出:
sam1成语初始化
第二步: 加载Test构造器: Test默认构造器被调用.
4.单例(单个实例)设计模式
1.静态方法和静态属性的经典使用
2.设计模式是在大量实践中总结和理论化之后选优的代码结构\编程风格\以及结局问题的思考方式
经典棋谱\ 不同的棋局\使用对应的不同棋盘 而且有时候还会使用多种棋盘解决同一种事情.
重点: 对某个类只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法 对某个类操作
饿汉式: 实例在类加载时就被创建。
1)构造器初始化 (防止直接new对象)
2)类的内部创建对象
3)向外暴露一个静态的公共方法. getlnstance
4)代码实现
package static_;
/**
* @Auther: qianl
* @Date: 2022/11/4 02:56
* @Description:
*/
public class SingleTonTest {
public static void main(String[] args) {
/* SingleTon01 s = SingleTon01.getInstance();
System.out.println(s);
SingleTon01 s1 = SingleTon01.getInstance();
System.out.println(s1);
System.out.println(s == s1);// == 引用类型 比较地址
//true
*/
//称为饿汉式的原因是 在类加载的时候 该对象作为静态资源也被加载了
//所以该对象无论是否被使用就先创建了再说
System.out.println(SingleTon01.i);
//先是类加载了 所以构造器也被使用了
//然后输出100
}
}
class SingleTon01 {
private String name;
public static int i =100;
private static SingleTon01 singleTon01
= new SingleTon01("小红");
private SingleTon01(String name) {
System.out.println("构造器被调用了");
this.name = name;
}
//返回类型是本类 方法名 获取实例
public static SingleTon01 getInstance() {
return singleTon01;
}
@Override
public String toString() {
return "SingleTon01{" +
"name='" + name + '\'' +
'}';
}
}
懒汉式:当类加载时 实例没有被创建,
package static_;
/**
* @Auther: qianl
* @Date: 2022/11/4 03:13
* @Description:
*/
public class SingleTon02 {
public static void main(String[] args) {
System.out.println(Cat.i);
//999
System.out.println(Cat.getInstance());
//构造器被调用 然后输出哈希值
/*999
构造器被调用了
static_.Cat@6d6f6e28*/
}
}
class Cat{
private String name;
public static int i=999;
private static Cat cat;
private Cat(String name) {
System.out.println("构造器被调用了");
this.name = name;
}
//当被使用到时 才会生成对象
public static Cat getInstance(){
if(cat ==null){
cat = new Cat("小可爱");
}
return cat;
}
}
饿汉式与懒汉式的异同:
相同点:
1,都是单例设计模式的实现
2.都是实例在编译过程中写死 而不被人创建
3.都只有一个public 的 getInstance()方法 获取实例(不是创建)
不同点:
1.饿汉式是类加载过程中 就直接创建了实例,而懒汉式 在调用getInstance()方法时才被创建实例
2.饿汉式存在资源浪费的可能 (类加载就创建了实例,只调用其静态属性时 就会浪费资源)
3.饿汉式不存在线程安全问题(类加载就生成实例 ) 而懒汉式存在线程安全问题( 运行时加载, 可能被其他调用该实例的线程抢先)
4.java.lang.Runtime 就说经典的单例模式
单例模式有八种方式.
Runtime源码:
这就是一个饿汉式 的单例实现方式
直接在静态属性(类属性)创建了随着类加载而生成的对象.
提供一个getRuntime()方法获取实例
其构造器是私有的.
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
5.final (最终的) 又叫常量 关键字
可以修饰类\属性\方法\局部变量
使用场景:
- 类不想被继承的时候, 使用final进行修饰
- 父类某方法不想被在子类覆盖重写(override)时,使用final进行方法修饰
- 当不希望某属性被修改时 使用final进行修饰 例如 派(Π) 根号2等常量的取值范围.
- 当不希望某个局部变量被修改, 可以使用final修饰 比如 计算工资时 某比例
命名格式:
全部大写 单词间使用_链接
private static final double THE_PI=3.14; // Π的设置在3.14
细节1:
-
又称常量 命名必须 XX_XX_XX
-
修饰属性时 定义时必须赋初值, 并且不能再修改. 赋值可以在如下任何位置
- 在类属性定义时
- 构造器中
- 代码块中
-
如果final修饰的属性是静态的,初始化的位置只能是:
- 定义时
- 静态代码块
- !!!不能在构造器中!!!
-
final类不能继承,但是可以实例化对象
-
如果类不是final类 但有final方法 则该类不能重写 但能继承.
package final_;
/**
* @Auther: qianl
* @Date: 2022/11/4 11:53
* @Description:
*/
public class FinalDetail {
}
class AA {
//定义时赋值
public final double TAX_RATE = 0.8;
public final double TAX_RATE2;
public final double TAX_RATE3;
//构造器赋值
public AA() {
TAX_RATE3 = 1.1;
// TAX_RATE6 = 100;
}
//代码块赋值
{
TAX_RATE2 = 8.8;
}
//静态常量 定义时赋值
public static final double TAX_RATE4 = 100;
public static final double TAX_RATE5;
// public static final double TAX_RATE6;
//在静态代码块赋值
static {
TAX_RATE5=1000;
}
}
final方法的继承 可以继承使用但不能重写
public class FinalDetail {
public static void main(String[] args) {
bb a1 = new bb();
a1.test();
//test
}
}
class aa{
public final void test(){
System.out.println("test");
}
}
class bb extends aa{
}
细节2:
-
final类的话 本类中不能将方法修饰写成final方法
-
final不能修饰构造器
-
final和static往往搭配使用, 效率更高 , 不会导致类加载 ,底层编译器进行了优化 节省了加载静态代码块的工作,也不会使得类进行加载. 测试在第二顺序时
public class FinalDetail { public static void main(String[] args) { System.out.println(Demo.i); //直接输出16 而不会加载静态代码块 //甚至不会生成比 i 应该更早生成的i1 这个静态常量 //所以 应该是 静态加常量的值 会被直接写入方法区的常量池 可以直接用类名进行调用 //而不会进行类的加载 } } class Demo{ public static final int i1 = test(); public static final int i = 16; static{ System.out.println("静态构造器被触发了"); } public static int test(){ System.out.println("这是第一个静态常量"); return 0; } }
-
包装类的(Integer,Double,Float,Boolean 等都是final) String也是final类.
因为是final类型 所以不能被继承 不能自己改写
课程练习:
1.计算圆面积 Circle
public class Circle {
public static void main(String[] args) {
Circle01 circle01 = new Circle01(200);
System.out.println(circle01.MathCircle());
}
}
class Circle01{
//半径
private double radius;
private final double PI = 3.14;
public Circle01(double radius) {
this.radius = radius;
}
public double MathCircle(){
return PI*radius*radius;
}
}
2.阅读程序:
final int X 定义属性 可以;
++X; 不允许修改值
X+1; 可以 这是新的int值
6.抽象类
基础:
- 标志: abstract 修饰类和方法 就称为抽象类 或者抽象方法
- 抽象方法必须在抽象类中
- 抽象方法的特点: 没有方法体 使用abstract修饰
- 抽象类的价值多用于设计,设计好后,让子类进行继承 并实现抽象类
- 在框架和设计模式中使用居多.
抽象类 父类的方法不确定性 .
比如 animal 一定有eat方法 但是不需要写具体的实现方法 而是完全交给父类去实现.
抽象类一般会被继承 由子类实现具体功能
abstract class Animal{
private String name;
public Animal(String name) {
this.name = name;
}
//父类方法不确定性 就是没有方法体
/*public void eat(){
}*/
//同时 类也需要写成抽象类
public abstract void eat();
}
抽象类细节:
- 抽象类 不能被实例化
- 抽象类不一定包含abstract方法.
- 一旦有抽象(abstract)方法 就一定要声明为抽象类
- abstract 只能修饰类和方法 不能修饰属性和其他
- 抽象类 还是一个类 可以有所以成员 比如 : 非抽象方法 属性 方法块 构造器等.
- 抽象方法里不能有方法体
- 继承抽象类 要么是子类变成抽象类 或者 实现所有抽象方法 抽象类只能由抽象类继承,抽象方法强制实现
- 抽象方法不能使用private final 和static 关键字 因为子类 私有的(本类使用) final (最终的不能修改) static 是和重写无关的关键字
抽象类课堂练习
1.final 不能通过 fianl 不能继承 不能重写
2.static 不能通过 static 与方法重写无关
3.private 不能通过 权限问题
4, 就是子类继承父类 但是必须实现父类的方法.
7.模板设计模式(抽象类 抽象方法的案例)
package abstract_;
/**
* @Auther: qianl
* @Date: 2022/11/4 13:11
* @Description:
*/
public class Abstract02 {
public static void main(String[] args) {
new AA().jobTime();
//消耗时间15
new BB().jobTime();
//BB消耗的时间15
}
}
abstract class TestTemplate{
//计算某段代码的消耗时间
public void jobTime() {
//获取时间
long start = System.currentTimeMillis();
job();//动态绑定机制 子类调用就会绑定子类方法
long end = System.currentTimeMillis();
System.out.println("AA消耗的时间" + (end - start));
}
//抽象方法
public abstract void job();
}
class AA extends TestTemplate{
@Override
public void job() {
long num = 0;
for (int i = 1; i <= 100000000; i++) {
num += 1;
}
}
}
class BB extends TestTemplate{
@Override
public void job() {
long num = 0;
for (int i = 1; i <= 100000000; i++) {
num += 1;
}
}
}
8.接口
抽象类和接口的区别
一 接口和抽象类的相似性
-
接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
-
接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
二 接口和抽象类的区别
-
(不能为普通方法提供方法体)接口里只能包含抽象方法,静态方法和默认方法(加default),不能为普通方法提供方法实现,抽象类则完全可以包含普通方法,接口中的普通方法默认为抽象方法。
-
(public static final 赋值)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的,并且必须赋值,否则通不过编译。
-
(是否有构造器)接口不能包含构造器,抽象类可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
-
(不能包含初始化块)接口里不能包含初始化块,但抽象类里完全可以包含初始化块。
-
(继承一个抽象类、多个接口)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
三 什么时候使用抽象类和接口
如果你拥有一些方法并且想让他们中的一些有默认实现,那么使用抽象类吧。
如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。
如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。
jdk1.8及以后, 接口也可以使用default修饰的方法为所有接口实现类统一增强功能,或者静态方法
//默认实现方法
default void dosm(){
//公有功能
}
//静态方法
public static demo(){
}
额外总结:
静态初始化块在类第一次加载时执行,且只执行一次。
非静态初始化块在构造器之前执行但前提是要调用构造方法。
静态初始化块》》非静态初始化块---构造器
非静态初始化块在构造器前面执行就想着不实例化也能执行....原来就算顺序是这样,也要初始化,不然无法执行初始化块。好坑的..(如标记11)
//非静态初始化块能初始化静态变量,也可以初始化实例变量。(顺序问题但是前提是得通过实例化来调用构造方法,内存中还没有这个实例变量,非静态变量依赖于对象存在。)
//静态初始化块可以初始化静态变量,但是不能初始化实例变量。和静态初始化块的加载时间有关
package java基本知识点复习;
public class 初始化块初始化静态变量 {
//非静态初始化块能初始化静态变量,也可以初始化实例变量。(顺序问题但是前提是得通过实例化来调用构造方法)
//静态初始化块可以初始化静态变量,但是不能初始化实例变量。和静态初始化块的加载时间有关。
static int i,j;
int k=0;
//非静态初始化
{
System.out.println("输出结果");
i=5;
System.out.println(i);
k=6;//内存中还没有这个k变量,非静态变量依赖于对象存在
System.out.println(k);
}
//静态初始化块
static{
j=6;
// k=0;
System.out.println("静态代码块");
}
public static void main(String[] args) {
System.out.println(i);
System.out.println(j);
//11 初始化块初始化静态变量 AA=new 初始化块初始化静态变量();
}
}
基本介绍:
接口就是给出一些没有实现的方法,封装到一起, 到某个类要使用的时候,在根据具体情况把这些方法写出来
语法:
interface 接口名{
//属性
//方法 不用写abstract抽象修饰词 默认是抽象方法
// 方法也可以写 (1.普通方法(抽象方法), 2. 默认实现方法 3. 静态方法)
// 默认实现方法 可以子类自动继承 子类调用 但不能重写
// 静态方法 使用类(接口).方法名 的方式使用
}
class 类名 implements 接口{
自己属性;
自己方法;
必须实现的接口的抽象方法
}
- 在jdk7.0前, 接口里的所有方法 都没有方法体,也就是都是抽象方法
- jdk8.0后接口类可以有静态(static)方法,默认(defult)方法,也就是说接口中可以有方法的具体实现
//接口
public interface AInterface {
//属性
public int n1 = 10;
//方法
public void hi();
//1.8以后 可以使用default关键字修饰
default public void ok(){
System.out.println("is OK..");
}
//静态方法
public static void cry(){
System.out.println("cry...");
}
}
//测试类
public class Interface {
public static void main(String[] args) {
new A().hi();
//可以使用接口的默认方法
new A().ok();
//静态方法需要静态调用 类.方法名
AInterface.cry();
}
}
//接口的实现类
class A implements AInterface{
@Override
public void hi() {
System.out.println("say hi");
}
}
使用场景:
1. 现在要制作战斗机\武装直升机 专家只需要把飞船需要的功能 或者规格 定下来 即可, 然后让人去具体实现即可. 规格或者范例
2. 一个项目经理 管理三个程序员 ,功能开发一个软件 为了控制和管理软件 项目经理 可以定义一些接口 ,然后由程序员去具体实现. (主要是规范方法名,方法属性,控制功能质量.)
3. 三个程序员,编写三个类 分别完成对Mysql Oracle DB2数据库的连接 donnect close...
也就是接口编程
DBInter 接口 去规定必须有connect 和close方法 这样至少连接数据库时的方法名是一致的 无论是连接哪种数据库
package abstract_;
//实现类
public class DBInterface {
public static void main(String[] args) {
MysqlDB mysqlDB = new MysqlDB();
UseDB useDB = new UseDB();
useDB.useDB(mysqlDB);
}
}
//具体实现
class MysqlDB implements DB{
@Override
public void connect() {
System.out.println("连接Mysql");
}
@Override
public void close() {
System.out.println("退出Mysql");
}
}
//调用
class UseDB{
public void useDB(DB db){
db.connect();
db.close();
}
}
//接口 public abstract都是默认的
interface DB{
void connect();
void close();
}
接口注意事项
1,) 接口不能实例化 instantiated (实例化)
2.) 接口中的所有方法是public方法 接口中抽象方法 可以不用abstract 修饰 public abstract都是默认的
3.) 一个普通类 实现 implement 接口 就必须实现接口的所有方法. 多个接口 就全部接口都要实现
4.)抽象类实现接口, 可以不用实现接口的方法.
在实现类的地方 指着红线 " alt + enter" 或者直接Alt+ins 找到 implement接口
5.)一个类同时可以实现多个接口
class A implement B,D,C { }
6.)接口中的属性 只能是 public static final 修饰符 比如 int A =1; 其实是 public static final int A = 1;也就是必须初始化.
7.)属性的访问方式: 接口名.属性名.
8.)一个接口不能继承其他的类,但是可以继承多个别的接口 单纯的抽象类 可以继承抽象类 也不能继承多个抽象类
接口的接口 是继承的关系
interface A extends B,C{}
9.) 接口的修饰符 只能是public 和默认 ,这点和类的修饰符是一样的.
interface 接口名 (默认) 或者 public interface 接口名 这个是
接口的多态特性:
1, 参数的多态 USB 接口 既可以接入 手机 也可以接入 电脑 ,就提点了接口的多态
也就是 接口引用 可以指向实例接口的类的对象. (一个方法 使用这个接口 进行引用 可以指向实现接口的类的方法.) 只要传入 A或者C 的对象 也就是实现B接口的类 就能实现各种 A或者C 的test方法
interface B{
void test;
}
class A implement B{
public void test(){
System.out.println("A的test实现")
}
}
class C implement B{
public void test(){
System.out.println("C的test实现")
}
class D {
public void (A a){
System.out.println(a.test);
}
}
2,多态数组:
也就是向下转型 这里默认是Usb 为接口 Phone 和 Camera 是实现了接口
案例: 给Usb数组中 存入Phone 和 相机Camera对象 Phone中有特有方法 call 遍历Usb数组 并实现特有方法
Usb usb = new Usb[2];
usb[0] = new Phone();
usb[1] = new Camera();
//遍历实现接口的固定方法 然后实现特有方法
for(int i = 0 ; i< usb.length; i++){
//都是动态绑定
usb[i].start();
usb[i].end();
//确定继承关系 并实现特有方法
if(usb[i] instanceof Phone){
((Phone)usb[i]).call();
}
}
3.多态传递 (和之前的多态一样 子类对象可以指向父类的引用)
多态传递则是
interface A{}
//B接口继承了A接口
interface B extends A{}
// test类 相当于 实现 A 与 B两个接口
class test implement B{}
interface A{}
class B implement A{}
class C implement A{}
main方法{
A a1 = new B();
A a2 = new C();
a1.test();
a2.test();
}
test (A a){
//这里就能传入 A类型 B类型 C类型的对象了 那么只要写一个接口的方法(父类的方法) 就能实现旗下所有子类的方法 (使用向下转型和判断 也能使用接口的 默认方法 静态方法)
}
多态传递则是
interface A{}
//B接口继承了A接口
interface B extends A{}
// test类 相当于 实现 A 与 B两个接口
class test implement B{}
接口练习:
1.程序判断题
B实现A接口
主方法创建B类
静态方法 不继承 通过类名 或者对象名可以点出.
int a = 23; 就是 public static final int a =13;
b.a √ 子类拥有父类属性与方法.
A.a √ 类名获取静态属性
B.a √ 子类拥有父类所有属性和方法
2.继承和接口
package abstract_;
/**
* @Auther: qianl
* @Date: 2022/11/5 18:25
* @Description:
*/
public class InterfaceAndExtends {
public static void main(String[] args) {
LittleMoney littleMoney = new LittleMoney("大圣");
littleMoney.kufu();
//大圣 会功夫
littleMoney.fly();
littleMoney.flying();
littleMoney.sim();
littleMoney.swimming();
/*
bird的默认飞行
小猴子实现bird飞的方法
重写小猴子的默认方法
大圣实现的游泳方法
*/
}
}
//猴子父类
class Money{
private String name;
public Money(String name) {
this.name = name;
}
public void kufu(){
System.out.println(name+" 会功夫");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
interface Fish{
//静态方法 不能重写
static void sim01(){
System.out.println("fish的默认方法");
}
//默认方法 可以重写!
default void sim(){
System.out.println("会游泳");
}
//游泳方法
void swimming();
}
interface bird{
default void fly(){
System.out.println("bird的默认飞行");
}
void flying();
}
//小猴子继承了猴子 但是不能继承和实现其他方法了
//子类继承父类 自动获得父类的功能 和接口的默认方法
//子类需要拓展功能 可以通过实现接口来拓展
// 接口的多继承(实现 ) 可以对java单继承继续一定的补充.
class LittleMoney extends Money implements Fish,bird{
public LittleMoney(String name) {
super(name);
}
//默认方法也能继承和重写
@Override
public void sim() {
System.out.println("重写小猴子的默认方法");
}
@Override
public void swimming() {
System.out.println(getName() + "实现的游泳方法");
}
@Override
public void flying() {
System.out.println("小猴子实现bird飞的方法");
}
}
3.接口多态练习
错误: 错误在pX()方法 其中的 x 不明确 应该写成 A.x 或者super.x
interface A01{
int x = 1;
}
class B01{
int x = 2;
}
class b extends B01 implements A01{
public void pX(){
System.out.println(super.x);
System.out.println(A01.x);
}
}
类定义的完善
抽象类是为了引出接口 归根结底 还是想方设法的 使用更高级的父类 来实现功能的扩展 低耦合 和课拓展.
类的五大成员: (1.)属性 (2.) 方法 (3)构造器 (4.) 代码块 (5.) 就是内部类
多态中: 父类引用指向子类对象的好处
HashMap 是 Map 接口的常用实现类(Map是一个接口),是一个键值对集合,
建议使用实现接口的方式使用Map,如:Map<String, Object> parameters = new HashMap<>();
不建议这样:HashMap<String, Object> parameters = new HashMap<>();
第一种更灵活,第二种直接写死了,如果换其他的Map实现类,需要重写 ,如果是用接口实现 (第一种)只需要替换后面的实现类即可。
父类引用指向子类对象的好处:
好处一:降低代码耦合度,让程序员关注业务的时候,更加关注父类能做什么,而不去关心若干个子类具体是怎么做的
如果我需要实现两个方法:喂猫和喂狗
feed(Cat cat){ ... }
feed(Dog dog){ ... }
如果Cat、Dog都是Animal的子类,那你就可以写成
feed(Animal animal){ ... }
Animal dog1 = new Dog(); //父类引用指向子类对象
Animal cat1 = new Cat();
feed(dog1);
feed(cat1);
有人说这是面向接口编程,可以降低程序的耦合性,即调用者不必关心调用的是哪个对象,只需要针对接口编程就可以了,被调用者对于调用者是完全透明的。
让你更关注父类能做什么,而不去关心子类是具体怎么做的,你可以随时替换一个子类,也就是随时替换一个具体实现,而不用修改其他。
好处二:增加代码的扩展性
比如最基本的一个方法 equals,他是Object类的一个方法,完整写法是 public boolean equals(Object obj)
注意这里的参数类型使用的是Object 而Object又是所有类的父类,所以你在调用这个方法的时候,这个参数可以传入Object的子类对象(即任意对象),所以这个方法适用于任何对象。
如果没有多态 这个方法就不能这么写,参数只能写一个具体的类,那么这个方法的适用范围就只是这一个类。。。
好处三:增加代码灵活性
假设有一个Animal的父类,和Dog、Cat子类
如果你在代码中,事先申请
private Animal an;
这样的好处是什么呢? an的实际类型在运行的时候才能具体确定,他可以是Dog Cat 或者Animal。因为你可以写:
an = new Animal();
也可以写
an = new Dog();
或
an = new Cat();
如果你一开始就申明了一个是Dog an; 那么你的an就只能是Dog 那么代码就不够灵活,以及的扩展的时候就不方便扩展。
9.内部类
四种内部类:
基本: 内部类就说 在类的内部嵌套另一个类的结构. 被嵌套的类称为内部类(inner class) 嵌套了其他类的类称为(outer class)
最大特点 可以直接访问私有属性 ( inner使用 outer 的私有(private)属性 )
基本语法:
class Outer{//外部类
class Inner{ //内部类
}
}
class Other{} //外部其他类
在局部位置上,比如(方法内/代码块中)
说明: 局部内部类是定义在外部类的局部位置,比如方法中,并且有类名
局部内部类(有类名) LocalinnerClass
- 可以直接访问外部类的所有成员,包括私有的. 因为内部类在外部类里面 private 本类内可以访问
- 不能添加访问修饰符,因为它的地位就说一个局部变量 不能使用访问修饰符 只能使用final修饰, 因为局部变量也可以使用final
- 作用域:仅仅在定义它的方法 或者代码块中.
- 局部内部类---访问--->外部类成员: 直接访问
- 外部类---访问---内部类成员: 创建对象 (依旧要在作用域内 也就是访问修饰符) 如果内部类的是private的属性 外部类不能访问 只能通过get set方法
- 外部其他类 不能访问局部内部类 (局部 也就是定义域内才能使用)
- 如果外部类和局部内部类的成员重名, 遵循就近原则, 可以使用 (外部类名.this.成员名) 去使用
- 单独的this.outer01 指的是 Inner02 的属性
public class Outer01{
private int outer01 = 10;
private void m2() {}
//局部内部类
public void m1() {
//局部内部类是定义在外部类的局部位置,通常在方法中
class Inner02 {
//可以直接访问外部类的所有成员,包含私有的
private int outer01 =100;
public void f1() {
//就近原则 属性名同名就近原则
System.out.println("局部内部类调用private属性" + outer01);
//单独的this.outer01 指的是 Inner02 的属性
System.out.println("调用外部类的私有属性" + Outer01.this.outer01);
m2(); //可以直接调用私有方法
new Inner01().Test(); //new 一个成员内部类
}
}
//可以直接在方法内 新建对象的方式 使用该类
Inner02 inner02 = new Inner02();
inner02.f1();
}
public void Test02() {
//new Inner02() 因为是在方法中写的内部类 所以必须在作用域内(m1方法中)使用.
}
}
匿名内部类(没有类名 重点!!!!!!) Anoneymous
- 本质是类
- 还是局部内部类(在方法或代码块中)
- 该类没有名字(系统中其实有名字 也就是class中)
- 通过debug 可以看到她的名字
- 同时还是一个对象
匿名内部类的名字
基本语法:
new 类或接口(参数列表){
类体
}
Anonymous 匿名的
匿名内部类(基于接口的)
//idea中没显示名字 而在文档资源管理器 中有对应的class文件.
匿名内部类的名字 是 外部类类名+ "$1" Outer$1 这样
//基于接口的匿名内部类
//1.需求: 想使用接口A,并创建对象
//2.传统方法, 写一个类,实现该接口,并创建对象
//3.我只想用一次,不想再次使用,这个类不想多次使用.
//4.匿名内部类 简化开发
//5.tiger编译类型 A() 运行类型 就是匿名内部类
//底层其实有名字的 叫Outer$1
//6.jvm底层在创建匿名内部类Outer$1,立刻创建了Outer$1实例,并把地址传给了tiger
//7.匿名内部类只会使用一次,就不再使用 类Outer$1就消失了 但是对象tiger能无限使用
package InnerClass_;
/**
* @Auther: qianl
* @Date: 2022/11/5 23:14
* @Description:
*/
public class Anonymous_ {
public static void main(String[] args) {
new Outer().method();
}
}
class Outer{//外部类
private int i1 = 10;//属性
public void method(){//方法
//基于接口的匿名内部类
//1.需求: 想使用接口A,并创建对象
//2.传统方法, 写一个类,实现该接口,并创建对象
//3.我只想用一次,不想再次使用,这个类不想多次使用.
//4.匿名内部类 简化开发
//5.tiger编译类型 A() 运行类型 就是匿名内部类
//底层其实有名字的 叫Outer$1
/*
class Outer$1 implement A{
@Override
public void cry() {
System.out.println("老虎叫...");
}
}
*/
A tiger =new A(){
@Override
public void cry() {
System.out.println("老虎叫...");
}
}; //是方法所以要分号
//idea中没显示名字 而在文档资源管理器 中有对应的class文件.
System.out.println("匿名内部类的运行类型"+getClass().getName());
//class InnerClass_.Outer
tiger.cry();
}
}
interface A{//接口
void cry();
}
class Father{//类
public Father(String name){//构造器
}
public void test(){//方法
}
}
匿名内部类(基于类的)
注意 基于类的 可以不实现方具体方法 就可以直接使用new Fater.test();了
//生成匿名内部类 Outer$2
//生成并返回给father 并且"father" 这个参数会传递给构造器
Father father =new Father("father"){
@Override
public void test() {
System.out.println("匿名内部类基于类重写test()");
}
};
father.test();
匿名内部类(基于抽象类)
必须实现 和接口一样
//抽象类的匿名构造器 Outer$3
abClass abclass =new abClass(){
@Override
void cry() {
System.out.println("抽象类匿名构造器重写方法cry()");
}
};
abclass.cry();
匿名内部类细节:
因为匿名内部类的语法比较奇特 它既有定义类的特征,也有创建对象的特征, 所以 匿名内部类有两种调用方法
编译类型 A() 运行类型 Outer$1 动态绑定, 运行类型是Outer$1
- 可以直接访问外部类的所有成员. 包括私有的
- 不能添加访问修饰符, 因为它的地位就说一个局部变量
- 作用域 :仅仅在定义它的方法或者代码块中
- 匿名内部类---访问---->外部类成员
- 外部其他类---不能访问--->匿名内部类(因为 匿名内部类地位是一个局部变量)
- 如果外部类和匿名内部类的成员重名时 , 匿名内部类访问的话, 默认遵循就近原则, 如果想访问外部类的成员, 则可以使用 ( 外部类名.this.成员 ) 去访问
//默认继承类 就是Outer$1 extends A{}
//1.第一种匿名内部类调用方法 直接调用
new A(){
@Override
public void cry(){
sout("hello~");
}
}.cry();
//2.第二种匿名内部类方法 创建一个新对象
A a = new A(){
@Override
public void cry(){
sout("hello~");
}
};
a.cry(); //动态绑定, 运行类型是Outer$1
匿名内部类的最佳实践:
当做实参直接传递, 简洁高效 方法中传入匿名内部类 直接实现接口 注意写法
interface AA{
public void cry();
}
main方法中:
public static void show(AA aa){
aa.cry();
}
//匿名内部类
show(new AA(){
public void cry(){
sout("AA cry")
}
});
//show方法中 传入一个匿名内部类
public class AnonymousTest_ {
public static void main(String[] args) {
//注意 AnonymousTest_$1 implement AA()
//调用方法 传入匿名内部类
show(new AA(){
public void cry(){
System.out.println("AA cry");
}
});
show(new AA(){
public void cry(){
System.out.println("另一个实现AA的cey方法");
}
});
//传统方法
show(new BB());
}
//静态方法 将传入的类 调用cry()方法 动态绑定 绑定匿名内部类的cry()方法
public static void show(AA aa){
aa.cry();
}
}
interface AA{
public void cry();
}
//(硬代码) 因为每次实现cry 都需要新建一个类 而且不好随意填加修改.
class BB implements AA{
@Override
public void cry() {
System.out.println("传统方法实现接口");
}
}
练习:
package InnerClass_;
import extend_.improve.Base;
/**
* @Auther: qianl
* @Date: 2022/11/7 18:13
* @Description:
*/
public class InnerClass02_ {
public static void main(String[] args) {
CellPhone cellPhone = new CellPhone();
//1.传递的是失恋了Bell的匿名内部类
//2.重写了ring
//3.方法中的 Bell bell = new Bell(){ 重写方法 };
cellPhone.alarmclock(new Bell() {
@Override
public void ring() {
System.out.println("懒猪起床了");
}
});
cellPhone.alarmclock(new Bell() {
@Override
public void ring() {
System.out.println("小伙伴上课了");
}
});
}
}
interface Bell{
void ring();
}
class CellPhone{
public void alarmclock(Bell bell){
bell.ring();
}
}
成员内部类 在外部类的成员位置上
写在外部类的成员位置上面, 可以直接访问外部类的所有成员.
外部类调用内部类的方法是 new对象
其他外部类 调用方法也是 new 对象 Outer().Inner inner = new Outer().Inner();
因为是成员 所以所有修饰符都可以使用public protected 默认 private
- 可以使用修饰符
- 在外部类的成员属性位置上,可以直接访问外部类的所有成员,包括private属性
- 作用域, 使用方式是new对象在调用方法.
非静态成员内部类(没有static)
package InnerClass_;
/**
* @Auther: qianl
* @Date: 2022/11/8 12:40
* @Description:
*/
public class MemberInnerClass_ {
public static void main(String[] args) {
OuterClass_ outerClass_ = new OuterClass_();
outerClass_.show();
//通过已有的外部类实例 新建一个内部类 成员内部类可以 点 出来
OuterClass_.InnerClass_ o_i01 = outerClass_.new InnerClass_();
o_i01.say();
//通过新建对象获取成员内部类 新建类的新建类
OuterClass_.InnerClass_ o_i = new OuterClass_().new InnerClass_();
o_i.say();
//通过方法获取成员内部类
OuterClass_.InnerClass_ outerClass_innerClass_ = outerClass_.getInnerClass_();
outerClass_innerClass_.say();
}
}
class OuterClass_{
private String name = "外部私有属性";
private void hi(){
System.out.println("hi");
}
public class InnerClass_{
private double i = 99.9;
private String name = "内部私有属性";
public void say(){
//如果重名 会就近原则 需要使用类名.this.属性来获取
System.out.println(OuterClass_.this.name + "内部私有属性: "+i);
hi();
}
}
public void show(){
System.out.println("show the InnerClass_().say()");
new InnerClass_().say();
}
//方法 返回成员内部类
public InnerClass_ getInnerClass_(){
return new InnerClass_();
}
}
静态成员内部类(有static)
//静态内部类
//1.放在成员变量的位置,
//2.使用static修饰
//3.可以直接访问外部类的静态成员,包括私有属性,但是不能访问非静态的
//4.可以添加修饰符(public protected 默认 private) 地位是一个成员
//5.访问方法 新建一个类
//6,如果存在同名属性 就近原则 要使用外部类的属性 因为是静态的 所以可以直接 类名.属性名
//注意一点细节:
静态内部类中不能创建静态属性
因为先加载外部类的静态属性 然后加载内部类 而如果加了静态属性 那就会和外部类一起创建 会报错
Inner classes cannot have static declarations
内部类不能声明静态属性
package InnerClass_;
/**
* @Auther: qianl
* @Date: 2022/11/19 00:37
* @Description:
*/
public class StaticInnerClass_ {
public static void main(String[] args) {
Outer10 outer10 = new Outer10();
outer10.show();
//新建一个内部类 注意 静态内部类不能通过 点new 的方式
Outer10.Inner01 inner01 = new Outer10.Inner01();
inner01.say();
//使用调用静态方法的方式使用
System.out.println("++++++");
Outer10.Inner01 inner011 = Outer10.getInner01();
inner01.say();
//使用外部类的调用非静态属性
System.out.println("+++++++");
Outer10.Inner01 inner012 = outer10.getInner01_();
inner012.say();
}
}
class Outer10 {
private int n1 = 10;
private static String name = "张三";
//静态内部类
//1.放在成员变量的位置,
//2.使用static修饰
//3.可以直接访问外部类的静态成员,包括私有属性,但是不能访问非静态的
//4.可以添加修饰符(public protected 默认 private) 地位是一个成员
//5.访问方法 新建一个类
public static class Inner01 {
public void say() {
System.out.println("这是外部类静态属性" + name);
}
}
public void show() {
System.out.println(n1);
Inner01 inner01 = new Inner01();
inner01.say();
}
public Inner01 getInner01_() {
return new Inner01();
}
public static Inner01 getInner01() {
return new Inner01();
}
}
test测试:
输出结果:1.调用外部类构造器
输出s2.a 就是输出 5
r.a 还是5
第二章:枚举类 enumeration(枚举 简写enum)
导入
预设一个场景:
Season类 季节类 只有
//对于季节而言,他的对象(具体值)只有四个 不会更多
//这个不能提醒其是四个固定的对象
//这时候使用枚举类 一个一个举例 将具体的对象一个一个举例出来
//创建的Season对象 四个固定对象(值) 而且是只读 不需要修改的
枚举的特点
- 枚举是一组常量的集合
- 枚举属于一种特殊的类,里面只包含一组有限的特定的对象
特点与演示
实现枚举的两种方式
- 自定义枚举
- 构造器私有化
- 本类内部创建一组对象
- 对外暴露对象(使用public static final 修饰符)
- 不提供set方法 只提供get方法
- 使用enum实现
- 使用 enum 代替 class
- 直接使用 SPRING("春天","舒适"); 这样创建对象
- 多个使用","号分隔
- 需要写在 成员属性的 前面
- 实际展示:
//演示自定义枚举类
class Season {
private String name;
private String desc;//描述
public static final Season SPRING = new Season("春天", "舒适");
public static final Season SUMMER = new Season("夏天", "炎热");
public static final Season AUTUMN = new Season("秋天", "凉爽");
public static final Season WINTER = new Season("冬天", "寒冷");
//1.先将构造器私有化,防止new
//2.强调setXX方法,防止属性修改
//3.在Season类内部创建静态类 , 直接创建固定的对象
//4.优化 加入final 进行不可修改.
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
}
//演示使用enum关键字来实现枚举类
enum Season2{
//使用enum来实现枚举类
//1.使用enum代替class
//2,public static final Season2 SPRING = new Season2("春天", "舒适");
//直接使用 Spring("春天","舒适");
//3,多个常量(对象) 直接使用,号分隔.
//4,要求将定义的常量对象, 写最前面
SPRING("春天","舒适"),SUMMER("夏天", "炎热"),
AUTUMN("秋天", "凉爽"),WINTER("冬天", "寒冷");
private String name;
private String desc;//描述
private Season2(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
}
细节
enum关键字的细节问题:
-
使用enum关键字, 自动继承 Enum类
( ctrl+H 查看父辈 只有Object 不行) ( instanceof enum 不行 报错(Expression expected) 应为 预期 表达式)
在enum类中写判断可以显示
public static void show(){ if(SPRING instanceof Enum){ System.out.println("SPRING 这个enum对象 继承了 Enum<E> 泛型"); } }
IDEA 指着enum 这个也能显示 继承的是 Enum<Enumeration3.Season2> 这个泛型 <>里显示的是包名
老韩 是使用javap 来显示
显示 注意 这个类是final 属性的 继承了Enum泛型
-
public static final 类名 对象名 = new 类名("属性"); 简化成了 类名("属性"); 但是 必须要知道是调用了那个构造器
也就是 必须要有该构造器
-
如果使用无参构造器 创建枚举对象,则实参列表和小括号都可以省略.
SPRING("春天", "舒适"), SUMMER("夏天", "炎热"), //有参 AUTUMN("秋天", "凉爽"), WINTER("冬天", "寒冷"), //有参 OUTER; //无参 分号收尾 private String name; private String desc;//描述 //无参 private Season2(){} //有参 private Season2(String name, String desc) { this.name = name; this.desc = desc; }
-
当有多个枚举对象时,使用逗号间隔 最后需要分号收尾.
SPRING("春天", "舒适"), SUMMER("夏天", "炎热"), //有参 AUTUMN("秋天", "凉爽"), WINTER("冬天", "寒冷"), //有参 OUTER; //无参 分号收尾
-
枚举对象必须在枚举类的行首.
练习
题1: OK
自带无参构造
枚举类 Gender 没有属性
BOY 和GIRL两个枚举对象
题2:
输出的是BOY 也就是调用了Gender2 的父类 Enum 的toString方法 返回的是"return name";
因为指向的是同一个对象 == 对引用类型判断的是地址 所以是 TRUE
Enum的成员方法
-
使用关键字 enum时, 会隐式继承Enum类, 这样我们就可以使用Enum类相关的方法. 因为子类自动继承父类的所有成员方法.
-
-
enum常用方法一览表
-
values(), ordinal() 和 valueOf() 方法
enum 定义的枚举类默认继承了 java.lang.Enum 类,并实现了 java.lang.Serializable 和 java.lang.Comparable 两个接口。
values(), ordinal() 和 valueOf() 方法位于 java.lang.Enum 类中:
- values() 返回枚举类中所有的值。(数组)
- ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样。(索引下标)
- valueOf()方法返回指定字符串值的枚举常量。(返回对象常量)
-
toString方法 返回的是当前对象名
public static void main(String[] args) {
Season2 season2 = Season2.SPRING;
//toString 可以省略 自动使用了.
System.out.println(season2.toString());
//Season{name='春天', desc='舒适'}
//name() 返回当前对象名 子类不能重写 通常在返回值的适合使用
System.out.println(season2.name());
//SPRING
//ordinal() 返回索引 从0开始
System.out.println(season2.ordinal());
// 0
//values() 返回枚举类的所有常量 返回的是 Season[] 数组
System.out.println(Arrays.toString(Season2.values()));
//[Season{name='春天', desc='舒适'}, Season{name='夏天', desc='炎热'},
// Season{name='秋天', desc='凉爽'}, Season{name='冬天', desc='寒冷'},
// Season{name='null', desc='null'}]
//valueOf() 根据对象名返回对象属性 必须是已有的常量名 否则报错
System.out.println(Season2.valueOf("SPRING"));
//Season{name='春天', desc='舒适'}
//compareTo() 比较 比较的是枚举常量在枚举类中的位置号(索引)
//就是 return self.ordinal - other.ordinal;
System.out.println(season2.compareTo(Season2.WINTER));
//-3
System.out.println(season2.compareTo(Season2.SPRING));
//0
System.out.println(season2.compareTo(Season2.SUMMER));
//-1
//equals() 引用类型比较
System.out.println(season2.equals(Season2.WINTER));
//false 里面就是比较 == 子类继承需要重写
/* public final boolean equals(Object other) {
return this==other;
}*/
}
enum课堂练习
题1:
如下
enum Week{
MONDAY("星期一"),TUESDAY("星期二"); //.....
private String name;
private Week(String name){
this.name = name;
}
//get方法...
}
class Test{
public static void main(String[] args){
System.out.println("===所有星期的信息如下===")
Week[] weeks = Week.values();
for(Week week:weeks){
System.out.println(week);
}
}
}
enum实现接口
- 由于 enum 已经继承了Enum
这个类 java中单继承 所以不能进行再继承其他的类 - 但是枚举类依旧是一个类 可以实现接口 也就是继承接口
interface IPlaying{
public void playing();
}
enum music implements IPlaying{
CLASSMUSINC;
@Override
public void playing(){
System.out.println("播放音乐");
}
}
main{
music.CLASSMUSIC.playing()
}
第三章:注解(Annotation)
导入
- 注解 又称为 元数据(Metadata) 用来修饰 包\ 类\ 方法\ 属性\ 构造器\ 局部变量 等数据信息
- 和注释一样 并不会影响程序逻辑 但是注解可以被编译或运行, 相当于在代码中的补充信息.
- 在javaSE中 注解的目的简单 标记过时的功能 忽略警告灯 在JavaEE中 比较重要 可以代替繁冗代码和XML配置
JDK中的基本注解
@Override 重写方法
重写方法 只能用于方法
如果写了@Override 如果有这个 编译器就会去检查是否真的重写了该方法 重写了就通过, 否则编译不通过
**其中的@interface 表示 注解类 ** jdk5.0加入的
ElementType.Method(方法) 要素类型: 方法
@Deprecated 标记过时
标记过时 比如打印当前时间
标记的元素已经过时 不推荐使用 但是依然可以使用.
{
main{
A a = new A();
a.h1(); // 会有删除线
}
}
@Deprecated
class A{
@Deprecated
public int n1 = 10;
@Deprecated
public void h1(){}
}
保持: 保留策略 点 "运行时间"
要素类型: 构造函数,字段,本地(局部)_变量,方法,包,参数,类型
constructor, field, local_variable, method, package, parameter, type
@Documented 的作用可以做到新旧版本的兼容和过度
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
@SuppressWarnings 抑制警告
注解的使用有以下三种:
unchecked 抑制没有进行类型检查操作的警告
- 抑制单类型的警告:@SuppressWarnings("unchecked")
- 抑制多类型的警告:@SuppressWarnings("unchecked","rawtypes")
- 抑制所有类型的警告:@SuppressWarnings("all")
- 抑制没有使用的警告: @SuppressWarnings("unused")
可以直接放在main方法的最上面 这样范围就是整个main了
关键字 | 用途 |
---|---|
all | 抑制所有警告 |
boxing | 抑制装箱、拆箱操作时候的警告 |
cast | 抑制映射相关的警告 |
dep-ann | 抑制启用注释的警告 |
deprecation | 抑制过期方法警告 |
fallthrough | 抑制在 switch 中缺失 breaks 的警告 |
finally | 抑制 finally 模块没有返回的警告 |
hiding | 抑制相对于隐藏变量的局部变量的警告 |
incomplete-switch | 忽略不完整的 switch 语句 |
nls | 忽略非 nls 格式的字符 |
null | 忽略对 null 的操作 |
rawtypes | 使用 generics (泛型) 时忽略没有指定相应的类型 |
restriction | 抑制禁止使用劝阻或禁止引用的警告 |
serial | 忽略在 serializable 类中没有声明 serialVersionUID 变量 |
static-access | 抑制不正确的静态访问方式警告 |
synthetic-access | 抑制子类没有按最优方法访问内部类的警告 |
unchecked | 抑制没有进行类型检查操作的警告 |
unqualified-field-access | 抑制没有权限访问的域的警告 |
unused | 抑制没被使用过的代码的警告 |
抑制警告 源码:
范围 TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE
该注解类 有数组 String[] values() 设置一个数组 比如: {"unchecked","rawtypes","unused"}
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
四种元注解
元注解 就睡修饰其他的注解:
本身作用不大:
@Retention 注解的作用范围
source 来源 class 加载 runtime 运行时
source 编译器将丢弃注释 (编译文件 也就是java源文件)
class 注释将由编译器记录在类文件中,但VM在运行时不需要保留注释。这是默认行为。(默认值)
类文件 .class文件
runtime 注释将由编译器记录在类文件中,并在运行时由VM保留,因此可以反射地读取。
运行到JVM
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
*返回保留策略。
*@return保留策略
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
public enum RetentionPolicy {
/**编译器将丢弃注释。
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**注释将由编译器记录在类文件中,但VM在运行时不需要保留注释。这是默认行为。
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**注释将由编译器记录在类文件中,并在运行时由VM保留,因此可以反射地读取。
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*@请参见java.lang.reflect.AnnotatedElement
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
@Target 指定注解在哪些地方使用
返回可应用批注类型的元素类型的数组。
返回的是一个可应用批注类型 的数组
要素类型: 构造函数,字段,本地(局部)_变量,方法,包,参数,类型
constructor, field, local_variable, method, package, parameter, type
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**返回可应用批注类型的元素类型的数组。
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
@Documented 指定该注解是否会在javadoc体现
Documented (记录在案的)
如果一个注解@B
,被@Documented
标注,那么被@B
修饰的类,生成文档时,会显示@B
。如果@B
没有被@Documented
标准,最终生成的文档中就不会显示@B
。
被标注则记录 否则不记录
作用范围: 注释类型 只能针对注释
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
@Inherited 子类会继承父类注解
Inherited 继承
标注了 则 父类注解 子类会继承 否则不继承 子类会自动获得该注解
针对注解类型
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
静态的练习:
题1:
输出结果:
- 对象 c price = 9.0; color = "red"
- 对象 c1 price = 100.0 ; color = "white" 但是它是静态属性 被修改过了 变成了 red
题2:
public class homework {
public static void main(String[] args) {
/* Frock frock = new Frock();
System.out.println(frock.getNextNum());
System.out.println(frock.getNextNum());*/
Frock frock0 = new Frock();
Frock frock1 = new Frock();
Frock frock2 = new Frock();
System.out.println(frock0.getSerialNumber());
System.out.println(frock1.getSerialNumber());
System.out.println(frock2.getSerialNumber());
/*100100
100200
100300
*/
}
}
class Frock{
private static int currentNum = 100000;
private int serialNumber;
public Frock() {
this.serialNumber = getNextNum();
}
public static int getNextNum(){
currentNum += 100;//增100
return currentNum;
}
public int getSerialNumber() {
return serialNumber;
}
}
题3:
package com.javastudy.enum_;
/**
* @Auther: qianl
* @Date: 2022/11/23 21:36
* @Description:
*/
public class HomeWork03 {
public static void main(String[] args) {
//自动动态绑定 多态 父类引用指向子类对象
Animal cat = new Cat();
cat.shout();
Animal dog = new Dog();
dog.shout();
}
}
// 抽象类 Animal 有 shout()
//Cat 和Dog类 实现shout();
abstract class Animal{
public void shout(){};
}
class Cat extends Animal{
@Override
public void shout() {
System.out.println("猫会喵喵叫");
}
}
class Dog extends Animal{
@Override
public void shout() {
System.out.println("狗会汪汪叫");
}
}
内部类练习:
题4:
public class HomeWork04 {
public static void main(String[] args) {
cellPhone cellPhone = new cellPhone();
//1 匿名内部类 既是一个类 也是一个对象 调用方法 传入对象的方法中
//2 匿名内部类 自动实现了接口 传递了一个匿名内部类 和两个待计算的值
//3 因为静态绑定 匿名内部类算是接口的实现 静态绑定就使用匿名内部类的方法 来实现计算.
//4 所以 编译类型是Computer 实现类型是 匿名内部类
cellPhone.testWork(new Computer() {
@Override
public double work(double n1, double n2) {
return n1 + n2;
}
},10,20);
}
}
//1. 计算器接口 有方法 work() 功能是运算
//2. 手机类cellPhone 方法testWork 测试计算器功能 要求CellPhone对象 的testWork方法 使用匿名内部类
interface Computer{
double work(double n1, double n2);
}
class cellPhone{
//1 调用testWork方法 直接传入一个 实现了Computer 接口的匿名内部类即可 也就是传入一个类 和要计算的值
//2 匿名内部类 可以磷化实现work 实现的结果 是从匿名内部类中传出的 给到result并打印出来
public void testWork(Computer computer, double n1, double n2){
double result = computer.work(n1,n2);
System.out.println("计算结果 = " + result);
}
}
题5:
public class HomeWork05 {
public static void main(String[] args) {
A a = new A();
a.getB();
//外部类name: A内部类name: B
}
}
// A类 局部内部类B B中有一私有常量 name 有一个法防 show() 打印name
// A中也定义一个私有变量 name 在show()法防中打印
class A{
private String name = "A";
private class B{
private String name = "B";
public void show(){
System.out.println("外部类name: "+A.this.name + "内部类name: "+ name);
}
}
public void getB(){
B b = new B();
b.show();
}
}
题6:
package com.javastudy.enum_;
/**
* @Auther: qianl
* @Date: 2022/11/23 22:05
* @Description:
*/
public class HomeWork06 {
public static void main(String[] args) {
//唐僧初始就做飞机
Person person = new Person("唐僧",new
Horse());
person.ordinary();//一般情况下
person.passRiver();//过河
person.ordinary();//一般情况下
person.flamingMountain();//过火焰山
person.passRiver();//过河
person.flamingMountain();//过火焰山
person.ordinary();//一般情况下
person.passRiver();//过河
person.flamingMountain();//过火焰山
person.ordinary();//一般情况下
}
}
//车辆类
interface Vehicles{
void work();
}
class Horse implements Vehicles{
@Override
public void work() {
System.out.println("骑马");
}
}
class Boat implements Vehicles{
@Override
public void work() {
System.out.println("乘船");
}
}
class Plane implements Vehicles{
@Override
public void work() {
System.out.println("坐飞机");
}
}
//工具类 不想新建对象 就要设置成静态
class VehicleFactory {
//马始终是同一匹 而船是没次都是新船
private static Horse horse = new Horse();//饿汉式
//获取马的对象
public static Horse getHorse(){
// return new Horse();
return horse;
}
//获取船的对象
public static Boat getBoat(){
return new Boat();
}
//获取飞机的对象
public static Plane getPlane(){
return new Plane();
}
}
class Person{
private String name;
private Vehicles vehicles;
// vehicles 能是vehicles这个接口的所有子类
//一个问题 如果不浪费 在构建对象时, 传入交通工具对象
public Person(String name, Vehicles vehicles) {
this.name = name;
this.vehicles = vehicles;
}
//把具体的要求 封装成方法 这里就是编程思想
public void passRiver(){
//防止始终使用传入的数据 马 不是马 就创建船
//只要属性不是船 就生成船
if(!(vehicles instanceof Boat)) {
vehicles = VehicleFactory.getBoat();
}
vehicles.work();
}
public void ordinary(){
//判断 vehicles 属性 一般情况下
if(!(vehicles instanceof Horse)){
//这里说明多态 父类引用 指向子类对象
vehicles = VehicleFactory.getHorse();
}
//这里体现使用接口调用
vehicles.work();
}
public void flamingMountain(){
//只要属性不是飞机 就生成飞机
if(!(vehicles instanceof Plane)){
vehicles = VehicleFactory.getPlane();
}
//方法的动态绑定
vehicles.work();
}
}
题7:
注意: 要习惯 以某个类为方法的返回类型
package com.javastudy.enum_;
/**
* @Auther: qianl
* @Date: 2022/11/24 11:45
* @Description:
*/
public class HomeWork07 {
public static void main(String[] args) {
Car car = new Car(60);
car.getAir().flow();
}
}
//Car类 有属性 类 类中方法
class Car{
private double temperature;//温度
public Car(double temperature) {
this.temperature = temperature;
}
class Air{
public void flow(){
if (temperature>40){
System.out.println("吹冷风");
}else if(temperature<0){
System.out.println("吹暖风");
}else{
System.out.println("关闭空调");
}
}
}
public Air getAir(){
return new Air();
}
}
枚举类练习:
题8:
package com.javastudy.enum_;
import com.sun.org.apache.bcel.internal.generic.NEW;
/**
* @Auther: qianl
* @Date: 2022/11/24 11:55
* @Description:
*/
public class HomeWork08 {
public static void main(String[] args) {
Color color0 = Color.RED;
color0.show(color0);
Color color1 = Color.BLUE;
color1.show(color1);
Color color2 = Color.YELLOW;
color2.show(color2);
Color color3 = Color.BLACK;
color3.show(color3);
}
}
interface Show{
void show(Show show);
}
enum Color implements Show{
RED(255,0,0),BLUE(0,0,255),BLACK(0,0,0),
YELLOW(255,255,0),GREEN(0,255,0);
private double redValue;
private double greenValue;
private double blueValue;
Color(double redValue, double greenValue, double blueValue) {
this.redValue = redValue;
this.greenValue = greenValue;
this.blueValue = blueValue;
}
@Override
public String toString() {
return "[" + redValue +
", " + greenValue +
", " + blueValue +
"]";
}
@Override
public void show(Show show) {
Color color = (Color) show;
switch (color){
case RED:
System.out.println(RED);
break;
case BLUE:
System.out.println(BLUE);
break;
case BLACK:
System.out.println(BLACK);
break;
case YELLOW:
System.out.println(YELLOW);
break;
case GREEN:
System.out.println(GREEN);
break;
default:
System.out.println("该颜色不存在");
}
}
}
第四章:异常 exception (意外情况):
异常的概念:
异常导入
package com.javastudy.exception;
/**
* @Auther: qianl
* @Date: 2022/11/24 17:25
* @Description:
*/
public class TheException_ {
public static void main(String[] args) {
int num1 = 10;
int num2 = 0;
// int result = num1/num2;
//因为错误 下面的不执行了 不能因为一个不算致命的bug而系统崩溃
//所以提供了一个异常处理机制来解决问题.
//使用try - catch来处理
//还是会报错 但是不影响系统的继续运行
try {
int result = num1/num2;
} catch (Exception e) {
// e.printStackTrace();
//输出异常信息
System.out.println(e.getMessage());
// / by zero
}
//能正常输出下一句
System.out.println("继续运行");
//double类型 会抛出Infinity 无穷
//int 类型 会显示 ArithmeticException (算术错误) : / by zero 被除数为0
}
}
概念:
-
java语言中, 将程序执行过程中发生的不正确情况成为"异常" (开发过程中的语法错误和逻辑错误不属于异常)
-
执行过程中的异常粉两类:
- Error(错误) : java虚拟机无法解决的严重问题: JVM系统内部错误 , 资源耗尽, 如: 栈溢出等
- Exception(意外情况): 其他因为编程错误 或偶然的外在因素导致的一般性问题, 可以使用针对性的代码继续处理 比如: 空指针 试图读取不存在的文件, 网络连接中断等
- 其中分两大类: 运行时异常{程序运行时,发生的异常} 不受检异常
- 比如用空值对象的引用(NullPointerException)、数组下标越界(ArrayIndexOutBoundException)。此类异常属于不可查异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。
- 编译时异常{编译时,编译器检查出的异常} 受检异常
- Exception中除RuntimeException极其子类之外的异常。编译器会检查此类异常,如果程序中出现此类异常,比如说IOException,必须对该异常进行处理,要么使用try-catch捕获,要么使用throws语句抛出,否则编译不通过。
- 其中分两大类: 运行时异常{程序运行时,发生的异常} 不受检异常
异常体系图:
异常体系图:
非受检的可以不处理
但是受检的必须处理
Throwable 是所有异常的父类
子类是error 错误(jvm问题) 会系统崩溃
exception 异常 不一定崩溃
按受不受检可分为:
分有非受检异常(unchecked ) 和受检异常 (checked)
error 和 exception 的runtimeException的子类 包括常见的: 算术异常 类异常 空指针异常
常见的运行时异常
1.NullPointerException 空指针
当应用程序试图在需要对象的地方使用null时, 抛出该异常
String name = null;
//获取字符串长度
System.out.println(name.length());
//java.lang.NullPointerException
2,ArithmeticException 算术异常
计算错误
int i1 = 10;
int i2 = 0;
//计算除法
int result = i1/i2;
//java.lang.ArithmeticException: / by zero
3.ArrayIndexOutOfBoundsException 数组下标越界
异常会显示数组原有长度
int[] i2 = {10,421,45,5213,53,5};
for (int i = 0; i <= i2.length; i++) {
System.out.println(i2[i]);
}
// java.lang.ArrayIndexOutOfBoundsException: 6
4.ClassCastException 类型转换异常
A b = new B();//向上转型 ok
B b2 = (B) b; //强制转型 从上转下 ok
C c2 = (C) b; //子类转子类
//java.lang.ClassCastException:
// com.javastudy.exception.B cannot be cast to com.javastudy.exception.C
5.NumberFormatException 数字格式不正确异常
当程序试图将字符串转换成一种数值类型,但是该字符串不能转换为适当格式时
String name = "是字符串呢";
int num = Integer.parseInt(name);
//java.lang.NumberFormatException: For input string: "是字符串呢"
常见的编译异常: 不处理不能通过编译
其他类似:
zhao不到该文件 会FileNotFoundException
异常课堂练习:
查看代码是否正确
- 越界异常
- 空指针异常
- 算术异常
- 类类型转换异常
异常处理概念
try-catch -finally
-
如果异常发生了,那么异常后面的代码不会执行,直接进入catch块,
-
如果异常没有发生, 则顺序自治县try的代码块,不会进入到catch
-
如果希望不管是否发生异常,都指向某段代码(比如关闭连接, 释放资源等,) 则使用代码块 finally{}
选中代码块后, 使用"ctrl + atl + t" 使用这个可以快速使用异常代码块
异常处理由小到大原则
- 可以有多个catch语句, 捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前, 比如(Exception在后, NUllPointerEcpetion 在前)
- 可以进行try-finally配合使用, 这种用法相当于没有捕获异常, 因此程序会直接崩掉.
场景: 执行一段代码,无论是否发生异常,都必须执行某个业务逻辑 - 可能是需要写程序日志 或者先进行保存 退出等操作 执行完这个业务逻辑后 会直接崩溃
只能进入一个catch
package com.javastudy.exception;
/**
* @Auther: qianl
* @Date: 2022/11/25 18:38
* @Description:
*/
public class TryCatchDetail {
public static void main(String[] args) {
//1. 如果**异常发生了**,那么异常后面的代码不会执行,**直接进入catch块**,
//2. 如果**异常没有发生**, 则顺序自治县try的代码块,**不会进入到catch**.
//3. 如果希望**不管是否发生异常**,都指向**某段代码(比如关闭连接, 释放资源等,)** 则**使用代码块 finally{}
//4.可以有多个catch语句, 捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前, 比如(Exception在后, NUllPointerEcpetion 在前)
//5. 可以进行try-finally配合使用, 这种用法相当于没有捕获异常, 因此程序会直接崩掉.
// 场景: 执行一段代码,无论是否发生异常,都必须执行某个业务逻辑
try {
Person person = new Person();
person = null;
System.out.println(person.getName()); //NullPointerException
int n1 = 10;
int n2 = 0;
int n3 = n1 / n2; //ArithmeticException
} catch (NullPointerException e) {
System.out.println("空指针异常" + e.getMessage());
} catch (ArithmeticException e) {
// e.printStackTrace();
//NullPointerException
System.out.println("算术异常" + e.getMessage());
//null 空指针异常
/// by zero
} catch (Exception e) {
System.out.println("其他异常" + e.getMessage());
} finally {
System.out.println("程序继续");
}
}
}
class Person {
private String name = "jack";
public String getName() {
return name;
}
}
throws
try-catch-finally练习:
题1: 输出了什么
//String的默认值是 null
//1.比较会不会出错? null和tom不等 空指针问题 除法反过来比较
//2.names[3] = "hspedu"; 这里超出数组边界
因为已经报错了 所以 else 和 return 1都不执行
然后走第二个catch
返回的是 3
所以输出 3
然后finally是必须执行
所以输出 4
4 覆盖掉了 3
题2:
返回什么
//i++ i = 1 然后i = 2
同样的nullPointerException
然后 ++i i=3
finally 必定执行
i=4
输出的还是4
题3:
输出什么:
由于finally必定执行所以输出
输出4
然后由于方法完全结束 给出返回值:
输出3
题4:
如果用户输入的不是一个整数,则提醒他反复输入, 直到输入一个整数为止
package com.javastudy.exception;
import java.util.Scanner;
import java.util.SplittableRandom;
/**
* @Auther: qianl
* @Date: 2022/11/25 19:16
* @Description:
*/
public class ExceptionPractice {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
//使用无限循环接受输入
for (;;){
String inputStr = scanner.next();
//在转换时抛出异常则说明不是int类型 如果没有抛出异常则break;
try {
Integer.parseInt(inputStr);
break;//正常则跳出
} catch (NumberFormatException e) {
System.out.println("输入类型不正确"+e.getMessage());
}
}
}
}
Throws 抛出 投掷
- 如果一个方法中可能生成某种异常,但是不能确定如何处理这种异常,则该方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理, 而由方法的调用者负责
- 在方法声明中用throws语句 可以声明抛出异常的列表, throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类
如果是程序员, 没有显示是处理异常,默认throws
方法f1后面 抛出了三种异常:
package com.javastudy.exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
/**
* @Auther: qianl
* @Date: 2022/11/26 03:23
* @Description:
*/
public class Throws_ {
public static void main(String[] args) {
}
public void f1() throws FileNotFoundException,NullPointerException,ArithmeticException{
//创建一个文件流对象
//1. 进行异常处理 java.io.FileNotFoundException
//2. 使用之前的try-catch-finally
//3. 使用Throws 抛出异常处理 让调用方法f1的去处理异常
//4. throws 可以是产生的异常类型,也可以是它的父类
//5. throws 关键字后可以是一场列表,即可以抛出多个异常
FileInputStream fis = new FileInputStream("D://a.txt");
}
}
Throws细节:
- 对于编译异常,程序中必须处理,
- 对于运行时异常,程序中没有处理, 则默认是throws的方式处理
- 子类重写父类的方法时, 对抛出异常的规定: 子类的重写的方法,所抛出的异常类型要和父类抛出的异常一致, 要么是父类抛出异常的子类型
- 在throws过程中,如果有方法try-catch 就相当于处理了异常,就可以不用throws 抛出截止
1、对于编译异常,程序中必须处理,
//编译类异常, 也就是IO ClassNotFound (类未建立) CloneNotSupported(不支持克隆) 三种 必须进行处理
2\ 对于运行时异常,程序中没有处理, 则默认是throws的方式处理 不强制处理
Error 程序错误 Runtime 运行时错误 只要使用了try-catch-finally 就算处理了 不会影响程序运行
3\子类 父类异常 throws 抛出时 子类抛出的必须是 <= 父类 (子异常必须 == 父异常或其子类)
4\方法a 中抛出 方法b调用方法a 那么a就要进行处理 (try或者抛出) 可以继续抛出 然后由调用对象去处理
5\ 静态方法 调用静态方法 必须处理被抛出的异常 因为都是类加载时就生成的 类加载的时侯就调用了的意思 就必须处理( try 或者抛出)
相对的 普通方法 调用静态方法 就不用必须处理 可以由调用者去处理(因为运行时的 自动trows)
子类父类抛出问题:
class F1{
public void f2() throws Exception {
FileInputStream fileInputStream = new FileInputStream("D://a.txt");
}
}
class F2 extends F1{
@Override
public void f2() throws FileNotFoundException {
FileInputStream fileInputStream = new FileInputStream("D://a.txt");
}
}
静态方法抛出问题
重点在 Throws 的是什么错误
运行时错误 有默认的处理机制 : 默认抛出
第一种情况: 静态调用静态 编译错误 (报错)
public static void t1(){
t2();//报错
}
public static void t2() throws FileNotFoundException{
FileInputStream fileInputStream = new FileInputStream("D://a.txt");
}
第二种情况: 普通调用静态 编译错误 (报错)
public void t1(){
t2();//错误
}
public static void t2() throws FileNotFoundException{
FileInputStream fileInputStream = new FileInputStream("D://a.txt");
}
第三种情况: 普通调用静态 运行时错误 (不报错)
public void t1(){
t2();
}
public static void t2() throws ArithmeticException{
}
第四种情况: 静态调用静态 运行时错误 (不报错)
public static void t1(){
t2();
}
public static void t2() throws ArithmeticException{
}
自定义异常
错误信息没有在 Throwable中出现,自己定义异常类
1.自定义异常 继承 RuntimeException 和Exception
2.如果继承Exception 属于编译异常
3.如果RuntimeException 则是运行异常(一般都是这个)
package com.javastudy.exception;
public class Exception02 {
public static void main(String[] args) {
Person01 person01 = new Person01(10);
//Exception in thread "main" com.javastudy.exception.CustomException: 年龄需要在[18-120]
}
}
测试类
class Person01{
private int age;
public Person01(int age) {
if (age<18||age>120) {
CustomException customException = new CustomException("年龄需要在[18-120]");
throw customException;
} else {
this.age = age;
}
}
}
//自定义异常
class CustomException extends RuntimeException{
//构造器: 名字
public CustomException(String message) {
super(message);
}
}
自定义异常:
//自定义异常
public CustomException(String message) {
super(message);
}
//super() 到这
public Throwable(String message) {
fillInStackTrace();
detailMessage = message;
//最后 默认信息 = 传入的信息
}
public synchronized Throwable fillInStackTrace() {
if (stackTrace != null ||
backtrace != null /* Out of protocol state */ ) {
fillInStackTrace(0);
stackTrace = UNASSIGNED_STACK;
}
return this;
}
throw和throws的对比
意义 | 位置 | 后面跟的东西 | |
---|---|---|---|
Throws | 异常处理的一致方式 | 方法声明处 | 异常类型 |
Throw | 手动生成异常对象的关键字 | 方法体中 | 异常对象 |
测试题:
1\使用方法A 输出 (1) "进入方法A" 新建"制造异常" 必定输出finally (2) "用A方法的finally"
2\ 执行完 方法A 抛出一个异常: "制造异常" 所以会进入 catch (3) 输出"制造异常"
3\ 继续进入: 方法B
4\输出: (4)"进入方法B" (5) "调用B方法的finally" 然后返回并结束
PS: Serializable
为什么Throwable要继承 Serializable(可串行的) 这个接口:
Serializable 是java.io包中 目的是可序列化 然后就可以在网络上传播了
java中的 IO流是最终要转化为: 字节流 进行操作 . 而实现了该接口 才能被 ObjectOutputStream 解析为字节流 被 ObjectInputStream 反解析为对象
序列化&反序列化
序列化: 就是 将对象转化为字节流的过程
反序列化: 就是 将字节流转化为对象的过程 比如:
1.利用mybatis框架编写持久层insert对象数据到数据库中时 (写入数据库)
2.网络通信时需要用套接字在网络中传送对象时,如我们使用RPC协议进行网络通信时; (网络编程 嵌套字)
实现方法: SerializableUID:
只有有这个UID 标识的 才可以 序列化和反序列化(字节流)
private static final long serialVersionUID = 1L;
课后作业:
题目1:
package com.javastudy.exception;
import java.util.Scanner;
/**
* @Auther: qianl
* @Date: 2022/11/30 13:19
* @Description:
*/
public class ExceptionWork01 {
//进行三种异常处理:1. 输入的数据格式, 2缺少命令行参数, 2.除0
public static void main(String[] args) {
try{
if(args.length!=2){
throw new ArrayIndexOutOfBoundsException("参数个数不对");
}
int n1= Integer.parseInt(args[0]);
int n2= Integer.parseInt(args[1]);
double res =cal(n1,n2);
System.out.println(res+"商");
}catch (ArrayIndexOutOfBoundsException e){
System.out.println(e.getMessage());
}catch (NumberFormatException e){
System.out.println("输入格式错误");
}catch (ArithmeticException e){
System.out.println("计算错误");
}
}
public static double cal(int n1,int n2){
return n1/n2;
}
}
命令行输入:
题目2:
1.缺少命令行(运行时的数组下标问题)
对比还没到 就报错了已经
Object o = args[2] 也是不行的 但是因为args[4] 修改通过以后 他就必定有值 就可以了
4.类型转换异常 String[]类 转Integer问题
Object o = args[1];
Integer i = (Integer)o;
//ClassCastException: java.lang.String cannot be cast to java.lang.Integer
题3:
1.新建一个RuntimeException
2.打印 "B"
3.因为返回的是一个Runtime错误 所以打印 "C"
4.然后打印 "A"
因为func() 触发错误以后 就走了catch 不往下执行了
5.try结束 下一行 打印 "D"
BCD
题4:
接受了一个exception异常 走catch
输出 B
输出C
输出D
异常处理小节:
代码或多或少会出现问题,进行异常处理可以缓解其脆弱性
重点: 异常体系图!! 哪些是运行时异常(非check) 哪些是编译异常(必须处理 check)
执行顺序问题: 1 finally必定执行 2 遇到异常走catch 3 try语句块下一句就不执行
throw 生成异常的关键字
throws 处理异常的一种方式
第五章:八大Wrapper(包装器)类
基础类型的包装类:
主要是char (字符型)---> Character(特点 特征) 和 int ---> Integer
其他的问题:
- 抬头变大写
- 两个特别的 Character 和 Integer
- 黄色部分的父类是 Number 另外两个不是
包装类 :
就是将普通的数据类型 包装成 类的类型
就可以拥有类的特性: 类方法,
Boolean:
Boolean
//public final class Boolean implements java.io.Serializable,Comparable<Boolean>
//实现的Serializable和Comparable 两个接口 直接父类就是Object
/*
public interface Serializable{}
标识 序列化与反序列化
public interface Serializable {
public int compareTo(T o);
}
对比
*/
Character:
Character
//public final class Boolean implements java.io.Serializable,Comparable<Boolean>
//实现的Serializable和Comparable 两个接口 直接父类就是Object
/*
public interface Serializable{}
标识 序列化与反序列化
public interface Serializable {
public int compareTo(T o);
}
对比
*/
PS:
Character 和 Boolean都是继承Object 实现Serializable和Comparable
继承Number类的包装类: Byte,Short,Integer,Long,Double,Float
public final class Byte extends Number implements Comparable<Byte> {}
//继承的是Number类
public abstract class Number implements java.io.Serializable {
//Number实现了Serializable这个接口
//同时,包装类还实现了Comparable这个接口
1.多态传递 即: 父类实现的接口,子类默认实现
包装类的自动装箱与拆箱
装箱 底层调用的是ValueOf() 方法
jdk5以前 手动装箱和拆箱
jdk5以后 自动装拆箱
//jdk5前
int i2= 100;
//两种方式
Integer integer = new Integer(i2);
Integer integer1 = Integer.valueOf(i2);
//手动拆箱
int i3 = integer.intValue();
//jdk5后
int i = 19;
//自动装箱
Integer n = i;
//自动拆箱
int i1 = n;
//底层 依旧是使用valueOf() 和intValue() 方法
Integer 转String类
Integer i = 100;
//1. 第一种方法
String str1 = i+""; //i拼接上字符串 就是字符类型
//2. 第二种方法
String str2 = i.toString();
//3. 第三种方式
String str3 = String.ValueOf(i);
String 转 Integer类
String str = "123";
//第一种方法:
Integer i1 = new Integer(str);//构造器 也是使用的parseInt();
//第二种方式
Integer i2 = Integer.valueOf(str);
//第三种方法
Integer i3 = Integer.parsInt(str);//使用自动装箱
课中测试题:
1.没问题 自动装箱
2.第二题: true 判断应该是 new Integer(1) 所以输出 的是 1.0 父类引用指向子类对象 [三元运算符是一个整体]
因为是一个整体 计算时 取最高优先级的 Double类型
3.if - else 是分别计算的 还是返回1
包装类方法:
注意
Integer 最大值: 0x8000000 最小 0x7fffffff;
Character 判断数字,字母,大小写,空格,大写小写转换
包装类面试题:
应该是:
false true false
首先: 类是引用类型 == 的是对象地址 返回结果是true 和false
其次: new Integer 是确认新建了一个对象 所以是false
再其次: Integer 是有 [-128,128) 的值 直接在常量池的 所以在范围内 就会指向同一个地址 而不会新建 (节省资源)
对自动装箱的 会进行直接赋予常量池的对象地址
自动装箱 底层使用的是 valueOf() 方法 如果在low和high之间 [-128, 127] 就是直接给地址 否则新建
IntegerCache.cache中:
题目2:
- 注意是否新建 new
- 注意[-128,127]常量池
- 注意引用类型和数据类型对比 == 比较的是值! (示例五和六 都是True)
String
含义:
- String对象就是保存字符串 一组字符序列
- 使用双引号括起来
- 字符串字符使用Unicode编码,一个字符占两个字节
- 常见构造器
- 实现 串行化(字节流 网络传播) 对比(对象对比) CharSequence 三个接口 和一个Object父类
- String 是final 类 不能被其他类继承
- 有属性 private final char value[] ; 用于存放字符串内容
- value 是一个final类型 不可以更改(需要功力) 就是这个value存储的是字符串对象的地址 不能一个对象地址指向另一个对象的地址 但是单个字符内容是可以变化的 如下:
继承关系:
构造器:
Comparable 的compareTo()方法
比较不等:
Javaz中不支持运算符重载,Java中的 < > <= >= 只能针对数字类型来使用,如果创建了两个对象来进行比较大小,就需要专门的方法来完成。
根据对象的某个值 对对象进行比较
equals 是比较相等的情况
创建方法:
//方法一: 直接赋值
String s ="hhhh";
//会先在常量池查看是否有该字符串 "hhhh" 有就直接指向地址, 没有就新建
//s 最终指向常量池的空间地址
//方法二:调用构造器
String s1 = new String("hhhh");
//在堆里创建控件,里面维护了value属性,指向常量池的 "hhhh" 的空间,如果常量池有 通过value指向 没有就新建 //s1 最终指向的是堆中的空间地址.
注意指向的地方是不一样的 所以地址不同
测试题:
题目1:
String的equals 重写过 比较的是内容(True)
== 比较的是对象地址 因为都是直接赋值 比较的是常量池的对象 (True)
题目2:
- equals(重写过)比较的内容 //T
- ==比较的地址 a是常量池 b是堆 //F
- b.intern() 就是返回来自 常量池的字符串 那么 a == b.intern() ==比较地址 a的地址是常量池 b.intern() 也是常量池 //T
- b是堆 b.intern()是常量池 所以是 // F
题目3:
- s2 == s3 比较地址 s2直接赋值,指向常量池, s3新建对象,指向堆 //F
- s2 == s4 比较地址 都是直接赋值 都指向常量池 //T
- s2.equals(s3) String重写了 比较内容 //T
- s1 == s2 //F
题目4:
注意: p1.name = "hspedu" 就是String name = "hspedu" 直接赋值
- String的 的quals比较内容 //T
- p1.name == p2.name 比较地址 都指向同一个常量池 //T
- p1.name == "hspedu" 转化为比较内容 //T
- s1 == s2 比较的堆里的地址 //F
字符串的特性:
String s1 = helllo 在 常量池 因为是直接赋值
s1 = haha hello不会消失 而是s1 指向常量池新的字符串
创建了两个对象
String是final类 字符串不可变,但内容可变
String面试题:
1.编译器做了优化,判断创建的常量池对象 是否有引用指向 就是创建了给谁用的
String a = "hello" + "abc"; // 等价于 String a = "helloabc";
a 创建了多少个对象 一个
2.题目二
一共三个对象 a b (池) c(堆)
看源码 先是new 一个StringBuilder sb= new StringBuilder
String value[]中 添加hello这个对象
然后使用sb.append 加在常量池中
然后添加abc 在value中
使用sb.append 加在常量池中
然后c = a+b (不是直接赋值) 检查c 也就是"helloabc" 是否存在常量池 没有则添加 到常量池
然后将地址给堆
c指向堆
注意: 直接赋值 " " 对象指向常量池 否则对象指向在堆中
String a = "hello";
String b ="abc";
String c = a + b; //分析 String到底这么执行的
//1.先创建一个StringBuilder
//2.执行 sb.append("hello");
//3.sb.append("abc");
//4.sb.toString方法
//c指向堆中的对象(String) value[] --> 池中 "helloabc"
//所以c指向的是堆中的对象
String d = "helloabc"; 那指向的是常量池
sout(c==d)//false
题目三:
s1 常量池
s2 常量池
s5 常量池
s6 堆 赋予的是 "hspedujava" 在常量池的地址
但是s6获取的是 .intern() 返回的是常量池里的对象
s5==s6 真
s5.equals(s6) 比较内容 真
题目四:
str指向池中地址
ch 是数组 在存在堆中 ch也指向的是堆中的数组[java]
调用ex方法 就会生成一共新栈
//注意 str是一个形式参数 也就是复制了值"hsp" 过来而已 不会更改原本的
str是直接赋值 指向常量池里的一个新值
ch[0] = 'h' 那么ch的值就变成了 hava
那么最后结果是 hsp and hava
注意: final 是指向的地址不能改变 那么 final int i = 3.14 ;不可更改 地址在栈
final int[] i = [1,2,3,4] 地址不能更改 地址在堆 但是值可以改变
String常用方法
1 | char charAt(int index) 返回指定索引处的 char 值。 |
---|---|
2 | int compareTo(Object o) 把这个字符串和另一个对象比较。 |
3 | int compareTo(String anotherString) 按字典顺序比较两个字符串。 |
4 | int compareToIgnoreCase(String str) 按字典顺序比较两个字符串,不考虑大小写。 |
5 | String concat(String str) 将指定字符串连接到此字符串的结尾。 |
6 | boolean contentEquals(StringBuffer sb) 当且仅当字符串与指定的StringBuffer有相同顺序的字符时候返回真。 |
7 | [static String copyValueOf(char] data) 返回指定数组中表示该字符序列的 String。 |
8 | [static String copyValueOf(char] data, int offset, int count) 返回指定数组中表示该字符序列的 String。 |
9 | boolean endsWith(String suffix) 测试此字符串是否以指定的后缀结束。 |
10 | boolean equals(Object anObject) 将此字符串与指定的对象比较。 |
11 | boolean equalsIgnoreCase(String anotherString) 将此 String 与另一个 String 比较,不考虑大小写。 |
12 | [byte] getBytes() 使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。 |
13 | [byte] getBytes(String charsetName) 使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。 |
14 | [void getChars(int srcBegin, int srcEnd, char] dst, int dstBegin) 将字符从此字符串复制到目标字符数组。 |
15 | int hashCode() 返回此字符串的哈希码。 |
16 | int indexOf(int ch) 返回指定字符在此字符串中第一次出现处的索引。 |
17 | int indexOf(int ch, int fromIndex) 返回在此字符串中第一次出现指定字符处的索引,从指定的索引开始搜索。 |
18 | int indexOf(String str) 返回指定子字符串在此字符串中第一次出现处的索引。 |
19 | int indexOf(String str, int fromIndex) 返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。 |
20 | String intern() 返回字符串对象的规范化表示形式。 |
21 | int lastIndexOf(int ch) 返回指定字符在此字符串中最后一次出现处的索引。 |
22 | int lastIndexOf(int ch, int fromIndex) 返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索。 |
23 | int lastIndexOf(String str) 返回指定子字符串在此字符串中最右边出现处的索引。 |
24 | int lastIndexOf(String str, int fromIndex) 返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。 |
25 | int length() 返回此字符串的长度。 |
26 | boolean matches(String regex) 告知此字符串是否匹配给定的正则表达式。 |
27 | boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) 测试两个字符串区域是否相等。 |
28 | boolean regionMatches(int toffset, String other, int ooffset, int len) 测试两个字符串区域是否相等。 |
29 | String replace(char oldChar, char newChar) 返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。 |
30 | String replaceAll(String regex, String replacement) 使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。 |
31 | String replaceFirst(String regex, String replacement) 使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。 |
32 | [String] split(String regex) 根据给定正则表达式的匹配拆分此字符串。 |
33 | [String] split(String regex, int limit) 根据匹配给定的正则表达式来拆分此字符串。 |
34 | boolean startsWith(String prefix) 测试此字符串是否以指定的前缀开始。 |
35 | boolean startsWith(String prefix, int toffset) 测试此字符串从指定索引开始的子字符串是否以指定前缀开始。 |
36 | CharSequence subSequence(int beginIndex, int endIndex) 返回一个新的字符序列,它是此序列的一个子序列。 |
37 | String substring(int beginIndex) 返回一个新的字符串,它是此字符串的一个子字符串。 |
38 | String substring(int beginIndex, int endIndex) 返回一个新字符串,它是此字符串的一个子字符串。 |
39 | [char] toCharArray() 将此字符串转换为一个新的字符数组。 |
40 | String toLowerCase() 使用默认语言环境的规则将此 String 中的所有字符都转换为小写。 |
41 | String toLowerCase(Locale locale) 使用给定 Locale 的规则将此 String 中的所有字符都转换为小写。 |
42 | String toString() 返回此对象本身(它已经是一个字符串!)。 |
43 | String toUpperCase() 使用默认语言环境的规则将此 String 中的所有字符都转换为大写。 |
44 | String toUpperCase(Locale locale) 使用给定 Locale 的规则将此 String 中的所有字符都转换为大写。 |
45 | String trim() 返回字符串的副本,忽略前导空白和尾部空白。 |
46 | static String valueOf(primitive data type x) 返回给定data type类型x参数的字符串表示形式。 |
47 | contains(CharSequence chars) 判断是否包含指定的字符系列。 |
48 | isEmpty() 判断字符串是否为空。 |
StringBuffer
StringBuffer 基本信息
注意 : StringBuffer 可变长字符序列
注意: StringBuffer 是一个可边长的字符序列 可以串行化
和String一样 有一共value[]
但是 不是final类 也就是不写在常量池 而是写在堆中
StringBuffer 是final 类 不能被继承
超出长度才会更改地址 所以不用每次创建新对象 效率高
String VS StringBUffer
- 不可变 VS 可变
- 常量池 VS 堆
- 不用每次都去更新地址 除非超过长度
StringBuffer 构造器
默认初始化是16个字符
StringBuffer(){
super(16);
}
//指定 char[] value 的大小
//3. 字符串的大小 + 16
StringBuffer(String str){
super(str.length()+16);
append(str);
}
String转成StringBuffer类型
1.使用构造器 注意 不影响原有的str
2/先构建无参构造器然后, 使用append方法
StringBuffer 转String类型
toString()方法 或者构造器
StringBuffer方法(包含直接父类的方法)
操作数组一样 增删 改查
append 追加 可以追加数字 true 小数 等
返回的是StringBuffer
查: replace 没找到就返回-1
StringBuffer练习:
1.包装类是可以null的
2.如果str == null 那么给值="null"
所以长度为4
输出 null
直接构造器 输入str 创建对象时 会(0+16) 相当于 null.length() 空值异常
练习2:
每三位分割
每三位加, 字符串处理
String str = "123456.78";
StringBuffer sb = new StringBuffer(str);
//在小数点前三位加分号
for(int i = sb.lastIndexOf(".") - 3; i>0;i -= 3){
sb = sb.insert(i,",");
}
StringBuilder
StringBuilder 基本信息
不保证同步! (不是线程安全的) 适用于单线程 单线程优先使用Stringbuilder类 以为比较快
继承结构图: 和StringBuffer 一样
注意: 由于没有做互斥 所以多线程会串线程
由于String 每次使用都会变地址
类型 | 字符序列 | 效率 | 线程 | 内存 |
---|---|---|---|---|
String | 不可变字符序列 | 低 每次修改重新定位地址 | -- | 常量池 |
StringBuffer | 可变长字符序列 | 较高(增删) | 安全 | 堆 |
StringBuilder | 可变长字符序列 | 高 | 不安全(单线程) | 堆 |
使用原则:
StringBulider方法:
同StringBuffer
Math(数学相关)
基本信息:
当他是一个工具类就好 都是(static)静态方法! 所以都可以在主函数中直接调用
Math方法:
ceilling(天花板)
ceil 向上取整 比如 -3.00009 取 -3.0 取>= 该参数的最小整数
floor 向下取整
round 四舍五入
sqrt 开方 只能大于0
random 返回的是0~1 [0,1) 的一个随机小数
找到固定值 使其随机的是一个固定值
取2-7的值 就是+1 十倍 就是+10 百倍 就是+100
序号 | 方法与描述 |
---|---|
1 | xxxValue() 将 Number 对象转换为xxx数据类型的值并返回。 |
2 | compareTo() 将number对象与参数比较。 |
3 | equals() 判断number对象是否与参数相等。 |
4 | valueOf() 返回一个 Number 对象指定的内置数据类型 |
5 | toString() 以字符串形式返回值。 |
6 | parseInt() 将字符串解析为int类型。 |
7 | abs() 返回参数的绝对值。 |
8 | ceil() 返回大于等于( >= )给定参数的的最小整数,类型为双精度浮点型。 |
9 | floor() 返回小于等于(<=)给定参数的最大整数 。 |
10 | rint() 返回与参数最接近的整数。返回类型为double。 |
11 | round() 它表示四舍五入,算法为 Math.floor(x+0.5),即将原来的数字加上 0.5 后再向下取整,所以,Math.round(11.5) 的结果为12,Math.round(-11.5) 的结果为-11。 |
12 | min() 返回两个参数中的最小值。 |
13 | max() 返回两个参数中的最大值。 |
14 | exp() 返回自然数底数e的参数次方。 |
15 | log() 返回参数的自然数底数的对数值。 |
16 | pow() 返回第一个参数的第二个参数次方。 |
17 | sqrt() 求参数的算术平方根。 |
18 | sin() 求指定double类型参数的正弦值。 |
19 | cos() 求指定double类型参数的余弦值。 |
20 | tan() 求指定double类型参数的正切值。 |
21 | asin() 求指定double类型参数的反正弦值。 |
22 | acos() 求指定double类型参数的反余弦值。 |
23 | atan() 求指定double类型参数的反正切值。 |
24 | atan2() 将笛卡尔坐标转换为极坐标,并返回极坐标的角度值。 |
25 | toDegrees() 将参数转化为角度。 |
26 | toRadians() 将角度转换为弧度。 |
27 | random() 返回一个随机数。 |
Arrays
Arrays基本信息:
操作数组:
Arrays 方法:
//遍历循环
Arrays.toString()
使用了StringBuilder
注意 return b.append(']').toString(); 返回 添加并打印的 值
Arrays.sort()
排序:
- 可以直接使用冒泡排序, 耶可以直接使用sort方法排序 都是由低到高
- 因为数组是引用类型 所以sort后 会直接影响实参
- sort重载 可以传入Comparator<?> 比较器 可以实现自定义排序
- 接口编程的思想,实现了Comparator接口的匿名内部类,要求实现compare方法
//强转 或者传入的是Integer类型 然后 返回值是i2与i1的差
当重写方法 c就是传入的接口 有了以后 就是走TimSort.srot()方法
走brnarySort方法 (二叉树排序方法)
使用的是传入的接口 c 重写后的方法
会通过匿名内部类的compare方法 来决定排序的顺序
然后到重写的方法: 注意传入的是i[1]和i[0]的值
然后 o1和o2 会逐步后延 获取数组 i 的其他值
本质是二叉排序树
自写 制定排序(冒泡)
package com.javastudy.math_;
import java.util.Arrays;
import java.util.Comparator;
/**
* @Auther: qianl
* @Date: 2022/12/5 13:39
* @Description:
*/
public class Sort_ {
public static void main(String[] args) {
/* Integer[] i = {1, 2, 41, -2, 45, 214};
//返回的值影响排序方式
Arrays.sort(i, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Integer i1 = (Integer)o1;
Integer i2 = (Integer)o2;
// return i1-i2;
return i2-i1; //[214, 45, 41, 2, 1, -2]
}
});
System.out.println(Arrays.toString(i));
//[-2, 1, 2, 41, 45, 214]*/
int[] arr = {1,-2,3,412,54,521,12};
Sort_.bubble01(arr, new Comparator() {
@Override
//o1 和 02 就是前后值
public int compare(Object o1, Object o2) {
int i1 = (Integer)o1; // 自动拆箱
int i2 = (Integer)o2;
return i1 - i2; // 1- (-2) >0 升序
}
});
System.out.println(Arrays.toString(arr));
}
public static void bubble01(int[] arr,Comparator c){
int temp=0;
for (int i = 0; i < arr.length-1; i++) {
for (int j=0; j<arr.length-1-i;j++){
//如果arr[j] - arr[j+1] >0 则从小到大
//如果arr[j] - arr[j+1] <0 则从大到小
if (c.compare(arr[j],arr[j+1])>0) {
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
}
binarySearch() 二分搜索法 必须先排好序
原理也简单 从中间值开始比较 比搜索值小则记录下标 然后将下标取2分 重复操作 也就是可以递归
进行查找 没有数据 返回 -(low + 1) 的值
Integer[] arr ={1,2,412,5,513,512,3};
Arrays.sort(arr);
int index = Arrays.binarySearch(arr,3);
System.out.println(index);
//2
//返回的是-(low +1) low 就是 检查到的元素位置
比如找的是92 大于90 所以位置在2与3之间 low就是3 返回 -4
copyOf() 数组元素的复制
Integer[] arr ={1,2,412,5,513,512,3};
Arrays.sort(arr);
Integer[] newArr = Arrays.copyOf(arr,arr.length);
System.out.println(Arrays.toString(newArr));
//[1, 2, 3, 5, 412, 512, 513]
如果arr.length -1 则从后递减元素
如果大于arr.length 则添加( null )元素
小于0 爆出gativeArraySizeException
方法底层使用的System.arraycopy() 方法
fill() 数组填充
equals 比较元素内容
aslist()
将一组值 转化为list 集合
// $表示内部类 (成员内部类)
Arrays 的结构
课后练习:
//老师做法: 转换后 使用判断 返回对应的值
以下是从大到小排列
package com.javastudy.math_;
import javax.naming.Name;
import java.util.Arrays;
import java.util.Comparator;
/**
* @Auther: qianl
* @Date: 2022/12/5 14:53
* @Description:
*/
public class SortWork_ {
public static void main(String[] args) {
Book[] books = new Book[5];
books[0] = new Book("青年",100);
books[1] = new Book("好",1);
books[2] = new Book("三个",99);
books[3] = new Book("火枪",400);
books[4] = new Book("矮人王",10);
//从小到大
Arrays.sort(books, new Comparator<Book>() {
@Override
public int compare(Book o1, Book o2) {
//前-后大于0 则是从小到大
return o1.getPrice()-o2.getPrice();
}
});
System.out.println(Arrays.toString(books));
//从大到小
Arrays.sort(books, new Comparator<Book>() {
@Override
public int compare(Book o1, Book o2) {
//前-后大于0 则是从小到大
return o2.getPrice()-o1.getPrice();
}
});
System.out.println(Arrays.toString(books));
//根据名字长度
Arrays.sort(books, new Comparator<Book>() {
@Override
public int compare(Book o1, Book o2) {
//根据名字长度
int i1 =o1.getName().length();
int i2 =o2.getName().length();
return i1-i2;
}
});
System.out.println(Arrays.toString(books));
}
}
//根据对象的某个属性进行排序
class Book{
private String name;
private int price;
public Book(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
System类
System方法:
exit()
System.out.println("ok");
// 0 表示状态: 正常
System.exit(0);
System.out.println("ok2");
System.arraycopy()
//红色是源数组
//蓝色是目标数组
length 可以是 src.length 表示全部元素
时间:
BigInter BigDecimal(大数处理)
保存更大的 整数
保存更精确的 浮点数
BigInter
大数运算 需要使用方法进行
BigDecimal
保留"分子" 的精度 分子分母中的分子
Date\calendar\kocakDate
Date
日期类 格式化日期 根据规范文本 返还日期
Date关系
所有其他字符'A'到'Z' 'a'到'z' 都被保留
然后使用时候 使用字母代表
SimpleDateFormat
public static void main(String[] args) throws ParseException {
Date d1 = new Date();
System.out.println(d1);
//默认格式
//Mon Dec 05 18:15:38 CST 2022
//创建 SimpleDateFormat对象 可以指定相应的格式
//按照格式规范来写
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
//转换成该类型
String format = simpleDateFormat.format(d1);
System.out.println(format);
//2022年12月05日 06:18:55 星期一
//可以将一个自渡川转成对应的Date类
//第一 将文本转换为date类 然后根据simpleDateFormat 的文本进行转换
//第二 需要在方法中抛出 throws ParseException 转换异常
//第三 在输出时 在调用 简化时间格式化 的方法 进行Date类的输出
String s ="2022年12月05日 06:18:55 星期一";
Date parse = simpleDateFormat.parse(s);
System.out.println(simpleDateFormat.format(parse));
}
Calendar(日历) 第二代日期类
继承结构:
可串行化 有三个内部类
基本使用方法:
//Calendar 是一个抽象类 构造器 private
//通过getInstance() 方法来获取实例
Calendar calendar = Calendar.getInstance();
//注意 使用的getInstance的类型
Calendar的方法
使用方法是 c.get(Calender.YEAR) 因为是静态的
//Calendar 是一个抽象类 构造器 private
//通过getInstance() 方法来获取实例
Calendar c = Calendar.getInstance();
System.out.println(c);
//获取日期的方法 c.get的方法
System.out.println(c.get(Calendar.YEAR));
System.out.println("月份"+ (c.get(Calendar.MONTH)+1));
//日 时 分 秒
System.out.println(c.get(Calendar.DATE));
System.out.println(c.get(Calendar.HOUR));
//二十四小时制的小时
System.out.println(c.get(Calendar.HOUR_OF_DATE));
System.out.println(c.get(Calendar.MINUTE));
System.out.println(c.get(Calendar.SECOND));
//没有专门的格式化方法 通过自己组合来进行 组合显示
System.out.println(c.get(Calendar.YEAR)+"-"+(c.get(Calendar.MONTH)+1)+"-"+c.get(Calendar.DATE));
//2022-12-5
前两代日期类的不足:
1.可变性: 像日期,时间应该是不可变的
2.偏移性: Date中的年份是从1900开始的,而月份都是从0开始的
3,格式化: 格式化只对Date有用,Calender不行
4.线程是不安全的 不能处理润秒等(每隔2天 多出1s) 不行处理闰年
LocalDate(日期) LocalTime(时间) LocalDateTime(日期时间)
JDK8加入的
-
LocalDate 只包含日期 可以获取日期字段, 年月日
-
LocalTime 只包含时间 获取时间字段 ,时分秒
-
LocalDateTime 日期+时间,年月日时分秒
继承结构:
8嗝接口 实现了一堆方法
使用方法:
//返回当前时间,的对象
LocalDateTime now = LocalDateTime.now();//LocalDate.now(); LocalTime.now()
System.out.println(now);
//2022-12-05T20:26:05.571
//如果只要年月日 就直接LocalDate.now()
System.out.println(now.getYear());
System.out.println(now.getMonth());
System.out.println(now.getMonthValue());
System.out.println(now.getDayOfMonth());
/* 2022
DECEMBER
12
5*/
//如果只要时分秒 可以使用LocalTime.now()
System.out.println(now.getHour());
System.out.println(now.getMinute());
System.out.println(now.getSecond());
/*20
31
22*/
日期格式化
默认日期是
2022-12-05T20:26:05.571
自定义格式化
和普通Date的SimpleDateFormat 一样的用法
//返回当前时间,的对象
LocalDateTime now = LocalDateTime.now();//LocalDate.now(); LocalTime.now()
System.out.println(now);
//2022-12-05T20:26:05.571
//2.格式化
//创建DateTimeFormatter对象
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH小时mm分钟ss秒");
System.out.println(dtf.format(now));
//2022年12月05日 20小时44分钟51秒
Instant 时间戳
结构:还有一个Object()父类
类似于Date
提供了一系列和Date类转换方法
第三代日期方法
puls和minus等方法进行时间加减计算
//返回当前时间,的对象
LocalDateTime now = LocalDateTime.now();//LocalDate.now(); LocalTime.now()
System.out.println(now);
//2022-12-05T20:26:05.571
//2.格式化
//创建DateTimeFormatter对象
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH小时mm分钟ss秒");
System.out.println(dtf.format(now));
//2022年12月05日 20小时44分钟51秒
//提供plus和minus方法 堆当前时间进行加减
LocalDateTime localDateTime = now.plusDays(7);//7天以后
System.out.println("7天后"+dtf.format(localDateTime)); //格式化
//minus 多少分钟前
LocalDateTime ldt = now.minusMinutes(3600);//3600分钟前
System.out.println("3600秒以后" + dtf.format(ldt));
本章作业:
1.指定部分字符反转 (使用老韩的)
注意: 传入目标字符串, 起始位置 结束位置
- 截取字符串 substring
- 转为字符数组 chars
- 反转 chars
- chars 转为String类型 s
- s1 = str.replaceAll(substring, s) 在str这个字符串中, 将截取的字符串替换成 反转后的字符串
- 输出
package com.javastudy;
/**
* @Auther: qianl
* @Date: 2022/12/5 21:23
* @Description:
*/
public class HomeWork01 {
public static void main(String[] args) {
String str = "abcdef";
String s = null;
try {
s = HomeWork01.reverse(str, 1, str.length() - 1);
} catch (Exception e) {
System.out.println(e.getMessage());
return;
}
System.out.println(s);
//aedcbf
}
public static String reverse(String str, int start, int end) {
//判断情况
//1.先写符合情况的 然后取反
if (!(str != null && start > 0 && end < str.length())) {
throw new RuntimeException("参数错误");
}
//截取出来
String substring = str.substring(start, end);
//转成字符数组
char[] chars = substring.toCharArray();
//反转数组
char ch = ' ';
for (int i = 0; i < chars.length / 2; i++) {
ch = chars[i];
chars[i] = chars[chars.length - 1 - i];
chars[chars.length - 1 - i] = ch;
}
String s = String.valueOf(chars);
//添加回去 替换
String s1 = str.replaceAll(substring, s);
return s1;
}
}
太复杂了 使用老韩的方法
注意1: 使用了异常处理
2:将字符串直接转化成了char[] 使用 start 和 end 进行反转 注意 i++,j--
3:直接返还 new String(chars) 可以直接将char[] 类型转换为String 并输出
package com.javastudy;
/**
* @Auther: qianl
* @Date: 2022/12/5 21:23
* @Description:
*/
public class HomeWork02 {
public static void main(String[] args) {
String str = "abcdef";
String s = null;
try {
s = HomeWork01.reverse(str, 1, str.length() - 1);
} catch (Exception e) {
System.out.println(e.getMessage());
return;
}
System.out.println(s);
//aedcbf
}
public static String reverse(String str, int start, int end) {
//判断情况
//1.先写符合情况的 然后取反
if (!(str != null && start > 0 && end < str.length())) {
throw new RuntimeException("参数错误");
}
//转成字符数组
char[] chars = str.toCharArray();
//反转数组
char ch = ' ';
for (int i = start, j = end; i < j; i++, j--) {
ch = chars[i];
chars[i] = chars[j];
chars[j] = ch;
}
return new String(chars);
}
}
题目2:
生成异常:
注意:
- 使用 用ascii码 或者 Character.isDigi 判断是否全是数字
- if的时候 先写正确的情况 然后取反来进行判断
package com.javastudy;
/**
* @Auther: qianl
* @Date: 2022/12/5 23:37
* @Description:
*/
public class HomeWork03 {
public static void main(String[] args) {
String s = "name";
String pw ="423123";
String em = "[email protected]";
try {
HomeWork03.userRegister(s,pw,em);
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println("程序继续");
}
//先写正确再取反
public static void userRegister(String name, String password, String emile) {
if (name == null || password == null || emile == null) {
throw new RuntimeException("不可为空");
}
//过关斩将
if (!(name.length() >= 2 && name.length() <= 4)) {
throw new RuntimeException("名字需要在2到4之间");
}
//小于6 或者判断出来是 true
//如果方法返还false 那么取反就是true 成立 说明有非数字
if (!(password.length() == 6 && isNumber(password))) {
throw new RuntimeException("密码全数字且必须6位");
}
int i = emile.indexOf("@");
int j = emile.indexOf(".");
// i与j>0 也就是不能小于0 不能没有
if (!(i > 0 && j > i)) {
throw new RuntimeException("邮箱格式错误");
}
}
public static boolean isNumber(String str) {
char[] chars = str.toCharArray();
for (char aChar : chars) {
//使用包装类方法 自动装箱
if (!(Character.isDigit(aChar))) {//如果false 说明不对
return false;
}
}
//两种方法 因为在 字符码中 是顺序排列的
for (char aChar : chars) {
if (aChar < '0' || aChar > '9') {
return false;
}
}
return true;
}
}
/*class emile{
private String name;
private int password;
private String Emile;
public emile(String name, int password, String emile) {
this.name = name;
this.password = password;
Emile = emile;
}
}*/
题目3:
还是操作String类型
注意:
-
if的判断 不是范围判断没必要取反
-
String format = String.format("%s,%S.%c", names[2], names[0], names[1].toUpperCase().charAt(0));
字符串格式化的方法
package com.javastudy;
/**
* @Auther: qianl
* @Date: 2022/12/6 00:24
* @Description:
*/
public class HomeWork04 {
public static void main(String[] args) {
String s = "Huang Hong Nan";
HomeWork04.theName(s);
}
/**
* @return java.lang.String
* 1.进行分割 split(" ")
* 2.对得到的String[] 进行格式化 String.format()文本格式化
* 3. 对输入的信息进行校验
* @Author qianl
* @Description //TODO
* @Date 0:28 2022/12/6
* @Param [name]
**/
public static void theName(String name) {
if(name ==null){
System.out.println("name" +
"不能为空");
return;
}
//这个是根据什么来分段的意思
String[] names =name.split(" ");
//不是范围判断没必要取反
if (names.length !=3){
System.out.println("输入格式不对");
return;
}
//返回人名 而且首字母大写
String format = String.format("%s,%S.%c", names[2], names[0], names[1].toUpperCase().charAt(0));
System.out.println(format);
//Nan,HUANG.H
}
}
题目4:
判断大小写数量和数字数量
主要是使用Character的isDigit方法来判断
1.自动装箱 2.多重判断
也可以使用 askii码 'A'到'Z' 'a'到'z' '0'到'9'
package com.javastudy;
/**
* @Auther: qianl
* @Date: 2022/12/6 00:43
* @Description:
*/
public class HomeWork05 {
public static void main(String[] args) {
String str = "213daFfaFAFASC8907d--fad1== =dc";
HomeWork05.isElementNumber(str);
//大写字母有7个小写字母有10个数字8个
}
public static void isElementNumber(String string){
if (string==null){
System.out.println("不能为空");
return;
}
char[] chars = string.toCharArray();
int uppercase = 0;
int lowercase = 0;
int number = 0;
for (char aChar : chars) {
if (Character.isUpperCase(aChar)){
uppercase++;
}else if(Character.isLowerCase(aChar)){
lowercase++;
}else if (Character.isDigit(aChar)){
number++;
}
}
System.out.println("大写字母有"+uppercase+"个" +
"小写字母有"+lowercase+"个" +
"数字"+number+"个");
}
}
就不用转换为char[]了
for (int i = 0; i < string.length(); i++) {
if(string.charAt(i) >='0'&&string.charAt(i)<='9'){
number++;
}else if(string.charAt(i)>='A'&&string.charAt(i)<='Z'){
uppercase++;
}else if(string.charAt(i)>='a'&&string.charAt(i)<='z'){
lowercase++;
}
}
题目5:
String内存测试题
s1 常量池
a 堆
b 堆
a == b false
a.equals(b) 没重写 false
a.name == b.name String 都指向同一个常量池地址 true
s4 堆
s5 常量池
s1 == s4 常量池 和堆 false
s4 == s5 堆 常量池 false
t1 = "hello"+s1 就不是直接赋值了 所以是 堆
t2 直接赋值 常量池
t1.intern() == t2 true
第十三章总结:
看视频 韩顺平 497
集合
Collection和Map接口之间的主要区别在于:Collection中存储了一组对象,而Map存储关键字/值对,即。
集合主要是两组(单列集合, 双列集合)
Collection的 List和set 都是单列集合
Map则是 双列集合 存放 K-V
数组的缺陷:
- 长度指定,指定后不可更改
- 保存的必须是同一类型的元素
- 使用数组继进行增删元素的代码比较麻烦 (需要拷贝,扩容,然后增加)
- 管理对象很麻烦
集合:
- 可以动态保存才能多个对象 (增删)
- 提供一系列方便的操作对象的方法: add,remove,set,get等
- 使用集合添加,删除新元素 更简洁了
用于储存对象的数组 : 根据储存方式(collection 储存一组对象; map是储存一组键值对) 可否为空 可否重复 可否排序等 分多种.
@SuppressWarnings("all")
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("111");
arrayList.add("222");
HashMap hashMap = new HashMap();
hashMap.put("111","qwe");
hashMap.put("222","asd");
}
Collection(收集 集合):
有两个子接口 : set(设置) list(列表)
1、Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。
一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。
Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。
2、所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数和有一个Collection参数的构造函数
其中无参数的构造函数用于创建一个空的Collection;有一个Collection参数的构造函数用于创建一个新的Collection。
结构图:
接口特点:
继承了接口Iterable (iterable 可迭代的 可重复的)
public interface Collection<E> extends Iterable<E> {
- collection实现子类 可以存放多个元素, 每个元素可以是Object
- 有些collection的实现类可以存放重复元素 有些不行
- 有些是有序的(List接口 列表 ) 有些不算有序的(Set接口)
- collection 接口 没有时间的实现子类 只能依靠其子接口的Set和List接口来实现 (多态继承)
collection 接口方法:
注意:
add和addAll 的区别 :
-
add和addAll 的区别 : add添加一个实例 (作为一个List实例元素)
但是addAll() 可以增加 一个实例中的多个元素 这些元素全部挨个当作List实例元素放进新List
ArraysList类 list01 空的 ----> []
ArraysList类 list02 中 有["element01","element02","element03"]
if add list01的元素是: ["list02"]
else id addAll list01的元素是: ["element01","element02","element03"]
removeAll同理
-
1
@SuppressWarnings("all")
public static void main(String[] args) {
//父类引用,指向子类对象 多态
List list = new ArrayList();
//add() 可以增加不同类型
list.add("jack");
list.add(10);
list.add(true);
System.out.println("list "+list);
//list [jack, 10, true]
//remove() 按索引 (remove (int))和按对象 (remove (Object) 也可以equals
list.remove(1);
// list.remove("jack");
System.out.println("list "+list);
//list [jack,true]
//contains() 查找是否存在某元素 返回boolean
System.out.println(list.contains("jack"));//true
//size() 获取元素个数 返回int
System.out.println(list.size());//2
//isEmpty 判断是否为空 返回boolean
System.out.println(list.isEmpty());//false
//clear 清空元素
list.clear();
System.out.println(list.isEmpty());//true 直接输出list的话 返回: []
ArrayList arrayList0 = new ArrayList();
arrayList0.add("红楼梦");
arrayList0.add("三国演义");
//addAll 添加多个元素 不能是空元素
list.addAll(arrayList0);
System.out.println("list "+list);
//list [红楼梦, 三国演义]
//containsAll 查看多个元素是否存在
System.out.println(list.containsAll(arrayList0));
//removeall 删除多个元素
System.out.println(list.removeAll(arrayList0));
}
Collection遍历元素:使用迭代器 Iterator
简化版本: for-each
-
Iterator迭代器 主要用于遍历Collection集合中的元素
-
所有实现了Collection接口的集合类都有一个iterator()方法, 用于返回一个实现了iterator接口的对象, 即返回一个迭代器.
-
iterator的结构
在Collection的父类接口 Iterable中
public interface Iterable<T> { /** * Returns an iterator over elements of type {@code T}. * * @return an Iterator. */ Iterator<T> iterator();
-
iterator仅用于遍历集合, Iterator 本身并不存放对象.
注意点: 返回的是一个实现了 iterator接口的 的实例对象 所以需要接收该对象 然后去遍历
实例: 调用iterator.next()方法前 必须先调用 iterator.hashNext() 方法
如果不调用 且下一条记录无效时 抛出NoSuchElementException异常
iterator.next() 返回的是Object类型
使用itit 可以快速生成 使用Ctrl+j 可以查看所有快捷键
注意重置迭代器: iterator = list.iterator();
List list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
Iterator iterator = list.iterator();
//iterator.hasNext() 是否存在下一个元素 返回boolean
//iterator.next() 表示下一个元素 1.指针下移 2.将下移以后的集合位置上的元素返回.
//使用itit 可以快速生成 使用Ctrl+j 可以查看
while (iterator.hasNext()){
//iterator.next() 返回的是Object类型
Object next = iterator.next();
System.out.println(next);
}
//1
//2
//3
//当while后 这时候iterator迭代器 指向了最后的元素
//再取会报错 NoSuchElementException
// 需要重置迭代器:
iterator = list.iterator();//重置迭代器了
for - each:
for-each 也是底层使用了迭代器
增强foreach 可以代替iterator迭代器 //难怪快捷键是 iter
增强for 就是简化版的iterator 本质一样, 只能用于遍历集合或数组
//iter
for (Object o : list) {
System.out.println(o);
}
底层依旧是迭代器:
for-each 使用debug 追进去也是Iterator
public Iterator<E> iterator() {
return new Itr();
}
//一样使用了 hasNext 和next
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
List接口:
List 实现接口Collection的接口 也有他自己的方法 (通过索引操作集合)
-
可重复 元素有序 (添加顺序与取出顺序一致) 使用的是线性方式 (队列) 存储
-
支持索引 Index 每个元素都有对应的顺序索引 即 支持索引
-
List容器中的元素都对应一个整数型的序号记载着容器中的位置,可以根据序号存取容器中的元素.
-
JDK api中List接口的实现类有:
java.util Interface List<E> //Type Parameters: E - the type of elements in this list //All Superinterfaces: 全部父类接口 Collection<E>, Iterable<E> //All Known Implementing Classes: 实现子类 AbstractList, AbstractSequentialList, "ArrayList", AttributeList, CopyOnWriteArrayList, "LinkedList", RoleList, RoleUnresolvedList, "Stack", "Vector"
常用的: ArrayList LinkedList Vector
1.使用get() 方法 可以获取索引的值 Vector LinkedList 都是可以使用同样的方法去遍历
所以可以靠索引 使用普通for循环来遍历:
System.out.println(list.get(1));
//2
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
2.add() addAll()
3.indexOf(Object obj) 返回首次出现的位置
4.lastIndexOf(Object obj) 返回最后一次出现的位置
5.remove(int index) 通过索引移出元素 有重复元素 移除索引低的哪个
6.removeAll() 移除目标集合中 符合另一个集合中所有的对应元素
7.set(int index,Object ele) 指定index位置为ele 替换
8.subList(int fromIndex,int toIndex) 返回从fromIndex到toIndex 位置的子集合
注意子集合 是左闭右开
而且: 二者会互相影响 (即 subList是直接引用了原来的List) 而使用subArraysList不会
1、对父(sourceList)子(subList)List做的非结构性修改(non-structural changes),都会影响到彼此。
2、对子List做结构性修改,操作同样会反映到父List上。
3、对父List做结构性修改,会抛出异常ConcurrentModificationException。
原因: subList 是ArraysList的一个内部类
list.set(1,"ele");
System.out.println("====");
// 注意 截取片段是左闭右开 [0,3);
List list01 =list.subList(0,3);
for (Object o : list01) {
System.out.println(o);
}
//1
//ele
//3
1、List是有序的Collection,使用此接口能准确的锁定每个元素的位置,可以通过索引访问List中的元素,类似于java数组。
2、List中是允许存在相同的元素的。
3、List除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator()接口,
和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。
4、实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。
List依据条件排序
public static void main(String[] args) {
List arrayList = new ArrayList();
arrayList.add(new Library("红楼",11.1,"曹雪芹"));
arrayList.add(new Library("西游",13.4,"吴承恩"));
arrayList.add(new Library("水浒",3.1,"罗贯中"));
System.out.println("排序前===");
for (Object o : arrayList) {
System.out.println(o);
}
sort(arrayList);
System.out.println("排序后===");
for (Object o : arrayList) {
System.out.println(o);
}
}
public static void sort(List lists){
for (int i = 0; i <lists.size()-1 ; i++) {
for (int j = 0; j < lists.size()-1-i; j++) {
//因为已经用o和 o1 复制了一份
Library o = (Library)lists.get(j);
Library o1 =(Library)lists.get(j+1);
//如果传进来的值大于0 说明 前一个大于后一个
if (o.getPrice()>o1.getPrice()){//交换
lists.set(j,o1);
lists.set(j+1,o);
}
}
}
}
}
ArrayList:
结构:
特点:
- 可以存储null值. 可以重复(List) 可以多个
- 实现方式是数组 存储空间连续
- 基本等同Vector
- 线程不安全
使用场景:
-
ArrayList 底层是通过维护了一个Object数组实现的,特点是查询速度快,增加删除慢
-
ArrayList 使用无参构造函数创建对象时,Object数组默认的容量是10,当长度不够时,自动增长0.5倍,也就是原来数组长度的1.5倍
-
当数据需要频繁的查询,而增加删除较少的时候,建议使用ArrayList数组存储数据。
-
多线程不使用ArrayList 线程不安全 源码没有synchronized
ArrayList底层详解:
- ArrayList 底层是通过维护了一个Object数组 elementData 实现的,特点是查询速度快,增加删除慢
- ArrayList 使用无参构造函数创建对象时,初始容量是0 ,第一次添加默认的容量是10,当长度不够时,自动增长0.5倍,也就是原来数组长度的1.5倍
- 如果使用指定大小的构造器 扩容会直接是elementData为1.5倍
1.Object[] 意味着什么都能放进去
无参构造器:底层
transient: 表示不会被序列化(不会转化成字节流) 表示短暂的,瞬间
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//DEFAULTCAPACITY_EMPTY_ELEMENTDATA:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//this.elementData ;
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
//注意这句话: 第一次添加:
"will be expanded to DEFAULT_CAPACITY when the first element is added."
"将在添加第一个元素时扩展为DEFAULT_CAPACITY。"
//DEFAULT_CAPACITY
private static final int DEFAULT_CAPACITY = 10;
//添加时:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e; //先赋值 后++
return true;
}
//ensureCapacityInternal 确保内部容量
//ensureCapacityInternal方法
private void ensureCapacityInternal(int minCapacity) {
//如果大小不变 那么就赋值 DEFAULT_CAPACITY = 10; 给 minCapacity (最小容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// DEFAULT_CAPACITY, minCapacity 取二者最大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//ensureExplicitCapacity 确保显式容量
//minCapacity 传入的是这个 {10}
//ensureExplicitCapacity方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//记录修改次数 线程问题
// overflow-conscious code 10>0
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//真正的扩容方法:
oldCapacity>>1 就是除于2
// 所以是1.5倍 oldCapacity + (oldCapacity >> 1)
// 最终 调用copyof方法 进行扩容
//Arrays.copyOf(elementData, newCapacity);
//第一次扩容 0 + 10/2 = 5;
//因为5 - 10 <0
//newCapacity = minCapacity; 新容量 = 10;
//又因为新容量小于最大值(MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;)
//if不进入
//Arrays.copyOf(elementData, newCapacity);
//第二次扩容 10 + 10/2 = 15
// 15 - 10 >0
//if不进入 newCapacity =15;
//一样小于最大容量
//if不进入
//Arrays.copyOf(elementData, newCapacity);
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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:
elementData = Arrays.copyOf(elementData, newCapacity);
}
Vector 底层:
Vector类定义: 结构
compact1, compact2, compact3
java.util
Class Vector<E>
java.lang.Object
java.util.AbstractCollection<E>
java.util.AbstractList<E>
java.util.Vector<E>
All Implemented Interfaces:
Serializable, Cloneable, Iterable<E>, Collection<E>, List<E>, RandomAccess
Direct Known Subclasses:
Stack
Vector类:
- 底层也是一个Object数组 elementData;
- Vector是线程同步的 即线程安全的 Vector的方法synchronized(同步的) 的
- 在开发中 需要线程同步时 使用Vector
无参构造器:
/** 构造一个空 向量 他的内部大小为10 当他的容量增量为0时
* Constructs an empty vector so that its internal data array
* has size {@code 10} and its standard capacity increment is
* zero.
*/
public Vector() {
this(10);
}
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//2倍
// int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
// capacityIncrement : oldCapacity);
private void grow(int minCapacity) {
// overflow-conscious code
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);
}
ArrayList与Vector比较
名字 | 底层结构 | 版本 | 线程安全 效率 | 扩容倍率 |
---|---|---|---|---|
ArrayList | 可变数组Object[] | 版本1.2 | 不安全,效率高 | 有参:1.5倍 无参:第一次add 后容量为10 第二次开始按1.5倍 |
Vector | 可变数组Obect[] | 版本1.0 | 安全,效率不高 | 有参:2倍 无参:初始为10,满后 add 2倍扩容 |
LinkedList: link
- 双向链表和双端队列 (first last)
- 可以店家任意元素 实现了List 元素可重复 可以存放null
- 线程不安全, 没有实现同步
类结构:
compact1, compact2, compact3
java.util
Class LinkedList<E>
java.lang.Object
java.util.AbstractCollection<E>
java.util.AbstractList<E>
java.util.AbstractSequentialList<E>
java.util.LinkedList<E>
Type Parameters:
E - the type of elements held in this collection
All Implemented Interfaces:
Serializable, Cloneable, Iterable<E>, Collection<E>, Deque<E>, List<E>, Queue<E>
- LinkedList底层维护了一个双向链表
- LinkedList中维护了两个属性 first 和 last 分别指向首节点和尾节点
- 每个节点(Node对象) 都维护了prev next item三个属性 prev指向前一个, 通过next指向后一个节点。 最终实现双向链表
- 所以LinkedList的元素的添加和删除,不通过数组完成,效率较高
first前节点(↓) 中间 last尾节点(↓)
添加:b指向前一个的a a的next 指向b 同时 b的next指向c c的prev指向b
删除:a的next指向c c的prev指向a b被删除
改查
自建双链表CRUD
增删改查的实现 还可以封装起来到Node里 注意每次(头 尾)增改
还有遍历后 first 和 last 的指针回溯问题
package com.javastudy;
import javax.xml.soap.Node;
/**
* @Author: qianl
* @Date: 2022/12/10 10:51
* @Description: item 类型是Object
*/
public class Node_ {
public static void main(String[] args) {
Node01 one = new Node01("第一个");
Node01 two = new Node01("第二个");
Node01 there = new Node01("第三个");
//双向链表
//首节点
one.next = two;
//中间节点
two.prev = one;
two.next = there;
//尾节点
there.prev = two;
Node01 first = one;// 指向头节点
Node01 last = there;//指向尾节点
//从头到尾遍历
while (true) {
//一开始 first = one;
//最后 first 指向了 there的next == null
if (first == null) {
break;
}
//输出 one 的内容
System.out.println(first);
//first = one的next
first = first.next;
}
//从尾到头遍历
while (true){
if (last == null){
break;
}
System.out.println(last);
last=last.prev;
}
//添加数据
Node01 five = new Node01("添加第五个在尾");
//尾添加
there.next = five;
five.prev = there;
last = five;
Node01 zero = new Node01("添加第0个在头");
//头添加
one.prev = zero;
zero.next = one;
first = zero;
Node01 four = new Node01("中间添加第四个");
//中间添加
there.next =four;
four.prev =there;
four.next =five;
five.prev =four;
//遍历
while (true){
if (first==null){
break;
}
System.out.println(first);
first = first.next;
}
//删除:
//删除第四个
there.next = five;
five.prev = there;
//遍历
System.out.println("====删除后====");
//重新指针:
first = zero;
while (true){
if (first==null){
break;
}
System.out.println(first);
first = first.next;
}
}
}
class Node01 {
private Object item;//存储内容
public Node01 next;//指向下一个节点
public Node01 prev;//指向上一个节点
private int size = 0;
public Node01(Object item) {
this.item = item;
}
@Override
public String toString() {
return "Node_{" +
"item=" + item +
'}';
}
}
LinkedList的CRUD和常用方法:
- 删除remove 增加add 都有专门的 堆first 和last的操作 removeFirst, addLast等
- set方法 修改 使用(index,element)来操作 单独查看首尾
- get方法 获得 查看 使用index 或者单独查看首尾
- poll() 投票 是出栈操作 从头开始 然后 依次出栈
- push() 推动 入栈操作 从头开始 依次入栈
- offer() 提供 查询是否有什么数 返回类型: true
package com.javastudy.linkedList_;
import java.util.LinkedList;
import java.util.List;
/**
* @Author: qianl
* @Date: 2022/12/10 13:02
* @Description:
*/
public class LinkedList_ {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add("第一个");
linkedList.add("第二个");
linkedList.add("第三个");
linkedList.add("第五个");
for (Object o : linkedList) {
System.out.println(o);
}
//增首尾(有专门的方法)
linkedList.addFirst("第0个");
linkedList.addLast("第六个");
for (Object o : linkedList) {
System.out.println(o);
}
//删除
linkedList.remove("第五个");
//删除首尾
linkedList.removeFirst();
linkedList.removeLast();
System.out.println(linkedList.size());
System.out.println("======");
//获取第几个 首尾元素
System.out.println(linkedList.get(1));
System.out.println(linkedList.getFirst());
System.out.println(linkedList.getLast());
//peek 窥探 查看?
System.out.println(linkedList.peek());
System.out.println(linkedList.peek());
//poll 投票 其实就是 弹夹弹出元素 出栈一样 返回被弹出的数量
// System.out.println(linkedList.poll());
// System.out.println(linkedList.poll());
linkedList.set(1,"我更改了第二个");
System.out.println("====");
for (Object o : linkedList) {
System.out.println(o);
}
//?
System.out.println(linkedList.pop());
System.out.println(linkedList.indexOf("第三个"));
//1
// offer 提供
System.out.println(linkedList.offer("第三个"));
//true
System.out.println("======");
for (Object o : linkedList) {
System.out.println(o);
}
//push 推 推动 ?
linkedList.push("第三个");
System.out.println("======");
for (Object o : linkedList) {
System.out.println(o);
}
/*======
我更改了第二个
第三个
第三个
======
第三个
我更改了第二个
第三个
第三个*/
}
}
LinkedList解析:
//无参方法
public LinkedList() {
}
//此时 first = null; last = null;
//指向add()后
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); // (null,e,null)
//last = 这个节点
last = newNode;
if (l == null) // √ first = newNode;
first = newNode;
else
l.next = newNode;
size++; //长度
modCount++; //模式记数
}
//所以第一次跑完 就是 first = Node01 (null,e,null) = last
//第二次 l = last = Node01
// Node01.next = Node02
// first = Node01(null,e,Node02)
// Node02(Node01,e,null) =last
ArrayList 和LinkedList 的比较
子类 | 底层结构 | 增删效率 | 改查效率 | Index |
---|---|---|---|---|
ArrayList | 可变数组(存储空间在一起) | 较低 数组扩容 | 较高 | 可以使用下标 |
LinkedList | 双向链表(存储空间不一定在一起) | 较高, 通过链表追加 | 较低 | 可以使用下标 |
使用场景
-
增删多使用ArrayList
-
改查多 使用LinkedList
-
一般 程序都是查询 使用ArrayList
-
也可以灵活运用 一个模块使用ArrayList 一个模块使用LinkedList
双链表:
1.1双向链表
相较单链表,双向链表除了data与next域,还多了一个pre域用于表示每个节点的前一个元素。这样做给双向链表带来了很多优势:
-
单向链表查找的方向只能是一个方向,而双向链表可以向前或者向后查找;
-
单链表如果想要实现删除操作,需要找到待删除节点的前一个节点。而双向链表可以实现自我删除。
双向链表结构示意图如下:
1.2 双向链表实现思路
与单链表实现类似,交集部分不再赘述,详情可参考文章:Java数据结构:单链表的实现与面试题汇总
遍历:
与单链表遍历方式一样,同时,双向链表可以支持向前和向后两种查找方式
添加:
添加到末尾
先找到双向链表最后一个节点
cur.next = newNode;
newNode.pre = cur;
按照no顺序添加
”先判断该链表是否为空,如果为空则直接添加
如果不为空则继续处理
根据no遍历链表,找到合适的位置
newNode.next = cur.next;
cur.next = newNode;
newNode.pre = cur;
修改操作与单链表实现步骤一致“
删除操作:
双向链表可以实现自我删除
直接找到待删除的节点
cur.pre.next = cur.next;
cur.next.pre = cur.pre;
需要特别注意是否为最后一个元素,如果为最后一个元素,cur.next为null,此时使用cur.next.pre会出现空指针异常,所以,如果为最后一个元素,则该步骤可以省略,cur.next为null即可。
以删除红色的2号节点为例:
1.3双向链表实例:
Java数据结构:双向链表的实现-阿里云开发者社区 (aliyun.com)
Set接口:
基本特点:
- 无序(没有index) 添加和取出的顺序不一致
- 不允许重复元素, 至多只有一个null
- JDK中的实现类有:
- 重点是HashSet 和TreeSet子类
compact1, compact2, compact3
java.util
Interface Set<E>
Type Parameters:
E - the type of elements maintained by this set
All Superinterfaces:
Collection<E>, Iterable<E>
All Known Subinterfaces:
NavigableSet<E>, SortedSet<E>
All Known Implementing Classes:
AbstractSet, ConcurrentHashMap.KeySetView, ConcurrentSkipListSet, CopyOnWriteArraySet, EnumSet, "HashSet", JobStateReasons, LinkedHashSet, "TreeSet"
Set方法:
注意没有索引 所以不能使用for循环遍历
底层是数组加链表
** //1.不能重复 一个null
//2.无序(存入取出顺序不一致)
//3.取出顺序是固定的**
package com.javastudy.Set_;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* @Author: qianl
* @Date: 2022/12/11 03:02
* @Description:
*/
public class SetMethod_ {
public static void main(String[] args) {
//1.不能重复 一个null
//2.无序(存入取出顺序不一致)
//3.取出顺序是固定的
Set set = new HashSet();
set.add("one");
set.add("two");
set.add("there");
set.add("four");
set.add(null);
set.add(null);//添加重复元素 不会重复添加
set.add(null);
//可以看见输出和输入顺序不一致
System.out.println("set" + set);
//set[null, four, one, there, two]
//删除 null值
set.remove(null);
//1.遍历方法: 迭代器
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
//2,增强for for - each
for (Object o : set) {
System.out.println(o);
}
//for循环 不能通过索引进行
/* for (int i = 0; i < set.size(); i++) {
}*/
}
}
HashSet:
- 实现了Set接口.
- 本质是一个hashMap (Map接口 键值对) 但是Collection中是 值
- 可以存放null值, 但是只能有一个null
- HashSet不保证元素是有序的,取决于hash后,再索引的结果 存取顺序不一定一致
- 但取出顺序固定
- 不能有重复元素/对象 Set接口的特点
- 本质是(数组 + 链表 + 红黑树)
结构:
本质是一个HashMap()
//本质是一个HashMap()
public HashSet() {
map = new HashMap<>();
}
HashSet()方法
package com.javastudy.Set_;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
/**
* @Author: qianl
* @Date: 2022/12/12 02:02
* @Description:
*/
public class HashSet_ {
@SuppressWarnings("all")
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("Rose");//会返回true
hashSet.add("Tom");
hashSet.add("Jere");
hashSet.add(null);
if (!(hashSet.add(null))){ //false
System.out.println("加载失败");
}
//迭代器加载
Iterator iterator = hashSet.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.print(next+" ");
}
//删除Tom
hashSet.remove("Tom");
System.out.println("\n"+"=====");
//增强for加载
for (Object o : hashSet) {
System.out.print(o+" ");
}
/*null Tom Rose Jere
=====
null Rose Jere */
}
}
储存对象时的问题: 要看hashSet 的底层 hashMap 的 add的过程
New Dog("Tom"); 因为对象中equals 和 HashCode没重写
所以 使用的String的equals 比较值时相等
但存储的是Dog对象 在堆中是不一样的 地址 所以可以
new String("Tom"); 不是直接赋值 所以也是堆中 然后引用的是对象在堆中的地址 equals重写了所以比较的是值
HashMap底层是(数组+链表+红黑树)
简单模拟成数组+链表结构 就是数组 16个元素 每个元素是一个链表
链表数组模拟 红黑树 就是将链表根据二分法分红黑树.
package com.javastudy.Set_;
/**
* @Author: qianl
* @Date: 2022/12/12 02:26
* @Description:
*/
public class HashSetStructure {
public static void main(String[] args) {
Node[] nodes = new Node[16];
//创建节点
Node john = new Node("john", null);
//假设hashCode 为2
nodes[2] = john; //将john挂载到2中
Node jack = new Node("jack", null);
john.next = jack;//将jack 挂载到 john后面
Node rose = new Node("Rose", null);
jack.next = rose;//将 rose 挂载到 jack后面
Node lucy = new Node("Lucy", null);
nodes[3] = lucy;//将lucy挂载到3中
System.out.println(nodes);
}
}
class Node{
Object item;//存元素
Node next;//下一个节点
public Node(Object item) {
this.item = item;
}
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
HashSet 添加是如何实现的 (hashCode() + equals())
-
先会得到一个hash值 然后进行转换---->索引值
-
然后查看索引位置是否有值 没有则直接放入
-
有值则会equals 进行比较 (对象比较地址,数字类型比较大小) ,相同放弃 不同添加在后面
-
链表数<=8
-
并且table >=64 则会变成红黑树
-
链表到8 而且table到64(初始16,两倍扩容) . 就会变成红黑树
所以 因为new String("Tom") 是字符串 的equals重写了 比较内容 所以没加入了
HashSet的add方法 ---->HashMap 的 put方法 运转机制:
package com.javastudy.Set_; import java.util.HashSet; /** * @Author: qianl * @Date: 2022/12/12 02:26 * @Description: */ public class HashSetStructure { public static void main(String[] args) { HashSet hashSet = new HashSet(); hashSet.add("John"); //1. 构造器 public HashSet() { map = new HashMap<>(); } //2.add方法 因为HashMap是键值对 所以有PRESENT 进行占位 是公用的 private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT)==null; (e,new Object()) } //3. 走map.put方法 其中走了 hash(key) 方法 主要是取出不一样的 hash值 static final int hash(Object key) { int h; //也就是 key(e)不是空值 则传入 key(e).hashCode() 异或运算 h>>>16 无符号除于16个2 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } //4. 传入hash(key)值 : table 的 索引值; 然后 key: e 元素; value : new Object(); false;true final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; Node<K,V>[] tab的数组长度: n //第一次扩容 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //resize方法: // 初创: 长度:16 //newCap = DEFAULT_INITIAL_CAPACITY; //临界值: 0.75*16 = 12; 也就是 12个都有值了就会扩容 //newThr = (int)(DEFAULT_LOAD_FACTOR*DEFAULT_INITIAL_CAPACITY); //threshold = newThr; //创建数组: Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; table 是一个Node<K,V>[]类型 长度为16的数组 //(n - 1) & hash 按位与计算 //p = tab[i = ---] == null; p没有 值 //就将值加入这个 tab[i]里 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null) //此时(hash表示在table的位置,key("john"),value = PRESENT,next = null) else { //在需要局部变量时,再创建 Node<K,V> e; K k; //如果索引位置对象的链表的第一个元素和准备添加的key(元素)的hash值一样 //而且满足以下之一: //1.== 二者是同一对象 //2.或者equals()比较后相同 比如String 就是内容相同 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //在判断 p 是不是一个红黑树, //如果是一个红黑树 就putTreeVal方法加入 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { //不是一个红黑树的话, for循环 遍历该p的所有链表进行比较 for (int binCount = 0; ; ++binCount) { //(1.)直接和第二个开始比较 比较完了都没有 那就加入到链表最后 // 添加到链表后,会立刻判断链表是否到8个节点,到了就转为红黑树 // 在treeifBin()中 还有判断,如果tab长度<64 则扩容 // 两个都满足 才开始转红黑树. // 否则break; if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } //(2)如果链表中有相同的 则break; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e;//将指针后移, } } //返回oldValue 那么不等于 null 就最终返回false. 添加失败 if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; //修改一次 模板计数+1 if (++size > threshold) //如果 ++size 大于临界值: 12 resize(); // 扩容 afterNodeInsertion(evict); return null; //返回 null } //返回到HashSet中 如果是null == null 则返回True public boolean add(E e) { return map.put(e, PRESENT)==null; } } }
HashSet中 HashMap扩容红黑树机制 (待学,数据结构时):
//3. 在java8中, 如果一条链表元素个数到达 8 并且 table的大小 >= 64, 就会进化成树化 (红黑树)
// 否则继续扩容 继续扩容 元素还是进入同一个链表 就会到9 到10 最多到10 (此时table必定到64)
// 所以链表最大值为10 然后数组中该链表 将变为红黑树类型(TreeNode)
- ?表示不确定的 java 类型
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值中的Key Value
- E (element) 代表Element
//1. 底层是HashMap 第一次添加 table数组扩容到 16 临界值(threshold)是16*加载因子(loadFacthor)是0.75 = 12;
//2. 数组到临界值后, 扩容2倍 16 * 2 =32; 新临界值: 32 * 0.75 = 24;
//3. 在java8中, 如果一条链表元素个数到达 8 并且 table的大小 >= 64, 就会进化成树化 (红黑树)
// 否则继续扩容 继续扩容 元素还是进入同一个链表 就会到9 到10 最多到10 (此时table必定到64)
// 所以链表最大值为10, 然后开始链表--->红黑树
//树华 treeify 使树木化 Bin 箱子 柜子
//进入该方法 表示链表元素到达 8
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//如果数组长度小于64, 就进行扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
//否则, 如果传入的 该链表的元素不是null
else if ((e = tab[index = (n - 1) & hash]) != null) {
//局部变量
TreeNode<K,V> hd = null, tl = null;
//至少do一次
do {
// 将链表中全部e循环一遍 进入replacementTreeNode方法 (更换TreeNode)
//返回 TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
// return new TreeNode<>(p.hash, p.key, p.value, next);}
//返回一个标准的TreeNode 并赋值给p
TreeNode<K,V> p = replacementTreeNode(e, null);
//第一个元素,使得hd = p , tl = p;
//后面所有元素挂在t1上;
//那么 hd只有初始元素, tl 有8个元素
if (tl == null)
hd = p;
//第二个元素 挂在t1后面
else {
p.prev = tl;
tl.next = p;
}
//使得t1 = p;
tl = p;
} while ((e = e.next) != null);
//走了上面必定true 进入 treeify中
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
//treeify(tab);方法`
final void treeify(Node<K,V>[] tab) {
//局部变量
TreeNode<K,V> root = null;
//循环,this = 就是tab ,
for (TreeNode<K,V> x = this, next; x != null; x = next) {
//next = x 的下一个
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;//初始化this的左右树
//第一个元素 rood = 第一个元素, parent表示没有父类, red表示为黑;
if (root == null) {
x.parent = null;
x.red = false;
root = x;
}
//第二个元素开始:
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;
//死循环 直到跳出 p = root = 第一个元素 x
for (TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
//dir记录元素状态
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root);
}
//树链表的属性:
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
HashSet实践:
题目1:
- 注意重写equals 帮助HashSet判断是否重复 考虑是否要重写hashCode
package com.javastudy.Set_;
import java.util.HashSet;
import java.util.Objects;
/**
* @Author: qianl
* @Date: 2022/12/13 03:44
* @Description:
*/
public class HashSetExercise {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
System.out.println(hashSet.add(new employee("a", 30)));
System.out.println(hashSet.add(new employee("b", 30)));
System.out.println(hashSet.add(new employee("b", 30)));
/*true
true
false*/
//测试一: 当删除重写的HashCode方法 就都是true了 进断点试试 会进入下面这个
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//表示tab[i] == null; 锁定的就不是数组tab[]中 同一个格子了
//测试二:放出hashCod
//1.在hashMap的put方法中 计算hash(key)使用了hashCod 得到4029
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//2.比较内容 e = p;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//3.不走加入链表的方法
//4.返回相同element 的原来那个元素 oldValue;
//5.最后返回的不是 null 所以返回false
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
}
class employee{
private String name;
private int age;
public employee(String name, int age) {
this.name = name;
this.age = age;
}
//重写equals 和hashCode 就是让 内容相同时,返回的hash值相同
@Override
public boolean equals(Object o) {
//同一对象 则返回true
if (this == o) return true;
//如果, 返回的类型 二者不都是employee类 返回false
if (o == null || getClass() != o.getClass()) return false;
//向下强转
employee employee = (employee) o;
//属性相等 则返回true;
return getAge() == employee.getAge() &&
getName().equals(employee.getName());
}
@Override
public int hashCode() {
//hash 返回 4029
return Objects.hash(getName(), getAge());
//1.使用的Arrays.hashCode;
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
// String.HashCode() 返回的是129;
// Integer.hashCode() 返回的是30;
//4. 31 * 129(前一个返回值) + 30(现在的返回值) = 3999 + 30 = 4029
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
//2.根据getName() 进入element.hashCode() 动态绑定 进入子类方法
// getName()进入String的hashCode()
// 输入的是b 所以 返回的是:98 31*1+98 = 129
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
//3.getAge()进入Integer的hashCode()
// 返回的是30
public static int hashCode(int value) {
return value;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
也有不使用get方法进行重写的
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
employee employee = (employee) o;
return age == employee.age &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
题目2:
重写MyDate类就好了
LinkedHashSet:
-
LinkedHashSet 是 HashSet的之类
-
底层是一个LinkedHashMap 底层维护了一个 数组 + 双向链表
-
LinkedHashSet 根据元素的hashCode 值 来决定元素的存储位置,同时使用链表维护元素次序。
使得元素看起来是以插入顺序保存的。
-
LinkedHashSet 属于Set 不允许添重复元素 也没有下标
-
遍历(迭代) LinkedHashSet 也能确保插入顺序和遍历顺序一致
-
注意区别 就是 table数组部分 使用了链表的结构 进行元素管理:
head = 1 ;
1 pre = null , next = 2;
2 pre = 1 , next = 3;
3 pre =2, next = 4;
4 pre = 3, next = null
同时元素 4 = tail ;
LinkedHashSet细节:
-
是hash表(哈希值形成的数组,且有键值双属性) 又叫散列表 + 双向链表 最终转红黑
-
其中 链表的第一个元素是 head 最后一个元素是 tail (双向链表 : first 和 last)
-
每个节点都有 before 和 after 属性,(相当于Node的 pre 和 next) 这样可以形成双向链表
-
添加时:
tail.after= newElement;
newElement.before = tail;
tail = newElement;
-
这样有链表,那就可以while() 遍历链表的数据了
-
需要输出循序与插入顺序一致时 使用.
底层是LinkedHashMap 其中散列表是 LinkedHahMap类型 table是 HashMap$Node类型(链表) 而存入的元素类型是 LinkedHashMap$Enty
也就是 一个LinkedHashMap中 有 数组 table 每个格子按 HashMap$Node(单链表) 存进整个散列表中的元素类型是LinkedHashMap$Entry(双链表)
HashMap$Node: HashMap的内部类
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
LinkedHashMap$Entry: LikedHashMap的内部类 继承实现了HashMap 所以数据才能放进HashMap
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; // 相当于pre和next
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
和普通HashMap一样, 初始16(第一次扩容) 等链表>8 且 table >= 64 一样转红黑
LinkedHashSet:
package com.javastudy.Set_;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Objects;
/**
* @Author: qianl
* @Date: 2022/12/13 11:41
* @Description:
*/
public class LinkedHashSet_ {
public static void main(String[] args) {
LinkedHashSet<Car> cars = new LinkedHashSet<Car>();
cars.add(new Car("布加迪威龙",100400));
System.out.println(cars.add(new Car("奥迪", 105000)));
System.out.println(cars.add(new Car("奥迪", 105000)));
/*true
false
*/
System.out.println(cars);
//[Car{name='布加迪威龙', price=100400}, Car{name='奥迪', price=105000}]
}
}
class Car{
private String name;
private Integer price;
public Car(String name, Integer price) {
this.name = name;
this.price = price;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return Objects.equals(name, car.name) &&
Objects.equals(price, car.price);
}
@Override
public int hashCode() {
return Objects.hash(name, price);
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
}
TreeSet:
底层TreeMap 红黑树 二叉树(使用红黑来平衡左右枝)
结构图:
TreeSet 的构造器 有Comparator 和Collection 的构造器.
可以设置根据不同的规则进行排序:
可以把传入的值按需要的规则进行排序, 比如: 字母的顺序进行排序
测试:
package com.javastudy.Set_;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
/**
* @Author: qianl
* @Date: 2022/12/15 11:42
* @Description:
*/
public class TreeSet_ {
public static void main(String[] args) {
//1. 可以使用无参构造器,创建TreeSet,依旧是无序的
//2. 字符串的大小来排序,可以传入匿名内部类: (比较器)
//3.
Set treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//下面的是String的compareTo方法
// return ((String)o1).compareTo((String)o2);//顺序排序
// return ((String)o2).compareTo((String)o1);//反序排序
//[tom, jack, d, c, b, a]
//按长度
// return ((String)o1).length()-((String)o2).length();
//[a, tom, jack] "b" "c" "d" 都不加入了
int num=0;
if((num=(((String)o1).length()-((String)o2).length()))==0)//相同长度字符串,默认比较
{
return ((String)o1).compareTo((String)o2);
}
else return -num;
//[jack, tom, a, b, c, d]
//考虑字符串相等的情况
/*
TreeSet<String> set = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int num=0;
if((num=(o1.length()-o2.length()))==0)//相同长度字符串,默认比较
{
return o1.compareTo(o2);
}
else return -num;
}
//return o1.length()-o2.length();
});
*/
/*
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
//返回的是字符比较数
return c1 - c2;
}
k++;
}
return len1 - len2;
}
*/
}
});
treeSet.add("jack");
treeSet.add("tom");
treeSet.add("a");
treeSet.add("c");
treeSet.add("d");
treeSet.add("b");
System.out.println(treeSet);
//[a, c, jack, tom]
//最终到这: HashMap的comparator
/*
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
*/
//开始put的debug
//1. put 动态绑定到Map的put
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
//2. 开始put
public V put(K key, V value) {
Entry<K,V> t = root;
//1. 加载第一个元素的时候
if (t == null) {
compare(key, key); // type (and possibly null) check
//第一次的时候 传入的是相同的数, 所以是检查空值 如果空值就报错 key = key!
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
//2. 第二个的元素的时候 是否有comparator的传入
Comparator<? super K> cpr = comparator;
//3. 有就进行判定
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);//比较两者
if (cmp < 0) //如果比较值<0 就放左边
t = t.left;
else if (cmp > 0) // 否则放右边
t = t.right;
else // =0的时候
return t.setValue(value); //否则修改value 表示同等 替换
} while (t != null);
}
//没有comparator的时候 就进行
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked") //自带排序
Comparable<? super K> k = (Comparable<? super K>) key;
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);
}
// 排序完 加入数据
Entry<K,V> e = new Entry<>(key, value, parent);
// 开始添加 parent表示前一个的
// 开始进行 添加在左或者右
if (cmp < 0)
parent.left = e;
else
parent.right = e;
//插入后修复 修复二叉树
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
//2. compare(key, key); 会比较
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
}
}
Map(映射,地图):
1、Map对象中,每一个关键字最多有一个关联的值。 映射关系 Key - Value
所以Key为自变量 定义域内取值 , Value 则是应变量 二者存在函数关系 x = 100y x = Key, Value = Value; 但是 Value是更小范围 x是更大范围. (一个缩小范围的过程)
定义域指的是自变量的取值范围,而值域是指因变量的取值范围
2、不能包括两个相同的键,一个键最多能绑定一个值。null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。
null 在 table[0] 里面可以是链表 所以 可以有多个值是null;
3、当get()方法返回null值时,即可以表示Map中没有该键,也可以表示该键所对应的值为null。
因此,在Map中不能由get()方法来判断Map中是否存在某个键,而应该用containsKey()(存在key)方法来判断。
4、Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射集合.
Map结构图:
Map继承图:
Map特点:
-
和集合Collection 并列存在 区别 Collection(收集) 是只有ment 而Map则是有 Key(e) - Value
-
常用String类作为 Map的key;
-
不允许重复 Key null只能有一个 Value null可以有多个 所以不能使用get() (会获取key 也会获取Value 会判断所以返回的boolean) 来表明是否有某个值 要用containsKey() 存在Key(e) 吗 返回boolean;
Map方法:
测试CUDR
package com.javastudy.Map_;
import java.util.*;
/**
* @Author: qianl
* @Date: 2022/12/13 14:06
* @Description:
*/
public class HashMap_ {
private static final Object PRESENT = new Object();
public static void main(String[] args) {
// Hashtable
Map map = new HashMap();
System.out.println(map.put("1000", PRESENT));
//null
//1.增 加入多个value为null
map.put("2000",PRESENT);
map.put("null",null);
map.put("3000",null);
//2.删 remove方法
System.out.println(map.remove("1000"));
//输出:map
System.out.println(map);
//{null=null, 2000=java.lang.Object@1540e19d, 3000=null}
//是否存在key"1000"
System.out.println(map.containsKey("1000"));
//false
//3查 get()方法测试null
System.out.println(map.get("null"));
//null
System.out.println(map.get("2000"));
//java.lang.Object@1540e19d
// 只能查 key 是null 返回null 否则返回value;
/* public V get(Object key) {
HashMap.Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}*/
//4.1改 替代 替换Value
map.replace("2000",10);
/*default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
*/
System.out.println(map);
//{null=null, 2000=10, 3000=null}
//4.2改 也可以直接使用相同的k ,替换value;
map.put("2000",30);
//5. 遍历 KeySet() 返回 hashMap 中所有映射项的集合集合视图。
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
}
}
Map接口的特点:
存入的属性是 HashMap$Node 实现了 Entry 所以一对 k-v 就是一个Entry
- HashMap中 put元素
- 新建Node
- HashMap$Node 就是HashMap的内部类 底层会形成一个链表
-
Collection 维护的数组 Values 存进数组中 ?????????????????????? Set 不是Collection的子类接口吗 那这两区别是啥?
Set底层是Map类型 常用的HashSet 或者TreeSet 底层是 HashMap 和TreeMap
所以 底层是 数组 + 链表 + 红黑树
Set原因是 不能重复 Collection 应该指的是List 接口 就是可以重复
然后一个Entry封装了这两个属性 可以使用EntrySet() 返回键值对的值
Entry中有 K getKey() 和 V getValue(); 返回对应值
- 维护了一个RntrySet集合 里面类型都是Entry 每个键值对 就是一个Entry(词典条目)
- 底层
总结:
- Key值 封装在 Set set = new KeySet()中
KeySet() 本质是 KeyMap() 属性是HashMap$Node implements Map.Entry 也就是实现了Entry
-
value 封装在 Collection c = new Valuse()中
-
EntrySet集合 方便遍历 必须存放Entry 简直对 每一个对象 都有 k,v EntrySet<Entry<K,V>> 实际存放的是Entry的子类 HashMap$Node.
-
使用EntrySet 集合 方便遍历 里面有 K getKey() 和 V getValue();
Map方法的遍历方法:6种
首先了解其中的结构:
- 最外层是EntrySet集合
- Key是 KeySet ; Value 是Collection 类型的 Values 数组
遍历需要的方法:
EntrySet 的作用:
将Map类型转换为Set类型:
Node实现了Entry接口,Entry接口中K表示key,即键,V表示value,即值。
Entry( Node对象 )是Map集合中的一个对象元素,而Map集合正是由一个个Entry( Node对象 )所构成
正是因为Node实现了Entry接口,所以使用Entry的时候也可以使用其getValue()和getKey()方法
遍历Map元素 理解难点
package com.javastudy.Map_;
import java.security.Key;
import java.util.*;
/**
* @Author: qianl
* @Date: 2022/12/14 10:51
* @Description:
*/
public class MapFor {
public static void main(String[] args) {
Map map = new HashMap();
map.put("00", null);
map.put(null, "01");
map.put("02", "02");
map.put("03", "03");
map.put("04", "04");
//(1) 取出全部key
Set setMapKey = map.keySet();
System.out.println("\n"+"=======第一种方式: 增强for取出键值对");
//1.1 增强for 取出key 和 values
for (Object key : setMapKey) {
System.out.print("key-Values:" + key + "--" + map.get(key) + "\t");
}
System.out.println("\n"+"=======第二种方式: 迭代器取出键值对");
//2.1 迭代器 取出key 和 values:
Iterator iterator = setMapKey.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.print("key-Values:" + next + "--" + map.get(next) + "\t");
}
//(2) 将所有values取出
Collection valuesMapValue = map.values();
//可以使用Collections(工具里)的遍历方法
System.out.println("\n"+"===只取value====第一种方式: 增强for");
//2.1 增强for
for (Object value : valuesMapValue) {
System.out.print("Value:"+ value + "\t");
}
System.out.println("\n"+"===只取value====第二种方式: 迭代器");
//2.2 迭代器
Iterator iterator1 = valuesMapValue.iterator();
while (iterator1.hasNext()) {
Object next = iterator1.next();
System.out.print("Value:"+ next + "\t");
}
//(3) 使用EntrySet 来取出k-v
Set set = map.entrySet();
// map.entrySet() 调用了Map里entrySet()方法 将Map集合转成Set集合
/*
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
*/
//返回的是EntrySet() 类型: Set
//final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
System.out.println("\n"+"===EntrySet取Key+value====第一种方式: for");
//3.1 增强for
for (Object kv : set) {
// System.out.print(kv+"\t");
//1. 这里会直接输出: k=v : 00=null null=01 02=02 03=03 04=04
//System.out.println(kv.getClass());//class java.util.HashMap$Node
//2. Map.Entry 是 接口 Map 中的子接口 Entry 将该接口作为一个类型 和String一样 是一种类型
//3. 向下强转 由Set里元素类型是
// 强转Map.Entry 然后使用Entry中 规定的方法 Map.Entry里有getValue和getKey;
Map.Entry entry = (Map.Entry)kv;
System.out.print(entry.getKey()+":" + entry.getValue() + "\t");
}
System.out.println("\n"+"===EntrySet取Key+value====第二种方式: 迭代器");
//3.2 迭代器
Iterator iterator2 = set.iterator();
while (iterator2.hasNext()) {
Object kv = iterator2.next();
Map.Entry entry=(Map.Entry)kv;
System.out.print(entry.getKey()+":" + entry.getValue() + "\t");
}
}
}
Map接口练习:
employee
package com.javastudy.Map_;
import org.omg.PortableInterceptor.INACTIVE;
import java.util.*;
/**
* @Author: qianl
* @Date: 2022/12/14 12:29
* @Description:
*/
public class MapExercise {
public static void main(String[] args) {
Map hashMap = new HashMap();
hashMap.put("0001", new employee("黄", 30, 4000.0));
//看看能不能加入 可以 因为Key不一样 value一样无所谓
hashMap.put("0002", new employee("黄", 30, 4000.0));
hashMap.put("0003", new employee("南", 30, 4000.0));
hashMap.put("0004", new employee("新", 30, 4000.0));
System.out.println(hashMap);
//遍历1:EntrySet
Set set = hashMap.entrySet();
//for-each
for (Object o : set) {
Map.Entry entry = (Map.Entry) o;
employee employee01 = (employee) entry.getValue();
//判断
if (employee01.getP() > 1800) {
System.out.println(entry.getKey() + ":" + employee01);
}
}
//迭代器
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
Map.Entry Key=(Map.Entry)next;//相当于 k - v
//通过key 来getValue 进行判断
employee employee01 = (employee)Key.getValue();
if (employee01.getP() > 1800){
System.out.println(Key.getKey() + ":" + employee01);
}
}
//遍历2:先获取key
Set set1 = hashMap.keySet();
for (Object o : set1) {
//注意转换成当前类型
employee employee01 = (employee) hashMap.get(o);
//大于1800就输出
if (employee01.getP() > 1800) {
System.out.println(o + ":" + employee01);
}
}
}
}
class employee{
//姓名 年龄 工资
private String name;
private Integer age;
private Double p;
//get set equals hashCode toString(必须)
public employee(String name, Integer age, Double p) {
this.name = name;
this.age = age;
this.p = p;
}
}
HashMap:
基本小结:
-
key不能重复, value可以重复 允许null key-value (HashMap$Node类型)
-
添加相同迭代key,则覆盖原有key-value (key无关, value会替代)
-
HashSet一样,不保证映射顺序,底层是hash表的方式来存储的
-
没有同步,因此线程不安全
覆盖替换val
V putVal(int hash, K key," V value", boolean onlyIfAbsent,
boolean evict) {
//1
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//2
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
//3 重点 使得传入的值
e.value = value; //value 是当前传入的value值
afterNodeAccess(e);
return oldValue;
}
1、Map的实现类,默认是情况下是非同步的,可以通过Map Collections.synchronizedMap(Map m)来达到线程 同步
2、允许null,即null value和null key。 可以有null作为key或者 value;
当元素的顺序(升序降序)很重要时选用TreeMap,当元素不必以特定的顺序进行存储时,使用HashMap。
Hashtable的使用不被推荐,因为HashMap提供了所有类似的功能,并且速度更快。
当你需要在多线程环境下使用时,HashMap也可以转换为同步的。 Map Collections.synchronizedMap(Map m)
当需要输入与输出顺序一致时 使用LinkedHashMap (也提高增删)
基础结构
compact1, compact2, compact3
java.util
Class HashMap<K,V>
java.lang.Object
java.util.AbstractMap<K,V>
java.util.HashMap<K,V>
Type Parameters:
K - the type of keys maintained by this map //此映射维护的键的类型
V - the type of mapped values //映射值的类型
All Implemented Interfaces:
Serializable, Cloneable, Map<K,V>
Direct Known Subclasses(切片):
LinkedHashMap, PrinterStateReasons
//维护了多个内部类 如Node(链表) EntrySet(双向链表) 等
//内部构造器:
HashMap 底层
- HashMap有内部类 Node 实现了Map.Entry接口 所以数据类型是Node 确切的说是 Map.Entry<K,V>
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
- 底层是 数组+链表+红黑树(二叉树) jdk7.0是数组+链表
扩容机制:
- table是Node类型 默认null 初始长度(第一次扩容)16 加载因子0.75
- 通过key计算hash值 得到: table索引, 没有元素,直接放入, 有元素(相等) 替换, 不相等就判断是 树还是链表 容量不足就扩容
- 以后在扩容就是2倍, 临界值也是原有两倍 为24;
- 当元素个数超过8 且table>=64 计划为红黑树.
HashMap的源码:
-
调用构造器 new HashMap()
加载因子 loadfactor = 0.75;
HashMap$Node[] table = null;
-
执行put 根据key获取hash值 并根据值 放进table的索引
第一次添加元素,扩容 16
然后进行数据添加
++modCount;
并且++size
如果size > 临界值 就扩容
-
第二次添加 依旧执行put 并获取hash值 同一个索引 就进行比较 equals ()
继续添加
-
添加key值一致,value不同的值
-
V putVal(int hash, K key," V value", boolean onlyIfAbsent, boolean evict) { //1 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //2 if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) //3 重点 使得传入的值 e.value = value; //value 是当前传入的value值 afterNodeAccess(e); return oldValue; }
HashTable:
1、Dictionary(词典) 的子类,默认是线程同步的。不允许关键字或值为null ,否则 空指针异常
2、实现一个Key-value映射的哈希表。任何非空的对象都可以作为key或者value
3\ 用法与HashMap一致
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
特点:
-
底层数组 HashTable$Entry[] 初始值 11; 素数
-
零界值 0.75 11 * 0.75 = 8;
-
扩容 按自己的扩容机制来继续
-
执行put里的 private void addEntry(int hash, K key, V value, int index)
如果if (count >= threshold) { 大于等于阈值 就扩容 rehash();
-
int newCapacity = (oldCapacity << 1) + 1; 扩大这么多
private transient int count;//初始 0 private int threshold;//阈值 private void addEntry(int hash, K key, V value, int index) { modCount++;//先加mod Entry<?,?> tab[] = table; //创建tab[] 临时变量 if (count >= threshold) { //如果超过阈值,则重新刷新表 // Rehash the table if the threshold is exceeded rehash(); tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length;//按位与,求余 } // Creates the new entry. 创建新条目。 @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++;//长度计数 } //扩容: protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // overflow-conscious code int newCapacity = (oldCapacity << 1) + 1; // 值为原来的值+1 也就是 11*2 = 22 + 1 也就是23也是一个素数. 一直到 47 95(不是素数) if (newCapacity - MAX_ARRAY_SIZE > 0) { if (oldCapacity == MAX_ARRAY_SIZE) //最大值了就跳过扩容 不管阈值继续加入. // Keep running with MAX_ARRAY_SIZE buckets return; newCapacity = MAX_ARRAY_SIZE; } Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++; threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; //复制元素且重写分配 for (int i = oldCapacity ; i-- > 0 ;) { for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) { Entry<K,V> e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = (Entry<K,V>)newMap[index]; newMap[index] = e; } } }
-
HashMap VS HaashTable
1 | 版本 | 线程安全 | 效率 | 允许null键null值 |
---|---|---|---|---|
HashMap | 1.2 | 不安全 | 高 | 可以 |
HashTable | 1.0 | 安全 | 较低 | 不可以 |
Properties(IO后学):
-
继承自HashTable类 并且实现了Map接口,
-
使用特点和HashTable类似
-
Properties 还可以用于 从xxx.properties文中,加载数据到Properties类对象.
-
主要为配置文件.
Properties 类位于 java.util.Properties ,是Java 语言的配置文件所使用的类, Xxx.properties 为Java 语言常见的配置文件,如数据库的配置 jdbc.properties, 系统参数配置 system.properties。 这里,讲解一下Properties 类的具体使用。
主要方法:
java可以操作数据库, db.Properties 记录DB的用户名和密码 然后操作密码什么的时候 就使用db.Properties 来进行修改 Properties 还可以用于 从xxx.properties文中,加载数据到Properties类对象.
属性文件a.properties如下:
name=root
pass=liu
key=value
读取a.properties属性列表,与生成属性文件b.properties。代码如下:
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Iterator;
import java.util.Properties;
public class PropertyTest {
public static void main(String[] args) {
Properties prop = new Properties();
try{
//读取属性文件a.properties
InputStream in = new BufferedInputStream (new FileInputStream("a.properties"));
prop.load(in); ///加载属性列表
Iterator<String> it=prop.stringPropertyNames().iterator();
while(it.hasNext()){
String key=it.next();
System.out.println(key+":"+prop.getProperty(key));
}
in.close();
///保存属性到b.properties文件
FileOutputStream oFile = new FileOutputStream("b.properties", true);//true表示追加打开
prop.setProperty("phone", "10086");
prop.store(oFile, "The New properties file");
oFile.close();
}
catch(Exception e){
System.out.println(e);
}
}
}
TreeMap:
使用无参构造器是无序的
需要传入比较器:
同TreeSet
集合\映射总结:
选择集合实现类:
如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
尽量返回接口而非实际的类型,如返回List而非 ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。
尽量写 Set set = new HashSet() // Map map = new TreeMap 需要时 只需要更改HashSet 就欧克了
Collections 工具类:
工具类方法01:
-
Collections 是一个操作Set List 和Map 等集合的工具类
-
Collections中 提供了一系列静态方法 对集合元素进行排序\ 查询和修改等操作,
-
排序操作(都是静态方法)
-
reverse(List) 反转List中元素的顺序
-
shuffle(List) 对List集合元素进行随机排序 (每次输出结果不一)
可以shuffle(List,rnd) rnd 是Random的随机数对象
-
sort(List) 根据元素的自然顺序对指定List 集合元素按升序排序
-
sort(List,Comparator) 根据指定的Comparator 产生的顺序 对List 集合元素进行自定义排序
-
swap(List int int ): 将指定list集合中的 i 处元素和 j 处元素进行交换 (index)
package com.javastudy.Map_;
import java.util.*;
/**
* @Author: qianl
* @Date: 2022/12/15 11:08
* @Description:
*/
public class Properties_ {
public static void main(String[] args) {
//1. Properties 继承 HashTale
//2. 可以通过 K-V 存放数据,当然 key value 都不能null
//增加
Properties properties = new Properties();
properties.put("001",1);//k-v
List arrayList = new ArrayList();
arrayList.add(new Test("001",1));
arrayList.add(new Test("002",2));
arrayList.add(new Test("003",3));
arrayList.add(new Test("004",4));
System.out.println(arrayList);
//[Test{name='001', age=1}, Test{name='002', age=2},
// Test{name='003', age=3}, Test{name='004', age=4}]
//1. 反转
Collections.reverse(arrayList);
System.out.println(arrayList);
//[Test{name='004', age=4}, Test{name='003', age=3},
// Test{name='002', age=2}, Test{name='001', age=1}]
//2. 随机排序 每次结果不一
Collections.shuffle(arrayList);
//使用迭代器 or For-Each
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.print(next+"\t");
}
System.out.println("\n"+"=====");
//3.1 使用元素的自然排序(升序) 不能是对象的 只能是String 之类的
//Collections.sort(arrayList);
//3.2 自定义排序 一个问题 假如不是以int类型进行 怎么处理
Collections.sort(arrayList, new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
Test t1;
Test t2;
//1.先进行类型转换
t1 = (Test)o1;
t2 = (Test)o2;
return t1.getAge()- t2.getAge();
}
});
//需要重写比较方法
//com.recomastudy.Map_。无法将测试强制转换为java.lang.Comparable 报错
for (Object o : arrayList) {
System.out.print(o+"\t");
}
//Test{name='001', age=1} Test{name='002', age=2}
// Test{name='003', age=3} Test{name='004', age=4}
System.out.println("=======");
//4. swap(List int int ): 将指定list集合中的 i 处元素和 j 处元素进行交换
//指的是下标!!!
Collections.swap(arrayList,1,3);
System.out.println(arrayList);
//[Test{name='001', age=1}, Test{name='004', age=4},
// Test{name='003', age=3}, Test{name='002', age=2}]
}
}
class Test{
private String name;
private Integer age;
@Override
public String toString() {
return "Test{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Test test = (Test) o;
return Objects.equals(name, test.name) &&
Objects.equals(age, test.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public Test(String name, Integer age) {
this.name = name;
this.age = age;
}
}
工具类方法02:
-
Object max(Collection) 根据自然排序,返回给定集合中最大元素
-
Object max(Collection, Comparator) : 根据Comparator指定的顺序,给出最大元素
-
Object min(Collection)
-
Object min(Collection, Comparator) (比较器)
-
int frequency(Collection, Object) :返回指定集合中指定元素的出现次数; (频次,频率)
-
void copy(List dest,List src): 将src的内容复制到dest中;
//需要实际添加元素类型!! 给初始值 保证一定有空间 上面泛型没有定义元素 就使用Object 这样对象也能放进去
List vector= Arrays.asList( new Object[list.size()] );
//Arrays.asList 将数组变成List类型 转换期间不能使用List的方法 -
boolean replaceAll(List list ,Object oldVal, Object newVal): 使用新值 替换List对象中的所有旧值
Collections.replaceAll(vector,"Hello",null);//将Hello ---> null
就是要填入初始值 ,并使得大小与被复制的List.size() 一致
package com.javastudy;
import java.util.*;
/**
* @Author: qianl
* @Date: 2022/12/17 11:17
* @Description:
*/
public class Collections_ {
public static void main(String[] args) {
List list = new ArrayList();
list.add("Hello");
list.add("World");
list.add("Java");
//1. Object max(Collection) 根据自然排序,返回给定集合中最大元素
System.out.println(Collections.max(list));
//World
//2. Object max(Collection, Comparator) : 根据Comparator指定的顺序,给出最大元素
System.out.println(Collections.max(list, new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
//根据长度 反向排序
return ((String)o2).length()-((String)o1).length();
}
}));
//Java
//3. Object min(Collection)
System.out.println(Collections.min(list));
//Hello
//4. Object min(Collection, Comparator) (比较器)
System.out.println(Collections.min(list, new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
// //根据长度 反向排序
return ((String)o2).length()-((String)o1).length();
}
}));
//Hello
//5. int frequency(Collection, Object) :返回指定集合中指定元素的出现次数; (频次,频率)
System.out.println(Collections.frequency(list,"Hello"));
//1
//6. void copy(List dest,List src): 将src的内容复制到dest中;
//注意 要把长度加入 Collections.copy() 的源码
/* int srcSize = src.size();
如果srcSize大于dest.size,将会抛出异常
if (srcSize > dest.size())
throw new IndexOutOfBoundsException(“Source does not fit in dest”);
*/
//注意: 已经放进入 size 但是在运行中还是0
//List vector = new Vector(list.size());
//需要实际添加元素类型!! 给初始值 保证一定有空间 上面泛型没有定义元素 就使用Object 这样对象也能放进去
List vector= Arrays.asList(new Object[list.size()]);
//Arrays.asList 将数组变成List类型 转换期间不能使用List的方法
Collections.copy(vector,list);
//IndexOutOfBoundsException
for (Object o : vector) {
System.out.println(o);
}
/*Hello
World
Java
*/
//7. boolean replaceAll(List list ,Object oldVal, Object newVal): 使用新值 替换List对象中的所有旧值
Collections.replaceAll(vector,"Hello",null);//将Hello ---> null
for (Object o : vector) {
System.out.println(o);
}
/*null
World
Java*/
}
}
本章测试题:
题目1:
注意切片 subString();
package com.javastudy;
import javax.xml.soap.Node;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @Author: qianl
* @Date: 2022/12/17 12:28
* @Description:
*/
public class HomeWork01_ {
public static void main(String[] args) {
List<News> listNews = new ArrayList<News>();
//添加进List中
listNews.add(new News("新冠确诊病例超千万,数百万印度教徒赴恒河\"圣浴\"引民众担忧"));
listNews.add(new News("男子突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生"));
//遍历过程,标题进行处理 15字显示 后续的"...";
Iterator iterator = listNews.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
if (next instanceof News) {
News news = (News)next;
if (news.getTotal().length()>15){
//切片 单 15表示 从第15个字符开始 (0,15) --> 从0 到15
String str = news.getTotal().substring(0,15);
//beginIndex 和 endIndex
System.out.println(str+"...");
}else{
System.out.println(news);
}
}
}
//新冠确诊病例超千万,数百万印度...
//男子突然想起2个月前钓的鱼还在...
}
}
//1. 新闻类
class News{
private String total;
private String Text;
//2. 只初始化标题
public News(String total) {
this.total = total;
}
//1. 只打印标题
@Override
public String toString() {
return "News{" +
"total='" + total + '\'' +
'}';
}
public String getTotal() {
return total;
}
public void setTotal(String total) {
this.total = total;
}
public String getText() {
return Text;
}
public void setText(String text) {
Text = text;
}
}
题目2:
注意: addAll removeAll 和containsAll 都是 需要传入一个Collection<Car> 类型的集合
package com.javastudy;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @Author: qianl
* @Date: 2022/12/17 13:04
* @Description:
*/
public class HomeWork02_ {
public static void main(String[] args) {
List<Car> listCar = new ArrayList<Car>();
Car car = new Car("宝马", 40000000);
Car car1 = new Car("宾利", 500000000);
//ArrayList 操作练习
listCar.add(car);
listCar.add(car1);
//删除
listCar.remove(car);
//查
System.out.println(listCar.contains(car));
//false
//长度
System.out.println(listCar.size());
//1
//是否空
System.out.println(listCar.isEmpty());
//false
//清空
listCar.clear();
List<Car> list = new ArrayList<Car>();
list.add(car);
list.add(car1);
//addAll 添加多个元素; 从index 处 开始加入
listCar.addAll(0,list);
//containsAll 查看
System.out.println(listCar.containsAll(list));
//true
list.removeAll(listCar);
System.out.println(list);
//[]
Iterator<Car> iterator = listCar.iterator();
while (iterator.hasNext()) {
Car next = iterator.next();
System.out.println(next);
}
for (Car car2 : listCar) {
System.out.println(car2);
}
/*Car{name='宝马', p=40000000}
Car{name='宾利', p=500000000}*/
}
}
class Car{
private String name;
private Integer p;
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", p=" + p +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getP() {
return p;
}
public void setP(Integer p) {
this.p = p;
}
public Car(String name, Integer p) {
this.name = name;
this.p = p;
}
}
题目3:
注意:
package com.javastudy;
import java.util.*;
/**
* @Author: qianl
* @Date: 2022/12/17 14:34
* @Description:
*/
public class HomeWork03_ {
@SuppressWarnings("all")
public static void main(String[] args) {
//使用HashMap 进行存储
Map empMap = new HashMap();
emp jack = new emp("jack", 650);
emp tom = new emp("tom", 1200);
emp smith = new emp("smith", 2900);
//添加
empMap.put(jack.getName(), jack.getPc());
empMap.put(tom.getName(), tom.getPc());
empMap.put(smith.getName(), smith.getPc());
//修改
empMap.put("jack",2600); //再次添加来修改
empMap.replace("jack",2600);//替代
//EntrySet方法:
Iterator iterator = empMap.entrySet().iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
Map.Entry me=(Map.Entry)next;
//1. 遍历员工
System.out.println(me.getKey());
}
while (iterator.hasNext()) {
Object next = iterator.next();
Map.Entry me=(Map.Entry)next;
//2. 遍历工资
System.out.println(me.getValue());
}
//keySet()方法
Set set = empMap.keySet();
for (Object o : set) {
System.out.println(o+":"+empMap.get(o));
}
//构造器:
Iterator iterator1 = empMap.keySet().iterator();
while (iterator1.hasNext()) {
Object next = iterator1.next();
System.out.println(next+"--"+empMap.get(next));
}
}
}
class emp{
private String name;
private int pc;
@Override
public String toString() {
return "emp{" +
"name='" + name + '\'' +
", pc=" + pc +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
emp emp = (emp) o;
return pc == emp.pc &&
Objects.equals(name, emp.name);
}
@Override
public int hashCode() {
return Objects.hash(name, pc);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPc() {
return pc;
}
public void setPc(int pc) {
this.pc = pc;
}
public emp(String name, int pc) {
this.name = name;
this.pc = pc;
}
}
题目4:
HashSet和TreeSet 实现去重:
HashSet:
-
获取相同的hash值 同时,比较内容 .则 e = p 当前元素 = 前一个元素 在for循环中
(p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
-
然后返回oldValue 不返回null;
V putVal(int hash, K key," V value", boolean onlyIfAbsent,
boolean evict) {
//1
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//2
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
//3 重点 使得传入的值
e.value = value; //value 是当前传入的value值
afterNodeAccess(e);
return oldValue;
}
TreeSet:
- 第一次传入的时候 进行null检查
- 第二次传入值 看是否有Comparator 的传入
- 没有Comparator 的时候
//1. 第一次传入 会进行null检查 因为TreeSet(TreeMap) 不允许有null
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
//2. 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
//修改value值 就是去重
return t.setValue(value);
} while (t != null);
}
//3,没有Comparator 的时候 遍历去重 先左后右
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
//修改value值 就是去重
return t.setValue(value);
} while (t != null);
}
//开始放入元素 parent记录了位置
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);//插入后修复平衡二叉树
题目5:
TreeSet 内不能有null值 newPerson() 是有地址的 在堆里 value是默认的 new Object()
-
因为第一次添加: t == null;
-
走compare(key,key)进行判断
~~~java
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) checkroot = new Entry<>(key, value, null); size = 1; modCount++; return null;
}
// comparatornull 对 走这个 ((Comparable<? super K>)k1).compareTo((K)k2) 走的super K的 因为Person() 没有重写相关方法
// Comparable 的compareTo() 没有重写 就会把 new Person() 强转成 Comparable类:
// 报错 ClassCastException 强制类型转换异常.
final int compare(Object k1, Object k2) {
return comparatornull ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
//所以 如果
class Person implements Comparable{
//重写 compareTo()方法 什么都不写 就不会报错了
//能输出出来是一个对象地址
}
~~~
题目6:
// 注意p1 是按现在的key值是person() 对象 所以他会去找p1(“cc”,1001) 但是hashSet里没有这个东西
p1是add后 计算hashCode 然后再改成CC hashCode没变 而p1是变以后的hashCode 就变了
主要是 HashCode ”AA“ 与"CC" 的值不一样 返回的hash值也就不一样
题目7:
PS: add() append() put() // remove() delete()的区别
add()
append()
put()
返回类型 和 删除的东西不一样
泛型:通用性
初始记忆: 使得泛型中的导入类型 是什么都可以
导入:
// 报错ClassCastException
public static void main(String[] args) {
Dog dog1 = new Dog("001", 1);
List list = new ArrayList();
list.add(dog1);
//如果混入了一只猫
list.add("Car");
for (Object o : list) {
//向下转型 .ClassCastException
Dog dog = (Dog)o;
System.out.println(dog);
}
}
原因:
- 不能对加入的数据类型进行规范 约束 (不安全)
- 遍历时,需要使用类型转换,如果集合中的数据量较大,对效率有影响(多次向下转型)
使用泛型来解决:
public static void main(String[] args) {
Dog dog1 = new Dog("001", 1);
// 1. <Dog> 表示ArrayList中的元素是Dog
List<Dog> list = new ArrayList<Dog>();
list.add(dog1);
//2. 如果混入了一只猫 直接编译不通过
//list.add("Car");
//3.Dog o:list 可以直接写Dog类型 不用转换.
for (Dog o : list) {
//不向下转型
System.out.println(o);
}
}
- Jdk5.0以后出现
- 不会在运行时出现ClassCastException
- 可以是某个类 \ 方法 \ 参数类型
测试:
package com.generic;
/**
* @Author: qianl
* @Date: 2022/12/17 21:27
* @Description:
*/
public class Generic01 {
public static void main(String[] args) {
//E 的具体类型 在创建时(编译时) 就确定了
Person<String> stringPerson = new Person<String>("Test");
System.out.println(stringPerson.getS()); // Test
}
}
class Person<E>{
E s;// E 表示s的数据类型 有点像占位符 E通常表示Element;
public Person(E s) {
this.s = s;
}
public void setS(E s) {
this.s = s;
}
public E getS(){
return s;
}
}
书写格式:
泛型有T E V K K: Key V: Value E: element T 表示Type 四个都可以 都表示类型
泛型的声明:
List 和 ArrayList 那样
K T V 不表示值 ,而表示类型
任何字母都行 通常大写
可以是类 属性 形参列表 返回属性等.
class Person<E> {
E s;// E 表示s的数据类型 有点像占位符 E通常表示Element;
public Person(E s) {
this.s = s;
}
public void setS(E s) {
this.s = s;
}
public E getS() {
return s;
}
}
泛型的实例化:举例 HashSet 和HashMap的使用情况:
package com.generic;
import java.util.*;
/**
* @Author: qianl
* @Date: 2022/12/19 17:43
* @Description:
*/
public class Generic02 {
public static void main(String[] args) {
//Test HashSet;
HashSet<Student> students = new HashSet<>();
students.add(new Student("john",12));
//标注了迭代器的类型
Iterator<Student> iterator = students.iterator();
//不需要转换
while (iterator.hasNext()) {
Student next = iterator.next();
System.out.println(next);
}
//Student{name='john', age=12}
HashMap<Integer, Student> studentIntegerHashMap = new HashMap<>();
studentIntegerHashMap.put(10001,(new Student("King",35)));
studentIntegerHashMap.put(10002,(new Student("King",36)));
//使用EntrySet()遍历
Iterator<Map.Entry<Integer, Student>> iterator1 = studentIntegerHashMap.entrySet().iterator();
while (iterator1.hasNext()) {
//可以不用手动向下转型了
Map.Entry<Integer, Student> next = iterator1.next();
System.out.println("学生ID:"+next.getKey()+ " 学生信息:"+next.getValue());
}
//学生ID:10001 学生信息:Student{name='King', age=35}
//学生ID:10002 学生信息:Student{name='King', age=36}
}
}
class Student{
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
泛型使用细节:
注意:
-
java中 标注了T 和E的 都只能是引用类型
-
在指定泛型具体类型后,可以传入该类型或者其子类类型
-
fanx1使用可以只写前面一个<> 后一个会提示: Explicit type argument Student can be replaced with <>
(显式类型参数Student可以替换为<>) 他会自行进行类型推断
-
如果没写<> 是默认类型 <E> E则是Object类型
泛型课堂练习:
题目1:
重点
- 编写MyDate类的比较方法
- 使用匿名内部类定制排序
- 日期的判断 (一步一步来 过关斩将)
- 自己写方法 可以参考String的compareTo方法 只写一个形参 注意要静态
- 可以将方法转为lambda表达式
- 注意老韩写法 直接在方法里进行判断 没有创建 compareTo方法 最后也封装在MyDate里
public static int compareTo(Object o1, Object o2) {
//先进行Object的转型
if (o1 instanceof MyDate || o2 instanceof MyDate) return 0;
//开始向下转型
MyDate d1 = (MyDate) o1;
MyDate d2 = (MyDate) o2;
//写一个另外的方法开始比较year day month
return ct(d1, d2);
}
public static int ct(MyDate o1, MyDate o2) {
//先排除完全对等的情况:
if (o1.year == o2.year && o1.month == o2.month && o1.day == o2.day) return 0;
//如果年月都相等 比较日期
if (o1.year == o2.year && o1.month == o2.month) {
return o1.day - o2.day;
}
//如果年相等 比较月份
if (o1.year == o2.year) {
return o1.month - o2.month;
}
//否则直接返回年的值
return o1.year - o2.year;
}
注意老韩写法: 最后也封装在MyDate里
employees.sort((o1,o2)->{
if (!(o1 instanceof Employee && o2 instanceof Employee)) return 0;
//比较年龄
if (o1.getName().equals(o2.getName())) return o1.getName().compareTo(o2.getName());
//比较年
int year=o1.getBirthday().getYear()-o2.getBirthday().getYear();
if (year!=0) return year;
//比较月
int month = o1.getBirthday().getMonth()-o2.getBirthday().getMonth();
if (month!=0) return month;
//比较日
return o1.getBirthday().getDay()-o2.getBirthday().getDay();
});
package com.generic;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Map;
import java.util.Objects;
/**
* @Author: qianl
* @Date: 2022/12/19 18:35
* @Description:
*/
public class Generic03 {
public static void main(String[] args) {
Employee john = new Employee("john", "30000", new MyDate(2000, 12, 10));
Employee tom = new Employee("tom", "30000", new MyDate(2000, 12, 10));
Employee jir = new Employee("jir", "30000", new MyDate(2000, 12, 10));
ArrayList<Employee> employees = new ArrayList<>();
employees.add(john);
employees.add(tom);
employees.add(jir);
//可以转为lambda表达式
employees.sort(new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
//判断类型
if (!(o1 instanceof Employee && o2 instanceof Employee)) return 0;
//名字排序
if (o1.getName().equals(o2.getName())) {
//String的自然排序
return o1.getName().compareTo(o2.getName());
} else {
// 注意将compareTo方法改为静态
return MyDate.compareTo(o1.getBirthday(), o2.getBirthday());
}
}
});
for (Employee employee : employees) {
MyDate birthday = employee.getBirthday();
System.out.println(employee.getName() + "," + employee.getSal() + ","
+ birthday.getYear() + "年" + birthday.getMonth() + "月" + birthday.getDay() + "日");
}
}
}
class MyDate {
private int year;
private int month;
private int day;
public static int compareTo(Object o1, Object o2) {
//先进行Object的转型
if (o1 instanceof MyDate || o2 instanceof MyDate) return 0;
//开始向下转型
MyDate d1 = (MyDate) o1;
MyDate d2 = (MyDate) o2;
//写一个另外的方法开始比较year day month
return ct(d1, d2);
}
public static int ct(MyDate o1, MyDate o2) {
//先排除完全对等的情况:
if (o1.year == o2.year && o1.month == o2.month && o1.day == o2.day) return 0;
//如果年月都相等 比较日期
if (o1.year == o2.year && o1.month == o2.month) {
return o1.day - o2.day;
}
//如果年相等 比较月份
if (o1.year == o2.year) {
return o1.month - o2.month;
}
//否则直接返回年的值
return o1.year - o2.year;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyDate myDate = (MyDate) o;
return year == myDate.year &&
month == myDate.month &&
day == myDate.day;
}
@Override
public int hashCode() {
return Objects.hash(year, month, day);
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
}
class Employee {
private String name;
private String sal;
private MyDate birthday;
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", sal='" + sal + '\'' +
", birthday=" + birthday +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Objects.equals(name, employee.name) &&
Objects.equals(sal, employee.sal) &&
Objects.equals(birthday, employee.birthday);
}
@Override
public int hashCode() {
return Objects.hash(name, sal, birthday);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSal() {
return sal;
}
public void setSal(String sal) {
this.sal = sal;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
public Employee(String name, String sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
}
lambda表达式:
当该接口中有且只有一个方法时, 可以使用lambda表达式 将方法参数直接写出来 不用写明重写的方法是哪个.
//可以转为lambda表达式
employees.sort(new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
//名字排序
if (o1.getName().equals(o2.getName())) {
//String的自然排序
return o1.getName().compareTo(o2.getName());
} else {
// 注意将compareTo方法改为静态
return MyDate.compareTo(o1.getBirthday(), o2.getBirthday());
}
}
});
//转为lambda表达式 o1 o2就是sort方法中只有一个需要重写的方法的传入参数
employees.sort((o1, o2) -> {
//名字排序
if (o1.getName().equals(o2.getName())) {
//String的自然排序
return o1.getName().compareTo(o2.getName());
} else {
// 注意将compareTo方法改为静态
return MyDate.compareTo(o1.getBirthday(), o2.getBirthday());
}
});
自定义泛型:
- 成员可以使用泛型
- 泛型数组 不能初始化
- 静态因为初始就加载在类加载器里 所以不能使用类的泛型
- 泛型类 在创建时确定类型的 填入<>中
- 没有指定时 就是Object;
package com.generic;
/**
* @Author: qianl
* @Date: 2022/12/19 21:05
* @Description:
*/
public class CustomGeneric {
public static void main(String[] args) {
Tiger<Double,String,Integer> g = new Tiger<>("john");
g.setT(10.9);//相当于给T 进行了 Double t = 10.9;
//g.setT("yy");//运行失败 已经标记过了是Double类型
System.out.println(g);
//Tiger{name='john', r=null, m=null, t=10.9}
Tiger g2 =new Tiger("john~~~");
g2.setT("yy");
System.out.println(g2);
//Tiger{name='john~~~', r=null, m=null, t=yy}
}
}
class Tiger<T,R,M>{
String name;
R r;
M m; //属性使用泛型
T t;
//因为 数组在new 不能确定T 的类型 就无法在内存中开空间
//T[] ts =new T[9];
//无法直接实例化类型参数“T”
//Type parameter 'T' cannot be instantiated directly
@Override
public String toString() {
return "Tiger{" +
"name='" + name + '\'' +
", r=" + r +
", m=" + m +
", t=" + t +
'}';
}
public Tiger(String name, R r, M m, T t) {//构造器使用泛型
this.name = name;
this.r = r;
this.m = m;
this.t = t;
}
public Tiger(String name) {
this.name = name;
}
//因为静态是类加载时(对象未创建时) 就存在了 所以不能确定类型 除非指定类型
/*public static void m1(M m){
}*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public R getR() {//方法使用泛型
return r;
}
public void setR(R r) {
this.r = r;
}
public M getM() {
return m;
}
public void setM(M m) {
this.m = m;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
自定义泛型(接口):
-
接口中 一样 静态不能使用
-
接口类型 在继承接口或者 实现接口的时候确定
-
没有指定类型, 就Object
interface A<U,R>{
R r;
//U u = "java"; 报错
void hi(R r);
void run(R r);
//可以写默认方法 default类型 然后不需要重写
default R method(U u){
return null;
}
}
//实现接口 或者继承接口时 定义类型 没有指定就是Object
class a implements A<String,Double>{
@Override
public void hi(Double aDouble) {
}
@Override
public void run(Double aDouble) {
}
}
自定义泛型方法:
泛型方法 普通类 泛型类中都可以 也就是
可以在任意类中写一个泛型的方法
被调用时 类型被确认
public void eat(E e){} 修饰符后没有<E,R,...> 这种不算泛型方法 只是使用泛型
案例:
//<A> 是泛型 给Ea使用的
public <A> void Ea(A t){
}
泛型方法练习:
泛型类{
问题 public void eat(U u){} 使用泛型 U没有声明
泛型方法(E e){
传入的10 会自动装箱 --> Integer 输出Integer
传入new Dog() //类型 Dog 输出的Dog
}
}
泛型的继承和通配符:
-
泛型不具备继承性!
List<Object> list = new ArrayList<String>(); 不行! 要么一致 要么后面不写
-
<?> 表示支持所有泛型类型
- : **支持A及其所有子类**, **规定了泛型的上限**
- **支持A类以及A类的父类**, 不限于直接父类 ,**规定了泛型的下限**
-
package com.generic; import sun.java2d.pipe.SpanIterator; import java.util.List; /** * @Author: qianl * @Date: 2022/12/20 10:44 * @Description: */ public class GenericExtends { public static void main(String[] args) { } //支持任意 也就是Object了 public static void pc1(List<?>c){ for (Object o : c) { System.out.println(o);//Object类型 } } //规定上限 父类以下的全部 public static void pc2(List<? extends AA>c){ for (AA aa : c) { System.out.println(aa); } } //规定下限 那上限到Object 不限于直接父类:父类的父类也行 public static void pc3(List<? super CC>c){ for (Object o : c) { System.out.println(o); } } } class AA{} class BB extends AA{} class CC extends BB{}
本章作业:
题目1:
泛型类 使用泛型的方法
泛型方法 还有Junit单元测试类 可以不使用main方法
-
new JUnit_.m1(); 这样使用
-
第二种 直接@Test 就可以了 可以将本来要放在主方法的测试 放在另一个方法中 然后运行该方法 .
-
需要添加 JUnit5.4
package com.generic;
import com.sun.xml.internal.bind.v2.model.core.ID;
import java.util.*;
/**
* @Author: qianl
* @Date: 2022/12/20 11:04
* @Description:
*/
public class HomeWork01 {
public static void main(String[] args) {
//创建User 并放进DAO中 使用其中的五个方法.
User john = new User(1001, 30, "john");
User tom = new User(1002, 33, "tom");
User jack = new User(1003, 30, "jack");
DAO<User> userDAO = new DAO<>();
//1. save 添加元素
userDAO.save("10001",john);
userDAO.save("10002",tom);
userDAO.save("10003",jack);
//2. 获取返回对象
User user = userDAO.get("10002");
System.out.println(user);
//User{id=1002, age=33, name='tom'}
//3. 替换值
userDAO.update("10003",new User(1003, 30, "yeas"));
System.out.println(userDAO.get("10003"));
//User{id=1003, age=30, name='yeas'}
//4. 返回全部T类型
Iterator<User> iterator = userDAO.list().iterator();
while (iterator.hasNext()) {
User next = iterator.next();
System.out.println(next);
}
/*User{id=1002, age=33, name='tom'}
User{id=1001, age=30, name='john'}
User{id=1003, age=30, name='yeas'}*/
//5. 删除指定id对象
userDAO.delete("10003");
}
}
class DAO<T>{
//需要给上元素
Map<String,T> tMap = new HashMap<>();
//1 保存T类型的对象 到 tMap里
public void save(String id,T entity){
tMap.put(id,entity);
}
//2 从tMap中获取id 对应的对象
public T get(String id){
//get 可能会判断Map中的value值
return tMap.get(id);
}
//3 替换key为id 的内容 改为entity对象
public void update(String id,T entity){
//重复添加 就能替换entity;
tMap.put(id,entity);
}
//4 返回map中存放的所有T对象
public List<T> list(){
List<T> list = new ArrayList<>(); //初始化第一次添加后有16容量
//直接增强for 遍历对象是 tMap.entrySet()
for (Map.Entry<String, T> next : tMap.entrySet()) {
//依次添加 可能空指针 所以需要给list进行初始化容量
list.add(next.getValue());
//方法调用“add”可能会产生“NullPointerException”
}
return list;
}
//5 删除指定id对象
public void delete(String id){
tMap.remove(id);
}
}
class User{
private int id;
private int age;
private String name;
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return id == user.id &&
age == user.age &&
Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(id, age, name);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
}
单词
英语 | 意思 |
---|---|
getInstance | 获取实例 |
Abstract | 抽象类 |
interface | 接口 |
implement | 执行 实施 使生效 |
default | 默认 |
instantiated | 实例化 |
inner | 内部 |
Anonymous | 匿名的 不公开的 |
protected | 保护 |
private | 私有的 |
ordinal | |
enumeration | |
season | |
autumn | |
winter | |
instanceof | |
compareTo | |
Documented | |
field | |
constructor | |
package | |
parameter | |
Documented | |
Inherited | |
exception | |
Throws | |
clone | |
Found | |
Support | |
Serializable | 可串行的 |
wrapper | 包装 |
append | 追加 |
synchronized | 已同步 |
scan | 扫描 |
Instance | 实例 |
plus | 加 |
minus | 减 |
Character | |
Integer | |
Uppercase | |
lowercase | |
system | 系统 |
math | |
substring | 子字符串 |
digit | 数字 |
format | 格式化 |
Exception | |
reverse | |
Comparator | |
compare | |
collection | 收集 聚集 <接口> |
map | 图 地图 |
collections | 收集 聚集 包装类 |
chapter | |
vector | 矢量 载体 |
linked | 链接的 |
iterable | 可迭代的 可重复的 |
Empty | 空的 |
contains | |
iterator | 迭代器 |
stream | 流动 |
put | 放 放置 |
add | 增加 |
append | 追加 |
remove | 去除 |
delete | 删除 |
contains | 包含 |
element | 元素 要素 |
instant | 瞬间 (时间戳) |
Exercise | |
synchronized | 同步的 |
RandomAccess | 随机存储 |
Clone | 克隆 |
empty | 空的 |
count | 数 |
next | 下一个 |
prev | 前一个 |
exercise | 练习 |
employee | 员工 |
增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)
instance 获取实例
abstract 抽象类
interface 接口
implement 执行 实施 使生效
default 默认
instantiated 实例化
inner 内部
anonymous 匿名的 不公开的
protected 保护
private 私有的
encapsulation 封装
account 账户
password 密码
balance 平衡;余额
extends 继承
pupil 小学生
graduate 大学毕业生
access 接近 权力
theory 理论 学说 原理
structure 结构
dynamic 动态
binding 绑定 结合
equal 平等的
monster 怪物
master 主人
resume 简历
details 细节
------------恢复内容结束------------
标签:02,JAVA,String,System,笔记,name,println,public,out From: https://www.cnblogs.com/qlxn/p/javaStudy__02.html