Java基础--基础知识

Updated on with 0 views and 0 comments

一、数据类型

1.1 基础数据类型

基础类型大小(字节)包装类型
boolean1Boolean
byte1Byte
char2Character
short2Short
int4Integer
float4Float
long8Long
double8Double

基本类型与包装类型之间的赋值,通过自动装箱和自动拆箱完成

1.2 缓存池

Byte、Character、Integer、Long、Short包装类型中存在数据缓存,调用吧valueOf方法或者自动装箱时,会先去缓存中查询该数据是否存在,存在则返回缓存数据

以Integer为例,Integer中有一个内部类IntegerCache,里面设置了缓存的数据范围是[-128,127],建立了一个cache数组,并在静态代码块中初始化了该数组

private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            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);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            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;
        }

        private IntegerCache() {}
    }

当我们调用valueOf方法时,会先到缓存的数组中查询数据,查询不到再创建

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

二、String

2.1 概览

String内部使用了一个char数组来存放数据,并且该数组被声明为final,即String被声明后就不能改变他的值了

2.2 hashCode

String的hashCode根据值来计算,而String的值又是不能改变的,所以hashCode也是不变的,hash值不变的话有以下几个优点

  • java的防范区有一个String常量池,如果字符串在常量池已经存在,则直接返回该字符串的引用,否则创建一个新的字符串
  • String不可变保证String多线程的安全性

2.3 String、StringBuffer和StringBuilder

可变性

  • String不可变
  • StringBuffer和StringBuilder可变

线程安全

  • String和StringBuilder线程不安全
  • StringBuffer线程安全,在方法关键词上使用了synchronized关键字保证线程安全

StringBuilder和StringBuffer可变是因为,在他们的父类AbstractStringBuilder中,char数组不是final声明的,并且提供了对这个数组的修改方法

    /**
     * The value is used for character storage.
     */
    char[] value;

    /**
     * The count is the number of characters used.
     */
    int count;

以StringBuilder为例

append方法中,传递一个字符串进去,会调用该字符串的getChars方法,并将当前StringBuilder的字符数组,传递进去

    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

在getChars方法中,将当前字符串的字符数组的值通过System.arraycopy方法复制到StringBuilder传递的dst[]中,以此实现了StringBuilder的可变性

    public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

2.4 String.intern()

通过new创建的字符串不会出现在字符串常量池中,通过=创建的字符串会到字符串池中查询,如果没有就默认加入到常量池中,有的话,就直接使用该字符串的引用;

intern()方法会判断字符串是否在字符串池中,如果存在则返回该字符串的引用,如果不存在,会将字符串加入到字符串池中并返回应用;

下述代码中,str1通过new创建,值为aaa,str2和str3都是通过=创建,所以str1==str2返回false,而str3==str2返回true,此时调用str1的intern方法,在字符串池中查询到了该字符串,则直接返回了这个字符串的引用,所以str2==str4返回true,但是对str1本身是没有任何影响的,str1任然指向自己创建的引用,所以str1==str4返回false;

        String str1 = new String("aaa");
        String str2 = "aaa";
        String str3 = "aaa";
        System.out.println(str1 == str2); // false
        System.out.println(str3 == str2); // true
        String str4 = str1.intern();
        System.out.println(str2 == str4); // true
        System.out.println(str1 == str4); // false

三、运算

3.1 参数传递

  • 传值:参数传递的是实际的值
  • 传址:参数传递的是变量引用的地址

声名一个类,里面有属性name

static class Dog {
    private String name = "dog";
    public Dog(){}
    public String setName(String name){
        return this.name = name;
    }
    public String getName(){
        return this.name;
    }
}

声明一个方法,参数是一个int整数和Dog类

    public static void testParam(int a,Dog dog){
        a = 100;
        dog.setName("1");
    }

在main方法中调用testParam方法

    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.setName("马");
        int a = 10;
        TestString.testParam(a,dog);
        System.out.println(a); // 输出10
        System.out.println(dog.getName()); // 输出马
    }

在调用testParam时,传递的int a是传递的值10,在testParam中改变a的值为100,再main方法中输出,值仍然为10,而dog的name在传参时,传递的是dog变量的引用地址,修改了dog的属性值,但是dog的引用地址仍然不变。

3.2 精度损失

Java 不能隐式执行向下转型,因为这会使得精度降低。例如double不能转成float,float也不能转成int。

四、继承

4.1 访问权限

java有以下几个权限访问修饰符,private、protected、public,如果不见修饰符(default)表示包级可见,权限从大到小依次为public>protected>default>private

image.png

如果子类重写了父类的方法,则子类中该方法的访问级别不能低于父类,一次满足里氏替换原则。

4.2 抽象类与接口

4.2.1 抽象类

抽象类及内部的抽象方法使用abstract关键字声明,抽象类不能被实例化。

@Data
public abstract class Animal {
    protected String name;

    protected abstract void eat();
}

public class Cat extends Animal{
    @Override
    protected void eat() {
        System.out.println(super.getName());
    }
}

注意上述嗲马中,eat方法的权限为protected,那么在子类中,该方法的权限就不能比protected低

4.2.2 接口

接口是抽象类的延申,在java8之前,接口中不允许有方法实现,但是从java8开始,接口也可以有默认的方法实现;

接口的字段和方法默认都是public,并且不允许定义为private或protected,接口的字段默认都是static和final的;

在实现类中,通过this即可访问接口定义的属性和默认方法

public interface EatInterface {
    public abstract void takeFood(); // Modifier 'abstract' is redundant for interface methods
    default void eat(){
        System.out.println("干饭");
    }
    public static final int x = 100; //Modifier 'public|、static、final' is redundant for interface fields
    int y = 100;
}
public class Eat implements EatInterface{
    @Override
    public void takeFood() {
        eat();
        System.out.println(this.x+this.y);
    }
}

4.2.3 比较

  • 抽象类满足IS-A关系,遵循里氏替换原则,接口不具有IS-A关系
  • 一个类可以实现多个接口,但不能继承多个抽象类
  • 接口的字段只能是static final类型,抽象类没有限制
  • 接口的成员(方法+属性)只能是public,抽象类没有限制
  • 接口的方法abstract是隐式的不用声明,抽象类则需要显示声明这是一个abstract方法

4.2.4 应用

以下场景考虑使用抽象类

  • 需要公用代码块
  • 需要继承重修类的一些固定方法,并重写一部分代码
  • 需要声明非静态的属性或非最终执行的属性

以下场景考虑使用接口

  • 为各个不相关的组件提供功能
  • 指定一个特定的操作,而不关心有谁来实现,怎么实现

4.3 super

  • 访问父类构造函数
  • 访问父类方法

4.4 重写和重载

4.4.1 重写

  • 使用@Override注解标识
  • 子类方法访问权限大于等于父类方法权限
  • 子类方法的返回类型必须是父类方法的返回类型或者它的子类

4.4.2 重载

  • 同一个类中,方法名相同,但是参数个数,类型,顺序不同
  • 返回值类型不同,其他相同则不是重载

五、Object

内部的几个重要方法

public final native Class<?> getClass();
public native int hashCode();
public boolean equals(Object obj);
protected native Object clone() throws CloneNotSupportedException;
public String toString();
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
protected void finalize() throws Throwable { }

5.1 equals()

5.1.1 等价关系

equals方法满足自反性、传递性、对成性、一致性,不是null的对象调用x.equals(null)均返回false

5.1.2 equals()和==

==在比较基本数据类型时,比较的时数据的值,在比较对象时,则比较对象的引用地址是否相同;

equals()比较的时对象所指向的地址值,equals方法可以重写;

5.1.3 hashCode()

hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。

5.1.4 toString()

默认实现时返回了类名+hashCode值

5.2 clone()

5.2.1 cloneable

clone方法在Object中使用了protected标识,在子类中,并需要显示重写该方法,一个类如果要调用clone方法,则必须实现Cloneable接口

public class CloneExample implements Cloneable{
    public CloneExample clone1() throws CloneNotSupportedException {
        return (CloneExample)super.clone();
    }
}
    public static void main(String[] args) throws CloneNotSupportedException {
        CloneExample cloneExample1 = new CloneExample();
        CloneExample cloneExample2 = cloneExample1.clone1();
    }

5.2.2 浅拷贝

原对象创建了一个新的对象,并把自身的属性的引用地址拷贝给新的对象

下述代码中,创建一个qh学校,qh里面有个学生叫龙,年龄17,然后对qh进行一个拷贝,建立一个bd学校,此时,修改qh中的哪个学生,年龄改为99,再去bd查询学生的年龄,发现也变成了99,然后通过==比较对象的地址,qh==bd返回false,而比较内部的对象则返回了一个true

public class School implements Cloneable  {
    private Student student;
    public School(){
        student = new Student();
        student.setName("龙");
        student.setAge(17);
    }

    public Student getStudent() {
        return student;
    }

    @Override
    protected School clone() throws CloneNotSupportedException {
        return (School) super.clone();
    }
}
public class Student {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
    public static void main(String[] args) throws CloneNotSupportedException {
        School qh = new School();
        School bd = qh.clone();
        qh.getStudent().setAge(99);
        System.out.println(bd.getStudent().getAge()); // 99
        System.out.println(qh.getStudent() == bd.getStudent()); // true
        System.out.println(qh == bd); // false
    }

5.2.3 深拷贝

将拷贝对象以及它的属性都重新创建,重写clone方法,并在clone方法中重新创建即可

六、常见关键字

6.1 final

6.1.1 修饰数据

final可以声明一个常量数据,声明后不能修改该数据

  • 对于基本类型,数值无法更改
  • 对于引用类型,无法更改对象的引用地址,但是可以修改对象本身的属性

6.1.2 修饰方法

添加了final的方法无法被子类重写,private方法被隐式声明了这是一个final方法

6.1.3 修饰类

这个类不能被继承

6.2 static

6.2.1 静态变量

静态变量,通过类名.属性名访问

public class Student {
    static String SCHOOL_NAME="昆明理工大学";
}

6.2.2 静态方法

修饰静态方法,通过类名.方法名访问

6.2.3 静态代码块

public class Student {
    static void doSomething(){
        System.out.println("你好");
    }
}

修饰静态代码块,类初始化时运行一次

public class Student {
    static{
        System.out.println("你好");
    }
}

6.2.4 静态内部类

修饰静态内部类,创建时通过new 外部类.静态内部类实现,静态内部类不能访问外部类的非静态的变量和方法

public class OutClass {
    class InnerClass{
    }

    static class StaticInnerClass{
    }
}
    public static void main(String[] args){
        OutClass outClass = new OutClass();
        OutClass.InnerClass innerClass = outClass.new InnerClass();
        OutClass.StaticInnerClass staticInnerClass = new OutClass.StaticInnerClass();
    }

6.2.5 静态导包

在使用静态变量和方法时不用再指明 ClassName,但是代码后面维护起来比较麻烦,可读性也比较差

下述代码中,使用了静态导包,导入了Math,访问下面的方法时,就可以不带Math.

import static java.lang.Math.*;

public class Main {
    public static void main(String[] args){
        System.out.println(Math.random());
        System.out.println(random());
    }
}

6.2.6 初始化顺序

静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。

  • 父类的静态变量,静态代码块
  • 子类的静态变量,静态代码块
  • 父类(实例变量、方法)
  • 父类(构造函数)
  • 子类(实例变量、方法)
  • 子类(构造函数)

七、反射

Class和java.lang.reflect一起提供了反射功能,java.lang.reflect主要包含以下三个类

  • Field 使用get和set获取修改类属性
  • Method 使用 invoke() 方法调用与 Method 对象关联的方法
  • Constructor 用 Constructor 创建新的对象

具体内容可以参考注解和反射 - 问尤龙の时光 (wenyoulong.com)

八、异常

Throwable是Java中所有异常(Exception)和错误(Error)的父类,error表示jvm异常,无法被捕获处理,Exception分为以下两类

  • 必检异常
  • 免检异常 (运行时异常)

几个常见的异常和错误

在这里插入图片描述

九、注解

参考以下文档

注解和反射 - 问尤龙の时光 (wenyoulong.com)

自己定义一个注解 - 问尤龙の时光 (wenyoulong.com)

十、泛型

泛型的本质是为了参数化类型,可以减少代码量,实现的代码复用,方便后期维护,

10.1 泛型类

在类名后添加泛型

public class Point {

    public static void main(String[] args){
        Node<String,Integer> node = new Node<>();
        node.put("龙",11);
    }
}

class Node<K,V>{
    private K key;
    private V value;

    public void put(K key,V value){
        this.key = key;
        this.value = value;
    }
}

10.2 泛型接口

在接口名后定义泛型,并在接口中使用泛型

interface A<T>{
    public T getInfo();
}
class AImp<T> implements A<T>{
    private T var;
    public AImp(T var){
        this.setVar(var);
    }
    public void setVar(T var){
        this.var = var;
    }
    public T getVar(){
        return this.var;
    }
    @Override
    public T getInfo() {
        return this.getVar();
    }
}
public class Info{
    public static void main(String[] args){
        A<String> a = new AImp<>("龙");
        System.out.println(a.getInfo());
    }
}

10.3 泛型方法

泛型也可以只用在方法上,用法参考

示例代码

interface A<T>{
    public T getInfo();
}
class AImp<T> implements A<T>{
    private T var;
    public AImp(T var){
        this.setVar(var);
    }
    public void setVar(T var){
        this.var = var;
    }
    public T getVar(){
        return this.var;
    }
    @Override
    public T getInfo() {
        return this.getVar();
    }
}

class Fun{
    public <T,Y> void fun(A<T> a,A<Y> b){
        System.out.println(a.getInfo()+" "+b.getInfo());
    }
}
public class Info{
    public static void main(String[] args){
        A<String> a = new AImp<>("龙");
        A<Integer> b = new AImp<>(11);
        Fun fun = new Fun();
        fun.fun(a,b);
    }
}

10.4 泛型受限

在使用泛型的时候,我们可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。

10.4.1 上限

通过extends指定泛型的只能为指定类型的子类

下述代码指定了传入的泛型只能是Number及其子类,传入Strng则报错

class Fun<T extends Number>{
    private T var;
  
}
public class Info{
    public static void main(String[] args){
        Fun<Float> floatFunc = new Fun<>();
        // Fun<String> stringFun = new Fun<>(); Type parameter 'java.lang.String' is not within its bound; should extend 'java.lang.Number'
    }
}

10.4.2 下限

通过super指定传入的泛型只能是指定类型的父类

下述代码指定了传入的泛型只能是String及其父类,传入Integer则报错

class Fun1<T>{
    private T var;
}
}
public class Info{
    public static void main(String[] args){
//        Fun1<Float> floatFunc = new Fun1<>();
//        fun(floatFunc); 报错
        Fun1<String> stringFun1 = new Fun1<>();
        fun(stringFun1);
    }

    public static void fun(Fun1<? super String> temp){
        System.out.print(temp + ", ") ;
    }
}

10.5 泛型数组

创建泛型数组使用 java.lang.reflect.Array.newInstance(Class<T> componentType, int length)

class ArrayWithType<T> {
    private T[] array;

    public ArrayWithType(Class<T> type, int size) {
        array = (T[]) Array.newInstance(type, size);
    }

    public T[] create() {
        return array;
    }
}
    public static void main(String[] args){
        ArrayWithType<String> stringArrayWithType = new ArrayWithType<>(String.class,10);
        String[] strings = stringArrayWithType.create();
        System.out.println(strings.toString());
    }

10.6 泛型擦除

所有泛型类的类型参数在编译时都会被擦除,泛型类会被替换成具体的类,虚拟机运行时中没有泛型,只有普通类和普通方法。

10.6.1 原始类型

原始类型就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,没有上下限的泛型,都会被替换成Object

class A<T> {
    private T value;
} 

上述代码替换后就变成了

class A {
    private Object value;
}

如果使用了限制泛型,那原始类型就是限制的泛型

class A<T extends Number>{
    private T value;
}
class A{
    private Number value;
}

10.7 泛型的性质

  • 基本类型不能作为泛型
  • 泛型类型无法实例化
  • 泛型类中,不能使用泛型修饰静态变量
  • 泛型类中的泛型方法使用的是方法自己的泛型,不是类的泛型
  • 不能抛出,也不能捕获泛型对象

标题:Java基础--基础知识
作者:wenyl
地址:http://www.wenyoulong.com/articles/2023/06/19/1687132406084.html