此篇主要记录 (搬运) 的是 Java 中一些常见概念的理解, 大致内容如下
final,finally,finalize 的区别
Java 的传值或者传引用的理解
重写 Override 和重载 Overload 的理解
组合和继承的理解
抽象类 abstract class 与接口 interface 的理解
静态嵌套类 (Static Nested Class) 和内部类 (Inner Class) 的不同
==,equals(),hashCode()和 compareTo()
常量池, String(不可变类),StringBuffer,StringBuilder
对 static,public static void main (String[] args)的理解
Java 程序的初始化顺序
抽象的 (abstract) 方法是否可同时是静态的(static), 是否可同时是本地方法(native), 是否可同时被 synchronized 修饰
静态方法是否能被重写
final,finally,finalize 的区别
1. final: 修饰符 (关键字) 用于声明属性, 方法和类, 分别表示属性不可变, 方法不可覆盖, 类不可继承.
final 类: 不能派生出子类, 即不能作为父类被继承. 因此一个类不能既被声明为 abstract, 又被声明为 final.
final 变量: 在声明时给定初值, 后续不能改变其初值 (基本类型) 或者引用(但是引用的对象的属性可以变更).
final 方法: 只能使用, 不能重载.
2. finally: 异常处理语句结构的一部分, 表示总是执行.
在异常处理时提供 finally 块来执行任何清除操作. 如果抛出一个异常, 那么相匹配的 catch 子句就会执行, 然后控制就会进入 finally 块, 除非 JVM 被关闭(System.exit(1)), 通常用作释放外部资源(不会被垃圾回收器回收的资源).
3. finalize:Object 类的一个方法, 在垃圾收集器执行的时候会调用被回收对象的此方法, 可以覆盖此方法提供垃圾收集时的其他资源回收, 例如关闭文件等.
Java 允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作. 这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的.
它是在 Object 类中定义的, 因此所有的类都继承了它. 子类覆盖 finalize()方法以整理系统资源或者执行其他清理工作.
注意, 无法确切地保证垃圾回收器何时调用该方法, 也无法保证调用不同对象的方法的顺序.
扩展: JDK 中哪些类是不能继承的?
用 final 修饰的类. 一般比较基本的类型或防止扩展类无意间破坏原来方法的实现的类型都应该是 finally 的, 在 JDK 中, String 和 StringBuffer 等都是基本类型, 所以 String,StringBuffer 等类是不能继承的.
Java 的传值或者传引用的理解
java 方法传递的参数, 有基本类型和引用类型 2 种.
基本类型: 方法传递的是基本类型参数值的一个副本, 在方法中对这个参数副本进行的任意变更, 都不会影响原变量值.
引用类型: 方法传递的是该引用的一个副本值, 副本和原参数指向的是同一个对象, 在方法内部对该副本所引用的对象内容进行的任何改变, 都会产生影响, 如果是将副本指向另一个对象引用, 则之后所做的操作对原引用对象不会产生任何影
重写 Override 和重载 Overload 的理解
1. 覆盖 / 重写(override)
子类对父类 (或接口) 方法的重新实现.
覆盖方法的标志必须要和被覆盖方法的标志完全匹配, 才能达到覆盖的效果;
覆盖的方法的返回值必须和被覆盖的方法的返回一致;
覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致, 或者是其子类;
被覆盖的方法不能为 private, 否则在其子类中只是新定义了一个方法, 并没有对其进行覆盖.
2. 重载 overload
同个类里面, 相同函数名不同参数个数 / 类型的方法.
在使用重载时只能通过不同的参数样式. 例如, 不同的参数类型, 不同的参数个数, 不同的参数顺序;
不能通过访问权限, 返回类型, 抛出的异常进行重载;
方法的异常类型和数目不会对重载造成影响;
对于继承来说, 如果某一方法在父类中是访问权限是 priavte, 那么就不能在子类对其进行重载, 如果定义的话, 也只是定义了一个新方法, 而不会达到重载的效果.
[参考 1:java 中重载和覆盖 (又称重写) 的区别] http://blog.csdn.net/sweetna/article/details/4360048
[参考 2:java 中重载与重写的区别] http://www.cnblogs.com/bluestorm/archive/2012/03/01/2376236.html
组合和继承的理解
在继承结构中, 父类的内部细节对于子类是可见的. 所以我们通常也可以说通过继承的代码复用是一种白盒式代码复用.(如果基类的实现发生改变, 那么派生类的实现也将随之改变. 这样就导致了子类行为的不可预知性;)
组合是通过对现有的对象进行拼装 (组合) 产生新的, 更复杂的功能. 因为在对象之间, 各自的内部细节是不可见的, 所以我们也说这种方式的代码复用是黑盒式代码复用.(因为组合中一般都定义一个类型, 所以在编译期根本不知道具体会调用哪个实现类的方法)
继承, 在写代码的时候就要指名具体继承哪个类, 所以, 在编译期就确定了关系.(从基类继承来的实现是无法在运行期动态改变的, 因此降低了应用的灵活性.)
组合, 在写代码的时候可以采用面向接口编程. 所以, 类的组合关系一般在运行期确定.
组合关系 | 继承关系 |
---|---|
优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立 | 缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性 |
优点:具有较好的可扩展性 | 缺点:支持扩展,但是往往以增加系统结构的复杂度为代价 |
优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象 | 缺点:不支持动态继承。在运行时,子类无法选择不同的父类 |
优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口 | 缺点:子类不能改变父类的接口 |
缺点:整体类不能自动获得和局部类同样的接口 | 优点:子类能自动继承父类的接口 |
缺点:创建整体类的对象时,需要创建所有局部类的对象 | 优点:创建子类的对象时,无须创建父类的对象 |
抽象类 abstract class 与接口 interface 的理解
广义上的抽象类 (Java 中包括抽象类或者接口) 往往用来表征我们在对问题领域进行分析, 设计中得出的抽象概念, 是对一系列看上去不同, 但是本质上相同的具体概念的抽象. 正是因为抽象的概念在问题领域没有对应的具体概念, 所以用以表征抽象概念的抽象类是不能够实例化的.
接口可以继承接口, 而且支持多重继承. 抽象类可以实现 (implements) 接口, 抽象类可继承具体类也可以继承抽象类.
OCP 原则 (Open-Closed Principle) 在 java 中的体现很大程度上就是接口和抽象类的设计, 即抽象化设计.
抽象类在 Java 语言中表示的是一种继承关系, Java 中是单继承 (普通类和抽象类) 和多实现(接口).
在抽象类中可以有自己的数据成员, 数据成员的值可以重新定义或者赋值, 也可以有非 abstarct 的成员方法, 即可以有具体的方法; 而在接口中, 只能够有静态的不能被修改的数据成员(也即 static final, 而且必须有初始值, 不过在接口中一般不定义数据成员), 而且所有的成员方法默认都是 public abstract.
实现抽象类和接口的类必须实现其中的所有抽象方法. 抽象类中可以有非抽象方法. 接口中则不能有实现方法. 如果子类没有实现父类的抽象方法, 则必须将子类也定义为为 abstract 类.
接口中不能含有静态代码块以及静态方法, 而抽象类可以有静态代码块和静态方法
接口的方法都是 public 的, 抽象类的方法可以是 public,protected,private 或者默认的 package, 抽象方法必须为 public 或者 protected(因为如果为 private, 则不能被子类继承, 子类便无法实现该方法), 缺省情况下默认为 public.
抽象类可以定义构造函数, 接口却不能.
抽象类表示对事物 (属性和行为) 的抽象, 即对类的抽象, 而接口是对行为的抽象, 是一种行为规范或者行为约定.
抽象类和接口所反映出的设计理念不同. 其实抽象类表示的是 "is-a" 关系, 接口表示的是 "like-a" 关系.
接口
接口用于描述系统对外提供的所有服务, 因此接口中的成员常量和方法都必须是公开 (public) 类型的, 确保外部使用者能访问它们;
接口仅仅描述系统能做什么, 但不指明如何去做, 所以接口中的方法都是抽象 (abstract) 方法;
接口不涉及和任何具体实例相关的细节, 因此接口没有构造方法, 不能被实例化, 没有实例变量, 只有静态 (static) 变量;
接口的中的变量是所有实现类共有的, 既然共有, 肯定是不变的东西, 因为变化的东西也不能够算共有. 所以变量是不可变 (final) 类型, 也就是常量了.
接口中不可以定义变量. 接口中的属性必然是常量, 只能读不能改, 这样才能为实现接口的对象提供一个统一的属性.
接口可以看成是对行为约定或者协议等的更高层次的抽象. 对修改关闭, 对扩展开放, 接口是对开闭原则的一种体现. 接口的方法默认是 public abstract; 接口中只能定义常量(final 修饰). 所以接口的属性默认是 public static final 常量, 且必须赋初值. 注意: final 和 abstract 不能同时出现.
参数 | 抽象类 | 接口 |
---|---|---|
默认的方法实现 | 可以有默认的方法实现 | 接口是完全抽象的。它根本不存在方法的实现 |
实现 | 子类使用 extends 关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明为 abstract 的方法的实现。 | 子类使用关键字 implements 来实现接口。它需要提供接口中所有声明的方法的实现 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
与正常 Java 类的区别 | 除了不能实例化抽象类之外,它和普通 Java 类没有任何区别 | 接口是更加抽象的类型 |
访问修饰符 | 抽象方法可以有 public、protected 和 default 这些修饰符 | 接口方法默认修饰符是 public。你不可以使用其它修饰符。 |
main 方法 | 抽象类可以有 main 方法并且可以运行它 | 接口没有 main 方法,因此不能运行它。 |
多继承 | 抽象方法可以继承一个类和实现多个接口 | 接口只可以继承一个或多个其它接口 |
速度 | 它比接口速度要快 | 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。 |
添加新方法 | 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 | 如果你往接口中添加方法,那么你必须改变实现该接口的类。 |
[参考 1: 详细解析 Java 中抽象类和接口的区别] http://www.cnblogs.com/beanmoon/archive/2012/12/06/2805221.html
[参考 2:Java 抽象类与接口的区别] http://www.importnew.com/12399.html
静态嵌套类 (Static Nested Class) 和内部类 (Inner Class) 的不同
以下摘自Java 编程思想
Static Nested Class 是被声明为静态 (static) 的内部类, 普通的内部类对象隐式地保存了一个引用, 只想创建它的外围类对象. 但是, 内部类是 static(即嵌套类)时, 意味着:
要创建嵌套类的对象, 并不需要依赖其外围类的对象的创建, 而通常的内部类需要在外部类实例化后才能实例化
不能从嵌套类的对象中访问非静态的外围类对象.
普通内部类的字段与方法, 只能放在类的外部层次上, 所以普通的内部类不能有 static 数据和 static 字段, 也不能包含嵌套类. 但是嵌套类可以包含所有这些东西.
==,equals(),hashCode()和 compareTo()
==: 是运算符, 用来比较两个数据是否相等, 比较的是变量所代表的内存地址是否一样
equals: 是 object 类提供的的一个方法, equals 是否相等取决于类中的这个方法是如何实现[重写] 的. String 类中的 equals 方法是比较值是否相等; object 类是 Java 中类层次结构的根类, Java 中所有的类都默认是 object 的子类, 数组类型也是 object 的子类, Object 类默认的 equals 实现是使用 == 进行判断, 即比较是否指向同一个地址, 或者基本类型的值是否相等.
- 1 == 1.0;// 成立
- s1="abc",s2="abc"; //(s1==s2)
equals()的性质
自反性 (reflexive): 对于任意不为 null 的引用值 x,x.equals(x) 一定是 true.
对称性 (symmetric): 对于任意不为 null 的引用值 x 和 y, 当且仅当 x.equals(y) 是 true 时, y.equals(x)也是 true.
传递性 (transitive): 对于任意不为 null 的引用值 x,y 和 z, 如果 x.equals(y) 是 true, 同时 y.equals(z)是 true, 那么 x.equals(z)一定是 true.
一致性 (consistent): 对于任意不为 null 的引用值 x 和 y, 如果用于 equals 比较的对象信息没有被修改的话, 多次调用时 x.equals(y) 要么一致地返回 true 要么一致地返回 false.
对于任意不为 null 的引用值 x,x.equals(null)返回 false.
需要注意的是当 equals()方法被 override 时, hashCode()也要被 override. 按照一般 hashCode()方法的实现来说, 相等的对象, 它们的 hash code 一定相等.
hashCode()方法
在一个 Java 应用的执行期间, 如果一个对象提供给 equals 做比较的信息没有被修改的话, 该对象多次调用 hashCode()方法, 该方法必须始终如一返回同一个 integer.
如果两个对象根据 equals(Object)方法是相等的, 那么调用二者各自的 hashCode()方法必须产生同一个 integer 结果.
并不要求根据 equals(java.lang.Object)方法不相等的两个对象, 调用二者各自的 hashCode()方法必须产生不同的 integer 结果. 然而, 程序员应该意识到对于不同的对象产生不同的 integer 结果, 有可能会提高 hash table 的性能.
hashCode 被用于 hash tables, 例如 HashMap 的比较中, 判断元素是否重复时, 先使用 hashCode 判断是否相等, 再利用 equals 来判断
compareTo()方法
进行排序时提供的比较大小的方法.
a.compareTo(b)>0 表示 a>b,a.compareTo(b)=0 表示 a=b,a.compareTo(b)>0 表示 a < b
[参考: Java 提高篇 --equals()与 hashCode()方法详解] http://www.cnblogs.com/Qian123/p/5703507.html
常量池, String(不可变类),StringBuffer,StringBuilder
常量池部分
1. class 文件常量池: java 的 class 文件中有一部分重要的组成称为常量池, 常量池主要存放两大类常量: 字面量 (Literal) 和符号引用(Symbolic Reference). 字面量比较接近于 Java 语言层面的常量概念, 如文本字符串, 声明为 final 的常量值等, 而符号引用则属于编译原理方面的概念, 包括了下面三类常量:
类和接口的全限定名(Fully Qualified Name)
字段的名称和描述符(Descriptor)
方法的名称和描述符
(参考周志明深入理解 Jva 虚拟机 第 2 版第 6 章)
2. 运行时常量池: class 文件常量池这部分内容将在类加载后进入方法区的运行时常量池存放, 运行时常量池还包含了翻译出来的直接引用. 虚拟机在加载 class 文件的时候进行动态连接. 当虚拟机运行时, 从常量池获得对应的符号引用, 再在类创建时或运行时解析, 翻译到具体的内存地址中, 即把符号引用转换为直接引用. 运行时常量池也包含了运行期间创建的新的常量(常见的 String 的 intern 方法), 即具备动态性.
常见的有字符串常量池和整型常量池(-128-127), 可以实现对象的共享, 避免频繁的创建和销毁.\
[参考 1:Java 常量池理解与总结] http://www.jianshu.com/p/c7f47de2ee80
[参考 2:Java 中几种常量池的区分] http://www.cnblogs.com/holos/p/6603379.html
当然, 重点去参考深入理解 Jva 虚拟机 第 2 版第 2 章, 第 6 章和第 7 章的内容(有待进一步学习).
String
不可变类型 (immutable), 是线程安全, 内部使用 private final char value[] 进行实现, 除非是常量, 否则每次创建时都会重新生成一个全新的字符串, 效率较低.
String 为什么设计为不可变: 设计考虑, 效率优化, 安全性, 字符串常量池, hashcode 缓存加快处理, 多线程安全等方面
StringBuffer
可变, 而且是线程安全的, 效率较 StringBuilder 低, 需要维护线程安全, 加锁, 同步.
StringBuilder
可变, 但是线程不安全.
StringBuffer 和 StringBuilder 都继承了 AbstractStringBuilder, 而 StringBuffer 大部分方法都是 synchronized, 也就是线程安全的, 而 StringBuilder 不是. 所以, 我们查看 API 可以知道, StringBuilder 可以操作 StringBuffer, 但是 StringBuffer 不可以操作 StringBuilder, 这也是线程的原因;
因 StringBuffer 要维持同步锁, 消耗部分资源, 因此效率上会相对低.
- String 类型和 StringBuffer 的主要性能区别: String 是不可变的对象, 因此在每次对 String 类型进行改变的时候, 都会生成一个新的 String 对象, 然后将指针指向新的 String 对象, 所以经常改变内容的字符串最好不要用 String, 因为每次生成对象都会对系统性能产生影响, 特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作, 性能就会降低.
- 使用 StringBuffer 类时, 每次都会对 StringBuffer 对象本身进行操作, 而不是生成新的对象并改变对象引用. 所以多数情况下推荐使用 StringBuffer, 特别是字符串对象经常改变的情况下.
使用策略:
(1)基本原则: 如果要操作少量的数据, 用 String; 单线程操作大量数据, 用 StringBuilder; 多线程操作大量数据, 用 StringBuffer.
(2)不要使用 String 类的 "+" 来进行频繁的拼接, 因为那样的性能极差的, 应该使用 StringBuffer 或 StringBuilder 类, 这在 Java 的优化上是一条比较重要的原则.
(3)为了获得更好的性能, 在构造 StringBuffer 或 StringBuilder 时应尽可能指定它们的容量. 当然, 如果你操作的字符串长度 (length) 不超过 16 个字符就不用了, 当不指定容量
(capacity)时默认构造一个容量为 16 的对象. 不指定容量会显著降低性能.
(4)StringBuilder 一般使用在方法内部来完成类似 "+" 功能, 因为是线程不安全的, 所以用完以后可以丢弃. StringBuffer 主要用在全局变量中.
(5)相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升, 但却要冒多线程不安全的风险. 而在现实的模块化编程中, 负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行, 因此: 除非确定系统的瓶颈是在 StringBuffer 上, 并且确定你的模块不会运行在多线程模式下, 才可以采用 StringBuilder; 否则还是用 StringBuffer.
[参考 1:String,StringBuffer, 与 StringBuilder 的区别] http://www.cnblogs.com/sevenlin/p/sevenlin_StringBuffer_StringBuilder20150806.html
[参考 2:Java:String,StringBuffer 和 StringBuilder 的区别] http://blog.csdn.net/kingzone_2008/article/details/9220691
对 static,public static void main (String[] args)的理解
static 的理解
static 变量: 静态变量或者类变量
static 方法: 静态方法或者类方法
static 代码块: 自动执行
static 类: 只能是静态内部类
public static void main (String[] args)的理解
public 关键字: 表示这是一个可由外部调用 (主要是虚拟机调用) 的方法.
static: 表示属于类.
void: 表示该方法没有返回值
main()方法是 java 引用程序的入口方法, 其参数是一个 String 对象的数组, 这是 Java 编译器要求必须这样做的, args 要用来存储命令行参数.
Java 程序的初始化顺序
Java 程序初始化顺序(父类静态变量, 父类静态代码块, 子类静态变量, 子类静态代码块, 父类非静态变量, 父类非静态代码块, 父类构造函数, 子类非静态变量, 子类非静态代码块, 子类构造函数)
抽象的 (abstract) 方法是否可同时是静态的(static), 是否可同时是本地方法(native), 是否可同时被 synchronized 修饰
都不能. 抽象方法需要子类重写, 而静态的方法是无法被重写的, 因此二者是矛盾的. 本地方法是由本地代码 (如 C 代码) 实现的方法, 而抽象方法是没有实现的, 也是矛盾的. synchronized 和方法的实现细节有关, 抽象方法不涉及实现细节, 因此也是相互矛盾的.
静态方法是否能被重写
属于类, 不能被继承, 不能重写.
来源: http://www.bubuko.com/infodetail-2582381.html