java的一些基础知识点
-
容易忽略的基础知识
- inner class cannot access local variable 提示的深究。
- local variable 必须赋初值?一直不都说,Java会给变量赋初值么?譬如boolean初值为false,int初值为0等等。今天看到了《Java编程思想》,说到,Java只给类成员变量默认值。
- 每一个重载函数,都必须要有一个惟一的参数类型列表:参数顺序不同也行,但是一般不这么用。返回值不同不行,否则调用的是编译器无法知道你调用的到底是哪个程序。
- 关于默认构造器:默认构造器指的是无参构造函数。一个类,你没写构造函数,默认会给你建一个。但是一旦你重载了构造函数--无论是有参数的还是没参数的,编译器就不会给你生成默认构造器(没参数的)。
- 构造函数,只能在构造函数的第一行被调用,不存在其他方法。
- 关于初始化顺序:假设有个Dog的类。
- 当首次创建类型为Dog类型的对象的时候,或者Dog类的静态方法/静态域首次被访问时,Java解释器必须定位Dog.class文件的路径。
- 然后载入Dog.class(后面会学到,将创建一个Class的对象),所有静态初始化的动作(指声明时候赋值)和显式的静态初始化动作(指在static{}的代码块)都将被执行。因此,静态初始化只在Class对象首次加载的时候进行一次。
- 当用new Dog()创建对象(或Class.newInstance)的时候,首先将在堆上为Dog对象分配足够的存储空间。
- 这块存储区域都将被清零:这意味着Dog对象中的所有基本数据类型都设为默认值,而引用类型数据被设置为null。
- 执行所有出现于字段定义处的初始化的动作和显式的实例初始化动作(指{}代码块)
- 执行构造器。
- 可变参数----通过数组来实现的。 在SE5之前,通过显式数组做参数来达到可变参数。 在SE5以后,可以通过Object… 来达到,避免显式传数组。
- 静态导入。static import 只导入某个类的类成员或类方法。
- java注释文档。
- javadoc用于提取注释的工具。默认javadoc只能为public和protected的成员进行文档注释。但可以通过-private来解除这个约束。javadoc命令只能出现在’/**和’*/’之间。主要有两种方式:嵌入html或使用“文档标签”。
- “文档标签”又分为“行内文档标签”和”独立文档标签“. 独立文档标签,必须@开头,放在行首。行内文档标签,在花括号内,也以@开头。
- swich语句.
在SE5之前,case只支持int和char那样的整数值,在SE5之后,开始支持enum. - foreach支持数组和容器。
-
枚举类型
- enum不同于C语言里的enum。
- 创建enum时候,编译器为你生成一个相关的类,这个类继承自java.lang.Enum. 其他大部分的表现也可以象类一样,例如继承接口,重载方法等。
- enum里的对象,都是这个类的static final的实例。
- 可以用==来比较enum实例?编译器自动为你提供equals和hashcode方法。
<T extends Enum<T>>表示T是一个enum实例。- EnumSet替代传统的int标志?
- EnumMap是一种特殊的Map,它要求key必须来自一个enum。
- 使用enum的职责链。“职责链模式”,利用enum元素的固定顺序。
- 使用enum的状态机。“状态机模式”,状态转换。
3.java的访问权限修饰
- 修饰类的成员(包括域和方法)的时候,可以使用private、包访问权限、protected、public这四种:
- private 只有在该类中使用.
- 包访问 同一包内可以访问.
- protected 同一包内可以访问,不同包内,当继承自它的类内部,也可以访问.
-
public 都可以访问。
- 修饰类的时候,只能是public或包访问权限(该类只能用于该包中)的;修饰内部类的时候,四个修饰都可以,修饰的时候,类访问权限就像修饰成员时一样,具有相同的访问权限。
- 只有包访问权限的类,在包外是访问不了的。很简单的例子,单例模式中,不仅要求构造函数是私有的,而且该类应该为public的,否则,该类在包外同样无法使用。
- 每个java文件,最多只能有一个public类(不包括内部类)。当没有public类的时候,文件的名字没有要求。
4.内部类
-
非static的内部类,拥有对外部类所有成员的访问权限(包括private)。如何做到的呢? 当某个外部类对象创建了一个内部类对象时,此内部类对象会秘密捕获一个指向外部类对象的引用。在内部类中,通过外部类.this来确定访问外部类;通过外部类的对象.new 来新建一个内部类对象。----“Selector 模式”。
- 用法:
- 内部类向上转型为接口,很方便隐藏实现细节。将实现接口的内部类设为private等,然后返回内部类的时候,向上转型为接口,即可以隐藏实现细节---外部完全访问也不用在意它。
- 在一个方法中,定义一个实现接口的内部类,并向上转型返回它。
- 在一个任意作用域内,定义一个实现接口的内部类,并向上转型返回它。
- 实现接口的匿名类:---new 一个接口的例子。实际上是,定义一个短暂的内部类,并且new 了一个出来,然后返回的时候,向上转型为接口了。
- 一个匿名类,扩展了有非默认构造函数的类。
- 一个匿名类,它执行字段初始化(匿名类没有构造函数)。
- 匿名内部类缺陷: 没有构造函数,只能实例初始化;只能扩展一个类或一个接口;
- 内部类缺陷: 不能有static数据和字段,也不能包含嵌套类。
- 匿名内部类适合用于工厂模式。
- 静态的内部类,称为嵌套类。
- 接口里,也可以放内部类哦。
- 内部类存在的原因:可以实现多重继承。
- 可以继承内部类,但无法覆盖内部类。
- 内部类标示:
Counter.class—对应接口
LocalInnerClass$1.class匿名内部类,继承Counter.classLocalInnerClass$1LocalCounter.class匿名内部类,继承LocalCounter.classLocalInnerClass.classLocalInnnerClass类
5.接口
###接口和内部类为我们提供一种将接口与实现分离的更加结构化的方法。
- 接口可以拥有域,但他们隐式的式static和final的,
- 接口中的方法,隐式的为public,也只能是public的。
- 接口能不能继承接口?可以继承接口,但必须要用extends而不是implements。
- 抽象类可以继承接口,抽象类可以继承实体类。
- 接口用于实现“策略设计模式”:一个统一的方法,接受接口当参数,来处理。
- 适配器模式: 当类库是被发现而不是创建的时候,继承既定的接口,并把不可改变的类传进构造函数,完成既定接口的实现.
- 生成遵循某个接口对象的典型方式就是“工厂模式”:调用工厂对象的方法,创建某个接口实现的对象。
-
注解
- 注解的定义和接口很像,而且也会被编译成class文件。
- 有四个元注解用于创建新的注解。
@Target用在什么地方@Retention在什么级别保存信息@Document文档.@Inherited- apt帮助程序员处理带有注解的java源代码。
-
注解处理器------?如何运行的?为什么需要Source(编译器丢弃)、和Class(在Class中,但会被VM丢弃)级别的注解?
-
复用类
复用类的三种方式:组合,继承以及代理。
代理是指: 将成员对象置于索要构造的类中,但,同时,我们在新类中暴露该成员对象的所有方法。
- 父类构造函数总会在第一行被调用。子类构造函数,第一行如果不是显示调用父类构造函数的话,会自动调用父类默认构造函数(即父类的无参构造函数)。这句话的潜台词也是:如果父类重载了默认无参构造函数,但是没有重写默认无参构造函数,编译器会报错。
继承还是组合?====继承比组合的惟一优势是可以向上转型?否则尽量使用组合。
-
关于final
final数据:
- 编译期常量。
- 运行时被初始化,而你不希望它被改变。
final参数,用于向匿名内部类传递数据。
final方法:禁止子类覆盖该方法。另外,所有private的方法都隐式指定为final的.
final类:禁止继承该类。
-
多态:
1.在构造函数里,尽量少调用重写的函数,由可能由意想不到的结果---与构造顺序有关。 2.重写函数的时候,可以将返回类型-协变。即子类重写函数,可以返回更具体的类。
-
持有对象
Arrays和Collections是Utils类。
- 使用Iterator访问容器。提供了通用的访问对象的方法。iterator()返回Iterator对象,使用next()获得序列下一个元素,hasNext()检查时候还有元素,remove()将迭代器新近返回的元素删除。
- ListIterator是Iterator的子类型,提供双向移动的能力。
- LinkedList还添加了可以当作Stack、Queue或双端队列的方法。
- PriorityQueue,优先级Queue。
- Iterable()接口,包含一个能产生Iterator的iterator()的方法,并且iterable接口可以被foreach用于在序列中移动。
19.异常
###设计异常的初衷应该是,把异常的处理集中处理,降低错误代码的复杂度。
- Throwable对象,是异常的根类。主要有三种子类,Error、Exception(检查异常)、RunTimeException(非检查异常)。
- 一些方法printStackTrace()、getStackTrace(). fillInStackTrace()构造异常。
- 异常链。Exception和RuntimeException提供Cause的构造器。其他类型的异常链接起来的方法应该用initCause().
- finally用于恢复内存之外的资源到初始状态。包括打开的文件、网络连接、数据库连接之类的。另外,finally只是在return 一霎那之前,先执行finally里面的代码。但注意System.exit()不会预先执行finally。
- 异常会丢失: finally代码的异常,会覆盖try里面的异常。
- 基本规则:
在创建需要清理的对象之后,立即进入一个try-finally语句块。但请注意,清除资源的代码,也有可能会抛出异常。应该仔细考虑所有可能性,并保证正确处理每种情况。
- 基本规则:在知道该如何处理的情况下才捕获异常。
-
CheckedExcepton vs UnCheckedException。 CheckedException导致错误处理代码到处都是而且往往是不知道如何处理。尽量把“被检查的异常”转换为“不检查的异常”
-
String
String 字符串常量; StringBuffer 字符串变量(线程安全); StringBuilder 字符串变量(非线程安全)
- 注意使用StringBuilder来提高效率.
- 字符串格式化,使用Formatter类。但String.format()这个静态方法更方便。
- 正则表达式。Pattern、Matcher。String对象的split方法也是正则的一个表现。---复习正则表达式。
-
RTTI
主要两种使用方式:假定我们在编译时已经知道所有的类型;反射机制;
假设有个Product的类.
- Product.class 与 Class.forName(“Product”). Class.forName()会导致类加载(即静态域被初始化)而Product.class不会。但是请注意编译期常量不要对类进行初始化就可以被读取。
- 泛化的Class引用:方便编译期检查。
Class<? extends Product>等。 - instanceof 关键字 与 Class.isInstance()方法。
- Class.getMethods与Class.getConstructors
- “代理模式”:原有类和代理类都实现接口。作用在哪里??
- 反射可以破坏封装。可以用包访问权限,但是如果对方知道名字,仍然可以用基类调用想隐藏的方法,即使是private的方法或域(使用Method.setAccessible(true))
22.泛型
- 泛型类和泛型接口用法一样。
class Product<T>orinterface Product<T>, 不用指明具体类型.接口泛型应用于“工厂方法”设计模式,例子如下:
public interface Generator<T> { T next(); } - 泛型方法: 将泛型参数列表放在返回值之前:
public <T> T someMethod(T x).
使用原则:尽量用泛型方法而不是泛型类。另外,static方法无法访问泛型类的类型参数。
- 显示的类型说明:在点操作服与方法命之间插入尖括号。
- 擦除:
- 在泛型代码内部,无法获取任何有关泛型参数类型的信息。所有的T都只能当Object类型变量来使用。譬如说:
ArrayList<String>与ArrayList<Integer>是相同的类型。为了避免擦除,要使用关键字extends语法。 - 泛型不能用于显式地引用运行时类型地操作之中,例如转型、instanceof、和new 表达式。即new T()编译器会报错地。但这些,都可以通过传入类型地class对象来补偿。譬如使用Class.isInstance()和Class.newInstance()来补偿。
-
不能创建泛型数组. 使用ArrayList来代替泛型数组
-
边界: Java泛型重用extends用来再泛型参数类型上设置限制条件。
class Colored<T extends Dimension & HasColor> - 通配符:假设Apple是Fruit的子类。
List<Fruit> flist = new ArrayList<Apple>()这个编译不会通过. - 子类型边界:
List<? extends Fruit> flist = new ArrayList<Apple>()就没有问题。但是这样一来flist.add(new Fruit())和flist.add(new Apple())都会报错。但是,Fruit f = flist.get(0)将运行正确。 - 超类型边界:设置可以用
List<? super T>
List<? super Apple>. 这个时候,就可以使用add方法了。 - 根据如何能够向一个泛型类型“写入”以及如何能够从一个泛型类型中“读取”,来着手思考子类型和超类型边界。
PS:Java里的擦除真是件相当恶心的事情。
-
IO流
- 首先1.0引入InputStream和OutputStream,1.1引入Reader和Writer。后来,默认认为Stream用于字节的读写,Reader\Writer用于字符的读写,并且很好的支持了Unicode。Reader和writer一般都需要提供编码方式,如果不提供默认选择的事UTF-8。
- 对于InputStream的继承来说,有ByteArrayInputStream(字节数组)、StringBufferInputStream(字符串)、FileInputStream(文件读取)以及用于扩展的FilterInputStream。 FilterInputStream,采用装饰器模式(继承那个类,并且包含那个类的实例),在内部更改InputStream的行为方式。主要就有两个子类,DataInputStream(基本数据类型的读取)和BufferInputStream(是否缓冲)。
- InputStreamReader可以把InputStream转化成Reader(适配器模式),OutputStreamWriter可以把OutputStream转化成Writer。
| Stream | Reader/Writer |
|---|---|
| FileInputStream | FileReader |
| FileOutputStream | FileWriter |
| StringBufferInputStream(弃用) | StringReader |
| StringWriter | |
| ByteArrayInputStream | CharArrayReader |
| ByteArrayOutputStream | CharArrayWriter |
| BufferedInputStream | BufferedReader(带有readLine方法) |
| BufferedOutputStream | BufferedWriter |
- DataInputStream,你完全可以使用DataInputStream,不过,如果你要使用Readline方法,就应该使用BufferedReader这个类。
-
available方法,工作方式随着读取的媒介类型不同而不同,请注意使用。
-
NIO
- ByteBuffer:唯一直接与通道交互的缓冲器。FileInputStream和FileOutputStream都可以产生FileChannels。
- Channel接受ByteBuffer对象用于读写。同时记住,要调用buffer.flip()准备,做好让别人去读取字节的准备。最后,再次使用时候,调用clear清出上次的数据。
- FileChannel存在transferTo和transFrom方法用于将一个通道与另外一个通道相连。
-
通过asCharBuffer、asShortBuffer等方法来获取视图缓冲区,从而来插入基本类型数据。
ByteBuffer .asCharBuffer.put("howdy");ByteBuffer.getChar(); - ByteBuffer.order()来改变字节排序方式。
ByteOrder.BIG_ENDIAN、ByteOrder.LITTLE_ENDIAN. - MappedByteBuffer. 内存映射文件。它还可以映射某个大文件的较小的部分。
- 文件加锁。Java的文件加锁直接映射到本地操作系统的加锁工具。 FileChannel可以通过tryLock或Lock来对文件加锁,而通过release来释放锁。 而且lock和trylock还可以进行文件的部分加锁。
- 文件的压缩。CheckedInputStream、CheckedOutputStream、ZipOutputStream、ZipInputStream、GZIPOutputStream、 GZIPInputStream。
- 对象的序列化:
- Java的序列化:只要对象实现了Serializable借口,对象的序列化处理就会非常简单。配合ObjectOutputStream、ObjectInputStream.
- transient关键字:避免属性被序列化。
- 实现Externalizable接口。
- 重写该类的writeObject和readObject方法.
- XML
-
Preference:windows是存在注册表里。
-
并发
- Runnable与Thread等创建线程的方法。实际上,如果Thread的Runnable对象不存在,就会运行Thread的run方法。可以理解这两张创建线程的方法,其实就是由Thread.java这个类实现来决定的。
- 推荐使用Executor---ExecutorService来管理你的线程对象。
- 如果你想从任务中产生返回值。使用Callable
而不是Runnable,并且使用ExecutorService .submit()方法调用它。 这个函数将返回一个Future对象,你可以通过isDone()查看是否任务完成,然后通过get()来获取结果。当然如果直接调用get()将会阻塞当前线程。 - 线程yeild(),sleep(),线程优先级:注意是在线程的run()方法中设置线程优先级。
- 后台线程:setDaemon(),一定要在线程start()之前设置。
后台线程,是指在程序运行的时候,在后台提供一种通用的服务的线程,并且这种线程不属于程序不可或缺的一部分。
一旦所有非后台线程都结束了,程序中的后台线程都将被杀死,程序停止运行。 也就是说后台线程中的finally里,有可能不会执行。 - join方法:等到该线程结束后,当前线程才能继续执行。
- 深入理解interrupted。其实是引入一种退出线程的一种机制。你同样也可以在自己耗时的操作里,添加自己的判断代码,看时候需要退出当前线程。
- 你不能捕获从线程中逃逸的异常。在SE5,可以使用Executor来解决这个问题。 采用Thread.setUncaughtExceptionHandler()方法。
- 线程同步问题。synchronized 可以保证 可见性和有序性。然后再配合wait和notify来达到线程同步的效果。
为了满足更复杂的要求。有必要引入Lock以及Condition,Semaphore(允许n个任务同时访问这些资源)等类。能更好更方便的解决,临界区资源的访问等问题。 主要是可重入等问题。
具体可参见这个网站: http://tutorials.jenkov.com/java-concurrency/index.html
另外两本书籍:
Java concurrency in Practice
Concurrent Programming in Java, Second Edition。
-
jvm结构:四部分
classloader —- execution engine —- native interface ——runtime data area
runtime data area:
1.java stack:是Java程序的运行区,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放。栈帧中主要保存3类数据:本地变量(Local Variables),包括输入参数和输 出参数以及方法内的变量
2.native method stack:本地方法栈
3.heap分为三部分:类加载器读取了类文件后,需要把类、方法、 常变量放到堆内存中.
|区域|作用|
|—–|—–|
|Permanent Space 永久存储区 |永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运 行环境必须的类信息.|
|Tenure generation space养老区 |养老区用于保存从新生区筛选出来的JAVA对象,一般池对象都在这个区域活跃. |
|Young Generation Space 新生区| 新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生 区又分为两部分:伊甸区(Eden space)和幸存者区(Survivor pace),所有的类都是在伊甸区被new出来的。)
- Method Area: 方法区是被所有线程共享,该区域保存所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义.
- PC Register:每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码,由执行引擎读取下一条指令。
JVM的每个实例都有一个它自己的方法域和一个堆,运行于JVM内的所有的线程都共享这些区域;当虚拟机装 载类文件的时候,它解析其中的二进制数据所包含的类信息,并把它们放到方法域中;当程序运行的时候, JVM把程序初始化的所有对象置于堆上;而每个线程创建的时候,都会拥有自己的程序计数器和 Java栈,其中 程序计数器中的值指向下一条即将被执行的指令,线程的Java栈则存储为该线程调用Java方法的状态;本地方 法调用的状态被存储在本地方法栈,该方法栈依赖于具体的实现。
性能监测工具:JDK里的。JConsole,VirtualVM