用亿点时间重学部分Java基础

从笔者个人角度, 针对Java基础中的一些生僻难点进行了归类整理, 希望可以帮到有需要的兄弟们

1 关键字

1.1 private

类中所有的private方法都隐式地指定为final

1.2 static

static修饰的变量永远不会被序列化

类中被static修饰的内容建议通过类名进行调用

通过静态导入import static导入的静态成员, 可以直接使用, 不需要再通过类名调用

1.3 strictfp

strictfp关键字可以应用于方法/类/接口, 不能应用于抽象方法/变量/构造函数, 确保在每个平台上获得相同的结果

常用于浮点数相关内容, 防止因平台不同导致数据精度不一致

Java 17中解决了浮点指令问题, 已移除此关键字

1.4 transient

对于不想序列化的变量, 使用transient

需要注意以下内容:

  • 只能修饰变量, 不能修饰类和方法
  • 修饰的变量, 在反序列化后变量值将会被置成类型的默认值. 例如, 如果是修饰 int 类型,那么反序列化后结果就是 0

2 基本类型

2.1 8种基本类型介绍

基本类型位数字节默认值
int3240
short1620
long6480L
byte810
char162'u0000'
float3240f
double6480d
boolean1false

其中boolean依赖于JVM厂商的具体实现, 理论上占1位, 可能出现不同

2.2 精度丢失问题

floatdouble都会丢失精度, 原因是数据转换为二进制可能会出现无限循环, 超出储存长度

在涉及金额等极为敏感的浮点数据时, 请使用BigDecimal来处理运算

在创建BigDecimal时, 应使用字符串参数或valueOf方法:

// YES!!
new BigDecimal("0.1");
BigDecimal.valueOf(0.1f);
// NO!! 会丢失精度
new BigDecimal(0.1f);
复制代码

2.3 包装类型

基本类型都有对应的包装类型, 其存在的意义在于允许null值含义

举个例子, 学生参加考试但成绩为0没有参加考试成绩为null是两种情形, 基本类型int没办法体现, 其包装类型Integer则可以

包装类型除FloatDouble外都存在常量池, 所以在比较时应使用equals方法而不是==:

// 常量池中存在一个Integer对象, 其值为1
Integer i1 = 1;
Integer i2 = 1;
Integer i3 = new Integer(1);
// 结果为true
i1 == i2;
// 结果为false
i1 == i3;
// 结果为true
i1.equals(i3);
复制代码

3 函数

3.1 函数签名

函数签名是函数在一个类中的唯一标识

内容包括方法名/参数类型/参数名, 不包括返回值

3.2 重载

发生在同一个类中(或者父类和子类之间), 方法名必须相同,参数类型不同/个数不同/顺序不同,方法返回值和访问修饰符可以不同

那可以存在方法名相同/参数相同但返回值不同的重载吗?

答案是不行, 3.1中的函数签名不包括返回值, 所以方法名相同/参数相同但返回值不同的方法会被认为是同一方法, 不能进行重载

3.3 重写

方法名/参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类

如果方法的返回值类型是 void 和基本数据类型,则返回值重写时不可修改

如果方法的返回值类型是引用类型,重写时可以返回该引用类型的子类

3.4 try-catch-finally

try语句和finally语句中都有return时, finally语句的返回值将会覆盖原始的返回值

换而言之, finally语句一定会执行

// 返回为0
try {
    return 1;
} catch (Exception e) {
    // ...
} finally {
    return 0;
}
复制代码

3.5 try-with-resource

适用于任何实现 java.lang.AutoCloseable或者 java.io.Closeable 的对象, 会自动调用close方法

4 面向对象

4.1 对象构建顺序

  1. 静态代码块
  2. 非静态代码块
  3. 构造方法

4.2 静态代码块

public class Person {

    static {
        // 静态代码块
    }
    
}
复制代码

静态代码只会在类的初始化步骤执行一次, 具体在代码中写法为:

  • new Person()
  • Class.forName("cn.houtaroy.models.Person")

举个具体的例子:

package cn.houtaroy.models;

public class Person {

    static {
        System.out.println("静态代码块执行");
    }
    
    public static void main(String[] args) {
        try {
            new Person();
            Class.forName("cn.houtaroy.models.Person");
            new Person();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
}
复制代码

上述代码只会有一次输出: 静态代码块执行

可以理解为: 静态代码块中定义的是不同对象共性的初始化内容

一个类中的静态代码块可以有多个,会按照它们出现的先后顺序依次执行

public class Person {

    static {
        // 先执行
    }
    static {
        // 再执行
    }
    
}
复制代码

静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问:

public class Person {

    static {
        // 可以赋值
        age = 0;
        // IDE报错, 无法访问
        System.out.printf("出生年龄: %d", age);
    }
    
    public static Integer age;
    
}
复制代码

4.3 静态内部类

  1. 不需要依赖外围类的创建
  2. 不能使用任何外围类的非静态成员变量和方法

静态内部类可用于实现单例模式, 优点是延迟初始化JVM提供的线程安全支持 :

public class PersonFactory {

    // 私有构造方法, 防止在外部调用
    private PersonFactory() {
        
    }

   // 私有静态内部类, 无法被外部访问
    private static class PersonFactoryHolder {
        private static final PersonFactory INSTANCE = new PersonFactory();
    }

    // 静态方法, 调用私有静态内部类获取唯一实例
    public static PersonFactory getInstance() {
        return PersonFactoryHolder.INSTANCE;
    }
    
}
复制代码

4.4 hashCodeequals

hashCode用于判断对象的hash值是否相同

equals用于判断对象是否相同

在用到hash的地方都会使用hashCodeequals, 例如HashMap. 所以在重写时, 请务必使二者的返回结果保持一致

4.5 值传递

Java中只有值传递, 哪怕是在面向对象, 举个具体的例子:

public class Test {

    public static void main(String[] args) {
        Student s1 = new Student("小张");
        Student s2 = new Student("小李");
        Test.swap(s1, s2);
        System.out.println("s1:" + s1.getName());
        System.out.println("s2:" + s2.getName());
    }

    public static void swap(Student x, Student y) {
        Student temp = x;
        x = y;
        y = temp;
        System.out.println("x:" + x.getName());
        System.out.println("y:" + y.getName());
    }
    
}
复制代码

输出结果为:

x:小李
y:小张
s1:小张
s2:小李
复制代码

可以看到, 在函数内部xy进行了交换, 但外部的s1s2并没有发生变化

所以这四个变量代表的含义是:

  • s1: 学生小张对象的引用的值
  • s2: 学生小李对象的引用的值
  • x: s1的深拷贝
  • y: s2的深拷贝

因为xy是深拷贝, 所以无论它们如何变化, 都不会影响原来的s1s2

4.6 this/super与静态

两者的概念范畴完全不同:

  • 静态方法是类范畴的概念
  • this/super是对象范畴的概念

4.7 Objects.equals

在进行比较时(并非类, 全部内容均可)推荐使用Objects.equals:

// 均为false, 且不会抛空指针异常
Objects.equals("Houtaroy", null);
Objects.equals(null, "Houtaroy");
复制代码

5 枚举

5.1 使用枚举相关集合

在将枚举作为元素或键值等使用时, 推荐使用枚举相关集合, 例如EnumSetEnumMap:

// 创建EnumSet
EnumSet<Gender> set = EnumSet.of(Gender.MAN);
// 创建EnumMap
EnumMap<Gender, String> map = new EnumMap<>(Gender.class);
map.put(Gender.MAN, "男人");
复制代码

5.2 实现设计模式

单例模式

利用枚举可以更简洁/高效/安全的实现单例模式, 且由JVM提供保障

public enum PersonFactory {

    INSTANCE;

    PersonFactory() {
      // 实现人员工厂初始化
      person = PersonCheckEntity.builder().id("test").build();
    }

    private PersonCheckEntity person;

    public static PersonFactory getInstance() {
      return INSTANCE;
    }

    public PersonCheckEntity getDeliveryStrategy() {
      return this.person;
    }
    
}
复制代码

策略模式

public enum PersonStrategy {

    STAND {
        @Override
        public void advance(Person person) {
            System.out.println("向前迈了一步");
        }
    },
    SIT {
        @Override
        public void advance(Person person) {
            System.out.println("向前爬了一截");
        }
    };
 
    public abstract void advance(Person person);
    
}

public class Person {
    
    private PersonStrategy status;
    
    public void advance() {
        status.advance(this);
    }
    
}
复制代码

状态模式

public enum PersonStrategy {

    STAND {
        @Override
        public void walk(Person person) {
            System.out.println("向前迈了一步");
        }
    },
    SIT {
        @Override
        public void walk(Person person) {
            System.out.println("坐着没办法走路, 站起来");
            person.setStatus(PersonStrategy.STAND);
            person.walk();
        }
    };
 
    public abstract void walk(Person person);
    
}

public class Person {
    
    private PersonStrategy status;
    
    public void walk() {
        status.walk(this);
    }
    
}
复制代码

6 反射

6.1 什么是反射

反射是框架的灵魂

它赋予了程序在运行过程中分析和使用类概念的能力, 使我们脱离最基本的业务逻辑, 站在更高的维度去处理和思考问题

Java中的利器注解便用到了反射

6.2 获取Class对象的四种方式

Class对象可以理解为类的描述

具体类

Class myClass = Target.class;
复制代码

Class.forName

Class myClass = Class.forName("cn.houtaroy.models.Target");
复制代码

对象实例

Target object = new Target();
Class myClass = object.getClass();
复制代码

类加载器

Class myClass = ClassLoader.loadClass("cn.houtaroy.models.Target");
复制代码

通过类加载器获取的Class不会执行初始化, 意味着不进行包括初始化等一系列步骤,静态块和静态对象不会执行

6.3 具体操作

创建目标类:

public class Target {

    private String value;

    public void say(String name) {
        System.out.printf("I love %s%n", name);
    }

    private void classValue() {
        System.out.printf("value is %s%n", value);
    }
    
}
复制代码

进行反射操作:

public class ExampleUtils {
    
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
        NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class<?> targetClass = Class.forName("cn.houtaroy.models.Target");
        Object targetObject = targetClass.newInstance();
        Method[] methods = targetClass.getMethods();
        for (Method method : methods) {
            System.out.printf("拥有方法: %s%n", method.getName());
        }
        Field[] fields = targetClass.getFields();
        for (Field field : fields) {
            System.out.printf("拥有字段: %s%n", field.getName());
        }
        targetClass.getDeclaredMethod("say", String.class).invoke(targetObject, "Java");
        Field valueField = targetClass.getDeclaredField("value");
        valueField.setAccessible(true);
        valueField.set(targetObject, "test");
        Method classValueMethod = targetClass.getDeclaredMethod("classValue");
        classValueMethod.setAccessible(true);
        classValueMethod.invoke(targetObject);
    }
    
}
复制代码

运行结果为:

拥有方法: say
拥有方法: wait
拥有方法: wait
拥有方法: wait
拥有方法: equals
拥有方法: toString
拥有方法: hashCode
拥有方法: getClass
拥有方法: notify
拥有方法: notifyAll
I love Java
value is test
复制代码
  • getMethodsgetFields方法只会读取public属性
  • 访问私有属性时使用getDeclaredMethodgetDeclaredField, 并调用setAccessible(true)修改其可使用性

从上述内容即可看出, 利用反射会带来一定程度的安全隐患

7 Java中的IO

程序I/O可分解为如下操作:

  1. 程序向操作系统发起I/O调用请求
  2. 系统内核等待I/O设备准备好数据
  3. 系统内核将数据从内核空间拷贝到用户空间

7.1 Blocking I/O

即同步阻塞I/O, 程序会一直等待到I/O操作执行完成

7.2 Non-blocking I/O

即I/O多路复用模型

同步非阻塞I/O使用轮询方式, 会非常消耗CPU资源, 在Web开发中几乎无法使用

I/O多路复用模型利用一个线程来管理, 通过减少无效的系统调用,减少了对 CPU 资源的消耗

7.3 Asynchronous I/O

即异步I/O模型, 通俗点就是系统内核完成后会执行程序指定的回调函数

8 小细节

8.1 StringBuilderStringBuffer区别

StringBuilder线程不安全

StringBuffer线程安全

8.2 字节流与字符流

字节(Byte)是计量单位,表示数据量多少,是计算机信息技术用于计量存储容量的一种计量单位,通常情况下一字节等于八位

字符(Character)是计算机中使用的字母、数字、字和符号, 在不同的编码中占用的字节数量不同

Java中存在字节流为什么还要提供字符流呢?

因为在不知道字符编码类型时, 使用字节流很容易出现乱码问题

音频文件、图片等媒体文件用字节流较好,如果涉及到字符的话使用字符流较好


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值