Java八股文系列之基础篇

基本数据类型

类型范围所占字节
boolean
byte-128 ~ 1271
char-2^15 ~ 2^15 - 12
short-2^15 ~ 2^15 - 12
int-2^31 ~ 2^31 - 14
float-3.40292347E+38~3.40292347E+384
long-2的63次方到2的63次方-18
double8

类型转换关系: byte -> char(short) -> int -> long -> float -> double

包装类型

能够将简单类型的变量表示一个类,在执行变量类型的相互转换时,我们会大量使用这些包装类。

用途有:

1.作为基本数据类型对应的类类型,提供了一系列实用的对象操作,如类型转换,进制转换等。
2.集合不允许存放基本数据类型,故常用包装类。
3.包含了基本数据类型的相关属性,如最大值,最小值,所占位数等。

包装类都是final修饰的,不可被继承。

包装类型都继承了Number抽象类

自动拆装箱

Integer a = 1; //装箱,调用了 Integer.valueOf(1);
int b = a; //拆箱,调用了 Integer.intValue();

new Integer(1); 和 Integer.valueOf(1); 区别:

new Integer(1); 每次都会创建一个对象。
Integer.valueOf(1); 会使用缓冲池中的对象,多次调用会取得同一个对象的引用。

valueOf() 方法实现:先判断值是否在缓冲池中,如果在直接返回缓冲池的内容。

缓冲池是什么

包装类型内存使用 private static class xxxCache, 声明一个内部使用的缓冲池。

如Integer中有个静态内部类IntegerCache, 里面有个cache[],也就是Integer常量池,常量池大小为一个byte(-128-127)。那么为啥设置为[-128 - 127],性能与资源的权衡,在jdk8所有的数值类缓冲池中,Integer的缓冲池IntegerCache	很特殊,这个缓冲池的下界是-128,上届是127,但是这个上届是可调整的,在jvm启动的时候,通过-XX:AutoBoxCacheMax = 来指定这个缓冲池上届大小。

BigDecimal

BigDecimal主要用于处理精度丢失的问题。

float 和 double 主要为了科学计算和工程计算而设计的。执行二进制浮点运算,这是为了在广泛的数字范围上提供较为精确地快速近似计算而精心设计的。然而,他们并没有提供精确结果。

float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
System.out.println("a : " + a);
System.out.println("b : " + b);
System.out.println(a == b);
        
运行结果
a : 0.100000024
b : 0.099999964
false

String

String是final修饰的,因此不可被继承。

jdk8内部使用char数组存储数据。
jdk9之后改用byte数组存储字符串,同时使用coder来标识使用了哪种编码。

对String 对象的任何改变都不会影响到原对象,想的改变操作都会生成新的对象。

不可变的好处:

1、可以缓存Hash值。
2、String pool的需要。如果一个String 对象已经被创建过,那么就可以直接从String pool 中获取。只有String 不可变才能实现。
3、安全性。String 是经常被使用的类型,String不可变可以保证参数不可变,如网路传输。

new String(“xxf”); 创建一个或者两个对象(String pool 中没有的话)。

jdk8字符串常量池是放在方法区中。

intern()方法:

当一个字符串调用intern()方法时,如果String pool中已经存在一个相等的(equals()判定),那么就会返回String pool中字符串引用。否则,就会在String pool中添加一个新的字符串,并返回这个字符串的引用。

String、 StringBuilder、 StringBuffer

类型是否可变线程是否安全
String不安全
StringBuilder不安全
StringBuffer安全

效率:

StringBuilder > StringBuffer > String

这个不是绝对的,例如 String s = “a” + “b”;的效率就比StringBuilder().append(“a”).append(“b”)高。

推荐使用场景:

少量字符串操作建议使用String
单线程操作大量字符串建议使用StringBuilder
多线程操作大量字符串建议使用StringBuffer

AbstractStringBuilder的扩容:

jdk8中 StringBuilder 的内部数据结构也是char数组,扩容的大小为原来的2倍然后加2 
int newCapacity = (value.length << 1) + 2;
这样做的原因我想是因为,我们操作StringBuilder拼接字符串的时候往往会增加分隔符,例如 逗号。这样可能会导致再次扩容,导致效率降低。

final关键字

范围作用
数据声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量
方法不能被重写
不可被继承

作用于基本类型: 数值无法改变。

作用于引用类型:引用不能变,不能引用其他对象,但是被引用的对象的本身数据是可以修改的。

static 关键字

静态变量:也称作类变量,可以通过类名直接访问。静态变量在内存中只会存一份(方法区中)。对于静态变量

存储方法区中,静态对象方法区只存引用,对象存储在堆中。

静态方法: 静态方法在类加载的时候就被加载了,不会依赖任何实例。所以静态方法必须有实现,不能是抽象方法。

静态代码块:在类初始化时候运行一次。

静态内部类:非静态内部类依赖于外部类的实例,也就是说需要创建外部类的实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。

public class OuterClass {

	//非静态内部类
    class InnerClass {
    }

	//静态内部类
    static class StaticInnerClass {
    }

    public static void main(String[] args) {
        // InnerClass innerClass = new InnerClass(); 
        // 'OuterClass.this' cannot be referenced from a static context
        OuterClass outerClass = new OuterClass();
        InnerClass innerClass = outerClass.new InnerClass();
        StaticInnerClass staticInnerClass = new StaticInnerClass();
    }
}

类初始化顺序:

静态变量和静态语句块优先于实例变量和普通语句块,静态语句块和静态实例变量取决于在代码中的位置。

public class Father {

    //顺序1
    public static String staticFiled = "staticFiled";

    //顺序2
    static {
        System.out.println("静态语句块");
    }

    //顺序3
    public static String filed = "filed";

    //顺序4
    {
        System.out.println("父类语句块");
    }

    //顺序5
    public Father() {
        System.out.println("父类空参构造器");
    }

    public Father(String s) {
        System.out.println("父类空有参参构造器");
    }
}

存在继承关系的话

1、父类的静态变量和静态语句块
2、子类的静态变量和静态语句块
3、父类的实例变量和普通语句块
4、父类的构造函数
5、子类的实例变量和普通语句块
6、子类的构造函数

Object 方法

public native int hashCode()
public boolean equals(Object obj)
protected natived Object clone() throws CloneNotSupportException
public String toString()
public final native Class<?> getClass()
protected void finalize() throws Throwable
//线程相关
public final native void notify()
public final native void notifyAll()
public final native void wait(long timeout) throws InterruptedException
public final native void wait(long timeout, int nanos) throws InterruptedException
public fianl native void wait() throws InterruptedException

equals 方法:

基本类型: == 是判断两个值是否相等,基本类型没法用equals
引用类型: == 是判断对象的引用是否相等,equals判断两个对象是否等价。

equals 方法重写:

 @Override
 public boolean equals(Object o) {
 	 //1、检查是否为同一个引用,是则返回 true
     if (this == o) return true;
     //2、检查是否是同一类型,不是则返回 false
     if (o == null || getClass() != o.getClass()) return false;
     //3、将 Object 对象进行转型
     User user = (User) o;
     //4、判断每个关键域是否相等
     return Objects.equal(id, user.id) &&
            Objects.equal(userName, user.userName) &&
            Objects.equal(phone, user.phone) &&
            Objects.equal(account, user.account) &&
            Objects.equal(createTime, user.createTime) &&
            Objects.equal(password, user.password);
 }

hashcode方法:

jdk	根据对象的内存地址值或者属性值计算出来的int类型的数值。

hashcode规定:
1、在java应用程序执行期间,如果在equals方法比较中所用的信息没有被修改,那么在同一个对象上多次调用hashCode方法时必须一致地返回相同的整数。如果多次执行同一个应用时,不要求该整数必须相同。
2、如果两个对象通过调用equals方法是相等的,那么这两个对象调用hashCode方法必须返回相同的整数。
3、如果两个对象通过调用equals方法是不相等的,不要求这两个对象调用hashCode方法必须返回不同的整数。但是程序员应该意识到对不同的对象产生不同的hash值可以提高哈希表的性能。

重写equals方法必须重写hashcode方法

由于hashCode方法默认是通过Object对象的地址计算出来的,由于两个对象的hashcode值(内存地址不同)是不相同的,如果不重写 hashcode 会违背上面第二条规定。

clone() 方法

主要用于对象拷贝。类需要去实现Cloneable接口(implements Cloneable)
浅拷贝:返回同一个引用对象。
深拷贝:返回原始对象的引用类型,引用不同对象(开辟一个新的内存空间)

访问修饰符

作用域同一类同一包子类不同包
public可见可见可见可见
protected可见可见可见不可见
private可见不可见不可见不可见
不写可见可见不可见不可见

面向三大特性:

封装:对象的属性信息隐藏在对象内部,不允许外部对象直接访问。
继承:不同类型直接经常会有一些共同点。
多态:一个对象具有多种状态。具体表现为父类的引用指向子类的实例。

重写和重载

重写: 
存在集成关系,子类重写父类方法。重写方法使用 @Override 注解

重写限制:
1、子类的访问权限必须大于父类。
2、子类方法的返回类型必须是父类的返回类型或者子类型
3、子类型方法抛出的异常必须是父类方法抛出类型或者子类型。

重载:在同一个类中,方法名相同参数不同(类型,个数,顺序其中之一不同);返回类型、修饰符可以相同,也可不同。

接口和抽象类

抽象类

抽象类和抽象方法都需要用 abstract 修饰。

如果一个类中包含抽象方法,这个类必须是抽象类。

接口

interface修饰类

接口的成员(方法和变量)默认都是public,不允许定义为 private和protected

接口的成员变量都是 final static 的。

Jdk8 的接口可以有default 修饰 默认的实现方法, jdk8之前是完全抽象的。

区别

一个类只能继承单个抽象类 ,但可以实现多接口。

接口 成员变量只能final static ,抽象类无限制。

接口的成员只能是public,抽象类无限制

jdk8 后接口除了default都必须是抽象方法,抽象类可以有非抽象方法。

异常

分为两种: Error 和 Exception ,他们都继承 Throwable。

Error: JAVA虚拟机无法处理的错误。

Exception: 没有Error那么严重, 一般是程序异常,可以修复。

Exception分类:

受检异常:需要用try catch 语句捕获并处理,使异常恢复。
运行时异常:程序运行时错误,例如 NullPointException,ClassCastException, IndexOutOfBoundException

异常处理:

try {
 //语句块内发生异常就会抛出
} catch(xxException e) {
//捕获try抛出的异常
} finally {
//总是会被执行的语句,一般如资源的关闭都会在这里处理
}
程序不会中断。

throw: 用于抛出异常,一般在方法内使用,throw new RuntimeException("XX异常:" + e);
throws: 方法抛出异常,方法出现异常后,程序会中断执行,并把异常向上抛出。

泛型

泛型的本质是类型参数化,也就是所操作的数据类型被指定为一个参数。

为什么要有泛型:

早期Java是使用Object来代表任意类型的,但是向下转型有强转的问题,这样程序就不太安全。

JAVA中集合对元素的类型是没有任何限制的。假如没有泛型,我们往集合中放入一个A类型对象,然后放入一个B类型对象,这样不会报任何语法错误。由于集合是不知道元素的类型是什么的,仅仅知道是Object。因此在get()的时候,返回的是Object。外边获取该对象,还需要强制转换。
有了泛型的话:
1、代码就会更加简洁(不用强制类型转换了)
2、编译时期就会给予警告,不会出现ClassCastException异常
3、可读性也加强了,代码编写的时候就知道集合存储的类型。

泛型使用方式: 泛型类,泛型接口,泛型方法

限定通配符:
<? extends T> 确保类型必须是T的子类型的上届。
<? supper T> 确保类型必须是T父类的下界
泛型必须使用限定内的类型初始化,否则会导致编译错误。
非限定通配符:
可以任意类型替代。

泛型擦除

JAVA泛型都是在编译器层面上的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。

例子: 在代码中定义List<String>List<Integer>等类型,在编译后都会变成List

public class Test {

    public static void main(String[] args) {
		//只能存储字符串
        ArrayList<String> list1 = new ArrayList<String>();
        list1.add("abc");

        //只能存储整数
        ArrayList<Integer> list2 = new ArrayList<Integer>();
        list2.add(123);

        //对比他们类型信息,返回true。说明String和Integer都被擦除了,都是原始类型
        System.out.println(list1.getClass() == list2.getClass());
    }

}

注解

注解用 @interface 修饰

四种元注解:

1、 @Documented 注解加入到javaDoc中
2、 @Retention: 作用时刻
		1)RententionPolicy.SOURCE: 在编译期就丢弃,这些注解在编译结束后就没有实际意义,也不会被写入字节码文件。如: @Override , @SuppressWarning。
		2)RententionPolicy.CLASS: 类加载的时候丢弃,字节码处理中有用。默认方式。
		3)RententionPolicy.RUNTIME: 始终不会丢弃,运行期也保留。可以通过反射技术获取。我们自己定义注解的时候通常使用这种方式。
3、@Target : 作用位置
		1)ElementType.CONSTRUCTOR: 构造器
		2)ElementType.FIELD: 类的成员变量、对象、属性(包括枚举)。
		3)ElementType.LOCAL_VARIABLE: 描述局部变量,RetentionPolicy.SOURCE情况下才有用。不常用。
		4)ElementType.METHOD: 方法
		5)ElementType.TYPE: 类、接口、枚举、注解 如 @Service @RestController
		6)ElementType.PARAMETER: 参数上 如@Valid , @ResquestBody
		7) ElementType.PACKAGE: 包
4、@Inherited: 是否允许子类集成该注解,是一个标记注解。表示标注的类型是被继承的。如果一个标注了@Inherited的注解作用于一个类,则表示这个注解作用于这个类的子类。

编写注解的规则:

1、成员参数只能是public 或默认的default
2、成员类型只能用八种基本数据类型和String、Enum、Class、annotations以及这些类型的数组。
3、获取注解必须使用JAVA反射技术来获取。

枚举

用 enum 修饰, 因为枚举类在jvm编译成class文件后,实际编译成 final的class,也就是在堆中是同一个对象,所以枚举类的比较用 == ,用equals也是可以的。因为枚举的equal还是 == 比较。

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

反射

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

反射的优缺点

优点:
1)可扩展性:应用程序可以利用全限定名创建对象实例。
缺点:
性能开销:反射设计动态解析,所以JVM无法对这些代码优化。反射的对象可能会比非反射低。
内部暴露:破坏了封装性。

反射的几种使用方式

        //1、全类名的方式
        try {
            Class clazz = Class.forName("com.example.demo.test.Children");
            Children c1 = (Children)clazz.newInstance();
            c1.p();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }

        //2、类名的方式
        Class<Children> clazz2 = Children.class;
        try {
            Children children2 = clazz2.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }

        //3、对象的方式
        Children children3 = new Children();
        Class clazz3 = children3.getClass();

下期预告:JAVA八股文系列之基础篇二——集合篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值