Java知识点总结(一)

Java 知识点总结(一)

1. 面向对象的特征有哪些方面?

  • 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
  • 继承:继承是从已有类得到信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让 变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
  • 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问 只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自 治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写 一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西, 只向外界提供最简单的编程接口。
  • 多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分 为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的 服务,那么运行时的多态性可以解释为:当 A 系统访问 B 系统提供的服务时,B 系统有多种提供服务的方式,但一切对 A 系统来说都是透明的(就像电动剃须 刀是 A 系统,它的供电系统是 B 系统,B 系统可以使用电池供电或者用交流电, 甚至还有可能是太阳能,A 系统只会通过 B 类对象调用供电的方法,但并不知道 供电系统的底层实现是什么,究竟通过何种方式获得了动力)。 方法重载 (overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override) 实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的 东西,要实现多态需要做两件事:1). 方法重写(子类继承父类并重写父类中已 有的或抽象的方法);2). 对象造型(用父类型引用引用子类型对象,这样同样 的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。

2. 访问修饰符 public 、private 、protected 、以及不写(默认)时的区别

修饰符 当前类 同包 子类 其他包
public true true true true
protect true true true false
default true true false false
private true false false false

类的成员不写访问修饰时默认为 default。默认对于同一个包中的其他类相当于公 开(public),对于不是同一个包中的其他类相当于私有(private)。受保护 (protected)对子类相当于公开,对不是同一包中的没有父子关系的类相当于私 有。Java 中,外部类的修饰符只能是 public 或默认,类的成员(包括内部类)的 修饰符可以是以上四种。

3. String 是最基本的数据类型吗?

不是。Java 中的基本数据类型只有 8 个:byte、short、int、long、float、double、 char、boolean;除了基本类型(primitive type),剩下的都是引用类型(reference type),Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型。

4. float f=3.4;是否正确?

不正确。 3.4是双精度数,将双精度型(double)赋值给浮点型(float)属于向下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换 float f =(float)3.4; 或者写成 float f =3.4F;。

  1. short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1; 有错吗?
    对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int 型,需要强制转换类型才能赋值给 short 型。而 short s1 = 1; s1 += 1;可以正确 编译,因为 s1+= 1;相当于 s1 = (short)(s1 + 1);其中有隐含的强制类型转换。

6. int 和 Integer 有什么区别?

Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本 数据类型,但是为了能够将这些基本数据类型当成操作对象,Java 为每一个基本数据类型引入了相应的包装类型(wrapper class),int 的包装类型是Integer ,从Java5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Java 为每个原始类型提供了包装类型:

  • 原始类型: boolean,char,byte,short,int,long,float,double
  • 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float, Double
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class AutoUnboxingTest{
    public static void main(String[] args){
    Integer a=new Integer(3);
    Integer b=3; // 将 3 自动装箱成 Integer 类型
    int c=3;
    System.out.println(a == b); //false, 两个引用没有引用同一对象
    System.out.println(a == c); //true, a自动拆箱成int类型再和c比较
    }
    }
    再如,代码如下:
    1
    2
    3
    4
    5
    6
    7
    public class Test03{
    public static void main(String[] args){
    Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
    System.out.println(f1 == f2);
    System.out.println(f3 == f4);
    }
    }
    如果不明就里很容易认为两个输出要么都是 true 要么都是 false。首先需要注意的是f1、f2、f3、f4 四个变量都是 Integer对象引用,所以下面的 == 运算比较的不是值而是引用。装箱的本质是什么呢? 当我们给一个 Integer 对象赋一个 int 值时候,会调用 Integer 类的静态方法 valueOf ,如果看看 valueOf 的源代码就知道发生了什么。
    1
    2
    3
    4
    5
    6
    7
    public static Integer valueOf(int i){
    if(i >= IntegerCache.low && i <= IntegerCache.high){
    return IntegerCache.cache[i + (-IntegerCache.low)];
    }
    return new Integer(i);
    }

    ntegerCache 是 Integer 的内部类,其代码如下所示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    /**
    * Cache to support the object identity semantics of autoboxing for
    values between
    * -128 and 127 (inclusive) as required by JLS. *
    * The cache is initialized on first usage. The size of the cache
    * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>}
    option.
    * During VM initialization, java.lang.Integer.IntegerCache.high
    property
    * may be set and saved in the private system properties in the * sun.misc.VM class.
    */

    private static class IntegerCache { static final int low = -128; static final int high;
    static final Integer cache[];
    static {
    // high value may be configured by property int h = 127;
    String integerCacheHighPropValue =
    sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) {
    try {
    int i = parseInt(integerCacheHighPropValue);
    i = Math.max(i, 127);
    // Maximum array size is Integer.MAX_VALUE
    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
    } catch( NumberFormatException nfe) {
    // If the property cannot be parsed into an int,
    ignore it.
    }
    }
    high = h;
    cache = new Integer[(high - low) + 1]; int j = low;
    for(int k = 0; k < cache.length; k++)
    cache[k] = new Integer(j++);
    // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127;
    }
    private IngegerCache(){}

    }

    简单的说,如果整型字面量的值在-128 到 127 之间,那么不会 new 新的 Integer 对象,而是直接引用常量池中的 Integer 对象,所以上面的面试题中 f3f4 的结果 是 false。
  1. &和&&的区别?
    &运算符有两种用法:(1)按位与;(2)逻辑与。&&运算符是短路与运算。逻辑与 跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是 true 整个表达式的值才是 true。&&之所以称为短路运算是因为,如果&&左边的 表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。很多时候我 们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是 null 而且不 是空字符串,应当写为:username != null &&!username.equals(“”),二者 的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行 字符串的 equals 比较,否则会产生 NullPointerException 异常。注意:逻辑或 运算符(|)和短路或运算符(||)的差别也是如此。

8. 解释内存中的栈(stack)、堆(heap)和方法区(method area) 的用法。

通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都是使用JVM 中的栈空间; 而通过new 关键字和构造器创建的对象则是放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器管理的主要区域都是采用分代收集算法,所以堆空间还可以细分为新生代和老生代,具体一点可以分为Eden、 Survivor(又可分为 From Survivor 和 To Survivor)、Tenured; 方法区和堆都是各个线程共享的内存区域,用于存储已经被 JVM 加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据; 程序中的字面量(literal)如直接书写的100、“hello” 和常量都是放在常量池中,常量池是方法区的一部分。 栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都是可以通过 JVM 的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发OutOfMemoryError 。
String str = new String("hello")
上面的语句中变量 str 放在栈上,而new 创建出来的字符串对象则是放在堆上, 而“hello”这个字面量是放在方法区的。

  • 补充 1:较新版本的 Java(从 Java 6 的某个更新开始)中,由于 JIT 编译器的发 展和”逃逸分析”技术的逐渐成熟,栈上分配、标量替换等优化技术使得对象一 定分配在堆上这件事情已经变得不那么绝对了。
  • 补充 2:运行时常量池相当于 Class 文件常量池具有动态性,Java 语言并不要求 常量一定只有编译期间才能产生,运行期间也可以将新的常量放入池中,String 类的 intern()方法就是这样的。
    看看下面代码的执行结果是什么并且比较一下 Java 7 以前和以后的运行结果是否 一致。
    1
    2
    3
    4
    String s1 = new StringBuilder("go") .append("od").toString();
    System.out.println(s1.intern() == s1); String s2 = new StringBuilder("ja")
    .append("va").toString(); System.out.println(s2.intern() == s2);


9. 描述一下 JVM 加载class 文件的原理机制

JVM 中的类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的类加载器是一个重要的Java 运行时的系统组件,它负责在运行时查找和装入类文件中的类。
由于 Java 的跨平台性,经过编译的Java 源程序并不是一个可执行程序,而是一个或者多个类文件。 当Java程序需要使用某个类时,JVM 会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读到内存中,通常是创建一个字节数组读入.class 文件,然后产生与所加载类对应的Class 对象。加载完成后,Class 对象还不完整,所以此时的类还不还不可用。类 被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设 置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后 JVM 对 类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么 就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。 类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加 载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader 的子类)。从 Java 2(JDK 1.2)开始,类加载过程采 取了父亲委托机制(PDM)。PDM 更好的保证了 Java 平台的安全性,在该机制 中,JVM 自带的 Bootstrap 是根加载器,其他的加载器都有且仅有一个父类加载 器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM 不会向 Java 程序提供对 Bootstrap 的引用。下面是关于几个类 加载器的说明:

  • Bootstrap:一般用本地代码实现,负责加载 JVM 基础核心类库(rt.jar);
  • Extension:从 java.ext.dirs 系统属性所指定的目录中加载类库,它的父 加载器是 Bootstrap;
  • System:又叫应用类加载器,其父类是 Extension。它是应用最广泛的 类加载器。它从环境变量 classpath 或者系统属性 java.class.path 所指定的目 录中记载类,是用户自定义加载器的默认父加载器。
-------------本文结束感谢您的阅读-------------
作者水平有限,文中难免存在一些错误,欢迎邮件@交流讨论~
Zongpeng Lin 微信 微信
Zongpeng Lin 支付宝 支付宝