java面试系列<1>——java基础

面试系列——java基础

1、数据类型

基本类型

byte/8、short/16、int/32、long/64、boolean/ 、char/16、float/32、double/64

boolean只有两个值,true、false,可以使用1bit来存储,但是具体大小没有明确规定,JVM会在编译时期将boolean类型转为int,使用1来表示true,0表示false。JVM支持boolean数组,但是是通过读写byte数组来实现的。

包装类型

基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。

Integer x=2   //装箱,调用了Integer.valueOf(2)
int y=x     //拆箱 调用了 X.intValue()

缓存池

new Integer(123)与Integer.valueOf(123)的区别在于:

  • new Integer(123)每次都会新建一个对象
  • Integer.valueOf(123)会使用缓存池中的对象,多次调用会取得同一对象的引用。
Integer x = new Integer(123);
Integer y = new Integer(123);
System.out.println(x == y);    // false
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z == k);   // true

valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。

2、String

概览

String被声明为final,因此它不可被继承。(Integer等包装类也不能被继承)

在java8中,String 内部使用char数组存储数据。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}

在java9之后,String类的实现改用byte数组存储字符串,同时使用coder来标识使用了那种编码。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final byte[] value;

    /** The identifier of the encoding used to encode the bytes in {@code value}. */
    private final byte coder;
}

value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。

不可变的好处

1、可以缓存hash值

因为String的hash值经常被使用,例如String用作HashMap的key。不可变的特性可以使得hash值也不可变,因此只需要进行一次计算。

2、String Pool的需要

字符串常量池,如果一个String对象已经被创建过了,那么会从String Pool中取得引用。只有String是不可变的,才可能使用String Pool。

3、安全性

String经常作为参数,String不可变性可以保证参数不可变。

4、线程安全

String不可变性天生具备线程安全,可以在多个线程中安全地使用。

String Pool

字符串常量池(String Pool)保存这所有字符串字面量,这些字面量在编译时期就确定。不仅如此,还可以使用String的intern()方法在运行过程中将字符串添加到String Pool中。

当一个字符串调用intern()方法时,如果String Pool中已经存在一个字符串和该字符串值相等(使用equals方法进行确定),那么就会返回String Pool中字符串的引用;否则,就会在String Pool中添加一个新的字符串,并返回这个新字符串的引用。

下面实例:s1和s2采用new String()的方式新建了两个不同字符串,而s3和s4是通过s1.intern()和s2.intern()方法取得同一个字符串引用。intern()首先把“aaa”放到String Pool中,然后返回这个字符串的引用,因此s3和s4引用的是同一字符串。

String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2);           // false
String s3 = s1.intern();
String s4 = s2.intern();
System.out.println(s3 == s4);           // true

如果采用"bbb"这种字面量的形式创建字符串,会自动地将字符串放入String Pool中。

String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6);  // true

new String("abc")

问题,使用这种方式一共会常见两个字符串对象(前提是String Pool中还没有"abc"字符串对象)

  • abc属于字符串字面量,因此编译时期会在String Pool中创建一个字符串对象,指向这个"abc"字符串字面量
  • 而使用new的方式会在堆中创建一个字符串对象。

3、运算

参数传递

java的参数是以值传递的形式传入方法中,而不是引用传递。

在给方法传入参数时候,如果传入对象,对象名其实代表地址,即将对象的地址传给该方法。在运行时候可以对对象进行改变。

在方法中改变对象的字段值会改变原对象该字段值,因为引用的是同一个对象。

float与double

java不能隐式执行向下转型,因为这会使得精度降低

  • 字面量属于double类型,不能直接将1.1直接赋值给float变量,因为这是向下转型。

    float f=1.1  //这会编译错误
    
  • 1.1f字面量才是float类型

    float f=1.1f
    

隐士类型转换

因为字面量1是int类型,它比short类型精度要高,因此不能隐式地将int类型向下转型为short类型

4、关键字

final

1、数据

声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。

  • 对于基本类型,final使数据不变
  • 对于引用类型,final使引用不变,也就不能引用其他对象,但是被引用的对象本身是可以修改的。
final int x = 1;
// x = 2;  //不能赋值给被final修饰的变量
final A y = new A();
y.a = 1;

2、方法

声明方法不能被子类重写。

private方法隐式地被指定为final,如果在子类中定义的方法和基类中的一个private方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。

3、类

声明类不允许被继承。

static

1、静态变量

  • 静态变量:又被称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。
  • 实例变量:没创建一个实例就会产生一个实例变量,它与实例同生共死。
public class A {

    private int x;         // 实例变量
    private static int y;  // 静态变量

    public static void main(String[] args) {
        // int x = A.x;  // Non-static field 'x' cannot be referenced from a static context
        A a = new A();
        int x = a.x;
        int y = A.y;
    }
}

2、静态方法

  • 静态方法在类加载的时候就存在了,它不依赖于任何实例。所有静态方法必须有实现,也就是说它不能是抽象方法。
  • 静态方法只能访问所属类的静态字段和静态方法,方法中不能有this和super关键字,因此这两个关键字与具体对象关联。

3、静态语句块

静态语句块在类初始化时运行一次

public class A {
    static {
        System.out.println("123");
    }

    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new A();
    }
}
123

4、静态内部类

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

5、初始化顺序

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

public static String staticField = "静态变量";

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

public String field = "实例变量";

{
    System.out.println("普通语句块");
}

最后才是构造函数的初始化

public InitialOrderTest() {
    System.out.println("构造函数");
}

存在继承的情况下,初始化顺序为:

  • 父类(静态变量、静态语句块)
  • 子类(静态变量、静态语句块)
  • 父类(实例变量、普通语句块)
  • 父类(构造函数)
  • 子类(实例变量、普通语句块)
  • 子类(构造函数)

5、继承

访问权限

java中有三个访问权限修饰符:private、protected以及public,如果不加访问修饰符,表示包级可见。

可以对类或类中的成员(字段和方法)加上访问修饰符。

  • 类可见表示其它类可以用这个类创建实例对象
  • 成员可见表示其它类可以用这个类的实例对象访问到改成员;

protected用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。

如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例去代替,也就是确保满足里式替换原则。

字段决不能是共有的,因为这么做的话就市区了对这个字段修改行为的控制,客户端可以对其随意修改。我们可以使用公有的getter和setter方法来替换公有字段,这样的话可以控制对字段的修改行为。

抽象类与接口

1、抽象类

抽象类和抽象方法都使用abstract关键字进行声明。如果一个类中包含抽象方法,那么这个类必须声明为抽象类。

抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承。

public abstract class AbstractClassExample {

    protected int x;
    private int y;

    public abstract void func1();

    public void func2() {
        System.out.println("func2");
    }
}
public class AbstractExtendClassExample extends AbstractClassExample {
    @Override
    public void func1() {
        System.out.println("func1");
    }
}
// AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated
AbstractClassExample ac2 = new AbstractExtendClassExample();
ac2.func1();

2、接口

接口是抽象类的延伸,在java8之前,他可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。

从java8开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口维护成本太高了。 在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类,让它们都实现新增的方法。

接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。从 Java 9 开始,允许将方法定义为 private,这样就能定义某些复用的代码又不会把方法暴露出去。

接口的字段默认都是 static 和 final 的。

重写与重载

1、重写

存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。

为了满足里式替换原则,重写有一下三个限制:

  • 子类方法的访问权限必须大于等于父类方法
  • 子类方法的返回类型必须是父类方法返回类型或为其子类型
  • 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型

2、重载

存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。

应该注意的是,返回值不同,其它都相同不算是重载。

反射

每一个类都有一个Class对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名 的.class文件,该文件内容保存着Class对象。

类加载相当于Class对象的加载,类在第一次使用时才动态加载到JVM中。也可以使用Class.forName("com.mysql.jdbc.Driver")这种方式来控制类的加载,该方法会返回一个Class对象。

反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的.class不存在也可以加载进来。

Class和java.lang.reflect一起对反射提供了支持,java.lang.reflect类库主要包含了以下三个类:

  • Field:可以使用get()和set()方法读取和修改Field对象关联的字段
  • Method:可以使用invoke()方法调用与Method对象关联的方法
  • Constructor:可以用Constructor和newInstance()创建新的对象

反射的优点:

  • 可扩展性:应用程序可以利用全限定类名创建可扩展对象的实例,来使用来自外部的用户自定义类。
  • 类浏览器和可视化开发环境:一个类浏览器需要可以枚举类的成员。可视化开发环境(如IDE)可以从反射中可用的类型信息中收益,帮助程序员编写正确的代码。
  • 调试器和测试工具:调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的API定义,以确保一组测试中有较高的代码覆盖率。

反射的缺点:

尽管反射很强大,但是不能滥用。如果一个功能可以不用反射完成,那么最好不用。

  • 性能开销:反射设计了动态类型的解析,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。
  • 安全限制:使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如Applet,那么这就是一个问题了。
  • 内部暴露:由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用。
posted @ 2021-03-30 15:14  胖墩哥  阅读(230)  评论(0编辑  收藏  举报