Java 和 C++ 都是面向对象的语言,都支持封装、继承和多态,但是,它们还是有挺多不相同的地方:
continue
: 指跳出当前的这一次循环,继续下一次循环break
:指跳出整个循环体,继续执行循环下面的语句return
:用于跳出所在方法,结束该方法的运行成员变量:作用范围是整个类,相当于C中的全局变量,定义在方法体和代码块之外,一般定义在类的声明之下;成员变量包括实例成员变量和静态成员变量(类变量);
局部变量:类的方法中的变量,访问修饰符不能用于局部变量,声明在方法、构造方法或代码块中,在栈上分配,无默认值,必须初始化后才能使用;
成员变量是属于对象的,可以被 public
,private
,static
等修饰符所修饰。局部变量是在代码块或者在方法中定义的变量或者是方法的参数,局部变量不能被 public
,private
,static
等修饰。但是成员变量和局部变量都能被 final 关键字所修饰
若成员变量使用 static
修饰,那么这个成员变量属于类。如果没有用 static
修饰,那么这个成员变量是属于实例的。实例(对象)存在于堆内存,局部变量则存在于栈内存。
成员变量属于对象,随着对象的创建而存在。而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。
成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值。但是如果被 final
关键字修饰必须显式的赋值,而局部变量不会自动赋值。
静态变量可以被类的所有实例共享。无论一个类创建了多少个对象,它们都共享同一份静态变量。
通常情况下,静态变量会被 final
关键字修饰成为常量
final 修饰 StringBuffer 后 还可以 append 吗?
可以。final 修饰的是一个引用变量,那么这个引用始终只能指向这个对象,但是这个对象内部的属性是可以变化的。
char
在 Java 中占两个字节)按照有无static
修饰,成员变量和方法可以分为
static
修饰的成员变量称为静态成员变量,也叫类变量,属于类本身,直接用类名.静态成员变量访问即可static
修饰的成员变量称为实例成员变量,属于类的每个对象的,必须用对象.实例成员变量来访问static
修饰的成员方法称为静态方法,也叫类方法,属于类本身的,直接用==类名.静态方法()==即可static
修饰的成员方法称为实例方法,属于类的每个对象的,必须用==对象.实例方法()==来访问注意:静态方法和私有方法不可以被重写
public static String staticField = "静态变量";
static {
System.out.println("静态代码块"); ---- 1
}
public String field = "实例变量"; ---- 2
{
System.out.println("实例代码块"); ----3
}
// 最后才是构造函数的初始化
public InitialOrderTest() {
System.out.println("构造函数"); ----4
}
初始化顺序:静态变量和静态代码块优先于实例变量和实例代码块,静态变量和静态代码块的初始化顺序取决于它们在代码中的顺序
存在继承的情况下,初始化顺序为:
为什么继承后子类的构造器会调用父类的构造器?
- 子类的构造器第一行默认有
super()
调用父类的无参构造器,写不写都存在
Java 中有 8 种基本数据类型,分别为:
这 8 种基本数据类型的默认值以及所占空间的大小如下:
基本类型 | 位数 | 字节 | 默认值 |
---|---|---|---|
byte | 8 | 1 | 0 |
short | 16 | 2 | 0 |
int | 32 | 4 | 0 |
long | 64 | 8 | 0 |
char | 16 | 2 | |
float | 32 | 4 | 0.0 |
double | 64 | 8 | 0.0 |
boolean | 1 | false |
- Java 里使用
long
类型的数据一定要在数值后面加上 L,否则将作为整型解析。- char a = ‘h’ ,char 是单引号, String a = “hello” String 是双引号
null
,而基本类型有默认值且不是 null
。int 和 Integer 有什么区别?
两个 new 生成的 Integer 变量的对比:
由于Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)
Integer i = new Integer(10000);
Integer j = new Integer(10000);
System.out.print(i == j); //false
Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。
Byte
,Short
,Integer
,Long
这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character
创建了数值在 [0,127] 范围的缓存数据,Boolean
直接返回 True
or False
。
如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。两种浮点数类型的包装类 Float
,Double
并没有实现缓存机制。
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// 输出 true
Float i11 = 333f;
Float i22 = 333f;
System.out.println(i11 == i22);// 输出 false
Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// 输出 false
ValueOf()
方法,xxxValue()
方法。Integer i = 10; //装箱
// 等价于 Integer i = Integer.valueOf(10)
int n = i; //拆箱
// 等价于int n = i.intValue()
Integer i1 = 40;
// Integer i1=40 这一行代码会发生装箱,也就是说这行代码等价于Integer i1=Integer.valueOf(40),因此 i1 直接使用的是缓存中的对象
Integer i2 = new Integer(40);
// Integer i2 = new Integer(40)会直接创建新的对象
System.out.println(i1==i2); // false
所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
首先为什么会出现浮点数运算的时候精度丢失问题呢?这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示。
如何解决呢?BigDecimal
可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal
来做的
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
System.out.println(x); /* 0.1 */
System.out.println(y); /* 0.1 */
System.out.println(Objects.equals(x, y)); /* true */
new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向 0 个或 1 个对象,一个对象可以有 n 个引用指向它。
构造方法是一种特殊的方法,主要作用是完成对象的初始化工作。构造方法的特性是:
在Java中定义一个不做事且没有参数的构造方法有什么作用?
- Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。
- 因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是:在父类里加上一个不做事且没有参数的构造方法。
如果一个类没有声明构造方法,也可以执行!因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。如果我们自己添加了类的构造方法(无论是否有参),Java 就不会再添加默认的无参数的构造方法了,我们一直在不知不觉地使用构造方法,这也是为什么我们在创建对象的时候后面要加一个括号(因为要调用无参的构造方法)。
如果我们重载了有参的构造方法,记得都要把无参的构造方法也写出来(无论是否用到),因为这可以帮助我们在创建对象的时候少踩坑
构造方法特点如下:
public class Student {
// 两个成员变量 私有
private String name;
private int age;
// 无参构造器
public Student(){
}
// 有参数构造器
public Student(String name,int age){
this.name = name;
this.age = age;
}
}
构造方法不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。
String str1 = "he";
String str2 = "llo";
String str3 = "world";
String str4 = str1 + str2 + str3;
字符串对象通过“+”的字符串拼接方式,实际上是通过 StringBuilder
调用 append()
方法实现的,拼接完成之后调用 toString()
得到一个 String
对象 。
Java用作字符串拼接有哪些方法?
加号拼接
StringBuilder 的 append
StringBuffer 的 append
String 的 concat
在拼接少数字符串(不超过4个)的时候,concat效率是最高的
多个字符串拼接的时候,StringBuilder/StringBuffer的效率是碾压的。
String
中的 equals
方法是被重写过的,比较的是 String 字符串的值是否相等。 Object
的 equals
方法是比较的对象的内存地址。
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
// 在堆中创建字符串对象”ab“
// 将字符串对象”ab“的引用保存在字符串常量池中
String aa = "ab";
// 直接返回字符串常量池中字符串对象”ab“的引用
String bb = "ab";
System.out.println(aa==bb);// true
String s1 = new String(“abc”) 这句话创建了几个字符串对象?
- 会创建 1 或 2 个字符串对象
如果字符串常量池中不存在字符串对象“abc”的引用,那么会在堆中创建 2 个字符串对象“abc”
String s1 = new String("abc");
如果字符串常量池中已存在字符串对象“abc”的引用,则只会在堆中创建 1 个字符串对象“abc”
// 字符串常量池中已存在字符串对象“abc”的引用
String s1 = "abc";
// 下面这段代码只会在堆中创建 1 个字符串对象“abc”
String s2 = new String("abc");
try
块 : 用于捕获异常。其后可接零个或多个 catch
块,如果没有 catch
块,则必须跟一个 finally
块。catch
块 : 用于处理 try 捕获到的异常。finally
块 : 无论是否捕获或处理异常,finally
块里的语句都会被执行。当在 try
块或 catch
块中遇到 return
语句时,finally
语句块将在方法返回之前被执行注意:不要在 finally 语句块中使用 return,当 try 语句 和 finally 语句都有 return 语句时, try 语句块中的 return 语句会被忽略。
不一定的!在某些情况下,finally 中的代码不会被执行。比如说 finally 之前虚拟机被终止运行的话,finally 中的代码就不会被执行。
try {
System.out.println("Try to do something");
throw new RuntimeException("RuntimeException");
} catch (Exception e) {
System.out.println("Catch Exception -> " + e.getMessage());
// 终止当前正在运行的Java虚拟机
System.exit(1);
} finally {
System.out.println("Finally");
}
输出:
Try to do something
Catch Exception -> RuntimeException
另外,在以下 2 种特殊情况下,finally
块的代码也不会被执行:
java.lang.AutoCloseable
或者 java.io.Closeable
的对象try-with-resources
语句中,任何 catch 或 finally 块在声明的资源关闭后运行Java 中类似于InputStream
、OutputStream
、Scanner
、PrintWriter
等的资源都需要我们调用close()
方法来手动关闭,一般情况下我们都是通过try-catch-finally
语句来实现这个需求,如下:
//读取文本文件的内容
Scanner scanner = null;
try {
scanner = new Scanner(new File("D://read.txt"));
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (scanner != null) {
scanner.close();
}
}
使用try-with-resources
语句改造上面的代码:
try (Scanner scanner = new Scanner(new File("test.txt"))) {
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
}
当然多个资源需要关闭的时候,通过使用分号分隔,可以在try-with-resources
块中声明多个资源。
Java 中 static 方法不能被重写,因为方法重写是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。
Java 中也不可以重写 private 的方法,因为 private 修饰的变量和方法只能在当前类中使用, 如果是其他的类继承当前类是不能访问到 private 变量或方法的,当然也不能重写。
注意:静态方法和私有方法不可以被重写
设计层面上的区别:
抽象类能使用 final 修饰吗?
不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类。
short s1 = 1;
s1 = s1 + 1; // 报错,在 s1 + 1 运算时会自动提升表达式的类型为 int ,那么将 int 型值赋值给 short 型变量,s1 会出现类型转换错误
short s1 = 1;
s1 += s1; // 正确: += 是 Java 语言规定的运算符,Java 编译器会对它进行特殊处理,因此可以正确编译
Integer 和 int 的比较延伸:
final:用于声明变量、方法和类,分别表示变量不可变、方法不可重写、被其修饰的类不可继承;
finally:异常处理语句结构的一部分,表示总是执行;
finallize:Object类的一个方法,在垃圾回收时会调用被回收对象的finalize
两个对象的 hashCode() 相同,equals() 不一定为 true。因为在散列表中,hashCode() 相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等【散列冲突】。
这个问题应该是有个前提,就是你需要用到 HashMap、HashSet 等 Java 集合,用不到哈希表的话,其实仅仅重写 equals() 方法也可以。而工作中的场景是常常用到 Java 集合,所以 Java 官方建议重写 equals() 就一定要重写 hashCode() 方法。
对于对象集合的判重,如果一个集合含有 10000 个对象实例,仅仅使用 equals() 方法的话,那么对于一个对象判重就需要比较 10000 次,随着集合规模的增大,时间开销是很大的。但是同时使用哈希表的话,就能快速定位到对象的大概存储位置,并且在定位到大概存储位置后,后续比较过程中,如果两个对象的 hashCode 不相同,也不再需要调用 equals() 方法,从而大大减少了 equals() 比较次数。
所以从程序实现原理上来讲的话,既需要 equals() 方法,也需要 hashCode() 方法。那么既然重写了 equals(),那么也要重写 hashCode() 方法,以保证两者之间的配合关系
Java 中 && 和 & 都是表示与的逻辑运算符,都表示逻辑运输符 and,当两边的表达式都为 true 的时候,整个运算结果才为 true,否则为 false。
long l1 = Math.round(-1.5); // -1
long l2 = Math.round(1.5); // 2
因为在数轴上取值时,中间值(0.5)向右取整,所以正 0.5 是往上取整,负 0.5 是直接舍弃
动态代理:当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新功能。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强。
动态代理的应用:Spring 的 AOP 、加事务、加权限、加日志。
Java中,实现动态代理有两种方式:
JDK 动态代理和 Cglib 动态代理的区别:
字节是存储容量的基本单位,字符是数字、字母、汉字以及其他语言的各种符号。1字节 = 8 个二进制位,一个字符由一个字节或者多个字节的二进制单位组成。
为什么Java中只有值传递?
先来回顾一下这样几个概念:同步与异步,阻塞与非阻塞。
同步和异步的区别在于调用者需不需要等待被调用者的处理结果。
阻塞和非阻塞的区别在于调用者的线程需不需要挂起。
BIO(jdk1.4之前):Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它基于流模型实现,一个连接一个线程,客户端有连接请求时,服务器端就需要启动一个线程进行处理,线程开销大。
NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO(jdk 1.7过后 又叫NIO 2):异步非堵塞 IO,是 NIO 的升级,异步 IO 的操作基于事件和回调机制,性能是最好的。也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
Java 支持 4 种不同的访问权限。
final 关键字:
修饰成员变量
public class Hello{
// 有static修饰的就是类变量,静态成员变量,
final static int a = 0; // 在声明的时候就要赋值,或者静态代码块赋值
static {
a = 0;
}
// 没有 static 修饰的是实例成员变量,在声明的时候就需要赋值,或者代码块中赋值,或者构造器赋值
final int b = 0;
{
b = 0;
}
}
修饰基本类型数据和引用类型数据
final int a = 1;
a = 2; // 非法
final int[] arr = {1,2,3,4};
arr[2] = -3; //合法,可以对数组中某个元素的值进行更改
arr = null; //非法,不能在对arr重新赋值
final Person p = new Persson(25);
p.setAge(24); //合法,可以修改引用类型的属性
p = null; // 非法
public class Test{
public static void main(String[] args){
}
// 局部final变量a、b
public void test(final int b){
final int a = 10;
// 匿名内部类:只能访问final变量a、b
new Thread(){
public void run(){
System.out.println(a);
System.out.println(a);
};
}.start();
}
}
内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。当外部类的方法结束时,局部变量就会被销毁,但是内部类对象可能还存在,这就有一个矛盾,内部类对象访问了一个不存在的变量。为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍然可以访问它,实际访问的是局部变量的copy,这样就好像延长了局部变量的生命周期。
将局部变量设置为final,可以保证内部类的成员变量和外部类的局部变量的一致性。
互斥关系,不能同时出现修饰成员!
abstract
修饰类,类是抽象类,必须被继承
final
修饰类,类不能被继承
abstract
修饰方法,方法必须被重写
final
修饰方法,方法不能被重写
接口的设计目的,是对类的行为进行约束,也就是提供一种机制,可以强制要求不同的类有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。
抽象类的设计目的,是代码复用。先有子类,再有父类,将子类共性的特征抽象为一个父类。抽象类不允许创建对象,表达的是 is a 的关系,比如 BMW is a Car。而接口是对行为的抽象,表达的是 like a 的关系,比如 Bird like a Aircraft,接口的核心是定义行为,即实现类可以做什么,至于实现类主体是谁、是如何实现的,接口并不关心。