面向对象

4/8/2022 JavaSE

# 面向对象

一个类中可以含有四个内容:「属性」「方法」「构造方法」「块」

# 属性

形式

// [] 括号里的内容不是必填的
权限修饰符 [特征修饰符] 数据类型 属性名字 [=]// 
public String name;

# 方法

特殊的方法: main 方法 它是专属于虚拟机用于启动应用程序所存在的一个特殊的静态方法

形式

// [] 括号里的内容不是必填的
权限修饰符 [特征修饰符] 返回值类型 方法名字 (参数列表) [抛出异常] [{
	方法体
}]
  
权限修饰符 返回值类型 方法名字 (参数列表){
	方法体
}

// 
public void sayHello(){
  System.out.println("hello world");
}

方法重载:具有相同的方法名,不同的参数列表

JDK1.5 版本之后出现了一个新的语法:动态参数列表。动态参数列表类似于 javascript 中的剩余参数

// x 本质是一个数组
public void test(int... x){
  for(int a: x){
    System.out.println(a)
  }
}

# 构造器

形式

// [] 括号里的内容不是必填的
权限修饰符 方法名字即为类名 (参数列表) [抛出异常] [{
	方法体;
}]

每个类中默认都有一个构造方法,若没有写构造方法,默认会提供一个无参的构造方法;若你自己写了构造方法,则会覆盖默认的构造方法

构造方法也存在方法重载,我们把它称之为构造方法重载

构造方法的主要功能就是进行初始化

# 程序块

  1. 写法:一对大括号包裹 {}
// 比如
public class Demo {
  // 程序块
  {
    System.out.println("我是程序块");
  }
  
  public static void main(String[] args) {
    Demo d = new Demo();
  }
}
  1. 执行时机:每一次我们调用构造方法之前,系统会帮我们自动地调用一次程序块

  2. 静态代码块:在程序块前添加 static 特征修饰符

# this关键字

用来代替某一个对象

  1. 在一个构造方法中调用另一个构造方法,通过 this() 形式,并且要放在第一行
  2. 在构造方法(或普通方法)中调用属性或方法,通过 this.属性名this.方法名() 形式

# 修饰符

# 权限修饰符

在 java 中,一个类中的哪些字段可以被哪些访问修饰符所修饰呢?首先,java 中常见的字段有**「类本身」「嵌套类」「构造器」「方法」「属性」**。其中有一个字段很特殊,就是 **Class(类本身)**只能应用 defaultpublic 这两个访问修饰符,其它字段都可以被所有访问修饰符所修饰。同时 interface(接口)也只能应用 defaultpublic 这两个访问修饰符,如下表格

private default protected public
Class No Yes No Yes
Nested Class Yes Yes Yes Yes
Constructor Yes Yes Yes Yes
Method Yes Yes Yes Yes
Field Yes Yes Yes Yes

这四个访问修饰符在的应用权限如下

同类 同包 子类 不同包
public
protected
default
private

# 特征修饰符

# final

  1. 什么情况下要使用 final 关键字

    1. 修饰某个类时,表示该类不可以被继承
    2. 修饰某个方法时,表示该方法不可以被重写
    3. 修饰类中的某个属性时,表示该属性不可以被修改
    4. 修饰局部变量时,表示该局部变量不可以被修改
      1. 如果修饰的变量是基本数据类型,则变量的值不可更改
      2. 如果修饰的变量是引用数据类型,则变量的地址(内存地址)不可更改,但地址空间内的数据可以更改
  2. final 关键字使用的一些小细节

    1. final 修饰的属性也称为常量,一般用大写字母表示

    2. final 修饰的属性在定义时必须初始化,并且以后不能再修改。初始化的位置可以有

      1. 在属性定义时,如:public final double PI = 3.14;
      2. 在构造函数中
      3. 在代码块中
    3. 如果 final 修饰的属性是静态的,则初始化的位置只能是

      1. 定义时
      2. 静态代码块中
    4. 如果类不是 final 类,但是含有 final 方法,虽然该方法不能被重写,但是可以被继承

    5. 一般来说,如果一个类已经是 final 类了,就没有必要将该 final 类中的方法也修饰成 final

    6. final 不能修饰构造函数

    7. final 和 static 往往搭配使用,效率更高,因为不会导致类加载

    8. Integer 等包装类、String、Math 等多数工具类都是 final 类

# static

  1. 可以修饰什么

    1. 修饰属性
    2. 修饰方法
    3. 修饰块
    4. 修饰类(内部类)
  2. 修饰的内容有什么特点

    1. 被 static 修饰的内容在类加载时就初始化了,创建的时期非常早
    2. 静态元素存储在静态元素区中,每一个类都有一个自己的这样的区域
    3. 静态元素只加载一次,被类对象和类本身共享

# abstract

# native

# transient

# synchronnized

# volatile

# 常用的工具类

# Object类

# ==运算符

  1. 既可以判断基本数据类型,也可以判断引用数据类型
  2. 当判断基本数据类型时,比较的是值是否相等,如:int i = 10; double d = 10.0
  3. 当判断引用数据类型时,比较的是对象的**地址(内存地址)**是否相同

# equals方法

// Object 中的 equals 方法
public boolean equals(Object obj) { // 判断地址是否相同
    return (this == obj);
}
  1. 由于 equals 是 Object 类中的方法,所以它只能应用于引用数据类型之间的判断
  2. 默认判断地址是否相同,Object 的子类中往往重写该方法用于判断内容是否相同,比如 Integer、String 类等
  3. 当然,你也可以在自己的类中重写 equals 方法

# equals与==面试题

// 代码题一
Person person1 = new Person();
person1.name = "lkj";
Person person2 = new Person();
person2.name = "lkj";

System.out.println(person1 == person2); // false
System.out.println(person1.name.equals(person2.name)); // true
System.out.println(person1.equals(person2)); // false

String str1 = new String("henry");
String str2 = new String("henry");

System.out.println(str1.equals(str2));// true
System.out.println(str1 == str2);// false

class Person{
  public String name;
}

// 代码题二
int it = 65;
float fl = 65.0f;
System.out.println("65 和 65.0f 是否相等?" + (it == fl));// T
char ch1 = 'A';
char ch2 = 12;
System.out.println("65 和 'A' 是否相等?" + (it==ch1));// T
System.out.println("12 和 ch2 是否相等?" + (12 == ch2)); // T

String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1 和 str2 是否相等?" + (str1 == str2));// F

System.out.println("str1 是否 equals str2?" + str1.equals(str2));// T

# hashCode方法

@HotSpotIntrinsicCandidate
public native int hashCode();

它是一个 native 方法(本地方法),native 方法会交由 java 虚拟机实现(调用时放入 本地方法栈 中),开发人员不需要关心

  1. 提高具有哈希结构的容器效率
  2. 两个引用如果指向的是同一个对象,则哈希值肯定相同
  3. 两个引用如果指向的是不同对象,则哈希值一般不同(因为有可能发生哈希碰撞导致哈希值相同,但这种概率极小,取决于设计的哈希算法)
  4. 哈希值主要由**地址号(内存地址)**来决定,但不能完全等同于地址号

# toString方法

// getClass().getName() 表示获取全类名(包名+类名)
// Integer.toHexString(hashCode()) 将该类的 hashCode 值转为十六进制表示
public String toString() {
  return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
  1. 默认返回 全类名+@+哈希值的十六进制
  2. 子类往往会重写 toString 方法,用于返回对象的属性信息
  3. 当直接输出一个对象时,toString 方法会被默认的调用。也就是说 System.out.println(obj); 等价于 System.out.println(obj.toString());

# 字符串相关的类

# String

String: 不可变的字符序列。底层使用 char[] 存储 StringBuffer: 可变(说明有扩容机制)的字符序列。线程安全,效率低,底层使用 char[] 存储 StringBuilder: 可变(说明有扩容机制)的字符序列。JDK5.0 新增的,线程不安全,效率高,底层使用 char[] 存储

理解 String,什么是「不可变性」

  1. String 类型声明为 final,表示不可继承
  2. String 实现了 Serializable 接口、Comparable 接口,表示字符串是支持序列化的、可以比较大小的
  3. String value 声明为 privete final byte[], 表示字符串长度不可变
  4. String 代表不可变的字符序列。简称:不可变性。体现在
    1. 当对字符串重新赋值时,需要给另外的内存区域赋值,再将此区域中的值赋值给该变量,不能使用原有内存中的 value 进行赋值
    2. 当对现有字符串进行拼接操作时,也需要给另外的内存区域赋值,再将此区域中的值赋值给该变量
    3. 当调用 String 的 replace 方法修改指定字符或字符串时,也需要重写指定另外的内存区域再赋值

String 实例化方式

  1. 通过字面量形式
// 此时的变量 s1、s2 的数据 "hello" 保存在方法区的字符串常量池中,并且只有一份
String s1 = "hello";
String s2 = "hello";
  1. 通过 new 的形式
// 此时的变量 s1、s2 的数据 "hello" 保存在堆内存中,并且是两份不同的数据,尽管都是字符串 "hello"
// 因为每次通过 new 创建对象时都会在堆内存中开辟一个新的空间,所以 s1、s2 变量保存的是两个不同的堆内存空间地址
String s1 = new String("hello");
String s2 = new String("hello");

面试题:String s = new String();方式创建对象,在内存中创建了几个对象?

答:两个:一个是堆内存中的 new 结构,另一个是 char[] 对应的常量池中的数据

字符串拼接

  1. 常量与常量的拼接结果在常量池中,且常量池中不会存在内容相同的常量
  2. 只要其中有一个是变量,结果就在堆中
  3. 如果拼接的结果调用 intern() 方法,其返回值就在常量池中
String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
String s4 = "hello" + "world";
String s5 = s1 + "world";
String s6 = "hello" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4); // true
System.out.println(s3 == s5); // false
System.out.println(s3 == s6); // false
System.out.println(s3 == s7); // false
System.out.println(s5 == s6); // false
System.out.println(s6 == s7); // false
String s8 = s7.intern();
System.out.println(s8 == s3); // true

一道面试题

public class App {
  String s = new String("te st");
  char[] c = {'t', 'e', 's', 't'};

  public void change(String str, char[] c){
    str = "exit";
    c[0] = 'b';
  }
  public static void main(String[] args) {
    App app = new App();
    app.change(app.s, app.c);
    System.out.println(app.s);// test
    System.out.println(app.c);// best
  }
}

String 与其它数据类型间的转换

  1. String 与数字
String s = "123";
int i = Integer.parseInt(s);// 将字符串转换为数字
String s2 = String.valueOf(i);// 将数字转换为字符串
  1. String 与 char[]
String s = "hello";
char[] chars = s.toCharArray();
String s1 = new String(chars);
  1. String 与 byte[]
String s = "hello";
byte[] bytes = s.getBytes(); // 使用默认的字符集进行转换
String s1 = new String(bytes);

# StringBuffer

# StringBuilder

# 日期时间API

  1. java.util.Date
  2. java.sql.Date
  3. System.currentTimeMillis
  4. Calendar

# JDK 8 之前

# JDK 8 之后

# Java比较器

# System类

# Math类

# BigInteger与BigDecimal

# package

Java 中的包(package)本质上就是磁盘上的目录,只不过在 Java 中需要在项目(app 或 API)的 src 目录下建立相关的子目录(package),这些子目录才会被 Java 视为包

Last Updated: 4/8/2022, 7:03:16 PM