Java基础知识点
1)面向对象的特性:封装、继承和多态
-
封装是指将对象的实现细节隐藏起来,然后通过公共的方法来向外暴露出该对象的功能。
安全、简化操作
-
继承是面向对象实现软件复用的重要手段,当子类继承父类后,子类是一种特殊的父类,能直接或间接获得父类的成员
缺点:
- 继承是强耦合关系,父类改变子类必须变 - 继承破坏了封装,对于父类而言,它的实现细节对子类来说都是透明的。
-
多态简而言之就是同一个行为具有多个不同表现形式或形态的能力。
条件:
- 继承
- 重写
- 向上转型
好处:当把不同的子类对象都当作父类类型来看,可以屏蔽不同子类对象之间的实现差异,从而写出通用代码。
-
抽象是指从特定的角度出发,从已经存在的一些事物中抽取我们所关注的特征、行为,从而形成一个新的事物的思维过程,是一种从复杂到简洁的思维方式。
2)面向对象和面向过程的区别?
答:面向过程是一种站在过程的角度思考问题的思想,强调的是功能行为,功能的执行过程
-
最小程序单元是函数,每个函数负责某一功能。主函数调用其他函数,普通函数之间可以相互调用,从而实现整个系统功能。
-
缺陷:采用自顶而下的设计方式,在设计阶段就需要考虑每个模块应该分解成哪些子模块,子模块又细分成更小的子模块,依此类推,直到将模块细化成一个个函数。
-
问题:
- 设计不够直观,与人类的思维习惯不一致
- 系统软件适应性差,可扩展性差,维护性低。
面向对象是一种站在对象角度思考问题的思想,我们把多个功能合理的放在不同对象里,强调的是具备某些功能的对象。
-
面向对象更加符合我们的常规思维方式,稳定性好,可重用性强,易于开发维护。在软件工程上,面向对象可以使工程更加模块化,实现更低的耦合和更高的内聚。
3)JDK和JRE的区别是什么?
- JRE :Java运行环境,它包括JVM、Java核心类库和支持文件,但并不包含开发工具(JDK)、编译器、调试器和其它工具。
- JDK:完整的Java软件开发包,包含了JRE,编译器和其他的工具(比如JavaDoc,Java调试器),可以让开发者开发、编译、执行Java程序。
4)覆盖和重载
-
覆盖(Override)是指子类对父类方法的一种重写,只能比父类抛出更少的异常,访问权限不能比父类小,被覆盖的方法不能是private的,否则只是在子类中重新定义了一个新方法。
-
重载表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同。
-
重载的条件:参数类型不同、参数个数不同、参数顺序不同
-
函数的返回值不同不可以构成重载,因为调用函数并不需要强制赋值。
void f(){} int f(){return 1;}
这两个方法只要编译器能够根据语境明确判断出语义,那么确实可以据此重载方法,而只是调用方法而忽略其返回值,就不能判断重载。
-
5)抽象类和接口的区别?
- 抽象类中可以没有抽象方法;接口中的方法必须是抽象方法;
- 抽象类中可以有普通的成员变量;接口中变量必须是
static final
类型的,必须被初始化,接口中只有常量没有变量。 - 抽象类只能单继承,接口可以继承多个父类接口;
- Java8中接口会有default方法,即方法可以被实现。Java8后接口也可以拥有默认的方法实现。
default
- 抽象类不能被实例化,需要继承抽象类才能实例化其子类。
- 接口的成员(字段+方法)默认都是public的,并且不允许定义为private或者protected
- 接口和抽象类如何选择?
- 如果要创建不带任何方法定义和成员变量的 基类,那么就应该选择接口而不是抽象类。
- 如果知道某个类是基类,那么第一个选择是应该让它成为一个接口,只有在必须要有方法定义和成员变量的时候,才应该选择抽象类。因为抽象类中允许存在一个或多个被具体实现的方法,只要方法没有被全部实现该类就仍是抽象类。
6)java和c++区别
- 都是面向对象语言,都支持封装、继承、多态
- 指针:Java不提供指针来直接访问内存,程序更加安全
- 继承:java单继承,c++支持多重继承。Java通过一个类实现多个接口来实现c++中的多继承。
- 内存:java有自动内存管理机制,不需要手动释放无用内存
7)“static”关键字
static关键字表明一个成员变量或方法可以在没有所属的类的实例变量的情况下被访问
- java中不能重写static方法,因为重写是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关。
- 也不能重写private方法,因为private修饰的变量和方法只能在当前类中使用,如果是其他的类继承当前类是不能访问到private变量或方法的。
8)java是值传递还是引用传递?
- Java再将一个对象参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。如果将参数引用别的对象,那么形参和实参的对象指向的是完全不同的对象,在一方改变另一方并不改变。
9)jdk中常用的包
java.lang、java.util、java.io、java.net、java.sql
10)jdk、jre、jvm区别
- jdk是java开发工具包,是Java开发环境的核心组件,并提供编译、调试和运行一个java程序所需要的所有工具,可执行文件和二进制文件,是一个平台特定的软件。
- jre是java运行时环境,是jvm的实施实现,提供了运行java程序的平台。jre包含jvm但是不包含java编译器、调试器等工具。
- jvm是java虚拟机,当我们运行一个程序时,jvm负责将字节码转换为特定机器代码,jvm提供了内存管理/垃圾回收和安全机制等。是java的核心并且具有平台独立性。
11)Integer的缓存机制
public static void main(String[] args){
Integer a = 5;
Integer b = 5;
//a==b a,b指向相同的地址
Integer c = 500;
Integer d = 500;
//c!=d Integer有缓存机制,在JVM启动初期就缓存了-128到127这区间的所有数字
Integer i = new Integer(5);
Integer j = new Integer(5);
//i!=j new开辟了新的空间,i和j两个对象分别指向堆中两块内存空间
}
Integer j = 20; 自动装箱
相当于调用Integer.valueOf(20),首先会判断 20 是否在 -128~127之间,如果在就直接从IntegerCache.cache缓存中获取指定数字的包装类,否自new一个Integer对象
12)String 对象和JVM内存划分的知识
String str1 = "ABCD";
最多创建一个String对象,最少不创建String对象。如果常量池中存在“ABCD”,那么str1直接引用,否则在常量池中创建“ABCD”的空间,再引用。
String str2 = new String("ABCD");
最多创建两个String对象,至少创建一个String对象。new关键字绝对会在堆空间创建一块新的内存区域,所以至少创建一个String对象。
13)i++与++i到底有什么不同?
不管是前置++还是后置++,都是先将变量的值加1,然后才继续计算的。
区别:
- 前置++时将变量的值加1后,使用增值的变量进行运算的,而后置++是首先将变量赋给一个临时变量,接下来对变量的值加1,然后使用哪个临时变量进行运算。
14)交换变量的三种方式
-
通过第三个变量
-
通过相加的方式
v.x = v.x + v.y;//把v.x与v.y的和存储在v.x中 v.y = v.x - v.y;//v.x-v.y的值就是v.x v.x = v.x - v.y;//v.x-v.y就是以前的 x y 的值
-
通过异或运算:(x ^ y ^ y) = x 一个变量异或另一个变量两次,结果仍为之前的变量
v.x = v.x ^ v.y; v.y = v.x ^ v.y; v.x = v.x ^ v.y;
异或的方法比相加的方法更加可取的地方在于,异或不存在数据溢出
15)Java对象初始化顺序
- 先调用父类构造函数(可以通过子类构造函数第一行用super指定,不指定就调用无参构造函数)
- 静态成员变量的初始化和静态初始化块按照在代码中的顺序执行,不指定值就赋予默认值,0/false/null(对象)
- 最后调用自身构造函数
16)true、false是布尔类型的字面常量,null是引用类型的字面常量;goto与const均是Java语言保留的关键字,没有任何语法应用。
17)exception和error
都是Throwable的子类。exception用于用户程序可以捕获的异常;error定义了不希望被用户程序捕获的异常。
exception一般是设计问题,只要正常运行从不会发送的情况;
error是一种严重问题,不指望程序处理——比如内存溢出
18)throw和throws
throw用来在程序中明确的抛出异常,throws表明方法不能处理的异常。
19)基本数据类型
-
String是类类型 不是基类型
-
基本类型:整型(4种)字符型(1种)浮点型(2种)布尔型(1种)
-
使用 == 比较值
- 如果比较Integer变量,默认比较的是地址值。
- 如果比较操作表达式,比较的是具体数值。
- 对于基本类型 == 判断两个值是否相等
- 对于引用类型 == 判断两个变量是否引用同一个对象
- 基本类型没有equals()方法
-
使用.equals()的情况
- 无论是Integer还是Long中的equals()默认比较的是 数值
- 引用类型equals判断引用的对象是否等价
- 特例:Long的equals()方法,jdk默认会判断是否是Long类型
-
new Integer(123) 和 Integer.valueOf(123)的区别:
-
new每次都会新建一个对象
-
valueOf会使用缓存池中的对象,多次调用会取得同一个对象的引用
-
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
-
首先会判断 123是否在 -128~127之间,如果在就直接从IntegerCache.cache缓存中获取指定数字的包装类,否自new一个Integer对象
-
-
基本类型对应缓冲池如下
boolean values true and false
all byte values
short -128 127
int -128 127
char in the range \u0000 to \u007F
20)String
概览:String被声明为final,因此它不可被继承
java 8 中内部使用 char数组存储数据
private final char value[];
java 9之后,改用byte数组存储字符串,同时使用coder来标识使用了哪种编码
private final byte[] byte;
private final byte coder;
不可变的好处
- 可以缓存hash值:因为String的hash值经常被使用,例如String用作HashMap的key。不变的特性可以使得hash值也不可变,因此只需要进行一次计算。
- String Pool的需要:如果一个String对象已经被创建过了,那么就会从String Pool中取得引用。只有String是不可变的,才可能使用String Pool
- 安全性:String常作为参数,可以保证参数不变。
- 线程安全:String不可变性天生具备线程安全,可以在多个线程中安全的使用。
String、StringBuffer、StringBuilder
- 可变性
- String不可变
- StringBuffer和StringBuilder可变
- 线程安全
- String线程安全
- StringBuilder不是线程安全的
- StringBuffer是线程安全的,内部使用synchronized进行同步
总结:
- 如果操作少量的数据用String
- 单线程操作字符串缓冲区下操作大量数据 StringBuilder
- 多线程操作字符串缓冲区下操作大量数据StringBuffer
String和StringBuffer的区别
-
String是immutable的,其内容一旦创建好之后,就不变
-
StringBuffer可变长,内容也可以变。原理:StringBuffer内部采用了字符数组存放数据,在需要增加长度的时候创建新的数组,并且把原来的数据复制到新的数组。
-
初始化:可以指定给对象的实体的初始容量为参数字符串s的长度再加16个字符
-
扩容:尝试将新容量扩为原来容量的1倍+2,然后判断一下如果不够直接扩容到需要的容量大小。
String Pool
-
字符串常量池保存着所有的字符串字面量(literal strings),这些字面量在编译时期就确定。可以通过String的intern()方法在运行过程中将字符串添加到常量池中。
-
当一个字符串调用intern()方法时,如果已存在一个字符串和该字符串值相等(采用equals方法判断),那么就会返回常量池中字符串的引用;否则添加新串,返回引用。
-
java7,常量池被移到堆中,不会导致反正该永久代中空间有限错误
new String(“abc”)
使用这种方式一共会创建两个字符串对象(前提是String Pool中还没有“abc”字符串对象)
- “abc”属于字符串字面量,因此编译时期会在字符串常量池中创建一个字符串对象,指向这个"abc"字符串字面量。
- new会在堆中创建一个字符串对象。
21)float与double
Java不能隐式执行向下转型,这会使精度降低。
float f = 1.1 ;//这是double类型
1.1f 字面量才是float类型
float f = 1.1f ;
- 隐式类型转换
short s1 = 1; //1是int类型,它比short类型精度要高,不嫩那个将int下转型为short
s1 += 1;//使用 += 可以执行隐式类型转换
//相当于 s1=(short) (s1+1);
22)里氏替换原则
子类必须能够替换掉所有的父类对象。
如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。
这是确保可以使用父类实例的地方都可以使用子类实例。
- 抽象类is-a关系,必须满足里氏替换原则,子类对象必须能够替换掉所有的父类对象。
- 接口更像like-a关系
23)继承小问题
-
接口是否可以继承接口?
可以,比如List就继承了接口Collection
-
抽象类是否可以实现接口
可以,比如MouseAdapter鼠标监听适配器是一个抽象类,就实现了MouseListener接口
-
抽象类是否可以继承实体类?
可以,所有抽象类,都继承了Object
24) equals方法的实现
1 public class EqualExample {
2
3 private int x;
4 private int y;
5 private int z;
6
7 public EqualExample(int x, int y, int z) {
8 this.x = x;
9 this.y = y;
10 this.z = z;
11 }
12
13 @Override
14 public boolean equals(Object o) {
15 if (this == o) return true;
16 if (o == null || getClass() != o.getClass()) return false;
17
18 EqualExample that = (EqualExample) o;
19
20 if (x != that.x) return false;
21 if (y != that.y) return false;
22 return z == that.z;
23 }
24}
- 检查是否为同一个对象的引用,如果是直接返回true
- 检查是否为同一个类型,如果不使返回假
- 将Object对象进行转型
- 判断每个关键域是否相等
hashCode()
hashCode()方法返回散列值,而equals判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。
在复习equals的时候也要复写hashCode,保证等价的两个对象的散列值也相同。
clone()
clone()类是Object的protected方法,它不是public,一个类不显示去重写clone(),其他类就不能去调用该类实例的clone()方法。
-
浅拷贝:拷贝对象和原始对象的引用类型引用同一个对象。
1 public class ShallowCloneExample implements Cloneable { 2 3 private int[] arr; 4 5 public ShallowCloneExample() { 6 arr = new int[10]; 7 for (int i = 0; i < arr.length; i++) { 8 arr[i] = i; 9 } 10 } 11 12 public void set(int index, int value) { 13 arr[index] = value; 14 } 15 16 public int get(int index) { 17 return arr[index]; 18 } 19 20 @Override 21 protected ShallowCloneExample clone() throws CloneNotSupportedException { 22 return (ShallowCloneExample) super.clone(); 23 } 24} 1 ShallowCloneExample e1 = new ShallowCloneExample(); 2 ShallowCloneExample e2 = null; 3 try { 4 e2 = e1.clone(); 5 } catch (CloneNotSupportedException e) { 6 e.printStackTrace(); 7 } 8 e1.set(2, 222); 9 System.out.println(e2.get(2)); // 222
-
深拷贝:拷贝对象和原始对象的引用类型引用不同对象
1 public class DeepCloneExample implements Cloneable { 2 3 private int[] arr; 4 5 public DeepCloneExample() { 6 arr = new int[10]; 7 for (int i = 0; i < arr.length; i++) { 8 arr[i] = i; 9 } 10 } 11 12 public void set(int index, int value) { 13 arr[index] = value; 14 } 15 16 public int get(int index) { 17 return arr[index]; 18 } 19 20 @Override 21 protected DeepCloneExample clone() throws CloneNotSupportedException { 22 DeepCloneExample result = (DeepCloneExample) super.clone(); 23 result.arr = new int[arr.length]; 24 for (int i = 0; i < arr.length; i++) { 25 result.arr[i] = arr[i]; 26 } 27 return result; 28 } 29} 1 DeepCloneExample e1 = new DeepCloneExample(); 2 DeepCloneExample e2 = null; 3 try { 4 e2 = e1.clone(); 5 } catch (CloneNotSupportedException e) { 6 e.printStackTrace(); 7 } 8 e1.set(2, 222); 9 System.out.println(e2.get(2)); // 2
final
- 对于基本类型,final使数值不变;
- 对于引用类型,final使引用不变,表示该引用只有一次指向对象的机会,也就不能引用其他对象,但是被引用的对象本身是可以修改的。
静态语句块与静态内部类
- 静态语句块在类初始化时运行一次
public class A{
static{
sout("123");
}
}
-
非静态内部类依赖于外部类的实例,而静态内部类不需要
-
当一个内部类没有使用static修饰的时候,是不能直接使用内部类创建对象,须要先使用外部类对象.new内部类对象 (外部类对象.new 内部类())
而静态内部类只需要new OuterClass.StaticInnerClass();
1 public class OuterClass { 2 class InnerClass { 3 } 4 5 static class StaticInnerClass { 6 } 7 8 public static void main(String[] args) { 9 // InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context 10 OuterClass outerClass = new OuterClass(); 11 InnerClass innerClass = outerClass.new InnerClass(); 12 StaticInnerClass staticInnerClass = new StaticInnerClass(); 13 } 14}
静态内部类不能访问外部类的非静态的变量和方法。
初始化顺序
存在继承的情况下,初始化顺序为:
父类(静态变量、静态语句块)
子类(静态变量、静态语句块)
父类(实例变量、普通语句块)
父类(构造函数)
子类(实例变量、普通语句块)
子类(构造函数)
静态内部类不能访问外部类的非静态的变量和方法。
初始化顺序
存在继承的情况下,初始化顺序为:
父类(静态变量、静态语句块)
子类(静态变量、静态语句块)
父类(实例变量、普通语句块)
父类(构造函数)
子类(实例变量、普通语句块)
子类(构造函数)