历时 2 Days
10 Hours
1060 Lines
Pt ( Patrick,本人新起英文名嘿嘿? ) 终于将大学learn的java基础重新拾起,继续分享自己内卷笔记。好不容易当个开心的gopher时,又要转回java坑了…泪目( Ĭ ^ Ĭ )
作者:变优秀的小白
Github:Github
CSDN:CSDN_Blog
爱好:Americano More Ice !
目录
Java-Docs(官方)
数据类型
基本类型
byte/8
char/16
short/16
int/32
float/32
long/64
double/64
boolean/~
包装类型
基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成
Integer x = 2; // 装箱 Integer.valueOf(2)
int y = x; // 拆箱 x.intValue()
复制代码
缓存池
理解 new Integer(123)
与 Integer.valueOf(123)
的区别
new Integer(123)
每次都会新建一个对象Integer.valueOf(123)
会使用缓存池中的对象,多次调用会取得同一个对象的引用
Integer x = new Integer(321);
Integer y = new Integer(321);
System.out.println(x == y); // false
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(x == y); // true
// 等同于上者
Integer m = 123;
Integer n = 123;
System.out.println(m == n); // true
复制代码
来我们看下valueOf()
源码
public static Integer ValueOf(int i) {
// 如果在缓存池
if (i >= IntegerCache.low && i <= IntegerCache.high)
// 返回缓存池内容
return IntegerCache.cache[i + (-IntegerCache.low)];
// 若不在缓存池,新建
return new Integer(i)
}
复制代码
Java8
中,Integer
缓存池默认大小为-128~127
,可以看下源码
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// 最大值可配置
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// 最大值数组大小Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// 若属性不能转换为int,忽略它
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
复制代码
基本类型对应的缓冲池
boolean
: true 和 falseshort
: -128 ~ 127int
: -128 ~ 127char
: \u0000 ~ \u007F
String
基本概念
String
声明类型为final
(不可继承,Integer
包装类也不能继承)
# java8 - char[]数组存储
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
# java9 - byte[]数组存储
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final byte[] value;
/** The identifier of the encoding used to encode the bytes in {@code value}. */
private final byte coder;
}
复制代码
不可变的好处
可缓存的hash
因为 String
的 hash
值经常被使用,例如 String
用做 HashMap
的 key
。不可变的特性可以使得 hash
值也不可变,因此只需要进行一次计算
String Pool需要
如果一个 String
对象已经被创建过了,那么就会从 String Pool
中取得引用(利用了不可变的特性)
安全性
String
经常作为参数,String
不可变性可以保证参数不可变
线程安全
不可变性天生具备线程安全,可以在多个线程中安全地使用
String、StringBuffer和StringBuilder
| 类型 | 可变性 | 线程安全 |
| — | — | —- |
| String | 不可变 | String 不可变,因此是线程安全的 |
| StringBuilder | 可变 | 不是线程安全的 |
| StringBuffer | 可变 | 是线程安全的,内部使用 synchronized 进行同步 |
String Pool
基本概念: 字符串常量池(String Pool
)保存着所有字符串字面量(literal strings
)
常用.intern()
将字符串添加到String Pool
中,.intern()
实现逻辑类似上面讲到的缓存池,有则引用,无则创建
举个例子
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2); // false
String s3 = s1.intern();
String s4 = s2.intern();
System.out.println(s3 == s4); // true
复制代码
new String(“aaa”)
若String Pool
没有"aaa"
字符串,最终会创建两个字符串对象。
"aaa"
属于字符串字面量是一个new
会在堆中创建是另一个
来看下String
构造函数的源码
// 可以看到,String()不会完全复制 value 数组内容,而是都会指向同一个 value 数组
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
复制代码
运算
参数传递
Java
的参数是以值传递的形式传入方法中,而不是引用传递
public class Dog {
String name;
Dog(String name) {
this.name = name;
}
String getName() {
return this.name;
}
void setName(String name) {
this.name = name;
}
String getObjectAddress() {
return super.toString();
}
}
class PassByValueExample {
public static void main(String[] args) {
// 指向一个对象
Dog dog = new Dog("A");
// 调用内部方法,改变对象的值
func(dog);
System.out.Println(dog.getName());
}
private static void func(Dog dog) {
dog.setName("B")
}
}
复制代码
我们再来看下指向不同对象的场景
public class PassByValueExample {
public static void main(String[] args) {
Dog dog = new Dog("A");
System.out.println(dog.getObjectAddress()); // Dog@4554617c
func(dog);
System.out.println(dog.getObjectAddress()); // Dog@4554617c
System.out.println(dog.getName()); // A
}
private static void func(Dog dog) {
System.out.println(dog.getObjectAddress()); // Dog@4554617c
dog = new Dog("B");
System.out.println(dog.getObjectAddress()); // Dog@74a14482
System.out.println(dog.getName()); // B
}
}
复制代码
float和double
Java
不能隐式执行向下转型,因为这会使得精度降低。
向下转型: 即精度高的往精度低去转换
隐式类型转换
比如,int
精度>short
精度,自然不可隐式向下转型,但 +=
或者 ++
运算符会执行隐式类型转换
short s1 = 1;
// 等价于
s1 = s1 + 1
s1 += 1;
s1++;
// 等价于
s1 = (short) (s1+1);
复制代码
switch
Java 7
起,switch
中等值判断支持使用String
对象, 不支持 long
、float
、double
,若过于复杂还是if
比较fix
String s = "a";
switch (s) {
case "a":
System.out.println("aaa");
break;
case "b":
System.out.println("bbb");
break;
}
复制代码
关键字
final
数据
声明数据为常量,可以是编译时常量,初始化后不能被改变的常量
- 对于基本类型,
final
使数值不变 - 对于引用类型,
final
使引用不变,但引用对象本身可变
final int x = 1;
final A y = new A();
y.a = 1;
复制代码
方法
声明方法不能被子类重写,private
方法隐式地被指定为 final
若在子类中定义的方法和基类中的一个 private
方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法
总的来说,子类定义与父类相同方法是不可重写的
类
final
声明类不可被继承
static
1静态变量
- 静态变量:又称类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只有一个。
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死
public class A {
private int x;
private static int y;
public static void main(String[] args) {
A a = new A();
int x = a.x;
int y = A.y;
}
}
复制代码
2静态方法
静态方法在类加载的时候就存在,它不依赖于任何实例。静态方法必须有实现,所以它不能是抽象方法。
只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字,因为这两个关键字与具体对象关联。
public class A {
private static int x;
private int y;
public static void func1() {
int a = x;
// int b = y; 不是静态变量报错
// int b = this.y; 不能有this和super关键字
}
}
复制代码
3静态语句块
静态语句块在类初始化时运行一次
public class A {
static {
System.out.println("123");
}
public static void main(String[] args) {
A a1 = new A();
A a2 = new A();
}
}
// output: 123
复制代码
4静态内部类
非静态内部类依赖于外部类的实例(即先创建外部类实例,才可用非静态内部类),静态内部类则不用
静态内部类不能访问外部类的非静态变量和方法
public class OuterClass {
// 非静态内部类
class InnerClass {}
static class StaticInnerClass {}
public static void main(String[] args) {
// 创建类实例
OuterClass outClass = new OuterClass();
// 依赖外部类实例
InnerClass innerClass = outerClass.new InnerClass();
StaticInnerClass staticInnerClass = new StaticInnerClass();
}
}
复制代码
5静态导包
使用静态变量和方法时不用再指明 ClassName
优点: 简化代码
缺点: 可读性大大降低
import static com.xxx.ClassName.*
6初始化顺序
默认场景
// 下面两者顺序取决于代码中的顺序
public static String staticField = "静态变量";
static {
System.out.println("静态语句块");
}
public String field = "实例变量";
public String field = "实例变量";
{
System.out.println("普通语句块");
}
public InitialOrderTest() {
System.out.println("构造函数");
}
复制代码
存在继承的场景
父类(静态变量、静态语句块)
子类(静态变量、静态语句块)
父类(实例变量、普通语句块)
父类(构造函数)
子类(实例变量、普通语句块)
子类(构造函数
复制代码
Object通用方法
equals()
等价关系
两个对象等价,五个条件
// 自反性
x.equals(x); // true
// 对称性
x.equals(y) == y.equals(x); // true
// 传递性
if (x.equals(y) && y.equals(z))
x.equals(z); // true;
// 一致性(多次调用结果不变)
x.equals(y) == x.equals(y); // true
// 与 null 的比较
x.equals(null); // false;
复制代码
等价与相等
对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法
对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价
Integer x = new Integer(1);
Integer y = new Integer(1);
System.out.println(x.equals(y)); // true
System.out.println(x == y); // false
复制代码
源码实现
public class EqualExample {
private int x;
private int y;
private int z;
public EqualExample(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public boolean equals(Object o) {
// 若是同一个对象的引用
if (this == o) return true;
// 若不是同一个类型
if (o == null || getClass() != o.getClass()) return false;
// Object对象进行转型
EqualExample that = (EqualExample) o;
// 若关键域不相等
if (x != that.x) return false;
if (y != that.y) return false;
// 若关键域相等
return z == that.z;
}
}
复制代码
hashCode()
hashCode()
返回哈希值,而 equals()
是用来判断两个对象是否等价
EqualExample e1 = new EqualExample(1,1,1);
EqualExample e2 = new EqualExample(1,1,1);
System.out.println(e1.equals(e2)); //true
HashSet<EqualExample> set = new HashSet<>();
set.add(e1);
set.add(e2);
System.out.println(set.size()); //2
复制代码
理想的哈希函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的哈希值上
R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失
因为与 2 相乘相当于向左移一位,最左边的位丢失。并且一个数与 31 相乘可以转换成移位和减法:31*x == (x<<5)-x,编译器会自动进行这个优化
@Override
public int hashCode() {
int result = 17;
result = 31 * result + x;
result = 31 * result + y;
result = 31 * result + z;
return result;
}
复制代码
toString()
public class ToStringExample {
private int number;
public ToStringExample(int number) {
this.number = number;
}
}
ToStringExample example = new ToStringExample(123);
System.out.println(example.toString())
// 默认返回该形式,@ 后面的数值为散列码的无符号十六进制表示
// ToStringExample@4554617c
复制代码
clone()
cloneable
clone()
是 Object
的 protected
方法,其他类要调用就要显式的重写 clone()
public class CloneExample {
private int a;
private int b;
}
CloneExample e1 = new CloneExample();
// CloneExample e2 = e1.clone(); // 报错,'clone()' has protected access in 'java.lang.Object'
复制代码
重写 clone()
实现
public class CloneExample {
private int a;
private int b;
@Override
public CloneExample clone() throws CloneNotSupportedException {
return (CloneExample)super.clone();
}
}
CloneExample e1 = new CloneExample();
try {
CloneExample e2 = e1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
// 报错,抛出CloneNotSupportedException,因为没有实现Cloneable接口
复制代码
让我们来改下
// 实现Cloneable接口是规定
public class CloneExample implements Cloneable {
private int a;
private int b;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
复制代码
浅拷贝
拷贝对象和原始对象的引用类型引用同一个对象
public class ShallowCloneExample implements Cloneable {
private int[] arr;
public ShallowCloneExample() {
arr = new int[10];
for(int i = 0; i < arr.length; i++) {
arr[i] = i;
}
}
public void set(int index, int value) {
arr[index] = value;
}
public int get(int index) {
return arr[index];
}
@Override
protected ShallowCloneExample clone() throws CloneNotSupportedException {
return (ShallowCloneExample) super.clone();
}
}
ShallowCloneExample e1 = new ShallowCloneExample();
ShallowCloneExample e2 = null;
try {
e2 = e1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); //222
复制代码
深拷贝
拷贝对象和原始对象的引用类型引用不同对象
public class DeepCloneExample implements Cloneable {
private int[] arr;
public DeepCloneExample() {
arr = new int[10];
for(int i = 0; i < arr.length; i++) {
arr[i] = i;
}
}
public void set(int index, int value) {
arr[index] = value;
}
public int get(int index) {
return arr[index];
}
@Override
protected DeepCloneExample clone() throws CloneNotSupportedException {
DeepCloneExample result = (DeepCloneExample) super.clone();
result.arr = new int[arr.length];
for(int i = 0; i < arr.length; i++) {
result.arr[i] = arr[i];
}
return result;
}
}
DeepCloneExample e1 = new DeepCloneExample();
DeepCloneExample e2 = null;
try {
e2 = e1.clone();
} catch (CloneNotSupportedException e) {
e.printStack();
}
e1.set(2, 222)
System.out.println(e2.get(2)); //2
复制代码
clone()替代方案
通过拷贝构造函数或拷贝工厂来拷贝对象(Effective Java
)
public class CloneConstructorExample {
private int[] arr;
public CloneConstructorExample() {
arr = new int[10];
for(int i = 0; i < arr.length; i++) {
arr[i] = i;
}
}
public CloneConstructorExample(CloneConstructorExample original) {
arr = new int[original.arr.length];
for(int i = 0; i < original.arr.length; i++) {
arr[i] = original.arr[i];
}
}
public void set(int index, int value) {
arr[index] = value;
}
public int get(int index) {
return arr[index];
}
}
CloneConstructorExample e1 = new CloneConstructorExample();
CloneConstructorExample e2 = new CloneConstructorExample(e1);
e1.set(2, 222);
System.out.println(e2.get(2)); // 2
复制代码
继承
访问权限
三个修饰符
private
protected
public
抽象类和接口
抽象类
如果一个类中包含抽象方法,那么这个类必须声明为抽象类
抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承
public abstract class AbstractClassExample {
protected int x;
private int y;
public abstract void func1();
public void func2 {
System.out.println("func2");
}
}
public class AbstractExtendClassExample extends AbstractClassExample {
@Override
public void func1() {
System.out.println("func1");
}
}
// 抽象类实例化报错
// AbstractClassExample ac1 = new AbstractClassExample();
AbstractClassExample ac2 = new AbstractClassExample();
ac2.func1();
复制代码
接口
接口的成员(字段 + 方法)默认都是 public
的,并且不允许定义为 private
或者 protected
。从 Java 9
开始,允许将方法定义为 private
,这样就能定义某些复用的代码又不会把方法暴露出去
接口的字段默认都是 static
和 final
public interface InterfaceExample {
void func1();
default void func2() {
System.out.println("func2");
}
int x = 123;
public int z = 0;
}
public class InterfaceImplementExample implements InterfaceExample {
@Override
public void func1() {
System.out.println("func1");
}
}
// InterfaceExample ie1 = new InterfaceExample();
// Output: 'InterfaceExample' is abstract; cannot be instantiated
InterfaceExample ie2 = new InterfaceImplementExample();
ie2.func1();
System.out.println(InterfaceExample.x);
复制代码
区别: 抽象类和接口
抽象类 | 接口 | |
---|---|---|
设计层面 | 抽象类提供了一种 IS-A 关系,需要满足里式替换原则,即子类对象必须能够替换掉所有父类对象 | 一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系 |
使用层面 | 一个类不能继承多个抽象类 | 一个类可以实现多个接口 |
字段 | 没有限制 | 只能是 static 和 final |
成员 | 多种访问权限 | public |
使用选择
-
接口:
- 需让不相关的类都实现一个方法,例如不相关的类都可以实现
Comparable
接口中的compareTo()
方法; - 需使用多重继承。
- 需让不相关的类都实现一个方法,例如不相关的类都可以实现
-
抽象类:
- 需在几个相关的类中共享代码。
- 需能控制继承来的成员的访问权限,而不是都为
public
- 需继承非静态和非常量字段
super
- 访问父类的构造函数
- 访问父类的成员
public class SuperExample {
protected int x;
protected int y;
public SuperExample(int x, int y) {
this.x = x;
this.y = y;
}
public void func() {
System.out.println("superExample.func()");
}
}
复制代码
public class SuperExtendExample extends SuperExample {
private int z;
public SuperExtendExample(int x, int y, int z) {
super(x, y);
this.z = z;
}
@Override
public void func() {
super.func();
System.out.println("superExtendExample.func()");
}
}
复制代码
调用
SuperExample e = new SuperExtendExample(1,2,3);
e.func();
// output
// superExample.func()
// superExtendExample.func()
复制代码
重写(Override)与重载(Overload)
重写(Override)
为了满足里式替换原则,重写有以下三个限制:
1.子类方法的访问权限必须大于等于父类方法
2.子类方法的返回类型必须是父类方法返回类型或为其子类型
3.子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型
@Override
注解作用: 使用编译器检查是否满足上面的三个限制条件
举个例子
// 父类
class SuperClass {
protected List<Integer> func() throws Throwable {
return new ArrayList<>();
}
}
// 子类
class SubClass extends SuperClass {
// 编译器自动检查是否满足限制条件
@Override
// public权限大于protected
// 返回类型修改为ArrayList<Integer>
// 异常类型为 Exception
public ArrayList<Integer> func() throws Exception {
return new ArrayList<>();
}
}
复制代码
调用方法逻辑: 先从本类中查找看是否有对应的方法,如果没有再到父类中查看,看是否从父类继承来,否则就要对参数进行转型,转成父类之后看是否有对应的方法
优先级: this.func(this)
=> super.func(this)
=> this.func(super)
=> super.func(super)
重载(Overload)
存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同,返回值不同不算
class OverloadingExample {
public void show(int x) {
System.out.println(x);
}
public void show(int x, String y) {
System.out.println(x + " " + y);
}
}
public static void main(String[] args) {
OverloadingExample example = new OverloadingExample();
example.show(1);
example.show(1, "2");
}
复制代码
反射
Class
和 java.lang.reflect
一起对反射提供了支持,java.lang.reflect
类库主要包含了以下三个类:
Field
:使用get()
和set()
方法读取和修改Field
对象关联的字段Method
:使用invoke()
方法调用与Method
对象关联的方法Constructor
:用Constructor
的newInstance()
创建新的对象
优点
- 可扩展性
- 类浏览器和可视化开发环境
- 调试器和测试工具
缺点
尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。
- 性能开销: 设计动态类型解析,JVM无法对代码进行优化,效率低。经常执行或对性能有要求时避免使用
- 安全限制: 必须在一个没有安全限制的环境中运行
- 内部暴露: 由于反射允许代码执行一些在正常情况下不被允许的操作,导致功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
异常
Throwable
来表示任何可以作为异常抛出的类,分为 Error
和 Exception
Error
- 表示 JVM 无法处理的错误
Exception
- 1.
受检异常
:需要用try...catch...
语句捕获并进行处理,可以从异常中恢复 - 2.
非受检异常
:是程序运行时错误,例如除 0 会引发Arithmetic Exception
,程序崩溃并且无法恢复
- 1.
泛型
// T 表示 Type
public class Box<T> {
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
复制代码
注解
理解为 => 辅助
Java
注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑。
JRE or JDK
JRE
:Java Runtime Environment
(运行环境的简称,提供了所需的环境) 它是一个JVM
程序,主要包括了JVM
的标准实现和一些Java
基本类库。JDK
:Java Development Kit
,Java
开发工具包,提供了Java
的开发及运行环境。JDK
是Java
开发的核心,集成了JRE
以及一些其它的工具,比如编译Java
源码的编译器javac
等。
Reference
Java 编程思想[M]
(Eckel B)Effective java
(Bloch J.)