一、JAVA基础
一、基本概念
1、配置环境变量
1.首先打开环境变量,然后在系统变量添加:
2.在path里面配置。
%JAVA_HOME%\bin
3.编写第一个java程序
public class HelloWorld{
public static void main(String[] args){
System.out.println("Hello World");
}
}
//println和print的区别
//println是输出之后换行
4.编译,运行
javac HelloWorld.java
java HelloWorld
添加: 查看字节码指令
1.编译 2.cd cd out/production/day1 (切换到文件对应的目录)
3. javap -v HelloWorld.class
2.面向过程和面向对象的区别?
- 面向过程:面向过程的性能比面向对象高。因为类调用的时候需要实例化,这个过程比较消耗资源。比如单片机、嵌入式开发、Linux等一般采用面对过程开发。但是面向过程不具备面对对象的易维护、易复用、易扩展的优点。
- 面向对象:易维护、易复用、易扩展。因为⾯向对象有封装、继承、多态性的特性,它可以设计出低耦合、更灵活的系统。但他的性能比面向过程低。
3.java语言有哪些特点:
- 简单易学;
- ⾯向对象(封装,继承,多态);
- 平台⽆关性( Java 虚拟机实现平台⽆关性);
- 可靠性;
- 安全性;
- ⽀持多线程( C++ 语⾔没有内置的多线程机制,因此必须调⽤操作系统的多线程功能来进
⾏多线程程序设计,⽽ Java 语⾔却提供了多线程⽀持); - ⽀持⽹络编程并且很⽅便( Java 语⾔诞⽣本身就是为简化⽹络编程设计的,因此 Java 语
⾔不仅⽀持⽹络编程⽽且很⽅便); - 编译与解释并存;
4.什么是JVM、 JDK 、 JRE?
1)JVM
Java 虚拟机(JVM)是运⾏ Java 字节码的虚拟机。JVM 有针对不同系统的特定实现 (Windows,Linux,macOS),⽬的是使⽤相同的字节码,它们都会给出相同的结果。字节码 和不同系统的 JVM 实现是 Java 语⾔“⼀次编译,随处可以运⾏”的关键所在。
2)JDK
它拥有 JRE 所拥有的⼀切,还有编译器(javac)和⼯具。它能够创建和编译程序。
3)JRE是 Java 运⾏时环境。它是运⾏已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机
(JVM),Java 类库,java 命令和其他的⼀些基础构件。但是,它不能⽤于创建新程序。
5. Java 和 C++的区别?
- 都是⾯向对象的语⾔,都⽀持封装、继承和多态
- Java 不提供指针来直接访问内存,程序内存更加安全
- Java 的类是单继承的,C++ ⽀持多重继承;虽然 Java 的类不可以多继承,但是接⼝可以多 继承。
- Java 有⾃动内存管理机制,不需要程序员⼿动释放⽆⽤内存
二、变量和运算符
一、数据类型
1.基本类型
整数型 | byte(1个字节) | short(2个字节) | int(4个字节) | long(8个字节) |
---|---|---|---|---|
浮点型 | float(4个字节) | double(8个字节) | ||
布尔型 | boolean[1个字节) | |||
字符型 | char(2个字节) |
public class VariableDemo3{
public static void main(String[] args){
//1.定义byte类型的变量
//数据类型 变量名 = 数据值;
byte a = 10;
System.out.println(a);
//2.定义short类型的变量
short b = 20;
System.out.println(b);
//3.定义int类型的变量
int c = 30;
System.out.println(c);
//4.定义long类型的变量
long d = 123456789123456789L;
System.out.println(d);
//5.定义float类型的变量
float e = 10.1F;
System.out.println(e);
//6.定义double类型的变量
double f = 20.3;
System.out.println(f);
//7.定义char类型的变量
char g = 'a';
System.out.println(g);
//8.定义boolean类型的变量
boolean h = true;
System.out.println(h);
}
}
注意:
1.JVM 会在编译时期将 boolean 类型的数据转换为 int,使用 1 来表示 true,0 表示 false。
2.long写的时候需要带l或L,float写的时候需要带f或F。
2.引用类型
- 类(class)
- 接口(interface)
- 数组([])
3.包装类型
这八种基本类型都有对应的包装类分别为:Byte、Short、Integer、Long、Float、Double、Boolean 、Character。
Integer的默认值是null; int的默认值是0。
基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。
Integer x = 2; // 装箱 调用了 Integer.valueOf(2)
int y = x; // 拆箱 调用了 X.intValue()
自动装箱演示:
//考虑以下代码:
List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50; i += 2)
li.add(i);
//实际上是:
List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50; i += 2)
li.add(Integer.valueOf(i));
自动拆箱演示:
//考虑以下方法:
public static int sumEven(List<Integer> li) {
int sum = 0;
for (Integer i: li)
if (i % 2 == 0)
sum += i;
return sum;
}
//实际上是:
public static int sumEven(List<Integer> li) {
int sum = 0;
for (Integer i : li)
if (i.intValue() % 2 == 0)
sum += i.intValue();
return sum;
}
Integer.parseInt()
重点方法:
static int parseInt(String s)
静态方法,传参String,返回int
int retValue=Integer.parseInt("123")
System.out.println(retValue+100);
(面试)手写一个Integer.parseInt()方法
public class Demo {
public static void main(String[] args) {
int num = parseInt("123456");
System.out.println(num);
}
public static int parseInt(String str) {
//声明一个字符数组
char[] chars = new char[str.length()];
//声明一个整型数组(用来装字符转换成整型数字结果)
int[] ints = new int[str.length()];
for (int i = 0; i < str.length(); i++) {
//分解字符串,装入字符数组
chars[i] = str.charAt(i);
//把字符数组里的字符通过-'0'转换成整型数字,装入整型数组
ints[i] = chars[i] - '0';
}
//把整型数组转换成整型
int num = 0;
for (int i = 0; i < ints.length; i++) {
num = num * 10 + ints[i];
}
return num;
}
}
缓存池
new Integer(123) 与 Integer.valueOf(123) 的区别在于:
- new Integer(123) 每次都会新建一个对象;
- Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。
valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。Integer x = new Integer(123);
Integer y = new Integer(123);
System.out.println(x == y); // false
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z == k); // true
在 Java 8 中,Integer 缓存池的大小默认为 -128~127。 ```java static final int low = -128;////缓存下届,不可改变了,只有上届可以改变 static final int high; static final Integer cache[];public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
static { // high value may be configured by property int h = 127;/h值,可以通过设置jdk的AutoBoxCacheMax参数调整(以下有解释), 自动缓存区间设置为[-128,N]。注意区间的下界是固定 / String integerCacheHighPropValue = sun.misc.VM.getSavedProperty(“java.lang.Integer.IntegerCache.high”); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127);//取较大的作为上界 //但要注意不能比Integer的边界MAX_VALUE大 // 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;
}
编译器会在自动装箱过程调用 valueOf() 方法,因此多个值相同且值在缓存池范围内的 Integer 实例使用自动装箱来创建,那么就会引用相同的对象。
```java
Integer m = 123;
Integer n = 123;
System.out.println(m == n); // true
基本类型对应的缓冲池如下:
- boolean values true and false
- all byte values
- short values between -128 and 127
- int values between -128 and 127
- char in the range \u0000 to \u007F
在使用这些基本类型对应的包装类型时,如果该数值范围在缓冲池范围内,就可以直接使用缓冲池中的对象。
在 jdk 1.8 所有的数值类缓冲池中,Integer 的缓冲池 IntegerCache 很特殊,这个缓冲池的下界是 - 128,上界默认是 127,但是这个上界是可调的,在启动 jvm 的时候,通过 -XX:AutoBoxCacheMax=
注意:float和double 是没有缓存池的,每次都是new一个新的对象。
二、变量
public class Demo{
//类变量
static int i=1;
//实例变量
int k=0;
public void method(){
//局部变量,必须赋值。
int i=0;
}
}
1.从语法形式上看:成员变量是属于类的,⽽局部变量是在⽅法中定义的变量或是⽅法的参数;成员变量可以被public,private,static等修饰符修饰,而局部变量不能被访问修饰符及static修饰。但是成员变量和局部变量都能被final修饰。
2.从变量在内存中的存储⽅式来看:如果成员变量是使用 static 修饰的,那么这个成员变量是属于类的,如果没有使用 static 修饰,这个成员变量是属于实例的。对象存于堆内存,如果局部变量类型为基本数据类型,那么存储在栈内存,如果为引⽤数据类型,那存放的是指向堆内存对象的引用或者是指向常量池中的地址。
3.从变量在内存中的⽣存时间上看:成员变量是对象的⼀部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
4. 成员变量如果没有被赋初值:则会自动以类型的默认值⽽赋值(⼀种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会⾃动赋值。
重点::有static的: 用类名去调 (注意:静态不会空指针。) 没有static的: 先new对象用引用去调。
三、运算
1.算术运算符
+ - * / % ++ --
i++,其实相当于i=i+1;
int num1 = 12;
int num2 = num1++;
System.out.println(num2);
结果是:12
先赋值后计算
int num1 = 12;
int num2 = ++num1;
System.out.println(num2);
结果是:13
先计算后赋值
自动类型提升:
结论:当容量小的数据类型的变量与容量大的数据类型变量做运算时,结果自动提升为容量大的数据类型。
特别的:当byte short char 三种数据类型的变量做运算时,结果为int型。
强制类型转换:(可能导致精度损失)
Double d1 =12.9;
int i1 = (int) d1;
System.out.println(i1);//12
由此引出一个结论:
算术运算符,int以下的做运算,结果都是int类型,long和其他的类型做运算,结果是long。
2.逻辑运算符
1. & 与 、 | 或
与(相当于和的意思,两边都正确才是正确的)
条件1 condition1 | 条件2 condition2 | 结果 |
---|---|---|
true | true | true |
true | false | false |
false | true | false |
false | false | false |
或(只要有一个正确,就可以)
条件1 condition1 | 条件2 condition2 | 结果 |
---|---|---|
true | true | true |
true | false | true |
false | true | true |
false | false | false |
2.短路运算符
&& 短路运算符 || 短路运算符
public class Test2 {
public static void main(String[] args) {
int i1=1;
int i2=1;
int k1=2;
int k2=2;
if(i1==k1&&k1==k2){
System.out.println("前面错误、不会进行后面的运算");
}
if(i1==i2||i1==k1){
System.out.println("前面正确、不会进行后面的运算");
}
}
}
3.三元运算符
条件 ? 结果1 : 结果2;
条件的结果一定是boolean ,只能是true或者false;
条件是可以复杂的
boolean condition1 = 5 > 3;
boolean condition2 = 5 > 3;
boolean condition3 = 5 > 3;
int num = (condition1 && (condition2 || condition3)) ? 1 : 2;
4.位移运算符
详细解析请点击:计算机基础 《计算机基础内容》《计算机基础内容》
用最有效率的方法计算 2 乘以 8
2 << 3
左移 3 位相当于乘以 2 的 3 次方,右移 3 位相当于除以 2 的 3 次 方)
5.常见的面试题
2.2Math.round()
四舍 五入
Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。
2.3float与double
float f =3.4正确吗?
不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于 向下转型会造成精度损失,因此需要强制类型转 换float f =(float)3.4; 或者写成 float f =3.4F;。
2.4隐私类型转换
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);其 中有隐含的强制类型转换。
2.5switch
从 Java 7 开始,可以在 switch 条件判断语句中使用 String 对象。
String s = "a";
switch (s) {
case "a":
System.out.println("aaa");
break;
case "b":
System.out.println("bbb");
break;
}
switch 不支持 long、float、double,是因为 switch 的设计初衷是对那些只有少数几个值的类型进行等值判断,如果值过于复杂,那么还是用 if 比较合适。
三、流程控制语句
1.if语句
if(布尔表达式){
java语句;
java语句;
}
执行原理:如果布尔表达式的结果是true,
则执行大括号中的程序。
如果是false,则执行括号外面的。
if(布尔表达式){
java语句;//分支一
}else{
java语句;//分支二
}
执行原理:如果布尔型表达式的结果是true,则执行分支一,分支二不执行。
一定会从2个中选一个的。
if(布尔表达式1){
java语句;
}else if(布尔表达式2){
java语句;
}else if(布尔表达式3){
java语句;
}else if(布尔表达式4){
java语句;
}else{
java语句;
}//以上条件没有一个成立的。这个else就执行了。
2.for循环
for(定义初始变量1;循环条件2;变量的变化值4){
循环语句块3;
}
for语句执行顺序为 1 2 3 4 2 3 4...
其中 ①定义初始变量只在进入循环时执行一次
如果循环条件有多个,则需要使用&& || 分隔开
如果初始变量和变化值有多个,则只需使用 , 逗号分隔开
3.foreach
foreach循环语句
for (元素类型 变量名 : 数组或集合) {
System.out.println(变量名);
}
四、方法
参数传递
Java 的参数是以值传递的形式传入方法中,而不是引用传递。
以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。
public class Dog {
String name;
Dog(String name) {
this.name = name;
}
String getName() {
return this.name;
}
void setName(String name) {
this.name = name;
}
String getObjectAddress() {
return super.toString();
}
}
以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。
在方法中改变对象的字段值会改变原对象该字段值,因为引用的是同一个对象。
class PassByValueExample {
public static void main(String[] args) {
Dog dog = new Dog("A");
func(dog);
System.out.println(dog.getName()); // B
}
private static void func(Dog dog) {
dog.setName("B");
}
}
但是在方法中将指针引用了其它对象,那么此时方法里和方法外的两个指针指向了不同的对象,在一个指针改变其所指向对象的内容对另一个指针所指向的对象没有影响。
public class PassByValueExample {
public static void main(String[] args) {
Dog dog = new Dog("A");
System.out.println(dog.getObjectAddress()); // Dog@4554617c
func(dog);
System.out.println(dog.getObjectAddress()); // Dog@4554617c
System.out.println(dog.getName()); // A
}
private static void func(Dog dog) {
System.out.println(dog.getObjectAddress()); // Dog@4554617c
dog = new Dog("B");
System.out.println(dog.getObjectAddress()); // Dog@74a14482
System.out.println(dog.getName()); // B
}
}
重写与重载
重载(Overload)
存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。
应该注意的是,返回值不同,其它都相同不算是重载。
class OverloadingExample {
public void show(int x) {
System.out.println(x);
}
public void show(int x, String y) {
System.out.println(x + " " + y);
}
}
public static void main(String[] args) {
OverloadingExample example = new OverloadingExample();
example.show(1);
example.show(1, "2");
}
重写
1. 重写(Override)
存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
为了满足里式替换原则,重写有以下三个限制:
- 子类方法的访问权限必须大于等于父类方法;
- 子类方法的返回类型必须是父类方法返回类型或为其子类型。
- 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。
使用 @Override 注解,可以让编译器帮忙检查是否满足上面的三个限制条件。
下面的示例中,SubClass 为 SuperClass 的子类,SubClass 重写了 SuperClass 的 func() 方法。其中:
- 子类方法访问权限为 public,大于父类的 protected。
- 子类的返回类型为 ArrayList
,是父类返回类型 List 的子类。 - 子类抛出的异常类型为 Exception,是父类抛出异常 Throwable 的子类。
- 子类重写方法使用 @Override 注解,从而让编译器自动检查是否满足限制条件。
```java
class SuperClass {
protected List
func() throws Throwable {
} }return new ArrayList<>();
class SubClass extends SuperClass {
@Override
public ArrayList
在调用一个方法时,先从本类中查找看是否有对应的方法,如果没有再到父类中查看,看是否从父类继承来。否则就要对参数进行转型,转成父类之后看是否有对应的方法。总的来说,方法调用的优先级为:
- this.func(this)
- super.func(this)
- this.func(super)
- super.func(super)
```java
/*
A
|
B
|
C
|
D
*/
class A {
public void show(A obj) {
System.out.println("A.show(A)");
}
public void show(C obj) {
System.out.println("A.show(C)");
}
}
class B extends A {
@Override
public void show(A obj) {
System.out.println("B.show(A)");
}
}
class C extends B {
}
class D extends C {
}
public static void main(String[] args) {
A a = new A();
B b = new B();
C c = new C();
D d = new D();
// 在 A 中存在 show(A obj),直接调用
a.show(a); // A.show(A)
// 在 A 中不存在 show(B obj),将 B 转型成其父类 A
a.show(b); // A.show(A)
// 在 B 中存在从 A 继承来的 show(C obj),直接调用
b.show(c); // A.show(C)
// 在 B 中不存在 show(D obj),但是存在从 A 继承来的 show(C obj),将 D 转型成其父类 C
b.show(d); // A.show(C)
// 引用的还是 B 对象,所以 ba 和 b 的调用结果一样
A ba = new B();
ba.show(c); // A.show(C)
ba.show(d); // A.show(C)
}
五、常用的api
1.Date
public class Test3 {
public static void main(String[] args) {
//Date怎么转化成String
Date nowtime = new Date();
SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String nowTime=sim.format(nowtime);
System.out.println(nowTime);
}
}
currentTimeMillis();
public class Test4 {
public static void main(String[] args) {
//获取自1970年1月1日00:00 :00 000到当前时间的总毫秒数
long nowTimeMillis=System.currentTimeMillis();
System.out.println(nowTimeMillis);
}
}
2.数学类
1.random
public class Test5 {
public static void main(String[] args) {
Random random = new Random();
int i1=random.nextInt(100);
System.out.println(i1);
byte[] bytes = new byte[5];
random.nextBytes(bytes);
System.out.println(Arrays.toString(bytes));
}
}
61 [43, 66, 2, 113, 114]
2. BigDecimal
zh:BigDecimal 属于大数据,精度极高。不属于基本数据类型,属于java对象(引用数据类型)。
面试:你处理过财务数据吗?用的哪一种类型?
千万别说double,要说java.math.BigDecimal
public class Test5 {
public static void main(String[] args) {
System.out.println(0.1+0.2);
BigDecimal bigDecimal = new BigDecimal("0.1");
BigDecimal num= bigDecimal.add(new BigDecimal("0.2"));
System.out.println(num);
}
}
0.30000000000000004 0.3
3.Math
public class Test5 {
public static void main(String[] args) {
System.out.println(Math.abs(-50));//绝对值 50
System.out.println(Math.ceil(3.01)); // 向上取整4.0
System.out.println(Math.floor(3.99)); //向下取整 3.0
System.out.println(Math.round(3.554)); // 四舍五入4
System.out.println(Math.sqrt(64.0)); // 平方根8. 0
System.out.println(Math.max(56, 78)); //两者之间较大的78
System.out.println(Math.min(56, 78)); //两者之间较小的56
System.out.println(Math.random()); // 随机数
System.out.println(Math.pow(2, 10)); //幂 1024.0
}
}
3.工具类
1.Arrays
1.更友好的显示数组
System.out.println(Arrays.toString(nums));
[1, 2, 3, 5, 6, 9]
否则就是:I@1b6d3586
2.排序
int[] nums = {1,3,9,2,5,6};
Arrays.sort(nums);
System.out.println(Arrays.toString(nums));
[1, 2, 3, 5, 6, 9]
3.二分查找
int[] nums = {1, 2, 3, 5, 6, 9};
int i = Arrays.binarySearch(nums, 5);
System.out.println(i);
结果:3 这是下标
4.数组拷贝
int[] nums = {1, 2, 3, 5, 6, 9};
nums = Arrays.copyOfRange(nums,0,2);
System.out.println(Arrays.toString(nums));
结果:[1, 2]
5.两个数组的比较
int[] nums = {1, 2, 3, 5, 6, 9};
int[] nums2 = {1, 2, 3, 5, 6, 9};
System.out.println(Arrays.equals(nums,nums2));
结果:true
4.Object
概述
public native int hashCode()
//native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。
public boolean equals(Object obj)
//用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户比较字符串的值是否相等。
protected native Object clone() throws CloneNotSupportedException
/*naitive方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,
表达式 x.clone() != x 为true,x.clone().getClass() == x.getClass() 为true。
Object本身没有实现Cloneable接口,
所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。*/
public String toString()
//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。
public final native Class<?> getClass()
//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。
protected void finalize() throws Throwable {}
//实例被垃圾回收器回收的时候触发的操作
public final native void notify()
/*native方法,并且不能重写。
唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。
如果有多个线程在等待只会任意唤醒一个 */
public final native void notifyAll()
/*native方法,并且不能重写。
跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程*/
public final native void wait(long timeout) throws InterruptedException
//native方法,并且不能重写。暂停线程的执行。
//注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间
public final void wait(long timeout, int nanos) throws InterruptedException
//多了nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。
//所以超时的时间还需要加上nanos毫秒。
public final void wait() throws InterruptedException
//跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
==和equals()方法有什么区别
== 解读
对于基本类型和引⽤类型 == 的作⽤效果是不同的,如下所示:
基本类型:⽐较的是值是否相同;
引⽤类型:⽐较的是引⽤是否相同;
代码示例:
String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true
代码解读:因为 x 和 y 指向的是同⼀个引⽤,所以 == 也是 true,⽽ new String()⽅法则重写开辟了内存空 间,所以 == 结果为 false,⽽ equals ⽐较的⼀直是值,所以结果都为 true。
equals 解读
public class equals {
public static void main(String[] args) {
/* equals方法的源代码:
public boolean euqals(Object obj){
return(this==obj);
*/
MyTime m1 = new MyTime(2008,8,8);
MyTime m2 = new MyTime(2008,8,8);
//重写equals方法之前
System.out.println(m1==m2);//false
System.out.println(m1.equals(m2));//false
//重写equals方法之后
System.out.println(m1.equals(m2));//true
}
}
class MyTime{
int year;
int month;
int day;
public MyTime() {
}
public MyTime(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public boolean equals(Object obj){
if(obj==null||!(obj instanceof MyTime)){
return false;
}
if(this==obj){
return true;
}
MyTime t =(MyTime)obj;
return this.year==t.year&&this.month==t.month&&this.day==t.day;
}
}
总结 :== 对于基本类型来说是值⽐较,对于引⽤类型来说是⽐较的是引⽤;⽽ equals 默认情况下是引⽤ ⽐较,只是很多类重写了 equals ⽅法,⽐如 String、Integer 等把它变成了值⽐较,所以⼀般情况下 equals ⽐较的是值是否相等。
HashCode()
概述
hashCode() 返回哈希值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价,这是因为计算哈希值具有随机性,两个值不同的对象可能计算出相同的哈希值。
在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象哈希值也相等。
为什么要有 hashCode
我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:
对象加入HashSet,先计算该对象的hashcode值,与其他已经加入的对象的hashcode作比较,如果没有相同的hashcode,hashset会假设该对象没有重复出现。如果有重复的hashcode,调用equals()方法进行比较,如果对象相同,就不会让其加入,如果对象不同,会被散列到其他位置。
这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
toString方法
1:源代码是这样的
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
2:以后所有的子类都重写toString方法。
3:重写就是把Object类的toString方法给覆盖了。
4:输出引用的时候会自动调用toStirng方法。【println底层调用了toString方法】
public class toString {
public static void main(String[] args) {
// 重写toString之前
MyTime m1 = new MyTime(1970,1,1);
System.out.println(m1); //MyTime@4554617c
// 重写toString之后
MyTime m2 = new MyTime(1970,1,1);
System.out.println(m2);//1970/1/1日
}
}
class MyTime{
int year;
int month;
int day;
public MyTime(){
}
public MyTime(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
@Override
public String toString() {
return this.year+"/"+this.month+"/"+this.day+"日";
}
}
Clone()
1. cloneable
clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
public class CloneExample {
private int a;
private int b;
}
CloneExample e1 = new CloneExample();
// CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object'
重写 clone() 得到以下实现:
public class CloneExample {
private int a;
private int b;
@Override
public CloneExample clone() throws CloneNotSupportedException {
return (CloneExample)super.clone();
}
}
CloneExample e1 = new CloneExample();
try {
CloneExample e2 = e1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
java.lang.CloneNotSupportedException: CloneExample
以上抛出了 CloneNotSupportedException,这是因为 CloneExample 没有实现 Cloneable 接口。
应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。
public class CloneExample implements Cloneable {
private int a;
private int b;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
2. 浅拷贝
拷贝对象和原始对象的引用类型引用同一个对象。
public class ShallowCloneExample implements Cloneable {
private int[] arr;
public ShallowCloneExample() {
arr = new int[10];
for (int i = 0; i < arr.length; i++) {
arr[i] = i;
}
}
public void set(int index, int value) {
arr[index] = value;
}
public int get(int index) {
return arr[index];
}
@Override
protected ShallowCloneExample clone() throws CloneNotSupportedException {
return (ShallowCloneExample) super.clone();
}
}
ShallowCloneExample e1 = new ShallowCloneExample();
ShallowCloneExample e2 = null;
try {
e2 = e1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); // 222
3. 深拷贝
拷贝对象和原始对象的引用类型引用不同对象。
public class DeepCloneExample implements Cloneable {
private int[] arr;
public DeepCloneExample() {
arr = new int[10];
for (int i = 0; i < arr.length; i++) {
arr[i] = i;
}
}
public void set(int index, int value) {
arr[index] = value;
}
public int get(int index) {
return arr[index];
}
@Override
protected DeepCloneExample clone() throws CloneNotSupportedException {
DeepCloneExample result = (DeepCloneExample) super.clone();
result.arr = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
result.arr[i] = arr[i];
}
return result;
}
}
DeepCloneExample e1 = new DeepCloneExample();
DeepCloneExample e2 = null;
try {
e2 = e1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); // 2
4. clone() 的替代方案
使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。
public class CloneConstructorExample {
private int[] arr;
public CloneConstructorExample() {
arr = new int[10];
for (int i = 0; i < arr.length; i++) {
arr[i] = i;
}
}
public CloneConstructorExample(CloneConstructorExample original) {
arr = new int[original.arr.length];
for (int i = 0; i < original.arr.length; i++) {
arr[i] = original.arr[i];
}
}
public void set(int index, int value) {
arr[index] = value;
}
public int get(int index) {
return arr[index];
}
}
CloneConstructorExample e1 = new CloneConstructorExample();
CloneConstructorExample e2 = new CloneConstructorExample(e1);
e1.set(2, 222);
System.out.println(e2.get(2)); // 2
六、String
概览
String 被声明为 final,因此它不可被继承。(Integer 等包装类也不能被继承)
在 Java 8 中,String 内部使用 char 数组存储数据。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final byte[] value;
/** The identifier of the encoding used to encode the bytes in {@code value}. */
private final byte coder;
}
value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。
不可变的好处
1. 可以缓存 hash 值
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
2. String Pool 的需要
如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
3. 安全性
String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。
4. 线程安全
String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
String, StringBuffer and StringBuilder
1. 可变性
- String 不可变
- StringBuffer 和 StringBuilder 可变
2. 线程安全
- String 不可变,因此是线程安全的
- StringBuilder 不是线程安全的
- StringBuffer 是线程安全的,内部使用 synchronized 进行同步
String Pool
概念:String str = “abc” str代表字符串常量 “abc”代表字符串对象1.使用字面量创建字符串对象
String str = “abc”
将字符串常量池中的”abc”地址返回给str,如果没有创建再返回。2.使用new关键字新建一个字符串对象
String str = new String(“abc”);
将字符串常量池中的”abc”复制一份给堆中,如果没有创建再给,然后将堆中对象的内存地址返回给str。 ```java String s1 = “abc”; String s2 = “abc”; System.out.println(s1==s2);//true
String x = new String(“xyz”); String y= new String(“xyz”); System.out.println(x=y); //false
![](https://cdn.nlark.com/yuque/0/2021/png/22016332/1637405438509-397fe510-d6cf-4ab1-a7a3-604113f7bdcc.png?x-oss-process=image%2Fresize%2Cw_1152%2Climit_0#averageHue=%23fafafa&from=url&id=Wvi01&originHeight=648&originWidth=1152&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)<br />在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。<br />(这图表示的是新版的)
<a name="Dxrfq"></a>
#### intern() 1.8
调用字符串对象的intern方法,会将该字符串对象尝试放入到串池中
- 如果串池中没有该字符串对象,则放入成功
- 如果有该字符串对象,则放入失败
无论放入是否成功,都会返回**串池中**的字符串对象
以下是jdk8的代码演示:
```java
public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);
//-------------------------------------------------------------------
//["1"]本来就有
String s3 = new String("1") + new String("1");
// new String("1")本来就有 new String("11")
//此时常量池中是没有 "11"的
s3.intern();
//创建"11",objc3引用的对象的地址和"11"地址是相同的.
String s4 = "11";
System.out.println(s3 == s4);
}
false true
注:图中绿色线条代表 string 对象的内容指向。 黑色线条代表地址指向。
public static void main(String[] args) {
String s = new String("1");
String s2 = "1";
s.intern();
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4);
}
false false
String常用的方法有哪些
- indexOf():返回指定字符的索引。
- charAt():返回指定索引处的字符。
- replace():字符串替换。
- trim():去除字符串两端空⽩。
- split():分割字符串,返回⼀个分割后的字符串数组。
- getBytes():返回字符串的 byte 类型数组。
- length():返回字符串⻓度。
- toLowerCase():将字符串转成⼩写字⺟。
- toUpperCase():将字符串转成⼤写字符。
- substring():截取字符串。 起始位置[包括] 终止位置[不包括]
- equals():字符串⽐较
- toCharArray(): 将字符串转换为char数组
- 将byte数组一部分转化为字符串
byte[] bytes={97,98,99};
System.out.println(new String(bytes,1,2))//bc
二、JAVA高级
一、面向对象
封装
封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外
界访问,我们大可不必提供方法给外界访问。
public class Person {
private String name;
private int age;
private String gender;
//无参构造
public Person() {
}
//外界只能通过getName获取姓名
public String getName() {
return name;
}
//外界只能通过getGender获取性别
public String getGender() {
return gender;
}
//外界可以通过setGender设置性别
public void setGender(String gender) {
this.gender = gender;
}
//age 属性可以供 work() 方法使用。
public void work(){
if(18<=age && age<50){
System.out.println(name+ "is working very hard");
}else {
System.out.println(name+ "is studying very hard");
}
}
}
继承
继承实现了 IS-A 关系,例如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal,从而获得 Animal 非 private 的属性和方法。(子类只是有,但是不能使用)。
继承应该遵循里氏替换原则,继承必须确保超类所拥有的性质在子类中仍然成立。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 向上转型 。
Animal animal = new Cat();
多态
多态指的是:
父类型引用指向子类型对象。
●编译阶段:绑定父类的方法。
●运行阶段: 动态绑定子类型对象的方法。
运行时多态有三个条件:
- 继承
- 覆盖(重写)
- 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方
法,又能调用子类的方法。
2.运算符:instanceof(运行阶段动态判断)
- instanceof运算符的运算结果只能是:true/false
- 假设(c instanceof Cat)为true表示:
c引用指向的堆内存中的java对像是一个Cat。
- 假设(c instanceof Cat)为false表示:
多态在实际开发中的作用
多态使用之前
public class Dog {
public void eat(){
System.out.println("狗狗喜欢吃骨头,吃的很香");
}
}
public class Cat {
public void eat(){
System.out.println("猫喜欢吃鱼,吃的很香");
}
}
public class Master {
public void feed(Dog d){
d.eat();
}
public void feed(Cat c){
c.eat();
}
public class Test {
public static void main(String[] args) {
Master master = new Master();
Dog dog = new Dog();
master.feed(dog);
Cat cat = new Cat();
master.feed(cat);
}
}
多态使用之后
public class Pet {
public void eat(){
}
}
public class Dog extends Pet{
public void eat(){
System.out.println("狗狗喜欢吃骨头,吃的很香");
}
}
public class Cat extends Pet{
public void eat(){
System.out.println("猫喜欢吃鱼,吃的很香");
}
}
public class Master {
public void feed(Pet pet){
//编译的时候,编译器发现pet是Pet类,会去Pet类中找eat()方法,结果找到了,编译通过。
//运行的时候,底层实际的对象是什么,就自动调用到该实际对象对应的eat()方法上。
//这就是多态的使用。
pet.eat();
}
}
public class Test {
public static void main(String[] args) {
Master master = new Master();
Dog dog = new Dog();
master.feed(dog);
Cat cat = new Cat();
master.feed(cat);
}
}
抽象类和接口
抽象类
抽象类的作用:
降低接口实现类在开发过程中实现接口的难度,同时将接口实现类不需要的方法交给抽象类完成。
接口
接口是完全抽象的,接口中只有抽象方法 + 常量。
接口中的抽象方法的public abstract可以省略。 接口中常量的public static fiana 可以省略。
比较
相同点:
接口和抽象类都不能实例化。
都包含抽象方法,其子类都必须重写这些抽象方法
不同点:
类型 | 抽象类 | 接口 |
---|---|---|
继承 | 抽象类可以继承一个类,但可以实现多个接口。 | 接口只可继承接口(一个或多个) |
成员变量 | 任何类型 | 只能是public static final |
方法 | 任意的修饰符 | 默认使用public修饰符 |
构造器 | 可以有构造器 | 不能有构造器 |
接口在开发中的作用
public interface FoodMeau{
void xihongshi();
void qiezi();
}
public class ChinaCook implements FoodMeau{
public void xihongshi(){
System.out.println("中国西红柿");
}
public void qiezi(){
System.out.println("中国茄子");
}
}
public class AmericCook implements FoodMeau {
public void xihongshi(){
System.out.println("美国西红柿");
}
public void qiezi(){
System.out.println("美国茄子");
}
}
public class Customer {
private FoodMeau foodMeau;
public Customer(){
}
public Customer(FoodMeau foodMeau){
this.foodMeau=foodMeau;
}
public void setFoodMeau( FoodMeau foodMeau){
this.foodMeau=foodMeau;
}
public FoodMeau getFoodMeau(){
return foodMeau;
}
public void order(){
foodMeau.xihongshi();
foodMeau.qiezi();
}
}
public class Test {
public static void main(String[] args) {
//创建厨师对象
FoodMeau cook1 = new ChinaCook();
//创建顾客对象
Customer customer = new Customer(cook1);;//如果去掉参数,则空指针异常
customer.order();//中国西红柿
//中国茄子
}
}
二、关键字
final
1. 数据
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
- 对于基本类型,final 使数值不变;
对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
final int x = 1;
// x = 2; // cannot assign value to final variable 'x'
final A y = new A();
y.a = 1;
2. 方法
声明方法不能被子类重写。
private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。
3. 类
声明类不允许被继承static
1.静态变量
静态变量:又称为类变量,类所有的实例都共享静态变量,也可以直接通过类名来访问静态变量,静态变量在内存中只存在一份。
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
注意:有static的: 用类名去调 (注意:静态不会空指针。) 没有static的: 先new对象用引用去调。
- 存储位置
- 静态变量存储在方法区中的静态区。
- 实例变量存储在堆内存。
- 局部变量储存在栈内存。
- 存储位置
public class A {
private int x; // 实例变量
private static int y; // 静态变量
public static void main(String[] args) {
// int x = A.x; // Non-static field 'x' cannot be referenced from a static context
A a = new A();
int x = a.x;
int y = A.y;
}
}
2.静态方法
静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。
public abstract class A {
public static void func1(){
}
// public abstract static void func2(); // Illegal combination of modifiers: 'abstract' and 'static'
}
只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字,因为这两个关键字与具体对象关联。
public class A {
private static int x;
private int y;
public static void func1(){
int a = x;
// int b = y; // Non-static field 'y' cannot be referenced from a static context
// int b = this.y; // 'A.this' cannot be referenced from a static context
}
}
3. 静态语句块
静态语句块在类初始化时运行一次。
public class A {
static {
System.out.println("123");
}
public static void main(String[] args) {
A a1 = new A();
A a2 = new A();
}
}
123
4.静态内部类
非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。
public class OuterClass {
class InnerClass {
}
static class StaticInnerClass {
}
public static void main(String[] args) {
// InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
OuterClass outerClass = new OuterClass();
InnerClass innerClass = outerClass.new InnerClass();
StaticInnerClass staticInnerClass = new StaticInnerClass();
}
}
静态内部类不能访问外部类的非静态的变量和方法。
5. 静态导包
在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。
import static com.xxx.ClassName.*
6. 初始化顺序
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
public static String staticField = "静态变量";
static {
System.out.println("静态语句块");
}
public String field = "实例变量";
{
System.out.println("普通语句块");
}
最后才是构造函数的初始化。
public InitialOrderTest() {
System.out.println("构造函数");
}
存在继承的情况下,初始化顺序为:
- 父类(静态变量、静态语句块)
- 子类(静态变量、静态语句块)
- 父类(实例变量、普通语句块)
- 父类(构造函数)
- 子类(实例变量、普通语句块)
-
this
1.this关键字可用来引用当前类的实例变量。主要用于形参与成员名字重名,用this来区分。
public Person(String name,int age){
this.name=name;
this.age=age;
}
2.this关键字可用于调用当前类方法。
public class Main{
public void fun1(){
System.out.println("hello,world")
}
public void fun2(){
this.fun1();//this可省略
}
public static void main(String[] args){
Main m = new Main();
m.fun2();
}
}
3.this()可以用来调用当前类的构造函数.(注意this()一定要放在构造函数的第一行)
class Persin{
private String name;
private String age;
public Person(){
}
public Person(String name){
this.name=name;
}
public Person(String name,int age){
this(name);
this.age=age;
}
}
super
1.super可以用来引用直接父类的实际变量(和this相似,主要用来区分父类和子类的字段)
2.super可以用来调用直接父类构造函数(super()一定要放在第一行)
3.super可以用来调用直接父类方法. ```java public class Main{ public static void mian(String[] args) Child child = new Child(“Father”,”Child”); child.test(); } } class Father{ protected String name; public Father(String name){this.name=name;
}
public void Say(){
System.out.println("hello,child");
} } class Child extends Father{ private Stirng mame; public Child(String name1,String name2){
super(name1);//调用直接父类构造函数
this.name=name2;
} public void test(){
System.out.println(this.name);
System.out.println(super.name);//引用直接父类的实例变量
super.Say(); //调用父类的直接方法
} }
- 访问父类的构造函数:
子类只能继承父类的**默认**构造函数,如果父类没有默认的构造函数,那子类不能从父类继承默认构造函数,<br />这时子类必须使用super()来实现对父类的非默认构造函数的调用.
- 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
**this和super的区别**<br />●相同点:<br />1. super()和this()都必须在构造函数的第一行进行调用, 否则就是错误的<br />2. this() 和super()都指的是对象,所以,均不可以在static环境中使用。<br />●不同点:<br />1. super() 主要是对父类构造函数的调用,this() 是对重载构造函数的调用<br />2. super() 主要是在继承了父类的子类的构造函数中使用,是在不同类中的使用; this() 主要<br />是在同一类的不同构造函数中的使用
<a name="i9i1Q"></a>
## 三、异常
**Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: Error 和 Exception。其中 Error 用来表示 JVM 无法处理的错误,Exception 分为两种:**
- **受检异常** :**需要用 try...catch... 语句捕获并进行处理,并且可以从异常中恢复;**
是Exception 中除RuntimeException及其子类之外的异常。Java编译器会检查受检查异常。常见的受检查异常有:IO相关的异常、ClassNotFoundException 、SQLException等。
- **非受检异常** **:是程序运行时错误,例如除 0 会引发 Arithmetic Exception,此时程序崩溃并且无法恢复**。包括RuntimeException类及其子类,表示JVM在运行期间可能出现的异常。Java编译器不会检查运行时异常。例如: NullPointException(空指针)、NumberFormatException(字符串转换为数字)、IndexOutofBoundsException(数组越界)、ClasscastException(类转换异常)、ArraystoreException(数据存储异常,操作数组时类型不一致)等。
![](https://cdn.nlark.com/yuque/0/2021/png/22016332/1627209976997-73217842-0f86-4843-9738-5fd543a2a218.png?x-oss-process=image%2Fresize%2Cw_1547%2Climit_0#averageHue=%233a3939&from=url&id=anx2g&originHeight=858&originWidth=1547&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
<a name="nvvHs"></a>
### (面试)一、受检异常和非受检异常区别是什么?
**受检异常和非受检异常区别是:**是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检查异常,否则就选择非受检查异常。
<a name="uafye"></a>
### (面试)二、throw和 throws的区别是什么?
1.throws:<br />1)声明位置:方法名之后<br />public void test1() throws NullPointerException{<br />}<br />2)作用: 通知开发人员当前方法在运行时,【有可能】抛出异常。<br />3)携带数据: throws 后面携带【异常类型】,一个throws后面可以携带多个异常类型。<br />4)调用: 当一个方法被throws修饰时,调用方法必须考虑异常捕捉问题。
2.throw:<br />1)声明位置:方法执行体<br />public void test1(){<br />throw new RuntimeException();<br />}<br />2)作用: throw 是一个命令,执行时抛出一个指定异常对象<br />3)携带数据: throw 后面携带【异常对象】,一个throw一次只能携带一个异常对象。<br />4)调用: 当一个方法内部存在throw命令时,在调用时可以不考虑异常捕捉问题。
<a name="fLVCB"></a>
### (面试)三、NoClassDefFoundError和ClassNotFoundException区别?
NoClassDefFoundError是一个Error类型的异常,是由JVM引起的,不应该尝试捕获这个异常。引起该异常的原因是JVM或ClassLoader尝试加载某类时在内存中找不到该类的定义,该动作发生在运行期间,即编译时该类存在,但是在运行时却找不到了,可能是编译后被删除了等原因导致。
ClassNotFoundException是一个受检查异常,需要显式地使用try-catch对其进行捕获和处理,或在方法签名中用throws关键字进行声明。当使用Class.forName, ClassLoader.loadClass或<br />ClassLoader.findSystemClass动态加载类到内存的时候,通过传入的类路径参数没有找到该类,就会抛出该异常;另一种抛出该异常的可能原因是某个类已经由一个类加载器加载至内存中,另一个加载器又尝试去加载它。
<a name="wqER0"></a>
### (面试)四、java中常见的异常
- java.lang.illegalAccessError:违法访问错误。当一个应用试图访问、修改某个类的域((Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。
- . java.lang.InstantiationError:实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常.
- java.lang.OutOfMemoryError:内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。
- java.lang.StackOverflowError:堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出或者陷入死循环时抛出该错误。
- . java.lang.ClassCastException:类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。
- java.lang.ClassNotFoundException:找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
- . java.lang.ArithmeticException:算术条件异常。譬如:整数除零等。
- java.lang.ArraylndexOutOfBoundsException:数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
- java.lang.IndexOutOfBoundsException:索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常。
- java.lang.InstantiationException:实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。
- . java.lang.NoSuchFieldException:属性不存在异常。当访问某个类的不存在的属性时抛出该异常。
- java.lang.NoSuchMethodException:方法不存在异常。当访问某个类的不存在的方法时抛出该异常。
- java.lang.NullPointerException:空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。
- java.lang.NumberFormatException:数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。
- java.lang.StringIndexOutOfBoundsException:字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。
<a name="XXgKV"></a>
### (面试)五、Throwable 类常用方法
- **public String getMessage()**:返回异常发生时的简要描述
- **public String toString()**:返回异常发生时的详细信息
- **public String getLocalizedMessage()**:返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
- **public void printStackTrace()**:在控制台上打印 Throwable 对象封装的异常信息
<a name="Tjz28"></a>
### (面试)六、try-catch-finally
- **try块:** 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
- **catch块:** 用于处理 try 捕获到的异常。
- **finally 块:** 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。
**在以下 3 种特殊情况下,finally 块不会被执行:**
1. 在 try 或 finally块中用了 System.exit(int)退出程序。但是,如果 System.exit(int) 在异常语句之后,finally 还是会被执行
2. 程序所在的线程死亡。
3. 关闭 CPU。
解释:finally语句<br />代码示例1
```java
public static int getInt( {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
}catch (ArithmeticException e) {
a = 30;
return a;
/*
*return a在程序执行到这一步的时候,这里不是return a而是return 30;这个返回路
径就形成了
*但是呢,它发现后面还有fina1ly,所以继续执行fina71y的内容,a=40
再次回到以前的路径,继续走return 30,形成返回路径之后,这里的a就不是a变量了,而是
常量30
*/
}fina1ly {
a = 40;
}
return a;
}
//执行结果:30
代码示例2
public static int getInt( {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
}catch (ArithmeticException e) {
a = 30;
return a;
}fina1ly {
a = 40;
//如果这样,就又重新形成了一条返回路径,由于只能通过一个return返回,所以这里直接返回40
return a;
}
//执行结果:40
七、自定义异常
/*
1、SUN提供的JDK内置的异常肯定是不够的用的。在实际的开发中,有很多业务,
这些业务出现异常之后,JDK中都是没有的。和业务挂钩的。那么异常类我们
程序员可以自己定义吗?
可以。
2、Java中怎么自定义异常呢?
两步:
第一步:编写一个类继承Exception或者RuntimeException.
第二步:提供两个构造方法,一个无参数的,一个带有String参数的。
死记硬背。
*/
public class MyException extends Exception{//编译时异常
public MyException(){
}
public MyException(String s){
super(s);
}
}
/*
public class MyException extends RuntimeException{ //运行时异常
}
*/
四、泛型
(面试)1. Java中的泛型是什么 ? 使用泛型的好处是什么?
它提供了编译期的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException。
一个被举了无数次的例子:
List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
Log.d("泛型测试","item = " + item);
}
毫无疑问,程序的运行结果会以崩溃结束:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
我们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题。
List<String> arrayList = new ArrayList<String>();
...
//arrayList.add(100); 在编译阶段,编译器就会报错
(面试)2. Java的泛型是如何工作的 ? 什么是类型擦除 ?
泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。
public class Main{
pub1ic static void main(string[] args) {
ArrayList<Integer> arrayList1 = new ArrayList<>();
ArrayList<String> arrayList2 = new ArrayList<>();
System.out.println(arrayList1.getclass() == arrayList2.getc1ass());
}
}
输出结果:
true
可以看到ArrayList
public class Main{
public static void main(String[] args) throws Exception{
ArrayList<Integer> arrayList =new ArrayList<>();
arrayList.add(1);
arrayList.getClass().getMethod("add",Object.class).invoke(arrayList,"a")
System.out.prinltn(arrayList.get(0));
System.out.prinltn(arrayList.get(1));
输出
1 a
可以看到通过反射进行add操作,ArrayList
- 既然存在类型擦除,那么Java是如何保证在ArrayList
添加字符串会报错呢?Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。 (面试)3. 什么是泛型中的限定通配符和非限定通配符 ?
限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T> 上限通配符 适合读 另一种是<? super T> 下限通配符 适合插
非限定通配符可以用任何类型来限定。
上限通配符:必修是T或者T的子类 下限通配符:必须是T或者T的父类。