面向对象
# 面向对象
一个类中可以含有四个内容:「属性」、「方法」、「构造方法」、「块」
# 属性
形式
// [] 括号里的内容不是必填的
权限修饰符 [特征修饰符] 数据类型 属性名字 [= 值];
//
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)
}
}
# 构造器
形式
// [] 括号里的内容不是必填的
权限修饰符 方法名字即为类名 (参数列表) [抛出异常] [{
方法体;
}]
每个类中默认都有一个构造方法,若没有写构造方法,默认会提供一个无参的构造方法;若你自己写了构造方法,则会覆盖默认的构造方法
构造方法也存在方法重载,我们把它称之为构造方法重载
构造方法的主要功能就是进行初始化
# 程序块
- 写法:一对大括号包裹
{}
// 比如
public class Demo {
// 程序块
{
System.out.println("我是程序块");
}
public static void main(String[] args) {
Demo d = new Demo();
}
}
执行时机:每一次我们调用构造方法之前,系统会帮我们自动地调用一次程序块
静态代码块:在程序块前添加 static 特征修饰符
# this关键字
用来代替某一个对象
- 在一个构造方法中调用另一个构造方法,通过
this()
形式,并且要放在第一行 - 在构造方法(或普通方法)中调用属性或方法,通过
this.属性名
或this.方法名()
形式
# 修饰符
# 权限修饰符
在 java 中,一个类中的哪些字段可以被哪些访问修饰符所修饰呢?首先,java 中常见的字段有**「类本身」、「嵌套类」、「构造器」、「方法」、「属性」**。其中有一个字段很特殊,就是 **Class(类本身)**只能应用 default 和 public 这两个访问修饰符,其它字段都可以被所有访问修饰符所修饰。同时 interface(接口)也只能应用 default 和 public 这两个访问修饰符,如下表格
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
什么情况下要使用 final 关键字
- 修饰某个类时,表示该类不可以被继承
- 修饰某个方法时,表示该方法不可以被重写
- 修饰类中的某个属性时,表示该属性不可以被修改
- 修饰局部变量时,表示该局部变量不可以被修改
- 如果修饰的变量是基本数据类型,则变量的值不可更改
- 如果修饰的变量是引用数据类型,则变量的地址(内存地址)不可更改,但地址空间内的数据可以更改
final 关键字使用的一些小细节
final 修饰的属性也称为常量,一般用大写字母表示
final 修饰的属性在定义时必须初始化,并且以后不能再修改。初始化的位置可以有
- 在属性定义时,如:
public final double PI = 3.14;
- 在构造函数中
- 在代码块中
- 在属性定义时,如:
如果 final 修饰的属性是静态的,则初始化的位置只能是
- 定义时
- 静态代码块中
如果类不是 final 类,但是含有 final 方法,虽然该方法不能被重写,但是可以被继承
一般来说,如果一个类已经是 final 类了,就没有必要将该 final 类中的方法也修饰成 final
final 不能修饰构造函数
final 和 static 往往搭配使用,效率更高,因为不会导致类加载。
Integer 等包装类、String、Math 等多数工具类都是 final 类
# static
可以修饰什么
- 修饰属性
- 修饰方法
- 修饰块
- 修饰类(内部类)
修饰的内容有什么特点
- 被 static 修饰的内容在类加载时就初始化了,创建的时期非常早
- 静态元素存储在静态元素区中,每一个类都有一个自己的这样的区域
- 静态元素只加载一次,被类对象和类本身共享
# abstract
# native
# transient
# synchronnized
# volatile
# 常用的工具类
# Object类
# ==
运算符
- 既可以判断基本数据类型,也可以判断引用数据类型
- 当判断基本数据类型时,比较的是值是否相等,如:int i = 10; double d = 10.0
- 当判断引用数据类型时,比较的是对象的**地址(内存地址)**是否相同
# equals方法
// Object 中的 equals 方法
public boolean equals(Object obj) { // 判断地址是否相同
return (this == obj);
}
- 由于 equals 是 Object 类中的方法,所以它只能应用于引用数据类型之间的判断
- 默认判断地址是否相同,Object 的子类中往往重写该方法用于判断内容是否相同,比如 Integer、String 类等
- 当然,你也可以在自己的类中重写 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 虚拟机实现(调用时放入 本地方法栈
中),开发人员不需要关心
- 提高具有哈希结构的容器效率
- 两个引用如果指向的是同一个对象,则哈希值肯定相同
- 两个引用如果指向的是不同对象,则哈希值一般不同(因为有可能发生哈希碰撞导致哈希值相同,但这种概率极小,取决于设计的哈希算法)
- 哈希值主要由**地址号(内存地址)**来决定,但不能完全等同于地址号
# toString方法
// getClass().getName() 表示获取全类名(包名+类名)
// Integer.toHexString(hashCode()) 将该类的 hashCode 值转为十六进制表示
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
- 默认返回
全类名+@+哈希值的十六进制
- 子类往往会重写 toString 方法,用于返回对象的属性信息
- 当直接输出一个对象时,toString 方法会被默认的调用。也就是说
System.out.println(obj);
等价于System.out.println(obj.toString());
# 字符串相关的类
# String
String: 不可变的字符序列。底层使用 char[] 存储 StringBuffer: 可变(说明有扩容机制)的字符序列。线程安全,效率低,底层使用 char[] 存储 StringBuilder: 可变(说明有扩容机制)的字符序列。JDK5.0 新增的,线程不安全,效率高,底层使用 char[] 存储
理解 String,什么是「不可变性」
- String 类型声明为 final,表示不可继承
- String 实现了 Serializable 接口、Comparable 接口,表示字符串是支持序列化的、可以比较大小的
- String value 声明为
privete final byte[]
, 表示字符串长度不可变 - String 代表不可变的字符序列。简称:不可变性。体现在
- 当对字符串重新赋值时,需要给另外的内存区域赋值,再将此区域中的值赋值给该变量,不能使用原有内存中的 value 进行赋值
- 当对现有字符串进行拼接操作时,也需要给另外的内存区域赋值,再将此区域中的值赋值给该变量
- 当调用 String 的 replace 方法修改指定字符或字符串时,也需要重写指定另外的内存区域再赋值
String 实例化方式
- 通过字面量形式
// 此时的变量 s1、s2 的数据 "hello" 保存在方法区的字符串常量池中,并且只有一份
String s1 = "hello";
String s2 = "hello";
- 通过
new
的形式
// 此时的变量 s1、s2 的数据 "hello" 保存在堆内存中,并且是两份不同的数据,尽管都是字符串 "hello"
// 因为每次通过 new 创建对象时都会在堆内存中开辟一个新的空间,所以 s1、s2 变量保存的是两个不同的堆内存空间地址
String s1 = new String("hello");
String s2 = new String("hello");
面试题:String s = new String();方式创建对象,在内存中创建了几个对象?
答:两个:一个是堆内存中的 new 结构,另一个是 char[] 对应的常量池中的数据
字符串拼接
- 常量与常量的拼接结果在常量池中,且常量池中不会存在内容相同的常量
- 只要其中有一个是变量,结果就在堆中
- 如果拼接的结果调用 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 与其它数据类型间的转换
- String 与数字
String s = "123";
int i = Integer.parseInt(s);// 将字符串转换为数字
String s2 = String.valueOf(i);// 将数字转换为字符串
- String 与 char[]
String s = "hello";
char[] chars = s.toCharArray();
String s1 = new String(chars);
- String 与 byte[]
String s = "hello";
byte[] bytes = s.getBytes(); // 使用默认的字符集进行转换
String s1 = new String(bytes);
# StringBuffer
# StringBuilder
# 日期时间API
- java.util.Date
- java.sql.Date
- System.currentTimeMillis
- Calendar
# JDK 8 之前
# JDK 8 之后
# Java比较器
# System类
# Math类
# BigInteger与BigDecimal
# package
Java 中的包(package)本质上就是磁盘上的目录,只不过在 Java 中需要在项目(app 或 API)的 src 目录下建立相关的子目录(package),这些子目录才会被 Java 视为包