`
clq9761
  • 浏览: 587802 次
  • 性别: Icon_minigender_1
  • 来自: 福建
社区版块
存档分类
最新评论

JAVA基础

 
阅读更多

一、JAVA基础

 

1.类的构建过程是从基类“向外”扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化。

 

2.继承技术的优点之一,就是它支持增量开发模式,你可以引入新代码而不会在现有代码中引发Bug,可以
 将新的Bug隔离在新的代码之中。


3.向上转型是指把某个对象的引用视为对其基类型引用的作法,判断该用组合还是继承的方法就是问一问自己
  是否需要从新类向基类进行向上转型。如果必须向上转型,则继承是必要的,否则应优先使用组合,
  因为组合更具灵活性。
向下转型也就是在继承层次中向下移动-应该能够获取类型信息,在运行期间对类型进行检查的行为称作运行时
   类型识别(RTTI)。


4.final字段,对于基本类型,使数值恒定不变,对于对象引用,使引用恒定不变,无法把它改为指向另一个对象。
   final方法,第一个原因是防止继承类修改它的含义。第二个原因是效率,同意编译器将针对该方法的所有调用
        都转为内嵌调用。
  final类表明不允许继承该类。


5.类的代码在初次使用时才加载,这通常是指直到类的第一个对象被构建时才发生加载,但是当访问static字段
或static方法时,也会发生加载。
一般的加载顺序为:基类静态成员—>派生类静态成员—>基类构造方法—>派生类成员—>派生类构造方法。


6.面象对象的三种基本特征:封装,多态和继承。封装通过合并特征和行为来创建新的数据类型,实现隐藏则
通过将细节私有化把接口和实现分离开来。多态通过分离做什么和怎么做,从另一角度将接口和实现分离开来。


7.多态允许我们不管导出类的存在,编写的代码只是与基类打交道,是一项让程序员将改变的事物与未变的事物
分离开来的重要技术。


8.Interface(接口)关键字产生一个完全抽象的类,没有提供任何具体实现,可看作纯粹的抽象类。
Java只能从一个非接口的类继承,其余的基元素都必须是接口。
使用接口的核心原因:一是为了能够向上转型为多个基类型,
二是与使用抽象基类相同,防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口。
接口中的任何字段都自动是static 和 final的,是一种很便捷的用来创建常量组的工具。
接口的所有成员自动被设置为public的。


9.内部类就像是一种简单的代码隐藏机制。它允许把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性。
在内部类所在的类的方法里面使用内部类的时候,与使用其它类没什么不同。如果想从外部类的非静态方法之外的
任意位置创建某个内部类的对象,必须具体地指明这个对象的类型:OuterClassName.InnerClassName。
内部类拥有其外围类的所有元素的访问权。


10.接口可以嵌套在类或其它接口中。当实现某个接口时,并不需要实现嵌套在其内部的任何接口,
而且private接口不能在定义它的类之外被实现。


11.匿名内部类,如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求
    其参数引用是final.

例:public class Parcel{
    public Destination dest(final String dest){
            return new Destination(){
               private int cost;
              {  cost =100;//此处在{}内进行实例初始化等同于创建构造器效果 }
                 private String label = dest;
                 public String readLable(){
                       return label;
                }
               ….
            }
     }
 }

 

12.局部内部类:与匿名内部类具有相同的行为和能力,使用局部内部类而不使用匿名内部类的唯一理由是,
     需要不止一个该内部类的对象。

public class LocalInnerClass{
   Counter getCounter(final String name){
       class LocalCounter implements Counter{
              //…
     }
        return new LocalCounter();
    }
}

 

 

13.嵌套类:如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static.

例:public  interface IInterface{
      static class Inner{
           //省略相关属性,方法
    }
}

 

    正常情况下,接口内部不放置任何代码,但嵌套类可作为接口的一部分。一般可使用嵌套类来放置测试代码。


14.使用内部类的原因:
   1.每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,
      对于内部类都没有影响。
   2.如果拥有的是抽象的类或具体的类,而不是接口,那就只能使用内部类才能实现多重继承。

 

15.异常:是指引发阻止当前方法或作用域继续执行的问题。
当抛出异常后,首先,同Java中其它对象的创建一样,将使用new在堆上创建异常对象。然后,当前的执行路径被终止,
并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找异常处理程序来继续执行程序,
它的任务是将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续执行下去。


16.所有的标准异常都有两个构造器:一个是缺省构造器,另一个是接受字符串作为参数。
JDK提供的异常体系不可能预见所有的希望加以报告的错误,可自定义异常来表示程序中可能会遇到的特定问题。
对异常来说,最重要的部分就是类名。异常对象中仅有的信息就是异常类型,除此之外不包含任何有意义的内容。


17.Throwable是异常类型的根类。有三种基本的异常类提供了带cause参数的构造器。
它们是Error(用于Java虚拟机报告系统错误)、Exception(可以被抛出的基本类型)
以及RuntimeException(运行时异常,自动被Java虚拟机抛出,通常不用捕获).
如果RuntimeException没有被捕获,那么程序退出时将调用异常的pringStackTrace()方法。


18.异常限制,当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。
异常限制对构造器不起作用,派生类的构造器可以抛出任何异常,而不必理会基类构造器所抛出的异常,
但必须包含基类构造器的异常说明,且不能捕获基类构造器抛出的异常。


19.异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常”,
     处理的重要目标是把错误处理的代码同错误发生的地点相分离。可以把“被检查的异常”转换为“不检常的异常”,
  例:

try{
     …
}catch(Exception e){
    throw new RuntimeException(e);
}

 
20.类型检查:运行时类型识别(RTTI),当只有一个指向对象基类的引用时,RTTI机制可以让你找出这个
     对象的确切类型。

例:Object[] shapeList={new Circle(),new Square()};
     Shape shape =  (Shape) shapeList[0];
     Circle circle  =  ( Circle) shapeList[0];

 
运行时识别对象和类信息主要有两种方式:一种是传统的“RTTI”,
另一种是反射机制,Java附带的库Java.lang.reflect包含了Field、Method、以及Constructor类
(每个类都实现Member接口),这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。
RTTI和反射之间的区别在于,对于RTTI来说,编译器在编译时打开和检查.class文件,
而对于反射机制来说,.class文件在编译时是不可获取的,是在运行时打开和检查.class文件。
Instanceof关键字也是RTTI的一种形式。


21.面向对象编程中基本的目的是:让代码只操纵对基类的引用,这样,如果要添加一个新类来扩展程序,
就不会影响到原来的代码。
应始终使用多态机制,只在必需的时候使用RTTI,如果基类来自一个库,或者由别人控制,
这时候RTTI便是一种解决之道,可继承一个新类,然后添加自己需要的方法,在代码的其它地方,
可以检查你自己特定的类型,并调用自己的方法。


22. Class对象:每个类都有一个Class对象,编译时产生,在运行时,当我们想生成这个类的对象时,
Java虚拟机(JVM)首先检查这个类的Class对象是否已经加载,如果尚未加载,JVM就会根据类名查找.class文件,
并将其载入。所以Java程序并不是一开始执行就被完全加载的。
(1).Class.forName(“className”),取得Class对象的引用的一种方法。
(2).Class.newInstance()使用所选的Class对象生成该类的新实例。
(3).类字面常量:
 例:Class[] petTypes={Pet.class,Dog.class,Cat.class};
       等价于Class[] petTypes={Class.forName(“c10.Pet”),
       Class.forName(“c10.Dog”),
        Class.forName(“c10.Cat”),};
(4).Class.isInstance(),是一种动态地调用instanceof运算符的途径。
与instanceof关键字生成的结果完全一样,保持了类型的概念。
而equals()和==比较运算符是比较实际的Class对象,没有考虑继承。
(5).Class.getInterfaces(),返回某个Class对象的包含的接口对象的数组。
(6).Class.getSuperClass(),获得某个Class对象的直接基类。


23. 数组与其它种类的容器之间的区别有三方面:效率、类型和保存基本类型的能力。在Java中,
数组是一种效率最高的存储和随机访问对象引用序列的方式。
数组只能保存特定类型(可以保存基本类型,容器则不能),对象数组和基本类型数组在使用上几乎是相同的,
唯一的区别就是对象数组保存的是引用,基本类型的数组直接保存基本类型的值。
数组的初始化:
(1).Weeble[] b =new Weeble[5];//默认初始化空值
(2).Weeble[] d = {new Weeble(),new Weeble(),new Weeble()};//数组的聚集初始化
(3).Weeble[] a = new Weeble[]{new Weeble(),new Weeble()};//动态的聚集初始化

 

24.Arrays类(java.util包中)的基本方法:
(1).equals()用于比较两个数组是否相等。
(2).fill()用于以某个值填充整个数组。
(3).sort()用于对数组排序;
(4).binarySearch()用于在已经排序的数组中查找元素。
(5).asList()接受任意的数组为参数,并将其转变为List容器。

 

25.容器
1.Collection,一组独立的元素,通常这些元素都服从某种规则。
List:必须保持元素特定的顺序。
   (1) ArrayList:允许对元素进行快速随机访问,但是向List中插入与移除元素的速度很慢。
   (2)LinkedList:对顺序访问进行了优化,向List中插入与移除的开销并不大,随机访问则相对较慢。
      可使用LinkedList来制作栈和队列。
Set: 不能有重复元素,加入Set的Object必须定义equals()方法以确保对象的唯一性。
   (1) HashSet:为快速查找而设计的Set,存入HashSet的对象必须定义hashCode().
   (2) TreeSet:保持次序的Set,底层为树结构。
2.Map,一组成对的“键值对”对象,就像一个小型数据库。
  (1) HashMap:Map基于散列表的实现。
  (2) TreeMap:基于红黑树的实现,查看“键”或“键值对”时,它们会被排序。
如果添加元素的顺序很重要,应该使用LinkedHashSet或者LinkedHashMap.

 

26. 使用Set的时候必段为类定义equals(),而使用哈希散列表时,必须为类定义hashCode(),
     作为一种编程风格,当覆盖equals()的时候,就应该同时覆盖hashCode().
     正确的equals()方法必须下列5个条件:
    (1).自反性。x.equals(x)一定返回true.
    (2).对称性。如果y.equals(x)返回true,则x.equals(y)也返回true.
    (3).传递性。如果x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)一定返回true.
    (4).一致性。无论调用x.equals(y)多少次,返回的结果应该保持一致。
    (5).对任何不是null的x,x.equals(null)一定返回false;
  正确的hashCode()计算的指导:
(1).给int变量result赋予某个非零值常量。
(2).为对象内每个有意义的字段f(即每个可以做equals()操作的字段)计算出一个int散列码c.
(3).合并计算得到的散列码:resutl = 37*result+c;
(4).返回result.
(5).检查hashCode()最后生成的结果,确保相同的对象有相同的散列码。
 设计hashCode()时最重要的因素就是无论何时,对同一个对象调用hashCode()都应该生成同样的值。
 要想使hashCode()实用,它必须速度快,并且必须有意义,必须基于对象的内容生成散列表。


27. 迭代器(Iterator),容器的方法iterator()要求容器返回一个Iterator。迭代器是一个对象,
它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层的结构。
如果原本使用ArrayList,后来考虑容器特点,想换用Set,迭代器的概念可以用于达成此目的。

 

二、线程并发

 

 

1、线程:对象技术提供了一种把程序划分成若干独立部分的方式。通常还需要把程序转换成彼此分离的、
 能独立运行的子任务,每一个这样的独立子任务都被称为一个“线程”。


2、进程:是一个自包含的运行程序,它有自己的地址空间,线程是进程内部的一个控制序列流。
 因此一个进程内可以具有多个并发执行的线程。


3、线程的基本操作
写一个线程最简单的做法是从java.lang.Thread继承,并实现run()方法,或者实现Runnable接口
的run()方法(解决java不能支持多重继承),run()里的代码就能够与程序里的其它线程“同时”执行。
(1)start():为线程执行特殊的初始化动作,然后调用run()方法。
(2)run(): 线程执行的具体动作,事实上总会有某种形式的循环,使得线程一直运行下去直到不再需要,
 所以要设定跳出循环的条件,或直接从run()返回。
(3)yield(),线程让步,通过yield()方法来作出让步,让别的线程使用CPU,这只是一个暗示,
 没有任何机制保证它将会被采纳。
(4)sleep(),线程休眠,使线程停止执行一段时间,该时间由给定的毫秒数决定。在调用sleep()方法的时候,
 必须把它放在try块中,因为sleep()方法在休眠时间到期之前有可能被中断。
 如果想使用interrupt()来中断一个挂起的线程,那么挂起的时候最好使用wait()而不是sleep(),
 这样就不太可能在catch子句里结束了。
(5)interrupt(),中断线程。
(6)setPriority(),设置线程的优先级jdk有10个优先级别,当调整优先级的时候,
 一般使用MAX_PRIORITY、NORM_PRIORITY和MIN_PRIORITY。
(7)setDaemon(),后台线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并
 不属于程序中不可或缺的部分。setDaemon(true)将线程设置为后台线程。
 后台线程创建的任何线程将被自动设置成后台线程。
(8)join(),加入到某个线程,一个线程可以在其他线程之上调用join()方法,其效果是等待一段时间直到
 第二个线程结束才继续执行。
 例如某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复。


4、线程共享受限资源,在多线程环境中,两个或多个线程同时使用同一个受限资源,必须防止这种资源访问的
 冲突,否则可能发生两个线程同时试图访问同一个银行账号、或向同一个打印机打印,改变同一个值等
 诸如此类的问题。要防止这类冲突,只要在线程使用资源的时候给它加一把锁就行了。考虑简单的“信号量”,
 它可以看成是在两个线程之间进行通信的标志对象。如果信号量的值是零,则它监控的资源是可用的,
 如果这个值是非零的,则被监控的资源不可用,线程必须等待。


5、Java提供关键字synchronized为防止资源冲突提供了内置支持。
 当线程要执行被synchronized关键字保护的代码片断的时候,它将检查信号量是否存在,
 然后获取信号量,执行代码,释放信号量。
 共享资源一般是以对象形式存在的内存片断,但也可以是文件、输入/输出端口,或者是打印机,要控制对
 共享资源的访问,得先把它包装进一个对象,然后把所有要访问这个资源的方法标记为synchronized。
 当在对象上调用其任意synchronized方法的时候,此对象都被加锁,这时该对象上的其他synchronized
 方法只有等到前一个方法调用完毕并释放了锁之后才能被调用。
 对于某个特定对象来说,其所有synchronized方法共享同一个锁,这能防止多个线程同时访问被编码为
 公用的内存。因此每个访问关键共享资源的方法必须全部是synchronized的,否则就会出错。

 

6、原子操作,即不能被线程调度机制中断的操作;一旦操作开始,那么它一定可以在可能发生的“上下文切换”
之前执行完毕。如果变量类型是除long或double以外的基本类型,对这种变量进行简单的赋值或者返回值
操作的时候,都是原子操作。不包括long和double的原因是因为它们比其他基本类型要大,
只要给long和doule加上volatile关键字,操作就是原子的了。原子操作不需要进行同步。

 

7、临界区,只是希望防止多个线程同时访问方法内部的部分代码而不是防止访问整个方法,
通过这种方法分离出来的代码段被称为“临界区”,它也使用synchronized关键字建立。

例:synchronized(syncObject){
}

 

通常最合理的对象就是方法调用所应用的当前对象synchronized(this)。


8、线程状态
(1)新建(new),线程对象已经建立,但还没有启动,所以它还不能运行。
(2)就绪(Runnable):在这种状态下,只要调度程序把时间片分配给线程,线程就可以运行。
(3)死亡(Dead):线程死亡的通常方式是从run()方法返回。
(4)阻塞(Blocked):线程能够运行,但有某个条件阻止它的运行。


9、线程协作,关键是通过线程之间的“握手机制”来进行的,这种握手可以通过Ojbect的方法wait()
和notify()来安全地实现 。
调用sleep()的时候锁并没有被释放,调用wait()方法释放了锁,这就意味着在调用wait()期间,
可以调用线程对象中的其它同步控制方法。
当一个线程在方法里遇到了对wait()的调用的时候,线程的执行被挂起,对象上的锁被释放。
Wait()、notify()、以及notifyAll()是基类Object的一部分,不属于Thread的一部分,
调用这些方法前必须”拥有”(获取)对象的锁。


10、死锁,线程之间相互等待的连续循环,没有哪个线程能够继续。当以下四个条件同时满足时,就会发生死锁:
(1)互斥条件。线程使用的资源中至少有一个是不能共享的。
(2)至少有一个进程它必须持有一个资源且正在等待获取一个当前被别的进程持有的资源。
(3)资源不能被进程抢占。
(4)必须循环等待。
要发生死锁的话,所有这些条件必须全部满足;所以要防止死锁的话,只需破坏其中一个即可。

 

三、对象的传递与返回


1、将一个引用传入某个方法之后,它仍然指向原来的对象。当你“传递”对象的时候,实际上是在传递对象的引用。

 

2、别名效应,指多个引用指向同一个对象,当某人修改那个对象时,别名带来的问题就会显现出来,
 如果还有其它的引用指向此对象,而使用那些引用的人,根本没想到对象会有变化,定然会对此感到十分诧异。

 

3、Java中对于基本类型而言是传值,而对于对象则是传引用。如果创建的方法会修改其参数,必须清楚地向用户
 说明它的使用方式,以及潜在危险。


4、浅层拷贝,只复制对象“表面”的部分。实际对象由以下几部分组成:
 对象的“表面”,加上由对象包含的引用指向的所有对象,再加上这些对象又指向的那些对象,通常称之为“对象网”。
 深层拷贝则是将对象网的对象全部复制,必须控制所有类的代码,或者至少对深层拷贝涉及的类要足够了解,
   才能知道它们是否正确执行了各自的深层拷贝。


 5、Object.clone()用来拷贝对象,它能够计算出对象大小,为新对象创建足够的内存空间,
 并把旧对象的所有比特位复制到新对象,称为“逐位复制”。如果希望一个类可以被克隆,需做到以下几点:
(1)实现Cloneable接口。
(2)覆盖clone()。
(3)在你的clone()中调用super.clone();
  (4) 在你的clone()中捕获异常。


6、通过序列化进行深层拷贝,如果将对象序列化之后再将其反序列化,那么其效果相当于克隆对象。
示例代码如下:

Person[] a =new Person[10];
for(int i=0;i<a.length;i++)
a[i]=new Person();
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream o = new ObjectOutputStream(buf);
for(int i=0;i<a.length;i++)
o.writeOjbect(a[i]);// 序列化写入
ObjectInputStream in = new ObjectInputStream(
  new ByteArrayInputStream(buf.toByteArray()));
Person[] b =new Person[10];
for(int i=0;i<b.length;i++)
b[i]=(Person)in.readObject();//反序列化读取

 

序列化的耗时操作大约为克隆的4倍。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics