Java 基础概念
JDK、JRE、JVM 有什么区别?
JDK
:Java Development Kit
的缩写,Java 开发工具包,包含JRE
、编译器及工具。JRE
:Java Runtime Environment
的缩写,Java 运行环境,包含JVM
及 Java 核心类库。JVM
:Java Virtual Machine
的缩写,Java 虚拟机,运行 Java 字节码的虚拟机。Java 和 C++ 比较
- 都是面向对象语言,支持封装、继承、多态;
- Java 不提供指针来直接访问内存,程序内存更安全;
- Java 中的类是单继承,C++ 支持多重继承;虽然 Java 的类不支持多继承,但是接口支持多继承;
- Java 有自动内存管理机制,不需要手动释放无用内存;
- 在 C 语⾔中,字符串或字符数组最后都会有⼀个额外的字符‘\0’来表示结束。但是,Java 语⾔中没有结束符这⼀概念。https://blog.csdn.net/sszgg2006/article/details/49148189
Java支持多继承吗?
不支持。
- Java中每个类都只能继承一个类,可以实现多个接口。但是可以通过多层继承实现。
- 类定义属性和方法,描述某一类事物的抽象。而接口定义了行为,并不限于任何具体意向。
- 从逻辑上说,单继承更加明确指出一个子类就应该是其父类代表的事物中某个更具体的类别。而接口则不同,接口定义了一些公共行为。因此类对接口的 implements 称为实现而不能称为继承。
对象引用、对象实体?
https://www.cnblogs.com/gojava/p/3615513.html
创建一个对象使用 new 关键字,共分为四个步骤:String str = new String();
- 等号右边的
new String
是以 String 类为模板,在堆空间里创建一个 String 类对象(也简称为 String 对象); - 最后的小括号
()
表示调用 String 类的构造方法来初始化这个刚创建的 String 对象; - 等号左边的
String str
创建了一个 String 类型的引用变量,可以用来指向 String 类型对象; =
操作使对象引用str
指向了第 1,2 步骤创建并初始化好的对象(实体)。
对象引用存在于栈内存(栈帧-局部变量表-reference),对象实体(对象实例数据)存在于堆内存。关于 Java 内存详见 JVM 相关内容。
重写与重载
方法的重写 Overriding 和方法的重载 Overloaded 都是 Java 多态性的一种表现。
- 方法名、参数列表必须相同;
- 返回值类型如果是基本数据类型和 void 必须相等,如果是引用数据类型必须相等或为子类;
- 抛出异常类型小于等于父类;
- 访问修饰符大于等于父类;
不能重写父类中访问修饰符为 private/static/final 的方法;
引用传递和值传递
概念
值传递,是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
- 引用传递,一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身。
Java 采用引用传递还值传递?
一般认为,Java 基本数据类型的传递都是值传递,Java 中实例对象的传递是引用传递。
基本数据类型传递原参数值的一个拷贝;
引用数据类型传递原参数的引用地址的拷贝,指向内存中的同一个对象;
例证:
public class TestTransfer {
public static void main(String[] args) {
int a = 1;
int b = 2;
swap(a, b);
System.out.println(a); //1
System.out.println(b); //2
int[] arr = { 1, 2 };
swap(arr);
System.out.println(Arrays.toString(arr)); //[10, 2]
}
private static void swap(int a, int b) {
a = 10;
b = 20;
}
private static void swap(int[] arr) {
arr[0] = 10;
}
}
方法传递时传递的参数其实仅值原参数的一个拷贝,对于基本类型传递的就是具体的值的拷贝,对于引用类型传递的是对象地址。
所以上述代码中的,基本数据类型 a,b 经过 swap 方法并没有改变其原始值;而引用类型 arr 数组经过 swap 方法就改变了原始值(引用类型直接对对象堆内存地址数据操作)。
Java 基础语法
Java 基本数据类型和对象必须初始化才能输出值!
int a;
System.out.println(a); // Variable 'a' might not have been initialized
Variable 'a' might not have been initialized
需要注意的是,对于静态变量,在类加载的时候会被初始化零值;对于普通成员变量,在创建对象的时候会被初始化零值。在这两种情况下,变量因为有零值是可以直接输出的。
Java的基本数据类型转换
- 自动类型转换:小—->大
byte->short->int->long->float->double
- 强制类型转换:大—->小
小类型 变量名 = (小类型) 大类型值
注意:自增/自减运算符、复合赋值运算符底层做了优化,内部自动强制类型转换;
如:++, —, +=, -=, …… - 类型提升:是指在多种不同数据类型的表达式中,类型会自动向范围表达大的值的数据类型提升;
short s = 10;
int i = s; // 自动类型转换
short t = (short) i; // 强制类型转换
// 类型提升
long count = 1000000;
int price = 1999;
long totalPrice = price * count;
short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗?
- short s1 = 1; s1 = s1 + 1;
错误!根据 Java 的基本数据类型转换规则,s1 为 short 类型的变量,而数字 1 为 int 类型,所以在表达式s1 = s1 + 1;
中,s1 会由于类型提升转为 int 类型与 1 进行运算,运算结果为 int 类型,而 int 类型的值 赋值给 short 类型的变量时需要强制类型转换。 - short s1 = 1; s1 += 1;
正确!在复合赋值运算符底层自动进行强制类型转换,所以此处实际上是s1 = (int)s1 + 1;
Integer a = 100; Integer b = 100; a == b 为true?
由以上的分析我们可以知道,当我们 Integer a = 56, b = 56, c = 180, d = 180; 的时候:
- 如果定义的变量在 -128 到 127 之间,则是直接去缓存 cache 里的值,所以如果数值一致则对应的地址值也会一致,所以我们用 == 判断两个值是否相等,是返回 true的;
- 如果定义的变量不在 -128到127之间,则通过new Integer(int i)的方式创建数值,并且每次都会重新new一个对象,这就导致每次的对象的数值即使一样但是地址值不一致,所以此时用 == 判断两个值是否相等就不如我们所愿了;
- 所以我们遇到包装类 Integer定义的变量的时候,如果要判断两个变量的值是否相等,则使用 equals来判断,尽量不要用 == 的来判断。
*不用中间变量交换 a, b 的值
// 加减法
a = a + b;
b = a - b;
a = a - b;
// 异或法 (亦可使用其他运算如位运算)
a = a ^ b;
b = a ^ b;
a = a ^ b;
// 乘除法(b != 0)
a = a * b
b = a / b
a = a / b
== 和 equals 区别
==
是一个比较运算符- 对于基本类型,比较的是具体的数值(
int
、double
…); - 对于引用类型,比较的是引用,也就是对应的内存地址;
- 对于基本类型,比较的是具体的数值(
equals
是超类Object
就具有的方法,因此所有的引用类型都具有这个方法,只用来比较引用数据类型;equals
方法默认比较对象内存地址,如果重写该方法(例如String
,Integer
等)比较的也是对应的属性值;
Object
类中 equals
方法源码:
public boolean equals(Object obj) {
return (this == obj);
}
由此可见,equals
方法判断的本质也是 ==
。当两个对象进行判断时,归根结底是判断这二者的内存地址是否相同(重写 equals
方法的类对象(String
, Integer
…)除外)。
new
对象是在堆内存中开辟一份空间,所以其引用变量就是指向了堆内存的地址;
图示:
示例:
String a = "abcde";
String b = "abcde";
String c = new String("abcde");
System.out.println(a==b); //true
System.out.println(a==c); //false
System.out.println(a.equals(b)); //true
System.out.println(a.equals(c)); //true
如上述所说,String
类重写了父类 Object
的 equals
方法,所以最后一个 equals
方法判断的也是属性值;
String
类中 equals
方法源码:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
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;
}
hashCode() 与 equals()
https://www.cnblogs.com/justdojava/p/11271438.html
理解 hash:https://www.cnblogs.com/JulianHuang/p/11275611.html
hashCode()
hashCode()
的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。- 哈希码的作用是确定该对象在哈希表中的索引位置。
hashCode()
定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有hashCode()
函数。equals()
equals()
它的作用是判断两个对象是否相等,如果对象重写了equals()
方法,比较两个对象的内容是否相等;如果没有重写,比较两个对象的地址是否相同,价于“==”。同样的,equals()
定义在 JDK 的Object.java 中,这就意味着 Java中 的任何类都包含有equals()
函数。hashCode 与 equals 的关系
- 如果我们没有在 HashMap、HashSet、HashTable 等本质是散列表的数据结构中使用到一个类,那么这个类的 hashCode() 和 equals() 就没有关系!这时,equals() 使用来比较该类的两个对象是否相等,而 hashCode() 根本没有任何作用。
- 反之,我们在散列表的数据结构中使用该类,如创建该类的 HashSet 集合。在这种情况下,该类的“hashCode() 和 equals() ”是有关系的:
- 如果两个对象相等,那么它们的 hashCode() 值一定相同。这里的相等是指,通过 equals() 比较两个对象时返回 true。
如果两个对象 hashCode() 相等,它们并不一定相等。因为在散列表中,hashCode() 相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等,此时就出现所谓的哈希冲突场景。
为什么重写 equals 时必须重写 hashCode ⽅法?
如果两个对象相等,则 hashcode 一定也是相同的。两个对象相等,对两个对象分别调用 equals 方法都返回 true。但是,两个对象有相同的 hashcode 值(只能说明在哈希表中的索引相同),它们的值不⼀定是相等的 。因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖。
两个对象的 hashCode() 相同,则 equals() 一定为 true?
如果两个对象相等(
equals()
值为true),那么它们的hashCode()
一定相同;- 如果两个对象的
hashCode()
相同,它们的equals()
不一定为true;static关键字?是否可覆盖?
static
关键字表明一个成员变量或者是成员方法在没有所属的类实例变量的情况下允许被访问。被static
标记的方法不能被覆盖,因为方法的覆盖是基于运行时动态绑定的,而static
方法在编译时就已经和该类绑定了,也就是static
标记的变量、方法都是随着类的加载而加载的,所以可以通过不创建对象实例就能访问该变量、方法;而不创建对象实例就访问非static
变量、方法是不行的,因为此时变量、方法还没有被创建。
static
关键字修饰的变量属于类变量,不需要实例化对象就可以使用,使用方式:类名.变量名
;static
关键字修饰的方法属于类方法,不需要实例化对象就可以使用,使用方式:类名.方法名
;
final
可以用来修饰类、方法、变量(包括成员变量和局部变量)。
- 被final修饰的类不能被继承;
- 被final修饰的方法不能被重写(覆盖)
final
类中的成员方法都会被隐式地指定为final
方法;- 如果父类中
final
修饰的方法同时访问控制权限为private
,将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与final
的矛盾,而是在子类中重新定义了新的方法。 private
方法通常被自动认为是final
方法(并不是)
- 被final修饰的成员变量表示为常量,必须在初始化时(声明时,或构造函数中)赋值,赋值后不可变;
abstract 关键字可以用来修饰类、方法;
用 abstract 修饰的类称为抽象类;
- 抽象类不能被实例化;
- 抽象方法不能有方法体,即抽象类中的方法只能声明,不能有具体的实现;
- 子类继承抽象类,必须重写父类中所有的抽象方法,否则子类也必须声明为抽象类;
- 抽象类中可以存在属性和方法,即抽象类中可以有非抽象的方法和属性;
普通类和抽象类的区别
- 普通类不能包含抽象方法,抽象类可以包含抽象方法。
- 抽象类不能直接实例化,普通类可以直接实例化
抽象类能被 final 修饰吗?
不能,定义抽象类就是让其他类继承的,
如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾。
接口与抽象类的区别
- 接口中所有的方法隐含都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法;
接口中定义的方法必须是不包含具体实现的(即隐式的抽象方法);但抽象类中既可以定义抽象方法(不包含具体实现)又可以定义非抽象方法 ( 包含具体实现 ); - 类实现接口就必须实现接口中定义的所有方法;但继承一个抽象类,可以不实现抽象类中的抽象方法 ( 但前提是这个类也是抽象的 );
- 类可以实现很多个接口,但是只能继承一个抽象类;
- 抽象类中可以有构造函数,可以有main方法并可以运行,而接口中都不存在;
- 抽象类可以是不提供接口方法实现的情况下实现接口;
- 接口中声明的变量默认都是final的;但抽象类可以包含非final的变量;
- 接口中的成员方法默认都是public的;但抽象类中的成员函数可以是 private、protected、public;
-
String 字符串相关
String 属于基本数据类型吗?
String 是特殊的引用类型
Java八大基础数据类型有:byte(1)、boolean(1)、char(2)、short(2)、int(4)、float(4)、long(8)、double(8);
String
是特殊的引用类型并且是final
的,JVM 使用字符串常量池储存字符串数据。创建新的字符串时,JVM 首先会去字符串常量池中寻找有没有该字符串,如果没有就添加到该常量池,如果有就返回该字符串在常量池中的引用。String s = "a"; s += "b";
,这段代码执行前后,字符串常量池中将出现a
和ab
两个字符串常量,而原本s
变量的引用指向了常量池中ab
。String s = new String("ab")
,这段代码一共创建了几个对象?一个或两个。如果字符串常量池中有了ab
这个字符串(比如在此之前已经使用了String str = "ab"
),那么新的s
对象引用其实仅仅是指向了字符串常量中的ab
,并没有创建新的字符串对象。但是,每次调用new
都会在堆内存开辟空间,创建一个String对象,这是肯定的。String、StringBuffer、StringBuilder的区别
String
是不可变字符串,StringBuffer
和StringBuilder
是可变字符串。如果经常改变字符串的原始数据,最好使用StringBuilder
代替。String
默认重写了equals
方法和hashCode()
方法;而StringBuffer
没有重写equals
方法,使用new StringBuffer("")
会直接在堆内存中开辟空间储存对象。因此将StringBuffer
对象储存进 Java 集合中可能会出现问题(通过 Hash 计算索引时会根据 equals 判断是否相等,这里所有的 StringBuffer 对象都是新对象,equals 判定为 false)。StringBuffer
是线程安全的,效率低;StringBuilder
是线程不安全的,效率高。String str=”i”与 String str=new String(“i”)一样吗?
不一样,因为内存的分配方式不一样。String str = "i"
的方式,Java 虚拟机会将其分配到常量池中;而 String str = new String("i")
则会被分到堆内存中。
string str = new string(“abc”) 创建了几个对象
一个或两个
如果字符串常量池内没有字符串 “abc”,就会创建两个对象;如果存在则只创建一个。
如何将字符串反转?
- 使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
示例代码:
// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("abcdefg");
System.out.println(stringBuffer.reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("abcdefg");
System.out.println(stringBuilder.reverse()); // gfedcba
String 类的常用方法都有那些?
- indexOf():返回指定字符的索引。
- charAt():返回指定索引处的字符。
- replace():字符串替换。
- trim():去除字符串两端空白。
- split():分割字符串,返回一个分割后的字符串数组。
- getBytes():返回字符串的 byte 类型数组。
- length():返回字符串长度。
- toLowerCase():将字符串转成小写字母。
- toUpperCase():将字符串转成大写字符。
- substring():截取字符串。
- equals():字符串比较。
Math 数学计算相关
Java中的Math.round(-1.5)等于多少?
等于 -1,因为数轴上取值时,中间值(0.5)向右取整,所以正 0.5 是往上取整,负 0.5 是直接舍弃。
System.out.println(Math.round(-1.5)); //-1
System.out.println(Math.round(1.5)); //2
System.out.println(Math.round(-1.4)); //-1
System.out.println(Math.round(1.4)); //1
System.out.println(Math.round(-1.6)); //-2
IO流
IO 流详细内容查看 File 类 IO 流
Java IO流的分类
数据的输入和输出在计算机中最终都是通过字节的形式传递的,对应通过InputStream
和OutputStream
实现,他们都是针对字节操作的。
而有时候通常需要读取一些完全是字符的文本数据,通常使用基于字节流的包装类字符流完成操作,他们通过InputStreamReader
和OutputStreamWriter
实现。
字符流是字节流的包装,即使有时候读取的是字符流,但也可能需要转换为字节写入。
字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。