Java基础2(期末复习用)

Java基础2

主要内容来自华科-辜老师ppt

以下仅将个人觉得重要的内容摘取整理出来了,并补充了一点内容用以理解

一、对象和类

类(class)定义或封装同类对象共有的属性和方法,即将同类型对象共有的属性和行为抽象出来形成类的定义。

Java没有struct和union

构造函数

无返回类型,名字同类名,用于初始化对象。

只在new时被自动执行。

必须是实例方法(无static),可为公有、保护、私有和包级权限。

如果类未定义任何构造函数,编译器会自动提供一个不带参数的默认构造函数。

Java没有析构函数,但垃圾自动回收之前会自动调用finalize( ),可以覆盖定义该函数(但是finalize调用时机程序员无法控制)。

访问对象:通过对象引用访问。JVM维护每个对象的引用计数器,只要引用计数器为0,该对象会由JVM自动回收。

在实例方法中有个this引用,代表当前对象(引用当前对象:相当于指针),因此在实例方法里,可以用this引用访问当前对象成员。

包package

包是一组相关的类和接口的集合。将类和接口分装在不同的包中,可以避免重名类的冲突,更有效地管理众多的类和接口。因此package就是C++里的namespace。

package语句必须出现在.java文件第一行,前面不能有注释行也不能有空白行,该.java文件里定义的所有内容(类、接口、枚举)都属于package所定义的包里。如果.java文件第一行没有package语句,则该文件定义的所有内容位于default包(缺省名字空间),但不推荐。

不同.java文件里的内容都可以属于同一个包,只要它们第一条package语句的包名相同。

如果要使用其它包里标识符,有二个办法:

  • 完全限定名,例如要调用java.util包里的Arrays类的sort方法: java.util.Arrays.sort(list);
  • 在package语句后面,先引入要使用其它包里的标识符,再使用:
    import java.util.Arrays; //或者: import java.util.*;
    Arrays.sort(list);

按需类型导入(type import on demand):并非导入一个包里的所有类,只是按需导入
import java.util.*;

不是把包里的标识符都引入到当前.java文件,只是使包里名字都可见,使得我们要使用引入包里的名字时可以不用使用完全限定名,因此在当前.java文件里可以定义与引入包里同名的标识符但二义性只有当名字被使用时才被检测到。

分析下面的错误

package p1;
public class A {
}

package p2;
import p1.*;  //按需导入,没有马上把p1.A引入到当前域
//因此当前文件里可以定义A
public class A {
    public static void main(String[] args){
        A a1 = new A();     //这时A是p2.A
        System.out.println(a1 instanceof p2.A); //true
	//当前域已经定义了A,因此要想使用package p1里的A,(自己有了,肯定用自己的)
	//只能用完全限定名
        p1.A a2 = new p1.A();
    }
}
package p1;
public class A {
}

package p2;
public class A {
}

package p3;
//可以按需导入,没有马上把p1.A,p2.A引入到当前域
//因此下面二个import不会保错
import p1.*;
import p2.*;//(不是立马导入,这时两个A还没导入,没有冲突)
public class B {
//当名字被使用时二义性才被检测
    A a; //报错,Reference to A is a ambiguous, p1.A and p2.A match; (自己没有,去两个*里面找)
    p1.A a1; //这时只能用完全限定名
    p2.A a2;
}

package p3;
import p1.A;
import p2.A;  //报错,p1.A is already defined in a single type import
public class B {
}

@Override可以不加,但是使用@Override注解有如下好处:
1:可以当注释用,方便阅读;

2:编译器可以给你验证@Override下面的方法名是否是父类中所有的,如果没有则报错。例如,如果没写@Override,而下面的方法名又写错了,这时你的编译器是可以编译通过的,因为编译器以为这个方法是你的子类中自己增加的方法。

Java注解为 Java 代码提供元数据。注解可以指示编译器做些额外的动作,甚至可以自定义Java注解让编译器执行自定义的动作。Java提供了Annotation API让我们自定义注解。

常量

常量通常定义为public。

final修饰实例方法时,表示该方法不能被子类覆盖(Override) 。非final实例方法可以被子类覆盖(见继承)

final修饰静态方法时,表示该方法不能被子类隐藏(Hiding)。非final静态方法可以被子类隐藏。

构造函数不能为final的。

构造函数是不能被继承的,也就不能被重写,final修饰实例方法主要是为了避免函数被重写,所以不用final修饰。

方法重载:同一个类中、或者父类子类中的多个方法具有相同的名字,但这些方法具有不同的参数列表(不含返回类型,即无法以返回类型作为方法重载的区分标准)

方法覆盖和方法隐藏:发生在父类和子类之间,前提是继承。子类中定义的方法与父类中的方法具有相同的方法名字、相同的参数列表、相同的返回类型(也允许子类中方法的返回类型是父类中方法返回类型的子类)

构造函数不能用static修饰,静态函数无this引用。

静态方法内部只能访问类的静态成员 (因为实例成员必须有实例才存在,当通过类名调用静态方法时,可能该类还没有一个实例)。

静态方法没有多态性。

类访问控制符:public和包级(默认);类的成员访问控制符:private、protected、public和包级(默认)

注意:类只有public和包级

Java继承时无继承控制(见继承,即都是公有继承,和C++不同),故父类成员继承到派生类时访问权限保持不变(除了私有)。

也就是说,Java不会根据 类的访问控制 去限制 类成员的访问控制

成员访问控制符的作用:

private: 只能被当前类定义的函数访问。

包级:无修饰符的成员,只能被同一包中的类访问。

protected:子类、同一包中的类的函数可以访问。

public: 所有类的函数都可以访问。

注意子类实例只能访问自己对应父类实例的protected成员,不能访问其他父类实例的protected成员。

访问控制针对的是类型而不是对象级别(小心)

public class Foo{
   private boolean x; 
   public void m(){
      Foo foo = new Foo();
      //因为对象foo在Foo类内使用,所以可以访问私有成员x,并不是只能访问this.x
      boolean b = foo.x //ok
   }   
}

有些特殊场合,可能会防止用户创建类的实例,这可以通过将构造函数声明为私有的来实现。

this引用指向调用某个方法的当前对象。

必看:this进阶理解,this在继承中到底是什么?

二、继承和多态

继承、子类和父类

如果class C1 extends C2,则称C1为子类(subclass),C2为父类(superclass)。

子类继承了父类中可访问的数据和方法,子类也可添加新的数据和方法,

子类不继承父类的构造函数。

一个类只能有一个直接父类(Java不支持多重继承,因为Java的设计者认为没有必要)。

Java的继承都是公有继承,因此被继承的就是父类,继承的类就是子类。因此父类的成员如果被继承到子类,访问权限不变。

说明:任何类在设计时应考虑覆盖祖先类Object的如下函数:equals,clone,toString等

父类的私有属性在子类中不可见(即不能在子类里直接访问)

但可以通过所继承的get和set方法设置和访问。

初始化块

初始化块是Java类中可以出现的第四种成员(前三种包括属性、方法、构造函数),分为实例初始化块和静态初始化块。

实例初始化模块(instance initialization block,IIB)是一个用大括号括住的语句块,直接嵌套于类体中,不在方法内。

**它的作用就像把它放在了类中每个构造方法的最开始位置,**用于初始化对象。

实例初始化块先于构造函数执行

作用:如果多个构造方法共享一段代码,并且每个构造方法不会调用其他构造方法,那么可以把这段公共代码放在初始化模块中。

一个类可以有多个初始化模块,模块按照在类中出现的顺序执行。

实例初始化模块还有个作用是可以截获异常

public class A{
    //在实例初始化块里初始化数据成员可以截获异常
    private InputStream fs = null;
    {
      try{ fs = new FileInputStream(new File(C:\\1.txt”));}
      catch(Exception e){}
    } 	
    public A(){}   
}

实例初始化模块最重要的作用是当我们需要写一个内部匿名类时:匿名类不可能有构造函数,这时可以用实例初始化块来初始化数据成员。(这里同时复习匿名内部类)

interface ISay{ public abstract void sayHello(); }
public class InstanceInitializationBlockTest {
    public static void main(String[] args){
        ISay say = new ISay()
        {   //这里定义了一个实现了ISay接口的匿名类
            //final类型变量一般情况下必须马上初始化,一种例外是:final实例变量可以在构造函数里再初始化。
            //但是匿名类又不可能有构造函数,因此只能利用实例初始化块
            private final int j;  //为了演示实例初始化块的作用,这里特意没有初始化常量j
            {
            	j = 0;  //在实例初始化块里初始化j
        	}
        	@Override
        	public void sayHello() { System.out.println("Hello");}
    	};
    	say.sayHello();
	}
}

一个类可以有多个实例初始化块,对象被实例化时,模块按照在类中出现的顺序执行,构造函数最后运行。

静态初始化模块是由static修饰的初始化模块{},只能访问类的静态成员,并且在JVM的Class Loader将类装入内存时调用。(类的装入和类的实例化是两个不同步骤,首先是将类装入内存,然后再实例化类的对象)。

在类体里直接定义静态变量相当于静态初始化块

第一次使用类时装入类

  • 如果父类没装入则首先装入父类,这是个递归的过程,直到继承链上所有祖先类全部装入
  • 装入一个类时,类的静态数据成员和静态初始化模块按它们在类中出现的顺序执行

实例化类的对象

  • 首先构造父类对象,这是个递归过程,直到继承链上所有祖先类的对象构造好
  • 构造一个类的对象时,按在类中出现的顺序执行实例数据成员的初始化及实例初始化模块
  • 执行构造函数函数体

super关键字(关注)

利用super可以显式调用父类的构造函数

super(parametersopt)调用父类的的构造函数。

必须是子类构造函数的第1条且仅1条语句(先构造父类)

如果子类构造函数中没有显式地调用父类的构造函数,那么将自动调用父类不带参数的构造函数。(也就是说,如果没显式调用父类构造函数,编译器自动在第一行补上super() )

父类的构造函数在子类构造函数之前执行。

不能使用super.super.p()这样的super链

如果一个类自定义了构造函数(不管有无参数),编译器不会自动加上无参构造函数

如果一个类没定义任何构造函数,编译器会自动地加上无参构造函数。

编译器在为子类添加无参构造函数时,函数体里会用super( )默认调用父类的无参构造函数,如果找不到父类无参构造函数,则编译器为子类添加无参构造函数失败,编译报错。

如果一个类定义了带参数的构造函数,一定别忘了定义一个无参的构造函数,原因是:由于系统不会再自动加上无参构造函数,就造成该类没有无参构造函数

如果父类没有无参构造函数,那么子类构造函数里若调用父类无参构造函数就会编译出错。

覆盖和隐藏

如果子类重新定义了从父类中继承的实例方法,称为方法覆盖(method override)。

仅当父类方法在子类里是可访问的,该实例方法才能被子类覆盖,即父类私有实例方法不能被子类覆盖父类实例私有方法自动视为final的。(final修饰的方法不能被重写)

静态方法不能被覆盖,如果静态方法在子类中重新定义,那么父类方法将被隐藏。

覆盖特性:一旦父类中的实例方法被子类覆盖,同时用父类型的引用变量引用了子类对象,这时不能通过这个父类型引用变量去访问被覆盖的父类方法(即这时被覆盖的父类方法不可再被发现)。因为实例方法具有多态性(晚期绑定)

在子类函数中可以使用super调用被覆盖的父类方法。

隐藏特性:指父类的变量(实例变量、静态变量)和静态方法在子类被重新定义,但由于类的变量(实例和静态)和静态方法没有多态性因此通过父类型引用变量访问的一定是父类变量、静态方法(即被隐藏的可再发现)

也就是说只有实例方法才具有多态性,静态方法不具有,静态方法早期绑定。

实例方法看运行时类型,静态方法看编译时类型。

方法覆盖的哲学涵义:子对象当然可以修改父类的行为(生物进化除了遗传,还有变异)

覆盖时可以改变访问控制,即可以更改protected为public等,仍然是覆盖。

绑定

绑定:找到函数入口地址的过程

引用变量o有二个类型:声明类型A,实际运行时类型B

多态性属于晚期绑定,静态属于早期绑定,重写(覆盖)属于晚期绑定,隐藏、重载等属于早期绑定

(初学这部分很容易犯错,建议参考一些个人博客的其他java习题,由于篇幅,就不在这里放了)

Object中的方法

java.lang.Object类是所有类的祖先类。如果一个类在声明时没有指定父类,那么这个类的父类是Object类。
它提供方法如toString、equals、getClass、clone、finalize。前3个为公有,后2个为保护。getClass为final(用于泛型和反射机制,禁止覆盖)。

equals方法:用于测试两个对象是否相等。Object类的默认实现是比较两个对象引用是否引用同一个对象。(通常要重写覆盖)

toString方法:返回代表这个对象的字符串。Object类的默认实现是返回由类名、@和hashCode组成。

Object的toString方法提供的信息不是很有用。因此通常子类应该覆盖该方法,提供更有意义的信息

要实现一个类的clone方法,首先这个类需要实现Cloneable接口,否则会抛出CloneNotSupportedException异常。

还要公有覆盖clone方法,即Object类里clone方法是保护的,子类覆盖这个方法时应该提升为public。

方法里应实现深拷贝clone,Object的clone实现是浅拷贝(按成员赋值)。

如果对象的数据成员有引用类型,则对象的拷贝不能简单地按成员赋值;而是要保证引用类型的数据成员引用的是不同对象,但内容一样。因此需要覆盖Object的clone方法,方法的实现必须是深拷贝

但是如果对象的数据成员为String及8种基本值类型对应的包装类类型,则这样的数据成员采用浅拷贝(按成员赋值就没有问题)。

原因:这些类型的对象的内容是不可更改的。如果把o2.s重新赋值为o2.s=“World”,实际是对o1.s没有任何影响,因为o2.s引用了另外的对象,和o1.s引用的对象不一样了。

 public Object clone() throws CloneNotSupportedException {
    // A newObj = new A(); //new一个新对象,该方法不好:在有继承关系的情况下,不利于复用父类的clone方法
	A newObj = (A)super.clone(); //强烈建议这么做(不要重复造轮子,尽量去使用父类的clone)
	newObj.values = this.values.clone(); //数组的clone是深拷贝,如果去掉clone,则是浅拷贝
	return newObj;
    }

多态性、动态绑定、类型转换

Class Student extends Person{}
Person p = new Student();//OK 父类引用可直接指向子类对象
Student s = new Person();//error,子类引用不能直接赋给父类引用

多态:通过引用变量调用实例函数时,根据所引用的实际对象的类型,执行该类型的相应实例方法,从而表现出不同的行为称为多态。通过继承时覆盖父类的实例方法实现多态

多态实现的原理:在运行时根据引用变量指向对象的实际类型,重新计算调用方法的入口地址(晚期绑定)。

当调用实例方法时,由Java虚拟机动态地决定所调用的方法,称为动态绑定(dynamic binding)或者晚期绑定或者延迟绑定(lazy binding)或者多态。

程序还没运行,编译器无法知道p会指向什么对象,编译器在编译时只能根据变量p的声明类型(Person)来类型检查。

为了避免风险,最好用instanceof来做实例类型检查。

重载和多态的关系

重载发生在编译时(Compile time),编译时编译器根据实参比对重载方法的形参找到最合适的方法。

多态发生在运行(Run time)时,运行时JVM根据变量所引用的对象的真正类型来找到最合适的实例方法。

有的书上把重载叫做“编译时多态”,或者叫“早期绑定”(早期指编译时)。

多态是晚期绑定(晚期指运行时)

绑定是指找到函数的入口地址的过程。

对象的访问运算符.优先与类型转换运算符。

  ((Circle)object).getArea() //OK
  (Circle)object.getArea(); //错误

final可以修饰变量、方法、类

final修饰变量

  • final成员变量:常量,数据初始化后不能再修改。
  • final局部变量:常量,数据初始化后不能再修改。

final修饰方法(实例方法和静态静态):最终方法,实例方法不能被子类覆盖,静态方法不能被隐藏

  • Object类的getClass( )

final类:最终类,不能派生子类。

  • String, StringBuffer
  • Math

三、抽象类和接口

抽象类和抽象方法的声明必须加上abstract关键字。

Java可定义不含方法体的方法,其方法体由子类根据具体情况实现,这样的方法称为抽象方法(abstract method),包含抽象方法的类必须是抽象类(abstract class)。

  • 包含抽象方法的类必须是抽象类

  • 抽象类和抽象方法必须用abstract关键字修饰

  • 没有包含抽象方法的类也可以定义成抽象类

抽象方法:使用abstract定义的方法或者接口中定义的方法(接口中定义的方法自动是抽象的,可以省略abstract)。

抽象类不能被实例化。

接口里方法编译器自动加上public abstract来修饰。

只有实例方法可以声明为抽象方法。

抽象类不能被实例化,即不能用new关键字创建对象(即new 右边的类型不能是抽象类)。

但是抽象类可以作为变量声明类型、方法参数类型、方法返回类型

为什么?因为一个抽象类型引用变量可以指向具体子类的对象

接口是公共静态常量和公共抽象实例方法的集合接口是能力、规范、协议的反映

接口不是类:(1)不能定义构造函数;(2)接口之间可以多继承,类可implements多个接口。(3)和抽象类一样,不能new一个接口

接口中的所有数据字段隐含为public static final
接口体中的所有方法隐含为public abstract

接口不是类(Java支持单继承类),一个接口可以继承多个接口。

任何实现该接口的类,必须实现该接口继承的其他接口。

空接口称为标记接口(markup interface)
空接口有什么作用?唯一目的允许你用instanceof检查对象的类型:
if(obj instanceof Cloneable)…

包装类没有无参构造方法

每一个数值包装类都有相应类型常量MAX_VALUE和MIN_VALUE。

JDK1.5开始允许基本类型和包装类之间的自动转换。

  • 将基本类型的值转换为包装类对象,称为装箱(boxing)
  • 将包装类对象转换为基本类型的值,称为开箱(unboxing)

四、异常和IO

异常产生的原因

  • Java虚拟机同步检测到一个异常的执行条件,间接抛出异常,例如:

    • 表达式违反了正常的语义,例如整数除零。
    • 通过空引用访问实例变量或方法。
    • 访问数组超界。
    • 资源超出了某些限制,例如使用了过多的内存。
  • 显式地执行throw语句抛出异常

  • Java异常都必须继承Throwable的直接或间接子类。用户通过继承自定义异常。

  • Java的异常分为二大类:从Exception派生的是程序级错误,可由程序本身处理;从Error派生是系统级错误,

    程序可不用处理(也基本上处理不了,例如JVM内存空间不够)。

  • Exception的子类里,除了RuntimeException这个分支外,其他的都是必检异常(即:要么在函数里用catch子句捕获并处理,要么在所在函数加上异常声明,PPT第5页例子)。 RuntimeException的子类是非必检异常

非必检异常(Unchecked Exception)是运行时异常(RuntimeException)和错误(Error)类及它们的子类, 非必检异常在方法里可不捕获异常同时方法头可不声明异常,编译器不会报错。但该发生的异常还是要发生。

其它的异常称为必检异常(Checked Exception),编译器确保必检异常被捕获或声明(即要不在方法里捕获异常,要不在方法头声明异常)

捕获:方法可以通过try/catch语句来捕获异常

声明:方法可以在方法头使用throws子句声明可能抛出异常

方法可以抛出的异常

方法里调用throw语句直接抛出的任何异常

调用另一个方法时,由被调用方法间接抛出的异常

如果方法不捕获其中发生的必检异常,那么方法必须声明它可能抛出的这些异常

一个try块后面可以有多个catch块。每个catch块可以处理的异常类型由异常类型参数指定。异常参数类型必须是从Throwable派生的类。

当try块中的语句抛出异常对象时,运行时系统将调用第一个异常对象类型与参数类型匹配的catch子句。如果被抛出的异常对象可以被合法地赋值给catch子句的参数,那么系统就认为它是匹配的(和方法调用传参一样,子类异常对象匹配父类型异常参数类型)。

无论try块中是否发生异常,都会执行finally块中的代码。通常用于关闭文件或释放其它系统资源。

public static String read(String filePath){
    String s = null;	
    BufferedReader reader = null; //BufferedReader一次读文本文件一行
    try{
        StringBuffer buf = new StringBuffer();
        reader = new BufferedReader(new InputStreamReader(new FileInputStream(new 
					File(filePath))));	
        while( (s = reader.readLine()) != null){//readLine方法读取到文件末尾返回null	
            buf.append(s).append("\n"); 
        }
	 s = buf.toString().trim();
    } 
    catch (FileNotFoundException e) { e.printStackTrace();} 
    catch (IOException e) { e.printStackTrace()}
    finally {
        if(reader != null) {
	    try { reader.close()} //由于close也可能出错,也要异常处理
	    catch (IOException e) { e.printStackTrace();}
        }
    }
    return s;
}

方法read内部已经处理了所有可能发生的异常,因此方法收不不需要加throws声明,同时read方法的调用代码不需要try/catch。

非必检异常(Unchecked Exception)是运行时异常(RuntimeException)和错误(Error)类及它们的子类, 方法可以不捕获同时不声明非必检异常(注意只是编译器不检查了,但如果真的有异常该抛出还是会抛出)

无论何时,throw以后的语句都不会执行。

无论同层catch子句是否捕获、处理本层的异常(即使在catch块里抛出或转发异常),同层的finally总是都会执行。

自定义异常类必须继承Throwable或其子类。

自定义异常类通常继承Exception及其子类,因为Exception是程序可处理的类。

如果自定义异常类在父类的基础上增加了成员变量,通常需要覆盖toString函数。

自定义异常类通常不必定义clone:捕获和处理异常时通常只是引用异常对象而已。

import  java.lang.Exception;
public class ValueBeyondRangeException extends Exception{
    int value, range;
    public ValueBeyondRangeException(int v, int r){ value=v; range=r; }
    public toString( ){ 
        return value + ” beyonds “ + range;
    }
}
//使用例子
int v = 1000,range = 100try{ 
	if(v > range)
		throw new ValueBeyondRangeException (v,range);
}
catch(ValueBeyondRangeException e){ System.out.println(e.toString( )); }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值