JavaSE

目录


面向对象编程

目录

访问修饰符

访问修饰符有:public、protected、默认、private

有下表的关系

访问级别 访问控制修饰符 同类 同包 子类 不同包
公开 public
受保护 protected ×
默认 没有修饰符 × ×
私有 private × × ×

封装

​ 封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将[数据]与操作数据的[源代码]进行有机的结合,形成“类”,其中数据和函数都是类的成员。

我理解到的结构是这样的:

​ 类中定义的数据是私有的外部调用该类时就不能修改其中的代码,只有通过类中定义的方法才能拿到数据。这样就可以隐藏实现细节,同时可以保证数据的安全。

​ 封装中涉及到getter和setter方法,可以通过alt+insert进行快捷添加

继承

​ 所有类的最顶层的父类是Object类

  • 子类继承了所有的属性和方法,但是私有属性不能再子类直接访问,需要通过封装时候讲到的getter方法进行访问数据。
  • 子类必须调用父类的构造器,完成父类的初始化:子类在调用构造器时,先调用父类的构造器,如果子类有构造器,再调用子类的构造器
  • 当创建子类对象时,不管使用子类的哪个构造器,默认情况下都会去调用父类的无参构造器,如果父类没有提供无参构造器,需要再子类的构造器中用super指明使用父类的哪个构造器完成父类的初始化工作,同样如果需要调用父类的其他构造器也是要显示调用,不然子类会默认调用父类的无参构造器
  • super必须放在构造器的第一行
  • super()、this() 都只能放在构造器第一行,所以这两个方法不能共存再一个构造器中
  • 父类构造器不限于直接父类,会一直调用父类的父类直到Object类
  • java中是单继承机制,不能滥用继承关系,需要满足 is-a 的关系

super关键字

  • super代表父类的引用,用于访问父类的属性、方法、构造器, 私有属性的不能访问

  • 当子类中有和父类中的成员重名时,为了访问父类的成员,必须通过super

  • super还可以访问父类的父类中的方法,但如果父类有同名方法,遵循就近原则进行访问

重写(Override)

​ 子类中重写方法时,需要名称、返回类型、参数一样才能叫做重写,否则就是重载。

  • 子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类 ,比如父类的返回类型是Object,子类重写的返回类型可以是String
  • 子类方法不能缩小父类方法的访问权限

重载(Overload)

​ 重载的名称相同,但是返回类型,形参列表,形参个数至少有一个不同,才能构成重载

多态

方法的多态

​ 方法中的重写和重载都体现了多态,就不赘述了。

对象的多态

  • 可以用父类的引用指向子类的对象:Dog类继承了Animal类,我们可以这样声明 Animal animal = new Dog(); 其中Animal是编译类型 Dog是运行类型
  • 编译类型在定义对象时已经确定了,所以不能改变。运行类型是可以变化的:animal = new Cat(); 此时animal的运行类型变成了Cat 但是编译类型还是Animal
  • 调用方法的时候看运行类型,访问成员时看编译类型,访问属性时访问的是编译类型的属性(有几点注意的,看多态的向上转型和向下转型

多态的向上转型和向下转型

  • 向上转型:父类指向了子类的引用(eg:Animal animal = new Dog())
    • 如果子类重写了父类的方法,执行animal.XXX()时,执行的时子类(这里的例子就是Dog类)重写后的方法。
    • 但是animal.XXX()不能访问子类的特有方法
  • 向下转型:当这个对象原本就是子类对象通过向上转型得到的时候才能够进行向下转型
    • 注意的是向下转型只能转回向上转型时对应的子类,不能转会继承相同父类的其他子类
1
2
3
4
5
6
7
8
9
10
Animal animal = new Dog()//向上转型
Dog dog = (Dog)animal // 向下转型

//正确
Animal animal = new Dog()
Dog dog = (Dog)animal

//错误
Animal animal = new Dog()
Cat cat = (Cat)animal//因为原对象是Dog向上转型来的

insteadOf比较符

​ 用于判断对象是否为XX类型或XX类型的子类型,返回True or False。有个小技巧,没用过但我觉得可能有用,在向下转型的时候可以有一个if添加用insteadof判断一下

Java的动态绑定机制

​ 基础:

动态绑定机制:

  • 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
  • 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class A {
public int i = 10;
public int sum(){
return getl()+10;
}

public int sum1(){
return i+10;
}

public int getl(){
return i;
}

public void getA(){
System.out.println("A");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class B extends A{

public int i = 20;
public int sum(){
return i + 20;
}

public int sum1(){
return i+10;
}

public int getl(){
System.out.println("B is "+i);
return i;
}

public void getB(){
System.out.println("B");
}
}

1
2
3
4
5
6
7
8
public class demo1 {
public static void main(String[] args) {
A a = new B();
System.out.println(a.sum());
System.out.println(a.sum1());

}
}

此时输出:

1
2
40
30

​ 我们把子类的sum方法给删除,再次执行,以下是分析:

​ 先看子类是否重写了sum方法,子类没有sum方法,返回去父类找到sum方法,但是return之前需要调用getI()方法,但是该方法在A和B都有,这里调用A的还是B的?通过动态绑定机制得到是调用B中的getI(这里因为是a.sum(),sum方法和a的运行类型绑定)

 Plus: 后面又遇到了动态绑定机制这个名词,还是有点模糊,看了一篇博客,讲的就是我的这个内容。<a href="https://blog.csdn.net/m0_46680603/article/details/121174836">博客地址</a>

精炼一句话:动态绑定机制是绑定运行类型对象方法的,而进入类中后,类中所用的属性通过就近原则进行调用,没有动态绑定机制

多态的应用

  • 多态数组(用父类的类型来创建一个数组,在setting时用子类的构造器初始化数组成员。当要使用子类的方法时,涉及到用条件语句和insteadof来判断类型,从而实现向下转型,再调用子类方法)
  • 多态参数(方法定义的形参类型为父类类型,实参类型允许为子类类型)

Object类

目录

equals方法

  • “==”是一个比较运算符
    • 如果判断基本类型,判断的是值是否相等(判断int num1 = 10,double num2 = 10.0,这两个值时返回true)
    • 如果判断引用类型,判断的是地址是否相等,即判断是不是同一个对象
  • euqls方法是Object类中的方法,只能判断引用类型

hashcode方法

  • 提高具有哈希结构的容器的效率
  • 哈希值不等于地址
  • 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
  • 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
  • 实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)
  • hashcode在需要时也会进行重写(留个坑)

toString方法

​ 官方文档写道:

    返回该对象的字符串表示。通常,toString 方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明但易于读懂的信息表达式。建议所有子类都重写此方法。 
    Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于: 
    getClass().getName() + '@' + Integer.toHexString(hashCode())

​ 默认返回:全类名+@+哈希值的十六进制。 当直接输出一个对象时,toString 方法会被默认地调用,eg: System.out.println(monster) 就会默认调用 monster.toString

finalize方法

​ 在java虚拟机进行垃圾处理的时候会先调用finalize方法,在必要时需要使用finalize方法时,一般会进行重写,使其打印一些东西。在大部分情况下不会使用该方法

面向对象编程(高级)

目录

类变量和类方法

类变量:

  • static变量是同一个类所有对象共享

  • static类变量,在类加载的时候就生成了

  • 类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问

  • 类变量的访问需要遵守相关的访问权限

  • 类变量的定义: 访问修饰符 + static + 数据类型 + 变量名

​ 类变量的访问:类名.类变量名 or 对象名.类变量名

  • 加上static 称为类变量或者静态变量,否则成为实例变量/普通变量/非静态变量

  • 类变量的生命周期是随着类的加载开始,类的消亡而销毁

类方法:

  • 访问修饰符+ static 数据返回类型 方法名(){ }

  • 类方法的调用:类名.类方法名 或者 对象名.类方法名

  • 如果希望不创建对象,也可以调用类中的方法,就应该把该方法做成静态方法,方便调用

  • 类方法中无this参数


理解main方法语法

  • java虚拟机需要调用类的main()方法,所以访问权限必须是public
  • java虚拟机在执行main方法时不必创建对象,所以该方法必须是static
  • 在main方法中,我们可以直接调用main方法所在类的静态方法或静态属性
  • 但是不能直接访问类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员

代码块

​ 代码块又叫初始化块,属于类中的成员,类似于方法,但没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用

  • 基本语法
1
2
3
4
5
[修饰符]{

代码

};
  • 修饰符可有可无,如果要写也只能写static

  • 代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的叫普通代码块/非静态代码块

  • 先调用代码块再调用构造器

细节

  • 静态代码块的作用是对类初始化,它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行一次。
  • 类什么时候被加载:

​ ①创建对象实例时(new)

​ ②创建子类对象实例,父类也会被加载

​ ③使用类的静态成员时

  • 创建一个对象时,再一个类调用的顺序是:
    • 调用静态代码块和静态属性初始化(静态代码块和静态属性初始化调用的优先级一样)
    • 调用普通代码块和普通属性的初始化
    • 调用构造方法
  • 创建一个子类对象时(继承关系)
    • 父类的静态代码块和静态属性
    • 子类的静态代码块和静态属性
    • 父类的普通代码块和普通属性初始化
    • 父类的构造方法
    • 子类的普通代码块和普通属性初始化
    • 子类的构造方法
  • 静态代码块只能直接调用静态成员,普通代码块可以调用任意成员(这里很容易理解,只要看到加载顺序就可以知道了)

单例设计模式

​ What:采取一定的方法保证再整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法

​ 单例设计模式有两种方法:饿汉式和懒汉式

  • 饿汉式实现步骤:
    • 构造器私有化
    • 类的内部创建对象
    • 向外暴露一个静态的公共方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class single {
public static void main(String[] args) {
GirlFriend instance = GirlFriend.getInstance();
}
}

class GirlFriend{
private String name;
private static GirlFriend gf = new GirlFriend("小红红");

private GirlFriend(String name) {
this.name = name;
}

public static GirlFriend getInstance(){
return gf;
}
  • 懒汉式实现步骤:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class single {
public static void main(String[] args) {
GirlFriend instance = GirlFriend.getInstance();
}
}

class GirlFriend{
private String name;
private static GirlFriend gf

private GirlFriend(String name) {
this.name = name;
}

public static GirlFriend getInstance(){
if(gf == null){
gf = new GirlFriend("小红红");
}
return gf;
}

饿汉式在类加载的时候就创建了对象,懒汉式在使用时才创建对象

饿汉式不存在线程安全的问题,懒汉式存在线程安全的问题


final关键字

使用情况:

  • 不希望类被继承时,可以用final修饰
  • 不希望父类的某个方法被子类重写时
  • 不希望某个属性的值被修改
  • 不希望某个局部变量被修改

final关键字细节

  • final修饰的属性在定义的时候必须初始化,赋值可以在如下位置

    • 定义时
    • 在构造器中
    • 在代码块中
  • 如果final修饰的属性是静态的,则初始化的位置只能是:

    • 在定义时

    • 在静态代码块

      这里可以看出静态的东西之和类有关,不能和对象扯上关系

  • final不能被继承,但是可以实例化对象

  • 如果一个类是final类,就没有必要把方法修饰成final

  • final不能修饰构造器

  • final和static往往搭配使用效率更高,当调用一个类中的final static变量时,不会调用static代码块


抽象类

​ 当父类的某些方法需要声明,但又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类

  • 当一个类中存在抽象方法时,需要将类声明为abstract类
  • 一般来说,抽象类会被继承,由子类来实现抽象方法
  • 抽象类不能被实例化,即不能被new
  • 抽象类不一定包含抽象方法
  • abstract只能修饰类和方法
  • 抽象类只有子类继承了父类中的方法,并且对其中的所有抽象方法进行了重写。该子类才不是抽象类,只要不是重写当中的所有抽象方法,那么这个子类还是抽象类
  • abstract不能和final、private、static共存,因为这些关键字和重写相违背
  • 抽象模板模式

接口

接口快速入门

​ I开头的是接口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//UsbInterface内容:
public interface UsbInterface {
public void start();
public void stop();
}


//phone内容:
public class phone implements UsbInterface{
@Override
public void start() {
System.out.println("手机开始工作");
}

@Override
public void stop() {
System.out.println("手机停止工作");
}
}

//Camera内容:
public class Camera implements UsbInterface {
@Override
public void start() {
System.out.println("相机开始工作");
}

@Override
public void stop() {
System.out.println("相机停止工作");
}
}

//Computer内容:
public class Computer {
public void work(UsbInterface usbInterface){
usbInterface.start();
usbInterface.stop();
}
}

//Interface01内容
public class Interface01 {
public static void main(String[] args) {
Camera camera = new Camera();
phone phone = new phone();
Computer computer =new Computer();

computer.work(phone);
}
}

​ Interface文件定义了接口对象需要拥有的参数,接口对象必须实现(重写)这些参数

​ 调用接口的对象可以选择性调用接口的方法

基本介绍:

  • 语法:

    • interface 接口名{
      //属性
      //方法
      }
      
      class 类名 implements 接口{
          自己属性;
          自己方法;
          //必须实现接口的所有方法
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78

      - 在JDK7.0之前接口的所有方法都没有方法体,都是抽象方法
      - Jdk8.0后接口可以有静态方法,默认方法。
      - 在接口中可以省略abstract关键字



      ### **接口注意事项:**

      - 接口不能被实例化
      - 接口中的所有方法是public
      - 抽象类实现接口时可以不实现接口的抽象方法
      - 一个类可以实现多个接口
      - 接口中的属性只能是public static final
      - 接口中的属性访问:接口名.属性名
      - 接口不能继承其他类,但可以继承多个别的接口



      ### **接口和继承**

      继承的价值主要在于解决代码的复用性和可维护性

      接口的价值主要在于设计好的各种规范



      ### 接口的多态属性

      ​ 接口引用可以指向实现了接口的类的对象



      ### 接口多态传递

      接口可以继承其他接口,当一个类实现一个接口时,如果这个接口继承了其他接口,该类也要实现那个接口的方法。



      ## 内部类

      ​ 一个类的内部又嵌套了另一个类结构,被嵌套的类叫做内部类

      ​ 类的五大成员:属性、方法、构造器、代码块、内部类



      ### 内部类的分类

      - 定义在外部局部位置上(比如方法类):
      - 局部内部类
      - 匿名内部类(没有类名)
      - 定义在外部类的成员位置上
      - 成员内部类(没有static修饰)
      - 静态内部类(使用static修饰)

      ### 局部内部类

      - 局部内部类可以访问外部类的所有成员和方法
      - 不能添加访问修饰符,局部内部类的本质也是类,但是是局部变量的一个形式
      - 其作用域在定义它的方法内或者代码块中
      - 局部内部类可以直接访问外部类的成员,外部类在方法中可以创建内部类的对象实例,再进行调用方法
      - 外部其他类不能访问局部内部类
      - 如果外部类金和局部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员需要使用(外部类名.this.成员)
      - 再main函数中谁调用了局部类所在的方法,外部类名.this就指向谁



      ### 匿名内部类

      ​ 匿名内部类是一个类,还是一个对象

      基本语法

      ```java
      new 类或接口(参数列表){
      类体
      };

外部其他类不能访问匿名内部类

成员内部类

  • 成员内部类可以直接访问外部类成员

  • 外部类要访问成员内部类需要创建对象再访问

  • 外部其他类访问成员内部类

    • 用外部的对象new一个内部类
    • 在外部类中编写一个方法返回new xxxx对象
  • 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)

静态内部类

外部其他类使用静态内部类:

  • new 外部类名.静态内部类名
  • 编写方法返回静态内部类的实例

枚举和注解

自定义枚举类

  • 构造器私有化
  • 本类内部创建对象
  • 对外暴露对象
  • 可以提供get方法,但是不提供set方法

枚举类

  • 如何定义枚举类型

1
enum Size{SMALL, MEDIUM, LARGE, EXTRA_LARGE }

这个声明定义的是一个类,有四个实例。在比较两个枚举类型的时候不需要调用equals,直接使用“==”。可以给枚举类型增加构造器、方法和字段

1
2
3
4
5
6
7
8
enum Size{
SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");
private String abbreviation;
//构造器
private Size(String abbreviation){this.abbreviation = abbreviation;}
//方法
public String getAbbreviation(){return abbreviation;}
}
  • 枚举的构造器总是私有的,前面的private可以省略

​ 通过反编译可以得到如下结论:enum是继承Enum类的,其中还有values和valueOf两种方法

  • 所有的枚举类型都是Enum类的子类,其中有一个toString的方法,这个方法会返回枚举常量名
  • toString的逆方法是静态方法valueOf
1
Size s = Enum.valueOf(Size.class, "SMALL")
  • 每个枚举类型都有一个静态的values方法,它将返回一个包含全部枚举对象的数组
1
Size[] values = Size.values();
  • ordinal方法返回enum声明中枚举常量的位置,从0开始计数
  • name方法返回枚举对象的名称

常用实例:

  • toString
  • name
  • ordinal
  • values
  • valueOf
  • compareTo

enum实现接口

enum不能继承其他类,因为enum已经隐式继承了Enum类,但可以实现接口


注解

注解也被称为元数据

三个基本的Annotation:

@Override:限定某个方法,是重写父类方法,改注解只能用于方法

@Deprecated:用于某个程序元素以及过时

@SuppressWarnings:抑制编译器警告

@Target是修饰注解的注解,称为元注解

​ Retention:指定注解的作用范围,三种 SOURCE,CLASS,RUNTIME

​ Target: 指定注解可以在哪些地方使用

​ Documented:指定该注解释放会在javadoc体现

​ Inherited 子类会继承父类注解


异常

异常体系图

异常处理细节

  • 对于编译异常,程序中必须处理,比如try-catch 或者 throws
    • 如果一个类中有两个静态方法A、B。若B抛出了一个编译异常,A调用了B方法,A中必须处理调用的B方法(try-catch 或者 throws)
  • 对于运行时异常,程序中如果没有处理,默认是throws处理,一直向上抛出异常

自定义异常

​ 步骤:

  • 自定义异常类名,继承Exception或RuntimeException

  • public class Throwsdetail {
        public static void main(String[] args) {
            
            int age = 190;
            if (!(age >=18 && age<=120)){
                throw new AgeException("年龄需要在18~120之间");
            }
    
            System.out.println("Correct!!!");
        }
    }
    
    
    class AgeException extends RuntimeException{
        public AgeException(String message) {
            super(message);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43



    ## throw和throws的区别

    ​ throws是一种方式 在方法声明处,后面加上异常类型

    ​ thorw 是手动生成异常对象的关键字,在方法体中,后面加上异常对象

    ------



    # 常用类



    ![](./Java基础学习笔记/image-20220216152348698.jpg)

    ## 装箱和拆箱

    ​ jdk5前的手动装箱和拆箱方式,装箱:基本类型->包装类型,反之,拆箱

    ​ jdk5以后自动装箱和拆箱方法

    ​ 自动装箱底层调用valueOf方法,比如Integer.valueOf()

    ```java
    //手动
    int n1 = 100;
    Integer integer = new Integer(n1);
    Integer integer1 = Integer.valueOf(n1);

    int i = integer.intValue();
    System.out.println(i);


    //自动
    int n2 = 200;
    Integer integer2 = n2;

    int n3 = integer2;
    System.out.println(n3);

包装类

​ 包装类转String:

1
2
3
4
5
6
7
Integer i = 100;
//method one
String str1 = i + "";
//method two
String str2 = i.toString();
//method three
String str3 = String.valueOf(i);

String转包装类

1
2
3
4
5
String str4 = "12345";

Integer i1 = Integer.parseInt(str4);

Integer i3 = new Integer(str4);

常用方法

1
2
3
4
5
6
7
8
9
10
Integer.MIN_VALUE
Integer.MAX_VALUE

Character.isDigit('a')//判断是不是数字
Character.isLetter('a')
Character.isUpperCase('a')
Character.isLowerCase('a')
Character.isWhitespace('a')//判断是否是空格
Character.toUpperCase('a')//转换大写
Character.toLowerCase('a')

注意Integer的valueOf源码

1
2
3
4
5
6
7
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

//IntegerCache.low = -128,IntegerCache.high = 172

所以在

1
2
3
Integer m = 128;
Integer n = 128;
system.out.println(m==n)//False

如果在low和high的范围内,jvm在加载的时候就创建了一个cache数组里面包含着-128~127数字

String类

1
2
3
4
String s1 = new String();
String s2 = new String(String original);
String s3 = new String(char[] a);
String s4 = new String(Char[] a, int startIndex, int count)
  • String类有一个value数组,用于存放字符串内容,并且这个属性是final,其指向的地址不可以修改,不是内容不可以修改(这需要理解一些Java虚拟机的内容)
    • final修饰引用类型的时候修饰的是final地址,修饰基本数据类型的时候final的是内容也就是基本数据类型的值
  • String 类实现了接口Serializable, String可以串行化
  • String 类实现了接口Comparable

String 对象的创建方式

1
2
String name1 = "Tom";//m1
String name2 = new String("Tom");//m2
  • m1方式创建对象:对象先在常量池中查找 “Tom” 这个字符串,如果有这个字符串就直接指向这个字符串的地址,如果没有就创建字符串再指向。

​ m2方式创建对象:现在堆中创建空间,这个空间有数组value的属性,value就在常量池中查找是 否存在“Tom” 字符串,如果有就直接指向,没有则创建再指向。

  • name1指向的是常量池中的“Tom”字符串的地址,name2指向的是堆中属性的地址
1
2
String s1 = "Hello";
s1 = "Haha"

这个语句创建了两个字符串变量,先指向常量池中的hello 再指向常量池中的haha。注意是value才不能修改地址

-

1
2
3
String a = "Hello";
String b = "abc";
String c = a+b;

c 先调用了StringBuilder类:StringBuilder sb = StringBuilder()

再执行sb.append(“Hello”) 和sb.append(“abc”) ,将这两个字符串加到value中

最后将c 指向 sb.toString(),c 其实是指向堆中的。

String类的常见方法

  • String 类保存字符串常量的。每次更新都需要重新开辟空间。Java设计者提供了StringBuilder 和 StringBuffer增强String功能

  • equals区分大小写,判断内容是否相等

  • equalsIgnoreCase,不区分大小写,判断是否相等

  • length获取字符的个数

  • indexOf获取字符在字符串中第一次出现的索引从0开始,找不到就返回-1

  • lastIndexOf从后往前找

  • substring截取指定范围的字串

  • trim去前后空格

  • charAt获取某索引处的字符

StringBuffer类

StringBuffer是可变长度的

StringBuffer类继承了AbstractStringBuilder,AbstractStringBuilder中有一个value数组,不是final修饰的,所以StringBuffer保存的字符串是在堆中的

StringBuffer储存空间有点像mallco,但是这个mallco是动态的

StringBuffer的构造器

  • StringBuffer() : 初始容量一个16个字符的缓冲区
  • StringBuffer() : 构造一个字符串缓冲区,它包含与指定的CharSequence相同的字符
  • StringBuffer(int capacity) : 构造一个不带字符,但具有初始容量的缓冲区,初始容量为传入的值
  • StringBuffer(String str) :构造一个字符串缓冲区,并将其初始化为指定的字符串内容,容量大小为 字符串长度+16 不同JDK好像不一样?

StringBuffer和String进行转换

String ->StringBuffer

1
2
3
4
5
6
String name ="Jack";
StringBuffer name = new StringBuffer(str);//m1

String name = "Tom";
StringBuffer str = new StringBuffer();
str.append(name);//m2

StringBuffer ->String

1
2
3
4
StringBuffer stringbuffer3 = new StringBuffer("Tom");
String str = stringbuffer3.toString();//m1

String s1 = new String(stringbuffer3)//m2

StringBuffer常用方法

1
2
3
4
5
6
7
StringBuffer str = new StringBuffer("Tom");
str.append;
str.delete(start,end)
str.replace(start,end,string);
str.indexOf();
str.insert(int loc,"str");
str.length;

StringBuilder类

一个可变的字符序列,此类提供了与StringBuffer兼容的API,但不保证同步(StringBuilder不是线程安全)。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能优先采用该类,因为比较快。

StringBuilder常用的就是append和insert

String使用注意

String s = “a”;

s +=”b”

原来的a对象已经丢弃,又产生了一个“ab”对象,如果反复执行s +=”b”,就会造成大量副本字符串留存在内存中降低效率,会极大地降低程序的性能

Math类

Math类都是静态方法,直接通过类名进行方法的使用

查API文档

Arrays类

  • Arrays.toString : 返回数组的字符串类型, 显示数组

  • Arrays.sort:排序,两种自然排序和定制排序,对传入的数组本身进行改变(数组是引用类型,会直接影响实参)

    • 也可以传入Comparator接口实现定制排序

    • Comparator中的两个参数,o1储存的是较小的数,o2储存较大的数

    • Comparator

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      Book[] books = new Book[4];
      books[0] = new Book("红楼梦",100);
      books[1] = new Book("金瓶梅",90);
      books[2] = new Book("青年文摘",5);
      books[3] = new Book("Java从入门到放弃",300);

      Arrays.sort(books, new Comparator() {
      @Override
      public int compare(Object o1, Object o2) {
      Book book1 = (Book) o1;
      Book book2 = (Book) o2;
      double price = book2.getPrice() - book1.getPrice();
      if(price>0) return -1;
      else if (price<0) return 1;
      else return 0;
      }
      });

      System.out.println(Arrays.toString(books));
      //output:[Book{name='青年文摘', price=5}, Book{name='金瓶梅', price=90}, Book{name='红楼梦', price=100}, Book{name='Java从入门到放弃', price=300}]
    • public static void sort(int[] a, int fromIndex, int toIndex)
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64

      - Arrays.binarySearch(arr,123):二分查找,必须是有序的,不存在就返回-(low+1)
      - Arrays.copyOf(arr, arr.length) 数组元素的复制, 需要数组接收
      - Arrays.fill(arr, syb) 数组元素的填充, 用syb填充arr中的所有元素
      - Arrays.equals(arr,arr1) 两个数组是否相等
      - Arrays.asList: 转换成list, List\<Interger> asList = Arrays.asList(2,3,4,5,6,1)

      ## System类

      - exit 退出当前程序
      - arraycopy
      - currentTimeMillens: 返回当前时间距离1970-1-1的毫秒数
      - System.gc()

      ## BigInteger和BigDecimal类

      BigInteger保存比较大的整型

      BigDecimal保存精度更高的浮点型

      方法:add、subtract、multiply、divide



      BigDecimal用除法可能会抛出异常,因为除出来可能是无限循环小数,在调用divide时,再传入BigDecimal.ROUND_CEILING就行

      ## 日期类

      查文档API

      ------



      # 集合

      - 提供了add、remove、set、get等方法
      - 可以动态保存任意多个对象

      ps:集合里面还需要去读它的源码。。。

      ## 集合体系图

      ![image-20220223212408696](./Java基础学习笔记/image-20220223212408696.png)

      ![image-20220223212545854](./Java基础学习笔记/image-20220223212545854.png)

      Collection接口继承了Iterable接口,Collection的子接口有List和Set

      ## Collection接口和常用方法

      Collection接口实现类的特点

      public interface Collection\<E> extends Iterable\<E>

      - collection实现子类可以存放多个元素,每个元素可以是Object
      - 有些Collection的实现类,可以存放重复的元素,有些不可以
      - 有些Collection的实现类,有些是有序的(List),有些不是有序的(Set)
      - Collection接口没有直接的实现子类,是通过子接口Set和List实现的



      - Collection接口的方法

add:添加单个元素
remove:删除指定元素
contains:查找元素是否存在
size:获取元素个数
isEmpty:判断是否为空
clear:清空
addAll:添加多个元素
containsAll:查找多个元素是否都存在
removeAll:删除多个元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57



- Collection接口遍历元素方式 - 使用Interator(迭代器)

- Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
- 所有实现了Collection接口的集合类都有一个iterator()方法,可以返回一个实现了Iterator接口的对象,即可以返回一个迭代器
- Iterator用于遍历集合,本身并不存放数据

- 使用方法

- 声明一个迭代器,让它指向要迭代的对象的迭代器方法
- 编写While循环 条件用迭代器的hasnext方法检测是否还有下一个元素



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

public class Collection_ {
public static void main(String[] args) {

Collection col = new ArrayList();
col.add(new Book("三国演义","罗贯中",10));
col.add(new Book("小李飞刀","古龙",5.1));
col.add(new Book("红楼梦","曹雪芹",34.6));

Iterator iterator = col.iterator();//第一步
while (iterator.hasNext()){//第二步
Object next = iterator.next();
System.out.println(next);
}
}

}

class Book{
private final String name;
private final double price;
private final String author;
public Book(String name,String author, double price) {
this.name = name;
this.price = price;
this.author = author;
}

@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", price=" + price +
", author='" + author + '\'' +
'}';
}
}

List接口相关方法

List接口是有序的,元素可重复

List的常用方法和Collection差不多,有一个set方法其声明为:Object set(int index, Object element)

相当于替换

subList(int fromIndex, int toIndex) 返回从fromIndex到toIndex的子集合

ArrayList基本等同于Vector除了ArrayList是线程不安全(执行效率高)

多线程的时候考虑用Vector

ArrayList接口底层接口

  • 在其中有一个Object数组elementData存放放入的元素

  • 如果创建对象时,使用的是无参构造器,初始的elementData容量为0,第一次添加,则扩容elementData为10,如需再次扩容,则为1.5倍

Vector

如果无参默认10 满后按两倍扩容

LinkedList

LinkedList底层实现了双向链表和双端队列特点

可以添加任意元素,元素可重复

线程不安全,没有实现同步

Set接口

set接口也是Collection的子接口,常用方法和Collection一样

set接口中的数据是无序的且不能重复,不能用索引的方式遍历set接口的对象,其中最多包含一个null

set输出是出现频率最高的在前面出现

HashSet

  • HashSet实现了Set接口
  • HashSet实际上是HashMap

泛型

泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//泛型类
public class Pair<T> {
private T first;
private T second;

public Pair(){
first = null;
second = null;
}

public Pair(T first, T second){
this.first = first;
this.second = second;
}

public T getFirst(){
return first;
}

public T getSecond(){
return second;
}
public void setFirst(T newValue){
first = newValue;
}
public void setSecond(T newValue){
second = newValue;
}
}

在声明类的时候用<>声明泛型的类型参数,在类中可用类型参数,之后会进行擦除

泛型方法

1
2
3
4
5
class ArrayAlg{
public static <S extends Comparable> S getMiddle(S... a){
return a[a.length/2];
}
}

其中用了extends限定了传入变量的类型,必须是实现了Comparable接口的类

1
<T extends BoundingType>

T 应该是限定类型的子类型,T和限定类型可以是类或者接口,有多个限定类型用 & 分割

泛型代码和虚拟机

1
无论何时定义一个泛型类型,都会自动提供一个相应的原始类型。这个原始类型的名字就是去掉类型参数后的泛型类型名。类型变量会被擦除,并替换为其限定类型(对于无限顶的变量则替换为Object)
  • 泛型在JAVA1.5 引入,在进入JVM前,泛型被擦除
  • 对于最上方的泛型类的例子,由于T 没有用extends限定所以会替换为Object
  • 对于标签接口会放在限定列表的末尾,如果放在第一位,虽会替换为标签变量,但编译器在必要时要向另一个变量插入强制类型转换
  • 桥方法:这个地方书上写的有点晦涩,会重开一篇来写。

限制与局限性

  • 不能用基本类型实例化类型参数:不能用基本类型代替类型参数,没有Pair(double) 只有Pair(Double)
  • 运行时类型查询只适用于原始类型:
1
2
if (a instanceof Pair<String>)
if (a instanceof Pair<T>)

这俩都会报错

  • 不能创建参数化类型的数组:
1
var table = new Pair<String>[10]; //错误

如果需要收集参数化类型对象,可以使用ArrayList

我的实验代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.ArrayList;
import java.util.Iterator;

@SuppressWarnings("all")
public class demo {
public static void main(String[] args) {
ArrayList test = new ArrayList<Pair<String>>();
test.add(new Pair<String>("Johnny","Yu"));
Iterator iterator = test.iterator();
while(iterator.hasNext()){
Pair<String> t = (Pair<String>) iterator.next();
System.out.println(t.getFirst());
}
}
}
  • Varargs警告
1
2
3
4
5
6
7
8
public static <T> void addAll(Collection<T> coll, T... ts){
for(T t: ts) coll.add(t);
}

Collection<Pair<String>> table = ...;
Pair<String> pair1 = ...;
Pair<String> pair2 = ...;
addAll(table , pair1, pair2);

这里会创建Pair<String>数组,违背了前面的规则,会得到一个警告,可以用@SuppressWarnings(“unchecked”)抑制警告,在Java7 中还可以用@SafeVarargs直接注解addAll方法。 这个注解只能用于声明为static、final、(java9中)private的构造器和方法

  • 不能实例化类型变量:不能直接new T(),在Java8之后的解决方法是让调用者提供一个构造器表达式: Pair<String> p = Pair.makePair(String::new); //设计到了函数式接口之后学Java8再回来看
  • 不能构造泛型数组:解决方法还是在实例化的时候提供一个数组构造表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//以下会报错
public static <T extends Comparable> T[] minmax(T... a){
var ret = new Comparable[2];
...
return (T[]) ret;
}

public static void main(String[] args){
String[] name = ArrayAlg.minmax("Tom","Dick","Harry");
}

//正确
public static <T extends Comparable> T[] minmax(IntFunction<T[]> constr,T... a){
T[] ret =constr.apply(2);
...
return (T[]) ret;
}

public static void main(String[] args){
String[] name = ArrayAlg.minmax(String[]::new, "Tom","Dick","Harry");
}

  • 泛型类的静态上下文中类型变量无效:不能在静态字段或方法中引用类型变量

  • 不能抛出或捕获泛型类的实例:既不能抛出也不能捕获泛型类的对象。实际上,泛型类扩展Throwable甚至都是不合法的

  • 可以取消对检查型异常的检查

  • 注意擦除后的冲突:当泛型类型被擦除后,不允许创建引发冲突的条件。

    为了支持擦除转换增加了一个限制:倘若两个接口类型是同一接口的不同参数化,一个类或类型变量就不能同时作为这两个接口类的子类:

    1
    2
    3
    4
    5
    6
    7
    //非法
    class Emplyee implements Comparable<Employee>{...}
    class Manger extends Employee implements Comparable<Manager>{....}
    /*
    * Manager会实现Comparable<Employee> 和 Comparable<Manager> 这是同一接口的不同参数
    *化,这是由于桥方法冲突导致的
    */

多线程及并发(基础)

创建线程的两种方式

  • 继承Thread类,重写run方法
  • 实现Runnable接口,重写run方法(底层是设计模式中的代理模式)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//第一种方法
public class Thread01 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.start();

}
}

class Dog extends Thread{
int count = 0;
@Override
public void run() {
while(true){
System.out.println("汪汪"+count++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

//第二种方法
public class Thread01 {
public static void main(String[] args) {
Dog dog = new Dog();
Thread thread = new Thread(dog);
thread.start();

}
}

class Dog implements Runnable{
int count = 0;
@Override
public void run() {
while(true){
System.out.println("汪汪"+count++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

买票窗口

使用买票窗口来模拟多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Thread01 {
public static void main(String[] args) {
Sellticket sellticket1 = new Sellticket();
Sellticket sellticket2 = new Sellticket();
Sellticket sellticket3 = new Sellticket();
Thread thread1 = new Thread(sellticket1);
Thread thread2 = new Thread(sellticket2);
Thread thread3 = new Thread(sellticket3);
thread1.start();
thread2.start();
thread3.start();
// sellticket1.start();
// sellticket2.start();
// sellticket3.start();

}
}

class Sellticket implements Runnable {
private static int ticketnum = 100;

@Override
public void run() {
while(true){
if(ticketnum <0){
System.out.println("售票结束");
break;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"剩余票数"+ticketnum--);
}
}
}

有一个很奇怪的现象:我用的是static 来对多个线程共享一个变量,使这三个线程所卖出去的票数的总数为100

但是之后会出现负数的情况

01

这是因为线程数据没有同步的关系。一个线程对数据进行了操作,但是没有同步到主存中,导致另一个线程得到的数据还是操作之前的数据,对于这个例子来说,得到的数据是大于0的所有通过了判断,没有退出,但是进入到输出语句时,第一个线程已经把数据刷到主存中了,得到的ticketnum就是0,这一次再执行一次减一操作就会出现负数。解决办法稍后带来^^