Java理论知识和编程问题总结
2012年08月15日

1、class.getResource 和 class.getClassLoader().getResource的区别

它们的区别在于路径上的区别:

Class.getResource(String path)

    path不以 '/' 开头时,默认是从此类所在的包下取资源;

    path 以 '/' 开头时,则是从ClassPath根下获取;

Class.getClassLoader().getResource(String path)

    path不能以 '/' 开头;(否则什么都取不到)

    path是从ClassPath根下获取;

由此可知:

class.getResource("/") == class.getClassLoader().getResource("")


2、关于IO流Buffered和flush的理解

以write()方法为例:

因为CPU的运算速度远快于IO的写操作,所以,在不使用Buffered的情况下,

每执行一个write方法,都要等前一个write方法的IO写操作完成后,才能继续,效率很低。

用了Buffered,则只管往内存里面写数据,到达一定数量后,再等待其完成。


而flush方法的作用是,无论有没有使用Buffered,在正常情况下,显然

数据都是有条不紊的输出,但是如果在内存中的数据还没有写入IO时io流就被关闭,

那么在半路上(内存中)的数据,就没来得及输出到IO中,所以可能会造成输出错误。

解决方法就是,在io关闭之前,调用flush方法,强制把内存中的数据写入IO。


另外,在有Buffered的情况下,如果缓冲区没满,它是不会输出到IO的,所以最后一次

write方法,必须flush,否则,很可能因为缓冲区没满而丢失一些内容。


那为什么很多时候,我们在输出的中途不断的调用flush呢?

其实我觉得没必要,频繁调用flush的人可能是对缓冲这个概念还不熟悉。



1、主(数据)类型,9种:boolean、byte(-128~127)、char(Unicode: 0~2^16-1)、short(-2^15~2^15-1)

、int(-2^31~2^31-1)、float(IEEE754)、long(-2^63~2^63-1)、double(IEEE754)、void


2、Java 1.1增加了两个类,用于进行高精度的计算:BigInteger和BigDecimal。

    尽管它们大致可以划分为“封装器”类型,但两者都没有对应的“主类型”。这两个类都有自己特殊的“方法”,对应于我们针对主类型执行的操作。也就是说,能对int或float做的事情,对BigInteger和BigDecimal一样可以做。只是必须使用方法调用,不能使用运算符。此外,由于牵涉更多,所以运算速度会慢一些。我们牺牲了速度,但换来了精度。

    BigInteger支持任意精度的整数。也就是说,我们可精确表示任意大小的整数值,同时在运算过程中不会丢失任何信息。

    BigDecimal支持任意精度的定点数字。例如,可用它进行精确的币值计算。至于调用这两个类时可选用的构建器和方法,请自行参考联机帮助文档。


3、一旦将变量作为类成员使用,就要特别注意由Java分配的默认值。……然而,这种保证却并不适用于“局部”变量——那些变量并非一个类的字段。所以,int x; 那么x会得到一些随机值(这与C和C++是一样的),不会自动初始化成零。


4、HashMap的迭代方法,使用迭代器 Iterator :

Iterator it = hs.keySet().iterator();
while(it.hasNext()){
    Object o = it.next();
    System.out.println(hs.get(o));
}


5、this调用构造函数

class Person{

	Person(){
	}
	
	Person(String name, int age){
		this.name = name;
		this.age = age;
	}
	
	Person(String name, int age, String address){
		this(name, age); //高级用法,调用上面那个构造函数,该句必须是本函数的第一句
		this.address = address;
	}
	
	String name;
	int age;
	String address;
}


2. 类的继承

//子类必须调用父类的构造函数,用super函数,super必须放在构造函数的第一句
class Student extends Person{
	int grade;
	Student(){
		//super();//可省略,编译器会默认加上,也可以用super传入参数
	}
	
	Student(int grade){
		this();//this()和super()的区别是super()是调用父类的构造函数
	}
}

补充:内部类

内部类可以任意的使用外部类的成员变量。但是它绝不是继承了外部类。

class A{
	int i;
	class B{
		int j;
		int funcB(){
			return i+j;
			//完整的写法为 return A.this.i+this.j;
		}
	}
}

编译时产生

A.class和A$B.class


在其他类中生成类A和类B:

A a = new A();

A.B b = new A().new B();

//或者 A.B b = a.new B();


最常用的其实是“匿名内部类”

常见实例:在一个类中,要调用一个接口。但是这个接口还没有任何实现类。

此时就可以在调用的地方(用一个匿名内部类)去实例化。匿名内部类一般都是作为参数使用的。在监听器和线程中经常用到。

例如

class B{
	void func(A a){
		a.doSomething(); //其中A是一个接口
	}
}

那么调用B的func时可以这么写:

B b = new B();
b.fun(
	new A(){
		//实现A接口中的抽象方法
		public void doSomething(){
			System.out.println("匿名内部类");
		}
	}
);

上面蓝色部分就是匿名内部类,可见,匿名内部类没有名字,它是一对中括号括起来的。它的实现方式为:new一个接口,紧跟着一对中括号,并在中括号中实现接口中的抽象方法。


补充:装饰者

工人接口、水管工、A公司的水管工(装饰者)

例如:

Plumber plumber = new Plumber(); //水管工
AWorder aWoker1 = new AWorder(plumber); // A公司的水管工,plumber转型成Worker

即,可以给水管工多加一层包装,使其包含A公司额外指定的功能。Plumber定义如下:

class Plumber implements Worker{
	public void doSomeWork(){
		System.out.println("修水管");
	}
}

AWorder定义如下:

class AWorker implements Worker{
	private Worker worker;
	public AWorker(Worker worker){
		this.woker = worker;
	}
	public void doSomeWork(){
		Sytem.out.println("你好"); //A公司额外添加的功能
		worker.doSomeWork(); //基础功能
	}
}


3. 成员函数的重载

//为避免重复写父类方法的代码,用super.父类成员函数名()的方式来调用父类的成员函数
void introduce(){
    super.introduce();
    System.out.println(" grade: " + grade);
}


 

4、集合、泛型

1) HashMap 和Hashtable 的区别?

HashMap 是Hashtable 的轻量级实现(非线程安全的实现),它们都完成了Map 接口,主要区别在于HashMap 允许空(null)键值(key)。由于非线程安全,效率上可能高于Hashtable。HashMap 允许将null 作为一个entry 的key 或者value,而Hashtable 不允许。HashMap 把Hashtable 的contains 方法去掉了,改成containsvalue 和containsKey。因为contains 方法容易让人引起误解。Hashtable 继承自Dictionary类,而HashMap 是Java1.2 引进的Map interface 的一个实现。 

最大的不同是,Hashtable 的方法是Synchronize 的,而 HashMap 不是,在多个线程访问 Hashtable时,不需要自己为它的方法实现同步,而HashMap就必须为之提供额外同步。

Hashtable 和HashMap 采用的hash/rehash 算法都大概一样,所以性能不会有很大的差异。


2) 说出ArrayList、Vector、LinkedList 的存储性能和特性?

ArrayList 和 Vector 都是使用数组方式存储数据,它们都允许直接按序号索引元素。但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。

由于Vector 使用了synchronized 方法(线程安全),通常性能上较ArrayList差。

LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度快索引速度慢。


9、线程、并发

1)创建线程的两种方法及区别?

    1. 继承Thread

    2. 实现Runnable接口。

Runnable除了具有Thread的功能外,还有如下一些好处:

    a) 多个线程可以操作相同的数据资源。

    b) 避免了Java单继承带来的局限。


2) 线程同步的方法?

    使用wait()、notify()、sleep()、allnotity()方法来使线程之间可以互相通信。

wait():等待状态,释放所持有的对象的lock,可以喝notify配套使用。

sleep():阻塞状态,不会释放对象锁,时间一到,即可进入可执行状态。

notify():唤醒一个处于等待状态的线程。但不能确切的指定唤醒哪一个,由JVM来决定,且不是按优先级。

allnotity():唤醒所有处于等待状态的线程,但是并不是给所有唤醒的线程一个对象的锁,而是让它们竞争,只有获得锁的那个线程才可以进入可执行状态。


关于yield()方法

yield()是指暂停当前正在执行的线程对象,并执行其他线程。暂停执行是指当前线程放弃所分得的 CPU 资源,并不是使线程处于阻塞状态,即线程仍处于可执行状态,随时可能再次分得CPU。调用yield()的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。


3) 线程的执行过程

New、start()、Runnable(就绪)、Blocked(阻塞)、Running(执行)、run()、Dead(死亡)


在一个Test中调用线程对象First,那么当Test中main运行时一共有三个线程:main线程、First线程和垃圾回收线程。

Test.java、First.java

First f = new First();
f.start();
for(int i=0;i<100;i++){
System.out.println("main-->"+i);
}
 
class Firt extends Thread{
public void run(){
for(int i=0;i<100;i++){
System.out.println("First-->"+i);
}
}
}

运行结果是 First-->i 和 main-->i 无序显示。


用Runnable实现线程

步骤:

  1. 生成一个Runnable接口实现类的对象

      RunnableImpl ri = new RunnableImpl();

  2. 生成一个Thread对象,并将Runnable接口实现类的对象作为参数传递给Thread对象。

      Thread t = new Thread(ri);

  3 . 执行Thread对象的start方法。

      t.start();


Thread.sleep(n)

不一定在n毫秒后能立即运行,因为它醒了之后要去争夺CPU,夺到CPU之后才能运行


Thread.yield()

自动的让出CPU,然后再去和其他线程一起去抢CPU,有可能还是它抢到CPU……


setPriority()

设置优先级,在start()之前设置:

t.setPriority(Thread.MAX_PRIORITY);

t.setPriority(Thread.MIN_PRIORITY);

最大优先级为10。最小优先级为1。


多线程数据安全、线程同步

例子:生成两个Thread对象,但是共用一个线程体(RunnableImpl),每一个线程都有名字。

class A implements Runnable {
   int i=100;
   public void run() {
      while(true) {
      //synchronized(this) {
        System.out.println(Thread.currentThread().getName()+"is running..."+i);
        i--;
        Thread.yield();
        if(i<0) {
          break;
        }
    ....
class ThreadTest {
    public static void main(String[] args) {
        A r = new A();
        Thread a = new Thread(r);
        Thread b = new Thread(r);
        a.setName("第a线程");
        b.setName("第b线程");
        a.start();
        b.start();
        System.out.println(Thread.currentThread.getName()+"is starting....");
    }
}

用Synchronized(this){...}同步代码块。锁住当前代码块,保证当前代码块只能被一个线程执行(但不一定能连续执行,只能确保其他线程无法使用该代码块,当其他线程试图执行该代码块时会一直等待,直到锁被释放)。

    

10、IO

IO流有三种分类方式

第一种分法:

  1. 输入流

  2. 输出流

第二种分法:

  1. 字节流

  2. 字符流

第三种分法:

  1. 节点流(原始数据)

  2. 处理流(再加工)


IO核心类

1. 字节流

 InputStream(抽象类): FileInputStream

 OutputStream(抽象类):FileOutputStream

2. 字符流和字符包装流

 Reader:FileReader、InputStreamReader、BufferedReader

 Writer:FileWriter、OutputStreamWriter、BufferedWriter


FileReader用法:[直接以字符的方式读文件]

直接用文件做参数:new FileReader("foo.in")


OutputStreamWriter用法:[以字节方式打开文件,用字符方式写数据,将字符流转换成字节流]

用OutputStream(和字符集charsetName)作参数,例如OutputStreamWriter( fos,"GBK" )


BufferedReader用法:

用FileReader()或者InputStreamReader做参数,例如BufferedReader(new FileReader("foo.in") );

new File("D://src","a.txt")

new File("D:\src","a.txt")

"//"等同于"\",前者是后者的转义符表达方式


System.out是FilterOutputStream定义的一个属性,字节流FilterOutputStream属于OutputStream的直接子类

PrintStream是FilterOutputStream的直接子类,但不会抛出IOException


如何创建一个InputStreamReader对象:

new InputStreamReader(new FileInputStream("data.txt"));

new InputStreamReader(System.in);



11、多态的实现方式:方法的重载和重写,涉及到继承和接口


12、重载和重写的区分?

   重写要求很严:重写的方法,它的参数列表、返回类型,都要和父类中的方法相同

   且它的 权限修饰符 级别要 大于等于 父类中的方法,例如父类是public,那么重写的时候就不能用private去修饰。而且重写的方法,抛出的异常要小于父类中的方法,例如父类没有抛出异常,则重写的方法就不能抛出异常。


13、静态内部类(Static Nested Class)和内部类(Inner Class)的区别?

    顾名思义,静态内部类为静态的,不需要外部类被初始化就可以直接被实例化。而一般内部类需要先new一个外部类才能实例化。


14、抽象类和接口的区别?

    接口中所有的方法都是抽象的,接口只能定义static final成员变量。


15、抽象(abstract)方法能否和static、native、synchronized一同使用?

    回答:都不可以。


16、异常

什么是异常?

    异常是指中断了正常指令流的事件。

    异常分为:

运行时异常(可以不进行异常捕获,运行时意外发生的,比如数组越界、除数为0)

非运行时异常(编译时异常,必须进行异常捕获)


常见类型有

Throwable -> 

    Error和Exception

Exception ->

    1. Uncheck Exception

          运行时异常:RuntimeException(ArithmeticException、IndexOutOfBoundsException)

    2. Ckeck Exception

         非运行时异常(编译时异常):IOException、ClassNotFoundException、SQLException


运行时异常的应用举例:

例如判断用户的年龄,如果年龄为负数,则终止程序。

1. 用非运行时异常(那么必须要进行异常捕捉)

try{
    if( age<0 ){
        throw new Exception("age must >= 0");
    }
}catch(Exception e){ }

        2. [推荐]用运行时异常(可以不进行捕捉,出错时直接中断程序)

if( age<0 ){
    throw new RuntimeException("age must >= 0");
}

       3. 用throws抛出异常,但是在调用的地方需要捕捉异常。一般在某个地方集中来捕获异常。


13. 断言

上面的异常处理的第二种方式:

if( age<0 ){
    throw new RuntimeException("age must >= 0");
}

可以改写成用断言来实现:

assert age >= 0:"age must >=0";

注意,它和if的判断条件是相反的,因为它是"假设age>=0",当假设为false时才打印后面那句。

断言的两种表达式:

1)assert expression1; 

2)assert expression1: expression2;

其实1)是2)的简单版本。


编译执行时要用

编译:javac -source 1.6 Test.java   ---其中,1.6指java的版本.

执行:java -ea Test