2天10小时1060行完结Java基础

历时 2 Days

10 Hours

1060 Lines

Pt ( Patrick,本人新起英文名嘿嘿? ) 终于将大学learn的java基础重新拾起,继续分享自己内卷笔记。好不容易当个开心的gopher时,又要转回java坑了…泪目( Ĭ ^ Ĭ )

作者变优秀的小白

GithubGithub

CSDNCSDN_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 和 false
  • short: -128 ~ 127
  • int: -128 ~ 127
  • char: \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

因为 Stringhash 值经常被使用,例如 String 用做 HashMapkey。不可变的特性可以使得 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对象, 不支持 longfloatdouble,若过于复杂还是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()Objectprotected 方法,其他类要调用就要显式的重写 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,这样就能定义某些复用的代码又不会把方法暴露出去

接口的字段默认都是 staticfinal

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");
}
复制代码

反射

Classjava.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类:

  • Field :使用 get()set() 方法读取和修改 Field 对象关联的字段
  • Method :使用 invoke() 方法调用与 Method 对象关联的方法
  • Constructor :用 ConstructornewInstance() 创建新的对象

优点

  • 可扩展性
  • 类浏览器和可视化开发环境
  • 调试器和测试工具

缺点

尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。

  • 性能开销: 设计动态类型解析,JVM无法对代码进行优化,效率低。经常执行或对性能有要求时避免使用
  • 安全限制: 必须在一个没有安全限制的环境中运行
  • 内部暴露: 由于反射允许代码执行一些在正常情况下不被允许的操作,导致功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

异常

Throwable 来表示任何可以作为异常抛出的类,分为 ErrorException

  • Error
    • 表示 JVM 无法处理的错误
  • Exception
    • 1.受检异常 :需要用 try...catch... 语句捕获并进行处理,可以从异常中恢复
    • 2.非受检异常 :是程序运行时错误,例如除 0 会引发 Arithmetic Exception,程序崩溃并且无法恢复

image.png

泛型

// 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

  • JREJava Runtime Environment(运行环境的简称,提供了所需的环境) 它是一个 JVM 程序,主要包括了 JVM 的标准实现和一些 Java 基本类库。
  • JDK: Java Development KitJava 开发工具包,提供了 Java 的开发及运行环境。JDKJava 开发的核心,集成了 JRE 以及一些其它的工具,比如编译 Java 源码的编译器 javac 等。

Reference

  • Java 编程思想[M](Eckel B)
  • Effective java(Bloch J.)

结束语:如果遇到什么疑问或者建议的,可直接留言评论!作者看到会马上一一回复

如果觉得小白此文章不错或对你有所帮助,期待你的一键三连?!❤️ ni ~

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享