Java基础知识

Java基础知识


注意:本问文部分内容参考《码出高效》,本人觉得这本书挺不错,可以去购买看看。部分参考极客时间专栏还有就是基于网上相关资源的整理。

一.Java概述

1.何为编程

编程就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程。

为了使计算机能够理解人的意图,人类就必须要将需解决的问题的思路、方法、和手段通过计算机能够理解的形式告诉计算机,使得计算机能够根据人的指令一步一步去工作,完成某种特定的任务。这种人和计算机之间交流的过程就是编程。

2.什么是Java

Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。

3.jdk1.5之后的三大版本

  • Java SE(J2SE,Java 2 Platform Standard Edition,标准版)
  • Java SE 以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。Java SE 包含了支持 Java Web 服务开发的类,并为Java EE和Java ME提供基础。
  • Java EE(J2EE,Java 2 Platform Enterprise Edition,企业版)
  • Java EE 以前称为 J2EE。企业版本帮助开发和部署可移植、健壮、可伸缩且安全的服务器端Java 应用程序。Java EE 是在 Java SE 的基础上构建的,它提供 Web 服务、组件模型、管理和通信 API,可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和 Web2.0应用程序。2018年2月,Eclipse 宣布正式将 JavaEE 更名为 JakartaEE
  • Java ME(J2ME,Java 2 Platform Micro Edition,微型版)
  • Java ME 以前称为 J2ME。Java ME 为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境。Java ME 包括灵活的用户界面、健壮的安全模型、许多内置的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持。基于 Java ME 规范的应用程序只需编写一次,就可以用于许多设备,而且可以利用每个设备的本机功能。

4.JVM、JRE和JDK的关系

JVM

Java Virtual Machine是Java虚拟机,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台。

JRE

Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包

如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。

JDK

Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等

JVM&JRE&JDK关系图

图片

5.什么是跨平台性?原理是什么

所谓跨平台性,是指java语言编写的程序,一次编译后,可以在多个系统平台上运行。

实现原理:Java程序是通过java虚拟机在系统平台上运行的,只要该系统可以安装相应的java虚拟机,该系统就可以运行java程序。

6.Java语言有哪些特点

  • 简单易学(Java语言的语法与C语言和C++语言很接近)
  • 面向对象(封装,继承,多态)
  • 平台无关性(Java虚拟机实现平台无关性)
  • 支持网络编程并且很方便(Java语言诞生本身就是为简化网络编程设计的)
  • 支持多线程(多线程机制使应用程序在同一时间并行执行多项任)
  • 健壮性(Java语言的强类型机制、异常处理、垃圾的自动收集等)
  • 安全性

7.什么是字节码?采用字节码的最大好处是什么

**字节码:**Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。

采用字节码的好处:

Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。

先看下java中的编译器和解释器:

Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行,这就是上面提到的Java的特点的编译与解释并存的解释。

Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。

8.什么是Java程序的主类?应用程序和小程序的主类有何不同?

一个程序中可以有多个类,但只能有一个类是主类。在Java应用程序中,这个主类是指包含main()方法的类。而在Java小程序中,这个主类是一个继承自系统类JApplet或Applet的子类。应用程序的主类不一定要求是public类,但小程序的主类要求必须是public类。主类是Java程序执行的入口点。

9.Java应用程序与小程序之间有那些差别?

简单说应用程序是从主线程启动(也就是main()方法)。applet小程序没有main方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟flash的小游戏类似。

10.Java和C++的区别

  • 都是面向对象的语言,都支持封装、继承和多态
  • Java不提供指针来直接访问内存,程序内存更加安全
  • Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
  • Java有自动内存管理机制,不需要程序员手动释放无用内存

11.Oracle JDK 和 OpenJDK 的对比

  • OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK的一个实现,并不是完全开源的;
  • Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到Oracle JDK就可以解决问题;
  • 在响应性和JVM性能方面,Oracle JDK与OpenJDK相比提供了更好的性能;
  • Oracle JDK不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本;
  • Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。

12. import java 和 javax 有什么区别?

刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来使用。然而随着时间的推移,javax 逐渐地扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包确实太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准 API 的一部分。所以,实际上 java 和 javax 没有区别。这都是一个名字。

二.Java基础语法

1.数据类型

1).Java有哪些数据类型?

  • 基本数据类型
    • 数值型
      • 整数类型(byte,short,int,long)
      • 浮点类型(float,double)
    • 字符型(char)
    • 布尔型(boolean)
  • 引用数据类型
    • 类(class)
    • 接口(interface)
    • 数组([])

还有一个变量是refvar,引用变量也叫引用句柄,它的默认值是null,存储引用变量的首地址。它占4B空间。

refobj则是无论是多么小的对象,最小占用12B空间(用于存储基本信息,称为对象头),但由于存储空间必须是8B的倍数,所以初始分配的空间至少16B。

图片

图片

**注意:**JVM没有针对boolean数据类型进行赋值的专用字节码指令,false就是用常量0进行赋值

float f=3.4;是否正确?

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

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);其中有隐含的强制类型转换。

switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上?

在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。

2.)对象

图片

图片

3).包装类型

包装类能够解决基本数据类型无法做到的事情:泛型类型参数、序列化、类型转换、高频区间数据缓存。在缓存区间可以直接使用==符合进行比较,除了Float和Double都有缓存

图片

缓存区间:

图片

推荐使用:

图片

自动装箱与拆箱

装箱:将基本类型用它们对应的引用类型包装起来;

拆箱:将包装类型转换为基本数据类型;

int 和 Integer 有什么区别

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

Integer a= 127 与 Integer b = 127相等吗

对于对象引用类型:==比较的是对象的内存地址。

对于基本数据类型:==比较的是值。

如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false

2.Java语言采用何种编码方案?有何特点?

Java语言采用Unicode编码标准,Unicode(标准码),它为每个字符制订了一个唯一的数值,因此在任何的语言,平台,程序都可以放心的使用。

3.什么Java注释

  • 单行注释 格式: // 注释文字
  • 多行注释 格式: /* 注释文字 */
  • 文档注释 格式:/** 注释文字 */

在程序中,尤其是复杂的程序中,适当地加入注释可以增加程序的可读性,有利于程序的修改、调试和交流。注释的内容在程序编译的时候会被忽视,不会产生目标代码,注释的部分不会对程序的执行结果产生任何影响。

注意事项:多行和文档注释都不能嵌套使用。

4.访问控制符

图片图片

5.关键字

1).final 有什么用?

用于修饰类、属性和方法;

  • 被final修饰的类不可以被继承
  • 被final修饰的方法不可以被重写
  • 被final修饰的变量不可以被改变,被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的

2).final finally finalize区别

  • final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
  • finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
  • finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断。

3).this关键字的用法

this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。

this的用法在java中大体可以分为3种:

  • 普通的直接引用,this相当于是指向当前对象本身。
  • 形参与成员名字重名,用this来区分:
  • 引用本类的构造函数

4).super关键字的用法

super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。

super也有三种用法:

  • 普通的直接引用,与this类似,super相当于是指向当前对象的父类的引用,这样就可以用super.xxx来引用父类的成员
  • 子类中的成员变量或方法与父类中的成员变量或方法同名时,用super进行区分
  • 引用父类构造函数
    • super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。
    • this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)。

5).this与super的区别

  • super: 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参)
  • this:它代表当前对象名(在程序中易产生二义性之处,应使用this来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用this来指明成员变量名)
  • super()和this()类似,区别是,super()在子类中调用父类的构造方法,this()在本类内调用本类的其它构造方法。
  • super()和this()均需放在构造方法内第一行。
  • 尽管可以用this调用一个构造器,但却不能调用两个。
  • this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
  • this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。

图片

6).static存在的主要意义

static的主要意义是在于创建独立于具体对象的域变量或者方法。以致于即使没有创建对象,也能使用属性和调用方法!

static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。

7).static的独特之处

  • 被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享
  • 在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,注意这是第一次用就要初始化,后面根据需要是可以再次赋值的。
  • static变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。赋值的话,是可以任意赋值的!
  • 被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。

8).static应用场景

因为static是被类的实例对象所共享,因此如果某个成员变量是被所有对象所共享的,那么这个成员变量就应该定义为静态变量。

因此比较常见的static应用场景有:

1、修饰成员变量 2、修饰成员方法 3、静态代码块 4、修饰类【只能修饰内部类也就是静态内部类】 5、静态导包

9).static注意事项

1、静态只能访问静态。 2、非静态既可以访问非静态的,也可以访问静态的。

6.String 相关问题

1).String 是如何实现的?它有哪些重要的方法?

  • 在JDK1.8中,String内部实际存储结构为cahr数组
  • 有多个构造函数,无参构造函数,有参构造函数(char[]数组, StringBuilder,StringBuffer)
  • equals方法,主要用来比较两个字符串是否相等,可以传递Object参数,首先会通过instanceof方法判断是否为String类型。最后通过比较每个字符是否相等来判断。
  • compareTo方法比较两个字符串,返回结果为int类型的值,当两个字符不一样的时候返回char1-char2

2).为什么String类型要用final修饰

  • 高效:使用final能够缓存结果,在传参的时候不需要考虑谁会修改它的值。如果是可变类则可能需要重新拷贝出一个新值进行传参,性能上就差了。以JVM中字符串常量池进行举例,只有字符串不可变时才能实现字符串常量池
  • 安全:在调用系统级操作指令之前,可能会进行校验,如果是可变类,校验之后,它的值可能又改变了,可能会造成严重的系统崩溃

3). == 和 equals 的区别

  • == 对于基本数据类型来说,是用于比较 “值”是否相等的;而对于引用类型来说,是用于比较引用地址是否相同的。

查看源码我们可以知道Object中也有equals()方法,源码如下 其实就是==

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

字符串则重写了equals方法

public boolean equals(Object anObject) {
//对象引用相同就直接返回了
    if (this == anObject) {
        return true;
    }
    // 判断需要对比的值是否为 String 类型,如果不是则直接返回 false
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
        //转换成char数组进行比较
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

4). String 和 StringBuilder、StringBuffer 的区别

  • 因为String是不可变的因此拼接字符串的话性能会比较差
  • 因此需要使用StringBuffer,它提供append和insert方法来用于字符串拼接,并且保证其线程安全
  • 由于StringBuffer保证了线程安全地,所以性能不高,在JDK1.5的时候引入了StringBuilder,其append方法与StringBuffer主要区别没有加synchronized。最后都是调用了AbstractStringBuilder的append方法
//这里的obj可以是String, 字符数组char[],还可以是抽象类AbstractStringBuilder的子类,CharSequence的实现类。主要还是前两个
@Override
public synchronized StringBuffer append(Object obj) {
    toStringCache = null;
    super.append(String.valueOf(obj));
    return this;
}
@Override
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}
//AbstractStringBuilder的append方法
public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    //确保字符数组的大小够
    ensureCapacityInternal(count + len);
    //将str的字符全部加到value数组从count开始的下标处
    str.getChars(0, len, value, count);
    count += len;
    return this;
}
// Documentation in subclasses because of synchro difference
public AbstractStringBuilder append(StringBuffer sb) {
    if (sb == null)
        return appendNull();
    int len = sb.length();
    ensureCapacityInternal(count + len);
    sb.getChars(0, len, value, count);
    count += len;
    return this;
}

可变性
String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。

线程安全性

String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

性能

每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

对于三者使用的总结

如果要操作少量的数据用 = String

单线程操作字符串缓冲区 下操作大量数据 = StringBuilder

多线程操作字符串缓冲区 下操作大量数据 = StringBuffer

5).String和JVM

String的常见创建方式有两种:new String() 的方式和直接赋值的方式,直接赋值的方式会先去字符串常量池中查找是否已经有此值,如果有则把引用地址直接指向此值,否则会先在常量池中创建,然后再把引用指向此值;而 new String() 的方式一定会先在堆上创建一个字符串对象,然后再去常量池中查询此字符串的值是否已经存在,如果不存在会先在常量池中创建此字符串,然后把引用的值指向此字符串,如下代码所示:

Strings1=new String("Java");
Strings2=s1.intern();
Strings3="Java";
System.out.println(s1 == s2); // false
System.out.println(s2 == s3); // true

它们在JVM的位置如图所示
图片

注意点:JDK 1.7 之后把永生代换成的元空间,把字符串常量池从方法区移到了 Java 堆上。

除此之外编译器还会对 String 字符串做一些优化,例如以下代码:

//代码 "Ja"+"va" 被直接编译成了 "Java" 
String s1 = "Ja" + "va";
String s2 = "Java";
System.out.println(s1 == s2);

6).在使用 HashMap 的时候,用 String 做 key 有什么好处?

HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。

7).字符型常量和字符串常量的区别

  • 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符
  • 含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)
  • 占内存大小 字符常量只占一个字节 字符串常量占若干个字节(至少一个字符结束标志)

8).什么是字符串常量池?

字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。

9).String有哪些特性

  • 不变性:String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性。
  • 常量池优化:String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。
  • final:使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性。

10).String str="i"与 String str=new String(“i”)一样吗?

不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。

11).String s = new String(“xyz”);创建了几个字符串对象

两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。

12.数组有没有 length()方法?String 有没有 length()方法

数组没有 length()方法 ,有 length 的属性。String 有 length()方法。

三.面向对象(码出高效)

1.面向对象的的几大特性

抽象:抽象是找出属性和行为的共性,属性是行为的基本生产资料,具有一定敏感性,不能直接暴露(注意在说面向对象三大特性是并不包括抽象)

**封装:**是在抽象基础上决定信息是否公开,公开等级,核心问题是以什么样方式暴露哪些问题。

主要任务是对属性,数据,部分内部敏感行为实现隐藏。

设计模型七大原则之一的迪米特法则就是对封装的具体要求,A模块使用B模块的某个接口行为,对B模块除此之外的其他信息知道的尽可能少。

继承:继承是用来表是类之间的is-a关系,判断标准是是否符合里式替换原则。LSP是指任何父类出现的地方子类都能出现。好处是就是**代码复用,**缺点是滥用之后会导致方法爆炸。提倡组合优先原则来扩展类的能力。

**多态:**子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。根据运行时实际数据类型,使同一个行为具有不同表现形式。

**override 覆写:**是子类实现接口或者继承父类时,保持方法签名完全相同,实现不同的方法体。是垂直方向上行为的不同实现。由JVM运行期间动态绑定,调用合适的覆写题来执行。

**overload 重载:**方法名称相同,但是参数类型,参数个数是不相同的,是水平方向上的不同实现。编译期确定方法调用,本质上重载的结果是完全不同的方法,静态绑定。

码出高效认为多态指覆写

2.面向对象基本原则是什么(设计原则)

单一职责原则SRP(Single Responsibility Principle)

一个类或者模块只负责完成一个职责。关于类和模块有两种理解方式。一种理解是:把模块看作比类更加抽象的概念,类也可以看作模块。另一种理解是:把模块看作比类更加粗粒度的代码块,模块中包含多个类,多个类组成一个模块。

开放封闭原则OCP(Open-Close Principle)

一个模块对于拓展是开放的,对于修改是封闭的

添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。关于定义,我们有两点要注意。第一点是,开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发。第二点是,同样的代码改动,在粗代码粒度下,可能被认定为“修改”;在细代码粒度下,可能又被认定为“扩展”。

里式替换原则LSP(the Liskov Substitution Principle LSP)

子类可以替换父类出现在父类能够出现的任何地方

违反例子:

  • 子类违背父类声明要实现的功能
  • 子类违背父类对输入、输出、异常的约定
  • 子类违背父类注释中所罗列的任何特殊说明

依赖倒置原则DIP(the Dependency Inversion Principle DIP)

高层模块(high-level modules)不要依赖低层模块(low-level)。高层模块和低层模块应该通过抽象(abstractions)来互相依赖。除此之外,抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions)。

  • 所谓高层模块和低层模块的划分,简单来说就是,在调用链上,调用者属于高层,被调用者属于低层。在平时的业务代码开发中,高层模块依赖底层模块是没有任何问题的。实际上,这条原则主要还是用来指导框架层面的设计,跟前面讲到的控制反转类似。我们拿 Tomcat 这个 Servlet 容器作为例子来解释一下。
  • Tomcat 是运行 Java Web 应用程序的容器。我们编写的 Web 应用程序代码只需要部署在 Tomcat 容器下,便可以被 Tomcat 容器调用执行。按照之前的划分原则,Tomcat 就是高层模块,我们编写的 Web 应用程序代码就是低层模块。Tomcat 和应用程序代码之间并没有直接的依赖关系,两者都依赖同一个“抽象”,也就是 Servlet 规范。Servlet 规范不依赖具体的 Tomcat 容器和应用程序的实现细节,而 Tomcat 容器和应用程序依赖 Servlet 规范。

接口分离原则ISP(the Interface Segregation Principle ISP)

  • 设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。
  • 客户端不应该被强迫依赖它不需要的接口。其中的“客户端”,可以理解为接口的调用者或者使用者。

迪米特法则(LOD)Law of Demeter

“高内聚、松耦合”是一个非常重要的设计思想,能够有效地提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。A模块使用B模块的某个接口行为,对B模块除此之外的其他信息知道的尽可能少。

3.拷贝方式

**浅拷贝:**只复制当前对象的所有基本数据类型,以及相应的引用变量,但没有复制引用变量指向的实际对象。

**彻底深拷贝:**拷贝的对象与母对象在任何引用路径上都不存在共享的实例对象,但是引用路径越深,则越接近JVM底层对象,其拷贝难度越大。

**一般深拷贝:**介于两者之间

4.接口与抽象类的区别

语法区别如下:

图片

  • 抽象类被继承时体现的是is-a关系,接口在被实现时体现的是can-do关系。
  • 抽象类是模板类设计,接口是契约式设计

备注:Java8中接口中引入默认方法和静态方法,以此来减少抽象类和接口之间的差异。现在,我们可以为接口提供默认实现的方法了,并且不用强制子类来实现它。

接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则:

  • 行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
  • 选择抽象类的时候通常是如下情况:需要定义子类的行为,又要为子类提供通用的功能。

5.抽象类能使用 final 修饰吗?

不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类

6.创建一个对象用什么关键字?对象实例与对象引用有何不同?

new关键字,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)

7.内部类的分类有哪些?

  • 静态内部类
  • 成员内部类
  • 局部内部类
  • 匿名内部类

图片

图片

8.内部类的优点

  • 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
  • 内部类不为同一包的其他类所见,具有很好的封装性;
  • 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
  • 匿名内部类可以很方便的定义回调。

9.局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?

public class Outer {
    void outMethod(){
        final int a =10;
        class Inner {
            void innerMethod(){
                System.out.println(a);
            }
        }
    }
}

为什么要加final呢?是因为生命周期不一致局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。

10.变量与方法

  • 成员变量与局部变量的区别有哪些?
成员变量局部变量
作用域针对整个类有效只在某个范围内有效。(一般指的就是方法,语句体内)
存储位置随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中。在方法被调用,或者语句被执行的时候存在,存储在栈内存中。当方法调用完,或者语句结束后,就自动释放。
生命周期随着对象的创建而存在,随着对象的消失而消失当方法调用完,或者语句结束后,就自动释放。
初始值有默认初始值没有默认初始值,使用前必须赋值
使用原则在使用变量时需要遵循的原则为:就近原则 首先在局部范围找,有就使用;接着在成员位置找。
  • 在Java中定义一个不做事且没有参数的构造方法的作用?

Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。

  • 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?

帮助子类做初始化工作。

  • 一个类的构造方法的作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么?

主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。

  • 静态变量和实例变量区别

静态变量: 静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。

**实例变量: **每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。

  • 静态方法和实例方法有何不同?

静态方法和实例方法的区别主要体现在两个方面

  • 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
  • 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制

通常静态方法用于定义工具类的方法,另外静态方法如果使用了可修改的对象,那么在并发时会存在线程安全问题。所以,工具类往往与单例相伴而生。

11.this和super的异同点

图片

12.类关系

  • **继承:**extends(is-a)
  • **实现:**implements(can-do)
  • **组合:**类是成员变量(contains-a)体现的是非常强的整体与部分的关系,同生共死,部分不能在整体之间共享
  • **聚合:**类是成员变量(has-a),聚合是一种可以拆分的整体与部分之间的关系,是非常松散的暂时组合,部分可以被拆出来给另一个整体。
  • **依赖:**import类(use-a)

图片

13.序列化

内存中的数据需要转化成二进制流才可以进行数据持久化和网络传输。将数据对象转换成二进制流的过程称为对象的序列化(Serialization)。反之将二进制流恢复为数据对象过程称为反序列化(Deserializatiod)

  • Java原生序列化

图片

  • Hessian序列化

图片

  • JSON序列化

图片

14.什么是方法签名?

方法签名包括方法名称和参数列表,是JVM标识方法的唯一索引,不包括返回值,也不包括访问权限控制符、异常类型等。

加入返回值可以是方法签名的一部分

图片

那么var到底接收是1.0d还是1L无从得知。

15.值传递

当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?

是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的

为什么 Java 中只有值传递?

首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。

Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。

16.构造方法

图片

另外静态代码块的优先级最高,在创建类对象时会先执行父类和子类的静态代码块,然后父类和子类的构造方法。静态代码块只执行一次。

17.覆写(又称动态绑定)

向上转型,注意两点

  • 无法调用到子类存在而父类不存在的方法
  • 可以调用到子类覆写了父类的方法,这是一种多态实现
Father father = new Son();
//Son覆盖了此方法
father.doSomething();

覆写父类需要满足四个条件:

  • 访问权限不能变小
  • 返回类型能够向上转型成父类的返回类型(注意int和Integer以及int和long都没有继承关系,所以不能兼容,而Object能够兼容任何对象,包括class,enum,interface)
  • 异常也要向上转型成为父类的异常
  • 方法名,参数类型以及个数必须严格一致。(@Override可以让编译器检查)

18.重载(又称静态绑定)

JVM在重载方法中,选择合适目标方法的顺序:

  • 精准匹配
  • 如果是基本数据类型,自动转换成更大范围的基本类型
  • 通过自动拆箱与装箱
  • 通过子类向上转型路线依次匹配
  • 通过可变参数匹配

19.泛型

在泛型定义时,约定俗成的符号包括:E代表Element,用于集合中的元素;T代表the type of object,表示某个类;K代表Key,V代表Value用于键值对表示元素。

  • 尖括号里的每个元素都指代一种未知类型
  • 尖括号的位置十分讲究,必须在类名之后或方法返回值之前
  • 泛型在定义处只具备执行Object方法的能力
  • 泛型只是一种在编写代码时的语法检查,只是在编译期间增加了一道检查,目的是促使程序员在使用泛型时安全放置和使用数据。

使用泛型的好处:

  • 类型安全:放置什么取出来就是什么,不用担心抛出ClassCastException异常
  • 提升可读性:从编码就显式地知道泛型所处理的对象类型
  • 代码重用:合并了同类型的处理代码,使代码重用度提高

20.对象相等判断(重要)

1).== 和 equals 的区别是什么

  • == : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型 == 比较的是值,引用数据类型 == 比较的是内存地址)
  • equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:

情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。

情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。

  • String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。
  • 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。

2).hashCode 与 equals (重要)

为什么重写equals时必须重写hashCode方法

hashCode()介绍

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode()函数。

散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)

为什么要有 hashCode

以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我《Head first java》第二版)。

hashCode()与equals()的相关规定

如果两个对象相等,则hashcode一定也是相同的

两个对象相等,对两个对象分别调用equals方法都返回true

两个对象有相同的hashcode值,它们也不一定是相等的

因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖

hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

3).对象的相等与指向他们的引用相等,两者有什么不同?

对象的相等 比的是内存中存放的内容是否相等而 引用相等 比较的是他们指向的内存地址是否相等。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值