1、JavaSE的笔记(1版本)

代码文件在idea的study_java_basics中

问题

idea中快捷键

1、idea中光标从插入变为输入,使用快捷键fn+insert
2、new 对象();自动补前面的快捷键: ctrl+alt+v
3、代码自动对齐快捷键:ctrl+alt+L
4、 idea中快捷键搜索Java文件:ctrl+shift+N
5、进入接口实现类的方法:鼠标点击接口方法名称,鼠标右键选择【Go to】——>【Implementation(s)】即可进入接口实现类的方法名称
6、进入方法定义:按住ctrl键,再点击方法(函数)的定义。或者,选中函数,右键菜单 - “Go To” - “Declaration or Usages”,跳转到该函数的定义。
7、idea中运行的快捷键ctrl+shift+f10

eclipse中快捷键

1、Java EE和java SE的区别

: https://www.cnblogs.com/sumuKiko/p/14428048.html

​ Java EE包含java SE

image-20230222010849215

2、Java动态绑定机制也称为运行时绑定

https://www.cnblogs.com/zwgitOne123/p/16983778.html

3、多态

条件:存在继承和重写 编译看左边,运行看右边

https://blog.csdn.net/z972065491/article/details/127220556

​ 1.当调用对象方法时,该方法会与【运行类型】对象的内存地址绑定。

​ 2.当调用属性时,没有动态绑定机制,哪里声明,哪里使用

4、在Oracle官网中找jdk

5、jdk jre jvm关系

image-20230224130902345

5、java中length与size的区别,Java中的length、length()和size方法的区别

1 java中的length属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了length这个属性.

2 java中的length()方法是针对字符串String说的,如果想看这个字符串的长度则用到length()这个方法.

3.java中的size()方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!

这个例子来演示这两个方法和一个属性的用法

public static void main(String[] args) {

String []list={“ma”,“cao”,“yuan”};

String a=“macaoyuan”;

System.out.println(list.length);

System.out.println(a.length());

List array=new ArrayList();

array.add(a);

System.out.println(array.size());

}

输出的值为:

3

9

1

6、选中函数,右键菜单 - “Go To” - “Declaration or Usages”,跳转到该函数的定义。

鼠标点击接口方法名称,鼠标右键选择【Go to】——>【Implementation(s)】即可进入接口实现类的方法名称

7、idea中光标从插入变为输入,使用快捷键fn+insert

6、new 对象();自动补前面的快捷键: ctrl+alt+v

​ ctrl+alt+v

7、代码自动对齐快捷键:ctrl+alt+L

​ ctrl+alt+L

8、Java中如何调用静态方法及非静态方法呢?

静态方法:

   我们将方法前面加上static的方法称之为静态方法

   静态方法中只能调用静态成员或者方法,不能调用非静态方法或者非静态成员(如果静态方法想调用非静态方法或者非静态成员需要先实例化即先new一个),而非静态方法既可以调用静态成员或者方法又可以调用其他的非静态成员或者方法

下文笔者讲述java中调用静态方法和非静态方法的示例分享,如下所示:

实现思路:
调用静态方法:
类名.方法名()
调用非静态方法:
类名 对象名 = new 类名();
对象名.方法名();
例:

登录后复制
package com.java265;
public class User {
public void info() { //定义一个方法
String name = “java265”; //局部变量
System.out.println(name);
}

public static void show(){
    String name = "88888";
    System.out.println(name);
}
public static void main(String[] args) {
    User user = new User();
    user.info(); //非静态方法调用  对象名.方法()
 
    User.show(); //静态方法调用 类名.方法() 
}
}

9、为什么类和接口不能使用private和protected?接口的方法不能使用private、protected、default

:https://dandelioncloud.cn/article/details/1505747927271305217

9.1为什么Java中的接口必须是public static final ?

Java中的接口必须是public static final的原因如下:

  1. public:接口中的方法和常量需要被其他类使用,因此必须是public的。
  2. static:接口中的常量是静态的,因为它们属于接口本身,而不是实现接口的类的实例。因此,它们必须是static的。
  3. final:接口中的常量是不可变的,因此必须是final的。这样可以确保实现接口的类不能修改接口中定义的常量的值。 总之,public static final是Java中接口的默认修饰符,这些修饰符确保了接口的可见性、常量的静态性和不可变性。

10、获取系统时间的三种方式

博客地址:https://blog.csdn.net/studyday1/article/details/127022722

业务场景中,不乏会需要取到当前系统的时间,做一些判断,比如判断某个执行过程需要花多长时间,然后将时间记录下来,返回给业务查看;或者需要或者一个唯一的值做一些表单单号,那么当前系统时间就是唯一的,可以适用,等等。那下面总结下有哪几种方式来获取。

一、System类中currentTimeMillis()方法
方法功能:返回从1970年1月1日午夜(UTC)开始到当前时间的毫秒值. 返回类型为 long ,表示毫秒为单位的当前时间。

特别注意:如果是想获取时间戳,推荐用System.currentTimeMillis(),获取时间戳效率最高,Date类也可以获取时间戳,效率较低。
@Test
public void test(){
long l = System.currentTimeMillis(); //获取时间戳效率最高
SimpleDateFormat dateFormat = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
String format = dateFormat.format(l);
System.out.println(l); //1663989713565
System.out.println(format);//2022-09-24
}

二、通过Date类来获取当前时间

@Test
public void test(){
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
String format = dateFormat.format(date);
System.out.println(format); //2022-09-24 11:28:22
System.out.println(date); //Sat Sep 24 11:28:22 CST 2022
String year = String.format(“%tY”, date);
String month = String.format(“%tm”, date);
String day = String.format(“%te”, date);
System.out.println(“今天是:”+year+“-”+month+“-”+day); //今天是:2022-09-24
}

三、通过Calendar类来获取当前时间

@Test
public void test(){
Calendar instance = Calendar.getInstance();
System.out.println(instance.getTimeInMillis()); //1663990917312
System.out.println(instance.getTime()); //Sat Sep 24 11:41:57 CST 2022
System.out.println(instance.get(Calendar.YEAR)); // 2022
System.out.println(instance.get(Calendar.MONTH)+1); // 9
System.out.println(instance.get(Calendar.DATE)); // 24
System.out.println(instance.get(Calendar.HOUR_OF_DAY)); //11
System.out.println(instance.get(Calendar.MINUTE)); //41
System.out.println(instance.get(Calendar.SECOND)); //57
}

11、输出对象,默认输出的是该对象的内存地址,(类名@哈希码值),重写tostring()方法,可以打印自己想要的输出格式。

toString()方法是Object类的方法,调用toString()会返回对象的描述信息。

1)为什么重写toString()方法呢?

如果不重写,直接调用Object类的toString()方法,打印的是该对象的内存地址(类名@哈希码值)。如下代码所示:

class Person {
    String name;
    String sex;
    int age;
    public Person() {}
    public Person(String name, String sex, int age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }
}

class Demo {
    public static void main(String[] args) {
        Person p = new Person();
        System.out.println(p);
    }
}

运行结果:Person@10dea4e

从上面代码可知,调用p的toString方法,打印出来的信息是类名+内存地址值。不符合要求。根据我们之前学的继承,假如父类的指定的功能能不能满足要求,那么子类可以复写父类的功能函数,那么该对象再调用toString()方法时,则会调用子类复写的toString方法。

(2)一般什么时候重写toString方法?

一般在编写代码,POJO类必须重写toString方法。如果继承了另一个POJO类,注意在前面加一下super.toString。

POJO(Plain Ordinary Java Object)即普通的java类,具有一部分getter/setter方法的那种类就可以称作为POJO类。重写toString()方法后,在方法执行跑出异常时,可以直接调用POJO的toString()方法打印其属性值,便于排查问题。

(3)如何重写toString()?

toString()方法重写代码样例如下:

class Person {
    String name;
    String sex;
    int age;

    public Person() {
    }

    public Person(String name, String sex, int age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", sex=" + sex + ", age=" + age + "]";
    }

}

编程习惯:开发者要对自定义的类重写toString(),对对象做详细的说明。

12、==和equals的区别

博客地址:https://www.zhihu.com/question/21917879

总结:

对于复合数据类型(引用数据类型)之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是内存中的存放位置的地址值,跟双等号(==)的结果相同;如果被复写,按照复写的要求来。

== 的作用:
  基本类型:比较的就是值是否相同
  引用类型:比较的就是地址值是否相同
equals 的作用:
  引用类型:默认情况下,比较的是地址值,重写该方法后比较对象的成员变量值是否相同

总结:重写equals方法是为了比较两个对象的值是否相等。或者说比较两个对象的属性是否相等。

Java中 如果重写equals方法,必须要重写hashcode方法吗?

是的,如果在Java中重写了equals方法,就必须同时重写hashCode方法。这是因为在Java中,hashCode方法和equals方法是密切相关的。hashCode方法返回一个对象的哈希码,而equals方法用于比较两个对象是否相等。如果两个对象相等,它们的哈希码必须相等。因此,如果重写了equals方法而没有重写hashCode方法,可能会导致在使用哈希表等数据结构时出现问题。因此,为了保证正确性,必须同时重写hashCode方法。

在Java中,如果重写了equals方法和hashcode方法,对象的值会通过equals方法进行比较,而不是通过默认的比较地址的方式。因此,如果两个对象的值相等,即使它们的地址不同,它们也会被认为是相等的。 但是,如果两个对象的值不相等,它们的地址也不同,那么它们仍然被认为是不相等的。因此,重写equals方法和hashcode方法只会影响对象值的比较,而不会影响地址的比较。

13、接口实现类中方法的调用

博客:https://www.cnblogs.com/xxmmqg/p/14422569.html

以下三个文件存在于同一个包下:

  1. 定义接口Dome_Interface.java
package cn.xxmmqg.Interface;

// 接口不能直接使用,必须有一个“实现类”来实现该接口
// 接口的实现类必须覆盖接口中的所有抽象方法,如果没有全部覆盖重写,则实现类必须是抽象方法
public interface Dome_Interface {
    // ==============抽象方法=================
    // 任何版本的java都可以定义抽象方法
    // 抽象方法的修饰符必须是 public abstract,且可以选择性省略
    public abstract void methodAbs1();
    public void methodAbs2();
    abstract void methodAbs3();
    void methodAbs4();
    // 以上都是抽象方法

    // ==============默认方法=================
    // 默认方法通过default修饰,而public 可以省略,但是【不能是其他的】
    // 默认方法可以通过【实现类】使用,而不需要被实现类重写
    public default void methodDef(){
        System.out.println("默认方法运行");
    }
    // 接口中可以定义默认方法,用于解决接口升级问题
    // 将新添加的接口功能定义成默认方法,那么已投入使用的实现类不需要改变也可使用该功能,实现类中也可以对其进行覆盖重写

    // ==============静态方法=================
    public static void methodSta(){
        System.out.println("接口的静态方法");
    }

    // ==============私有方法=================
    // 定义私有方法,用于解决多个【默认方法】代码重复的问题
    // 如果私有方法中加上修饰符static,就能解决多个【静态方法】代码重复问题
    private void methodPri(){
        System.out.println("重复代码运行");
    }
    public default void methodDef1(){
        System.out.print("methodDef1运行 ");
        this.methodPri();
    }
    public default void methodDef2(){
        System.out.print("methodDef2运行 ");
        this.methodPri();
    }

    // ==============成员变量=================
    // 接口中定义成员变量,但是必须使用 public static final三个关键字进行修饰,且可以选择性省略
    // 效果上看,就是接口的常量,以下定义方法中都是【常量】
    // 接口中的常量必须进行赋值
    public static final int NUM_1 = 10; // 常量一般使用全大写字母加下划线
    int NUM_2 = 11;
    public int NUM_3 = 12;
    static int NUM_4 = 13;
    final int NUM_5 = 14;
}

2.定义接口的实现类Dome_Implement.java

package cn.xxmmqg.Interface;

public class Dome_Implement implements Dome_Interface{
    // 覆盖重写抽象方法
    @Override
    public void methodAbs1() {
        System.out.println("Abs1");
    }
    @Override
    public void methodAbs2() {
        System.out.println("Abs2");
    }
    @Override
    public void methodAbs3() {
        System.out.println("Abs3");
    }
    @Override
    public void methodAbs4() {
        System.out.println("Abs4");
    }
}

3.主函数Dome_Main.java

package cn.xxmmqg.Interface;

public class Dome_Main {

    public static void main(String[] args) {

        Dome_Implement impl = new Dome_Implement();

        System.out.println("--------------------");
        impl.methodAbs1();
        impl.methodAbs2();
        impl.methodAbs3();
        impl.methodAbs4();

        System.out.println("--------------------");
        // 调用默认方法,如果实现类中没有定义,则向上找接口的
        impl.methodDef();
        impl.methodDef1();
        impl.methodDef2();


        System.out.println("--------------------");
        // 调用静态方法
//        impl.methodSta(); // 不能通过接口实现类的对象来调用接口的静态方法,因为一个类可以实现多个接口,这些接口的静态方法有可能重名
        Dome_Interface.methodSta(); // 正确调用接口静态方法的方法

        System.out.println("--------------------");
        // 访问接口中的常量
        System.out.println(Dome_Interface.NUM_1);
        System.out.println(Dome_Interface.NUM_3);
    }
}

主函数运行结果:

--------------------
Abs1
Abs2
Abs3
Abs4
--------------------
默认方法运行
methodDef1运行 重复代码运行
methodDef2运行 重复代码运行
--------------------
接口的静态方法
--------------------
10
12

14、重写tostring()方法

如果不重写tostring(),输出对象时,结果:Student@5e481248

public class Student {
        String name;
        int age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
  
    public static void main(String[] args) {
        Student student = new Student();
        student.setAge(12);
        student.setName("李春雨");
        System.out.println(student);
    }
}

重写tostring()过后,输出想要的格式。结果:Student{name=‘李春雨’, age=12}

public class Student {
        String name;
        int age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public static void main(String[] args) {
        Student student = new Student();
        student.setAge(12);
        student.setName("李春雨");
        System.out.println(student);
    }
}

15、Java默认无参构造方法,如果写了一个有参构造方法,那就一定要写一个无参构造方法。

16、Java中,什么是对象,什么是对象引用

如下表达式

A a1 = new A();

它代表A是类,a1是引用,a1不是对象,new A()才是对象,a1引用指向new A()这个对象。

在JAVA里,“=”不能被看成是一个赋值语句,它不是在把一个对象赋给另外一个对象,它的执行过程实质上是将右边对象的地址传给了左边的引用,使得左边的引用指向了右边的对象。JAVA表面上看起来没有指针,但它的引用其实质就是一个指针,引用里面存放的并不是对象,而是该对象的地址,使得该引用指向了对象。在JAVA里,“=”语句不应该被翻译成赋值语句,因为它所执行的确实不是一个赋值的过程,而是一个传地址的过程,被译成赋值语句会造成很多误解,译得不准确。

再如:

A a2;

它代表A是类,a2是引用,a2不是对象,a2所指向的对象为空null;

再如:

a2 = a1;

它代表,a2是引用,a1也是引用,a1所指向的对象的地址传给了a2(传址),使得a2和a1指向了同一对象。

综上所述,可以简单的记为,在初始化时,“=”语句左边的是引用,右边new出来的是对象。

在后面的左右都是引用的“=”语句时,左右的引用同时指向了右边引用所指向的对象。

再所谓实例,其实就是对象的同义词。

17、idea中测试类@Test注解,只能存在一个构造方法。

18、Java中 为什么main方法为什么是static

在Java中,main方法被视为程序的入口点,它是程序的起点。为了使Java虚拟机能够调用main方法,它必须是静态的。静态方法可以在没有创建对象的情况下被调用,因此在程序启动时,**Java虚拟机可以直接调用main方法,而不需要先创建对象。**此外,静态方法可以被类和对象共享,这使得它更适合作为程序的入口点。因此,main方法必须是静态的。

19、java中引用数据类型有哪些?

Java中的引用数据类型包括:(总的来说就是,类、接口、数组)因为list集合是接口,他有很多实现类

  1. 类型:类、接口、枚举
  2. 数组类型:一维数组、多维数组
  3. 字符串类型:String类
  4. 集合类型:List、Set、Map等
  5. 包装类型:Integer、Double、Boolean等
  6. 自定义类型:用户自定义的类、接口、枚举等。

20、Java中所有的包装类

Java中所有的包装类如下: 1. Boolean 2. Byte 3. Character 4. Short 5. Integer 6. Long 7. Float 8. Double

21、基本类型数据不能为空

基本数据类型(如int、double、boolean等)不能为null,因为它们是值类型,而不是引用类型。只有引用类型(如String、Integer、Boolean等)可以为null。

22、java中,什么是静态绑定,什么是动态绑定

静态绑定和动态绑定都是Java中的绑定方式,它们是指在编译时和运行时分别确定方法或变量的实际引用。 静态绑定是指在编译时就已经确定了方法或变量的实际引用,也称为早期绑定。在静态绑定中,编译器根据变量或方法的声明类型来确定其实际引用。例如,如果一个变量声明为Animal类型,那么在编译时就会确定它的实际类型为Animal,即使它被赋值为Dog类型的对象,也只能调用Animal类中定义的方法。

动态绑定是指在运行时根据对象的实际类型来确定方法或变量的实际引用,也称为晚期绑定。在动态绑定中,编译器不会根据变量或方法的声明类型来确定其实际引用,而是在运行时根据对象的实际类型来确定。例如,如果一个变量声明为Animal类型,但它被赋值为Dog类型的对象,那么在运行时就会根据对象的实际类型来确定其实际引用,即调用Dog类中定义的方法。

23、Java中 类变量和实例变量都可以写get set方法吗

是的,Java中类变量和实例变量都可以写get set方法。

对于类变量,也称为静态变量,可以使用静态方法来访问和修改它们。静态方法可以通过类名直接调用,而不需要创建类的实例。因此,可以在类中定义静态方法来获取和设置类变量的值。 对于实例变量,也称为非静态变量,需要通过创建类的实例来访问和修改它们。可以在类中定义实例方法来获取和设置实例变量的值。这些方法通常被称为getter和setter方法,分别用于获取和设置实例变量的值。 无论是类变量还是实例变量,都可以使用get和set方法来控制对它们的访问和修改,从而提高代码的封装性和安全性。

24、Java中组合和继承都是为了代码复用,但是如何选择?

  1. 继承 继承是指一个类继承另一个类的属性和方法。当一个类需要扩展或修改另一个类的功能时,可以使用继承。例如,一个子类可以继承父类的属性和方法,并添加自己的属性和方法。继承的优点是代码重用,可以减少代码量,但缺点是会增加类之间的耦合性,如果父类的实现发生变化,子类也需要相应地修改。

  2. 组合 组合是指一个类包含另一个类的实例作为自己的属性。当一个类需要使用另一个类的功能,但不需要继承它的属性和方法时,可以使用组合。例如,一个汽车类可以包含一个引擎类的实例作为自己的属性。组合的优点是可以灵活地组合不同的类,减少了类之间的耦合性,但缺点是需要更多的代码来管理对象之间的关系。

    因此,判断使用组合还是继承,需要根据具体的场景来决定。如果需要扩展或修改另一个类的功能,可以使用继承;如果只需要使用另一个类的功能,可以使用组合。

25、判断对象是否属于类,用instanceof关键字

if(e instanceof Person) //判断对象e是不是属于类Person

{返回Boolean类型的值,true或false}

26、Java中有且仅有一个公共类,可以有多个类

在一个Java源文件中,只能有一个公共类(就是public class 类名),且该类的名称必须与文件名相同。但是,可以有多个非公共类(class 类名,此时是缺省类,就是没有加访问修饰符,只能被同一个包里面的其他类访问)存在于同一个源文件中。这些非公共类可以被同一个包中的其他类访问。

27、Java中 非公共类可以写main方法吗?

非公共类可以写main方法,但是只有公共类的main方法才能被JVM执行。如果非公共类中有main方法,需要通过公共类的main方法来调用。如果需要在非公共类中执行代码,可以在公共类中创建该非公共类的实例并调用其方法。

28、Java中,为什么说抽象方法实现了多态

抽象方法是一种没有实现的方法,只有方法签名,没有方法体。在Java中,抽象方法必须在抽象类中声明,而抽象类不能被实例化,只能被继承。子类必须实现抽象类中的所有抽象方法,否则子类也必须声明为抽象类。

多态是指同一种类型的对象,在不同的情况下表现出不同的行为。在Java中,多态可以通过继承和接口实现。抽象方法实现了多态的原因是,子类必须实现抽象类中的抽象方法,而子类可以根据自己的需要实现不同的行为,从而实现了多态。

例如,假设有一个抽象类Animal,其中有一个抽象方法makeSound(),表示动物发出声音的行为。现在有两个子类Dog和Cat,它们分别实现了makeSound()方法,分别表示狗和猫发出声音的行为。当我们调用makeSound()方法时,可以根据实际的对象类型来决定调用哪个子类的方法,从而实现了多态。

29、Java中 泛型和object类型有什么区别?

Java中泛型和Object类型的主要区别在于类型安全性和编译时检查。 泛型是Java中的一种类型参数化机制,它允许在编译时指定类型参数,从而在运行时可以确保类型安全性。泛型可以在编译时检查类型错误,避免了在运行时出现类型转换异常的情况。泛型还可以提高代码的可读性和可维护性。 Object类型是Java中的一种通用类型,可以表示任何对象。使用Object类型时,编译器无法检查类型错误,因此在运行时可能会出现类型转换异常。此外,使用Object类型时需要进行类型转换,这会降低代码的可读性和可维护性。 因此,泛型比Object类型更加类型安全和可靠,可以提高代码的可读性和可维护性。

30、为什么不能定义泛型常量?

Java中的泛型常量是指在定义常量时使用泛型类型的常量。它的语法形式为: java public static final <T> T CONSTANT_NAME = value; 其中,<T>表示泛型类型参数,CONSTANT_NAME表示常量名,value表示常量的值。 不能定义泛型常量

为什么不能定义泛型常量?

Java中不能定义泛型常量的原因是因为泛型类型在编译时会被擦除,而常量的值必须在编译时确定。因此,如果定义了一个泛型常量,编译器无法确定它的类型,也就无法确定它的值。这就导致了泛型常量无法被定义。 另外,Java中的常量必须是静态的,而泛型类型是与类实例化相关的,因此无法定义静态泛型常量。 如果需要定义常量,可以使用final修饰符来定义常量。例如: public static final int MAX_VALUE = 100; 这样定义的常量可以在编译时确定其值,并且可以在程序中被多次引用。

31、idea中快捷键搜索Java文件:ctrl+shift+N

32、Java 为什么非静态不能直接访问静态的?

如果非静态需要访问静态成员变量或者静态方法,可以通过创建对象new来实现访问,也可以通过类名.静态成员变量来访问。

Java中的静态成员变量是在类加载时被初始化的,这是因为静态成员变量属于类级别的变量,与对象无关,因此在类加载时就需要被初始化。

在 Java 中,非静态方法是属于对象的,而静态方法是属于类的。因此,非静态方法只能通过对象来访问静态方法,而不能直接访问静态方法。 这是因为静态方法在类加载时就已经存在,而非静态方法需要在对象创建后才能存在。如果非静态方法可以直接访问静态方法,那么在对象创建之前,静态方法就已经被调用了,这是不合理的。

Java基础(正文)

第1章 基础语法

一、基本数据类型

1、Java的八大基本数据类型、类型默认值、引用类型、Java常量、Java常用转义字符、自动类型转换···········

2、自动类型转换,低级到高级。

低 ------------------------------------> 高
byte``—>``short``,``char``—> ``int` `—> ``long``—> ``float` `—> ``double

容量大的类型转为容量小的数据类型,必须使用强制转换。

浮点数到整数的转换是舍弃小数部分,而不是四舍五入。

3、数据类型占的字节数(八大基本数据类型)

//        boolean    1位
//        byte    8位  = 1字节
//        short char   16位  = 2字节
//        int   float  32位   =4字节
//        double long   64位  = 8字节

byte值的范围:-2(8-1)------2(8-1)-1 即是-128~127

Java引用数据类型:类、接口、数组。

4、int数据溢出

数据溢出

当某一种类型的数值达到此类型能够保存的最大(小)值之后,继续扩大(缩小),就会出现数据溢出问题。

int x = Integer.MAX_VALUE; // 得到整型的最大值 
System.out.println("x = "+x);
System.out.println("x+1 = "+(x+1)); 
System.out.println("x+2 = "+(x+2)); 

输出结果: 
x = 2147483647 
x+1 = -2147483648 
x+2 = -2147483647 

当最大值加上 1 时,结果变成Integer范围中最小的值,

当最大值加上 2 时,结果变成Integer范围中第二小的值,这就发生了数据溢出。

若是想避免发生数据溢出,程序中就必须加上数值范围的检查,或者使用比Integer表示范围更大的数据类型,如Long。

为了避免 int 类型的溢出,可以在表达式中的任一常量后加上大写的 L,或是在变量前面加上 long,进行强制类型转换。

// 当程序发生数据溢出之后,可用强制类型进行转换 
int x =Integer.MAX_VALUE ; 
System.out.println("x = "+x); 
System.out.println("x + 1 = "+(x+1)); 
System.out.println("x + 2 = "+(x+2L)); 
System.out.println("x + 3 = "+((long)x+3)); 

输出结果: 
x = 2147483647 
x + 1 = -2147483648 
x + 2 = 2147483649 
x + 3 = 2147483650

5、强制类型转换

  1. 条件是转换的数据类型必须是兼容的。

  2. (type) value type是要强制类型转换后的数据类型 实例:

    public class Test{

    public` `static` `void` `main(String[] args){

    int` `i1 = 123;

    byte` `b = (byte) i1; //强制类型转换为byte

    System.out.println("int强制类型转换为byte后的值等于"` `+ b); ``} }`

    运行结果:

    int``强制类型转换为``byte``后的值等于``123

6、隐式类型转换

隐式类型转换

这是一个很经典的题目,先看代码:

1 short a = 1;
2 short b = 2;
3 
4 short c = a + b;

答案是第4行代码出现编译错误:“可能损失精度”

原因:在进行 a + b 操作时,会把结果的类型“隐式”提升为 int 类型,此时再使用 short 类型的 c 变量引用时,就会出现“损失精度”

可以把 a + b 的结果强制转换为 short 类型,但是可能导致数值精度发生错误。也就是说可以解决编译期的错误,但是可能在运行期出现BUG

1 short a = 1;
2 short b = 2;
3 
4 // short c = (a + b);
5 short c = (short)(a + b);

还可以使用 int 或者 long 类型变量接收返回值

1 short a = 1;
2 short b = 2;
3 
4 int c = (a + b);

二、变量的类型

1、变量声明

在Java语言中,所有的变量在使用前必须声明。声明变量的基本格式如下:

type identifier [ = value][, identifier [= value] ...] ;

格式说明:type为Java数据类型。identifier是变量名。可以使用逗号隔开来声明多个同类型变量。

2、Java变量类型

Java语言支持的变量类型有:

  • 类变量(静态成员变量):独立于方法之外的变量,用 static 修饰。

  • (成员变量)实例变量:独立于方法之外的变量,不过没有 static 修饰。

  • 局部变量:类的方法中的变量。

  • 类变量一般为private或者public,具体取决于变量的作用范围和访问权限。如果变量只在类内部使用,则应该将其声明为private,如果变量需要在类外部使用,则应该将其声明为public。

  • 实例变量一般为private,因为实例变量是与对象相关联的,只有对象自己才能访问和修改它们。如果实例变量是public,那么任何对象都可以直接访问和修改它们,这可能会导致数据不一致或者安全问题。因此,为了保证数据的封装性和安全性,实例变量一般应该声明为private,并提供公共的访问方法来访问和修改它们。

public class Test{

  static int allClicks=0;    // 类变量(静态成员变量)    可以通过类名.静态变量或者对象名.静态变量       static修饰的变量或者方法,不能使用this和super调用,因为static修饰的方法和变量是属于类的,而this和super分别代表本类对象和上层父类,属于对象的概念。因此static修饰的变量或方法,不能使用this和super调用。
  
  String str = "hello world";  // 实例变量(成员变量)    实例变量一般设为私有的,private修饰(解释如下,加粗部分)  可以通过this.成员变量
  
  public void method() {
      int i = 0;  // 局部变量
  }

}

既:我们将方法前面加上static的方法称之为静态方法

如果是在静态方法调用静态可以直接类名.静态成员变量,或者类名.静态方法

静态方法中只能调用静态成员或者静态方法,不能调用非静态方法或者非静态成员

非静态方法既可以调用静态成员或者方法又可以调用其他的非静态成员或者方法()

(如果静态方法想调用非静态方法或者非静态成员需要先实例化,即先new一个对象,对象的引用.静态成员变量;),

为什么实例变量一般声明为私有的

将实例变量设为公有或私有是设计者在声明类时所做的权衡。通过将实例变量设置为公共变量,您可以公开类实现的细节,从而提供更高的效率和表达式的简洁性,但可能会阻碍未来的维护工作。通过隐藏类的内部实现的细节,您有可能在将来更改类的实现,而不会破坏使用该类的任何代码。

如果一个成员变量被声明为public,那么其他类的对象可以直接访问和修改这个成员变量。这种情况下,没有任何限制,所有人都可以去修改这个成员变量。因此,为了保证数据的安全性和封装性,建议将成员变量声明为private,并提供公共的getter和setter方法来访问和修改成员变量。这样可以控制对成员变量的访问和修改,从而保证数据的安全性和封装性。

类变量、实力变量、局部变量的访问范围:

类变量:类变量是指在类中定义的静态变量,它们属于类本身,而不是类的任何实例。类变量的作用域是整个类,可以被类中的任何方法或构造函数访问,也可以通过类名直接访问。

实例变量:实例变量是指在类中定义的非静态变量,它们属于类的每个实例。实例变量的作用域是整个类的实例,可以被类中的任何方法或构造函数访问,但不能通过类名直接访问。

局部变量:局部变量是指在方法或代码块中定义的变量,它们只在定义它们的方法或代码块中可见。局部变量的作用域仅限于定义它们的方法或代码块,不能被其他方法或代码块访问。

3、局部变量

  • 局部变量声明在方法、构造方法或者语句块中;
  • 局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,变量将会被销毁;
  • 访问修饰符不能用于局部变量;
  • 局部变量只在声明它的方法、构造方法或者语句块中可见;
  • 局部变量是在栈上分配的。
  • 局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。

4、成员变量、局部变量、局部变量数组的默认值

成员变量

  • 成员变量如果未初始化,会有默认的初始值的

boolean:false
byte:0
short:0
char:
int:0
long:0
float:0.0
double:0.0
String:null
String[]:null

  • 有一点奇怪,char类型变量后面什么也没有输出。不过,这并不是char类型变量没有默认值,而是默认值为“空字符”,也就是‘\u0000’,数值为0,我们可以证明一下。
package deep;
 
public class CharDefaultValue {
    static char c;
 
    public static void main(String[] args) {
        System.out.println((int) c);
        System.out.println(c == '\u0000');
    }
}
//---------------------测试结果
0 
true

局部变量

  • 相对于成员变量,局部变量没有默认值(不管是什么类型),如果试图使用一个局部变量的值,而这个局部变量尚未初始化,就会产生编译错误,例如:
    public static void main(String[] args) {
        int value;
        System.out.println(value);
    }

错误提示: The local variable value may not have been initialized

局部变量数组数组

  • 对于数组而言,如果数组使用new在堆上分配了空间,则数组的元素就会获得默认值,即使数组变量为局部变量也是如此。
    public static void main(String[] args) {
        int[] value = new int[10];
        System.out.println(value[0]);
    }
 
//结果:0 0 0 0 0 0 0 0 0 0

可以把数组的元素看作是数组的成员变量(实际上不是),当数组分配空间时,数组的元素(行为类似于成员变量)就可以获得默认的初始值。不过,对于局部变量数组本身(即value),如果没有初始化,同样没有默认值。(如下)

String[] a = new String[10];//有
     int[] b;//不new的话还是没有!!!!会直接报错
      for( String i : a ) System.out.print( i + " " );
      for( int i : b ) System.out.println(i + " " );
  • 错误:
    img

5、实例变量(成员变量)

  • 实例变量声明在一个类中,但在方法、构造方法和语句块之外;(类的成员变量(实例变量)必须放在类体中,但又不能包含在某个方法中)
  • 当一个对象被实例化之后,每个实例变量的值就跟着确定;
  • 实例变量在对象创建的时候创建,在对象被销毁的时候销毁;
  • 实例变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息;
  • 实例变量可以声明在使用前或者使用后;
  • 访问修饰符可以修饰实例变量;
  • 实例变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见;

​ 为什么实例变量一般声明为私有的

将实例变量设为公有或私有是设计者在声明类时所做的权衡。通过将实例变量设置为公共变量,您可以公开类实现的细节,从而提供更高的效率和表达式的简洁性,但可能会阻碍未来的维护工作。通过隐藏类的内部实现的细节,您有可能在将来更改类的实现,而不会破坏使用该类的任何代码。

  • 实例变量具有默认值。数值型变量的默认值是0,布尔型变量的默认值是false,引用类型变量的默认值是null。变量的值可以在声明时指定,也可以在构造方法中指定;
  • 实例变量可以直接通过变量名访问。但在静态方法以及其他类中,就应该使用完全限定名:ObejectReference.VariableName(对象引用.实例变量),也就是先new 一个对象,将对象给引用变量。

6、类变量(静态成员变量)

  • 类变量也称为静态成员变量,在类中以 static 关键字声明,但必须在方法之外。
  • 无论一个类创建了多少个对象,类只拥有类变量的一份拷贝。(也就是说,访问的都是同一个地址)
  • Java中的类变量(也称为静态变量)是属于类的,而不是属于类的任何一个实例。因此,无论类变量被哪个实例访问,它们都是同一个地址。这是因为类变量在类加载时就已经被初始化,而不是在实例化时初始化。因此,所有实例都共享同一个类变量的值。
  • 静态变量除了被声明为常量外很少使用。常量是指声明为public/private,final和static类型的变量。常量初始化后不可改变。常量的命名规范是使用全大写字母,单词之间用下划线分隔。
  • 静态变量储存在静态存储区。经常被声明为常量,很少单独使用static声明变量。
  • 静态变量在第一次被访问时创建,在程序结束时销毁。
  • 与实例变量具有相似的可见性。但为了对类的使用者可见,大多数静态变量声明为public类型。
  • 默认值和实例变量相似。数值型变量默认值是0,布尔型默认值是false,引用类型默认值是null。变量的值可以在声明的时候指定,也可以在构造方法中指定。此外,静态变量还可以在静态语句块中初始化。
  • 静态变量可以通过:ClassName.VariableName的方式访问。(也就是类名.变量名 这样子访问)
  • 类变量被声明为public static final类型时,类变量名称一般建议使用大写字母。如果静态变量不是public和final类型,其命名方式与实例变量以及局部变量的命名方式一致。

静态成员变量或方法的访问方式: 静态方法或者非静态方法,访问静态成员变量,都可以通过类名.静态成员变量名来访问。

package com.ji_chu;

public class Demo3 {
    public static int age = 10;
}
package com.ji_chu;

import org.junit.Test;

public class Demo3Test { //访问同一个静态变量,访问的是同一个地址,也就是说修改的值也是相同的
//静态访问静态
    public static void main(String[] args) {
        int age = Demo3.age;
        System.out.println(age);

        //静态访问非静态需要new 对象,对象引用.静态成员变量
    }
//非静态访问静态:类名.静态成员变量
    @Test
    public void run(){
        int age = Demo3.age;
        System.out.println(age);

    }
//非静态访问静态:对象引用.静态成员变量
    @Test
    public  void run2(){
        Demo3 demo3 = new Demo3();
        int age = demo3.age;

        System.out.println(age);
    }
}

7、总结:静态方法和非静态方法之间的调用(自己总结)

static修饰的方法想要调用非static的方法,或者调用非static的成员变量,需要实例化对象进行调用,就是 类名 对象名 = new 类名(); 对象名.变量名

同一个类中,static修饰的方法想要调用static的变量,可以通过类名.变量名 来调用

**不同类中,static修饰的静态方法想要访问另一个类的静态常量,**例如 public static final int ED=126;需要通过继承到该类,再通过父类名.常量名,或者直接常量名进行调用。

final修饰的常量值不可变,

static修饰的静态变量可以直接通过类名.变量名访问

既:我们将方法前面加上static的方法称之为静态方法

静态方法中只能调用静态成员或者静态方法,不能调用非静态方法或者非静态成员(如果静态方法想调用非静态方法或者非静态成员需要先实例化,即先new一个),而非静态方法既可以调用静态成员或者方法又可以调用其他的非静态成员或者方法

三、常量

final datatype CONSTANTNAME = VALUE;

关键字 数据类型 变量名 = 值;

常量必须在同一条语句中声明和赋值。

四、访问修饰符

image-20230417165905781

五、声明数组

//       声明的同时赋值
        String a[] = new String[]{"s","ss"};
        int[] ty = new int[]{1,2,3};
        char[] tu = new char[]{'s','e'};
//        声明过后再赋值
        int[] tr = new int[10];
        tr[0]=1;
        char[] tr1 = new char[10];
        tr1[0]='1';

六、获取系统日期、位移运算符、位运算符(与或非,按位与或非)

位移运算符博客:https://zhuanlan.zhihu.com/p/436446666?utm_id=0

& | ~ 和&& || !的不同是,&& 遇到假,还要计算&& 右边的表达式,||遇到真还要计算||右边的表达式。而& | ~ 不会继续计算右边的表达式。

获取系统日期三种方法:(如下代码所示)

import org.junit.Test;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class GetDate {
    //获取系统当前日期的三种方式,下面三个测试类
    @Test
    public void test(){
        long l = System.currentTimeMillis(); //获取时间戳效率最高
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format = dateFormat.format(l);
        System.out.println(l); //1663989713565
        System.out.println(format);//2022-09-24
    }
    @Test
    public void test1(){
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format = dateFormat.format(date);
        System.out.println(format); //2022-09-24 11:28:22
        System.out.println(date);  //Sat Sep 24 11:28:22 CST 2022
        String year = String.format("%tY", date);
        String month = String.format("%tm", date);
        String day = String.format("%te", date);
        System.out.println("今天是:"+year+"-"+month+"-"+day); //今天是:2022-09-24
    }
    @Test
    public void test3(){
        Calendar instance = Calendar.getInstance();
        System.out.println(instance.getTimeInMillis()); //1663990917312
        System.out.println(instance.getTime()); //Sat Sep 24 11:41:57 CST 2022
        System.out.println(instance.get(Calendar.YEAR)); // 2022
        System.out.println(instance.get(Calendar.MONTH)+1); // 9
        System.out.println(instance.get(Calendar.DATE)); // 24
        System.out.println(instance.get(Calendar.HOUR_OF_DAY)); //11
        System.out.println(instance.get(Calendar.MINUTE)); //41
        System.out.println(instance.get(Calendar.SECOND)); //57
    }
private int l = 12;

    @Override
    public String toString() {
        return "GetDate{" +
                "l=" + l +
                '}';
    }

    public static void main(String[] args) {
        GetDate getDate = new GetDate();
        GetDate getDate1 = new GetDate();
        System.out.println(getDate);
        System.out.println(getDate1);
        if(getDate == getDate1){ //判断地址
            System.out.println("111111");
        }
        if(getDate.equals(getDate1) ){ //判断地址
            System.out.println("22222");
        }
    }
}

七、流程控制

顺序、选择、循环

1、选择

选择if后面只能跟boolean型。

if(){

}else{

}

switch中表达式的值必须为整型或字符型,常量值1必须也是整型或字符型。

switch(表达式){

case 常量值1;

​ ;

​ break;

default:

​ ;

}

2、循环

while 、 do while while后面表达式的值为boolean类型,do while与while的区别是 do while至少执行一次

for(表达式1;表达式2;表达式3){

​ ;

}

第2章 面向对象

封装、继承、多态

一、类

成员变量可以被public、private、和static等修饰符所修饰,而局部变量的作用域只在这个方法内,不能被访问控制修饰符及static等所修饰;成员变量和局部变量都可以被final所修饰而成为常量。

什么是对象?

在Java中,对象是类的一个实例。它是一个具有状态和行为的实体,可以通过调用其方法来执行操作。对象具有属性和方法,属性是对象的状态,而方法是对象的行为。在Java中,对象是通过使用new关键字来创建的,它会调用类的构造函数来初始化对象的状态。对象在Java中是一种非常重要的概念,因为它们允许我们将现实世界中的事物抽象为代码中的实体,并且可以通过对象之间的交互来实现复杂的功能。

1、创建类的对象(两种方式)

​ 类名 对象名;

​ 对象名= new 类名();

类名 对象名 = new 类名();

package com.ji_chu;

public class Temporary {
    public static void main(String[] args) {
        Temporary temporary = new Temporary();
        temporary.run(); //可以调用run方法
        temporary.run1();
    }
    public void run(){
        System.out.println("调用run方法");
    }
    public void run1(){
        run();//可以调用run方法
        System.out.println("经过run1方法");
    }
}

运行结果:

调用run方法
调用run方法
经过run1方法

2、对象的使用

对象名.成员变量

对象名.成员方法

3、对象的比较:比较两个对象的地址

​ 回顾:==和equals的区别

public class Test3 {
    private double hei;

    public Test3() {

    }

    public Test3(double hei) {  //;默认是无参构造方法,但是写了有参构造方法,需要写无参构造方法
        this.hei = hei;
    }

  public void compare(Test3 v){
        if(this == v){  //==比较两个对象的地址
            System.out.println("这两个对象相等");
        }else{
            System.out.println("这两个对象不相等");
        }
  }

    public static void main(String[] args) {
        Test3 v1 = new Test3(2);
        Test3 v2 = new Test3(2);
        Test3 v3 = v1;
        v1.compare(v2);
        v1.compare(v3);
    }
}

运行结果:

​ 这两个对象不相等

​ 这两个对象相等

4、对象的内存分配

new运算符为对象动态分配了内存空间。

声明对象的为Test类型并没有分配空间

Test t1;

只有使用new创建对象过后,

t1 = new(1,2);

内存中才划分了储存空间。

5、对象的内存释放

Java中,定义对象时,通过new为对象分配内存,对象使用完过后内存的释放是由系统自动完成的。这就是Java的“自动垃圾收集”机制。当一个对象的引用不存在时,则该对象被认为是不再需要的,它所占用的内存就被系统回收。当时一些特殊情况下,也可以使用finialize()方法手动回收。

6、Java中,什么是对象,什么是对象引用

如下表达式

A a1 = new A();

它代表A是类,a1是引用,a1不是对象,new A()才是对象,a1引用指向new A()这个对象。

a1也叫对象的引用变量。

在JAVA里,“=”不能被看成是一个赋值语句,它不是在把一个对象赋给另外一个对象,它的执行过程实质上是将右边对象的地址传给了左边的引用,使得左边的引用指向了右边的对象。JAVA表面上看起来没有指针,但它的引用其实质就是一个指针,引用里面存放的并不是对象,而是该对象的地址,使得该引用指向了对象。在JAVA里,“=”语句不应该被翻译成赋值语句,因为它所执行的确实不是一个赋值的过程,而是一个传地址的过程,被译成赋值语句会造成很多误解,译得不准确。

再如:

A a2;

它代表A是类,a2是引用,a2不是对象,a2所指向的对象为空null;

再如:

a2 = a1;

它代表,a2是引用,a1也是引用,a1所指向的对象的地址传给了a2(传址),使得a2和a1指向了同一对象。

综上所述,可以简单的记为,在初始化时,“=”语句左边的是引用,右边new出来的是对象。

在后面的左右都是引用的“=”语句时,左右的引用同时指向了右边引用所指向的对象。

再所谓实例,其实就是对象的同义词。

7、匿名对象

创建对象的标准格式:
类名称 对象名(引用变量名) = new 类名称();
匿名对象就是只有右边的对象,没有左边的名字和赋值运算符。
new 类名称();
  • 注意事项:匿名对象只能使用唯一的一次,下次再使用不得不创建一个新的对象,当这个方法执行完,这个匿名对象就变成了垃圾。
  • 使用建议:如果确定一个对象只需要使用一次,就可以使用匿名对象
methodParam(new Scanner(System.in));

//使用匿名对象来进行传参

//使用匿名函数作为返回值

匿名对象是指没有名字的对象。实际上,对于对象实例化操作来讲,对象真正有用的部分是在堆内存中,而栈内存中只是保存了一个对象的引用名称(严格来讲是对象在堆内存的地址),所谓匿名对象是指,只开辟了堆内存空间,而没有栈内存指向的对象。

package com.ji_chu;

public class Temporary {
    public static void main(String[] args) {
        new Temporary().run();//匿名对象
    }
    public void run(){
        System.out.println("调用run方法");
    }
}

8、堆内存

博客地址:https://zhuanlan.zhihu.com/p/529280783

什么是堆内存?
堆内存是java内存中的一种,它的作用是用于存储java中的对象和数组,当我们new一个对象或者创建一个数组的时候,就会在堆内存中开辟一段空间给它,用于存放。
堆内存的特点是什么?
第一点:堆其实可以类似的看做是管道,或者说是平时去排队买票的情况差不多,所以堆内存的特点就是:先进先出,后进后出,也就是你先排队好,你先买票。
第二点:堆可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,但缺点是,由于要在运行时动态分配内存,存取速度较慢。
new对象在堆中如何分配?
由Java虚拟机的自动垃圾回收器来管理。

9、栈内存

什么是栈内存
栈内存是Java的另一种内存,主要是用来执行程序用的,比如:基本类型的变量和对象的引用变量
栈内存的特点
第一点:栈内存就好像一个矿泉水瓶,往里面放入东西,那马先放入的沉入底部,所以它的特点是:先进后出,后进先出
第二点:存取速度比堆要快,仅次于寄存器,栈数据可以共享,但缺点是,存在栈中的数据大小与生存必须是确定的,缺乏灵活性
栈内存分配机制
栈内存可以称为一级缓存,由垃圾回收器自动回收
数据共享
例子:
int a = 3;
int b = 3;

10、构造函数

构造函数是在对象创建时,初始化对象成员的方法。

构造方法的名称必须与所在类的类名完全相同(包括大小写),没有返回值,void也不能用,因为一个类的构造函数的返回值的类型就是该类的对象。

构造方法只能由new运算符调用,不能由程序员调用。

类的构造函数是不能被继承的,但子类可以调用父类的构造函数。

不允许构造函数指定返回类型和返回值,void也不行

构造函数可以重载,以提供多种不同参数形式的构造函数。

类中没有提供任何构造函数,系统才启动默认的无参构造函数。如果写了有参构造函数,必须手写无参构造函数,不然要报错。

10.1 Java中 为什么构造器的变量不能是static ?

在Java中,构造器是用于创建对象的特殊方法。它们被用来初始化对象的实例变量。因为构造器是用于创建对象的,所以它们不能是静态的。静态变量是属于类的,而不是属于对象的。如果构造器中的变量是静态的,那么这些变量将会在类加载时被初始化,而不是在对象创建时被初始化。这样就会导致构造器无法正确地初始化对象的实例变量,从而导致程序出错。因此,构造器中的变量不能是静态的。

如下,no不能被构造器初始化,name可以。

public class ConstructorLearning {
    private static String no;
    String name;

    public ConstructorLearning(String name) {
        this.name = name;
    }
}

11、this引用

A a1 = new A();

它代表A是类,a1是引用,a1不是对象,new A()才是对象,a1引用指向new A()这个对象。

a1也叫对象的引用变量。

1、成员变量与局部变量重名时,在方法中使用this,表示的是该方法所在类中的成员变量。(this是当前对象自己,也就是当前对象的引用) 注意,this不能调用static修饰的变量。

2、this作为当前参数进行传递。

3、this用于构造函数的自我调用。

12、static变量

static修饰的成员变量称为类的静态变量,不能把它当作实例对象的成员变量。

静态变量属于类所有,和实例变量不同的地方是,静态变量直接通过类名引用,实例变量只有在生成实例对象之后才存在,才可以被引用。

可以把静态变量称为类变量,可以把非静态变量称为实例变量。

public class Test{

static int allClicks=0;    // 类变量(静态成员变量)    可以通过类名.静态变量或者对象名.静态变量

String str = "hello world";  // 实例变量(成员变量)    实例变量一般设为私有的,private修饰(解释如下,加粗部分)  可以通过this.成员变量

public void method() {
    int i = 0;  // 局部变量
}

}

静态变量是一个公共的储存单元,不保存在某一个对象实例的内存空间中,而是保存在类的内存空间的公共存储单元。换句话说,对于类的任何一个具体对象而言,静态变量是一个公共的存储单元,任何一个类对象访问它时,取得的都是相同的数值。同样,任何一个类的对象去修饰它时,也都是对同一个内存单元操作。

Java中 为什么静态成员变量一般都是private?

​ 静态成员变量一般都是private的原因是为了保证数据的安全性和封装性。静态成员变量是属于类的,而不是属于对象的,因此它们可以被类的所有对象共享。如果静态成员变量是public的,那么它们可以被任何对象直接访问和修改,这样会破坏数据的封装性和安全性。而如果静态成员变量是private的,那么只有类内部的方法才能访问和修改它们,这样就保证了数据的封装性和安全性。同时,通过提供公共的静态方法来访问和修改静态成员变量,可以更好地控制数据的访问和修改。

静态成员变量的使用格式有两种:

类名.静态成员变量名

对象名.静态成员变量名*(这种方式需要先创建对象,也就是,A a = new A(); )

静态变量的优点:使用所有对象的公共存储空间,可以节省大量的内存空间,尤其是大量创建对象的时候。

13、static方法

static修饰的方法属于类的静态方法,称为类方法。静态方法的实质是属于整个类的方法,而不加static修饰符的方法,是属于某个具体对象的方法,称为实例方法。实例方法不能用类名.静态方法名来调用,只能用对象名来调用。

调用静态方法格式如下:

类名.静态方法名

对象名.静态方法名

为什么要将方法声明为static,有以下几重含义:

1、非静态的方法属于某一个对象的方法,在这个对象创建时,对象的方法在内存中拥有自己专用的代码段,而static方法是属于整个类的,它在内存中的代码段将被本类创建的所有对象所共用,而不是被任意一个对象所专用。

2、由于static是属于整个类的,它不能操作和处理某个对象的成员,而只能处理属于整个类的成员,也就是说static方法只能访问static成员变量或者调用static方法。或者说,在静态方法中不能访问实例变量的实例方法,如果静态方法需要访问实例变量或实例方法,需要通过传递实例对象的方式来实现。

3、静态方法中不能使用this和super,因为它们都代表的是对象的概念,this代表本类对象,super代表上层父类的概念。

14、方法的调用

1、方法的调用就是实参向形参传递数据,把程序流程转移到调用方法的入口处。方法定义时声明的参数列表是形参,重在说明参数类型,方法调用时提供的参数是实参。实参在个数、顺序、类型上必须保持一致,如果实参和形参的类型不一致,应符合类型转换规则。

2、Java参数传递,两种类型的参数:

基本数据类型——传值调用

引用数据类型——传递地址调用

3、调用形式

调用对象成员方法一般形式为:

对象名.方法名(【实际参数列表】)

静态方法属于类方法,调用的一般形式为:

类名.方法名(【实际参数列表】)

当方法参数是类类型时,方法的实参就是一个对象,这就是对象作为方法参数的形式。所有Java对象的传递都是通过引用实现,传递的是内存地址,而不是整个对象的拷贝。

int result = add(3, 5); // 3和5是实参

public static int add(int x, int y) { //x和y是形参

return x + y; }

第3章 继承、封装、多态

Java三大特性:封装、继承、多态

一、组合

1、继承和组合的选用

组合和继承目的都是为了代码复用,判断使用组合还是继承,需要根据具体的场景来决定。如果需要扩展或修改另一个类的功能,可以使用继承;如果只需要使用另一个类的功能,可以使用组合。 简单来说,如果只是想重用某个类的方法,使用组合完全可以实现。

继承的缺点:会增加类之间的耦合性,如果父类的实现发生变化,子类也需要相应地修改。

组合的优点:是可以灵活地组合不同的类,减少了类之间的耦合性,

组合的缺点:是需要更多的代码来管理对象之间的关系,也就是需要更大的代码量。

1.2 Java中有两种代码复用的方式:

​ 第一种方式是直接在新的类中,使用已有的类来定义一个对象作为新类的一个成员,这种方式成为组合;(如下已有类Wheel 定义一个对象为frontLeftWheel,总共定义了四个对象)

​ 第二种方式是按照已有类的类型来创建新类。

1.1 在Java中,继承和组合都是实现代码重用的方式,但它们的应用场景略有不同。

当一个类需要扩展另一个类的功能时,可以使用继承。

​ 例如,如果有一个Animal类,现在需要创建一个Dog类,那么可以让Dog类继承Animal类,这样Dog类就可以拥有Animal类的所有属性和方法,并且还可以添加自己的属性和方法。

另一方面,当一个类需要使用另一个类的功能时,可以使用组合。

​ 例如,如果有一个Car类,现在需要在Car类中使用Engine类的功能,那么可以在Car类中创建一个Engine对象,并调用Engine类的方法来实现需要的功能。 总的来说,当需要扩展现有类的功能时,使用继承;当需要使用其他类的功能时,使用组合。但是,需要注意的是,过度使用继承和组合都可能导致代码复杂性增加,因此需要谨慎使用。

2、组合

在Java中,组合是指一个类包含另一个类的对象作为其成员变量。这种关系是一种强关联关系,表示一个类是由另一个类组成的。组合关系通常用于表示整体与部分之间的关系,其中整体对象包含部分对象,部分对象不能独立存在。 例

如,一个汽车类可以包含多个轮子对象作为其成员变量,这种关系就是组合关系。

在代码中,可以通过在类中定义成员变量来实现组合关系,例如:

```
public class Car {
    private Wheel frontLeftWheel;
    private Wheel frontRightWheel;
    private Wheel rearLeftWheel;
    private Wheel rearRightWheel;
    
    // 构造方法和其他方法省略
}

public class Wheel {
    // 轮子的属性和方法省略  
    
    }
}
```

在上面的例子中,Car类包含四个Wheel对象作为其成员变量,这就是一种组合关系。

通过组合关系,Car类可以使用Wheel类的属性和方法,同时也可以控制Wheel对象的创建和销毁。

如果需要使用成员变量,则如下,先创建一个对象给左边的引用,就可以调用了。( private Person v1 = new Person(); 下面可以调用 v1.run(); )

​ 如果不创建对象给左边的成员变量v1的引用,此时左边的v1引用成员变量指向的是空地址,就会空指针异常。(如果直接写private Person v1,那么程序会抛出空指针异常)

package com.ji_chu;

import org.junit.Test;

public class Temporary {
    private A e;

    @Test
    public void test(){
        A a = new A();
        a.run(); //正常调用run()方法
        e.run();//空指针异常:java.lang.NullPointerException

    }
}

class A{
    public void run(){
        int value = 10;
        System.out.println(value);
    }
}

所以,组合就是在一个新类中使用一个已有类来定义一个对象作为新类的成员。

二、继承

继承是面向对象(OOP)的一个基本特性。

继承中,已有的类叫超类、父类、基类,创建的类叫字类、派生类、孩子类。

继承的关键字extends。

在Java中,定义继承的格式为:

public class 父类名{

}

public class 子类名 extends 父类名 {

// 子类的成员变量和方法

}

其中,子类名为要定义的子类的名称,父类名为要继承的父类的名称。

使用关键字extends表示子类继承自父类。

在子类中可以定义自己的成员变量和方法,也可以重写父类的方法。

1、子类的注意点

(1)、Java中,不支持多继承,因为多继承会带来菱形继承问题!但是,Java提供了接口(interface)的概念来实现类似多继承的功能,一个类可以实现多个接口。

(2)、子类有父类的所有属性和方法,但是,不继承父类的构造方法,子类可以增加自己的属性和方法,并且子类无法访问父类的私有成员或方法( 如果要访问私有成员,就写get、set方法)。

(3)Java中,子类继承的权限要比父类的权限要大,不能比父类的权限小。例如:子类中的public属性继承父类中的private属性。

(4)、子类可以直接访问Java中被protected修饰的成员变量和成员方法,无论是同一个类、同一个包下的其它同类、不同包的子类都可以访问被protected修饰的成员变量和成员方法,但是,不在同一个包中的非子类类不能直接访问被protected修饰的成员变量和成员方法。

(5)访问父类的方法用super关键字,格式:super.方法名();

2、继承中的构造函数

备注:默认构造函数就是无参数构造函数。

(1)、如果父类没有定义默认的构造函数,全都是带参数的构造函数,子类会默认调用父类的无参构造函数。如果父类没有无参构造函数,子类必须显式地调用父类的有参构造函数,并传入相应的参数。如果子类没有显式地调用父类的构造函数,编译器会自动插入一个默认的无参构造函数调用,但是如果父类没有无参构造函数,编译器会报错。

因此,子类必须显式地调用父类的构造函数,以确保父类的构造函数被正确地调用。 (子类需要调用父类的构造方法,就需要显示调用)

显示调用父类构造函数的方法:super(参数)

需注意,调用父类的构造函数的语句:super(参数),必须在子类构造函数的第一条语句。

在Java中,子类可以通过使用super关键字来调用父类的构造方法。如果需要调用父类的哪个构造方法,就参数个数一一对应就好了。

super关键字必须是子类构造方法中的第一条语句,用于调用父类的构造方法。

以下是一个示例1:

public class ParentClass {

public ParentClass(int num) {

// 父类构造方法

}

}

public class ChildClass extends ParentClass {

public ChildClass(int num) {

super(num); // 调用父类构造方法 ,(显示调用父类的构造方法)

// 子类构造方法

} }

在上面的示例中,子类ChildClass通过super关键字调用了父类ParentClass的构造方法,并传递了一个int类型的参数num。这样就可以在子类中使用父类的构造方法来初始化父类中的成员变量。

以下示例2:

```public class ChildClass extends ParentClass {

public ChildClass(int arg1, int arg2) {

super(arg1); // 调用父类的构造方法

​ // 子类的其他构造方法代码 }

public ChildClass(int arg1, int arg2, int arg3) {

// 子类的构造方法代码

} }

三、重写和重载

重写(overide)重载(overload)

1、方法重载和重写的区别

1、重载:Java可以在一个类中定义几个同名的方法,只要这些方法具有不同的参数集合(参数的个数、类型、次序),这些方法成为重载。返回值可以相同也可以不同。方法重载的目的是为了提高代码的复用性和可读性。

2、重写:方法重写是指在子类中重新定义父类中已有的方法,方法名、参数列表和返回值类型必须与父类中的方法相同。方法重写的目的是为了实现多态性,即在运行时根据对象的实际类型调用相应的方法。

  1. 方法重载是编译时多态,方法重写是运行时多态。
  2. 方法重载是静态绑定(编译时绑定),方法重写是动态绑定(运行时绑定)
  3. 方法重载与方法重写的区别在于方法重载是在同一个类中定义多个方法,而方法重写是在子类中重新定义父类中已有的方法。

2、方法重写

Java中的方法调用是先在子类中寻找。当子类继承了父类的方法时,如果子类中定义了与父类相同的方法名、参数列表和返回类型的方法,那么在调用该方法时,会优先调用子类中的方法,而不是父类中的方法。如果子类中没有定义与父类相同的方法,则会在父类中寻找该方法。这种行为被称为方法重写(override)。如果需要在子类中调用父类中的方法,可以使用super关键字来实现。

方法重写(Method Overriding)是指在子类中定义一个与父类中同名、同参数列表、同返回值类型的方法,从而覆盖父类中的方法实现。在Java中,方法重写是实现多态性的一种方式。

方法重写的规则如下:

  1. 方法名、参数列表和返回值类型必须与父类中被重写的方法相同。

  2. 访问修饰符不能比父类中被重写的方法的访问修饰符更严格。例如,如果父类中的方法是public,那么子类中重写的方法也必须是public。

  3. 子类中重写的方法不能抛出比父类中被重写的方法更多的异常。

  4. 子类中重写的方法不能使用比父类中被重写的方法更严格的访问修饰符。

  5. 如果父类中被重写的方法是final或private,那么子类中不能重写该方法。

  6. 如果父类中被重写的方法是static,那么子类中不能重写该方法,但可以定义一个同名的静态方法。

四、final关键字

final修饰常量:常量值不能被修改;

final修饰方法,public final void run(){}:方法不能被重写;

final修饰类,public final class Person(){},类不能被继承。

五、多态

方法重载是编译时多态,方法重写是运行时多态。

方法重载是静态绑定(编译时绑定),方法重写是动态绑定(运行时绑定)

1、静态绑定和动态绑定

(1)、程序绑定的概念:
绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来。对java来说,绑定分为静态绑定和动态绑定;或者叫做前期绑定和后期绑定。

静态绑定:在程序执行前方法已经被绑定(也就是说在编译过程中就已经知道这个方法到底是哪个类中的方法),此时由编译器或其它连接程序实现。例如:C。
针对java简单的可以理解为程序编译期的绑定;这里特别说明一点,java当中的方法只有final,static,private和构造方法是前期绑定
动态绑定:后期绑定:在运行时根据具体对象的类型进行绑定。
若一种语言实现了后期绑定,同时必须提供一些机制,可在运行期间判断对象的类型,并分别调用适当的方法。也就是说,编译器此时依然不知道对象的类型,但方法调用机制能自己去调查,找到正确的方法主体。不同的语言对后期绑定的实现方法是有所区别的。但我们至少可以这样认为:它们都要在对象中安插某些特殊类型的信息。

(2)、关于final,static,private和构造方法是前期绑定的理解
对于private的方法,首先一点它不能被继承,既然不能被继承那么就没办法通过它子类的对象来调用,而只能通过这个类自身的对象来调用。因此就可以说private方法和定义这个方法的类绑定在了一起。

final方法虽然可以被继承,但不能被重写(覆盖),虽然子类对象可以调用,但是调用的都是父类中所定义的那个final方法,(由此我们可以知道将方法声明为final类型,一是为了防止方法被覆盖,二是为了有效地关闭java中的动态绑定)。

构造方法也是不能被继承的(网上也有说子类无条件地继承父类的无参数构造函数作为自己的构造函数,不过个人认为这个说法不太恰当,因为我们知道子类是通过super()来调用父类的无参构造方法,来完成对父类的初始化, 而我们使用从父类继承过来的方法是不用这样做的,因此不应该说子类继承了父类的构造方法),因此编译时也可以知道这个构造方法到底是属于哪个类。

对于static方法,具体的原理我也说不太清。不过根据网上的资料和我自己做的实验可以得出结论:**static方法可以被子类继承,但是不能被子类重写(**覆盖),但是可以被子类隐藏。(这里意思是说如果父类里有一个static方法,它的子类里如果没有对应的方法,那么当子类对象调用这个方法时就会使用父类中的方法。而如果子类中定义了相同的方法,则会调用子类的中定义的方法。唯一的不同就是,当子类对象上转型为父类对象时,不论子类中有没有定义这个静态方法,该对象都会使用父类中的静态方法。因此这里说静态方法可以被隐藏而不能被覆盖。这与子类隐藏父类中的成员变量是一样的。隐藏和覆盖的区别在于,子类对象转换成父类对象后,能够访问父类被隐藏的变量和方法,而不能访问父类被覆盖的方法)
由上面我们可以得出结论,如果一个方法不可被继承或者继承后不可被覆盖,那么这个方法就采用的静态绑定。

(3)、java的编译与运行
java的编译过程是将java源文件编译成字节码(jvm可执行代码,即.class文件)的过程,在这个过程中java是不与内存打交道的,在这个过程中编译器会进行语法的分析,如果语法不正确就会报错。

Java的运行过程是指jvm(java虚拟机)装载字节码文件并解释执行。在这个过程才是真正的创立内存布局,执行java程序。

java字节码的执行有两种方式: (1)即时编译方式:解释器先将字节编译成机器码,然后再执行该机器码;(2)解释执行方式:解释器通过每次解释并执行一小段代码来完成java字节码程序的所有操作。(这里我们可以看出java程序在执行过程中其实是进行了两次转换,先转成字节码再转换成机器码。这也正是java能一次编译,到处运行的原因。在不同的平台上装上对应的java虚拟机,就可以实现相同的字节码转换成不同平台上的机器码,从而在不同的平台上运行)

前面已经说了对于java当中的方法而言,除了final,static,private
和构造方法是前期绑定外,其他的方法全部为动态绑定
而动态绑定的典型发生在父类和子类的转换声明之下:
比如:Parent p = new Children();

其具体过程细节如下:
1:编译器检查对象的声明类型和方法名。
假设我们调用x.f(args)方法,并且x已经被声明为C类的对象,那么编译器会列举出C 类中所有的名称为f 的方法和从C 类的超类继承过来的f 方法。
2:接下来编译器检查方法调用中提供的参数类型。
如果在所有名称为f 的方法中有一个参数类型和调用提供的参数类型最为匹配,那么就调用这个方法,这个过程叫做“重载解析”。
3:当程序运行并且使用动态绑定调用方法时,虚拟机必须调用同x所指向的对象的实际类型相匹配的方法版本。

假设实际类型为D(C的子类),如果D类定义了f(String)那么该方法被调用,否则就在D的超类中搜寻方法f(String),依次类推。

JAVA 虚拟机调用一个类方法时(静态方法),它会基于对象引用的类型(通常在编译时可知)来选择所调用的方法。相反,当虚拟机调用一个实例方法时,它会基于对象实际的类型(只能在运行时得知)来选择所调用的方法,这就是动态绑定,是多态的一种。动态绑定为解决实际的业务问题提供了很大的灵活性,是一种非常优美的机制。

与方法不同,在处理java类中的成员变量(实例变量和类变量)时,并不是采用运行时绑定,而是一般意义上的静态绑定。所以在向上转型的情况下,对象的方法可以找到子类,而对象的属性(成员变量)还是父类的属性(子类对父类成员变量的隐藏)

Java代码

public class Father {
    protected String name = "父亲属性";
}

​ public class Son extends Father {
​ protected String name = “儿子属性”;
​ public static void main(String[] args) {
​ Father sample = new Son();
​ System.out.println(“调用的属性:” + sample.name);
​ }
​ }
结论,调用的成员为父亲的属性。
这个结果表明,子类的对象(由父类的引用handle)调用到的是父类的成员变量。所以必须明确,运行时(动态)绑定针对的范畴只是对象的方法。

现在试图调用子类的成员变量name,该怎么做?最简单的办法是将该成员变量封装成方法getter形式
代码如下:
Java代码

public class Father {
    protected String name = "父亲属性";

    public String getName() {
        return name;
    }

}  

public class Son extends Father {
    protected String name = "儿子属性";
public String getName() {
    return name;
}

public static void main(String[] args) {
    Father sample = new Son();
    System.out.println("调用的属性:" + sample.getName());
}
}

结果:调用的是儿子的属性

java为什么对属性要采取静态的绑定方法。这是因为静态绑定是有很多的好处,它可以让我们在编译期就发现程序中的错误,而不是在运行期。这样就可以提高程序的运行效率!而对方法采取动态绑定是为了实现多态,多态是java的一大特色。多态也是面向对象的关键技术之一,所以java是以效率为代价来实现多态这是很值得的。

1.1静态绑定和动态绑定的总结

Java中成员变量就是属性。

总结出:继承存在的时候,对于父类属性、父类方法、子类属性、子类方法进行调用的方法如下。

对方法采取动态绑定是为了实现多态

Java对属性(成员变量)是静态绑定,对于方法(成员方法)动态绑定**。

多态存在的两个条件:继承和重写,当继承和重写存在时,

口诀:成员变量(属性)编译看左边,运行看左边 编译和运行都是看左边父类的成员变量,但是要存在重写。

成员方法编译看左边,运行看右边。 也就是说,要先看左边的父类中的方法有没有被重写,被重写了才会继续执行,运行结果看右边,如果没被重写,编译报错。

可得: 成员变量在编译和运行都是看的左边

​ 成员方法会检查左边的父类中有没有调用的方法run(),执行的是右边子类中重写的方法run()。如果在父类中没找到,会报错

,编译不能通过。

(1)、属性是静态绑定,静态绑定时编译时

Father sample = new Son(); 此时Father 为父类,Son为子类

当父类和子类中同时存在一个属性name,此时sample.name取得的是父类中的name属性,因为静态绑定是编译中绑定,因此编译运行都看看左边。上面实例化对象的式子,左边为Father ,则调用Father 中的name属性。

上面即可得,前提条件是:多态(存在继承、重写、向上转型),获取的是父类的属性,

也就是说前提条件是:多态(继承、重写(前提是当前被调用的子类方法,是重写了父类的方法)、向上转型),如果想要获取子类的属性,那么将子类属性写上对应的set方法,获取方法就行了, 如下(2)。 如果不是多态,那就创建子类的对象,再调用子类的属性或者方法。 Son sample = new Son();

(2)、方法是动态绑定,动态绑定是运行时

Father sample = new Son(); 此时Father 为父类,Son为子类

当父类和子类中同时存在一个属性name,并且同时存在一个获取name属性的get方法,此时sample.getName()调用的是子类中的getName()方法。因为动态绑定是运行时绑定,运行看右边,上面式子,调用子类Son中的方法。

如果想要获取父类的方法,直接通过super.方法()

如果想要获取子类的方法:直接给子类创建对象,引用变量.子类的方法

Father sample = new Son(); 此时Father 为父类,Son为子类

注意:可以将子类的对象赋值给父类的引用,但是不能将父类的对象赋值给子类的引用,也就是说(Son sample = new Father; ),这样写是会报错的。

所以向上转型是可以的,Java中的向上转型是指将一个子类对象转换为其父类类型的过程。

2、多态

Java 实现多态有三个必要条件:继承、重写和向上转型(即父类引用指向子类对象)

要实现多态,必须存在继承、向上转型和重写这三个条件。

继承是多态的前提,因为子类必须继承父类的方法和属性,才能在子类中重写父类的方法。

向上转型是多态的实现方式之一,它是将子类对象赋值给父类引用的过程,这样就可以通过父类引用调用子类重写的方法,实现多态。

重写是多态的关键,它是指子类重写父类的方法,使得在运行时根据实际对象类型调用相应的方法,实现多态。如果没有重写,就无法实现多态。

向上转型

要理解多态必须要明白什么是”向上转型”,比如,一段代码如下,Dog 类是 Animal 类的子类:

Animal a = new Animal(); //a是父类的引用指向的是本类的对象

Animal b = new Dog(); //b是父类的引用指向的是子类的对象

注:不能使用一个子类的引用去指向父类的对象,因为子类对象中可能会含有父类对象中所没有的属性和方法。

例如:

class Animal {

//父类方法

public void bark() {

System.out.println(“动物叫!”);

}

}

class Dog extends Animal {

//子类重写父类的bark方法

public void bark() {

System.out.println(“汪、汪、汪!”);

}

//子类自己的方法

public void dogType() {

System.out.println(“这是什么品种的狗?”);

}

}

public class Test {

public static void main(String[] args) {

Animal a = new Animal();

Animal b = new Dog();

Dog d = new Dog();

a.bark();

b.bark(); //首先编译的时候检查左边父类Animal 中有bark()方法,子类也有自己的bark()方法,运行的时候就看右边,直接调用子类的bark()方法

//b.dogType();

//b.dogType()编译不通过 , 编译看左边,检查一次 因为父类Animal 没有方法dogType(),

d.bark();

d.dogType();

}

}

编译运行:

$ javac Test.java

$ java Test

动物叫!

汪、汪、汪!

汪、汪、汪!

这是什么品种的狗?

六、抽象类(只能被单继承)

Java的类(抽象类也是类)只支持单继承,类和抽象类可以继承多个接口,接口也可以被多个实现类实现,接口可以继承多个接口。

Java中抽象类和接口都不能被实例化。

抽象类的目的是为了提供一种抽象的模板或者基础类。抽象类中可以包含抽象方法和非抽象方法,抽象方法没有具体的实现,需要在子类中实现,而非抽象方法则有具体的实现,子类可以直接继承使用。通过使用抽象类,可以将一些通用的属性和方法抽象出来,让子类去实现具体的细节,从而提高代码的复用性和可维护性。

抽象类和抽象方法用abstract修饰。

1、抽象类的定义

抽象类的定义格式如下:

```

public abstract class ClassName {

// 抽象方法的定义

public abstract void methodName();

// 非抽象方法的定义

public void nonAbstractMethod() {

// 方法体 }

} ```

其中,关键字abstract用于修饰抽象类,抽象类中可以包含抽象方法和非抽象方法。抽象方法没有方法体,只有方法声明,需要在子类中实现具体的方法体。非抽象方法有方法体,可以直接在抽象类中实现,子类可以直接继承使用。抽象类不能被实例化(不能产生对象),只能被继承

抽象类的子类必须重写抽象类的抽象方法,并提供方法体。如果没有重写全部的抽象方法,那么子类仍然为抽象类。

2、抽象类与实现类存在多态性,用代码验证

下面是一个简单的例子,其中有一个抽象类Animal和两个实现类Dog和Cat。在主函数中,我们创建了一个Animal类型的数组,其中包含两个Dog和一个Cat对象。然后我们遍历这个数组,调用每个对象的makeSound()方法,这里就体现了多态性。

abstract class Animal {
    public abstract void makeSound();
}

class Cat extends Animal{
    @Override
    public void makeSound(){
        System.out.println("Cat:miao miao miao");
    }
}
class Dog extends Animal{

    @Override
    public void makeSound() {
        System.out.println("Dog:ao ao ao");
    }
}

public class TestAnimal {
    public static void main(String[] args) {
        Animal[] animals = new Animal[2];
        animals[0] = new Dog();
        animals[1] = new Cat();

        for(Animal animal:animals){ //此处为增强for,下面有关于增强for的介绍,集合中有两个元素,所以上面声明数组的个数要为2,不然遍历数组的时候,会出现空指针异常。
            animal.makeSound();
        }
    }
}
    输出结果为:
    Dog:ao ao ao
    Cat:miao miao miao

可以看到,我们通过Animal类型的数组来存储不同类型的对象,然后通过调用makeSound()方法来实现多态性。在运行时,程序会自动选择正确的实现类来执行方法。传入不同对象,得到不同的方法结果。

多态的三个必要条件:继承、重写、向上转型(父类引用变量指向子类的对象) ,上面例子中,实现类(子类)继承了父类,并实现(重写了父类的方法makeSound,还进行了向上转型),因此,抽象类和实现类存在多态性。

3、增强for

增强for循环是Java中的一种循环语句,也称为foreach循环。它可以遍历数组或集合中的元素,而不需要使用传统的for循环的索引变量。

增强for循环的语法如下:

for (element : collection) {

// 循环体

}

其中,element是集合中的元素,collection是要遍历的集合。在循环体中,可以直接使用element来操作集合中的元素。

增强for循环的优点是简洁、易读,可以减少代码量和出错的可能性。但是,它也有一些限制,例如无法访问集合的索引,无法修改集合中的元素等。因此,在某些情况下,传统的for循环可能更加灵活和适用。

七、接口

1、Java中有了抽象类,为什么还要使用接口?

ava中抽象类和接口都是用来实现多态性的机制,但它们有不同的用途和特点。

抽象类是一种特殊的类,它不能被实例化,只能被继承。它可以包含抽象方法和非抽象方法,抽象方法没有实现,需要子类去实现。抽象类可以提供一些通用的实现,但是它只能被单继承,因此它的灵活性有限。 接口是一种规范,它定义了一组方法的签名,但是没有实现。

Java的类(抽象类也是类)只支持单继承,类和抽象类可以继承多个接口,一个类可以实现多个接口,接口也可以被多个实现类实现,接口可以继承多个接口。

Java中抽象类和接口都不能被实例化。

接口可以被多个类实现,一个类可以实现多个接口。

2、接口的定义

(1)、Java中的接口的成员方法默认是public static final。这是因为接口中的方法是用来定义行为的,而不是用来实现行为的。因此,它们必须是公共的,以便其他类可以使用它们。同时,接口中的方法是静态的,因为它们不依赖于实例化对象,而是独立于任何对象的。最后,接口中的方法是常量,因为它们不能被修改,只能被实现。

接口中的**(抽象方法)成员方法默认public abstract的。但是,接口中的成员变量默认public static final**的。因此,接口中的成员方法可以省略public和abstract关键字,但是不能省略static关键字。

接口中的成员变量必须是public static final,也就是常量。这是因为接口中的成员变量默认是public static final修饰的,而且不能被修改,平时定义成员变量 public static final可以省略。这样做的目的是为了确保接口的实现类能够访问到这些常量,并且保证这些常量的值不会被修改。

例如:

```
public interface MyInterface {
    int MAX_VALUE = 100; // 等价于 public static final int MAX_VALUE = 100;  //因为
    void doSomething(); // 等价于 public abstract void doSomething();  //因为接口中的方法是需要被实现类实现的,所以是抽象的abstract
    static void doStatic() {
        // 接口中可以定义静态方法
    }
}
```

接口中定义的静态方法可以直接通过接口名调用,不需要实例化接口。例如: MyInterface.doStatic();

静态方法可以用于提供一些公共的功能,例如工具类方法等。注意,静态方法不能访问接口中的非静态成员,因为接口中的成员都是默认为public static final的。

接口与实现类之间存在多态性,多态存在的三个条件(继承、重写、向上转型)

(2)、接口的实现类必须实现接口中的所有方法吗?

Java中接口的实现类必须实现接口中的所有方法,实现类实现了接口的所有方法,实现类方可实例化。

如果一个类实现了一个接口,但没有实现接口中的所有方法,那么这个类必须声明为抽象类。

因此,实现类必须实现接口中定义的所有方法,否则编译器会报错。

八、抽象类和接口相同与不同

相同点: 1. 都不能被实例化,只能被继承或实现。 2. 都可以包含抽象方法,需要子类实现。 3. 都可以被用来实现多态性。

不同点: 1. 抽象类可以包含非抽象方法和属性(成员变量),而接口只能包含抽象方法和常量(成员变量为public static final,也就是常量)。

  1. 子类只能继承一个抽象类,但子类可以实现多个接口。
  2. 抽象类的方法可以有访问修饰符,而接口的方法默认为public。
  3. 接口可以被用来定义类型,而抽象类不能。
  4. 抽象类的构造方法可以有参数,而接口没有构造方法。

第4章 访问权限

一、包的定义

包的成员包括:类、接口、子包,子包里面又可以包含类、接口、子包。

在Java中,包里面的文件命名需要遵循以下规则:

  1. 文件名必须与类名相同,包括大小写。

  2. 文件名必须以.java为后缀。

  3. 文件名必须使用驼峰命名法,即首字母小写,后面的单词首字母大写。

  4. 包名必须使用小写字母,多个单词之间用点号(.)分隔。

例如,

一个名为com.example.myapp的包中包含一个名为MyClass的类,那么该类的文件名应该为MyClass.java,放置在com/example/myapp目录下。这个类的完整限定名为:com.example.myapp.MyClass

​ 同一个包中不能有两个相同的成员,比如已经在包中定义了MyClass类,就不能在目录com/example/myapp下面再定义一个叫MyClass的类,也不能再定义一个叫MyClass的接口。

二、包的使用

1、在包中封装工具类,修饰符的类型

工具类可以是public,也可以是default(即不加访问修饰符),甚至可以是private。一般来说,如果工具类的方法需要在其他类中被调用,那么就应该将工具类声明为public。但是,如果工具类的方法只在同一个包中被调用,那么就可以将工具类声明为default或private,以避免不必要的访问。

如果说将工具类设置成了private,那么在其它包中使用不可以访问该工具类,

​ 如果想要访问private类中的私有属性或方法,可以通过反射访问,但是,访问私有成员可能会破坏封装性,**应该谨慎使用,**在Java 9及以上版本中,使用反射访问私有成员可能会受到模块化的限制,需要在模块描述文件中声明对应的模块访问权限。总之,虽然可以通过反射访问私有成员,但是应该遵循封装性原则,尽量避免直接访问私有成员。 如果想要访问,应该将类设置为public 。

2、包的导入及命名

导入使用import,使用通配符 * ,可以导入包下面的所有类了,例如。import java.util.*

通常把域名的反向作为包名的第一部分,例如域名是example.com,反向就是com.example,后面再跟一个名字比如lcy,那么com.example.lcy就可以形成一个完整的包名了。一个包可以由许多的类构成。

3、访问权限修饰符

Java中有四种访问权限修饰符,分别是:

  1. public:公共的,可以被任何类访问。
  2. protected:受保护的,可以被同一包内的类和不同包中的子类访问。
  3. default(即不加修饰符):默认的,可以被同一包内的类访问。
  4. private:私有的,只能被同一类内部访问,其他类无法访问。
  5. 这些访问权限修饰符可以用来控制类、方法、变量的访问范围,从而实现封装性和安全性。

总结出来访问控制关键字有此顺序:private<default<protected<public ,private最严格,public最不严格。

第5章 数组和集合

数组是Java中的一个对象,类Object中定义的方法可以用于数组对象。Java可以声明任何类型的数组,包括基本类型和引用类型,数组在使用前必须先声明。

数组的大小是固定的,为了使程序方便地存储和操作数目不固定的一组数据,JDK类库中提供了Java集合,,所有集合都存放在Java.util包中,与Java数组不同,Java集合不能存放基本数据类型,只能存放对象地引用,为了表达方便,我们简称对象。

数组是在内存空间申请地一段连续地内存空间,其中数组名便是数组地首地址,数组下标从0开始,N-1结束,N是元素个数。

一、一维数组

1、一维数组的声明方式

//声明对象数组

Mydate a[];

声明数组时不能指定其长度(数组中的元素个数):int a[5];这是错误的写法int a[];才是正确的写法

声明:char s[];//注意这只是声明,是栈地址,说明此时还没有进行真正的内存分配;

​ int a[] = new int[5];//此时使用关键字new来创建数组,初始化的过程中,进行了空间分配。new int[5];是堆地址

2、一维数组的创建和初始化

每个数组都有一个属性length指明它的长度,也就是该数组的元素个数。

(1)、// 声明的同时赋值
String a[] = new String[]{“s”,“ss”};//静态初始化:在定义数组的同时,就为数组元素分配空间并赋值
int[] ty = new int[]{1,2,3};
char[] tu = new char[]{‘s’,‘e’};
(2)、// 声明过后再赋值,
int[] tr = new int[10]; //动态初始化:数组定义与数组分配空间和赋值的操作分开进行;
tr[0]=1;
char[] tr1 = new char[10];
tr1[0]=‘1’;

或者

int a[] = new int[5];
       for (int j = 0;j < a.length;j++){
           a[j] = j+1;
           System.out.println(a[j]);
       }

运行结果:

1
2
3
4
5

(3)、//声明过后不赋值

int a[] = new int[5];   //默认初始化:数组是引用类型,他的元素相当于类的成员变量,因此数组分配内存空间后,每个元素按照成员变量的规则被初始化。
System.out.println(a[2]);    //成员变量有默认值,局部变量没有默认值

输出结果为:0

二、二维数组

分别对应行和列

(1)、静态初始化

int a[][] = new int [2][3];
a[0][1] = 45;

(2)、动态初始化

int b[][] = new int[][]{{1,1,1},{2,2,2}};

或者

int[][] arr = new int[3][4];
for (int i = 0; i < arr.length; i++) {
    for (int j = 0; j < arr[i].length; j++) {
        arr[i][j] = i * j;
    }
}

三、集合

集合大纲

image-20230504111043127

0-0 快速走过集合的遍历。

package com.ji_he_Collections;

import org.junit.Test;

import java.util.*;

public class collectionDelete {
    @Test
    public void run(){
        //colection 无序 无下标 不能重复
        Collection objects = new ArrayList();
        objects.add("nihao25");
        objects.add("jhdask5656");

        String value = "";
        for (Object collection:objects){
           value = (String) collection;
            System.out.println(value.toString());
        }
        System.out.println("--------------");
        Iterator iterator = objects.iterator();
        while (iterator.hasNext()){
            Object next = iterator.next();
            System.out.println(next.toString());

        }
    }
    @Test
    public void run1(){
        List list1 = new ArrayList();
        list1.add("nihao25");
        list1.add("jhdask5656");
        list1.add("dsa545");

        ListIterator listIterator = list1.listIterator();
        while (listIterator.hasNext()){
            Object next = listIterator.next();
            System.out.println(next.toString());
        }
        System.out.println("1111111111");
        while (listIterator.hasPrevious()){
            Object previous = listIterator.previous();
            System.out.println(previous.toString());

        }
    }
    @Test
    public void run2(){//vector的枚举器
        Vector<Object> objects = new Vector<>();
        objects.add("1213");
        objects.add("1566");

        Enumeration<Object> elements = objects.elements();
        while (elements.hasMoreElements()){
            Object o = elements.nextElement();
            System.out.println(o.toString());
        }
    }

    @Test
    public void run3(){//Map集合的entrySet()和keySet()
    Map接口提供了两种遍历方式:entrySet()和keySet(),分别使用增强for和迭代器iterator遍历
        HashMap<String, String> hashMap1 = new HashMap<>();
        hashMap1.put("user1","123");
        hashMap1.put("user2","545646");

        for (Map.Entry<String,String> entry:hashMap1.entrySet()){
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key+":"+value);
        }
        System.out.println("-------------");
        for (String key:hashMap1.keySet()){
            System.out.println(key+":"+hashMap1.get(key));
        }
        System.out.println("-----------");
        Iterator<Map.Entry<String, String>> iterator = hashMap1.entrySet().iterator();
        while(iterator.hasNext()){
            Map.Entry<String, String> next = iterator.next();
            String key = next.getKey();
            String value = next.getValue();
            System.out.println(key+":"+value);
        }
        System.out.println("-----------");

        Iterator<String> iterator1 = hashMap1.keySet().iterator();
        while (iterator1.hasNext()){
            String key = iterator1.next();
            String value = hashMap1.get(key);
            System.out.println(key+":"+value);
        }
    }
}

1、集合概念

  • 概念:对象的容器,定义了对多个对象进行操作的常用方法 可实现数组的功能

    Java中的集合只能存放对象基本数据类型(如int、double等)不能直接存放在集合中,需要使用对应的包装类(如Integer、Double等)来存放。集合的实现类都是泛型类,需要指定存放的对象类型。

  • 和数组的区别:

    • 数组长度固定 集合长度不固定(大小可动态扩展)

    • 数组可以存储基本类型和引用类型 集合只能存储引用类型

    • 数组只能存储相同数据类型的数据,集合可以存储各种不同类型的数据。

    • 位置:Java.util.* (这个包下面包含了集合类)

    • Java.util.*是Java编程语言中的一个包(package),它包含了一系列常用的工具类和数据结构,如集合类、日期类、时间类、随机数生成器、枚举类、位集合类等等。这个包是Java标准库中最常用的包之一,它提供了许多实用的方法和数据结构,可以帮助程序员更方便地进行开发。

      Java的集合就是一个接口。

Java中的集合是一种动态数据结构,可以存储不同类型的数据,因为它们是通过泛型来实现的。泛型可以在创建集合时指定集合中元素的类型,因此可以存储不同类型的数据。例如,List可以存储字符串类型的数据,List可以存储整数类型的数据,List可以存储任何类型的数据。

而数组是一种静态数据结构,它在创建时必须指定元素的类型,因此只能存储相同类型的数据。例如,int[]只能存储整数类型的数据,String[]只能存储字符串类型的数据。如果要存储不同类型的数据,需要创建多个数组或使用Object类型的数组,但这样会增加代码的复杂度和维护成本。

2、Collection体系集合

image-20230605233124551

  1. ArrayList:适用于需要频繁访问元素的场景,但不适用于频繁插入或删除元素的场景。

  2. Vector:与ArrayList类似,但是线程安全,适用于多线程环境下需要频繁访问元素的场景。

  3. LinkedList:适用于频繁插入或删除元素的场景,但不适用于需要频繁访问元素的场景。

  4. HashSet:适用于需要快速查找元素的场景,但不保证元素的顺序。

  5. TreeSet:适用于需要有序访问元素的场景,但插入和删除元素的效率较低。

  6. HashMap:适用于需要快速查找元素的场景,但不保证元素的顺序。

  7. TreeMap:适用于需要有序访问元素的场景,但插入和删除元素的效率较低。

Java TreeMap是有序的。它根据键的自然顺序或者自定义的比较器进行排序。默认情况下,它按照键的自然顺序进行排序,如果键是字符串,则按照字典顺序排序。如果需要按照自定义的顺序进行排序,则可以通过实现Comparator接口来指定比较器。无论使用哪种方式,TreeMap都会保持有序状态。

3、父接口Collection

(1)、父接口Collection

特点: 代表一组任意类型的对象,无序、无下标、不能重复
方法:
boolean add(Object obj) //添加一个对象
boolean addAll(Collection c)//将一个集合中的所有对象添加到此集合中
void clear()//清空此集合中的所有对象
boolean contains(Object o)//检查此集合是否包含o对象
boolean equals(Object o)//判断此对象 是否与指定对象 相等
boolean isEmpty()//判断此集合是否为空
boolean remove (Object o) //在此集合中移除o对象
int size()//返回此集合中的元素个数
Object[] toArray()//将此集合转为数组
Iterator iterator()//迭代

(2)、Collection的使用

注意:在Java中,可以使用泛型来指定集合中存储的元素类型。但是,如果不指定类型,Java会默认将集合中的元素类型视为Object类型。 因此,当使用Collection objects = new ArrayList();时,Java会将objects集合中的元素类型视为Object类型,这意味着可以将任何类型的对象添加到集合中,包括字符串、整数、自定义对象等。虽然这种做法不会报错,但是在编译时无法进行类型检查,可能会导致运行时出现类型转换异常或其他错误。因此,建议在使用集合时尽可能指定元素类型,以提高代码的可读性和安全性。

使用add()、remove()、clear()、size()

package com.ji_he;

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo {
    public static void main(String[] args) {
        //创建集合
        Collection objects = new ArrayList();//Collection是接口,因此new其实现类ArrayList的对象,向上转型:父类引用指向子类对象
        //注意,上面这个对象是new ArrayList(),是list集合,list集合(接口)是可以有重复元素的。
        //添加元素
        objects.add("苹果");
        objects.add("茄子");
        objects.add("香蕉");
        //输出集合
        System.out.println(objects);    // 结果为:[苹果, 茄子, 香蕉]
        //输出集合元素个数
        System.out.println(objects.size());   //结果为:3
        
        //删除元素
        objects.remove("香蕉");
        System.out.println(objects.size());
        //清空所有元素
        objects.clear();
        //输出集合元素的个数
        System.out.println(objects.size());
    }

}

Java中的for-each循环也被称为增强for循环,它是一种简化数组和集合遍历的语法糖。使用for-each循环可以遍历数组和实现了Iterable接口的集合类,避免了使用传统for循环时需要手动获取数组或集合元素的索引和长度等操作,使代码更加简洁易读。

增强for循环可以遍历所有实现了Iterable接口的集合类,例如List、Set、Map等。对于数组,也可以使用增强for循环进行遍历。但是对于其他类型的数据结构,如栈、队列等,就不能使用增强for循环进行遍历了。

Java中的迭代器可以遍历实现了Iterable接口的集合类,包括ArrayList、LinkedList、HashSet、TreeSet等。同时,也可以遍历数组,但需要将数组转换为List类型。

Java中的Map接口提供了两种迭代器:keySet()和entrySet()。其中,keySet()方法返回一个包含Map中所有键的Set集合,可以通过迭代器遍历所有键;entrySet()方法返回一个包含Map中所有键值对的Set集合,可以通过迭代器遍历所有键值对。

(3)、迭代器Iterator接口的工作过程

image-20230504123812497

是的,Iterator是Java中的一个接口,它定义了一组用于遍历集合元素的方法。Iterator接口提供了三个方法:hasNext()、next()和remove(),它们分别用于检查集合中是否还有元素、返回下一个元素以及从集合中删除当前元素。所有实现了Iterator接口的类都必须实现这三个方法。

//遍历两种方式:增强for和迭代器Iterator
//创建集合
    Collection objects = new ArrayList();//Collection是接口,因此new其实现类ArrayList的对象,向上转型:父类引用指向子类对象
    //添加元素
    objects.add("苹果");
    objects.add("茄子");
    objects.add("香蕉");
    //遍历集合
    //1、增强for遍历集合中的元素,为什么使用增强for,是因为集合Collection没有下表,所以不能使用普通for循环
    for (Object i:objects){  //前面为元素类型和元素,后面为元素所在的集合
        System.out.println(i);  //结果为:苹果 茄子 香蕉
    }

    //使用迭代器Iterrator遍历
    Iterator it = objects.iterator();
    while (it.hasNext()){   //hasNext():有没有下个元素
        String i = (String) it.next();  //获取下一个元素
        System.out.println(i);
        //此处不能使用集合父接口Collection的删除方法
        //objects.remove(i);
        it.remove(); //删除当前元素
    }
    System.out.println(objects.size());
    //判断objects中是否有西瓜,true是有,false是没有
    System.out.println(objects.contains("西瓜"));
    //判断objexts是否为空,如果为null,为true,不为空则为false
    System.out.println(objects.isEmpty());
}

为什么上面不能使用父接口集合Collection的remove()方法,而是要用迭代器的remove()方法?

​ 这会导致在使用迭代器遍历集合时,使用 objects.remove(i); 删除元素会抛出 ConcurrentModificationException 异常。因为在使用迭代器遍历集合时,不能使用集合自身的删除方法,而是要使用迭代器的 remove() 方法来删除元素。

(4)、Collection的实际应用
package com.ji_he;

public class StudentCollectionDemo2 {
    private String name;
    private int age;

    public StudentCollectionDemo2() {

    }

    public StudentCollectionDemo2(String name, int age) {
        this.name = name;
        this.age = 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;
    }

    @Override
    public String toString() {
        return "StudentCollectionDemo2{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
package com.ji_he;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionDemo2 {
    public static void main(String[] args) {
        Collection stud = new ArrayList();
        StudentCollectionDemo2 s1 = new StudentCollectionDemo2("张三",125);
        StudentCollectionDemo2 s2 = new StudentCollectionDemo2("李四",126);
        StudentCollectionDemo2 s3 = new StudentCollectionDemo2("王五",99);

        stud.add(s1);
        stud.add(s2);
        stud.add(s3);
        System.out.println(stud.toString());

        stud.add(s3); //因为上面对象是new ArrayList(),list接口是可以存在重复对象的。list集合(接口):有序,有下标,元素可重复
        System.out.println(stud.toString());

        stud.remove(s3);//stud对象中保存的是地址,并不是对象本身,因此执行remove()并不会删除对象,所以,两个s3对象,还会存在一个
        System.out.println(stud.toString());

        for (Object object:stud){
            System.out.println(object);
        }

        Iterator it = stud.iterator();
        while (it.hasNext()){
            Object next = it.next();
            System.out.println(next.toString());
        }

        System.out.println(stud.contains(s3));
        System.out.println(stud.isEmpty());
        System.out.println(stud.size());
        stud.clear();
        System.out.println("清空后,stud中元素的个数为:"+stud.size());
    }
}

运行结果:

[StudentCollectionDemo2{name='张三', age=125}, StudentCollectionDemo2{name='李四', age=126}, StudentCollectionDemo2{name='王五', age=99}]
[StudentCollectionDemo2{name='张三', age=125}, StudentCollectionDemo2{name='李四', age=126}, StudentCollectionDemo2{name='王五', age=99}, StudentCollectionDemo2{name='王五', age=99}]
[StudentCollectionDemo2{name='张三', age=125}, StudentCollectionDemo2{name='李四', age=126}, StudentCollectionDemo2{name='王五', age=99}]
StudentCollectionDemo2{name='张三', age=125}
StudentCollectionDemo2{name='李四', age=126}
StudentCollectionDemo2{name='王五', age=99}
StudentCollectionDemo2{name='张三', age=125}
StudentCollectionDemo2{name='李四', age=126}
StudentCollectionDemo2{name='王五', age=99}
true
false
3
清空后,stud中元素的个数为:0

4、List子接口(集合)

(1)、List子接口

特点:有序 有下标 元素可以重复
方法
void add(int index , Object o)//在index处插入对象o
boolean addAll(int index,Collection c)//将一个集合中的元素添加到此集合的index位置
Object get(int index)//返回集合中指定位置的元素
LIst subList (int fromIndex ,int toIndex)//返回fromIndex 到toIndex的元素

下列两个语句有什么不同?

objects.add(0,"橘子");//添加元素,第一个参数为下标,第二个为对象
objects.add("橙子");

这两个语句的作用不同。

第一个语句是将一个元素 “橘子” 插入到列表 objects 的第一个位置,原来在该位置的元素和后面的元素都会向后移动一个位置。

第二个语句是将一个元素 “橙子” 添加到列表 objects 的末尾。 因此,第一个语句会改变列表的顺序,而第二个语句不会改变列表的顺序。

Java中的列表迭代器(ListIterator)和迭代器(Iterator)都是用于遍历集合类中的元素的接口,但它们之间有以下区别:

  1. ListIterator可以双向遍历列表,而Iterator只能单向遍历。

  2. ListIterator可以在遍历过程中修改列表中的元素,而Iterator只能删除元素

  3. ListIterator有add()方法,可以在遍历过程中向列表中添加元素,而Iterator没有。

  4. ListIterator可以获取当前元素的索引位置,而Iterator不能

  5. ListIterator只能用于List接口的实现类,而Iterator可以用于任何Collection接口的实现类。

  6. 总之,如果需要在遍历过程中修改列表中的元素或者添加元素,应该使用ListIterator。如果只需要遍历元素并删除元素,可以使用Iterator。

列表迭代器(ListIterator)和Iterator迭代器的区别:ListIterator可以向前、向后遍历,在遍历过程中添加、修改元素、获取当前元素位置,而Iterator不能,在遍历过程中Iterator只能删除元素。

//ListIterator迭代器是Iterator迭代器的子接口,它只能用于遍历List集合,但是 Iterator 可以遍历以下集合: 1. List 2. Set 3. Queue 4. Deque 5. Map (通过 Map.entrySet() 方法获取 Iterator)

// ListIterator迭代器提供了一些额外的方法,如hasPrevious()、previous()、add()、set()等。
//
//- hasPrevious():判断是否还有上一个元素。
//- previous():返回上一个元素。
//- add():在当前元素之前插入一个元素。
//- set():替换当前元素。
(2)、List子接口的使用①
package com.ji_he;

import java.util.*;

public class ListDemo3 {
    public static void main(String[] args) {
        List objects = new ArrayList();
        //添加元素的两种方式
        objects.add(0,"橘子");//添加元素,第一个参数为下标,第二个为对象
        objects.add("橙子");
        objects.add("香蕉");
        System.out.println(objects.toString());
        System.out.println(objects.size());
        //删除元素
        objects.remove("香蕉");//删除指定元素
        System.out.println(objects.toString());
        objects.remove(1);//删除指定下标的位置
        System.out.println(objects.toString());
        System.out.println("-----------");

        //遍历
        //增强for
        for (Object o:objects){
            System.out.println(o.toString());
        }
        System.out.println("-------------");
        //迭代器
        Iterator it = objects.iterator();
        while (it.hasNext()){
            Object next = it.next();
            System.out.println(next.toString());
            it.remove();
        }
        System.out.println(objects.size());
        //添加两个元素,此时西瓜在下标0处,因为之前的都清空了
        objects.add("西瓜");
        objects.add("梨子");
        //普通for循环(因为list有下标,所以可以用普通for循环)
        for (int i=0; i < objects.size();i++){
            System.out.println("元素位置:"+i);
            System.out.println(objects.get(i));  //get()方法:通过元素下标获取元素
        }

        //使用列表迭代器
        System.out.println("-------------------------------------");
        //列表迭代器(ListIterator)和Iterator迭代器的区别:ListIterator可以向前、向后遍历,
        // 在遍历过程中添加、修改元素、获取当前元素位置,而Iterator不能,在遍历过程中Iterator只能删除元素。
//        ListIterator迭代器是Iterator迭代器的子接口,它只能用于遍历List集合,
//        但是 Iterator 可以遍历以下集合: 1. List 2. Set 3. Queue 4. Deque 5. Map (通过 Map.entrySet() 方法获取 Iterator)
        ListIterator listIterator = objects.listIterator();//listIterator()是一个方法
        while (listIterator.hasNext()){//使用迭代器,从前往后遍历
            //nextIndex():返回当前位置的后一个元素的索引   举例:当前集合长度为2,元素下标为0.
            System.out.println("当前元素下标为:"+listIterator.nextIndex()+"当前元素为:"+ listIterator.next());
        }
        while (listIterator.hasPrevious()){//使用迭代器,从后往前遍历
//            previousIndex():返回当前位置的前一个元素的索引。
            System.out.println("当前元素下标为:"+listIterator.previousIndex()+"当前元素为:"+ listIterator.previous());
        }
        //判断当前元素是否存在
        System.out.println(objects.contains("西瓜"));
        System.out.println(objects.size());
        System.out.println(objects.isEmpty());//判断是否为null,为Null则为true
        //获取元素下标
        System.out.println(objects.indexOf("西瓜"));
    }
}

运行结果:

[橘子, 橙子, 香蕉]
3
[橘子, 橙子]
[橘子]
-----------
橘子
-------------
橘子
0
元素位置:0
西瓜
元素位置:1
梨子
-------------------------------------
当前元素下标为:0当前元素为:西瓜
当前元素下标为:1当前元素为:梨子
当前元素下标为:1当前元素为:梨子
当前元素下标为:0当前元素为:西瓜
true
2
false
0
(3)、List子接口的使用②

Java中的自动装箱是什么?

Java中的自动装箱(autoboxing)是指将基本数据类型自动转换为对应的包装类类型。

例如,将int类型的值自动转换为Integer类型的对象。

这样做的好处是可以方便地在基本数据类型和包装类类型之间进行转换,使得代码更加简洁和易读。

Java中的集合类是不需要重写toString()方法的,因为它们已经实现了默认的toString()方法。这个默认的toString()方法会返回一个包含集合中所有元素的字符串表示形式,格式为"[元素1, 元素2, …, 元素n]"。

如果需要自定义集合的toString()方法,可以通过继承集合类并重写toString()方法来实现。

package com.ji_he;

import java.util.ArrayList;
import java.util.List;

public class ListDemo4 {
    public static void main(String[] args) {
        //创建集合
        List list = new ArrayList();
        //添加数字(自动装箱)
        list.add(12);
        list.add(45);
        list.add(54);
        list.add(21);
        list.add(78);
        list.add(98);
        System.out.println("元素个数:"+list.size());
        System.out.println(list.toString());
        list.remove(2);//List集合中remove()方法的括号中跟的是元素索引位置,即角标
        System.out.println(list.toString());

        //subList()方法,返回子集合,含头不含尾
        List li  = list.subList(1,3);
        System.out.println(li.toString());
    }
}

运行结果:

元素个数:6
[12, 45, 54, 21, 78, 98]
[12, 45, 21, 78, 98]
[45, 21]

5、List接口的 实现类

ArrayList:
数组结构实现 查询快 增删慢 Arraylist: 储存结构-- 数组
运行效率快 线程不安全
Vector(了解):
数组结构实现 查询快 增删慢
运行效率慢 线程安全
LinkedList:
链表结构实现 增删快 查询慢 链表有单链表、双链表、循环链表

(1)、ArrayList类的使用
package com.ji_he;

import java.util.Objects;

public class Demo5 {
    private String name;
    private int age;

    public Demo5() {
    }

    public Demo5(String name, int age) {
        this.name = name;
        this.age = 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;
    }

    @Override
    public String toString() {
        return "Demo5{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Demo5 demo5 = (Demo5) o;
        return age == demo5.age && name.equals(demo5.name);
    }
}
package com.ji_he;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;

public class ArrayListDemo5 {
    public static void main(String[] args) {
        //创建集合
        ArrayList arrayList = new ArrayList<>();

        Demo5 d1 = new Demo5("李欣",21);
        Demo5 d2 = new Demo5("李文",22);
        Demo5 d3 = new Demo5("李海",25);
        Demo5 d4 = new Demo5("李玉",28);

        arrayList.add(d1);
        arrayList.add(d2);
        arrayList.add(d3);
        arrayList.add(d4);
        System.out.println(arrayList.toString());
        System.out.println("元素个数:"+arrayList.size());

        for (Object ay:arrayList){
            System.out.println(ay.toString());
        }

        //也可以用for遍历,此处省略,前面写过
        System.out.println("------------------------------");
        //Iterator迭代器遍历
        Iterator it = arrayList.iterator();
        while (it.hasNext()){
              Demo5 o = (Demo5) it.next();
            System.out.println(o.toString());
        }
        System.out.println("------------------------------");
        //列表迭代器
        ListIterator lt =arrayList.listIterator();
        while (lt.hasNext()){//从前往后遍历
             int index=lt.nextIndex();
             Demo5 value = (Demo5) lt.next();
            System.out.println("下标:"+index+"元素:"+value);
        }
        System.out.println("------------------------------");
        while (lt.hasPrevious()){//从后往前遍历
            int index = lt.previousIndex();
            Demo5 value = (Demo5) lt.previous();
            System.out.println("下标:"+index+"元素:"+value);
        }
        //判断
        System.out.println(arrayList.contains(new Demo5("李玉",28))); //这里需要看下面
        System.out.println(arrayList.size());
        System.out.println(arrayList.isEmpty());

        //查找:获取元素下标
        System.out.println(arrayList.indexOf(new Demo5("李玉",28)));
        System.out.println(arrayList.toString());
    }
}

System.out.println(arrayList.contains(new Demo5(“李玉”,28)));

如上,本来contains是用来判断集合中是否存在对象,此时equals方法比较的是两个对象之间的地址,如果地址相同,则为true

但是我们业务本身需要的是判断两个对象的属性值(也就是成员变量的值)是否相等,此时需要重写equals方法。

因为在判断集合中是否包含元素时,会调用元素的equals方法进行比较。而Demo5类中没有重写equals方法,所以默认使用Object类中的equals方法,即比较两个对象的地址是否相同。在这里,new Demo5(“李玉”,28)创建了一个新的对象,其地址与集合中的d4对象地址不同,因此返回false。如果想要比较两个Demo5对象的属性是否相同,需要重写equals方法。

(2)、Vector类的使用 (可以使用枚举器遍历)

Java中的枚举器(Enumerator)可以用于遍历任何实现了Enumeration接口的集合类,不仅限于Vector集合。例如,Hashtable、Stack、Properties等集合类也实现了Enumeration接口,因此也可以使用枚举器进行遍历。

package com.ji_he;

import java.util.Enumeration;
import java.util.Vector;

public class VectorDemo5 {
    public static void main(String[] args) {
        //创建集合
        Vector<Object> vector = new Vector<>();
        //添加元素
        vector.add("香蕉");
        vector.add("橘子");
        vector.add("芒果");
        vector.add("西瓜");
        System.out.println("元素个数为:"+vector.size());
        System.out.println(vector.toString());
        //遍历元素  枚举法  增强for等也可以
        Enumeration elements = vector.elements();
        while(elements.hasMoreElements()){
            String s = (String) elements.nextElement();
            System.out.println(s);
        }

        System.out.println("---------------------");
        for (Object v:vector){
            System.out.println(v.toString());
        }

        //判断
        System.out.println(vector.contains("西瓜"));
        System.out.println(vector.isEmpty());
        //其他方法
        System.out.println(vector.firstElement());
        System.out.println(vector.lastElement());
        System.out.println(vector.elementAt(0));
        //删除元素
        vector.remove(0);
        System.out.println(vector.toString());
        vector.remove("西瓜");
        System.out.println(vector.toString());
        vector.clear();
        System.out.println(vector.toString());
    }
}

运行结果:

元素个数为:4
[香蕉, 橘子, 芒果, 西瓜]
香蕉
橘子
芒果
西瓜


香蕉
橘子
芒果
西瓜
true
false
香蕉
西瓜
香蕉
[橘子, 芒果, 西瓜]
[橘子, 芒果]
[]

(3)、LinkedList类的使用
package com.ji_he;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;

public class LinkedListDemo6 {
    public static void main(String[] args) {
        //创建集合
        LinkedList linkedList = new LinkedList<>();
        Demo5 d1 = new Demo5("李欣",21);
        Demo5 d2 = new Demo5("李文",22);
        Demo5 d3 = new Demo5("李海",25);
        Demo5 d4 = new Demo5("李玉",28);
        //添加元素
        linkedList.add(d1);
        linkedList.add(d2);
        linkedList.add(d3);
        linkedList.add(d4);

        System.out.println(linkedList.toString());
        System.out.println("元素个数:"+linkedList.size());
        //增强for遍历
        for (Object ay:linkedList){
            System.out.println(ay.toString());
        }
        System.out.println("------------------------------");
        //普通for遍历
        for(int i=0;i<linkedList.size();i++){
            System.out.println(linkedList.get(i));
        }

        System.out.println("------------------------------");
        //Iterator迭代器遍历
        Iterator it = linkedList.iterator();
        while (it.hasNext()){
            Demo5 o = (Demo5) it.next();
            System.out.println(o.toString());
        }
        System.out.println("------------------------------");
        //列表迭代器
        ListIterator lt =linkedList.listIterator();
        while (lt.hasNext()){//从前往后遍历
            int index=lt.nextIndex();
            Demo5 value = (Demo5) lt.next();
            System.out.println("下标:"+index+"元素:"+value);
        }
        System.out.println("------------------------------");
        while (lt.hasPrevious()){//从后往前遍历
            int index = lt.previousIndex();
            Demo5 value = (Demo5) lt.previous();
            System.out.println("下标:"+index+"元素:"+value);
        }
        //判断
        System.out.println(linkedList.contains(new Demo5("李玉",28)));
        System.out.println(linkedList.size());
        System.out.println(linkedList.isEmpty());

        //查找:获取元素下标
        System.out.println(linkedList.indexOf(new Demo5("李玉",28)));
        System.out.println(linkedList.toString());
    }
}

运行结果

[Demo5{name='李欣', age=21}, Demo5{name='李文', age=22}, Demo5{name='李海', age=25}, Demo5{name='李玉', age=28}]
元素个数:4
Demo5{name='李欣', age=21}
Demo5{name='李文', age=22}
Demo5{name='李海', age=25}
Demo5{name='李玉', age=28}
------------------------------
Demo5{name='李欣', age=21}
Demo5{name='李文', age=22}
Demo5{name='李海', age=25}
Demo5{name='李玉', age=28}
------------------------------
Demo5{name='李欣', age=21}
Demo5{name='李文', age=22}
Demo5{name='李海', age=25}
Demo5{name='李玉', age=28}
------------------------------
下标:0元素:Demo5{name='李欣', age=21}
下标:1元素:Demo5{name='李文', age=22}
下标:2元素:Demo5{name='李海', age=25}
下标:3元素:Demo5{name='李玉', age=28}
------------------------------
下标:3元素:Demo5{name='李玉', age=28}
下标:2元素:Demo5{name='李海', age=25}
下标:1元素:Demo5{name='李文', age=22}
下标:0元素:Demo5{name='李欣', age=21}
true
4
false
3
[Demo5{name='李欣', age=21}, Demo5{name='李文', age=22}, Demo5{name='李海', age=25}, Demo5{name='李玉', age=28}]
(4)、ArrayList和LinkedList的区别

image-20230504184025070

6、泛型

(1)、泛型简介

Java泛型是JDK1.5引入的新特性,其本质是参数化类型,把类型作为参数传递。

常见的泛型有泛型类、泛型接口、泛型方法。

语法:

<T,…>类型占位符 表示一种引用类型

  • 好处
    • 提高代码的重用性
    • 防止类型转换异常,提高代码安全性
(2)、泛型类

语法 : 类名后加<T,E,K…> T类型占位符 是一种引用类型 如果编写多个使用逗号分隔开。T只是占位符,也可以用其它大写字母代替

package com.fan_xing;

public class MyGeneric<T> {
    //使用泛型
    //1.创建变量
    T t;    //泛型成员变量的默认值为null    t的值为:null
    //2.方法参数
    public  void  show(T t){
        // T t =new T();  不可new对象  因为无法确定T的类型
        this.t = t;
        System.out.println(t);
    }
    //3.泛型作为方法的返回值
    public T getT(){
        return  t;
    }
}

new泛型类对象必须指明T的类型

package com.fan_xing;

public class TestGeneric {
    public static void main(String[] args) {
        //使用泛型创建对象
        //泛型为引用类型  不同泛型类型之间不能相互赋值
        MyGeneric<String> mg1 = new MyGeneric<String>();
        mg1.show("生活");
        System.out.println(mg1.t);
        String S = mg1.getT();

        MyGeneric<Integer> mg2 = new MyGeneric<Integer>();
        mg2.show(150);
        System.out.println(mg2.t);
        Integer i1 = mg2.getT();
         //不同泛型类型之间不能相互赋值
        //Mygeneric<String> mg1=mg2;  错误
    }
}

运行结果:

生活
生活
150
150

(3)、泛型接口

泛型接口的定义如下:固定格式,接口后面的不能去掉,如果去掉会报错。因为下面接口中的方法,使用了泛型,所以接口应该定义为泛型。

package com.fan_xing;

public interface MyInterface<T> {     //泛型接口语法:接口名<T>
        public static String name= "泛型";       //注意:不能定义泛型静态常量
        T show(T t);
    }

Java中的泛型常量是指在定义常量时使用泛型类型的常量。它的语法形式为: java public static final <T> T CONSTANT_NAME = value; 其中,<T>表示泛型类型参数,CONSTANT_NAME表示常量名,value表示常量的值。 不能定义泛型常量

为什么不能定义泛型常量?

Java中不能定义泛型常量的原因是因为泛型类型在编译时会被擦除,而常量的值必须在编译时确定。因此,如果定义了一个泛型常量,编译器无法确定它的类型,也就无法确定它的值。这就导致了泛型常量无法被定义。 另外,Java中的常量必须是静态的,而泛型类型是与类实例化相关的,因此无法定义静态泛型常量。 如果需要定义常量,可以使用final修饰符来定义常量。例如: public static final int MAX_VALUE = 100; 这样定义的常量可以在编译时确定其值,并且可以在程序中被多次引用。

(4)、泛型实现类

方式一:实现时声明类型

package com.fan_xing;

public class MyInterfaceImpl implements MyInterface<String>{//泛型接口实现类,创建时必须声明之前T的类型,
    @Override 
    public String show(String t) {  //这里重写泛型接口方法时,这里的T也会自动转换
        System.out.println(t);
        return  t;
    }
}

方式二:调用时声明类型

package com.fan_xing;

public class MyInterfaceImpl2<T> implements MyInterface<T> {
    //未声明T的类型时,必须将前面的实现类也声明为泛型类,这样接口处的T指的就是泛型类的T,上面一行中的两个T相同,在new实现类时,T将会被一起定义
    @Override
    public T show(T t) {
        System.out.println(t);
        return t;
    }
}

测试的类

package com.fan_xing;

public class TestMyGeneric {
    public static void main(String[] args) {
        MyInterfaceImpl myInterface = new MyInterfaceImpl();
        myInterface.show("泛型接口方式一,实现类处写泛型的具体类型");
        //调用接口中的静态常量:接口名.常量名
        System.out.println(MyInterface.name);

        //创建实现类时,没有指定T的类型,当时是将实现类变为泛型类,所以在new对象时需要指明T泛型的具体类型,此处指明为Integer,也可以指明为其它类型
        MyInterfaceImpl2<Integer> myInterface2 = new MyInterfaceImpl2<>();
        myInterface2.show(12345);
    }
}

运行结果:

泛型接口方式一,实现类处写泛型的具体类型
泛型
12345

(5)、泛型方法

泛型方法:

package com.fan_xing;

public class MygenericMethod {
    public  <T> T method (T t){
        System.out.println("泛型方法:"+t);
        return t;
    }
}

调用泛型方法:泛型方法调用时,不需要指明T类型,方法会自动根据传入数据调整类型

package com.fan_xing;

public class TestMygenericMethod {
    public static void main(String[] args) {
        MygenericMethod mygenericMethod = new MygenericMethod();
        mygenericMethod.method(12345);//调用时,不需要指明T类型,方法会自动根据传入数据调整类型

    }
}

运行结果:

泛型方法:12345

(5.1)、泛型好处

通过上面这个泛型方法,可以知道有如下好处:

以上面方法为例,show()方法仅仅能定义一种传入数据类型,因此就需要方法重载,写多个show()方法,而泛型只需要写一个,提高了泛型的复用性。

(6)、Java的拆箱

Java中的拆箱是什么?

Java中的拆箱(Unboxing)是将包装类型(如Integer、Double等)转换为对应的基本数据类型(如int、double等)的过程。

拆箱操作可以通过自动拆箱或显式拆箱来实现。 自动拆箱是指在需要使用基本数据类型的地方,自动将包装类型转换为对应的基本数据类型。

例如:

java Integer

i = 10;

int j = i; // 自动拆箱

显式拆箱是指通过调用包装类型的xxxValue()方法来手动将包装类型转换为对应的基本数据类型。

例如:

java Integer i = 10;

int j = i.intValue(); // 显式拆箱

Java中可以自动拆箱和装箱,也可以手动进行拆箱和装箱操作。

装箱是将基本类型转换为包装类型,而拆箱是将包装类型转换为基本类型。

(7)、泛型集合

概念:参数化类型、类型安全的集合,强制集合元素的类型必须一致。

特点

编译时即可以检查,而非运行时抛出异常。 // object类型的话,编译的时候 不提示,运行的时候抛出异常。

访问时,不必类型转换(拆箱)。

不同泛型之间引用不能相互赋值,泛型不存在多态。

package com.fan_xing;

import java.util.ArrayList;
import java.util.Iterator;

public class GenericSet {//泛型集合

    public static void main(String[] args) {
        ArrayList<StudentGenericSet> arrayList = new ArrayList<StudentGenericSet>();//指定泛型为类StudentGenericSet的类型
        StudentGenericSet s1 =new StudentGenericSet("张三",12);
        StudentGenericSet s2 =new StudentGenericSet("李四",13);
        arrayList.add(s1);
        arrayList.add(s2);
        //arrayList.add("123"); 指定了泛型为类StudentGenericSet 的类型,所以添加String类型会报错。

        //迭代器也是泛型
        Iterator<StudentGenericSet> iterator = arrayList.iterator();
        while (iterator.hasNext()){
            StudentGenericSet next = iterator.next();
            System.out.println(next);
        }

        ArrayList<String>   arrayList1= new ArrayList<String>();
//        arrayList = arrayList1;//报错:不同的泛型集合不能相互赋值
       //报错:不兼容的类型: java.util.ArrayList<java.lang.String>无法转换为java.util.ArrayList<com.fan_xing.StudentGenericSet>
    }
}

7、Set子接口(集合)

(1)、Set子接口

特点:无序 无下标 元素不可重复。

方法:全部继承自Collection中的方法。

Set集合取出元素的两种方法:迭代器遍历和增强for遍历。

Set的构造函数有一个约束条件,传入Collection接口参数不能包含重复元素,也就是说加入Set接口(集合)的元素必须定义equals方法和hashcode方法,以确保对象的唯一性

哈希码(也称hashcode的值,也就是两个对象的地址)

如果要比较两个对象的值,equals方法和hashcode方法必须同时重写!!!!!!!(Set集合必须重写,因为Set集合不能包含重复元素,所以需要重写equals方法和hashcode来达到去重的目的)
//当存入元素的哈希码(hashcode的值,也就是两个对象的地址)相同时,会调用equals进行确认,若结果为true 则拒绝后者加入,
  如果hashcode的值不同,不会调用equals方法。 
  equals方法和hashcode方法方法没有重写调用的是默认equals,比较两个对象的地址,如果重写了equals和hashcode,那么会比较两个对象的值 。

1.hashSet:无序、无下标、不可重复(要是对象不重复就要重写equals()和hashcode()方法)

hashSet是需要重写equals()和hashcode()方法,当存储对象的时候,先调用hashCode()方法计算一个哈希值(两个对象的地址(hashcode值),看是不是相同的,也就是在集合中存不存在),在集合中查找是否有哈希值相同的对象。如果没有哈希值相同相同的对象,直接将对象存入。如果有哈希值相同的对象(就是当两个对象的地址相同时,比如对象p3和p3),则和哈希值相同的对象进行equals()比较。比较结果为false就存入对象引用,比较结果为true则用新的值覆盖旧的值。

2.treeSet:有序、无下标、不可重复(排序)

TreeSet存储对象的时候,通过指定的比较算法,对对象进行排序。创建TreeSet时如果没有传入比较器,则会按照对象的自然顺序来排序,自然排序就是实现了Comparable接口之后compareTo方法中定义的顺序。

如果我们不希望使用自然排序,还在TreeSet的构造函数中可以传入一个比较器。比较器就是实现了Comparator接口的对象,按照其中compare方法排序。

如果需要在TreeSet中使用contains方法来判断对象是否存在,那么就需要重写equals方法,因为contains方法会使用equals方法来判断对象是否相等。如果不重写equals方法,contains方法会使用默认的Object.equals方法,这可能会导致错误的结果。 总之,如果只是使用TreeSet来存储对象,不需要重写equals方法。但是,如果需要在TreeSet中使用contains方法来判断对象是否存在,就需要重写equals方法。

(2)、Set子接口的使用
package com.ji_he_Set;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class SetDemo1 {
    public static void main(String[] args) {
        //创建集合
        Set<String> set = new HashSet<>();
        //添加数据
        set.add("青蛙");
        set.add("王子");
        set.add("呱呱呱");
        //集合的长度
        System.out.println("元素个数为:"+set.size());
        System.out.println(set.toString());

        //删除
        set.remove("呱呱呱");
        System.out.println(set.toString());

        //遍历
        //增强for
        for(String s :set){
            System.out.println(s.toString());
        }
        System.out.println("----------------");
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()){
            String next = iterator.next();
            System.out.println(next);
        }

        //判断是否存在该元素、判断集合是否null
        System.out.println("该集合是否存在元素青蛙:"+set.contains("青蛙"));
        System.out.println(set.isEmpty());
    }
}

8、Set接口的实现类

实现类

  • HashSet
  • TreeSet

HashSet

  • 基于HashSet计算元素存放位置

  • 当存入元素的哈希码相同时,会调用equals进行确认,若结果为true 则拒绝后者加入

  • HashSet是基于哈希表实现的,HashSet使用哈希表存储元素,它的元素是无序的,因为哈希表中元素的存储位置是根据元素的哈希值来确定的。

  • HashSet类:集合中的元素:无序、无下标、无重复数据
    

TreeSet

  • 基于排列顺序实现元素不可重复
  • 实现SortedSet接口 对集合元素自动排序
  • 元素对象的类型必须实现Comparable接口,指定排列顺序
  • 通过CompareTo方法确认是否是重复元素
  • TreeSet是基于红黑树实现的。而TreeSet使用红黑树来存储元素,它的元素是有序的,因为红黑树中元素的存储位置是根据元素的大小关系来确定的
  • Java中的TreeSet是有序的,它按照元素的自然顺序进行排序。如果要使用自定义的排序方式,可以在创建TreeSet时传入一个实现了Comparator接口的比较器对象。虽然TreeSet是有序的,但是它并不保证元素的插入顺序和遍历顺序一致因此可以认为TreeSet的元素是无序的。
HashSet类:集合中的元素:有序、无下标、无重复数据  
(1)、HashSet类的使用① (向集合中传入字符串对象)

HashSet的查询效率比ArrayList高。这是因为HashSet是基于哈希表实现的,它使用哈希函数将元素映射到一个桶中,然后在桶中查找元素。因此,**HashSet的查询时间复杂度为O(1),而ArrayList的查询时间复杂度为O(n)。**在大多数情况下,HashSet比ArrayList更适合用于查找操作。

在Java中,当我们使用HashSet集合时,需要重写equals()和hashCode()方法的情况是:

  1. 自定义类作为HashSet的元素类型时,需要重写equals()和hashCode()方法以确保HashSet能够正确地判断两个对象是否相等。

  2. 如果我们想要在HashSet中存储自定义对象,并且希望HashSet中的元素不重复,那么我们需要重写equals()和hashCode()方法,以确保HashSet能够正确地判断两个对象是否相等。

  3. 如果我们需要在HashSet中使用自定义对象作为键,那么我们需要重写equals()和hashCode()方法,以确保HashMap能够正确地查找键值对。

    总之,当我们需要在HashSet中使用自定义对象时,需要重写equals()和hashCode()方法,以确保HashSet能够正确地判断两个对象是否相等。

HashSet集合(类)的使用

Set的实现类:HashSet的基本使用:添加、删除、遍历、判断
package com.ji_he_Set;

import java.util.HashSet;
import java.util.Iterator;

/*
* Set的实现类:HashSet的基本使用:添加、删除、遍历、判断
* HashSet类:集合中的元素:无序、无下标、无重复数据
*
* */
public class HashSetDemo2 {
    public static void main(String[] args) {
        HashSet<String> hashBasic = new HashSet<String>();
        hashBasic.add("小小");
        hashBasic.add("大大");
        hashBasic.add("吵吵");

        System.out.println("元素个数:"+ hashBasic.size());
        System.out.println(hashBasic.toString());
        System.out.println("-----------------------");
        //增强for遍历
        for (String i:hashBasic){
            System.out.println(i.toString());
        }
        System.out.println("----------------------");

        //迭代器遍历
        Iterator<String> iterator = hashBasic.iterator();
        while (iterator.hasNext()){
            String next = iterator.next();
            System.out.println(next.toString());
        }

        System.out.println("---------------------");
        //判断元素是否在集合中
        System.out.println(hashBasic.contains("小小"));
        //判断在集合中的位置
        hashBasic.remove("小小");
        System.out.println(hashBasic.toString());
        System.out.println(hashBasic.size());
    }
}

运行结果:

元素个数:3
        [吵吵, 小小, 大大]
        -----------------------
        吵吵
        小小
        大大
        ----------------------
        吵吵
        小小
        大大
        ---------------------
        true
        [吵吵, 大大]
        2
(2)、HashSet类的使用② (向集合中传入自定义类的对象)

向集合中传入自定义类的对象:也就是类的一个实例

向HashSet集合中添加对象

1)、创建对象类

package com.ji_he_Set;

import java.util.Objects;

public class Demo3 {
    private String name;
    private int age;

    public Demo3(){

    }

    public Demo3(String name, int age){
        this.name = name;
        this.age = 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;
    }

    @Override
    public String toString() {
        return "Demo3{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Demo3 demo3 = (Demo3) o;
        return age == demo3.age && name.equals(demo3.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

2)、使用HashSet集合进行操作

如果要比较两个对象的值,equals方法和hashcode方法必须同时重写!!!!!

equals方法和hashcode方法必须同时重写
//当存入元素的哈希码(hashcode的值,也就是两个对象的地址)相同时,会调用equals进行确认,若结果为true 则拒绝后者加入,
如果hashcode的值不同,不会调用equals方法。
equals方法和hashcode方法方法没有重写调用的是默认equals,比较两个对象的地址,如果重写了equals和hashcode,那么会比较两个对象的值 。

package com.ji_he_Set;

import java.util.HashSet;
import java.util.Iterator;
/*
* equals方法和hashcode方法必须同时重写
* */

public class PersonHashSetDemo3 {
    public static void main(String[] args) {
        //创建集合
        HashSet<Demo3>  demo3 = new HashSet<Demo3>();
        //添加数据
        Demo3 d1 = new Demo3("lisa",12);
        Demo3 d2 = new Demo3("tony",13);
        Demo3 d3 = new Demo3("enty",14);

        demo3.add(d1);
        demo3.add(d2);
        demo3.add(d3);
        //当存入元素的哈希码(hashcode的值,也就是两个对象的地址)相同时,会调用equals进行确认,若结果为true 则拒绝后者加入, 如果hashcode的值不同,不会调用equals方法。     equals方法和hashcode方法必须同时重写,   方法没有重写调用的是默认equals,比较两个对象的地址,         如果重写了equals和hashcode,那么会比较两个对象的值 。
        // 此时方法没有重写调用的是默认equals,比较两个对象的地址,所以d3不能添加
        //如果重写了equals和hashcode,那么会比较d3的值,也不能添加,因为d3的值已经存在了
        demo3.add(d3);//d3重复,不能添加,d3地址已经有了 ,//重写了equals和hashcode更不能添加了,因为值也存在

         //现在没有重写equals和hashcode就可以添加,因为与d3对象的地址不同,只是值相同,,如果重写了equals和hashcode就不能添加了
        demo3.add(new Demo3("enty",14));

        System.out.println(demo3.toString());
        System.out.println("该集合中的元素个数为:"+demo3.size());

        //增强for遍历
        for (Demo3 d:demo3){
            System.out.println(d.toString());
        }
        System.out.println("-----------------------");
        //迭代器遍历
        Iterator<Demo3> iterator = demo3.iterator();
        while (iterator.hasNext()){
            Demo3 next = iterator.next();
            System.out.println(next);
        }

        //判断对象是否存在,存在为true,不存在为false
        System.out.println(demo3.contains(new Demo3("enty",14)));//需要重写equals方法和hashcode方法,不然它的结果为false
        //为什么要重写,是因为  //当存入元素的哈希码(hashcode的值,也就是两个对象的地址)相同时,会调用equals进行确认,若结果为true 则拒绝后者加入,
        如果hashcode的值不同,不会调用equals方法
        //没有重写的equals比较的是对象的地址,重写equals方法和hashcode方法才比较的是值,equals方法和hashcode方法必须同时重写

        //判断是否为null,null 为true,非空为false
        System.out.println(demo3.isEmpty());
    }
}

运行结果:

[Demo3{name='lisa', age=12}, Demo3{name='enty', age=14}, Demo3{name='tony', age=13}]
        该集合中的元素个数为:3
        Demo3{name='lisa', age=12}
        Demo3{name='enty', age=14}
        Demo3{name='tony', age=13}
        -----------------------
        Demo3{name='lisa', age=12}
        Demo3{name='enty', age=14}
        Demo3{name='tony', age=13}
        true
        false
(3)、TreeSet类的使用① (向集合中传入字符串对象)
package com.ji_he_Set;

import java.util.Iterator;
import java.util.TreeSet;
/*
* Set的实现类:TreeSet的基本使用:添加、删除、遍历、判断
 * TreeSet类:集合中的元素:有序、无下标、无重复数据
* */

public class TreeSetDemo4 {
    public static void main(String[] args) {
        //创建集合
        TreeSet<String> treeBasic = new TreeSet<String>();
        //添加数据
        treeBasic.add("小小");
        treeBasic.add("大大");
        treeBasic.add("吵吵");

        System.out.println("元素个数:"+ treeBasic.size());
        System.out.println(treeBasic.toString());
        System.out.println("-----------------------");
        //增强for遍历
        for (String i:treeBasic){
            System.out.println(i.toString());
        }
        System.out.println("----------------------");

        //迭代器遍历
        Iterator<String> iterator = treeBasic.iterator();
        while (iterator.hasNext()){
            String next = iterator.next();
            System.out.println(next.toString());
        }

        System.out.println("---------------------");
        //判断元素是否在集合中
        System.out.println(treeBasic.contains("小小"));
        //判断在集合中的位置
        treeBasic.remove("小小");
        System.out.println(treeBasic.toString());
        System.out.println(treeBasic.size());
    }
}

运行结果:

元素个数:3
        [吵吵, 大大, 小小]
        -----------------------
        吵吵
        大大
        小小
        ----------------------
        吵吵
        大大
        小小
        ---------------------
        true
        [吵吵, 大大]
        2
(4)、TreeSet类的使用② (向集合中传入自定义类的对象)

Java TreeSet是一个有序集合,可以按照两种方式进行排序:

  1. 自然排序:使用元素的自然顺序进行排序。例如,对于整数,自然顺序是从小到大排序。对于字符串,自然顺序是按字典顺序排序。
  2. 定制排序(也叫比较器排序、自定义排序):使用Comparator接口实现定制排序。可以创建一个Comparator对象,然后使用TreeSet的构造函数或者TreeSet的compare()方法来指定排序方式。例如,可以按照字符串长度进行排序,或者按照字符串中某个字符的出现次数进行排序。

自然排序是指按照元素的自然顺序进行排序,例如数字按照从小到大的顺序排序,字符串按照字典序排序。Java中的大部分类都实现了Comparable接口,该接口定义了compareTo方法,用于比较两个对象的大小关系。因此,如果要使用自然排序,需要保证元素实现了Comparable接口,并且在创建Treeset对象时不传入比较器。 简单点来说,将元素类实现comparable接口,并重写compareto方法,在该方法中定义排序规则。

指定比较器排序,如果元素没有实现Comparable接口,或者需要按照其他方式进行排序,可以通过传入比较器来实现。比较器是一个实现了Comparator接口的类,该接口定义了compare方法,用于比较两个对象的大小关系。简单点来说,①、新建一个比较器类,实现comparator接口,重写compare方法。②、新建一个比较器的对象,作为比较器的参数。

总之,当元素实现了Comparable接口并且需要按照自然顺序排序时,使用自然排序;当元素没有实现Comparable接口或者需要按照不同的顺序进行排序时,使用比较器排序。

Java中实现了Comparable接口并需要按照自然顺序排序的元素包括:

  1. 数值类型:Byte、Short、Integer、Long、Float、Double
  2. 字符类型:Character
  3. 布尔类型:Boolean
  4. 枚举类型:Enum
  5. 字符串类型:String
  6. 时间类型:Date、LocalDate、LocalTime、LocalDateTime、ZonedDateTime
  7. 集合类型:List、Set、SortedSet、Map、SortedMap
  8. 自定义类型:自定义类实现Comparable接口,重写compareTo方法
1)、TreeSet类的复杂使用:(自然排序和比较器排序,)
1.1)、自然排序

自然排序:数字的话默认排序升序;

字符串根据26个字母排升序:

Java中的TreeSet是基于红黑树实现的,它可以自动对元素进行排序。对于字符串类型的元素,TreeSet默认使用自然排序(Natural Ordering)进行排序。 在Java中,字符串的自然排序是按照Unicode码点的顺序进行排序的。对于汉字字符串,它们的Unicode码点是按照拼音的顺序排列的。因此,TreeSet会按照汉字字符串的拼音顺序进行升序排序。 例如,如果有以下汉字字符串: {“张三”, “李四”, “王五”, “赵六”} TreeSet会按照拼音的顺序进行排序,得到的结果为: {“李四”, “王五”, “张三”, “赵六”} 其中,“李四"的拼音是"Lǐ sì”,“王五"的拼音是"Wáng wǔ”,“张三"的拼音是"Zhāng sān”,“赵六"的拼音是"Zhào liù”。按照拼音的顺序进行排序后,就得到了升序排列的结果。

自然排序中,数字默认升序排序。

   例如,如果我们创建一个TreeSet并添加一些整数:
   ```
    TreeSet<Integer> set = new TreeSet<>();
    set.add(5);
    set.add(2);
    set.add(8);
    set.add(1);
    set.add(9);
    ```

    则TreeSet会按照自然顺序对这些整数进行排序,即从小到大排序。因此,set中的元素顺序为1, 2, 5, 8, 9。

自然排序中,数字默认升序排序,如果想要降序排序。

默认情况下,TreeSet使用自然排序(Natural Ordering)进行排序,即按照元素的自然顺序进行排序。对于整数类型的元素,自然顺序是升序排序。如果要按照降序排序,可以使用Comparator接口来定义一个比较器,然后将其传递给TreeSet的构造函数。例如,以下代码将创建一个降序排列的TreeSet:
```
TreeSet<Integer> set = new TreeSet<>(Comparator.reverseOrder());
set.add(5);
set.add(2);
set.add(8);
set.add(1);
set.add(9);
```

在这个例子中,我们使用了Comparator.reverseOrder()方法来创建一个比较器,该比较器将元素按照降序排列。然后将该比较器传递给TreeSet的构造函数,以便TreeSet使用该比较器进行排序。

自然排序:字符串默认升序。

如果我们创建一个TreeSet并添加一些字符串:

    TreeSet<String> set = new TreeSet<>();
    set.add("apple");
    set.add("banana");
    set.add("orange");
    set.add("pear");
    ```

    则TreeSet会按照字符串的自然顺序进行排序,即按照字母顺序排序。因此,set中的元素顺序为apple, banana, orange, pear。



**自然排序:字符串降序。**

可以使用TreeSet的构造函数来创建一个降序的TreeSet,如下所示:

    ```
    TreeSet<String> set = new TreeSet<>(Collections.reverseOrder());
    set.add("apple");
    set.add("banana");
    set.add("orange");
    set.add("pear");
    ```

    这样创建的set会按照元素的降序排列。另外,也可以使用TreeSet的descendingSet()方法来获取一个降序的视图,如下所示:

    ```
    TreeSet<String> set = new TreeSet<>();
    set.add("apple");
    set.add("banana");
    set.add("orange");
    set.add("pear");

    TreeSet<String> descendingSet = (TreeSet<String>) set.descendingSet();
    ```

    这样获取的descendingSet也会按照元素的降序排列。



##### 1.2)、比较器排序

比较器排序(升序):

自定义的比较器中,compare方法返回的是o1 - o2,这意味着如果o1比o2大,返回的结果为正数,如果o1比o2小,返回的结果为负数,如果o1和o2相等,返回的结果为0。因此,这个比较器会按照从小到大的顺序对元素进行排序,即升序排列。因此,输出结果应该是[1, 2, 5, 8, 9]。

```java
class MyComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        // 自定义比较规则
        return o1 - o2;; // 
    }
}

    TreeSet<Integer> set = new TreeSet<>(new MyComparator());
set.add(5);
        set.add(2);
        set.add(8);
        set.add(1);
        set.add(9);
        System.out.println(set); 

比较器排序(降序):

```java
class MyComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        // 自定义比较规则
        return o2 - o1; // 降序排列
    }
}

    TreeSet<Integer> set = new TreeSet<>(new MyComparator());
        set.add(5);
        set.add(2);
        set.add(8);
        set.add(1);
        set.add(9);
        System.out.println(set); // 输出 [9, 8, 5, 2, 1]
1.3)、使用比较器排序,传入的集合的数据是自定义类(重点)

步骤一:先创建类StudentDemo6:

package com.ji_he_Set;

public class StudentDemo6 {
    private String name;
    private int age;

    public StudentDemo6(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public StudentDemo6(){

    }

    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;
    }

    @Override
    public String toString() {
        return "StudentDemo6{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                '}';
    }
}

步骤二:在自定义类中,实现Comparator接口,重写compare()方法,int型和String类型的升序降序,如何具体重写compare()方法,及遍历TreeSet集合的两种方式:增强for 和迭代器。

package com.ji_he_Set;

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

public class TreeSetDemo6 {
    public static void main(String[] args) {//在TreeSet的构造函数中可以传入一个比较器
        TreeSet<StudentDemo6> treeSet = new TreeSet<StudentDemo6>(new Comparator<StudentDemo6>() { // 使用自定义排序规则
            @Override
            public int compare(StudentDemo6 o1, StudentDemo6 o2) {  //重写compare方法
//                return o1.getAge()-o2.getAge(); 按照年龄排升序
//                return o2.getAge()-o1.getAge(); 按照年龄排降序
//                 return o2.getName().compareTo(o1.getName()); 按照姓名字典序排降序
                  // return o1.getName().compareTo(o2.getName()); 按照姓名字典序排升序
                /*如果只写上面四句中的其中一句,那么下面两行代码就只能插入s3,而不能插入s4。(具体原因看两行代码的注释)
                * StudentDemo6 s3 = new StudentDemo6("bty",32);
                  StudentDemo6 s4 = new StudentDemo6("bty",33);
                * 因为,在TreeSet中,元素的排序是根据元素的比较结果来确定的。如果两个元素的比较结果为0,则认为它们是相等的,
                * 只能插入其中一个元素。在这个例子中,s3和s4的名字都是"bty",因此它们的比较结果为0,只能插入其中一个元素。
                * 由于s3先被插入,因此s4无法插入成功。如果想要插入s4,可以在比较器中增加对年龄的比较,这样就可以避免出现相同名字的元素无法插入的情况。
                *
                * */
                /*首先根据姓名排升序,如果姓名相同则按照年龄排升序*/
                int n1 = o1.getName().compareTo(o2.getName());
                int n2 = o1.getAge() - o2.getAge();
                return n1==0?n2:n1;
                /*
                * compareTo方法是用于比较两个对象的大小关系的方法。它是Comparable接口中的方法,该接口定义了一个compareTo方法,
                * 用于比较当前对象和另一个对象的大小关系。该方法返回一个整数值,表示当前对象和另一个对象的大小关系。
                * 如果当前对象小于另一个对象,则返回负整数;如果当前对象等于另一个对象,则返回0;如果当前对象大于另一个对象,则返回正整数。
                  该方法通常用于对对象进行排序或查找操作。例如,可以使用该方法对字符串、数字等进行排序。
                * */
                /*
                * compareTo()方法比较的是大小关系,而equals()方法比较的是相等关系。
                */
            }
        });

        StudentDemo6 s1 = new StudentDemo6("lisa",45);
        StudentDemo6 s2 = new StudentDemo6("anna",49);
        StudentDemo6 s3 = new StudentDemo6("bty",32);
        StudentDemo6 s4 = new StudentDemo6("bty",33);
        treeSet.add(s1);
        treeSet.add(s2);
        treeSet.add(s3);
        treeSet.add(s4);

        System.out.println(treeSet.toString());//输出根据名字升序的集合
        System.out.println("------------------");
        //增强for遍历
        for(StudentDemo6 i:treeSet){//遍历的集合为treeSet,里面的对象的引用变量类型为StudentDemo6
            System.out.println(i.toString());
        }

        System.out.println("------------------");
        Iterator<StudentDemo6> iterator = treeSet.iterator();
        while (iterator.hasNext()){//判断下一个位置是否还有数据,比如下标0,先在0之前的位置
            StudentDemo6 next = iterator.next();//取出下一个位置的数据
            System.out.println(next);
        }
        /*
        * Java中的集合只能存放对象数据,因为Java是一种面向对象的语言,它的基本数据类型(如int、double等)不是对象,
        * 而是原始数据类型,不能直接存储在集合中。因此,如果需要将基本数据类型存储在集合中,
        * 需要使用对应的包装类(如Integer、Double等)来封装成对象。
        * */
    }
}

运行结果:

[StudentDemo6{name='anna', age='49'}, StudentDemo6{name='bty', age='32'}, StudentDemo6{name='bty', age='33'}, StudentDemo6{name='lisa', age='45'}]
        ------------------
        StudentDemo6{name='anna', age='49'}
        StudentDemo6{name='bty', age='32'}
        StudentDemo6{name='bty', age='33'}
        StudentDemo6{name='lisa', age='45'}
        ------------------
        StudentDemo6{name='anna', age='49'}
        StudentDemo6{name='bty', age='32'}
        StudentDemo6{name='bty', age='33'}
        StudentDemo6{name='lisa', age='45'}

9、Map子接口(集合)

(1)、Map子接口

Map接口(集合)的特点:

存放键值对,Java中的Map集合只能存放对象。Map是一个键值对的集合,其中键和值都必须是对象。键和值可以是任何类型的对象,但必须是对象。如果要存储基本数据类型,可以使用对应的包装类作为键或值。例如,可以使用Integer作为键或值来存储整数。

键:无序、无下标、不允许重复(唯一)

值:无序、无下标、允许重复

Map的添加记录是:put(K key,V value)

  1. HashMap:适用于需要快速查找元素的场景,但不保证元素的顺序。

    HashMap同样是需要重写equals()和hashcode()方法

    当存储一个键值对的时候,先调用hashCode()方法计算一个哈希值(两个对象的地址(hashcode值),看是不是相同的,也就是在集合中存不存在),在集合中查找是否有哈希值相同键对象。如果没有哈希值相同相同的键对象,直接将键值对存入。如果有哈希值相同的对象(就是当两个对象的地址相同时,比如对象p3和p3),则和哈希值相同的键进行equals()比较。比较结果为false就存入键值对,比较结果为true则用新的值覆盖旧的值。

  2. TreeMap:适用于需要有序访问元素的场景,但插入和删除元素的效率较低。

    当存储键值对的时候,通过指定的比较算法,对键对象进行排序,以二叉树形式进行存储。创建TreeMap时如果没有传入比较器,则会按照键对象的自然顺序来排序,自然排序就是实现了Comparable接口之后compareTo方法中定义的顺序。

    如果我们不希望使用自然排序,还在TreeMap的构造函数中可以传入一个比较器。比较器就是实现了Comparator接口的对象,按照其中compare方法排序。

    TreeMap的自然排序和比较器排序参考,前面的TreeSet的排序代码。

image-20230506122840310

image-20230506123338093

(2)、Map子接口的使用
迭代方式:

1)、a.keySet:将Map中所有的键存入到Set集合。因为Set具备迭代器,所以可以用迭代方式取出所有的键,再根据get方法,获取每一个键对应的值。

Map集合的取出原理:将Map集合转成Set集合,再通过迭代器取出。

2)、b.entrySet:将集合中的映射关系存入到Set集合中,而这个关系的数据类型就是:Map.Entry。Entry其实就是Map中的一个static内部接口。为什么要定义在内部呢?因为只有有了Map集合,有了键值对,才会有键值的映射关系。关系属于Map集合中的一个内部事物,而且该事物在直接访问Map集合中的元素。

Java中的Map接口提供了两种遍历方式:entrySet()和keySet(),分别使用增强for和迭代器iterator遍历

下面是思路:

Java中的Map接口提供了两种遍历方式:entrySet()和keySet()。

  1. entrySet()方法

    entrySet()方法返回一个Set集合,其中包含Map中的所有键值对。每个键值对都表示为一个Map.Entry对象,该对象包含键和值两个属性。可以使用增强for循环或迭代器遍历entrySet()集合。

    使用增强for循环遍历entrySet()集合的示例代码如下:

        Map<String, Integer> map = new HashMap<>();
        // 添加键值对
        map.put("A", 1);
        map.put("B", 2);
        map.put("C", 3);

// 使用增强for循环遍历entrySet()集合
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
        System.out.println(entry.getKey() + " : " + entry.getValue());
        }
使用迭代器遍历entrySet()集合的示例代码如下:

    
        Map<String, Integer> map = new HashMap<>();
        // 添加键值对
            map.put("A", 1);
            map.put("B", 2);
            map.put("C", 3);
    
    // 使用迭代器遍历entrySet()集合
            Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
            while (iterator.hasNext()) {
            Map.Entry<String, Integer> entry = iterator.next();
            System.out.println(entry.getKey() + " : " + entry.getValue());
            }
 2. keySet()方法

    keySet()方法返回一个Set集合,其中包含Map中的所有键。可以使用增强for循环或迭代器遍历keySet()集合,然后通过键获取对应的值。

    使用增强for循环遍历keySet()集合的示例代码如下:

        Map<String, Integer> map = new HashMap<>();
        // 添加键值对
            map.put("A", 1);
            map.put("B", 2);
            map.put("C", 3);
    
    // 使用增强for循环遍历keySet()集合
            for (String key : map.keySet()) {
            System.out.println(key + " : " + map.get(key));
            }
​    使用迭代器遍历keySet()集合的示例代码如下:

        Map<String, Integer> map = new HashMap<>();
        // 添加键值对
            map.put("A", 1);
            map.put("B", 2);
            map.put("C", 3);
    
    // 使用迭代器遍历keySet()集合
            Iterator<String> iterator = map.keySet().iterator();
            while (iterator.hasNext()) {
            String key = iterator.next();
            System.out.println(key + " : " + map.get(key));
            }


##### Map子接口的使用(使用上面的两种遍历方式)

Map接口提供了两种遍历方式:entrySet()和keySet(),分别使用增强for和迭代器iterator遍历

下面对Map集合基本操作,以及四种遍历的使用

//keySet()的增强for遍历

//keySet()的iterator迭代器遍历

 //entrySet()的增强for遍历

 //entrySet()的iterator迭代器遍历

下面是具体的:

        package com.ji_he_Map;
    
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map;
    
    //Map接口提供了两种遍历方式:entrySet()和keySet(),分别使用增强for和迭代器iterator遍历
    //下面对Map集合基本操作,以及四种遍历的使用
    public class MapDemo1 {
        public static void main(String[] args) {
            //创建集合
            Map<String, String>map = new HashMap<>();
            //添加元素,用put(K,V)方法,K为键,V为值。     list和set中用的add()方法添加元素
            map.put("admin","admin");
            map.put("user","user123");
            map.put("use1","user123");
        //键值对的个数
        System.out.println("键值对的个数为:"+map.size());
        System.out.println(map.toString());//打印集合对象
        //复制
        Map<String, String> map1 = new HashMap<>();//要相同类型才能复制
        map1 = map;
        System.out.println(map1.toString());
    
        //判断
        System.out.println(map.containsKey("user"));//判断是否包含键
        System.out.println(map.containsValue("admin"));//判断是否包含值
        System.out.println(map.isEmpty());//判断是否为null
    
        //返回键对应的值
        System.out.println(map.get("admin"));
    
        //视图
        System.out.println("返回映射对,也就是所有的键值对:"+map.entrySet());//返回映射对,也就是所有的键值对
        System.out.println("返回所有的键:"+map.keySet());//返回所有的键
        System.out.println("返回所有的值:"+map.values());//返回所有的值
    
        System.out.println("------------------------------------------");
        //迭代 keySet()和entrySet()
        //keySet()的增强for遍历
        for (String key:map.keySet()){//map.keySet(),将map集合转为set集合,并将键放入set集合中,此时得到key键集合(map.keySet())
            String value = map.get(key);//通过键取到值
            System.out.println("键:"+key+",值;"+value);
        }
        System.out.println("----------------------------");
        //keySet()的iterator迭代器遍历
        Iterator<String> iterator = map.keySet().iterator();//拿到所有的key(键)集合,并放入集合iterator中
        while (iterator.hasNext()){
            String key = iterator.next();//得到key
            System.out.println("键:"+key+",值:"+map.get(key));//通过键,取到值
        }
    
        //entrySet()的增强for遍历
        System.out.println("----------------------------");
        for (Map.Entry<String,String> entry:map.entrySet()){//获取到所有的键值对对象,并放入集合entry中
            String key = entry.getKey();//在键值对中得到key(键)
            String value = entry.getValue();
            //也可以写成下面这个,得到键对应的值
           // String value = map.get(key);//通过键得到值
    
            System.out.println("键:"+key+",值:"+value);
        }
    
        System.out.println("-----------------");
        //entrySet()的iterator迭代器遍历
        Iterator<Map.Entry<String, String>> iterator1 = map.entrySet().iterator();//拿到所有的键值对对象,并放入Set集合iterator1中
    
        while (iterator1.hasNext()){
            //从一个名为iterator1的迭代器中获取下一个Map.Entry对象,并将其分配给一个名为entry的变量。
            // Map.Entry是Java中的一个接口,表示一个键值对(key-value pair)
            Map.Entry<String, String> entry = iterator1.next();//获取下一个键值对,给变量entry
            String key = entry.getKey();//键值对中得到键
            String value = entry.getValue();//键值对中得到值
            System.out.println("键:"+key+",值:"+value);
        }
    }
}

## 10、Map子接口的实现类

#### (1)、HashMap的使用(向集合中传入自定义类的对象,UserDemo5类的属性对应HashMap中<K,V>的K)

HashMap的特点:

①、键不能重复、值可以重复

②、存放键值对

③、无序

④、存储结构为:哈希表

⑤、HashMap的重复依据:键的hashCode()方法和equals()方法



**HashMap同样是需要重写键的equals()和hashcode()方法,比如说下面的列子,是将自定义类的两个属性作为键,那么需要重写两个属性的equals方法和hashcode方法**



**为什么要这样向集合中传入自定义类的对象,UserDemo5类对应HashMap中<K,V>的K                     为什么要这么写?**

创建了一个HashMap对象,将一个自定义的UserDemo2对象作为键,将一个字符串作为值存储在HashMap中。
* 这种使用自定义对象作为键的方式可以在需要根据对象来查找对应值的场景中使用,
* 例如在一个用户管理系统中,可以将用户对象作为键,将用户所在地区作为值存储在HashMap中,这样可以根据用户对象快速查找对应的地区信息。

创建类UserDemo5,包含两个属性,两个属性作为HashMap的键

向集合中传入自定义类的对象,UserDemo5类的两个属性对应HashMap中<K,V>的K

    package com.ji_he_Map;
    
    import java.util.Objects;
    
    public class UserDemo2 {
        private String userName;
        private String passWord;
    public UserDemo2(String userName, String passWord) {
        this.userName = userName;
        this.passWord = passWord;
    }
    public UserDemo2(){
    
    }
    
    public String getUserName() {
        return userName;
    }
    
    public void setUserName(String userName) {
        this.userName = userName;
    }
    
    public String getPassWord() {
        return passWord;
    }
    
    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }
    
    @Override
    public String toString() {
        return "UserDemo2{" +
                "userName='" + userName + '\'' +
                ", passWord='" + passWord + '\'' +
                '}';
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        UserDemo2 userDemo2 = (UserDemo2) o;
        return userName.equals(userDemo2.userName) && passWord.equals(userDemo2.passWord);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(userName, passWord);
    }
}

向集合中传入自定义类的对象,UserDemo5类的两个属性对应HashMap中<K,V>的K

package com.ji_he_Map;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/* Map的实现类之HashMap

  • 创建了一个HashMap对象,将一个自定义的UserDemo2对象作为键,将一个字符串作为值存储在HashMap中。

  • 这种使用自定义对象作为键的方式可以在需要根据对象来查找对应值的场景中使用,

  • 例如在一个用户管理系统中,可以将用户对象作为键,将用户所在地区作为值存储在HashMap中,这样可以根据用户对象快速查找对应的地区信息。

  • */
    public class HashMapeUserDemo2 {
    public static void main(String[] args) {
    HashMap<UserDemo2, String> hashMap = new HashMap<UserDemo2,String>();
    UserDemo2 u1 = new UserDemo2(“user1”,“123”);
    UserDemo2 u2 = new UserDemo2(“user2”,“1234”);
    UserDemo2 u3 = new UserDemo2(“user3”,“12345”);
    hashMap.put(u1,“重庆”); //这样写可以根据用户对象u1,快速查找值为:重庆,的数据信息
    hashMap.put(u2,“四川”);
    hashMap.put(u3,“云南”);
    System.out.println(hashMap.size());
    System.out.println(hashMap.toString());
    //删除
    hashMap.remove(“u2”,“四川”);
    //判断
    System.out.println(hashMap.containsKey(u1));//为true
    System.out.println(hashMap.containsKey(new UserDemo2(“user1”,“123”)));//为false,
    // 因为没有重写equals和hashcode方法,重写前面两个方法后,hashMap.containsKey(new UserDemo2(“user1”,“123”))为true
    System.out.println(hashMap.containsValue(“重庆”));//判断是否存在值为:重庆的键值对
    System.out.println(“-------------------”);
    //遍历
    //增强for的keySet()遍历
    for (UserDemo2 key:hashMap.keySet()){//获取键的set集合
    String value = hashMap.get(key);
    System.out.println(“键:”+key.toString()+“,值:”+value);
    }
    System.out.println(“-------------------”);
    //增强for的entrySet()遍历
    for (Map.Entry<UserDemo2, String> entry:hashMap.entrySet()){//获取键值对的set集合,放入entry中
    UserDemo2 key = entry.getKey();
    String value = entry.getValue();
    System.out.println(“键:”+key.toString()+“,值:”+value);
    }
    System.out.println(“-------------------”);
    //迭代器的keySet()遍历
    Iterator iterator = hashMap.keySet().iterator();//将键的集合放入set集合中,给iterator保存
    while (iterator.hasNext()){
    UserDemo2 key = iterator.next();
    String value = hashMap.get(key);
    System.out.println(“键:”+key.toString()+“,值:”+value);
    }

    System.out.println(“-------------------”);
    //迭代器的entrySet()遍历
    Iterator<Map.Entry<UserDemo2, String>> iterator1 = hashMap.entrySet().iterator();//将键值对的集合放入set集合中,给iterator1保存
    while (iterator1.hasNext()){
    Map.Entry<UserDemo2, String> entry = iterator1.next();
    UserDemo2 key = entry.getKey();
    String value = entry.getValue();
    System.out.println(“键:”+key.toString()+“,值:”+value);
    }


  }
}

运行结果:
集合的长度(包含键值对的个数:)3
{UserDemo2{userName=‘user1’, passWord=‘123’}=重庆, UserDemo2{userName=‘user3’, passWord=‘12345’}=云南, UserDemo2{userName=‘user2’, passWord=‘1234’}=四川}
true
true
true
-------------------
键:UserDemo2{userName=‘user1’, passWord=‘123’},值:重庆
键:UserDemo2{userName=‘user3’, passWord=‘12345’},值:云南
键:UserDemo2{userName=‘user2’, passWord=‘1234’},值:四川
-------------------
键:UserDemo2{userName=‘user1’, passWord=‘123’},值:重庆
键:UserDemo2{userName=‘user3’, passWord=‘12345’},值:云南
键:UserDemo2{userName=‘user2’, passWord=‘1234’},值:四川
-------------------
键:UserDemo2{userName=‘user1’, passWord=‘123’},值:重庆
键:UserDemo2{userName=‘user3’, passWord=‘12345’},值:云南
键:UserDemo2{userName=‘user2’, passWord=‘1234’},值:四川
-------------------
键:UserDemo2{userName=‘user1’, passWord=‘123’},值:重庆
键:UserDemo2{userName=‘user3’, passWord=‘12345’},值:云南
键:UserDemo2{userName=‘user2’, passWord=‘1234’},值:四川


#### (2)、TreeMap的使用

创建类StudentDemo3,对象作为集合TreeMap的键

    package com.ji_he_Map;
    
    public class StudentDemo3 {
        private String name;
        private int age;
    public StudentDemo3(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public StudentDemo3() {
    }
    
    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;
    }
    
    @Override
    public String toString() {
        return "StudentDemo3{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

创建集合,完成基本操作和遍历

       package com.ji_he_Map;
    
    import com.ji_he_Set.StudentDemo6;
    
    import java.util.Comparator;
    import java.util.Map;
    import java.util.TreeMap;
    
    public class TreeMapDemo3 {
        public static void main(String[] args) {
            TreeMap<StudentDemo3, String> treeMap = new TreeMap<>(new Comparator<StudentDemo3>() {//比较器排序
                @Override
                public int compare(StudentDemo3 o1, StudentDemo3 o2) {
                    int n1 = o1.getName().compareTo(o2.getName());
                    int n2 = o1.getAge() - o2.getAge();
                    return n1==0?n2:n1;
                }
            });
       StudentDemo3 s1 = new StudentDemo3("li",13);
        StudentDemo3 s2 = new StudentDemo3("li",11);
        StudentDemo3 s3 = new StudentDemo3("anna",20);
        treeMap.put(s1,"1556626262qq.com");
        treeMap.put(s2,"155154@qq.com");
        treeMap.put(s3,"778795@qq.com");
        System.out.println(treeMap.size());
        System.out.println(treeMap.toString());
    
        for (StudentDemo3 key:treeMap.keySet()){
            String value = treeMap.get(key);
            System.out.println("键:"+key+",值:"+value);
        }
        System.out.println("------------");
        for (Map.Entry<StudentDemo3,String> entry:treeMap.entrySet()){
            StudentDemo3 key = entry.getKey();
            String value = entry.getValue();
            System.out.println("键:"+key+",值:"+value);
        }
        System.out.println(treeMap.containsKey(s1));
        System.out.println(treeMap.containsValue("778795@qq.com"));
        System.out.println(treeMap.isEmpty());
        treeMap.remove(s1);
        System.out.println(treeMap.size());
        treeMap.clear();
        System.out.println(treeMap.size());
    }
}

##  11、Collections接口的使用

Java中的Collections接口是一个框架,提供了一系列的静态方法和泛型集合类,用于操作集合对象(如List、Set、Map等)。它提供了一些常用的算法和数据结构,如排序、查找、替换、复制等,使得集合操作更加方便和高效。

使用Collections接口可以实现以下功能:

        1. 排序:可以使用Collections.sort()方法对List进行排序。
    
        2. 查找:可以使用Collections.binarySearch()方法在有序List中查找元素。
    
        3. 替换:可以使用Collections.replaceAll()方法替换List中的元素。
    
        4. 复制:可以使用Collections.copy()方法将一个List复制到另一个List中。
    
        5. 集合的同步:可以使用Collections.synchronizedXXX()方法将非线程安全的集合变为线程安全的集合。
    
        6. 不可变集合:可以使用Collections.unmodifiableXXX()方法创建不可变的集合。

总之,Collections接口提供了一些常用的集合操作方法,可以方便地对集合进行操作,提高代码的可读性和效率。



常用的一些操作


       package com.ji_he_Collections;
    
    import java.util.*;
    
    public class CollectionsArraylistDemo1 {
        public static void main(String[] args) {
            ArrayList<Integer> arrayList = new ArrayList<>();
            arrayList.add(12);
            arrayList.add(45);
            arrayList.add(12);
            arrayList.add(78);
            arrayList.add(98);
            System.out.println("元素个数:"+arrayList.size());
            System.out.println(arrayList.toString());
            //排列升序
            Collections.sort(arrayList);
            System.out.println("升序:"+arrayList.toString());
       //二叉树查找,排序之后,一定要是升序的时候
        int s = Collections.binarySearch(arrayList, 98);//参数一:List集合,参数二:值  //集合中存在该值就返回索引,不存在为负
        System.out.println(s);
        //倒序
        Collections.reverse(arrayList);
        System.out.println(arrayList.toString());
        //乱序
        Collections.shuffle(arrayList);
        System.out.println(arrayList);
        //copy复制
        ArrayList<Integer> arrayList2 = new ArrayList<>();
        for (int i =0;i < arrayList.size();i++){
            arrayList2.add(0);
        }
        Collections.copy(arrayList2,arrayList);//要求复制的列表大小一致,所以要经过上面的for先进行赋值0
        System.out.println(arrayList2);
    
        System.out.println("-------------");
        //其它
        //list转为数组
        Integer[] array = arrayList.toArray(new Integer[0]);
        System.out.println(Arrays.toString(array));
        System.out.println(array.length);
        //数组转为列表
        String[]  name = {"张三","李四"};
        //数组转为的列表为受限列表,不能添加或者删除
        List objects = Arrays.asList(name);
        //基本类型转换时,需要包装类型
        //int[] i1={10,20,30,40};
        //List<int> list3 = Arrays.asList(i1); //报错
        Integer[] i1={10,20,30,40};
        List<Integer> list3 = Arrays.asList(i1); //报错
        System.out.println(list3);
    }
}

运行结果:

元素个数:5
        [12, 45, 12, 78, 98]
        升序:[12, 12, 45, 78, 98]
        4
        [98, 78, 45, 12, 12]
        [12, 98, 12, 78, 45]
        [12, 98, 12, 78, 45]
        -------------
        [12, 98, 12, 78, 45]
        5
        [10, 20, 30, 40]

第6章 Java的异常处理

1、异常的概念

异常指程序在运行过程中,出现的非正常的情况,最终会导致java虚拟机(jvm)的非正常停止。(异常也称例外,是在程序运行过程中发生的、会打断程序正常执行的事件。)

(1)、异常如何处理

当异常出现时,执行预先准备好的程序。

(2)、异常处理必要性

减少用户的损失、同时也减少给用户带来的不必要的麻烦;也可以利用异常处理给与用户一些提示。

程序错误分为三种:1.编译错误;2.运行时错误;3.逻辑错误。

(1)编译错误是因为程序没有遵循语法规则,编译程序能够自己发现并且提示我们错误的原因和位置,这个也是大家在刚接触编程语言最常遇到的问题。

(2)运行时错误是因为程序在执行时,运行环境发现了不能执行的操作。

(3)逻辑错误是因为程序没有按照预期的逻辑顺序执行。异常也就是指程序运行时发生错误,而异常处理就是对这些错误进行处理和控制。

(3)、自动产生异常

产生原因

程序运行过程中,遇到错误的代码,自动产生异常

结果

程序中一旦出现异常,程序不能继续执行之后的代码,则程序被终止掉

(4)、手动产生异常

关键字

throw

语法

修饰符 返回值类型 方法名(形参列表) throws 异常 的类名1,异常类名2{// 方法的实现部分}

位置

定义在方法内部。

结果

作用效果类似于 return语句,终止当前方法、函数;程序运行时因异常而被终止掉。

2、异常的分类

Throwable 类是所有异常和错误的超类,下面有 Error 和 Exception 两个子类分别表示错误和异常。其中异常类 Exception 又分为运行时异常和非运行时异常,这两种异常有很大的区别,也称为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。

Exception 类用于用户程序可能出现的异常情况,它也是用来创建自定义异常类型类的类。

Error 定义了在通常环境下不希望被程序捕获的异常Error 类型的异常用于 Java 运行时由系统显示与运行时系统本身有关的错误。堆栈溢出是这种错误的一例。这种异常发生时,Java虚拟机(JVM),一般会选择线程终止。

运行时异常都是RuntimeException 类及其子类异常,如 NullPointerException、IndexOutOfBoundsException 等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般由程序逻辑错误引起,程序应该从逻辑角度尽可能避免这类异常的发生。

非运行时异常是指 RuntimeException 以外的异常,类型上都属于 Exception 类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如 IOException、ClassNotFoundException 等以及用户自定义的 Exception 异常,一般情况下不自定义检查异常。表 1 列出了一些常见的异常类型及它们的作用。

image-20230607195714216

3、异常处理

异常处理涉及到五个关键字,分别是try、catch、finally、throw、throws。

判断字符串为空,下面会报空指针异常。

  String str = null;
if (str.isEmpty()) { // 空指针异常
        // 代码逻辑
        }

运行结果:

Exception in thread “main” java.lang.NullPointerException

分析:字符串为null时,直接调用isEmpty方法就会报空指针异常。一种解决方案如下,先判断字符串是否为null,不为null再执行isEmpty方法。

String str = null;
if (str != null && str.isEmpty()) { // 无异常
        // 代码逻辑
        }
(1)、异常的捕获
package com.yi_chang;

public class exception {
    public static void main(String[] args) {
        try {
            String value = null;
            if (value.isEmpty()){
                System.out.println("0000"); //尝试运行的代码
            }
        }catch (Exception e){  //catch(异常类型 异常的变量名)    Exception是接口Throwable的子类
            System.out.println(e);
            System.out.println(e.getMessage());//e.getMessage()获取异常的信息
            System.out.println(e.toString());//toString()获取异常的名字和异常的信息
        }
       finally {
            //异常发生,方法返回之前,总是要执行的代码
        }
    }
}

注意,try、catch、finally三个语句块都不能单独使用,三者可以组成try…catch…finally、try…catch、try…finally三种结构,catch可以一个或者多个,finally最多一个。多个catch块的时候,只会匹配到其中一个异常类并执行catch块代码。而不会再执行别的catch块,并且匹配catch的顺序是从上到下。

e.getMessage()方法:获取异常的信息。

toString()方法:获取异常的名字和异常的信息。

(2)、异常的抛出
  • throw用于手动抛出异常,作为程序员可以在任意位置手动抛出异常。

  • throws用于在方法上标识要暴露的异常,抛出的异常交由调用者处理。

  • throw用在方法内,后面跟上要抛出的异常类对象.

  • throws修饰在方法上,告诉调用者此方法可能会抛出异常,后面跟上要抛出的异常类名。

1)、throws抛出异常

throws抛出异常,作为程序员可以在任意位置手动抛出异常

public class exceptionThrows {
    public static void main(String[] args) throws Exception {
            String i = null;
        if (i==null) {
                throw new Exception("出现手动抛出的异常");
            }
    }
}

运行结果:

Exception in thread “main” java.lang.Exception: 出现手动抛出的异常
at com.yi_chang.exceptionThrows.main(exceptionThrows.java:11)

2)、throws声明某个方法可能会抛出的方法

//throws后面跟异常类,可以多个

package com.yi_chang;

public class exceptionThrows {
    public static void main(String[] args) {
        exceptionThrows te = new exceptionThrows();
        te.run();
    }

    public void run() throws NullPointerException,IndexOutOfBoundsException{//throws后面跟异常类,可以多个
       try {
           String i = null;
           if (i.isEmpty()){
               System.out.println("0000");
           }
       }catch (NullPointerException e){
           System.out.println(e.toString());//此时抛出该异常
       }
    }
}

运行结果:

java.lang.NullPointerException

分析上面:字符串为null时,直接调用isEmpty方法就会报空指针异常。一种解决方案如下,先判断字符串是否为null,不为null再执行isEmpty方法。

第7章 Java的输入输出流(暂时没写)

第8章 Java多线程

1、进程和线程

进程

程序是静止的,运行中的程序就是进程

进程的三个特征:

1:动态性:进程是运行中的程序,要动态的占用内存,CPU和网络等资源

2:独立性:进程与进程之间是相互独立的,彼此有自己的独立内存区域

3:并发性:假如CPU是单核,同时刻其实内存只有一个进程在执行,CPU会分时轮询切换以此为每个进程服务

线程

一个进程可以有多个线程,线程是进程中的一个独立执行单元,创建开销相对进程较小,同时支持并发

线程的作用

1:可以提高程序的效率,线程也支持并发,可以有更多机会得到CPU

2:多线程可以解决很多业务模型

3:大型高并发技术的核心技术

4:涉及到多线程的开发可能都比较难理解

Thread类的API:

1:public void setName(String name):给当前线程取名字

2:public void getName():获取当前线程名字

– 线程存在默认名称,子线程的默认名称是:Thread-索引

– 主线程的默认名称是:main

3:public static Thread currentThread()

– 获取当前线程对象,这个代码在哪个线程中,就得到哪个线程对象

4:public static void sleep(long time):让当前线程休眠多少毫秒再继续执行

2、线程的三种创建方式

线程的创建方式

1:直接定义一个类继承线程类Thread,重写run()方法,创建线程对象;调用线程对象的start()方法启动线程

2:定义线程任务类实现Runnable接口,重写run()方法,创建线程任务对象,把线程任务对象包装成线程对象,调用start()方法启动线程

3:实现Callable接口

(1)、直接定义一个类继承线程类Thread

接下来多线程的内容看下面网址。

https://www.cnblogs.com/tianshu/p/16090966.html

第9章 反射

1、反射的概述

反射机制:是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法对于任意一个对象,都能够调用它的任意属性和方法;这种动态获取信息以及动态调用对象方法的功能称为 java 语言的反射机制。

他的工作原理是这样的:当一个字节码文件加载到内存的时候, jvm会对该字节码进行解剖,然后创建一个对象的Class对象,jvm把字节码文件的信息全部都存储到该Class对象中,我们只要获取到Class对象,我们就可以使用该对象设置对象的属性或者调用对象的方法等操作。**

反射可以动态获取类的信息,进一步实现需要的功能
例如: Spring框架通过XML文件描述类的基本信息,使用反射机制动态装配对象

为什么需要反射?
Java程序中的对象在运行时可以表现为两种类型,即编译时类型和运行时类型。例如 Person p = new Student(); ,这行代码将会生成一个p变量,该变量的编译时类型为Person,运行时类型为Student。

有时,程序在运行时接收到外部传入的一个对象,该对象的编译时类型是Object,但程序又需要调用该对象的运行时类型的方法。这就要求程序需要在运行时发现对象和类的真实信息,而解决这个问题有以下两种做法:

第一种做法是假设在编译时和运行时都完全知道类型的具体信息,在这种情况下,可以先使用instanceof运算符进行判断,再利用强制类型转换将其转换成其运行时类型的变量即可。
第二种做法是编译时根本无法预知该对象和类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。
具体来说,通过反射机制,我们可以实现如下的操作:

程序运行时,可以通过反射获得任意一个类的Class对象,并通过这个对象查看这个类的信息;
程序运行时,可以通过反射创建任意一个类的实例,并访问该实例的成员;
程序运行时,可以通过反射机制生成一个类的动态代理类或动态代理对象。

2、 获取Class类对象的三种方式

获取Class类对象有三种方式:

类名.class属性
对象名.getClass()方法
Class.forName(全类名)方法
这三种方式分别在程序不用阶段调用,程序运行阶段与获取类方法对应的关系图如下:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(1)、获取Class类对象的三种方式,代码示例
    class ReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        // 1.Class类中的静态方法forName("全类名")
        //全类名:包名 - 类名
        Class clazz1 = Class.forName("com.fanshe.Student");
        System.out.println(clazz1);
    // 2.通过class属性来获取(类名.class)
    Class clazz2 = Student.class;
    System.out.println(clazz2);

    // 3.利用对象的getClass方法来获取class对象
    // getClass方法是定义在Object类中.
    Student s = new Student();
    Class clazz3 = s.getClass();
    System.out.println(clazz3);
        System.out.println(clazz1 == clazz2);
    System.out.println(clazz2 == clazz3);
}
}

运行结果:

image-20230608191453159

三个方法均能获得该类对象,并且三个类是相等的。

(2)、获取Class类对象的三种方式,自己写的代码示例
package com.fan_she;

import org.junit.Test;
public class fan_she {
    @Test
    public void test() throws ClassNotFoundException {
        //获取class对象的三种方式
        //方式一  Class类中的静态方法forName("全类名")     //全类名:包名 - 类名
        Class aClass = Class.forName("com.fan_she.Student");
        System.out.println(aClass.getName()); //将获取到的class对象的Name进行输出
        System.out.println(aClass);

        System.out.println("---------------------");
        
        //方式二 通过类名.class,获取到class对象
        Class<Student> aClass1 = Student.class;
        System.out.println(aClass1.getName());
        
        //方式三 通过对象名.getClass(),获取class对象
        Student student = new Student();
        Class aClass2 = student.getClass();
        System.out.println(aClass2.getName());
    }
}

运行结果:

com.fan_she.Student
class com.fan_she.Student
---------------------
com.fan_she.Student
com.fan_she.Student

3、获取到class对象,如何来操作属性和方法

1)、创建类Student

package com.fan_she;

public class Student {
    public int no = 11;
    private  String name = "lcy";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public int getNo() {
        return no;
    }

}

2)、创建类fan_she

package com.fan_she;

import org.junit.Test;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class fan_she {//获取到class对象,如何来操作属性和方法
    @Test
    public void test() throws ClassNotFoundException, NoSuchMethodException,
            NoSuchFieldException, InstantiationException, IllegalAccessException, InvocationTargetException {
        // Class类中的静态方法forName("全类名")     //全类名:包名 - 类名
        Class aClass = Class.forName("com.fan_she.Student");
        System.out.println(aClass.getName()); //将获取到的class对象的Name进行输出
        System.out.println(aClass);

        //1、使用创建的反射对象访问方法信息
        Method getNo = aClass.getMethod("getNo");  //通过反射对象获取方法的信息,"getNo"是Student中的一个方法
        System.out.println(getNo);

        //获取到方法信息后,如何调用方法
        Object o = aClass.newInstance();//创建类的实例,通过获取到的class对象来创建
        Object result = getNo.invoke(o);//调用getNo方法,getNo是上面创建反射对象后返回的方法信息。
        // Object result = getNo.invoke(o);//调用getNo方法
        // 第一个参数o是方法所属的对象,如果是静态方法则可以传入 `null`,
        // 后面的参数是方法的参数列表,如果该方法没有参数,则不需要传入参数。调用方法后,可以通过返回值获取方法的返回结果。

        System.out.println(result); //输出调用getNo方法的返回值

        System.out.println("------------------");

        //2、使用创建的反射对象访问属性信息
        Field no = aClass.getField("no"); //通过反射获取属性的信息 no为public   getDeclaredField()
        Field name = aClass.getDeclaredField("name");//通过反射获取属性的信息 name为private  需要使用 getDeclaredField()来获取属性的信息
        System.out.println(name);
        //获取到属性信息后,如何访问和设置属性的值;
        Student student = new Student();
        //获取属性name的值
        name.setAccessible(true);//如果属性是私有的,需要先调用setAccessible(true)方法来设置访问权限。如果不是private就不需要设置
        Object o1 = name.get(student);//student是属性所属的对象
        System.out.println(o1);

        //设置属性name的值
        name.set(student,"zhangsan"); //student是属性所属的对象,"zhangsan"是需要给属性name要设置的新值
        Object o2 = name.get(student);
        System.out.println("修改后的name值:"+o2);
    }
}

运行结果:

com.fan_she.Student
class com.fan_she.Student
public int com.fan_she.Student.getNo()
11
------------------
private java.lang.String com.fan_she.Student.name
lcy
修改后的name值:zhangsan

4、反射的应用

Java的反射机制在实际项目中应用广泛,常见的应用场景有:

使用JDBC时,如果要创建数据库的连接,则需要先通过反射机制加载数据库的驱动程序 ;
多数框架都支持注解/XML配置,从配置中解析出来的类是字符串,需要利用反射机制实例化;
面向切面编程(AOP)的实现方案,是在程序运行时创建目标对象的代理类,这必须由反射机制来实现。

第10章 JDBC连接数据库(暂时没写)

1、在idea中创建mysql的数据库和表

2、在项目中添加mysql驱动需要用到的jar包,jar包去官网下载

在File下面的Project Structure中的Modules中的Dependencies中添加下载好的jar包。

image-20230612003815477

3、statement接口

package com.jdbc;

import java.sql.*;

public class TestJdbc {
    public  static void main(String[] args) throws ClassNotFoundException, SQLException {
        String URL = "jdbc:mysql://localhost:3306/javaweb_jdbc_database?useUnicode=true&characterEncoding=utf-8";
        String USER = "root";
        String PASSWORD = "root";
        //加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        //连接数据库
        Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
        //创建数据库对象
        Statement statement = conn.createStatement();
        //查询
        String sql_select="select * from users;";
        //执行sql,并得到返回数据集对象,executeQuery()方法适合查询,executeUpdate()方法适合增删改,返回受影响的行数
        ResultSet result = statement.executeQuery(sql_select);

        while (result.next()){
            System.out.println("id:"+result.getInt("id"));
            System.out.println("name:"+result.getString("name"));
            System.out.println("password:"+result.getString("password"));
            System.out.println("email:"+result.getString("email"));
            System.out.println("birthday:"+result.getString("birthday"));
        }

        //增加
        String sql_insert= "insert into users(id,name,password,email,birthday)values(6,'王大胖','123456','12345@qq.com','2021-05-02');";
        int executeUpdate = statement.executeUpdate(sql_insert);
        if(executeUpdate > 0){
            System.out.println("插入成功");
        }

        result.close();
        statement.close();
        conn.close();
    }
}
resultSet结果集,记录的是当前查询出来的内容,是一条内容,从第一行上方开始( 即列名行),
因为不知道查询出来的结果有多少行,所以需要使用while遍历的方式,进行每行数据的查询;循环条件为 resultSet.next()
通过 resultSet.get数据类型(第几列) 获取到数据库中当前行的第几列的值 如
    resultSet.getString(2);

4、statement的子接口preparestatement

package com.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class TestJdbc2 {
     public static void main(String[] args) throws ClassNotFoundException, SQLException {
         String URL = "jdbc:mysql://localhost:3306/javaweb_jdbc_database?useUnicode=true&characterEncoding=utf-8";
         String USER = "root";
         String PASSWORD = "root";
         //加载驱动
         Class.forName("com.mysql.jdbc.Driver");
         //连接数据库
         Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
         //预处理
         //插入
//         PreparedStatement ps = conn.prepareStatement("insert into users values(?,?,?,?,?)");
//         ps.setInt(1,8);
//         ps.setString(2,"李武");
//         ps.setString(3,"123456");
//         ps.setString(4,"4551511@qq.com");
//         ps.setString(5,"2021-04-05");
//         int i = ps.executeUpdate();
//
//         if(i > 0){
//             System.out.println("插入成功");
//         }
//        ps.close();

         //查询
//         PreparedStatement ps2 = conn.prepareStatement("select * from users where id = ?");
//        ps2.setInt(1,2);
//
//         ResultSet result = ps2.executeQuery();
//         while(result.next())
//         {
//             System.out.println("id:"+result.getInt("id"));
//             System.out.println("name:"+result.getString("name"));
//             System.out.println("password:"+result.getString("password"));
//             System.out.println("email:"+result.getString("email"));
//             System.out.println("birthday:"+result.getString("birthday"));
//         }
//         result.close();
//         ps2.close();

         //删除
//         PreparedStatement ps_delete = conn.prepareStatement("delete  from users where id = ?");
//         ps_delete.setInt(1, 4);
//         int update = ps_delete.executeUpdate();
//
//         if (update > 0) {
//             System.out.println("删除成功");
//
//         }
//         ps_delete.close();

         //修改
         PreparedStatement ps_update = conn.prepareStatement("update users set id = ?, name = ? ," +
                 "password = ?, email = ?, birthday = ? where id = ?");
         ps_update.setInt(1, 10);
         ps_update.setString(2,"路易小小");
         ps_update.setString(3,"123456");
         ps_update.setString(4,"10086@qq.com");
         ps_update.setString(5,"2020-04-05");
         ps_update.setInt(6, 8);
         int update = ps_update.executeUpdate();

         if (update > 0) {
             System.out.println("修改成功");

         }
         ps_update.close();

         conn.close();

         /*
         总结:preparedstatement会预编译sql语句和可以防止sql注入,用了动态参数占位符”?“提高了可读性和维护性;
             statement没有预处理,没有动态参数占位符
          */
     }
}

5、statement和preparedstatement的区别

总结:preparedstatement会预编译sql语句和可以防止sql注入,用了动态参数占位符”?“提高了可读性和维护性;
statement没有预处理,没有动态参数占位符

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值