【知识总结】java基础

语言特性

面向对象编程三大特性

封装:封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法。

继承:继承就是子类继承父类的特征和行为,使用继承我们能够非常方便地复用以前的代码。

多态:多态就是同一个接口,使用不同的实例而执行不同操作


Java和C++的区别?

  1. 都是面向对象的语言,都支持封装、继承、多态
  2. java不提供指针访问内存,程序内存更安全
  3. java的类是单继承,C++支持多继承。但是java接口可以多继承
  4. java有自动内存管理机制,不需要程序员手动释放无用内存

JDK和JRE的区别

  • JDK:Java Development Kit(java开发工具包)
  • JRE:Java Runtime Enviroment(Java运行时环境)

JDK包含JRE。JRE是运行必要的环境,可单独运行。

如果你需要运行java程序,只需安装JRE就可以了。如果你需要编写java程序,需要安装JDK。


JDK8 的新特性

  • 用元空间替代了永久代
  • 引入了 Lambda 表达式
  • 引入了日期类(LocalDate、LocalTime、LocalDateTime)
  • 引入了接口的默认方法和静态方法
  • 修改了 HashMap 和 ConcurrentHashMap 的实现。**(然后等着被问)

浅拷贝和深拷贝

http://res.mianshigee.com/upload/article/20200411/8dc13f2b-1201-46ef-b4a0-1ca9ab65074e.jpg

浅拷贝:只复制指针,新旧对象共享同一块内存。

深拷贝:创建一个一模一样的对象,新旧对象不共享内存。修改新对象不会改到原对象。

注解

注解是一种标记,使类或接口附加额外信息,帮助编译器和 JVM 完成一些特定功能,例如 @Override 标识一个方法是重写方法。

元注解是自定义注解的注解,例如:

@Target:约束作用位置

@Rentention:约束生命周期

@Documented:表明这个注解应该被 javadoc 记录。


泛型

泛型的本质即 “参数化类型”,就是可以把类型像方法的参数那样传递。

  1. 类型安全,放置什么类型,取出来就是什么类型,不存在 ClassCastException 类型转换异常。
  2. 提升可读性,编码阶段就显式知道泛型集合、泛型方法等处理的对象类型。
  3. 代码重用,合并了同类型的处理代码。

泛型的实现原理

Java 的泛型是伪泛型。

实现原理是类型擦除。Java的泛型只存在于编译期,一旦编译成字节码,泛型将被擦除。泛型的作用在于在编译阶段保证我们使用了正确的类型,并且由编译器帮我们加入转型动作,使得转型是不需要关心且安全的。


反射

在运行状态中,对于任意一个类都能知道它的所有属性和方法,对于任意一个对象都能调用它的任意方法和属性,这种动态获取信息及调用对象方法的功能称为反射。

反射的缺点是破坏了封装性以及泛型约束。


Class 类

在程序运行期间,Java 运行时系统为所有对象维护一个运行时类型标识,这个信息会跟踪每个对象所属的类,虚拟机利用运行时类型信息选择要执行的正确方法,保存这些信息的类就是 Class,这是一个泛型类。

获取 Class 对象:① 类名.class 。② 对象的 getClass方法。③ Class.forName(类的全限定名)


序列化与反序列化

  • 序列化其实就是将对象转化成可传输的字节序列格式,以便于存储和传输。
  • 反序列化就是将字节序列格式转换成对象的过程。

Java是值传递还是引用传递

JVM 内存有划分为栈和堆,局部变量和方法参数是在栈上分配的,基本类型和引用类型都占 4 个字节,当然 long 和 double 占 8 个字节。

而对象所占的空间是在堆中开辟的,引用类型的变量存储对象在堆中地址来访问对象,所以传递的时候可以理解为把变量存储的地址给传递过去,因此引用类型也是值传递。


Integer缓存池

因为根据实践发现大部分的数据操作都集中在值比较小的范围,因此 Integer 搞了个缓存池,默认范围是 -128 到 127 。可以通过 JVM 参数修改缓存的最大值,最小值不能改。

Integer a = 127;
Integer a1 = 127;
System.out.println(a == a1);    //true

Integer b = 128;
Integer b1 = 128;
System.out.println(b == b1);    //false

异常

Exception 和 Error 都是继承了Throwable类,他们的区别如下:

  • Exception 是程序正常运行过程中可以预料到的意外情况,应该被开发者捕获并且进行相应的处理。
  • Error 是指在正常情况下不太可能出现的情况,绝大部分的 Error 都会导致程序挂掉。

最常见的RuntimeException:

  1. NullPointerException 空指针异常。(调用了未经初始化 或者 不存在的对象)
  2. NumberFormatException 字符串转换为数字异常。(字符串中包含非数字型字符)
  3. IndexOutOfBoundsException 数组角标越界异常。
  4. IllegalArgumentException 方法传递参数错误
  5. ClassCastException 数据类型转换异常

数据类型

基本数据类型

  • 1字节 = 8位二进制
//四种整数类型:
byte b = 127;                 //1字节
short s 32767;                  //2字节
int i = 0;                       //4字节
long l = 0L;                  //8字节,范围值比int更大

//两种浮点类型:
float f = 0.00F;             //4字节
double d = 0.00;             //8字节

//一种字符类型:
char c = 'a';                 //2字节

//一种布尔类型:
boolean bl = true;             //1字节

自动装箱/拆箱

每个基本数据类型都对应一个包装类,除了 int 和 char 对应 Integer 和 Character 外,其余基本数据类型的包装类都是首字母大写即可。

自动装箱: 将基本数据类型包装为一个包装类对象,例如向一个泛型为 Integer 的集合添加 int 元素。

自动拆箱: 将一个包装类对象转换为一个基本数据类型,例如将一个包装类对象赋值给一个基本数据类型的变量。

比较两个包装类数值要用 equals ,而不能用 ==


包装类的作用

我们知道Java是一个面向对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型。例如我们在使用集合类型时就一定要使用包装类型。

它相当于将基本类型“包装”起来,使得它具有对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。


包装类型与基本类型的区别

  1. 声明方式不同:包装类型需要用new关键字创建。
  2. 存储方式不同:基本类型直接将变量存储在栈中;而包装类型将对象放在堆中,通过引用来使用。
  3. 初始值不同:基本类型的初始值如int为0,boolean为false;而包装类型的初始值为null

String str="i"与 String str=new String("i")一样吗?

不一样。内存的分配方式不一样。

String str="i":JVM会将其分配到常量池中,如果常量池有i,则返回i的地址。没有就创建i

String str=new String("i"):被分配到堆内存中新开辟一块空间。


String s1 = new String("abc");这句话创建了几个字符串对象?

​ 将创建 1 或 2 个字符串。如果池中已存在字符串文字“abc”,则池中只会创建一个字符串“s1”。如果池中没有字符串文字“abc”,那么它将首先在池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象。


StringBuffer、StringBuilder的区别

  • 内存:

    String是不可变的对象;(被final修饰)

    StringBuffer、StringBuilder可以在原有对象的基础上进行操作;(可变)

  • 线程安全:

    StringBuffer线程安全,有同步锁
    StringBuilder线程不安全

  • 效率:StringBuilder > StringBuffer > String
  • 使用场景:

    String:操作少量数据,字符串不变时

    StringBuilder:单线程操作大量数据(方法内部,用完回收)

    StringBuffer:多线程操作大量数据(主要用在全局变量中)

StringBuffer和StringBuilder都继承自抽象类AbstractStringBuilder。抽象类AbstractStringBuilder内部都提供了一个自动扩容机制,当发现长度不够的时候(初始默认长度是16),会自动进行扩容工作,扩展为原数组长度的2倍加2。

ps:超过16个字符,尽可能指定容量,不指定会显著降低性能。


String不可变性

String 类和其存储数据的成员变量 value 字节数组都是 final 修饰的。对一个 String 对象的任何修改实际上都是创建一个新 String 对象,再引用该对象。只是修改 String 变量引用的对象,没有修改原 String 对象的内容。

好处:

① 节省常量池的内存空间。(执行String s = "abc"时,JVM会先到常量池找“abc”对象)

② 允许String对象缓存hashcode(不需要每次重新计算hashcode)

③ 安全性(String有许多地方当做参数,如果被人改变String引用指向的对象,会造成安全漏洞)


基本操作

== 和 equals 的区别是什么?

  • 功能不同

    • == 的作用:

      • 对于基本类型:比较的是值是否相同
      • 对于引用类型:比较的是引用是否相同
    • equals 作用:

      • equals本质就是==,只不过String和Integer等重写了equals方法,把它变成了值比较。(内容是否相等)
  • 定义不同

    • ==是一个运算符号,equals() 是java的一个方法

字符串反转

  • 利用StringBuilder自带的reverse()方法(要new一个StringBuilder对象,将代反转的字符串作为参数传入)
  • 调用toCharArray()将字符串转换为char[]数组,倒序遍历数组

        String s = "hello java";
    
        //调用toCharArray()将字符串转换为char[]数组,倒序遍历数组
        char[] array = s.toCharArray(); 
    
        StringBuilder s1 = new StringBuilder();    
    
        for (int i = array.length - 1; i >= 0; i--) {
            s1.append(array[i]); // 倒序遍历的字符添加进StringBuilder对象中
        }
    
        System.out.println(s1);    //输出反转数组

两个对象的 hashCode()相同,则 equals()也一定为 true吗?

不对,两个对象的 hashCode()相同,equals()不一定 true。("通话" 、 "重地")

而且equals方法可以被重写。


String 类的常用方法都有那些?

删:

方法 作用
trim() 删除字符串的头尾空白符
subString() 1、从指定位置截取到字符串结尾 2、截取指定范围的内容
String s = "HiHellojava";        //电脑从0开始计数
String s1 = s.substring(2, 7);    //截取Hello。从2的H开始截,结果不包含第7个位置的内容。

改:

方法 作用
valueOf(基本数据型态) 将基本数据型态转换成String(char[]也行,int[]不行)
toUpperCase() 字符转换为大写后的字符串。
toUpperCase() 字符转换为小写后的字符串。

查:

方法 作用
length() 返回字符串的长度
indexOf() 查找字符串开始出现的位置,返回下标,不存在返回-1。
charAt() 返回指定索引处的字符。索引范围为从 0 ~ length()-1
startsWith() 测试此字符串是否以指定的前缀开始。返回一个布尔值
endsWith() 测试此字符串是否以指定的后缀结束。返回一个布尔值

为啥重写equals要重写hashCode()?

例如在 HashMap 中,如果 key 重复的话,就会覆盖之前的值。

如果传入的key是一个我们创建的对象,重写hashcode方法能使他们的hashcode相同。重写equals方法能使他们用 equals() 比较能够判断相同。

但是HashMap判断覆盖的方法是先比较hashcode值再比较equals,如果我们不重写hashCode,就不能成功的覆盖key。


try...catch...finally情况

在 try 中的 return 真正返回之前会执行 finally 中的语句。如果 finally 中有 return,就不会执行 try 中的 return 了。

public static void main(String[] args) {
    System.out.println("main..." + getValue());        // 返回0
}

public static int getValue() {
    int i = 0;
    try {
        System.out.println("try...");
        return i;
    } finally {
        System.out.println("finally...");
        i++;
    }
}

上面例子返回的 i 值为 0,因为 try 中有 return ,会先将 try 中的变量放到本地变量表,finally中的操作不会影响 i 的值。(如果finally中也 return i,那就不会执行 try 中的 return了)

public static void main(String[] args) {
    System.out.println("main..." + getValue()); 
}

public static int getValue() {
    int i = 0;
    try {
        int result = 5 / 0;
    } catch (Exception e) {
        return i++;
    } finally {
        ++i;
    }
    return i;
}

try 出现异常,执行 catch,catch 有 return,先执行 finally 的内容,但是 finally 里的操作不会影响 i 的值。

i 还是为 0,最后执行 return i++,先返回 i ,再执行加一操作。所以方法返回的是 0

public static void main(String[] args) {
    System.out.println("main..." + getValue()); // 返回0
}

public static int getValue() {
    int i = 0;
    try {
        int result = 5 / 0;
    } catch (Exception e) {
        return i++;
    } finally {
        return ++i;
    }
}

在 catch 语句中,在执行 return 语句时,要返回的结果已经准备好了(i=1),就在此时,程序转到 finally 执行了,此时 return ++i 把原来存放到本地变量表的 i 加一并返回,返回的结果为 2

建议:① 不要在catch和finally块中有return语句;② 建议在finally中只进行资源的清理操作;

面向对象

重载和重写

重载指方法名称相同,但参数类型个数不同,是行为水平方向不同实现。对编译器来说,方法名称和参数列表组成了一个唯一键,称为方法签名,JVM 通过方法签名决定调用哪种重载方法。不管继承关系如何复杂,重载在编译时可以根据规则知道调用哪种目标方法,因此属于静态绑定。

JVM 在重载方法中选择合适方法的顺序:① 精确匹配。② 基本数据类型自动转换成更大表示范围。③ 自动拆箱与装箱。④ 子类向上转型。⑤ 可变参数。

重写指子类实现接口或继承父类时,保持方法签名完全相同,实现不同方法体,是行为垂直方向不同实现。

元空间有一个方法表保存方法信息,如果子类重写了父类的方法,则方法表中的方法引用会指向子类实现。父类引用执行子类方法时无法调用子类存在而父类不存在的方法。

重写方法访问权限不能变小,返回类型和抛出的异常类型不能变大,必须加 @Override


Object类

equals:检测对象是否相等,默认使用 == 比较对象引用。可以重写 equals 方法自定义比较规则。x.equals(null) 返回 false。

hashCode:根据对象的地址或者内容算出来的int类型的数值,值可能相同。为了在集合中正确使用,一般需要同时重写 equals 和 hashCode,要求 equals 相同 hashCode 必须相同。支持此方法是为了提高哈希表的性能。

toString:打印对象时默认的方法,如果没有重写打印的是表示对象值的一个字符串。

clone:naitive方法,用于创建并返回 当前对象的一份拷贝。

finalize:方法将在垃圾回收器清除对象之前调用,但具有不定性

getClass:返回包含对象信息的类对象。

wait:让线程处于等待状态,锁会释放

notify / notifyAll :唤醒在等待队列的某个 / 所有线程


final 修饰符

  1. 修饰类:表示该类不能被继承 (谨慎选择)
  2. 修饰方法:表示方法不能被重写
  3. 修饰变量:表示变量只能一次赋值以后值不能被修改(常量)

​ 引用变量被final修饰之后,虽然不能再指向其他对象(指向内存地址不会变),但是该内存地址中保存的对象信息, 是可以进行修改的.


接口和抽象类

语法维度 抽象类 接口
成员变量 无特殊要求 默认 public static final 常量
构造方法 有构造方法,不能实例化 没有构造方法,不能实例化
方法 抽象类可以没有抽象方法,但有抽象方法一定是抽象类。 默认 public abstract,JDK8 支持默认/静态方法,JDK9 支持私有方法。
继承 单继承 多继承

抽象类不能用final修饰,因为定义抽象类就是为了让其他类继承的。


访问权限控制符

访问权限控制符 本类 包内 包外子类(继承关系) 任何地方
public
protected ×
× ×
private × × ×

子类初始化顺序

  1. 父类静态代码块和静态变量。
  2. 子类静态代码块和静态变量。
  3. 父类普通代码块和普通变量。
  4. 父类构造方法。
  5. 子类普通代码块和普通变量。
  6. 子类构造方法。
先静态后普通、先父后子(普通成员变量、普通代码块、构造函数按照代码先后顺序执行)

线程

多线程的优缺点

优点

1、加快程序的运行速度,使程序的响应速度更快。
2、可以把占据长时间的程序中的任务放到后台去处理,同时执行其他操作,提高效率
3、可以随时停止任务
4、可以分别设置各个任务的优先级

缺点

1、线程切换会消耗系统内存
2、同一进程中某一个多线程结束时,其他线程也要结束
3、由于多个线程之间存在共享数据,因此容易出现线程死锁的情况
4、对线程的管理需要额外的 CPU 开销


线程是越多越好吗?

不是,线程多了可以提高程序的并行执行速度,但不是越多越好。

线程越多,就意味着更多的内存资源被占用。而且,一个CPU不是同时执行多个线程的,而是轮流执行的,线程太多,CPU必须不断的在各个线程间快速更换,线程间的切换消耗了许多时间,导致CPU利用率反而下降了。


线程的生命周期

new:新建状态,线程被创建且未启动,此时还未调用 start 方法。

runnable:Java 将操作系统中的就绪和运行两种状态统称为 RUNNABLE,此时线程有可能在等待时间片,也有可能在执行。

blocked:阻塞状态,可能由于锁被其他线程占用、调用了 sleepjoin 方法、执行了 wait 方法等。

waiting:等待状态,该状态线程不会被分配 CPU 时间片,需要其他线程通知或中断。可能由于调用了无参的 waitjoin 方法。

time_waiting:限期等待状态,可以在指定时间内自行返回。可能由于调用了带参的 waitjoin 方法。

terminated:终止状态,表示当前线程已执行完毕或异常退出。


线程的创建方式

① 继承 Thread 类并重写 run 方法。实现简单,但不符合里氏替换原则,不可以继承其他类。

② 实现 Runnable 接口并重写 run 方法。避免了单继承局限性,编程更加灵活,实现解耦。

③实现 Callable 接口并重写 call 方法。可以获取线程执行结果的返回值,并且可以抛出异常。


关于线程的几种方法

sleep 方法会导致当前线程进入休眠状态,与 wait 不同的是该方法不会释放锁资源,进入的是 TIMED-WAITING 状态。

yiled 方法使当前线程让出 CPU 时间片给优先级相同或更高的线程,回到 RUNNABLE 状态,与其他线程一起重新竞争CPU时间片。

join 方法用于等待其他线程运行终止,如果当前线程调用了另一个线程的 join 方法,则当前线程进入阻塞状态,当另一个线程结束时当前线程才能从阻塞状态转为就绪态,等待获取CPU时间片。底层使用的是wait,也会释放锁。


线程通信的方式

volatile 告知程序任何对变量的读需要从主内存中获取,写必须同步刷新回主内存,保证所有线程对变量访问的可见性。

synchronized 确保多个线程在同一时刻只能有一个处于方法或同步块中,保证线程对变量访问的原子性、可见性和有序性。

等待通知机制指一个线程 A 调用了对象的 wait 方法进入等待状态,另一线程 B 调用了对象的 notify/notifyAll 方法,线程 A 收到通知后结束阻塞并执行后序操作。对象上的 waitnotify/notifyAll 如同开关信号,完成等待方和通知方的交互。

管道 IO 流用于线程间数据传输,媒介为内存。PipedOutputStream 和 PipedWriter 是输出流,相当于生产者,PipedInputStream 和 PipedReader 是输入流,相当于消费者。管道流使用一个默认大小为 1KB 的循环缓冲数组。输入流从缓冲数组读数据,输出流往缓冲数组中写数据。当数组已满时,输出流所在线程阻塞;当数组首次为空时,输入流所在线程阻塞。

ThreadLocal 是线程共享变量,但它可以为每个线程创建单独的副本,副本值是线程私有的,互相之间不影响。


你可能感兴趣的:(java)