【Java知识体系】你以为的Java基础 VS 实际上的Java基础

大家好!我是未来村村长,就是那个“请你跟我这样做,我就跟你这样做!”的村长👨‍🌾!

||To Up||

未来村村长正推出一系列【To Up】文章,该系列文章重要是对Java开发知识体系的梳理,关注底层原理和知识重点。”天下苦八股文久矣?吾甚哀,若学而作苦,此门无缘,望去之。“该系列与八股文不同,重点在于对知识体系的构建和原理的探究。


​ 该篇文章没有系统的梳理那些常见的Java基础知识点,而是作者在读《Java核心技术卷Ⅰ》发现的一些高校教材忽略但是面试经常问到的知识点,进行一个详细整理。

一、位运算符

1、原码、反码与补码

  • 原码:指十进制数的二进制形式,用最高位表示符号位,‘1’表示负号,‘0’表示正号。其他位存放该数的二进制的绝对值。
  • 反码:正数的反码还是等于原码,负数的反码就是他的原码除符号位外,按位取反。
  • 补码:正数的补码等于他的原码,负数的补码等于其反码+1。

​ 我们知道的原码是十进制的二进制形式,那反码和补码的作用是干啥的呢?用来计算。我们常说的位运算就是基于补码来进行计算,求补码我们常常用到反码,虽然两者之间除了计算关系以外没有必然联系。

//5的原码:0000 0101
//-5的原码:1000 0101
//-5的反码:1111 1010
//-5的补码:1111 1011

2、&、|、~、^

  • &:如果两边都为1,结果为1,否则为0
  • |:如果两边任一边为1,结果为1,否则为0
  • ~:取反,1为0,0为1
  • ^:如果两边相同则为0,不同则为1
//5的二进制为101,6的二进制位110
5 & 6;
//计算结果100,十进制为4[0*1+0*2+0*4]
5 | 6;
//计算结果111,十进制为7[1*1+1*2+1*4]
~ 5;
//计算结果010,十进制为2
5 ^ 6;
//计算结果为011,十进制为3

3、<<(左移)和>>(右移)

x<<a的过程如下:

  • x转换为二进制的补码形式
  • x的补码向左移动a位
  • 移位后的数转换为十进制
5<<1;
//0000 0101 向左移动1位
//0000 1010 变成10[0*1+1*2+0*4+1*8]
5>>1;
//0000 0010 变成2

二、类与对象

1、常见关系

  • uses-a:依赖关系,如果一个类的方法使用或操纵另一个类的对象,则称为一个类依赖于另一个类。
  • is-a:继承关系,一个类通过extends实现对另一个类的拓展
  • has-a:聚合关系,一个类的对象含有另一个类的对象

2、修饰符

修饰符说明
static可修饰成员变量和方法,被static修饰的变量和方法会在类加载时执行加载,未被static修饰的变量和方法称为实例变量和实例方法。静态方法不能操纵实例变量。
final可修饰类、方法和成员变量,被final修饰的类不能被继承,被final修饰的方法不能被重写,被final修饰的成员变量为常量,在程序执行过程中不可变。
abstract可修饰类和方法,被abstract修饰的类称为抽象类,抽象类中可以有抽象方法,也可以没有。被abstract修饰的方法,仅有方法头没有实现,含有abstract方法的类必须是abstract类。abstract不能和final同用。
default为了解决接口中不能有默认方法的问题。
private访问控制符,可修饰方法和成员变量。
protected访问控制符,可修饰方法和成员变量。
public访问控制符,可修饰类、成员变量和方法,不可修饰局部变量。
nativeJava平台有个用户和本地C代码进行互操作的API,称为Java Native Interface (Java本地接口)。native 关键字告诉编译器(其实是JVM)调用的是该方法在外部定义,这里指的是C。

​ 在继承关系中,子类重写父类的方法其访问权限不能降级。私有方法不能通过对象进行访问,子类不继承父类的private方法和private变量。private可以和static并用。

​ 被abstract修饰的方法不能被private修饰,默认被public修饰,且不能被static修饰,只能为实例方法。

3、方法传参

​ Java语言中的方法参数总是按值调用,即传入方法的参数是参数值的副本。在Java中有两种参数类型,一是基本数据类型,二是对象引用。

public int sum(int arr[]){
    int sum = 0;
    for(int i=0;i<arr.length;i++) sum+=arr[i];
    return sum;
}
//该方法在传入参数arr1时
int arr1[];
sum(arr1);
//可看作下列形式
public int sum(int arr[]){
    int arr[] = arr1;//建立一个副本
    int sum = 0;
    for(int i=0;i<arr.length;i++) sum+=arr[i];
    return sum;
}

​ 所以方法:

  • 不能修改基本数据类型的参数

  • 可以改变对象参数的状态,这里改变的是状态(对象的成员变量或数组对象中的元素值)

  • 不能让一个对象参数引用一个新的对象

    注意看最后一点,假设A是一个类,如下操作是无法执行成功的,我们只能通过对象去找到具体的实例进行修改

public void change(A a,A b){
    A temp = a;
    a = b;
    b = temp;
}
//因为执行过程其实可以看作,对对象的赋值其实是对对象副本的赋值
public void change(A a,A b){
    A temp1 = a;
    A temp2 = b;
    A temp = temp1;
    temp1 = temp2;
    temp2 = temp;
}

4、初始化块

​ 初始化数据字段的方法除了在声明时赋值和在构造器中设置值,还有第三种机制——初始化块。在一个类的声明中,可以包含任意多个代码块,只要构造这个类的对象,这些块就会被执行。

class People{
    private static int nextNo = 1;
    private int no;
    {
    	no = nextNo;
        nextNo++;
    }
}
//每当new一个对象时,就会执行代码块中的表达式
People people = new People();

5、对象包装器与自动装箱

​ Integer、Long、Float、Double、Short、Byte、Character、Boolean对应int、long、fooat等基本类型,我们将这些类称为包装器(wrapper)。

​ Java中对于基本类型和其对应对象的转换会自动完成,我们称为自动装箱和自动拆箱。

(1)自动装箱:
list.add(3);
//自动转换为
list.add(Integer.valueOf(3));
(2)自动拆箱:
int n = list.get(i);
//自动转换为
int n = list.get(i).intValue();

三、继承

1、子类构造器

​ 如果子类的构造器没有显式地调用父类的构造器,将自动调用父类的无参构造器。如果父类没有无参构造器,并且子类没有显式调用父类的其他有参构造器,则会发生编译错误。

super();

​ super的含义有两个:

  • 调用父类的方法
  • 调用父类的构造器

​ this的含义也有两个:

  • 隐式参数的引用
  • 调用该类的其他构造器

2、Object类

​ Object类是Java.lang下的核心类,如果某个类没有明确指出继承一个父类,则Object被认为是这个类的父类。Object提供了以下方法:getClass、hashCode、equals、toString、notify、notifyAll、wait、clone、finalize、registerNatives。

(1)equals

​ Object类中的equals方法用于检测一个对象是否等于另一个对象,对于未重写equals方法的类会默认使用Object的equals方法。该方法如下,使用==进行比较。

public boolean equals(Object obj) {
        return (this == obj);
}

​ String类重写了equals方法,具体如下:

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

​ 我们可以看到,其先比较两个String对象是否是同一引用,如果是则返回True,如果不是的话会将其转换为char[]数组进行逐一比较。

(2)hashCode
public native int hashCode();

​ 这里的hashCode使用native修饰,说明是一个外部方法。哈希码是由对象计算出的整型值,两个不同的对象的哈希码基本不相同,但是不一定。我们在重写hashCode方法和equals方法时要遵循这样的规则:x.equals(y)返回true,则x.hashCode()与y.hashCode必须返回相同的值。即equals返回true,则两者的哈希码必须相等。两者的哈希码相等但两者不一定相等。

​ String类的hashCode()方法如下:其中value代表该字符串的char数组。

//private final char value[];
public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
}
(3)getClass
public final native Class<?> getClass();

​ 同样是一个Native Method,这里利用反射机制,可以通过对象来获取类对象。

3、深拷贝与浅拷贝【常问】

​ 深拷贝和浅拷贝都是对象的拷贝。Object中的clone方法声明为protected,所以不能直接调用。

​ 浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。

​ 深拷贝:深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

​ 我们一般在实现clone方法前,先实现Cloneable接口,这个接口指示一个类提供了一个安全的clone方法。

​ Object中的clone方法是一个浅拷贝方法,方法声明为protected我们需要对其进行重写,浅拷贝只对Object进行克隆。

class Person implements Cloneable{
    Head head = handsomeHead;
    //创建了一个Peison类,类中有head对象赋值handsomeHead
    public Person clone() throws CloneNotSupportedException{
        return (Person) super.clone();
    }
}

​ 我们也可以写一个深拷贝,深拷贝还需要对fields中的对象变量进行克隆。

class Person implements Cloneable{
    Head head = handsomeHead;
    public Person clone() throws CloneNotSupportedException{
        Person cloned = (Person) super.clone();
        cloned.head = (Head) head.clone();
    }
}

四、反射机制

1、Class类

​ 有一句话“面向对象的世界里,万物皆对象”,在Java中类也是对象,我们写的每一个类都是对象都是java.lang.Class类的对象。称为类的类型对象,一个 Class 对象对应一个加载到 JVM 中的一个 .class 文件。我们可以通过这个 Class 对象看到类的结构,就好比一面镜子。所以我们形象的称之为:反射。

​ 我们可以看到Class的构造方法是一个私有方法,说明只有JVM能通过调用该方法创建该类的对象。

private Class(ClassLoader loader) {
    classLoader = loader;
}

​ 我们回顾一下类的加载机制,类的加载会经历五个过程:

  • 加载:通过类的完全限定名称获取定义该类的字节流,在堆中生成一个代表该类的Class对象【饿汉单例】,作为方法区该类各种数据的访问入口
  • 验证:确保class文件的字节流的信息符合虚拟机要求
  • 准备:为类变量(static)分配内存并设置初始值。
  • 解析:将常量池符号引用替换为直接引用
  • 初始化:虚拟机执行类构造器<clinit>()方法的过程

​ Class有几个重要的方法:

public T newInstance();//创建对象
public String getName();//返回完整类名带包名
public String getSimpleName();//返回类名
public Field[] getFields();//返回类中public修饰的属性
public Field[] getDeclaredFields();//返回类中所有的属性
public Field getDeclaredField(String name);//根据属性名name获取指定的属性
public native int getModifiers();//获取属性的修饰符列表
public Method[] getDeclaredMethods();//返回类中所有的实例方法
public Method getDeclaredMethod(String name, Class<?>… parameterTypes);//根据方法名name和方法形参获取指定方法
public Constructor<?>[] getDeclaredConstructors();//返回类中所有的构造方法
public Constructor getDeclaredConstructor(Class<?>… parameterTypes);//根据方法形参获取指定的构造方法
public native Class<? super T> getSuperclass();//返回调用类的父类
public Class<?>[] getInterfaces();//返回调用类实现的接口集合

2、反射的原理与作用

​ 反射的概念:反射使Java代码能够发现有关已加载类的字段、方法和构造函数的信息,并在安全限制内使用反射的字段、方法和构造函数对其底层对应项进行操作。

​ Java反射机制的实现要借助于4个类:Class,Constructor,Field,Method;通过这四个对象我们可以粗略的看到一个类的各个组成部分,其中最核心的就是Class类,它是实现反射的基础,必须先获得Class才能获取Method、Constructor、Field。

  • Class:类的类型对象
  • Constructor:类的构造器对象
  • Field:类的属性对象
  • Method:类的方法对象

​ 反射的作用:在运行时判断任意一个对象所属的类,构造任意一个类的对象,判断任意一个类所具有的成员变量和方法,调用任意一个对象的方法。例如动态代理、工厂模式。

3、获取类以及其成员变量、构造方法和方法

(1)获取类

​ 要想获取类的成员变量、构造方法和方法,就必须先获取类的类型对象,获取class对象有以下方式:

方式备注
Class.forName(“完整类名带包名”)静态方法
对象.getClass()
任何类型.class
(2)获取构造方法

​ 在Class对象中获取全部Constructor的方法如下:

Constructor[] constructors = xxxClass.getDeclaredConstructors();

​ Constructor类的相关方法如下,从方法中我们可以知道当我们从class获取了Constructor后,就可以返回其方法名等信息以及使用该构造器创建实例对象。

public String getName();//返回构造方法名
public int getModifiers();//获取构造方法的修饰符列表,返回的修饰符是一个数字
public Class<?>[] getParameterTypes();//返回构造方法的修饰符列表
public T newInstance(Object … initargs);//创建对象

​ 获取指定的构造方法,通过该构造方法获取实例对象

Constructor construction = xxxClass.getDeclaredConstructor(int.class, String.class, String.class, boolean.class);//根据parameterTypes获取指定的构造方法,对应的字段类型
Object xxxObject = constructionc1.newInstance(123, "xxx", "xxx", true);//Constructor类的newInstance方法
(3)获取成员变量

​ 在Class对象中获取全部Fields的方法如下

Field[] fields = xxxClass.getFields();

​ Field类相关方法如下,从方法中我们可以知道获取了Field对象后,不仅能返回该类相关成员变量的信息,还能设置属性值

public String getName();//返回属性名
public int getModifiers();//获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号
public Class<?> getType();//以Class类型,返回属性类型
public void set(Object obj, Object value);//设置属性值
public Object get(Object obj);//读取属性值

​ 通过class获取指定类型的field,并为其赋值

Class studentClass = Class.forName("xxx");
Object obj = studentClass.newInstance();
Field Field1 = studentClass.getDeclaredField("Field1Name");
noField.set(obj,xxxValue);
(4)获取方法

​ 在Class对象中获取全部Method的方法如下

Method[] methods = xxxClass.getDeclaredMethods();

​ Method类相关方法如下,通过其方法我们可以知道,其不仅能返回方法的相关信息,还能对返回方法进行调用。

public String getName();//返回方法名
public int getModifiers();//获取方法的修饰符列表
public Class<?> getReturnType();//以Class类型,返回方法类型
public Class<?>[] getParameterTypes();//返回方法的修饰符列表
public Object invoke(Object obj, Object… args);//调用方法

​ 获取指定方法并调用

Class xxxClass = Class.forName("xxx");
Object obj = xxxClass.newInstance();
Method xxxMethod = xxxClass.getDeclaredMethod("MethodName",ParameterType1,ParameterType2);//注:没有形参就不传
Object resValues = xxxMethod.invoke(obj, xxx, xxx);//接收返回的结果,需要强转型

(5)创建实例

​ 我们可以通过获取的类,直接创建实例对象,也可以通过获取相应的构造方法来创建实例对象。

Object obj = constructor.newInstance(ParameterTypes);
Object obj = xxxClass.newInstance();//无参构造

五、泛型机制

1、泛型的原理

​ 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

​ Java泛型是用于编译时期的类型检查,是伪泛型。因为无论何时定义了一个泛型类型,都会自动提供一个相应的原始类型。这个原始类型的名字就是去掉类型参数后的泛型类型名。类型变量会被擦除,Java编译器进行类型擦除步骤如下:

  • 检查泛型类型,获取目标原始类型
  • 擦除泛型变量,并替换为限定类型(对于限定则替换为Object)
  • 调用相关函数,将结果强制转换为目标类型

例如:

class xxxClass<T>{    
    private T value;    
    public T getValue() {    
        return value;    
    }    
    public void setValue(T  value) {    
        this.value = value;    
    }    
}

擦除后

class xxxClass{    
    private Object value;    
    public Object getValue() {    
        return value;    
    }    
    public void setValue(Object  value) {    
        this.value = value;    
    }    
}

​ 泛型的相关作用如下:

  • 保证了类型的安全性,当定义泛型类型后,如果使用两种不同的类型在编译时期即会报错
  • 消除编译时期的强制转换,使代码具有可读性,但实际上是将强制转换过程交给Java编译器执行

2、泛型的运用

(1)泛型类
public class 类名 <T,V> {
    T value1;
    V value2;
    V mehthod(V value){
    	return value;
    }
}
(2)泛型接口
public interface 接口名 <T,V> {
    T value1;
    V value2;
    V mehthod(V value);
}
(3)泛型方法
public <T> T genercMethod(T t){
    return t;
}

3、通配符和限定符

​ 严格的泛型类型使用起来不够灵活,Java设计者设计了通配符来使泛型的运用更加灵活:

  • 通配符?:在泛型中可以使用?来代表任意类型。

  • 父类限定super:super是类型上界,意味着我们只能传递给该类、接口或方法xxxClass类或者该类的子类。

<? super xxxClass>
  • 子类限定extends:extends是类型下界,意味着我们只能传递给该类、接口或方法xxxClass类或者该类的父类。
<? extends xxxClass>

4、泛型使用限制

  1. 不能实例化类型变量(对象)
  2. 不能构造泛型数组,但可以声明
  3. 不能在静态字段或方法中引用类型变量
  4. 注意擦除后的冲突:若两个接口类型是同一接口的不同参数化[泛型参数],一个类或类型变量不能作为这两个接口的子类

——————————————————————

作者:未来村村长

参考:《Java核心技术卷Ⅰ》

个人网站:www.76pl.com

👨‍🌾点个关注,白嫖不迷路👩‍🌾

——————————————————————

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未来村村长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值