面试宝典第四章Java基础知识

4.1——基本概念

4.1.2——java和C/C++有什么异同

1.java为解释型语言
其运行过程为程序源代码经过Java编译器编译生成字节码,然后由JVM解释运行,而C/C++为编译型语言,源代码进过编译后生成可执行的二进制代码,因此java的执行速度比C/C++慢,但是java可以跨平台执行,C/C++不行
2.java为纯面向对象语言
所有的代码(包括函数,变量等)必须在类中实现,除基本类型外,所有的类型都是类。此外,java语言中不存在全局变量或者全局方法,而C/C++兼具面向对象和面向过程编程的特点,可以定义全局变量和全局函数
3.与C++语言相比,java语言中没有指针的概念
这样有效防止类C/C++语言中操作指针可能引发的系统问题,从而使程序变得更安全
4.与C/C++语言相比,java语言不支持多继承
但是java语言引入的接口这一概念,可以同时实现多个接口,由于接口也具有多态特性,因此在java语言中可以实现多个接口来实现C++语言中多重继承的功能
5.在C++语言中,需要开发人员去管理对内存的分配(包括申请和释放)
而java语言提供了垃圾回收器来实现垃圾的自动回收,不需要程序的显式的管理内存的分配,在C++语言中,通常会把释放资源的代码放到析构函数中,java语言中虽然没有析构函数,但是却引入了一个finalize()方法,当垃圾回收器级那个要释放无用对象的内存的时候,会首先调用该对象的finalize()方法,因此研发人员不需要关心也不需要知道对象所占的内存空间何时会被释放

4.1.3——main方法

1.main方法可以被final和synchronized修饰,但是不可以被abstract修饰
2.同一个文件中可以有多个main()方法,每个类都可以有自己的mian方法,但是只有与文件名同名的那个main方法才会作为整个程序的入口

4.1.5——java程序的初始化

1.类的加载先于类的初始化,类的加载是父类先于子类的,如果main()方法在单独的一个类中,那这个类先被加载

4.1.7——一个java文件能否定义多个类

1.一个java文件可以定义多个类,但是最多只能有一个类被public修饰,并且这个类的类名必须与文件名相同,如果这个文件中没有public的类,那么文件名是随便一个类的名字即可

4.1.9——为什么java中有些接口没有任何方法

1.在java语言中,有些接口内部没有声明任何的方法,也就是说,实现这些接口的类不需要重写任何的方法,这些没有任何方法声明的接口又被叫做标识接口,标识接口仅仅充当一个标识作用,用来表明实现它的类属于一个特定的类型,比如java类库中已经存在的标识接口有Cloneable和Serializable等
Clonealbe:此类实现了 Cloneable 接口,以指示 Object.clone() 方法可以合法地对该类实例进行按字段复制。
Serializable:类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。

4.2——面向对象技术

4.2.1——面向对象和面向过程有什么区别

1.出发点不同
面向对象方法是用符合常规思维的方式来处理客观问题,强调把问题域的要领直接映射到对象与对象之间的接口上,而面向过程方法强调的是过程的抽象化和模块化,它是以过程为中心构造或处理客观世界问题的
2.层次逻辑问题
面向对象方法是用计算机逻辑来模拟客观世界中的物理存在,以对象的集合类作为处理问题的基本单位,尽可能的使计算机世界向客观世界靠拢,使得问题的处理更加清晰直接,面向对象方法是使用类的层次结构来实现类之间的继承和发展
而面向过程方法处理问题的基本单位是能清晰准确表达问题的模块,用模块的层次结构概括模块与模块之间的关系与功能,把客观世界的问题抽象成计算机可以处理的问题
3.数据处理的方式与控制程序不同,面向对象方法将数据与对应的代码封装成一个整体,原则上其它对象不能直接修改其数据,即对象的修改只能由其本身的成员函数来完成,控制程序方式上是通过”事件驱动”来激活和运行程序,而面向过程方法是直接通过程序来处理数据,处理完毕之后即可显示处理结果,在控制程序的方法上是按照设计调用或返回程序,不能自由的导航,各模块之间存在控制与被控制,调用与被调用的关系。

4.2.5——组合和继承有什么区别

组合和继承是面向对象中两种代码复用的方式。
组合是值在新类中创建原有类的对象,来重复利用原有类的功能(实际上我们在做题过程中遇到的代码大部分都是这种形式),而继承是面向对象的特征之一,组合是显示的,而继承是隐式的。组合和继承存在对应关系,组合中的整体类对应继承中的子类,组合中的局部类对应继承中的父类。
Car,Vehicle,Tire
Car和Vehicle是继承关系(用is a来理解),而Car和Tire是组合关系(用has a来理解)
class Vehicle{}
class Car extends Vehicle{}

class Tire{}
class Car{
private Tire t = new Tire();
}
在实际使用中遵循两个原则
1.除非两个类是is a的关系,否则不要轻易的使用继承,不要为了单纯的代码重用而使用继承,因为过多的使用继承会降低代码的可维护性,比如当父类被修改的时候,会影响到所有继承他的子类,从而增加程序维护的难度和成本
2.不要仅仅为了实现多态而是用继承,如果类之间没有”is a”的关系,可以通过组合和接口的方式来达到相同的目的。在java语言中,能够使用组合就不要使用继承。

4.2.8——抽象类和接口有什么异同?

1.除去内部的属性,方法这些问题,要记住一个老是被我忽略的问题,就是抽象类的类名一定要用abstract来修饰(public ,和默认都可以作为访问修饰符),这一定和抽象类内部类似,而接口的接口名也是可以使用(public和默认作为访问修饰符),这和接口内部全部被public隐藏修饰有所不同。
2.抽象类和接口表示的两种抽象机制的不同之处到底在哪里?
抽象类表达的是一个实体
而接口表达的是一个概念

4.2.10——如何获取父类的类名

1.java语言提供了获取类名的办法:getClass().getName(),开发人员可以调用这个方法来获取类名,代码如下
public class Test{
Public void test(){
system.out.println(this.getClass().getName());
}
Public static void main(String[] args){
new Test().test();
}
}
输出结果:Test
2.那么是否可以通过调用父类的getClass().getName方法来获取父类的类名呢?
Class A{}
public class B extends A{
public void test(){
System.out.println(super.getClass().getName());
}
public static void main(String[] args){
new B().test();
}
}
输出结果:B
为什么输出结果不是A而是B呢?主要原因在于java语言中任何类都继承自Object类,getClass()方法在Object类中被定义为final和native,子类不能覆盖该方法,因此this.getClass()和super.getClass()最终都调用的是Object类中的getClass()方法,而Object类中的getClass()方法的返回是:返回此Object的运行时类。所以还是输出B。
3.那么如何可以在子类中得到父类的名字呢?
可以通过java的反射机制,使用getClass().getSuperClass().getName();
class A{}
class B extends A{
public void test(){
System.out.println(this.getClass().getSuperClass().getName());
}
Public static void main(String[] args){
New A().test();
}
}
输出:A
4.Object类下的getClass()
public final Class

4.3——关键字

4.3.2——break,continue以及return有什么区别

1.break用于强行跳出当前循环,不再执行剩余代码,但是如果是多层循环嵌套,并且break语句出现在嵌套循环的内层的时候,它仅仅只是中止了内循环的执行,而不影响外循环

可以使用带标识的break语句来跳出多重循环
先在多重循环外面定义个标识,然后再循环体里面使用带有标识的break语句

public class Test{
    public static void main(String[] args){
    out:
    for(int i = 0; i < 5; i++){
        for(int j = 0 ; j < 5; j++){
            if(j>1){
                break out;
            }
            System.out.println(j);
        }
    }
    System.out.println("break");
    }
}
//输出结果
//0
//1
//break

2.continue用于停止当前循环,不再执行下面的语句,但不是跳出整个循环,简单来说,continue只是中断一次循环的执行。
3.return语句是一个跳转语句,用来表示从一个方法返回,可以使程序回到调用该方法的地方。当执行main方法的时候,return语句可以是程序执行返回到java运行系统。

4.3.3——final,finally和finalize有什么区别

1.final
1)final用于声明属性,方法,和类;分别表示属性不可变,方法不可重写,类不可以继承
2)final修饰属性时,指的是引用不可变,对于基本类型的数据,很好理解,对于一个对象来说,指的是对象的地址值不可变,而对象的内容是可以变化的
比如:
public static void main(String[] args){
final StringBuffer s = new StringBuffer(“Hello”);
s.append(“world”);//编译不报错
s = new StringBuffer(“HelloWorld”);//编译报错
}
3)final修饰的变量必须被初始化
定义成员变量是,普通的变量可以不用给出赋值,比如:int num;因为这时候会给出一个默认值0,并且在之后可以赋值多次(要注意的是多次赋值是指在构造代码块区或者构造方法内,而不是在成员变量区,成员变量区只能对成员变量定义(并赋值),不能进行其他任何操作),但是如果这个变量被final修饰,那么必须对这个变量进显性行初始化,并且只能初始化(赋值)一次,有这么几种初始化规则。
A.在定义的时候初始化
final int num = 10;
B.final 成员变量可以在初始化块中初始化(构造代码块),但是不可以再静态代码块中初始化(原因是静态代码块先加载,这时候还没这个变量)
C.静态final成员变量可以在静态初始化块(静态代码块),但不可以在初始化块中初始化(类似道理)
D.在类的构造器中初始化,但是静态final成员变量不可以在类的构造方法中初始化。
4)final修饰方法,表示该方法不允许任何子类重写这个方法,但是子类是可可以继承这个方法的
5)final修饰类,此类不能被继承,所有的方法都不能被重写,但是这不表示final类的成员变量也是不可修改的,要想做到final类的成员变量不可改变,必须给成员变量加final修饰,值得注意的是:一个类不可以即声明为abstract又声明为final
6)final修饰一个参数的时候,表示这个参数在方法的内部不可以被修改

package 随手打的测试类;

class Test1{
    int a;
    {
        a = 10;
    }
    static{
        //a = 10;报错无法在静态初始化块中初始化非静态的成员变量
        int a = 10;//代码块中定义的变量,会消失在内存中,不对成员变量起作用
    }
    Test1(){
        a = 20;//可以赋值多次,根据初始化顺序确定最后值
    }

}
class Test2{
    static int a;
    {
        a = 15;
        final int a = 10; //代码块中定义的变量,会消失在内存中,不对成员变量起作用
    }
    static {
        a = 10;
    }
    Test2(){
        a = 20;
    }
}
class Test3{
    final int a;
    {
        a = 20;
    }
    static {
        //a = 20;报错无法在静态初始化块中初始化非静态的成员变量
    }
    Test3(){
        //a = 10;可以在构造器重赋值,但是final修饰的成员变量只能被赋值一次所以还是报错
    }
}
class Test4{
    static final int a;
    {
        //a = 10;
        int a = 10;//加上int之后,表示的是这个代码块中初始化了一个变量a,代码块结束后就成了垃圾
        //static int b =10;同时在代码块中,只有final和默认修饰符可以修饰代码块内部的初始化变量
    }
    static {
        a = 20;
        int a = 10;//这里同理,不会对成员变量a产生影响,代码块结束后消失在内存中
    }
    Test4(){
        //a = 10;无法在构造器中初始化static final修饰的成员变量
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Test1 t1 = new Test1();
        Test2 t2 = new Test2();
        Test3 t3 = new Test3();
        Test4 t4 = new Test4();
        System.out.println(t1.a);//输出结果 20
        System.out.println(t2.a);//输出结果 20
        System.out.println(t3.a);//输出结果 20
        System.out.println(t4.a);//输出结果 20
    }
}

7)在这里对成员变量相关做一个总结
A.成员变量区除了对成员变量进行定义(并赋初始值之外),什么都不能做,将成员变量的定义和赋值分开也不行

class{
    int x;
    //x = 20;报错
}

B.无论是在初始化块,静态初始化块,构造器中定义的和成员变量同名的变量(注意代码块中定义的变量只能用默认或者final修饰),其编译不报错,但是不会对成员变量产生影响,代码块结束之后就消失在内存中
C.默认修饰的成员变量,可以在初始化块和构造器中初始化,不能在静态初始化块中初始化,可以赋值多次,也可以不赋值,会给出默认值
D.static修饰的成员变量,可以在初始化块,静态初始化块和构造器重初始化,可以赋值多次
E.final修饰的成员变量,可以在初始化块和构造器中初始化,不能在静态初始化块中初始化,和默认的成员变量不同的地方在于,只能赋值一次,且必须被赋值
F.static final修饰的成员变量,只能在静态代码块中初始化,不能再静态初始化块和构造器重初始化,(如果只是static和final结合,那么应该在这个三处都可以初始化,但是实际不是如此,由于static修饰的成员变量可以通过类名调用,没有final修饰的时候,即使没有被赋值也会给出初始值,但是被final修饰后,必须立刻赋值,由于此时是没有对象的,所以不能再构造代码块和构造器中赋值,同理到只被static修饰,其实在静态区赋值和在构造区赋值虽然都可以初始化,但其实是不一样的)且只能赋值一次,并且必须被赋值

package 随手打的测试类;
class A{
    static int x;
    static {
    }
    A(){
        x = 30;
    }
}
public class Test {
    public static void main(String[] args) {
        System.out.println(A.x);//输出0
        System.out.println(new A().x);//输出30

    }
}

2.finally
finally作为异常处理的一部分,只能用于try/catch语句中,并且附带一个语句块,表示这个语句块最终一定会被执行,经常用在需要释放资源的情况下。异常处理中,可以没有finally。并且finally也不是一定会被执行,有两种情况下是不会被执行的
1)程序在进入try块之前就出现异常
2)在try块中强制退出,也不会去执行finally块中的代码(System.exit(0))

3.finalize
finalize()是Object类的一个方法,在垃圾回收器执行的时候会调用被回收对象的finalize()方法,可以覆盖此方法来实现对其他资源的回收,需要注意的是,一旦垃圾回收器准备好释放对象占用的空间,将首先调用其fanalize()方法,并再下一次垃圾回收动作发生时,才会真正去回收对象占用的内存

4.3.5——static关键字有哪些作用

1.static关键字主要有两种作用:第一,为某种数据类型或对象分配单一的存储空间,与创建对象的个数无关。第二,实现某个方法或属性与类而不是与对象关联在一起,也就是说在不创建对象的情况下就就可以通过类来调用方法或使用类的属性。具体而言在java语言中,static主要有4种使用情况
2.static成员变量
静态变量属于类,在内存中只存在一个复制(所有实例都指向同一内存地址),只要了类被加载,这个静态变量就会被分配空间,就可以使用了。静态变量的使用有两种方式:类.静态变量和对象.静态变量。
需要注意的一点是:在java语言中,不能在方法体中定义static变量(与类相关,在方法中无法加载)
3.static成员方法
同样的,static成员方法的用例和static成员方法类似,同时需要注意的是,static方法中不能使用this和super关键字,不能直接调用非static方法(但是可以通过创建对象的方式简洁调用),因为当static方法被调用的时候,这个类的对象可能没有创建,即使已经创建了,也无法确定调用的是哪个对象的方法。同理static方法也不能访问非static类型的变量。
4.用static修饰的成员方法和成员变量本质上是全局的,但是如果在static变量前用private修饰,则表示这个变量或者方法只能在本类中使用,其他类不能通过类名来调用
5.static代码块
静态代码块也是在类加载时被执行,如果有多个静态代码块,jvm将会按顺序执行,static代码块经常用来初始化静态变量。需要注意的是,静态代码块只会执行一次
6.static内部类
static内部类,它可以不依赖于外部类实例对象而被实例化,通常的内部类需要在外部类实例化之后才能实例化。静态内部类不能与外部类有相同的名字,不能访问外部类的普通成员,只能访问外部类中的静态成员和静态方法(包括私有类型),需要注意的是,只有内部类才能被定义为static
public class Outer{
static int num = 10;
static class Inner{
void method(){
System.out.println(num);
}
}
public static void main(String[] args){
Outer.Inner oi = new Outer.Inner();
oi.method()
}
}
输出10

4.5——字符串与数组

4.5.1—字符串创建和储存的机制是什么?

1.直接赋值实例化字符串:String s1 = abc;String s2 = abc

对于String s1 = abc;String s2 = abc;在jvm中存在一个字符串池,其中保存很多String对象,并且可以共享使用,当创建一个字符串常量时,会首先在字符串常量池中查找是否已有相同的字符串被定义,如果已经定义,则直接获取对其的引用,不需要新创建对象;若没有定义,则首先创建这个对象,然后把它加入到字符串常量池中,再将它的引用返回。

2.构造方法实例化字符串:String s3 = new String(“abc”);String s4 = new String(“abc”)

对于String s1 = new String(“abc”);String s2 = new String(“abc”),由于new总会产生新的对象,所以两个引用对象s1,s2所指向的地址空间肯定是不同的。
为了便于理解,这种实例化的过程可以分成两个部分
第一部分:
新建对象的过程,即new String(“abc”);这一个过程会调用String类的构造函数,调用这个构造函数时,传入了一个字符串常量,因此new String(“abc”)也就等价于”abc”和”new String()”两个操作了。若字符串常量池中不存在”abc”,则会创建一个字符串常量”abc”,并将其添加到字符串常量池中;若存在,则不创建,然后new String()会在堆内存中创建一个新的对象,所以s3和s4指向的是堆中不同的String对象,地址值当然不同。
第二部分:
赋值过程,只是定义了一个名为s的String类型变量,将一个String类型的对象的引用赋值给s,这个过程不会产生新的对象
这里写图片描述

需要注意的是,可以通过手工入池的方式使引用直接指向字符串常量池

代码1

public class TestString {
    public static void main(String args[]){
     String str =new String("Lance").intern();//对匿名对象"hello"进行手工入池操作
     String str1="Lance";
     System.out.println(str==str1);//true
    }
}

3.字符串的相加问题

1.

String s = "a"+"b"+"c"+"d"`

编译器在编译阶段直接编译成abcd,显然abcd会进入字符串常量池
2.

String s1 = a;
String s2 = b;
String s = s1+s2;

String s1 = a;
String s2 = s1 +"b";

String类型的相加实际上是先创建一个StringBuffer对象,然后通过append方法来完成字符串拼接,然后再将StringBuffer类型转为String类型,这个新对象储存在堆中。
3.代码测试结果

String s1 = "a";
String s2 = S1 + "b";
String s2 = "a" + "b";
syso(s2 == "ab");//false
syso(s3 == "ab");//true

4.5.2—“==”,equals和hashCode有什么区别?

1.”==”

“==” 运算符是用来比较两个变量的值是不是相等
1)基本数据类型
如果这个变量是基本的数据类型,就是直接比较这两个基本数据类型的值是不是不是相等
2)引用数据类型(对象)
如果变量指的是对象(引用数据类型),那么涉及了两块内存,对象本省占用一块内存(堆内存),变量也占用一块内存,这块内存中存储的数值就是对象占用的那块内存的首地址。
所以如果要看两个变量是否指向同一个对象,就可以使用”==”进行比较,如果要比较这两个对象的内容是否相同,那么”==”是无法实现的。
这里写图片描述

2.equals()

1)Object类中的equals()
equals()方式是Object类中提供的方法之一,每一个java类都继承自Object类,所以没有对象都具有equals(),Object类中的equals方法是直接使用”==”运算符比较两个对象,完全等价于”==”.
2)被重写的equals()
相比较于”==”,equals()的特殊性就在于它可以被重写,所以在某些重写过equals()的类中,equals()是用来比较两个对象的内容是否相同,如String类(需要注意的是StringBuffer和StringBuild类没有重写equals())。

3.hashCode()

1)Object类中的hashCode()
Object类中的hashCode()是用来比较两个对象是否相等的,方法返回对象在内存中的地址转换成的一个int值,所以如果没有重写hashCode(),那么任何独享的hashCode()返回的值都不会相等
2)equals()和hashCode()
虽然equals()也可以用来比较两个对象是否相等,但是和hashCode()还是有区别。一般来说,equals()是给用户调用,如果用户想要比较两个对象的内容是否相等,那么重写equals()方法即可。而hashCode()一般用户不会去调用,相当于文件中的md5
3)Object.hashCode通用规定
a.在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
b. 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
c. 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。
所以一般来说在重写equal()的同时也要重写hashCode(),两者间的关系是
equals()相等,那么hashCode()也必须相等
equals()不相等,那么hashCode()可能相等也可能不等
hashCode()不相等,那么equals()也不相等
hashCode()相等,那么equals()可能相等也可能不等

4.5.3—String,StringBuffer,StringBuilder和StringTokenizer的区别?

1.String和SringBuffer

区别a:修改字符串
String是不可变类,也就是说,String对象一旦被创建,其值将不能被改变而StringBuffer是可变类,在对象创建之后仍然可以对其值进行修改,因此适合在需要被共享的场合使用,而当一个字符串经常需要被修改时,最好用StringBuffer来实现。
String保存的字符串在修改时,其原理是首先创建一个StringBuffer,然后调用StringBuffer的append()方法,最后调用StringBuffer的toString()方法把结果返回
代码1

String s = "hello";
s += "world";

StringBuffer sb = new StringBuffer(s);
s.append("world");
s = sb.toString();

由上面的代码我们可以看出,String类型保存的字符串在修改时创建了很多的无用对象,会影响程序的运行效率。
区别b:实例化
String在实例化时,既可以通过构造方法实例化,也可以通过直接赋值的方法进行实例化,而StringBuffer只能通过构造方法进行实例化。

2.StringBuilder和StringBuffer

StringBuilder和StringBuffer类似,但是StringBuilder不是线程安全的,而StringBuffer是线程安全的。
执行效率上StringBulider > StringBuffer > String

3.StringTokenizer是用来分割字符串的工具类

代码2

import java.util.StringTokenizer;
public class Test{
    public static void main(String[] args){
        StringTokenizer st = new StringTokenizer("welcome to our country");
        while(st.hasMortTokens){
            System.out.println(st.nextToken());
        }
    }
}
/*
程序的运行结果为
welcome
to
our
country
*/

4.5.4—Java中数组是不是对象?

数组是指具有相同类型的数据的集合,它们一般具有固定的长度,并且在内存中占据连续的空间。在C/C++语言中,数组名只是一个指针,这个指针指向了数组的首元素,既没有属性也没有方法可以调用,而在java语言中,数组不仅有自己的属性(例如length属性),也有一些方法可以被调用(例如clone方法)。由于对象的特点是封装了一些数据,同时提供了一些属性和方法,从这个角度讲,数组是对象。每个数字类型都有其对应的类型,可以通过instanceof(关键字)来判断数据的类型。

4.5.5—数组的初始化方式有哪几种?

1.一维数组

1)声明方式
一维数组有两种声明方式type arrayName[]或者 type[] arrayName(推荐使用第二种),其中,type既可以是基本的数据类型,也可以是类。
2)初始化值
数字被创建后会根据数组存放的数据类型初始化成对应初始值

int[] arr   0
char[] ch  口 //(是一个这个形状的字符)
String[] st     null

3)两种初始化方式
a.动态建立了一个包含5个整型值的数组,默认初始化为0

int[] arr = new int [5];

当然在使用时候,可以将声明和初始化分开写

int[] arr; 
arr = new int [5];

b.声明一个数组类型变量并初始化

int[] arr = new int [] {1,2,3,4,5};
//(这种方式可以缩写成int[] arr = {1,2,3,4,5};)

同样的在使用时可以将声明和初始化分开写

int[] arr; 
arr = new int [] {1,2,3,4,5};

(注意声明和初始化分开,就不可以缩写了,就是不能int[] arr; arr ={1,2,3,4,5};)
这里特别注意一下:{1,2,3,4,5}这种形式只能用于初始化中,后面的赋值一律要用new int[] {1,2,3,4,5}

2.二维数组

1)声明方式
二维数组有三种声明方式type arrayName[][];type[][] arrayName和type[] arrayName[]。需要注意的是,在声明二维数组时,其中[]必须为空
2)初始化方式
a.通过初始化列表的方式来初始化

type arrayName[][] = {{c11,c12,c13..},{c21,c22,c23..},{c31,c32,c33...}...};

b.通过new关键字来给数组申请储存空间

type arrayName[][] = new type[行数][列数];

c.与C/C++语言不同的是,java语言中,二维数组的第二维长度可以不同,其定义方式如下
int[][] arr = {{1,3},(1,2,3)};
int[][] arr = new int [2][];arr[0] = new int[] {1,3};arr[1] = new int[] {1,2,3};

4.5.6—length属性与length()方法,size()方法

java语言中,length()方法是针对字符串而言的,String类提供了字符串方法来计算字符串长度。
length属性是java中数组的长度属性
size()方法是针对泛型集合而言的,查看泛型集合中有多少个元素

4.6——异常处理

4.6.1—try,catch,finally的执行机制

1.catch块和finally块至少要有一个

2.关于try,catch,finally块中有return的返回情况

1)只是try,catch块中有return的情况
我们知道try块在返回之前,会将返回值存储在一个指定位置,然后再去执行finally,等执行完finally块,才会将返回值返回。

代码1

public class Test{
    public static int testFinally(){
        try{
            return 1;
        }catch(Exception e){
            return 0;
        }finally{
            System.out.println("execute finally");
        }
    }
    public static void main(String[] args){
    int result = testFinally();
    System.out.println(result);
    }
}

/*
程序的运行结果为
exacute finally
1
*/

2)try-finally块或者catch-finally块中都有return的情况
这种情况下finally中的return会覆盖别处的return语句,最终返回到调用者那里的是finally中的return。

代码2

public class Test{
    public static int testFinally(){
        try{
            return 1;
        }catch(Exception e){
            return 0;
        }finally{
            System.out.println("execute finally");
            return 3;
        }
    }
    public static void main(String[] args){
    int result = testFinally();
    System.out.println(result);
    }
}

/*
程序的运行结果为:
execute finally
3
*/

需要注意的是,由于finally块的作用是为了保证无论出了什么样的情况,finally块中的代码一定要被执行,所以,finally块中的return是不能放在code前面的,再这样会报错
即不能出现
finally{
return 0;
syso{};
}

3.返回值是引用类型的情况

由try,catch,finally机制我们知道,如果finally中没有return语句,那么返回值在进入finally块之前就已经是确定的了,那么无论finally块中如何修改这个值,返回值也不会改变。但是同样的也要考虑返回值和返回引用的区别,比如是一个引用类型,那么finally块中修改会影响返回的结果。

代码3

public class Test{
    public static int testFinally(){
        int result = 1;
        try{
            result = 2;
            return result;
        }catch(Exception e){
            return 0;
        }finally{
            result = 3;
            System.out.println("execute finally1");
        }
    }
    public static StringBuffer testFinally2(){
        StringBuffer s = new StringBuffer("Hello");
        try{
            return s;
        }catch(Exception e){
            return null;
        }finally{
            s.append("world");
            System.out.println("excute finally2");
        }
    }
    public static void main(String[] args){
        int resultVal = testFinally();
        System.out.println(resultVal);
        StringBuffer resultRef = testFinally2();
        System.out.println(resultRef);
    }
}
/*
程序运行结果为:
execute finally1
2
excute finally2
Helloworld

*/

4.finally块中的代码是不是一定会执行

在java语言的异常处理中,finally块的作用就是为了保证无论发生什么情况,finally块里的代码总是会被执行。那有没有什么情况是finally块不一定会执行的情况呢?有两种情况
1)当程序进入try语句块之前就出现异常时,会直接结束,不会执行finally块中的代码

代码1

int i = 5/0;
try{}....

程序在int i = 5/0时会抛出异常,导致没有执行try块,因此finally块也没有执行
2)当程序在try块中强制退出时也不会去执行finally块中的代码,

代码2

Try{
System.out.println(“try block”);
System.exit(0);
}

程序在try块中通过调用System.exit(0)强制退出了程序,因此导致finally块中的代码没有被执行

4.6.2——异常处理的原理是什么?

1.首先java把异常当作对象来处理,并且定义了一个基类Throwable(可抛出的)作为所有异常的父类,这个基类有两个子类:Error(错误)和Exception(异常)两个大类。异常这个概念是指程序运行时(非编译时)所发生的非正常的情况或者错误时,jvm就会将出现的错误表示为一个异常并抛出
2.Error表示程序在运行期间出现了严重的错误,并且这种错误是不可以恢复的,由于这属于jvm层次的错误,因此这种错误会导致程序终止执行。此外编译器不会检查Error是否被处理,因此在程序中不推荐去捕获Error类的异常,主要原因是Error类的异常多是由于逻辑错误导致的,属于应该解决的错误,也就是说,一个正确的程序是不应该存在Error的。比如OutOfMemoryError,ThreadDeath等都属于错误,当这些异常发生的时候,jvm一般选择将线程中止。
3.Exception表示可恢复的异常,是编译器可以捕捉到的,它又包含两种:checked exception(检查异常)和runtime exception(运行时异常),注意这里不是类的名称,而是子类的特征。
1)checked exception是程序中最常见的异常,所有继承自Exception并且不是运行时异常的异常都是checked exception,比如常见的IO异常和SQL异常。这种异常都发生在编译阶段,java编译器强制程序去捕获此类型的异常,即将可能会出现这些异常的代码放入try块中,把对异常处理的代码放入catch块中,这种异常一般发生在如下几种情况
A.异常的发生并不会导致程序出错,进行处理后可以继续执行后续的操作,例如,当连接数据库失败后,可以重新连接后进行后续操作
B.程序依赖不可靠的外部条件
2)runtime exception运行时异常不同于checked exception检查异常,编译器没有强制对其进行捕获并处理,如果不对这种异常进行处理,当出现这种异常的时候,会由jvm来处理。在java语言中,最常见的异常包括
NullPointerException(空指针异常)
ClassCastException(类型转换异常)
ArrayIndexOfBoundsExceprion(数组越界异常)
ArrayStoreException(数组存储异常)
BufferOverflowException(缓冲区溢出异常)
ArithmeticException(算术异常)
出现运行时异常的时候,系统会把异常一直往上层抛出,知道遇到处理代码为止,若没有处理块,就抛到最上层,如果是多线程就用Thread.run()方法抛出,如果是单线程就用main()方法抛出。抛出之后,如果是线程,那么这个线程也就退出了,如果是主程序抛出的异常,那么整个程序也就中止了,所以如果不对运行时的异常进行处理,后果很严重,一旦发生问题,要么是线程中止,要么是主程序中止。
3)java异常处理中用到了多态的概念,如果在异常处理中,先捕获了基类,再捕获子类,那么捕获子类的代码永远不会被执行,因此,在进行一场捕获时,正确的写法是先捕获子类,在捕获基类的异常信息
如:
Try{
//
}catch(SQLException e1){
}catch(Exception)
4)尽早抛出异常,同时对捕获的异常进行处理,或者从故障中恢复,或者让程序继续执行,对捕获的异常不进行任何处理是一个非常不好的习惯,这样将非常不利于调试,但是也不是抛出的异常越多越好,对于有些异常,如运行时异常,实际上根本不必处理
5)可以根据实际的需求定义异常类,这些自定义的异常类自要继承自Exception类即可
异常能处理就处理,不能处理就抛出,对于一般异常,如果不能进行行之有效的处理,最好转换为运行时异常抛出,对于最终没有处理的异常,jvm会进行处理。

4.10——多线程

4.10.1——什么是线程,它与进程有什么区别?为什么要使用多线程

1.线程是指程序在执行过程中,能够执行程序代码的一个执行单元。在java语言中,线程有4个状态:运行,就绪,挂起,结束
2.进程是指一段正在执行的程序。而线程有的时候也被称为轻量级进程,它是程序执行的最小单元,一个进程可以拥有多个线程,各个线程之间共享程序的内存空间(代码段、数据段和堆空间)以及一些进程级的资源(例如打开的文件),但是各个线程拥有自己的栈空间,进程与线程的对比关系P143
3.在操作系统级别上,程序的执行都是以进程为单位的,而每个进程中通常都会有多个线程互不影响的并发执行,那么为什么要使用多线程呢?其实是因为线程的使用为研发带来了巨大的便利,具体有以下几个方面
A.使用多线程可以减少程序相应的时间(可以联想烧水,煮饭,学习)
B.与进程相比,线程的创建和切换开销更小,由于启动一个新的线程必须给这个线程分配独立的地址空间,建立许多数据结构来维护线程代码段,数据段等信息,而运行于同一进程的线程共享代码段,数据段,线程的启动或者切换的开销要比进程少很多,同时多线程在数据共享方面效率非常高
C.多CPU计算机本来就具有执行多线程的能力,如果使用单个线程,将无法重复利用计算机资源,造成巨大浪费
D.使用多线程可以简化程序的结构,使程序便于理解和维护,一个非常复杂的进程可以分成多个线程来执行

4.10.2——同步和异步有什么区别?

1.同步:当多个线程访问同一个资源时,会同时对共享数据进行操作,那么同步会通过锁来实现多线程的安全问题,线程A对同步代码区进行操作时,会获取锁,使得其他线程无法进入同步代码区,只有等待A释放锁,其他线程才有机会能获取锁进入同步代码区。
2.异步:每个线程都访问不同资源,不存在对共享数据进行操作的情况下,不必关心其他线程的行为,所以,当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望程序等待方法的返回的时候,就应该使用异步,异步可以提高程序执行的效率

4.10.3——如何实现java多线程,用内部类实现多线程

1.有三种方法
1).继承Thread类,重写run()方法
2).实现Runnable类,实现run()方法
3).实现Callable接口,重写call()方法
这部分知识在23.多线程中有详细介绍

2.用内部类来实现多线程(这个下午再写)

4.10.4——run()方法和start()方法有什么区别?

1.对于run()方法,内部封装了被线程执行的方法,直接调用只是当作普通方法来调用
2.对于start()方法,是启动线程的方法,调用start()方法会启动线程,然后会由JVM自动调用run()方法

4.10.5——多线程同步的实现方式有哪些?

1.synchronized关键字
1)synchronized块
2)synchronized方法
synchronizde方法的锁对象是this,即本类的一个实例对象
静态synchronized方法的锁对象是本类的字节码的对象

2.Lock(这个下午再写)

4.10.6——sleep()方法和wait()方法有什么区别?

sleep()和wait()都是使线程暂停执行的方法,区别在于
1.原理不同(苏醒方式)
sleep()方法是在Thread类中的静态方法,是线程用来控制自身流程的,等待时间一到,会自动苏醒进入就绪状态。
wait()方法是Object类中的方法,用于线程之间的通信,线程调用这个方法后,会进入等待,直到其他线程调用notify()或者notifyAll()方法才会醒过来,进入就绪状态
2.对锁的处理不同
调用sleep()方法不会释放锁
调用wait()方法会释放线程锁占用的锁,这样在线程等待期间,其他线程可以进入同步代码块中对数据进行操作
3.使用区域不同
由于wait()方法的特殊意义,所以只能放在同步控制方法或者同步语句块中去使用,而sleep()方法可以在任何地方使用
4.最后就是sleep()方法必须捕获异常,sleep的过程中有可能被其他对象调用它的interrupt(),产生InterruptedException异常,而wait(),notify(),notifyall()都不需要捕获异常
5.引申:sleep()方法和yield()方法有什么区别?
1)sleep()方法给其他线程运行机会的时候不考虑线程的优先级,因此会给低优先级的线程运行的机会,而yield()方法只会给相同优先级或者更高优先级的线程机会
2)线程在执行完sleep()方法后会转入堵塞状态,所以,执行sleep()方法的线程在指定的时间内肯定不会被执行,而yield()方法只是使当前线程回到就绪状态,所以执行yield()方法的线程可能在进入到就绪状态后又马上执行。

4.10.7终止线程的方式有哪些?

1.stop()和suspend()方法,已过时
2.过时的方法虽然可以阻止线程,但是如果当线程处于非运行状态,就不能使用了,此时可以使用interrupt()方法来打破阻塞的情况,当interrupt()方法被调用的时候,会抛出InterruptedException异常,可以通过在run方法中捕获这个异常来让线程安全的退出
3.如果程序因为I/O异常而停滞,进入非运行状态,基本上要等待I/O完成才能离开这个状态,在这种情况下,无法使用interrupt()方法来使程序离开run方法。这就需要一个替代的方法,基本思路也是触发一个异常,而这个异常与所使用的I/O有关。

4.10.8——synchronized和Lock有什么异同?

1.用法不一样。
synchronized通过同步方法或者同步代码块框起需要同步的代码;而Lock需要显式的指定起始位置和终止位置。synchronized是托管给JVM执行的,而Lock的锁定是通过代码实现的。
2.性能不一样。
JDK5中增加了一个Lock接口的实现类ReentrantLock。他们的性能在不同的情况下有所不同:在资源竞争不是很激烈的情况下,synchronized的性能要优于ReentrantLock,但是在资源竞争很激烈的情况下,synchronized的性能会下降的非常快,而ReentrantLock的性能基本保持不变。
3.锁的机制不一样
synchronized获得锁和释放的方式都是在块结构中,并且是自动解锁,不会出现异常而导致没有被释放从而引发的死锁问题,而Lock则需要开发人员手动去在finally块中释放,否则会引起死锁问题
4.虽然synchronizd和Lock都可以用来实现多线程的同步,但是,最好不要同时使用这两种同步机制,因为ReetrantLock和synchronized所使用的机制不同,所以他们的运行是独立的,使用时互不受影响

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值